+ timeout(33);
+ game_begin(&state->board);
+}
+
+#if HAVE_CURSES_MOUSE_SUPPORT
+/*
+ * Given x, y as screen coordinates, record which (if any) toolbar function
+ * label is at that position, to be performed later.
+ *
+ * If no function is indicated, returns zero. Otherwise, returns non-zero.
+ */
+static int press_toolbar(struct app_state *state, int x, int y)
+{
+ return state->toolbar_click = curs_toolbar_mouse_func(state, x, y);
+}
+
+/*
+ * Given x, y as screen coordinates, perform the toolbar action.
+ *
+ * Perform the action previously recorded by press_toolbar, if and only if
+ * the x, y screen coordinates correspond to the same function.
+ */
+static void release_toolbar(struct app_state *state, int x, int y)
+{
+ int func = curs_toolbar_mouse_func(state, x, y);
+
+ if (func && state->toolbar_click == func) {
+ curs_execute_function(state, func);
+ }
+
+ state->toolbar_click = 0;
+}
+
+/*
+ * Given x, y as screen coordinates, determine which (if any) game tile is
+ * at that position.
+ *
+ * If there is no such tile, performs no action and returns 0.
+ *
+ * Otherwise, attempts to move that tile and returns non-zero.
+ */
+static int press_tile(struct app_state *state, int x, int y)
+{
+ int game_x, game_y, tile_w, tile_h;
+ uint_fast32_t cursor_mask, move_mask;
+
+ getbegyx(state->gamewin[PLAYWIN_AREA], game_y, game_x);
+ getmaxyx(state->gamewin[PLAYWIN_TILEBORDER], tile_h, tile_w);
+ tile_w += tile_w & 1;
+
+ /* special case the left spacer column */
+ if (x == game_x+1)
+ x++;
+
+ if (x < game_x+2 || (x -= game_x+2)/5 >= tile_w)
+ return 0;
+ if (y < game_y+1 || (y -= game_y+1)/5 >= tile_h)
+ return 0;
+
+ /* OK, selected a tile. */
+ x /= tile_w;
+ y /= tile_h;
+
+ /* Disable the keyboard cursor due to mouse action */
+ cursor_mask = state->cursor < 0 ? -1 : 1ul << state->cursor;
+ state->cursor = -1;
+
+ move_mask = do_move(state, x, y);
+ if ((cursor_mask & move_mask) == 0) {
+ curs_redraw_game(state, cursor_mask);
+ doupdate();
+ }
+
+ return 1;
+}
+
+static void do_mouse(struct app_state *state)
+{
+ unsigned long bstate;
+ int x, y;
+
+#if HAVE_CURSES_GETMOUSE_NCURSES
+ MEVENT mev;
+
+ if (getmouse(&mev) == ERR)
+ return;
+
+ x = mev.x, y = mev.y;
+ bstate = mev.bstate;
+#elif HAVE_CURSES_REQUEST_MOUSE_POS
+ request_mouse_pos();
+ x = MOUSE_X_POS;
+ y = MOUSE_Y_POS;
+
+ bstate = 0;
+
+#define set_bstate_helper(button) do { if (BUTTON_CHANGED(button)) \
+ switch (BUTTON_STATUS(button)) { \
+ case BUTTON_RELEASED: bstate |= BUTTON ## button ## _RELEASED; break; \
+ case BUTTON_PRESSED: bstate |= BUTTON ## button ## _PRESSED; break; \
+ } \
+} while (0);
+
+ set_bstate_helper(1);
+ set_bstate_helper(3);
+#endif
+ if (bstate & BUTTON3_PRESSED) {
+ state->view_goal_on_game = 1;
+ curs_redraw_game(state, game_check_goal(&state->board));
+ doupdate();
+ }
+
+ if (bstate & BUTTON3_RELEASED) {
+ state->view_goal_on_game = 0;
+ curs_redraw_game(state, game_check_goal(&state->board));
+ doupdate();
+ }
+
+ /* Ignore button1 if holding button3 to view goal */
+ if (state->view_goal_on_game & 1)
+ return;
+
+ if (bstate & BUTTON1_PRESSED) {
+ if (press_toolbar(state, x, y));
+ else if (press_tile(state, x, y));
+ }
+
+ if (bstate & BUTTON1_RELEASED) {
+ release_toolbar(state, x, y);
+ }
+}
+#endif /* HAVE_CURSES_MOUSE_SUPPORT */
+
+static void do_move_cursor(struct app_state *state, int c)
+{
+ uint_fast32_t mask = 0;
+
+ if (state->cursor < 0) {
+ /* Cursor was hidden; reset it */
+ do_reset_cursor(state);
+ } else {
+ mask = 1ul << state->cursor;
+ }
+
+ if (state->view_goal_on_game) {
+ state->view_goal_on_game = 0;
+ mask |= game_check_goal(&state->board);
+ }
+
+ switch (c) {
+ case KEY_UP:
+ if ((state->cursor -= 5) < 0)
+ state->cursor += 25;
+ break;
+ case KEY_DOWN:
+ if ((state->cursor += 5) >= 25)
+ state->cursor -= 25;
+ break;
+ case KEY_LEFT:
+ if ((state->cursor -= 1) % 5 == 4 || state->cursor < 0)
+ state->cursor += 5;
+ break;
+ case KEY_RIGHT:
+ if ((state->cursor += 1) % 5 == 0)
+ state->cursor -= 5;
+ break;
+ }
+
+ curs_redraw_game(state, mask | 1ul << state->cursor);
+ doupdate();
+}
+
+static void do_keystroke(struct app_state *state, int c)
+{
+ int last_input = state->last_input;
+ state->last_input = c;
+
+ switch (c) {
+ case KEY_DOWN: case KEY_UP: case KEY_LEFT: case KEY_RIGHT:
+ do_move_cursor(state, c);
+ break;
+ case '\t':
+ state->view_goal_on_game ^= 2u;
+ curs_redraw_game(state, game_check_goal(&state->board));
+ doupdate();
+ break;
+ case ' ':
+ if (!(state->view_goal_on_game & 1) && state->cursor >= 0)
+ do_move(state, state->cursor%5, state->cursor/5);
+ break;
+ }
+
+ /* ESC+# keys */
+ if (last_input == '\33' && c >= '0' && c <= '9') {
+ curs_execute_function(state, (c -= '0') == 0 ? 10 : c);
+ }
+
+ /* F# keys */
+ if (c >= KEY_F(1) && c <= KEY_F(10)) {
+ curs_execute_function(state, c - KEY_F0);
+ }
+}
+
+/* One iteration of main input loop */
+void do_mainloop(struct app_state *state)
+{
+ int c = getch();
+
+ switch (c) {
+#ifdef KEY_RESIZE
+ case KEY_RESIZE:
+ setup_mainwin(state);
+ clear();
+ refresh();
+ curs_redraw_game(state, -1);
+ curs_redraw_goal(state, -1);
+ curs_draw_toolbar(state);
+ update_timer(state, state->timer_ms);
+ doupdate();
+ break;
+#endif
+#if HAVE_CURSES_MOUSE_SUPPORT
+ case KEY_MOUSE:
+ do_mouse(state);
+ break;
+#endif
+ default:
+ do_keystroke(state, c);
+ case ERR:;
+ }
+
+ if (state->board.x <= 4) {
+ update_timer(state, game_elapsed(&state->board));
+ doupdate();