/* * Curses UI for slide puzzle game * Copyright © 2022 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include "help.h" #include "version.h" #include "cursesopt.h" #include "game.h" static const char *progname = "rrace"; static const struct option lopts[] = { LOPTS_INITIALIZER, {0} }; static struct app_state { struct board board; WINDOW *tile_border, *tile_fill; } state; static void print_version(void) { version_print_head("rrace-curses", stdout); puts("License GPLv3+: GNU GPL version 3 or any later version"); puts("This is free software: you are free to change and redistribute it."); puts("There is NO WARRANTY, to the extent permitted by law."); } static void print_usage(FILE *f) { fprintf(f, "Usage: %s [options]\n", progname); if (f != stdout) fprintf(f, "Try %s --help for more information.\n", progname); } static void print_help(void) { struct lopt_help help = {0}; const struct option *opt; print_usage(stdout); putchar('\n'); puts("Options:"); for (opt = lopts; opt->name; opt++) { if (!lopt_get_help(opt, &help)) continue; help_print_option(opt, help.arg, help.desc, 20); } putchar('\n'); printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT); } static void draw_tile(struct app_state *state, unsigned colour, unsigned x, unsigned y) { int attr, ch; int w, h; assert(colour < TILE_MAX); attr = COLOR_PAIR(colour); switch (colour) { case TILE_RED: ch = 'X'; break; case TILE_ORANGE: ch = '|'; break; case TILE_GREEN: ch = '+'; break; 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; } getmaxyx(state->tile_border, h, w); w = 2*(w+1)/2; if (mvwin(state->tile_border, 2+h*y, 4+w*x) == ERR) return; if (colour != TILE_EMPTY) { wattrset(state->tile_border, attr); box(state->tile_border, 0, 0); mvderwin(state->tile_fill, 1, 1); wbkgdset(state->tile_fill, A_REVERSE|attr|ch); werase(state->tile_fill); } else { werase(state->tile_border); } wnoutrefresh(state->tile_border); } static int curs_redraw_tile(struct app_state *state, unsigned x, unsigned y) { uint_fast32_t pos = board_position(x, y); unsigned char tile = 0; if (state->board.game[0] & pos) tile |= 1; if (state->board.game[1] & pos) tile |= 2; if (state->board.game[2] & pos) tile |= 4; assert(tile < TILE_MAX); draw_tile(state, tile, x, y); return tile; } static void curs_redraw_game(struct app_state *state, uint_fast32_t mask) { int i; for (i = 0; i < 25; i++) { if (mask & 1) { curs_redraw_tile(state, i%5, i/5); } mask >>= 1; } } static void curs_alloc_tiles(struct app_state *state) { int w, h, tilesz; WINDOW *tile; getmaxyx(stdscr, h, w); tilesz = (h - 4) / 5; if (tilesz < 3) tilesz = 3; h = -1; if (state->tile_border) { getmaxyx(state->tile_border, h, w); } if (h == tilesz) { /* Nothing to do. */ return; } if (state->tile_border) { delwin(state->tile_fill); delwin(state->tile_border); } state->tile_border = tile = newwin(tilesz, 2*tilesz-1, 0, 0); state->tile_fill = derwin(tile, tilesz-2, 2*tilesz-3, 1, 1); } static void app_initialize(int argc, char **argv) { int opt; if (argc > 0) progname = argv[0]; while ((opt = getopt_long(argc, argv, SOPT_STRING, lopts, 0)) != -1) { switch (opt) { case LOPT_VERSION: print_version(); exit(EXIT_SUCCESS); case LOPT_HELP: print_help(); exit(EXIT_SUCCESS); default: print_usage(stderr); exit(EXIT_FAILURE); } } game_reset(&state.board); initscr(); start_color(); if (curs_set(0) != ERR) leaveok(stdscr, TRUE); cbreak(); keypad(stdscr, TRUE); mousemask(BUTTON1_PRESSED, NULL); mouseinterval(0); noecho(); init_pair(TILE_RED, COLOR_RED, COLOR_BLACK); init_pair(TILE_ORANGE, COLOR_YELLOW, COLOR_BLACK); init_pair(TILE_YELLOW, COLOR_YELLOW, COLOR_BLACK); init_pair(TILE_GREEN, COLOR_GREEN, COLOR_BLACK); init_pair(TILE_BLUE, COLOR_BLUE, COLOR_BLACK); init_pair(TILE_WHITE, COLOR_WHITE, COLOR_BLACK); curs_alloc_tiles(&state); refresh(); } static void do_move(struct app_state *state, int x, int y) { uint_fast32_t mask; if ((mask = game_do_move(&state->board, x, y)) != 0) { curs_redraw_game(state, mask); refresh(); } } static void do_mouse(struct app_state *state, MEVENT *mev) { if (mev->bstate == BUTTON1_PRESSED) { int w, h, x, y; /* Determine size of the game area */ getmaxyx(state->tile_border, h, w); w = 2*(w+1)/2; if (mev->x < 4 || (x = mev->x - 4)/5 >= w) return; if (mev->y < 2 || (y = mev->y - 2)/5 >= h) return; do_move(state, x/w, y/h); } } int main(int argc, char **argv) { setlocale(LC_ALL, ""); app_initialize(argc, argv); curs_redraw_game(&state, -1); refresh(); while (1) { int c = getch(); MEVENT mev; switch (c) { case KEY_RESIZE: curs_alloc_tiles(&state); clear(); refresh(); curs_redraw_game(&state, -1); refresh(); break; case KEY_MOUSE: if (getmouse(&mev) != ERR) do_mouse(&state, &mev); break; } } }