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/map.c | 259 ++++++++++++++++++++++++++--------------- 1 file changed, 167 insertions(+), 92 deletions(-) (limited to 'apps/plugins/puzzles/src/map.c') diff --git a/apps/plugins/puzzles/src/map.c b/apps/plugins/puzzles/src/map.c index 9df2d22b52..9cea0d4647 100644 --- a/apps/plugins/puzzles/src/map.c +++ b/apps/plugins/puzzles/src/map.c @@ -14,7 +14,12 @@ #include #include #include -#include +#include +#ifdef NO_TGMATH_H +# include +#else +# include +#endif #include "puzzles.h" @@ -25,7 +30,7 @@ */ #if defined STANDALONE_SOLVER #define SOLVER_DIAGNOSTICS -bool verbose = false; +static bool verbose = false; #elif defined SOLVER_DIAGNOSTICS #define verbose true #endif @@ -40,12 +45,6 @@ bool verbose = false; #define FIVE (FOUR+1) #define SIX (FOUR+2) -/* - * Ghastly run-time configuration option, just for Gareth (again). - */ -static int flash_type = -1; -static float flash_length; - /* * Difficulty levels. I do some macro ickery here to ensure that my * enum and the various forms of my name list always match up. @@ -180,7 +179,9 @@ static void decode_params(game_params *params, char const *string) params->n = atoi(p); while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++; } else { - params->n = params->w * params->h / 8; + if (params->h > 0 && params->w > 0 && + params->w <= INT_MAX / params->h) + params->n = params->w * params->h / 8; } if (*p == 'd') { int i; @@ -252,6 +253,8 @@ static const char *validate_params(const game_params *params, bool full) { if (params->w < 2 || params->h < 2) return "Width and height must be at least two"; + if (params->w > INT_MAX / 2 / params->h) + return "Width times height must not be unreasonably large"; if (params->n < 5) return "Must have at least five regions"; if (params->n > params->w * params->h) @@ -1659,6 +1662,10 @@ static char *new_game_desc(const game_params *params, random_state *rs, } } + if (retlen + 10 >= retsize) { + retsize = retlen + 256; + ret = sresize(ret, retsize, char); + } ret[retlen++] = 'a'-1 + run; ret[retlen++] = ','; @@ -1711,8 +1718,8 @@ static const char *parse_edge_list(const game_params *params, int i, k, pos; bool state; const char *p = *desc; - - dsf_init(map+wh, wh); + const char *err = NULL; + DSF *dsf = dsf_new(wh); pos = -1; state = false; @@ -1723,8 +1730,10 @@ static const char *parse_edge_list(const game_params *params, * pairs of squares whenever the edge list shows a non-edge). */ while (*p && *p != ',') { - if (*p < 'a' || *p > 'z') - return "Unexpected character in edge list"; + if (*p < 'a' || *p > 'z') { + err = "Unexpected character in edge list"; + goto out; + } if (*p == 'z') k = 25; else @@ -1747,10 +1756,12 @@ static const char *parse_edge_list(const game_params *params, y = (pos - w*(h-1)) % h; dx = 1; dy = 0; - } else - return "Too much data in edge list"; + } else { + err = "Too much data in edge list"; + goto out; + } if (!state) - dsf_merge(map+wh, y*w+x, (y+dy)*w+(x+dx)); + dsf_merge(dsf, y*w+x, (y+dy)*w+(x+dx)); pos++; } @@ -1759,8 +1770,10 @@ static const char *parse_edge_list(const game_params *params, p++; } assert(pos <= 2*wh-w-h); - if (pos < 2*wh-w-h) - return "Too little data in edge list"; + if (pos < 2*wh-w-h) { + err = "Too little data in edge list"; + goto out; + } /* * Now go through again and allocate region numbers. @@ -1769,17 +1782,22 @@ static const char *parse_edge_list(const game_params *params, for (i = 0; i < wh; i++) map[i] = -1; for (i = 0; i < wh; i++) { - k = dsf_canonify(map+wh, i); + k = dsf_canonify(dsf, i); if (map[k] < 0) map[k] = pos++; map[i] = map[k]; } - if (pos != n) - return "Edge list defines the wrong number of regions"; + if (pos != n) { + err = "Edge list defines the wrong number of regions"; + goto out; + } *desc = p; + err = NULL; /* no error */ - return NULL; + out: + dsf_free(dsf); + return err; } static const char *validate_desc(const game_params *params, const char *desc) @@ -1789,7 +1807,7 @@ static const char *validate_desc(const game_params *params, const char *desc) int *map; const char *ret; - map = snewn(2*wh, int); + map = snewn(wh, int); ret = parse_edge_list(params, &desc, map); sfree(map); if (ret) @@ -2252,16 +2270,6 @@ static char *solve_game(const game_state *state, const game_state *currstate, return dupstr(aux); } -static bool game_can_format_as_text_now(const game_params *params) -{ - return true; -} - -static char *game_text_format(const game_state *state) -{ - return NULL; -} - struct game_ui { /* * drag_colour: @@ -2275,11 +2283,41 @@ struct game_ui { int drag_pencil; int dragx, dragy; bool show_numbers; + bool large_stipples; int cur_x, cur_y, cur_lastmove; bool cur_visible, cur_moved; + + /* + * User preference to enable alternative versions of the + * completion flash. Some users have found the colour-cycling + * default version to be a bit eye-twisting. + */ + enum { + FLASH_CYCLIC, /* cycle the four colours of the map */ + FLASH_EACH_TO_WHITE, /* turn each colour white in turn */ + FLASH_ALL_TO_WHITE /* flash the whole map to white in one go */ + } flash_type; }; +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int flash_type = -1; + + if (!initialised) { + char *env; + + initialised = true; + + if ((env = getenv("MAP_ALTERNATIVE_FLASH")) != NULL) + flash_type = FLASH_EACH_TO_WHITE; + } + + if (flash_type != -1) + ui_out->flash_type = flash_type; +} + static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); @@ -2288,24 +2326,55 @@ static game_ui *new_ui(const game_state *state) ui->drag_pencil = 0; ui->show_numbers = false; ui->cur_x = ui->cur_y = 0; - ui->cur_visible = false; + ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); ui->cur_moved = false; ui->cur_lastmove = 0; + ui->flash_type = FLASH_CYCLIC; + legacy_prefs_override(ui); return ui; } -static void free_ui(game_ui *ui) +static config_item *get_prefs(game_ui *ui) { - sfree(ui); + config_item *ret; + + ret = snewn(4, config_item); + + ret[0].name = "Victory flash effect"; + ret[0].kw = "flash-type"; + ret[0].type = C_CHOICES; + ret[0].u.choices.choicenames = ":Cyclic:Each to white:All to white"; + ret[0].u.choices.choicekws = ":cyclic:each-white:all-white"; + ret[0].u.choices.selected = ui->flash_type; + + ret[1].name = "Number regions"; + ret[1].kw = "show-numbers"; + ret[1].type = C_BOOLEAN; + ret[1].u.boolean.bval = ui->show_numbers; + + ret[2].name = "Display style for stipple marks"; + ret[2].kw = "stipple-style"; + ret[2].type = C_CHOICES; + ret[2].u.choices.choicenames = ":Small:Large"; + ret[2].u.choices.choicekws = ":small:large"; + ret[2].u.choices.selected = ui->large_stipples; + + ret[3].name = NULL; + ret[3].type = C_END; + + return ret; } -static char *encode_ui(const game_ui *ui) +static void set_prefs(game_ui *ui, const config_item *cfg) { - return NULL; + ui->flash_type = cfg[0].u.choices.selected; + ui->show_numbers = cfg[1].u.boolean.bval; + ui->large_stipples = cfg[2].u.choices.selected; } -static void decode_ui(game_ui *ui, const char *encoding) +static void free_ui(game_ui *ui) { + sfree(ui); } static void game_changed_state(game_ui *ui, const game_state *oldstate, @@ -2388,6 +2457,26 @@ static int region_from_ui_cursor(const game_state *state, const game_ui *ui) EPSILON_Y(ui->cur_lastmove)); } +static const char *current_key_label(const game_ui *ui, + const game_state *state, int button) +{ + int r; + + if (IS_CURSOR_SELECT(button) && ui->cur_visible) { + if (ui->drag_colour == -2) return "Pick"; + r = region_from_ui_cursor(state, ui); + if (state->map->immutable[r]) return "Cancel"; + if (!ui->cur_moved) return ui->drag_pencil ? "Cancel" : "Clear"; + if (button == CURSOR_SELECT2) { + if (state->colouring[r] >= 0) return "Cancel"; + if (ui->drag_colour >= 0) return "Stipple"; + } + if (ui->drag_pencil) return "Stipple"; + return ui->drag_colour >= 0 ? "Fill" : "Clear"; + } + return ""; +} + static char *interpret_move(const game_state *state, game_ui *ui, const game_drawstate *ds, int x, int y, int button) @@ -2401,21 +2490,21 @@ static char *interpret_move(const game_state *state, game_ui *ui, */ if (button == 'l' || button == 'L') { ui->show_numbers = !ui->show_numbers; - return UI_UPDATE; + return MOVE_UI_UPDATE; } if (IS_CURSOR_MOVE(button)) { move_cursor(button, &ui->cur_x, &ui->cur_y, state->p.w, state->p.h, - false); + false, NULL); ui->cur_visible = true; ui->cur_moved = true; ui->cur_lastmove = button; - return UI_UPDATE; + return MOVE_UI_UPDATE; } if (IS_CURSOR_SELECT(button)) { if (!ui->cur_visible) { ui->cur_visible = true; - return UI_UPDATE; + return MOVE_UI_UPDATE; } if (ui->drag_colour == -2) { /* not currently cursor-dragging, start. */ int r = region_from_ui_cursor(state, ui); @@ -2427,7 +2516,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, ui->drag_pencil = 0; } ui->cur_moved = false; - return UI_UPDATE; + return MOVE_UI_UPDATE; } else { /* currently cursor-dragging; drop the colour in the new region. */ alt_button = (button == CURSOR_SELECT2); /* Double-select removes current colour. */ @@ -2452,14 +2541,14 @@ static char *interpret_move(const game_state *state, game_ui *ui, ui->dragx = x; ui->dragy = y; ui->cur_visible = false; - return UI_UPDATE; + return MOVE_UI_UPDATE; } if ((button == LEFT_DRAG || button == RIGHT_DRAG) && ui->drag_colour > -2) { ui->dragx = x; ui->dragy = y; - return UI_UPDATE; + return MOVE_UI_UPDATE; } if ((button == LEFT_RELEASE || button == RIGHT_RELEASE) && @@ -2484,18 +2573,18 @@ drag_dropped: ui->drag_colour = -2; if (r < 0) - return UI_UPDATE; /* drag into border; do nothing else */ + return MOVE_UI_UPDATE; /* drag into border; do nothing else */ if (state->map->immutable[r]) - return UI_UPDATE; /* can't change this region */ + return MOVE_UI_UPDATE; /* can't change this region */ if (state->colouring[r] == c && state->pencil[r] == p) - return UI_UPDATE; /* don't _need_ to change this region */ + return MOVE_UI_UPDATE; /* don't _need_ to change this region */ if (alt_button) { if (state->colouring[r] >= 0) { /* Can't pencil on a coloured region */ - return UI_UPDATE; + return MOVE_UI_UPDATE; } else if (c >= 0) { /* Right-dragging from colour to blank toggles one pencil */ p = state->pencil[r] ^ (1 << c); @@ -2605,7 +2694,7 @@ static game_state *execute_move(const game_state *state, const char *move) */ 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; @@ -2729,7 +2818,7 @@ static void draw_error(drawing *dr, game_drawstate *ds, int x, int y) static void draw_square(drawing *dr, game_drawstate *ds, const game_params *params, struct map *map, - int x, int y, unsigned long v) + int x, int y, unsigned long v, bool large_stipples) { int w = params->w, h = params->h, wh = w*h; int tv, bv, xo, yo, i, j, oldj; @@ -2802,7 +2891,8 @@ static void draw_square(drawing *dr, game_drawstate *ds, draw_circle(dr, COORD(x) + (xo+1)*TILESIZE/5, COORD(y) + (yo+1)*TILESIZE/5, - TILESIZE/4, COL_0 + c, COL_0 + c); + large_stipples ? TILESIZE/4 : TILESIZE/7, + COL_0 + c, COL_0 + c); } /* @@ -2857,6 +2947,11 @@ static void draw_square(drawing *dr, game_drawstate *ds, draw_update(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); } +static float flash_length(const game_ui *ui) +{ + return (ui->flash_type == FLASH_EACH_TO_WHITE ? 0.50F : 0.30F); +} + static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldstate, const game_state *state, int dir, const game_ui *ui, @@ -2872,29 +2967,18 @@ static void game_redraw(drawing *dr, game_drawstate *ds, ds->drag_visible = false; } - /* - * 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. - */ if (!ds->started) { - int ww, wh; - - game_compute_size(&state->p, TILESIZE, &ww, &wh); - draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND); draw_rect(dr, COORD(0), COORD(0), w*TILESIZE+1, h*TILESIZE+1, COL_GRID); - - draw_update(dr, 0, 0, ww, wh); + draw_update(dr, COORD(0), COORD(0), w*TILESIZE+1, h*TILESIZE+1); ds->started = true; } if (flashtime) { - if (flash_type == 1) - flash = (int)(flashtime * FOUR / flash_length); + if (ui->flash_type == FLASH_EACH_TO_WHITE) + flash = (int)(flashtime * FOUR / flash_length(ui)); else - flash = 1 + (int)(flashtime * THREE / flash_length); + flash = 1 + (int)(flashtime * THREE / flash_length(ui)); } else flash = -1; @@ -2913,12 +2997,12 @@ static void game_redraw(drawing *dr, game_drawstate *ds, bv = FOUR; if (flash >= 0) { - if (flash_type == 1) { + if (ui->flash_type == FLASH_EACH_TO_WHITE) { if (tv == flash) tv = FOUR; if (bv == flash) bv = FOUR; - } else if (flash_type == 2) { + } else if (ui->flash_type == FLASH_ALL_TO_WHITE) { if (flash % 2) tv = bv = FOUR; } else { @@ -2990,7 +3074,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, for (x = 0; x < w; x++) { unsigned long v = ds->todraw[y*w+x]; if (ds->drawn[y*w+x] != v) { - draw_square(dr, ds, &state->p, state->map, x, y, v); + draw_square(dr, ds, &state->p, state->map, x, y, v, ui->large_stipples); ds->drawn[y*w+x] = v; } } @@ -3048,15 +3132,7 @@ static float game_flash_length(const game_state *oldstate, { if (!oldstate->completed && newstate->completed && !oldstate->cheated && !newstate->cheated) { - if (flash_type < 0) { - char *env = getenv("MAP_ALTERNATIVE_FLASH"); - if (env) - flash_type = atoi(env); - else - flash_type = 0; - flash_length = (flash_type == 1 ? 0.50F : 0.30F); - } - return flash_length; + return flash_length(ui); } else return 0.0F; } @@ -3079,12 +3155,8 @@ 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; @@ -3093,12 +3165,13 @@ static void game_print_size(const game_params *params, float *x, float *y) * compute this size is to compute the pixel puzzle size at a * given tile size and then scale. */ - game_compute_size(params, 400, &pw, &ph); + game_compute_size(params, 400, 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->p.w, h = state->p.h, wh = w*h, n = state->p.n; int ink, c[FOUR], i; @@ -3257,13 +3330,15 @@ const struct game thegame = { dup_game, free_game, true, solve_game, - false, game_can_format_as_text_now, game_text_format, + false, NULL, NULL, /* can_format_as_text_now, text_format */ + 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, 20, game_compute_size, game_set_size, @@ -3277,7 +3352,7 @@ const struct game thegame = { game_status, true, true, game_print_size, game_print, false, /* wants_statusbar */ - false, game_timing_state, + false, NULL, /* timing_state */ 0, /* flags */ }; -- cgit v1.2.3