From 09aa8de52cb962f1ceebfb1fd44f2c54a924fc5c Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Mon, 22 Jul 2024 21:43:25 -0400 Subject: puzzles: resync with upstream This brings the puzzles source in sync with Simon's branch, commit fd304c5 (from March 2024), with some added Rockbox-specific compatibility changes: https://www.franklinwei.com/git/puzzles/commit/?h=rockbox-devel&id=516830d9d76bdfe64fe5ccf2a9b59c33f5c7c078 There are quite a lot of backend changes, including a new "Mosaic" puzzle. In addition, some new frontend changes were necessary: - New "Preferences" menu to access the user preferences system. - Enabled spacebar input for several games. Change-Id: I94c7df674089c92f32d5f07025f6a1059068af1e --- apps/plugins/puzzles/src/pattern.c | 224 ++++++++++++++++++++++++------------- 1 file changed, 145 insertions(+), 79 deletions(-) (limited to 'apps/plugins/puzzles/src/pattern.c') diff --git a/apps/plugins/puzzles/src/pattern.c b/apps/plugins/puzzles/src/pattern.c index df720b7d82..b370a3dc2f 100644 --- a/apps/plugins/puzzles/src/pattern.c +++ b/apps/plugins/puzzles/src/pattern.c @@ -7,7 +7,12 @@ #include #include #include -#include +#include +#ifdef NO_TGMATH_H +# include +#else +# include +#endif #include "puzzles.h" @@ -53,6 +58,7 @@ typedef struct game_state_common { int *rowdata, *rowlen; bool *immutable; int refcount; + enum { FS_SMALL, FS_LARGE } fontsize; } game_state_common; struct game_state { @@ -176,7 +182,10 @@ static const char *validate_params(const game_params *params, bool full) { if (params->w <= 0 || params->h <= 0) return "Width and height must both be greater than zero"; - if (params->w * params->w < 2) + if (params->w > INT_MAX - 1 || params->h > INT_MAX - 1 || + params->w > INT_MAX / params->h) + return "Puzzle must not be unreasonably large"; + if (params->w * params->h < 2) return "Grid must contain at least two squares"; return NULL; } @@ -360,7 +369,7 @@ static int compute_rowdata(int *ret, unsigned char *start, int len, int step) #define STILL_UNKNOWN 3 #ifdef STANDALONE_SOLVER -bool verbose = false; +static bool verbose = false; #endif static bool do_recurse(unsigned char *known, unsigned char *deduced, @@ -441,6 +450,8 @@ static bool do_row(unsigned char *known, unsigned char *deduced, int rowlen, i, freespace; bool done_any; + assert(len >= 0); /* avoid compile warnings about the memsets below */ + freespace = len+1; for (rowlen = 0; data[rowlen]; rowlen++) { minpos_done[rowlen] = minpos_ok[rowlen] = len - 1; @@ -654,7 +665,7 @@ static bool solve_puzzle(const game_state *state, unsigned char *grid, #ifndef STANDALONE_PICTURE_GENERATOR static unsigned char *generate_soluble(random_state *rs, int w, int h) { - int i, j, ntries, max; + int i, j, max; bool ok; unsigned char *grid, *matrix, *workspace; unsigned int *changed_h, *changed_w; @@ -670,11 +681,7 @@ static unsigned char *generate_soluble(random_state *rs, int w, int h) changed_w = snewn(max+1, unsigned int); rowdata = snewn(max+1, int); - ntries = 0; - do { - ntries++; - generate(rs, w, h, grid); /* @@ -719,7 +726,7 @@ static unsigned char *generate_soluble(random_state *rs, int w, int h) #endif #ifdef STANDALONE_PICTURE_GENERATOR -unsigned char *picture; +static unsigned char *picture; #endif static char *new_game_desc(const game_params *params, random_state *rs, @@ -908,6 +915,10 @@ static const char *validate_desc(const game_params *params, const char *desc) p = desc; while (*desc && isdigit((unsigned char)*desc)) desc++; n = atoi(p); + if (n <= 0) + return "all clues must be positive"; + if (n > INT_MAX - 1) + return "at least one clue is grossly excessive"; rowspace -= n+1; if (rowspace < 0) { @@ -965,7 +976,7 @@ static const char *validate_desc(const game_params *params, const char *desc) static game_state *new_game(midend *me, const game_params *params, const char *desc) { - int i; + int i, j; const char *p; game_state *state = snew(game_state); @@ -1003,6 +1014,26 @@ static game_state *new_game(midend *me, const game_params *params, } } + /* + * Choose a font size based on the clues. If any column clue is + * more than one digit, switch to the smaller size. + */ + state->common->fontsize = FS_LARGE; + for (i = 0; i < params->w; i++) + for (j = 0; j < state->common->rowlen[i]; j++) + if (state->common->rowdata[state->common->rowsize * i + j] >= 10) + state->common->fontsize = FS_SMALL; + /* + * We might also need to use the small font if there are lots of + * row clues. We assume that all clues are one digit and that a + * single-digit clue takes up 1.5 tiles, of which the clue is 0.5 + * tiles and the space is 1.0 tiles. + */ + for (i = params->w; i < params->w + params->h; i++) + if ((state->common->rowlen[i] * 3 - 2) > + TLBORDER(state->common->w) * 2) + state->common->fontsize = FS_SMALL; + if (desc[-1] == ',') { /* * Optional extra piece of game description which fills in @@ -1217,7 +1248,7 @@ static game_ui *new_ui(const game_state *state) ret = snew(game_ui); ret->dragging = false; ret->cur_x = ret->cur_y = 0; - ret->cur_visible = false; + ret->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); return ret; } @@ -1227,18 +1258,26 @@ static void free_ui(game_ui *ui) sfree(ui); } -static char *encode_ui(const game_ui *ui) -{ - return NULL; -} - -static void decode_ui(game_ui *ui, const char *encoding) +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) { } -static void game_changed_state(game_ui *ui, const game_state *oldstate, - const game_state *newstate) +static const char *current_key_label(const game_ui *ui, + const game_state *state, int button) { + if (IS_CURSOR_SELECT(button)) { + if (!ui->cur_visible) return ""; + switch (state->grid[ui->cur_y * state->common->w + ui->cur_x]) { + case GRID_UNKNOWN: + return button == CURSOR_SELECT ? "Black" : "White"; + case GRID_FULL: + return button == CURSOR_SELECT ? "White" : "Grey"; + case GRID_EMPTY: + return button == CURSOR_SELECT ? "Grey" : "Black"; + } + } + return ""; } struct game_drawstate { @@ -1247,6 +1286,7 @@ struct game_drawstate { int tilesize; unsigned char *visible, *numcolours; int cur_x, cur_y; + char *strbuf; /* Used for formatting clues. */ }; static char *interpret_move(const game_state *state, game_ui *ui, @@ -1254,7 +1294,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, int x, int y, int button) { bool control = button & MOD_CTRL, shift = button & MOD_SHFT; - button &= ~MOD_MASK; + button = STRIP_BUTTON_MODIFIERS(button); x = FROMCOORD(state->common->w, x); y = FROMCOORD(state->common->h, y); @@ -1294,7 +1334,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, ui->drag_start_y = ui->drag_end_y = y; ui->cur_visible = false; - return UI_UPDATE; + return MOVE_UI_UPDATE; } if (ui->dragging && button == ui->drag) { @@ -1323,7 +1363,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, ui->drag_end_x = x; ui->drag_end_y = y; - return UI_UPDATE; + return MOVE_UI_UPDATE; } if (ui->dragging && button == ui->release) { @@ -1351,20 +1391,21 @@ static char *interpret_move(const game_state *state, game_ui *ui, x1, y1, x2-x1+1, y2-y1+1); return dupstr(buf); } else - return UI_UPDATE; + return MOVE_UI_UPDATE; } if (IS_CURSOR_MOVE(button)) { int x = ui->cur_x, y = ui->cur_y, newstate; - char buf[80]; - move_cursor(button, &ui->cur_x, &ui->cur_y, state->common->w, state->common->h, false); - ui->cur_visible = true; - if (!control && !shift) return UI_UPDATE; + char buf[80], *ret; + ret = move_cursor(button, &ui->cur_x, &ui->cur_y, + state->common->w, state->common->h, false, + &ui->cur_visible); + if (!control && !shift) return ret; newstate = control ? shift ? GRID_UNKNOWN : GRID_FULL : GRID_EMPTY; if (state->grid[y * state->common->w + x] == newstate && state->grid[ui->cur_y * state->common->w + ui->cur_x] == newstate) - return UI_UPDATE; + return ret; sprintf(buf, "%c%d,%d,%d,%d", control ? shift ? 'U' : 'F' : 'E', min(x, ui->cur_x), min(y, ui->cur_y), @@ -1379,7 +1420,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, if (!ui->cur_visible) { ui->cur_visible = true; - return UI_UPDATE; + return MOVE_UI_UPDATE; } if (button == CURSOR_SELECT2) @@ -1637,7 +1678,7 @@ static bool check_errors(const game_state *state, int i) */ static void game_compute_size(const game_params *params, int tilesize, - int *x, int *y) + const game_ui *ui, int *x, int *y) { /* Ick: fake up `ds->tilesize' for macro expansion purposes */ struct { int tilesize; } ads, *ds = &ads; @@ -1692,6 +1733,8 @@ static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) ds->numcolours = snewn(ds->w + ds->h, unsigned char); memset(ds->numcolours, 255, ds->w + ds->h); ds->cur_x = ds->cur_y = 0; + ds->strbuf = snewn(state->common->rowsize * + MAX_DIGITS(*state->common->rowdata) + 1, char); return ds; } @@ -1699,6 +1742,8 @@ static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) static void game_free_drawstate(drawing *dr, game_drawstate *ds) { sfree(ds->visible); + sfree(ds->numcolours); + sfree(ds->strbuf); sfree(ds); } @@ -1743,19 +1788,43 @@ static void draw_numbers( int *rowdata = state->common->rowdata + state->common->rowsize * i; int nfit; int j; + int rx, ry, rw, rh; + int fontsize; - if (erase) { - if (i < state->common->w) { - draw_rect(dr, TOCOORD(state->common->w, i), 0, - TILE_SIZE, BORDER + TLBORDER(state->common->h) * TILE_SIZE, - COL_BACKGROUND); - } else { - draw_rect(dr, 0, TOCOORD(state->common->h, i - state->common->w), - BORDER + TLBORDER(state->common->w) * TILE_SIZE, TILE_SIZE, - COL_BACKGROUND); - } + if (i < state->common->w) { + rx = TOCOORD(state->common->w, i); + ry = 0; + rw = TILE_SIZE; + rh = BORDER + TLBORDER(state->common->h) * TILE_SIZE; + } else { + rx = 0; + ry = TOCOORD(state->common->h, i - state->common->w); + rw = BORDER + TLBORDER(state->common->w) * TILE_SIZE; + rh = TILE_SIZE; } + clip(dr, rx, ry, rw, rh); + if (erase) + draw_rect(dr, rx, ry, rw, rh, COL_BACKGROUND); + + /* + * Choose a font size that's suitable for the lengths of clue. + * Only column clues are interesting because row clues can be + * spaced out independent of the tile size. For column clues, we + * want to go as large as practical while leaving decent space + * between horizintally adjacent clues. We currently distinguish + * two cases: FS_LARGE is when all column clues are single digits, + * and FS_SMALL in all other cases. + * + * If we assume that a digit is about 0.6em wide, and we want + * about that space between clues, then FS_LARGE should be + * TILESIZE/1.2. If we also assume that clues are at most two + * digits long then the case where adjacent clues are two digits + * long requries FS_SMALL to be TILESIZE/1.8. + */ + fontsize = (TILE_SIZE + 0.5F) / + (state->common->fontsize == FS_LARGE ? 1.2F : 1.8F); + /* * Normally I space the numbers out by the same distance as the * tile size. However, if there are more numbers than available @@ -1768,32 +1837,38 @@ static void draw_numbers( nfit = max(rowlen, nfit) - 1; assert(nfit > 0); - for (j = 0; j < rowlen; j++) { - int x, y; - char str[80]; + if (i < state->common->w) { + for (j = 0; j < rowlen; j++) { + int x, y; + char str[MAX_DIGITS(*rowdata) + 1]; - if (i < state->common->w) { - x = TOCOORD(state->common->w, i); + x = rx; y = BORDER + TILE_SIZE * (TLBORDER(state->common->h)-1); y -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(state->common->h)-1) / nfit; - } else { - y = TOCOORD(state->common->h, i - state->common->w); - x = BORDER + TILE_SIZE * (TLBORDER(state->common->w)-1); - x -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(state->common->w)-1) / nfit; + sprintf(str, "%d", rowdata[j]); + draw_text(dr, x+TILE_SIZE/2, y+TILE_SIZE/2, FONT_VARIABLE, + fontsize, ALIGN_HCENTRE | ALIGN_VCENTRE, colour, str); } - - sprintf(str, "%d", rowdata[j]); - draw_text(dr, x+TILE_SIZE/2, y+TILE_SIZE/2, FONT_VARIABLE, - TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, colour, str); - } - - if (i < state->common->w) { - draw_update(dr, TOCOORD(state->common->w, i), 0, - TILE_SIZE, BORDER + TLBORDER(state->common->h) * TILE_SIZE); } else { - draw_update(dr, 0, TOCOORD(state->common->h, i - state->common->w), - BORDER + TLBORDER(state->common->w) * TILE_SIZE, TILE_SIZE); + int x, y; + size_t off = 0; + const char *spaces = " "; + + assert(rowlen <= state->common->rowsize); + *ds->strbuf = '\0'; + /* Squish up a bit if there are lots of clues. */ + if (rowlen > TLBORDER(state->common->w)) spaces++; + for (j = 0; j < rowlen; j++) + off += sprintf(ds->strbuf + off, "%s%d", + j ? spaces : "", rowdata[j]); + y = ry; + x = BORDER + TILE_SIZE * (TLBORDER(state->common->w)-1); + draw_text(dr, x+TILE_SIZE, y+TILE_SIZE/2, FONT_VARIABLE, + fontsize, ALIGN_HRIGHT | ALIGN_VCENTRE, colour, ds->strbuf); } + + unclip(dr); + draw_update(dr, rx, ry, rw, rh); } static void game_redraw(drawing *dr, game_drawstate *ds, @@ -1807,14 +1882,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds, bool cmoved; if (!ds->started) { - /* - * The initial contents of the window are not guaranteed - * and can vary with front ends. To be on the safe side, - * all games should start by drawing a big background- - * colour rectangle covering the whole window. - */ - draw_rect(dr, 0, 0, SIZE(ds->w), SIZE(ds->h), COL_BACKGROUND); - /* * Draw the grid outline. */ @@ -1936,24 +2003,21 @@ static int game_status(const game_state *state) return state->completed ? +1 : 0; } -static bool game_timing_state(const game_state *state, game_ui *ui) -{ - return true; -} - -static void game_print_size(const game_params *params, float *x, float *y) +static void game_print_size(const game_params *params, const game_ui *ui, + float *x, float *y) { int pw, ph; /* * I'll use 5mm squares by default. */ - game_compute_size(params, 500, &pw, &ph); + game_compute_size(params, 500, ui, &pw, &ph); *x = pw / 100.0F; *y = ph / 100.0F; } -static void game_print(drawing *dr, const game_state *state, int tilesize) +static void game_print(drawing *dr, const game_state *state, const game_ui *ui, + int tilesize) { int w = state->common->w, h = state->common->h; int ink = print_mono_colour(dr, 0); @@ -2027,12 +2091,14 @@ const struct game thegame = { free_game, true, solve_game, true, game_can_format_as_text_now, game_text_format, + NULL, NULL, /* get_prefs, set_prefs */ new_ui, free_ui, - encode_ui, - decode_ui, + NULL, /* encode_ui */ + NULL, /* decode_ui */ NULL, /* game_request_keys */ game_changed_state, + current_key_label, interpret_move, execute_move, PREFERRED_TILE_SIZE, game_compute_size, game_set_size, @@ -2046,7 +2112,7 @@ const struct game thegame = { game_status, true, false, game_print_size, game_print, false, /* wants_statusbar */ - false, game_timing_state, + false, NULL, /* timing_state */ REQUIRE_RBUTTON, /* flags */ }; -- cgit v1.2.3