]> git.draconx.ca Git - rrace.git/blobdiff - src/curses.c
Pack/unpack the tile planes arithmetically.
[rrace.git] / src / curses.c
index 06ec3519c87e530bcaa4837c7ff7276c882bab47..f66589eab3dac2fac9ae85f993eded99259f375f 100644 (file)
 
 #include "help.h"
 #include "version.h"
-#include "cursesopt.h"
-#include "game.h"
 
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#include "cursesui.h"
+#include "cursesopt.h"
 
 enum {
        GAME_YPOS = 1 // top row of game and goal areas.
@@ -38,31 +36,7 @@ enum {
 static const char *progname = "rrace";
 static const struct option lopts[] = { LOPTS_INITIALIZER, {0} };
 
-enum {
-       WINDOW_TILEBORDER,
-       WINDOW_TILEFILL,
-       WINDOW_AREA,
-       WINDOW_MAX,
-};
-
-static struct app_state {
-       struct board board;
-
-       WINDOW *gamewin[WINDOW_MAX], *goalwin[WINDOW_MAX];
-       WINDOW *timer;
-
-       /* Most recently displayed timer value, for screen redraw. */
-       uint_least32_t timer_ms;
-
-       /* Location of the keyboard cursor */
-       int_least8_t cursor;
-
-       /* If true, the goal will be displayed over the main play area. */
-       uint_least8_t view_goal_on_game;
-
-       /* Clicked toolbar item */
-       uint_least8_t toolbar_click;
-} state;
+static struct app_state state;
 
 static void print_version(void)
 {
@@ -101,7 +75,7 @@ static void print_help(void)
 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];
+       WINDOW *border = win[PLAYWIN_TILEBORDER], *fill = win[PLAYWIN_TILEFILL];
        int w, h, attr, ch, bc = selected ? '#' : 0;
 
        assert(colour < TILE_MAX);
@@ -114,7 +88,7 @@ static void draw_tile(WINDOW **win, unsigned colour, unsigned selected,
        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_SHADOW);
        }
 
        getmaxyx(border, h, w);
@@ -141,17 +115,11 @@ static void draw_tile(WINDOW **win, unsigned colour, unsigned selected,
 
 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,
-           unsigned selected)
+           uint_least32_t *gp, unsigned selected)
 {
-       uint_fast32_t pos = board_position(x, y);
-       unsigned char tile = 0;
+       unsigned tile = board_tile(gp, 5*y+x);
 
-       if (bit0 & pos) tile |= 1;
-       if (bit1 & pos) tile |= 2;
-       if (bit2 & pos) tile |= 4;
        assert(tile < TILE_MAX);
-
        draw_tile(win, tile, selected, x, y, start_column);
        return tile;
 }
@@ -159,7 +127,7 @@ redraw_tile(WINDOW **win, unsigned x, unsigned y, unsigned start_column,
 static void redraw_area_border(WINDOW **win, unsigned x, unsigned sz)
 {
        int w, h, tr = 0, rs = 0, br = 0, bs = 0, bl = 0;
-       WINDOW *area = win[WINDOW_AREA];
+       WINDOW *area = win[PLAYWIN_AREA];
 
        getmaxyx(stdscr, h, w);
 
@@ -199,8 +167,7 @@ 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],
-                                   i == state->cursor);
+                                   4, gp, i == state->cursor);
                }
                mask >>= 1;
        }
@@ -208,36 +175,47 @@ static void curs_redraw_game(struct app_state *state, uint_fast32_t mask)
 
 static void curs_redraw_goal(struct app_state *state, uint_fast32_t mask)
 {
-       uint_least16_t *gp = state->board.goal;
+       uint_least32_t gp[3] = {
+               state->board.goal[0],
+               state->board.goal[1],
+               state->board.goal[2]
+       };
        int i, x, y;
 
-       if (!state->goalwin[WINDOW_AREA])
+       if (!state->goalwin[PLAYWIN_AREA])
                return;
 
-       getbegyx(state->goalwin[WINDOW_AREA], y, x);
+       getbegyx(state->goalwin[PLAYWIN_AREA], y, x);
        if (mask == -1)
                redraw_area_border(state->goalwin, x, 3);
 
        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], 0);
+                       redraw_tile(state->goalwin, i%3, i/3, x+2, gp, 0);
                }
                mask >>= 1;
        }
 }
 
+/* Thin wrapper around wresize which returns ERR if support is unavailable. */
+static int do_wresize(WINDOW *window, int h, int w)
+{
+#if HAVE_CURSES_WRESIZE
+       return wresize(window, h, w);
+#endif
+       return ERR;
+}
+
 static WINDOW *realloc_area(WINDOW **orig, int h, int w, int y, int x)
 {
        WINDOW *win = *orig;
 
        if (win) {
-#if HAVE_CURSES_WRESIZE
-               if (wresize(win, h, w) != ERR) {
+               if (do_wresize(win, h, w) != ERR) {
                        mvwin(win, y, x);
                        return win;
                }
-#endif
+
                delwin(win);
        }
 
@@ -248,19 +226,15 @@ static WINDOW *realloc_area(WINDOW **orig, int h, int w, int y, int x)
 
 static void realloc_tiles(WINDOW **win, int h)
 {
-       WINDOW *border = win[WINDOW_TILEBORDER], *fill = win[WINDOW_TILEFILL];
+       WINDOW *border = win[PLAYWIN_TILEBORDER], *fill = win[PLAYWIN_TILEFILL];
        int w = 2*h - 1;
 
        if (fill && border) {
                int old_w, old_h;
 
-#if HAVE_CURSES_WRESIZE
-               if (wresize(fill, h-2, w-2) != ERR
-                   && wresize(border, h, w) != ERR)
-               {
+               if (do_wresize(fill, h-2, w-2) != ERR
+                   && do_wresize(border, h, w) != ERR)
                        return;
-               }
-#endif
 
                getmaxyx(border, old_h, old_w);
                if (old_h == h)
@@ -272,8 +246,8 @@ static void realloc_tiles(WINDOW **win, int h)
        if (border)
                delwin(border);
 
-       win[WINDOW_TILEBORDER] = border = newwin(h, w, 0, 0);
-       win[WINDOW_TILEFILL] = derwin(border, h-2, w-2, 1, 1);
+       win[PLAYWIN_TILEBORDER] = border = newwin(h, w, 0, 0);
+       win[PLAYWIN_TILEFILL] = derwin(border, h-2, w-2, 1, 1);
 }
 
 static void setup_mainwin(struct app_state *state)
@@ -302,16 +276,20 @@ static void setup_mainwin(struct app_state *state)
        /* Frame for game area */
        w = MIN(scr_w-2, 3+10*gamesz);
        h = MIN(scr_h-GAME_YPOS, 2+5*gamesz);
-       realloc_area(&state->gamewin[WINDOW_AREA], h, w, GAME_YPOS, 2);
+       realloc_area(&state->gamewin[PLAYWIN_AREA], h, w, GAME_YPOS, 2);
 
        /* Frame for goal area */
        w = MIN(scr_w-split, 3+6*goalsz);
        h = MIN(scr_h-GAME_YPOS, 2+3*goalsz);
-       realloc_area(&state->goalwin[WINDOW_AREA], h, w, GAME_YPOS, split);
+       realloc_area(&state->goalwin[PLAYWIN_AREA], h, w, GAME_YPOS, 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);
+       curs_draw_toolbar(state);
 }
 
 static void app_initialize(int argc, char **argv)
@@ -392,7 +370,8 @@ 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_SHADOW, COLOR_BLACK, COLOR_BLACK);
+       init_pair(RR_COLOUR_TOOLBAR, COLOR_CYAN, COLOR_BLACK);
 
        setup_mainwin(&state);
        refresh();
@@ -408,7 +387,7 @@ static void update_timer(struct app_state *state, uint_fast32_t ms)
        mvwprintw(state->timer, 0, 0, "Time: %u:%.2u.%.3u",
                  min, sec, (unsigned)ms);
        wclrtoeol(state->timer);
-       wrefresh(state->timer);
+       wnoutrefresh(state->timer);
 }
 
 static uint_fast32_t do_move(struct app_state *state, int x, int y)
@@ -443,7 +422,7 @@ static void do_reset_cursor(struct app_state *state)
        state->cursor = 5*state->board.y + state->board.x;
 }
 
-static void do_new_game(struct app_state *state)
+void curs_new_game(struct app_state *state)
 {
        game_reset(&state->board);
 
@@ -458,6 +437,77 @@ static void do_new_game(struct app_state *state)
 }
 
 #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;
@@ -500,29 +550,20 @@ static void do_mouse(struct app_state *state)
                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)
 {
@@ -565,6 +606,9 @@ 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);
@@ -579,40 +623,59 @@ static void do_keystroke(struct app_state *state, int 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') {
+               curs_execute_function(state, (c -= '0') == 0 ? 10 : c);
+       }
 
-       do_new_game(&state);
+       /* F# keys */
+       if (c >= KEY_F(1) && c <= KEY_F(10)) {
+               curs_execute_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);
+               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;
+       case KEY_MOUSE:
+               do_mouse(state);
+               break;
 #endif
-               default:
-                       do_keystroke(&state, c);
-               case ERR:;
-               }
+       default:
+               do_keystroke(state, c);
+       case ERR:;
+       }
 
-               if (state.board.x <= 4)
-                       update_timer(&state, game_elapsed(&state.board));
+       if (state->board.x <= 4) {
+               update_timer(state, game_elapsed(&state->board));
+               doupdate();
        }
 }
+
+int main(int argc, char **argv)
+{
+       setlocale(LC_ALL, "");
+       app_initialize(argc, argv);
+
+       curs_new_game(&state);
+       while (1)
+               do_mainloop(&state);
+       abort();
+}