/*
* 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;
}
}
}