]> git.draconx.ca Git - rrace.git/commitdiff
Add basic keyboard control to curses UI.
authorNick Bowler <nbowler@draconx.ca>
Wed, 8 Jun 2022 03:44:01 +0000 (23:44 -0400)
committerNick Bowler <nbowler@draconx.ca>
Wed, 8 Jun 2022 03:44:01 +0000 (23:44 -0400)
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

index eecf3a19e736a6f9e13552eea465fe130a9fb5dc..215129ef1705e134ddb06ddaf90d82d2ca56aec6 100644 (file)
@@ -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);
                }
        }
 }