From de588fc223e0ecb155d25a98934a43630b97eea7 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 7 Jun 2022 23:44:01 -0400 Subject: [PATCH] Add basic keyboard control to curses UI. Since the mouse support in curses is not always available, we need to provide a keyboard-based alternative. The arrow keys move a cursor around the positions in the game area, and space moves the selected tile. --- src/curses.c | 90 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 12 deletions(-) diff --git a/src/curses.c b/src/curses.c index eecf3a1..215129e 100644 --- a/src/curses.c +++ b/src/curses.c @@ -45,6 +45,7 @@ static struct app_state { struct board board; WINDOW *gamewin[WINDOW_MAX], *goalwin[WINDOW_MAX]; + int cursor; } state; static void print_version(void) @@ -81,12 +82,11 @@ static void print_help(void) printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT); } -static void draw_tile(WINDOW **win, unsigned colour, +static void draw_tile(WINDOW **win, unsigned colour, unsigned selected, unsigned x, unsigned y, unsigned start_column) { WINDOW *border = win[WINDOW_TILEBORDER], *fill = win[WINDOW_TILEFILL]; - int attr, ch; - int w, h; + int w, h, attr, ch, bc = selected ? '#' : 0; assert(colour < TILE_MAX); attr = COLOR_PAIR(colour); @@ -97,6 +97,8 @@ static void draw_tile(WINDOW **win, unsigned colour, case TILE_YELLOW: ch = '~'; attr |= A_BOLD; break; 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); } getmaxyx(border, h, w); @@ -106,9 +108,6 @@ static void draw_tile(WINDOW **win, unsigned colour, return; if (colour != TILE_EMPTY) { - wattrset(border, attr); - box(border, 0, 0); - mvderwin(fill, 1, 1); wbkgdset(fill, A_REVERSE|attr|ch); werase(fill); @@ -116,12 +115,18 @@ static void draw_tile(WINDOW **win, unsigned colour, werase(border); } + if (bc || colour) { + wattrset(border, attr); + wborder(border, bc, bc, bc, bc, bc, bc, bc, bc); + } + wnoutrefresh(border); } static int redraw_tile(WINDOW **win, unsigned x, unsigned y, unsigned start_column, - uint_fast32_t bit0, uint_fast32_t bit1, uint_fast32_t bit2) + uint_fast32_t bit0, uint_fast32_t bit1, uint_fast32_t bit2, + unsigned selected) { uint_fast32_t pos = board_position(x, y); unsigned char tile = 0; @@ -131,7 +136,7 @@ redraw_tile(WINDOW **win, unsigned x, unsigned y, unsigned start_column, if (bit2 & pos) tile |= 4; assert(tile < TILE_MAX); - draw_tile(win, tile, x, y, start_column); + draw_tile(win, tile, selected, x, y, start_column); return tile; } @@ -170,7 +175,8 @@ static void curs_redraw_game(struct app_state *state, uint_fast32_t mask) for (i = 0; i < 25; i++) { if (mask & 1) { redraw_tile(state->gamewin, i%5, i/5, - 4, gp[0], gp[1], gp[2]); + 4, gp[0], gp[1], gp[2], + i == state->cursor); } mask >>= 1; } @@ -191,7 +197,7 @@ static void curs_redraw_goal(struct app_state *state, uint_fast32_t mask) for (i = 0; i < 9; i++) { if (mask & 1) { redraw_tile(state->goalwin, i%3, i/3, - x+2, gp[0], gp[1], gp[2]); + x+2, gp[0], gp[1], gp[2], 0); } mask >>= 1; } @@ -309,6 +315,7 @@ static void app_initialize(int argc, char **argv) } game_reset(&state.board); + state.cursor = 5*state.board.y + state.board.x; initscr(); start_color(); @@ -336,12 +343,13 @@ 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); setup_mainwin(&state); refresh(); } -static void do_move(struct app_state *state, int x, int y) +static uint_fast32_t do_move(struct app_state *state, int x, int y) { uint_fast32_t mask; @@ -349,6 +357,8 @@ static void do_move(struct app_state *state, int x, int y) curs_redraw_game(state, mask); refresh(); } + + return mask; } #if HAVE_CURSES_MOUSE_SUPPORT @@ -379,6 +389,7 @@ static void do_mouse(struct app_state *state) } #endif if (bstate == BUTTON1_PRESSED) { + uint_fast32_t cursor_mask, move_mask; int w, h; /* Determine size of the game area */ @@ -388,11 +399,64 @@ static void do_mouse(struct app_state *state) if (x < 4 || (x -= 4)/5 >= w) return; if (y < 2 || (y -= 2)/5 >= h) return; - do_move(state, x/w, y/h); + /* Turn off the keyboard cursor when using the mouse */ + cursor_mask = state->cursor < 0 ? -1 : 1ul << state->cursor; + state->cursor = -1; + + move_mask = do_move(state, x/w, y/h); + if ((cursor_mask & move_mask) == 0) { + curs_redraw_game(state, cursor_mask); + refresh(); + } } } #endif +static void do_move_cursor(struct app_state *state, int c) +{ + uint_fast32_t mask = 1ul << state->cursor; + + if (state->cursor < 0) { + /* Reset keyboard cursor to the empty position */ + state->cursor = 5*state->board.y + state->board.x; + } + + 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); + refresh(); +} + +static void do_keystroke(struct app_state *state, int c) +{ + switch (c) { + case KEY_DOWN: case KEY_UP: case KEY_LEFT: case KEY_RIGHT: + do_move_cursor(state, c); + break; + case ' ': + if (state->cursor >= 0) + do_move(state, state->cursor%5, state->cursor/5); + break; + } +} + int main(int argc, char **argv) { setlocale(LC_ALL, ""); @@ -421,6 +485,8 @@ int main(int argc, char **argv) do_mouse(&state); break; #endif + default: + do_keystroke(&state, c); } } } -- 2.43.2