WINDOW_MAX,
};
+/* Colour pair enumeration */
+enum {
+ /* Pairs 1-6 correspond to tile colours */
+ RR_COLOUR_CURSOR = TILE_MAX, // black on black, for the cursor
+ RR_COLOUR_TOOLBAR, // cyan on black (use reverse video)
+ RR_COLOUR_MAX
+};
+
static struct app_state {
struct board board;
WINDOW *gamewin[WINDOW_MAX], *goalwin[WINDOW_MAX];
- WINDOW *timer;
+ WINDOW *toolbar, *timer;
+ int last_input;
/* Most recently displayed timer value, for screen redraw. */
uint_least32_t timer_ms;
case TILE_BLUE: ch = 'o'; attr |= A_BOLD; break;
case TILE_WHITE: ch = '.'; attr |= A_BOLD; break;
- case TILE_EMPTY: attr = A_BOLD|COLOR_PAIR(TILE_MAX);
+ case TILE_EMPTY: attr = A_BOLD|COLOR_PAIR(RR_COLOUR_CURSOR);
}
getmaxyx(border, h, w);
win[WINDOW_TILEFILL] = derwin(border, h-2, w-2, 1, 1);
}
+/*
+ * Given the toolbar function number (between 1 and 10, inclusive), and the
+ * total width of the screen, return the character position for the start of
+ * its label display.
+ *
+ * The intention is to divide the width of the screen into 10 roughly
+ * equally-sized areas, spreading out the remainder so that the width of
+ * each label is a monotone increasing function of the total width.
+ *
+ * The minimum size of a label is 6 characters (2 of which are used for the
+ * number indicator)
+ */
+static int toolbar_xpos(int i, int total_width)
+{
+ int button_width = MAX(6, total_width/10);
+ int rem = total_width - 10*button_width;
+ int pos = (i-1)*button_width;
+
+ switch (rem) {
+ case 9: pos += i > 6;
+ case 8: pos += i > 2;
+ case 7: pos += i > 7;
+ case 6: pos += i > 3;
+ case 5: pos += i > 8;
+ case 4: pos += i > 4;
+ case 3: pos += i > 9;
+ case 2: pos += i > 5;
+ }
+
+ return pos;
+}
+
+static void draw_toolbar(struct app_state *state)
+{
+ WINDOW *toolbar = state->toolbar;
+ int i, w, lw;
+
+ getmaxyx(toolbar, i, w);
+ werase(toolbar);
+
+ lw = MAX(6, w/10);
+ mvwprintw(toolbar, 0, toolbar_xpos( 1, w)+2, "%.*s", lw, "Help");
+ mvwprintw(toolbar, 0, toolbar_xpos( 2, w)+2, "%.*s", lw, "NewGame");
+ mvwprintw(toolbar, 0, toolbar_xpos(10, w)+2, "%.*s", lw, "Exit");
+
+ mvwchgat(toolbar, 0, 0, -1, A_REVERSE, RR_COLOUR_TOOLBAR, 0);
+ for (i = 1; i <= 10; i++)
+ mvwprintw(toolbar, 0, toolbar_xpos(i, w), "%2d", i);
+
+ wnoutrefresh(state->toolbar);
+}
+
static void setup_mainwin(struct app_state *state)
{
int w, h, gamesz, goalsz, scr_w, scr_h, split;
/* Status area */
w = MAX(0, scr_w-split-1);
realloc_area(&state->timer, 1, w, GAME_YPOS+h, split+1);
+
+ /* Toolbar */
+ realloc_area(&state->toolbar, 1, scr_w, scr_h-1, 0);
+ draw_toolbar(state);
}
static void app_initialize(int argc, char **argv)
init_pair(TILE_GREEN, COLOR_GREEN, COLOR_BLACK);
init_pair(TILE_BLUE, COLOR_BLUE, COLOR_BLACK);
init_pair(TILE_WHITE, COLOR_WHITE, COLOR_BLACK);
- init_pair(TILE_MAX, COLOR_BLACK, COLOR_BLACK);
+ init_pair(RR_COLOUR_CURSOR, COLOR_BLACK, COLOR_BLACK);
+ init_pair(RR_COLOUR_TOOLBAR, COLOR_CYAN, COLOR_BLACK);
setup_mainwin(&state);
refresh();
game_begin(&state->board);
}
+static void do_function(struct app_state *state, unsigned func)
+{
+ switch (func) {
+ case 2:
+ do_new_game(state);
+ break;
+ case 10:
+ endwin();
+ exit(0);
+ }
+}
+
#if HAVE_CURSES_MOUSE_SUPPORT
+/*
+ * Returns the toolbar function (1 through 10) under the given x, y screen
+ * coordinates, or 0 if the coordinates are outside of the toolbar.
+ */
+static int mouse_toolbar_function(struct app_state *state, int x, int y)
+{
+ int toolbar_x, toolbar_y, toolbar_w, toolbar_h, i;
+
+ getbegyx(state->toolbar, toolbar_y, toolbar_x);
+ getmaxyx(state->toolbar, toolbar_h, toolbar_w);
+
+ if ((void)toolbar_x, y != toolbar_y)
+ return 0;
+
+ /* OK, selected a button, determine which one */
+ for (i = 10; i > 1; i--) {
+ if ((void)toolbar_h, x >= toolbar_xpos(i, toolbar_w))
+ break;
+ }
+
+ return i;
+}
+
+/*
+ * 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 = mouse_toolbar_function(state, x, y);
+}
+
+/*
+ * 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 = mouse_toolbar_function(state, x, y);
+
+ if (func && state->toolbar_click == func) {
+ do_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[WINDOW_AREA], game_y, game_x);
+ getmaxyx(state->gamewin[WINDOW_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;
doupdate();
}
- if (!state->view_goal_on_game && bstate & BUTTON1_PRESSED) {
- uint_fast32_t cursor_mask, move_mask;
- int w, h;
-
- /* Determine size of the game area */
- getmaxyx(state->gamewin[WINDOW_TILEBORDER], h, w);
- w = 2*(w+1)/2;
-
- if (x < 4 || (x -= 4)/5 >= w) return;
- if (y <= GAME_YPOS || (y -= GAME_YPOS+1)/5 >= h) return;
+ /* Ignore button1 if holding button3 to view goal */
+ if (state->view_goal_on_game & 1)
+ return;
- /* Turn off the keyboard cursor when using the mouse */
- cursor_mask = state->cursor < 0 ? -1 : 1ul << state->cursor;
- state->cursor = -1;
+ if (bstate & BUTTON1_PRESSED) {
+ if (press_toolbar(state, x, y));
+ else if (press_tile(state, x, y));
+ }
- move_mask = do_move(state, x/w, y/h);
- if ((cursor_mask & move_mask) == 0) {
- curs_redraw_game(state, cursor_mask);
- doupdate();
- }
+ if (bstate & BUTTON1_RELEASED) {
+ release_toolbar(state, x, y);
}
}
-#endif
+#endif /* HAVE_CURSES_MOUSE_SUPPORT */
static void do_move_cursor(struct app_state *state, int c)
{
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);
do_move(state, state->cursor%5, state->cursor/5);
break;
}
-}
-int main(int argc, char **argv)
-{
- setlocale(LC_ALL, "");
- app_initialize(argc, argv);
+ /* ESC+# keys */
+ if (last_input == '\33' && c >= '0' && c <= '9') {
+ do_function(state, (c -= '0') == 0 ? 10 : c);
+ }
- do_new_game(&state);
+ /* F# keys */
+ if (c >= KEY_F(1) && c <= KEY_F(10)) {
+ do_function(state, c - KEY_F0);
+ }
+}
- while (1) {
- int c = getch();
+/* One iteration of main input loop */
+void do_mainloop(struct app_state *state)
+{
+ int c = getch();
- switch (c) {
+ switch (c) {
#ifdef KEY_RESIZE
- case KEY_RESIZE:
- setup_mainwin(&state);
- clear();
- refresh();
- curs_redraw_game(&state, -1);
- curs_redraw_goal(&state, -1);
- update_timer(&state, state.timer_ms);
- break;
+ case KEY_RESIZE:
+ setup_mainwin(state);
+ clear();
+ refresh();
+ curs_redraw_game(state, -1);
+ curs_redraw_goal(state, -1);
+ draw_toolbar(state);
+ update_timer(state, state->timer_ms);
+ break;
#endif
#if HAVE_CURSES_MOUSE_SUPPORT
- case KEY_MOUSE:
- do_mouse(&state);
- break;
+ 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));
+ default:
+ do_keystroke(state, c);
+ case ERR:;
}
+
+ if (state->board.x <= 4)
+ update_timer(state, game_elapsed(&state->board));
+}
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_ALL, "");
+ app_initialize(argc, argv);
+
+ do_new_game(&state);
+ while (1)
+ do_mainloop(&state);
+ abort();
}