2 * Curses UI for slide puzzle game
3 * Copyright © 2022 Nick Bowler
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
28 #include "cursesopt.h"
31 static const char *progname = "rrace";
32 static const struct option lopts[] = { LOPTS_INITIALIZER, {0} };
34 static struct app_state {
37 WINDOW *tile_border, *tile_fill;
40 static void print_version(void)
42 version_print_head("rrace-curses", stdout);
43 puts("License GPLv3+: GNU GPL version 3 or any later version");
44 puts("This is free software: you are free to change and redistribute it.");
45 puts("There is NO WARRANTY, to the extent permitted by law.");
48 static void print_usage(FILE *f)
50 fprintf(f, "Usage: %s [options]\n", progname);
52 fprintf(f, "Try %s --help for more information.\n", progname);
55 static void print_help(void)
57 struct lopt_help help = {0};
58 const struct option *opt;
64 for (opt = lopts; opt->name; opt++) {
65 if (!lopt_get_help(opt, &help))
67 help_print_option(opt, help.arg, help.desc, 20);
71 printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
75 draw_tile(struct app_state *state, unsigned colour, unsigned x, unsigned y)
80 assert(colour < TILE_MAX);
81 attr = COLOR_PAIR(colour);
83 case TILE_RED: ch = 'X'; break;
84 case TILE_ORANGE: ch = '|'; break;
85 case TILE_GREEN: ch = '+'; break;
86 case TILE_YELLOW: ch = '~'; attr |= A_BOLD; break;
87 case TILE_BLUE: ch = 'o'; attr |= A_BOLD; break;
88 case TILE_WHITE: ch = '.'; attr |= A_BOLD; break;
91 getmaxyx(state->tile_border, h, w);
94 if (mvwin(state->tile_border, 2+h*y, 4+w*x) == ERR)
97 if (colour != TILE_EMPTY) {
98 wattrset(state->tile_border, attr);
99 box(state->tile_border, 0, 0);
101 mvderwin(state->tile_fill, 1, 1);
102 wbkgdset(state->tile_fill, A_REVERSE|attr|ch);
103 werase(state->tile_fill);
105 werase(state->tile_border);
108 wnoutrefresh(state->tile_border);
111 static int curs_redraw_tile(struct app_state *state, unsigned x, unsigned y)
113 uint_fast32_t pos = board_position(x, y);
114 unsigned char tile = 0;
116 if (state->board.game[0] & pos) tile |= 1;
117 if (state->board.game[1] & pos) tile |= 2;
118 if (state->board.game[2] & pos) tile |= 4;
119 assert(tile < TILE_MAX);
121 draw_tile(state, tile, x, y);
125 static void curs_redraw_game(struct app_state *state, uint_fast32_t mask)
129 for (i = 0; i < 25; i++) {
131 curs_redraw_tile(state, i%5, i/5);
137 static void curs_alloc_tiles(struct app_state *state)
142 getmaxyx(stdscr, h, w);
143 tilesz = (h - 4) / 5;
148 if (state->tile_border) {
149 getmaxyx(state->tile_border, h, w);
157 if (state->tile_border) {
158 delwin(state->tile_fill);
159 delwin(state->tile_border);
162 state->tile_border = tile = newwin(tilesz, 2*tilesz-1, 0, 0);
163 state->tile_fill = derwin(tile, tilesz-2, 2*tilesz-3, 1, 1);
166 static void app_initialize(int argc, char **argv)
173 while ((opt = getopt_long(argc, argv, SOPT_STRING, lopts, 0)) != -1) {
187 game_reset(&state.board);
191 if (curs_set(0) != ERR)
192 leaveok(stdscr, TRUE);
195 keypad(stdscr, TRUE);
196 mousemask(BUTTON1_PRESSED, NULL);
200 init_pair(TILE_RED, COLOR_RED, COLOR_BLACK);
201 init_pair(TILE_ORANGE, COLOR_YELLOW, COLOR_BLACK);
202 init_pair(TILE_YELLOW, COLOR_YELLOW, COLOR_BLACK);
203 init_pair(TILE_GREEN, COLOR_GREEN, COLOR_BLACK);
204 init_pair(TILE_BLUE, COLOR_BLUE, COLOR_BLACK);
205 init_pair(TILE_WHITE, COLOR_WHITE, COLOR_BLACK);
207 curs_alloc_tiles(&state);
211 static void do_move(struct app_state *state, int x, int y)
215 if ((mask = game_do_move(&state->board, x, y)) != 0) {
216 curs_redraw_game(state, mask);
221 static void do_mouse(struct app_state *state, MEVENT *mev)
223 if (mev->bstate == BUTTON1_PRESSED) {
226 /* Determine size of the game area */
227 getmaxyx(state->tile_border, h, w);
230 if (mev->x < 4 || (x = mev->x - 4)/5 >= w) return;
231 if (mev->y < 2 || (y = mev->y - 2)/5 >= h) return;
233 do_move(state, x/w, y/h);
237 int main(int argc, char **argv)
239 setlocale(LC_ALL, "");
240 app_initialize(argc, argv);
242 curs_redraw_game(&state, -1);
251 curs_alloc_tiles(&state);
254 curs_redraw_game(&state, -1);
258 if (getmouse(&mev) != ERR)
259 do_mouse(&state, &mev);