diff options
| author | Franklin Wei <git@fwei.tk> | 2018-04-17 18:18:40 -0400 |
|---|---|---|
| committer | Franklin Wei <git@fwei.tk> | 2018-04-17 18:20:12 -0400 |
| commit | 6dc9d1d7309613fdc0518c03b2cdf3253d4c52fd (patch) | |
| tree | f6c9abf4a05f640134aa3d248a9498a06f596712 /apps/plugins | |
| parent | 7a12e796a62b916dae5de1b2986e8cb11ac5be4c (diff) | |
| download | rockbox-6dc9d1d7309613fdc0518c03b2cdf3253d4c52fd.zip rockbox-6dc9d1d7309613fdc0518c03b2cdf3253d4c52fd.tar.gz rockbox-6dc9d1d7309613fdc0518c03b2cdf3253d4c52fd.tar.bz2 rockbox-6dc9d1d7309613fdc0518c03b2cdf3253d4c52fd.tar.xz | |
puzzles: enable all the remaining games
They all work now :). I merged in part of Chris Boyle's Android port of
Puzzles to give the front end a way to know what keys the back end needs.
This also re-syncs to the latest upstream sources.
Change-Id: Ie0409bbb32f617ae5abf4f81be3b45d1552db9bb
Diffstat (limited to 'apps/plugins')
46 files changed, 470 insertions, 103 deletions
diff --git a/apps/plugins/puzzles/README.rockbox b/apps/plugins/puzzles/README.rockbox index 012fe78..15bc550 100644 --- a/apps/plugins/puzzles/README.rockbox +++ b/apps/plugins/puzzles/README.rockbox @@ -51,3 +51,6 @@ March 2018: Added help styling. Changed from simple_viewer to display_text for displaying help text. compress.c now does additional processing on the help text, and also requires a slightly modified halibut. + +April 2018: Finished up the rest of the games. All work now! Surely +there's still bugs to fix, so stay tuned... diff --git a/apps/plugins/puzzles/SOURCES.games b/apps/plugins/puzzles/SOURCES.games index 2fbedae..1b958d0 100644 --- a/apps/plugins/puzzles/SOURCES.games +++ b/apps/plugins/puzzles/SOURCES.games @@ -1,17 +1,17 @@ -/* commented-out games cannot be played due to controls and/or bugs */ +/* every game works! :) */ src/blackbox.c src/bridges.c src/cube.c src/dominosa.c src/fifteen.c -//src/filling.c /* requires numeric input */ +src/filling.c src/flip.c src/flood.c src/galaxies.c src/guess.c src/inertia.c -//src/keen.c /* numeric input */ +src/keen.c src/lightup.c src/magnets.c src/map.c @@ -29,11 +29,11 @@ src/singles.c src/sixteen.c src/slant.c src/tents.c -//src/towers.c /* numeric input */ +src/towers.c src/tracks.c src/twiddle.c -//src/undead.c /* keyboard input */ -//src/unequal.c /* numeric input */ +src/undead.c +src/unequal.c src/unruly.c src/untangle.c @@ -46,6 +46,6 @@ src/untangle.c /* no c200v2 */ #if PLUGIN_BUFFER_SIZE > 0x14000 src/pearl.c -src/loopy.c /* mouse input */ -//src/solo.c /* numeric input */ +src/loopy.c +src/solo.c #endif diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c index cf0358c..9865407 100644 --- a/apps/plugins/puzzles/rockbox.c +++ b/apps/plugins/puzzles/rockbox.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2017 Franklin Wei + * Copyright (C) 2018 Franklin Wei * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -139,28 +139,36 @@ static int ud_l = 0, ud_u = 0, ud_r = LCD_WIDTH, ud_d = LCD_HEIGHT; static char *titlebar = NULL; +/* some games can run in a separate thread for a larger stack (only + * Solo for now) */ +static int thread = -1; + /* how to process the input (custom, per-game) */ static struct { - bool want_spacebar, falling_edge, ignore_repeats, rclick_on_hold; + bool want_spacebar; /* send spacebar event on long-press of select */ + bool falling_edge; /* send events upon button release, not initial press */ + bool ignore_repeats; /* ignore repeated button events (currently in all games but Untangle) */ + bool rclick_on_hold; /* if in mouse mode, send right-click on long-press of select */ + bool numerical_chooser; /* repurpose select to activate a numerical chooser */ } input_settings; static bool accept_input = true; /* last timer call */ static long last_tstamp; -static volatile bool timer_on = false; +static bool timer_on = false; static bool load_success; /* debug settings */ -/* did I mention there's a secret debug menu? */ +/* ...did I mention there's a secret debug menu? */ static struct { int slowmo_factor; bool timerflash, clipoff, shortcuts, no_aa, polyanim; } debug_settings; -/* re-implementations of many rockbox primitives, adapted to draw into - * a custom framebuffer. */ +/* These are re-implementations of many rockbox drawing functions, adapted to + * draw into a custom framebuffer (used for the zoom feature). */ static void zoom_drawpixel(int x, int y) { if(y < zoom_clipu || y >= zoom_clipd) @@ -323,7 +331,7 @@ static void zoom_alpha_bitmap(const unsigned char *bits, int x, int y, int w, in } } -/* font management */ +/* font management routines */ static struct bundled_font { int status; /* -3 = never tried loading, or unloaded, -2 = failed to load, >= -1: loaded successfully */ @@ -343,8 +351,7 @@ static void unload_fonts(void) loaded_fonts[i].status = -3; } access_counter = -1; - cur_font = FONT_UI; - rb->lcd_setfont(cur_font); + rb->lcd_setfont(cur_font = FONT_UI); } static void init_fonttab(void) @@ -451,7 +458,7 @@ fallback: return; } -/*** Drawing API ***/ +/*** Drawing API (normal, no zoom) ***/ static void offset_coords(int *x, int *y) { @@ -1138,6 +1145,8 @@ static void rb_draw_circle(void *handle, int cx, int cy, int radius, } } +/* blitters allow the game code to save/restore a piece of the + * framebuffer */ struct blitter { bool have_data; int x, y; @@ -1341,8 +1350,7 @@ static void draw_title(bool clear_first) } int w, h; - cur_font = FONT_UI; - rb->lcd_setfont(cur_font); + rb->lcd_setfont(cur_font = FONT_UI); rb->lcd_getstringsize(str, &w, &h); rb->lcd_set_foreground(BG_COLOR); @@ -1435,7 +1443,7 @@ const drawing_api rb_drawing = { NULL, }; -/** functions exported to puzzles code **/ +/** utility functions exported to puzzles code **/ void fatal(const char *fmt, ...) { @@ -1449,7 +1457,10 @@ void fatal(const char *fmt, ...) rb->splash(HZ * 2, buf); va_end(ap); - exit(1); + if(rb->thread_self() == thread) + rb->thread_exit(); + else + exit(PLUGIN_ERROR); } void get_random_seed(void **randseed, int *randseedsize) @@ -1457,7 +1468,7 @@ void get_random_seed(void **randseed, int *randseedsize) *randseed = snew(long); long seed = *rb->current_tick; rb->memcpy(*randseed, &seed, sizeof(seed)); - *randseedsize = sizeof(long); + *randseedsize = sizeof(seed); } static void timer_cb(void) @@ -1512,11 +1523,57 @@ static void send_click(int button, bool release) midend_process_key(me, x, y, button + 6); } +static int choose_key(void) +{ + char *game_keys = NULL; + + const game *gm = midend_which_game(me); + if(gm->request_keys) + game_keys = gm->request_keys(midend_get_params(me)); + + if(!game_keys) + return; + + int options = strlen(game_keys); + int sel = 0; + + while(1) + { + midend_process_key(me, 0, 0, game_keys[sel]); + midend_redraw(me); + rb->lcd_update(); + + int button = rb->button_get(true); + switch(button) + { + case BTN_LEFT: + if(--sel < 0) + sel = options - 1; + break; + case BTN_RIGHT: + if(++sel >= options) + sel = 0; + break; + case BTN_PAUSE: + return -1; + case BTN_FIRE: + midend_force_redraw(me); + rb->lcd_update(); + free(game_keys); + + /* the key has already been sent to the game */ + return 0; + } + } +} + /* This function handles most user input. It has specific workarounds * and fixes for certain games to allow them to work well on * Rockbox. It will either return a positive value that can be passed - * to the midend, or a negative flag value. Set do_pausemenu to false - * to just return -1 on BTN_PAUSE and do nothing else. */ + * to the midend, or a negative flag value. `do_pausemenu' sets how + * this function will handle BUTTON_PAUSE: if true, it will handle it + * all, otherwise it will simply return -1 and let the caller do the + * work (this is used for zoom mode). */ static int process_input(int tmo, bool do_pausemenu) { LOGF("process_input start"); @@ -1547,7 +1604,7 @@ static int process_input(int tmo, bool do_pausemenu) return 0; } - /* These games want a spacebar in this event. */ + /* These games want a spacebar in the event of a long press. */ if(!mouse_mode && input_settings.want_spacebar) return ' '; } @@ -1769,10 +1826,38 @@ static int process_input(int tmo, bool do_pausemenu) break; case BTN_FIRE: - if(!strcmp("Fifteen", midend_which_game(me)->name)) - state = 'h'; /* hint */ + if(input_settings.numerical_chooser) + { + if(choose_key() < 0) + { + if(do_pausemenu) + { + /* quick hack to preserve the clipping state */ + bool orig_clipped = clipped; + if(orig_clipped) + rb_unclip(NULL); + + int rc = pause_menu(); + + if(orig_clipped) + rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); + + last_keystate = 0; + accept_input = true; + + return rc; + } + else + return -1; + } + } else - state = CURSOR_SELECT; + { + if(!strcmp("Fifteen", midend_which_game(me)->name)) + state = 'h'; /* hint */ + else + state = CURSOR_SELECT; + } break; default: @@ -1807,7 +1892,8 @@ static int process_input(int tmo, bool do_pausemenu) return state; } -/* either pan around a zoomed-in image or play zoomed-in */ +/* This function handles zoom mode, where the user can either pan + * around a zoomed-in image or play a zoomed-in version of the game. */ static void zoom(void) { rb->splash(0, "Please wait..."); @@ -1955,6 +2041,8 @@ static void zoom(void) } } +/** settings/preset code */ + static const char *config_choices_formatter(int sel, void *data, char *buf, size_t len) { /* we can't rely on being called in any particular order */ @@ -2176,8 +2264,7 @@ static bool config_menu(void) char *title; config_item *config = midend_get_config(me, CFG_SETTINGS, &title); - cur_font = FONT_UI; - rb->lcd_setfont(cur_font); + rb->lcd_setfont(cur_font = FONT_UI); bool success = false; @@ -2372,13 +2459,15 @@ static void full_help(const char *name) rb->lcd_set_foreground(LCD_WHITE); rb->lcd_set_background(LCD_BLACK); unload_fonts(); - cur_font = FONT_UI; - rb->lcd_setfont(cur_font); + rb->lcd_setfont(cur_font = FONT_UI); + /* The help text is stored in compressed format in the help_text[] + * array. display_text wants an array of pointers to + * null-terminated words, so we create that here. */ char *buf = smalloc(help_text_len); LZ4_decompress_tiny(help_text, buf, help_text_len); - /* fill the word_ptrs array to pass to display_text */ + /* create the word_ptrs array to pass to display_text */ char **word_ptrs = smalloc(sizeof(char*) * help_text_words); char **ptr = word_ptrs; bool last_was_null = false; @@ -2428,6 +2517,7 @@ static void init_default_settings(void) } #ifdef DEBUG_MENU +/* Useless debug code. Mostly a waste of space. */ static void bench_aa(void) { rb->sleep(0); @@ -2738,8 +2828,7 @@ static size_t giant_buffer_len = 0; /* set on start */ static void fix_size(void) { int w = LCD_WIDTH, h = LCD_HEIGHT, h_x; - cur_font = FONT_UI; - rb->lcd_setfont(cur_font); + rb->lcd_setfont(cur_font = FONT_UI); rb->lcd_getstringsize("X", NULL, &h_x); h -= h_x; midend_size(me, &w, &h, TRUE); @@ -2798,10 +2887,9 @@ static bool string_in_list(const char *target, const char **list) return false; } +/* this function sets game-specific input settings */ static void tune_input(const char *name) { - /* game-specific stuff */ - static const char *want_spacebar[] = { "Magnets", "Mines", @@ -2824,7 +2912,8 @@ static void tune_input(const char *name) /* wait until a key is released to send an action */ input_settings.falling_edge = string_in_list(name, falling_edge); - /* in all games but untangle (mouse mode overrides this) */ + /* ignore repeated keypresses in all games but untangle (mouse + * mode overrides this no matter what) */ static const char *ignore_repeats[] = { "Untangle", NULL @@ -2848,6 +2937,18 @@ static void tune_input(const char *name) }; mouse_mode = string_in_list(name, mouse_games); + + static const char *number_chooser_games[] = { + "Filling", + "Keen", + "Solo", + "Towers", + "Undead", + "Unequal", + NULL + }; + + input_settings.numerical_chooser = string_in_list(name, number_chooser_games); } static const char *init_for_game(const game *gm, int load_fd, bool draw) @@ -3143,21 +3244,10 @@ static int mainmenu_cb(int action, const struct menu_item_ex *this_item) return action; } -enum plugin_status plugin_start(const void *param) +static void puzzles_main(void) { - (void) param; - -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - /* boost for init */ - rb->cpu_boost(true); -#endif - - giant_buffer = rb->plugin_get_buffer(&giant_buffer_len); - rb_atexit(exit_handler); - init_tlsf(); - init_default_settings(); init_fonttab(); @@ -3255,7 +3345,7 @@ enum plugin_status plugin_start(const void *param) case 7: /* we don't care about freeing anything because tlsf will * be wiped out the next time around */ - return PLUGIN_OK; + return; default: break; } @@ -3294,13 +3384,13 @@ enum plugin_status plugin_start(const void *param) /* quit without saving */ midend_free(me); sfree(colors); - exit(PLUGIN_OK); + return; case -3: /* save and quit */ save_game(); midend_free(me); sfree(colors); - exit(PLUGIN_OK); + return; default: break; } @@ -3329,3 +3419,35 @@ enum plugin_status plugin_start(const void *param) sfree(colors); } } + +enum plugin_status plugin_start(const void *param) +{ + (void) param; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + /* boost for init */ + rb->cpu_boost(true); +#endif + + giant_buffer = rb->plugin_get_buffer(&giant_buffer_len); + init_tlsf(); + + if(!strcmp(thegame.name, "Solo")) + { + /* Solo needs a big stack */ + int stack_sz = 16 * DEFAULT_STACK_SIZE; + uintptr_t old = smalloc(stack_sz); + + /* word alignment */ + long *stack = ((uintptr_t)old & (uintptr_t)(~0x3)) + 4; + stack_sz -= ((char*)stack - (char*)old); + + thread = rb->create_thread(puzzles_main, stack, stack_sz, 0, "puzzles" + IF_PRIO(, PRIORITY_USER_INTERFACE) IF_COP(, CPU)); + rb->thread_wait(thread); + } + else + puzzles_main(); + + return PLUGIN_OK; +} diff --git a/apps/plugins/puzzles/src/blackbox.c b/apps/plugins/puzzles/src/blackbox.c index ffc7f7c..caaa9eb 100644 --- a/apps/plugins/puzzles/src/blackbox.c +++ b/apps/plugins/puzzles/src/blackbox.c @@ -1521,6 +1521,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/bridges.c b/apps/plugins/puzzles/src/bridges.c index 0a326f2..098fea2 100644 --- a/apps/plugins/puzzles/src/bridges.c +++ b/apps/plugins/puzzles/src/bridges.c @@ -3238,6 +3238,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/cube.c b/apps/plugins/puzzles/src/cube.c index 2a79a81..7491565 100644 --- a/apps/plugins/puzzles/src/cube.c +++ b/apps/plugins/puzzles/src/cube.c @@ -1751,6 +1751,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/dominosa.c b/apps/plugins/puzzles/src/dominosa.c index a072ac6..73b744d 100644 --- a/apps/plugins/puzzles/src/dominosa.c +++ b/apps/plugins/puzzles/src/dominosa.c @@ -1723,6 +1723,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/fifteen.c b/apps/plugins/puzzles/src/fifteen.c index f749e96..35bcb32 100644 --- a/apps/plugins/puzzles/src/fifteen.c +++ b/apps/plugins/puzzles/src/fifteen.c @@ -1103,6 +1103,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/filling.c b/apps/plugins/puzzles/src/filling.c index 231de4c..c331dac 100644 --- a/apps/plugins/puzzles/src/filling.c +++ b/apps/plugins/puzzles/src/filling.c @@ -1287,6 +1287,11 @@ static const char *validate_desc(const game_params *params, const char *desc) return (area < sz) ? "Not enough data to fill grid" : NULL; } +static char *game_request_keys(const game_params *params) +{ + return dupstr("1234567890\b"); +} + static game_state *new_game(midend *me, const game_params *params, const char *desc) { @@ -2125,6 +2130,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + game_request_keys, game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/flip.c b/apps/plugins/puzzles/src/flip.c index 9e5716a..481c4b5 100644 --- a/apps/plugins/puzzles/src/flip.c +++ b/apps/plugins/puzzles/src/flip.c @@ -1327,6 +1327,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/flood.c b/apps/plugins/puzzles/src/flood.c index 854469a..840ff64 100644 --- a/apps/plugins/puzzles/src/flood.c +++ b/apps/plugins/puzzles/src/flood.c @@ -1350,6 +1350,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/galaxies.c b/apps/plugins/puzzles/src/galaxies.c index 5d7dbdf..11efd34 100644 --- a/apps/plugins/puzzles/src/galaxies.c +++ b/apps/plugins/puzzles/src/galaxies.c @@ -3652,6 +3652,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/guess.c b/apps/plugins/puzzles/src/guess.c index a14f3bd..98019d2 100644 --- a/apps/plugins/puzzles/src/guess.c +++ b/apps/plugins/puzzles/src/guess.c @@ -1493,6 +1493,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/inertia.c b/apps/plugins/puzzles/src/inertia.c index 22cf235..8206d00 100644 --- a/apps/plugins/puzzles/src/inertia.c +++ b/apps/plugins/puzzles/src/inertia.c @@ -2234,6 +2234,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/keen.c b/apps/plugins/puzzles/src/keen.c index ec7af12..f3142a0 100644 --- a/apps/plugins/puzzles/src/keen.c +++ b/apps/plugins/puzzles/src/keen.c @@ -1251,6 +1251,20 @@ static const char *validate_desc(const game_params *params, const char *desc) return NULL; } +static char *game_request_keys(const game_params *params) +{ + int i; + int w = params->w; + char *keys = smalloc(w+2); + for (i = 0; i < w; i++) { + if (i<9) keys[i] = '1' + i; + else keys[i] = 'a' + i - 9; + } + keys[w] = '\b'; + keys[w+1] = '\0'; + return keys; +} + static game_state *new_game(midend *me, const game_params *params, const char *desc) { @@ -2354,6 +2368,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + game_request_keys, game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/latin.c b/apps/plugins/puzzles/src/latin.c index 50fae3c..716064c 100644 --- a/apps/plugins/puzzles/src/latin.c +++ b/apps/plugins/puzzles/src/latin.c @@ -964,13 +964,30 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff, got_result: #ifdef STANDALONE_SOLVER - if (solver_show_working) - printf("%*s%s found\n", - solver_recurse_depth*4, "", - diff == diff_impossible ? "no solution (impossible)" : - diff == diff_unfinished ? "no solution (unfinished)" : - diff == diff_ambiguous ? "multiple solutions" : - "one solution"); + if (solver_show_working) { + if (diff != diff_impossible && diff != diff_unfinished && + diff != diff_ambiguous) { + int x, y; + + printf("%*sone solution found:\n", solver_recurse_depth*4, ""); + + for (y = 0; y < solver->o; y++) { + printf("%*s", solver_recurse_depth*4+1, ""); + for (x = 0; x < solver->o; x++) { + int val = solver->grid[y*solver->o+x]; + assert(val); + printf(" %s", solver->names[val-1]); + } + printf("\n"); + } + } else { + printf("%*s%s found\n", + solver_recurse_depth*4, "", + diff == diff_impossible ? "no solution (impossible)" : + diff == diff_unfinished ? "no solution (unfinished)" : + "multiple solutions"); + } + } #endif latin_solver_free_scratch(scratch); diff --git a/apps/plugins/puzzles/src/lightup.c b/apps/plugins/puzzles/src/lightup.c index fd363e0..acee853 100644 --- a/apps/plugins/puzzles/src/lightup.c +++ b/apps/plugins/puzzles/src/lightup.c @@ -2303,6 +2303,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/loopy.c b/apps/plugins/puzzles/src/loopy.c index c14412d..f5f8917 100644 --- a/apps/plugins/puzzles/src/loopy.c +++ b/apps/plugins/puzzles/src/loopy.c @@ -3658,6 +3658,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/magnets.c b/apps/plugins/puzzles/src/magnets.c index ebb5376..c9f97b6 100644 --- a/apps/plugins/puzzles/src/magnets.c +++ b/apps/plugins/puzzles/src/magnets.c @@ -2409,6 +2409,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/map.c b/apps/plugins/puzzles/src/map.c index 3a16335..2037510 100644 --- a/apps/plugins/puzzles/src/map.c +++ b/apps/plugins/puzzles/src/map.c @@ -3236,6 +3236,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/midend.c b/apps/plugins/puzzles/src/midend.c index 4ccb1f9..0c91388 100644 --- a/apps/plugins/puzzles/src/midend.c +++ b/apps/plugins/puzzles/src/midend.c @@ -25,6 +25,11 @@ struct midend_state_entry { int movetype; }; +struct midend_serialise_buf { + char *buf; + int len, size; +}; + struct midend { frontend *frontend; random_state *random; @@ -63,8 +68,8 @@ struct midend { int nstates, statesize, statepos; struct midend_state_entry *states; - char *newgame_undo_buf; - int newgame_undo_len, newgame_undo_size; + struct midend_serialise_buf newgame_undo, newgame_redo; + int newgame_can_store_undo; game_params *params, *curparams; game_drawstate *drawstate; @@ -158,8 +163,11 @@ midend *midend_new(frontend *fe, const game *ourgame, me->random = random_new(randseed, randseedsize); me->nstates = me->statesize = me->statepos = 0; me->states = NULL; - me->newgame_undo_buf = NULL; - me->newgame_undo_size = me->newgame_undo_len = 0; + me->newgame_undo.buf = NULL; + me->newgame_undo.size = me->newgame_undo.len = 0; + me->newgame_redo.buf = NULL; + me->newgame_redo.size = me->newgame_redo.len = 0; + me->newgame_can_store_undo = FALSE; me->params = ourgame->default_params(); me->game_id_change_notify_function = NULL; me->game_id_change_notify_ctx = NULL; @@ -221,6 +229,7 @@ static void midend_purge_states(midend *me) if (me->states[me->nstates].movestr) sfree(me->states[me->nstates].movestr); } + me->newgame_redo.len = 0; } static void midend_free_game(midend *me) @@ -257,7 +266,8 @@ void midend_free(midend *me) if (me->drawing) drawing_free(me->drawing); random_free(me->random); - sfree(me->newgame_undo_buf); + sfree(me->newgame_undo.buf); + sfree(me->newgame_redo.buf); sfree(me->states); sfree(me->desc); sfree(me->privdesc); @@ -386,36 +396,39 @@ void midend_force_redraw(midend *me) static void newgame_serialise_write(void *ctx, const void *buf, int len) { - midend *const me = ctx; + struct midend_serialise_buf *ser = (struct midend_serialise_buf *)ctx; int new_len; - assert(len < INT_MAX - me->newgame_undo_len); - new_len = me->newgame_undo_len + len; - if (new_len > me->newgame_undo_size) { - me->newgame_undo_size = new_len + new_len / 4 + 1024; - me->newgame_undo_buf = sresize(me->newgame_undo_buf, - me->newgame_undo_size, char); + assert(len < INT_MAX - ser->len); + new_len = ser->len + len; + if (new_len > ser->size) { + ser->size = new_len + new_len / 4 + 1024; + ser->buf = sresize(ser->buf, ser->size, char); } - memcpy(me->newgame_undo_buf + me->newgame_undo_len, buf, len); - me->newgame_undo_len = new_len; + memcpy(ser->buf + ser->len, buf, len); + ser->len = new_len; } void midend_new_game(midend *me) { - me->newgame_undo_len = 0; - if (me->nstates != 0) { + me->newgame_undo.len = 0; + if (me->newgame_can_store_undo) { /* * Serialise the whole of the game that we're about to * supersede, so that the 'New Game' action can be undone - * later. But if nstates == 0, that means there _isn't_ a - * current game (not even a starting position), because this - * is the initial call to midend_new_game when the midend is - * first set up; in that situation, we want to avoid writing - * out any serialisation, because it would be useless anyway - * and just confuse us into thinking we had something to undo - * to. + * later. + * + * We omit this in various situations, such as if there + * _isn't_ a current game (not even a starting position) + * because this is the initial call to midend_new_game when + * the midend is first set up, or if the midend state has + * already begun to be overwritten by midend_set_config. In + * those situations, we want to avoid writing out any + * serialisation, because they will be either invalid, or + * worse, valid but wrong. */ - midend_serialise(me, newgame_serialise_write, me); + midend_purge_states(me); + midend_serialise(me, newgame_serialise_write, &me->newgame_undo); } midend_stop_anim(me); @@ -528,30 +541,31 @@ void midend_new_game(midend *me) if (me->game_id_change_notify_function) me->game_id_change_notify_function(me->game_id_change_notify_ctx); + + me->newgame_can_store_undo = TRUE; } int midend_can_undo(midend *me) { - return (me->statepos > 1 || me->newgame_undo_len); + return (me->statepos > 1 || me->newgame_undo.len); } int midend_can_redo(midend *me) { - return (me->statepos < me->nstates); + return (me->statepos < me->nstates || me->newgame_redo.len); } struct newgame_undo_deserialise_read_ctx { - midend *me; + struct midend_serialise_buf *ser; int len, pos; }; static int newgame_undo_deserialise_read(void *ctx, void *buf, int len) { struct newgame_undo_deserialise_read_ctx *const rctx = ctx; - midend *const me = rctx->me; int use = min(len, rctx->len - rctx->pos); - memcpy(buf, me->newgame_undo_buf + rctx->pos, use); + memcpy(buf, rctx->ser->buf + rctx->pos, use); rctx->pos += use; return use; } @@ -624,12 +638,22 @@ static int midend_undo(midend *me) me->statepos--; me->dir = -1; return 1; - } else if (me->newgame_undo_len) { - /* This undo cannot be undone with redo */ + } else if (me->newgame_undo.len) { struct newgame_undo_deserialise_read_ctx rctx; struct newgame_undo_deserialise_check_ctx cctx; - rctx.me = me; - rctx.len = me->newgame_undo_len; /* copy for reentrancy safety */ + struct midend_serialise_buf serbuf; + + /* + * Serialise the current game so that you can later redo past + * this undo. Once we're committed to the undo actually + * happening, we'll copy this data into place. + */ + serbuf.buf = NULL; + serbuf.len = serbuf.size = 0; + midend_serialise(me, newgame_serialise_write, &serbuf); + + rctx.ser = &me->newgame_undo; + rctx.len = me->newgame_undo.len; /* copy for reentrancy safety */ rctx.pos = 0; cctx.refused = FALSE; deserialise_error = midend_deserialise_internal( @@ -642,6 +666,7 @@ static int midend_undo(midend *me) * contain the dummy error message generated by our check * function, which we ignore.) */ + sfree(serbuf.buf); return 0; } else { /* @@ -652,6 +677,22 @@ static int midend_undo(midend *me) * replaced by the wrong file, etc., by user error. */ assert(!deserialise_error); + + /* + * Clear the old newgame_undo serialisation, so that we + * don't try to undo past the beginning of the game we've + * just gone back to and end up at the front of it again. + */ + me->newgame_undo.len = 0; + + /* + * Copy the serialisation of the game we've just left into + * the midend so that we can redo back into it later. + */ + me->newgame_redo.len = 0; + newgame_serialise_write(&me->newgame_redo, serbuf.buf, serbuf.len); + + sfree(serbuf.buf); return 1; } } else @@ -660,6 +701,8 @@ static int midend_undo(midend *me) static int midend_redo(midend *me) { + const char *deserialise_error; + if (me->statepos < me->nstates) { if (me->ui) me->ourgame->changed_state(me->ui, @@ -668,6 +711,63 @@ static int midend_redo(midend *me) me->statepos++; me->dir = +1; return 1; + } else if (me->newgame_redo.len) { + struct newgame_undo_deserialise_read_ctx rctx; + struct newgame_undo_deserialise_check_ctx cctx; + struct midend_serialise_buf serbuf; + + /* + * Serialise the current game so that you can later undo past + * this redo. Once we're committed to the undo actually + * happening, we'll copy this data into place. + */ + serbuf.buf = NULL; + serbuf.len = serbuf.size = 0; + midend_serialise(me, newgame_serialise_write, &serbuf); + + rctx.ser = &me->newgame_redo; + rctx.len = me->newgame_redo.len; /* copy for reentrancy safety */ + rctx.pos = 0; + cctx.refused = FALSE; + deserialise_error = midend_deserialise_internal( + me, newgame_undo_deserialise_read, &rctx, + newgame_undo_deserialise_check, &cctx); + if (cctx.refused) { + /* + * Our post-deserialisation check shows that we can't use + * this saved game after all. (deserialise_error will + * contain the dummy error message generated by our check + * function, which we ignore.) + */ + sfree(serbuf.buf); + return 0; + } else { + /* + * There should never be any _other_ deserialisation + * error, because this serialised data has been held in + * our memory since it was created, and hasn't had any + * opportunity to be corrupted on disk, accidentally + * replaced by the wrong file, etc., by user error. + */ + assert(!deserialise_error); + + /* + * Clear the old newgame_redo serialisation, so that we + * don't try to redo past the end of the game we've just + * come into and end up at the back of it again. + */ + me->newgame_redo.len = 0; + + /* + * Copy the serialisation of the game we've just left into + * the midend so that we can undo back into it later. + */ + me->newgame_undo.len = 0; + newgame_serialise_write(&me->newgame_undo, serbuf.buf, serbuf.len); + + sfree(serbuf.buf); + return 1; + } } else return 0; } @@ -1587,6 +1687,8 @@ static const char *midend_game_id_int(midend *me, const char *id, int defmode) sfree(par); + me->newgame_can_store_undo = FALSE; + return NULL; } @@ -2224,13 +2326,14 @@ static const char *midend_deserialise_internal( me->statepos = data.statepos; /* - * Don't save the "new game undo" state. So "new game" twice or + * Don't save the "new game undo/redo" state. So "new game" twice or * (in some environments) switching away and back, will make a * "new game" irreversible. Maybe in the future we will have a * more sophisticated way to decide when to discard the previous * game state. */ - me->newgame_undo_len = 0; + me->newgame_undo.len = 0; + me->newgame_redo.len = 0; { game_params *tmp; diff --git a/apps/plugins/puzzles/src/mines.c b/apps/plugins/puzzles/src/mines.c index 340bb4b..0a66a25 100644 --- a/apps/plugins/puzzles/src/mines.c +++ b/apps/plugins/puzzles/src/mines.c @@ -3193,6 +3193,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/net.c b/apps/plugins/puzzles/src/net.c index ab2425e..2242633 100644 --- a/apps/plugins/puzzles/src/net.c +++ b/apps/plugins/puzzles/src/net.c @@ -3239,6 +3239,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/netslide.c b/apps/plugins/puzzles/src/netslide.c index d4d9856..af4ef7d 100644 --- a/apps/plugins/puzzles/src/netslide.c +++ b/apps/plugins/puzzles/src/netslide.c @@ -1871,6 +1871,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/nullgame.c b/apps/plugins/puzzles/src/nullgame.c index bc19c1e..13c464c 100644 --- a/apps/plugins/puzzles/src/nullgame.c +++ b/apps/plugins/puzzles/src/nullgame.c @@ -285,6 +285,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/palisade.c b/apps/plugins/puzzles/src/palisade.c index e495bbe..0be8ac5 100644 --- a/apps/plugins/puzzles/src/palisade.c +++ b/apps/plugins/puzzles/src/palisade.c @@ -1390,6 +1390,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/pattern.c b/apps/plugins/puzzles/src/pattern.c index e067f3a..f715df4 100644 --- a/apps/plugins/puzzles/src/pattern.c +++ b/apps/plugins/puzzles/src/pattern.c @@ -2000,6 +2000,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/pearl.c b/apps/plugins/puzzles/src/pearl.c index 4f3be50..07949b5 100644 --- a/apps/plugins/puzzles/src/pearl.c +++ b/apps/plugins/puzzles/src/pearl.c @@ -2621,6 +2621,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/pegs.c b/apps/plugins/puzzles/src/pegs.c index 565ba94..823d5c2 100644 --- a/apps/plugins/puzzles/src/pegs.c +++ b/apps/plugins/puzzles/src/pegs.c @@ -1316,6 +1316,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/puzzles.h b/apps/plugins/puzzles/src/puzzles.h index 47f4b7e..031bdfc 100644 --- a/apps/plugins/puzzles/src/puzzles.h +++ b/apps/plugins/puzzles/src/puzzles.h @@ -613,6 +613,7 @@ struct game { void (*free_ui)(game_ui *ui); char *(*encode_ui)(const game_ui *ui); void (*decode_ui)(game_ui *ui, const char *encoding); + char *(*request_keys)(const game_params *params); void (*changed_state)(game_ui *ui, const game_state *oldstate, const game_state *newstate); char *(*interpret_move)(const game_state *state, game_ui *ui, diff --git a/apps/plugins/puzzles/src/range.c b/apps/plugins/puzzles/src/range.c index 41c6a35..03bdf6d 100644 --- a/apps/plugins/puzzles/src/range.c +++ b/apps/plugins/puzzles/src/range.c @@ -613,15 +613,16 @@ static move *solver_reasoning_recursion(game_state *state, /* FIXME: add enum alias for smallest and largest (or N) */ for (colour = M_BLACK; colour <= M_WHITE; ++colour) { newstate = dup_game(state); - newstate->grid[cell] = colour; + newstate->grid[cell] = colour == M_BLACK ? BLACK : WHITE; recursive_result = do_solve(newstate, nclues, clues, buf, DIFF_RECURSION); - free_game(newstate); if (recursive_result == NULL) { + free_game(newstate); solver_makemove(r, c, M_BLACK + M_WHITE - colour, state, &buf); return buf; } for (i = 0; i < n && newstate->grid[i] != EMPTY; ++i); + free_game(newstate); if (i == n) return buf; } } @@ -1812,6 +1813,7 @@ struct game const thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/rect.c b/apps/plugins/puzzles/src/rect.c index c9923ad..3d84f04 100644 --- a/apps/plugins/puzzles/src/rect.c +++ b/apps/plugins/puzzles/src/rect.c @@ -2977,6 +2977,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/samegame.c b/apps/plugins/puzzles/src/samegame.c index d7d0676..4dfc302 100644 --- a/apps/plugins/puzzles/src/samegame.c +++ b/apps/plugins/puzzles/src/samegame.c @@ -1656,6 +1656,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/signpost.c b/apps/plugins/puzzles/src/signpost.c index edcf63a..04a3152 100644 --- a/apps/plugins/puzzles/src/signpost.c +++ b/apps/plugins/puzzles/src/signpost.c @@ -2247,6 +2247,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/singles.c b/apps/plugins/puzzles/src/singles.c index 5929d82..35c5fce 100644 --- a/apps/plugins/puzzles/src/singles.c +++ b/apps/plugins/puzzles/src/singles.c @@ -1828,6 +1828,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/sixteen.c b/apps/plugins/puzzles/src/sixteen.c index 9fa7032..6bba4c8 100644 --- a/apps/plugins/puzzles/src/sixteen.c +++ b/apps/plugins/puzzles/src/sixteen.c @@ -1189,6 +1189,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/slant.c b/apps/plugins/puzzles/src/slant.c index 3fd6611..5eeb668 100644 --- a/apps/plugins/puzzles/src/slant.c +++ b/apps/plugins/puzzles/src/slant.c @@ -2164,6 +2164,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/solo.c b/apps/plugins/puzzles/src/solo.c index dd04cdf..ac0b807 100644 --- a/apps/plugins/puzzles/src/solo.c +++ b/apps/plugins/puzzles/src/solo.c @@ -510,7 +510,9 @@ static const char *validate_params(const game_params *params, int full) if ((params->c * params->r) > 31) return "Unable to support more than 31 distinct symbols in a puzzle"; if (params->killer && params->c * params->r > 9) - return "Killer puzzle dimensions must be smaller than 10."; + return "Killer puzzle dimensions must be smaller than 10"; + if (params->xtype && params->c * params->r < 4) + return "X-type puzzle dimensions must be larger than 3"; return NULL; } @@ -3103,6 +3105,8 @@ static int check_valid(int cr, struct block_structure *blocks, sfree(used); return FALSE; } + + memset(used, FALSE, cr); for (i = 0; i < cr; i++) if (grid[diag1(i)] > 0 && grid[diag1(i)] <= cr) used[grid[diag1(i)]-1] = TRUE; @@ -3604,6 +3608,20 @@ static struct block_structure *gen_killer_cages(int cr, random_state *rs, return b; } +static char *game_request_keys(const game_params *params) +{ + int i; + int cr = params->c * params->r; + char *keys = smalloc(cr+2); + for (i = 0; i < cr; i++) { + if (i<9) keys[i] = '1' + i; + else keys[i] = 'a' + i - 9; + } + keys[cr] = '\b'; + keys[cr+1] = '\0'; + return keys; +} + static char *new_game_desc(const game_params *params, random_state *rs, char **aux, int interactive) { @@ -5575,6 +5593,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + game_request_keys, game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/tents.c b/apps/plugins/puzzles/src/tents.c index 48d420e..48da7f2 100644 --- a/apps/plugins/puzzles/src/tents.c +++ b/apps/plugins/puzzles/src/tents.c @@ -2626,6 +2626,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/towers.c b/apps/plugins/puzzles/src/towers.c index 9ccc6ae..14b12c6 100644 --- a/apps/plugins/puzzles/src/towers.c +++ b/apps/plugins/puzzles/src/towers.c @@ -864,6 +864,21 @@ static const char *validate_desc(const game_params *params, const char *desc) return NULL; } +static char *game_request_keys(const game_params *params) +{ + int i; + int w = params->w; + char *keys = smalloc(w+2); + keys[0] = '\b'; + for (i = 0; i < w; i++) { + if (i<9) keys[i] = '1' + i; + else keys[i] = 'a' + i - 9; + } + keys[w] = '\b'; + keys[w+1] = '\0'; + return keys; +} + static game_state *new_game(midend *me, const game_params *params, const char *desc) { @@ -1993,6 +2008,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + game_request_keys, game_changed_state, interpret_move, execute_move, @@ -2072,6 +2088,17 @@ int main(int argc, char **argv) break; } + if (really_show_working) { + /* + * Now run the solver again at the last difficulty level we + * tried, but this time with diagnostics enabled. + */ + solver_show_working = really_show_working; + memcpy(s->grid, s->clues->immutable, p->w * p->w); + ret = solver(p->w, s->clues->clues, s->grid, + diff < DIFFCOUNT ? diff : DIFFCOUNT-1); + } + if (diff == DIFFCOUNT) { if (grade) printf("Difficulty rating: ambiguous\n"); @@ -2084,9 +2111,6 @@ int main(int argc, char **argv) else printf("Difficulty rating: %s\n", towers_diffnames[ret]); } else { - solver_show_working = really_show_working; - memcpy(s->grid, s->clues->immutable, p->w * p->w); - ret = solver(p->w, s->clues->clues, s->grid, diff); if (ret != diff) printf("Puzzle is inconsistent\n"); else diff --git a/apps/plugins/puzzles/src/tracks.c b/apps/plugins/puzzles/src/tracks.c index 3899007..5122f89 100644 --- a/apps/plugins/puzzles/src/tracks.c +++ b/apps/plugins/puzzles/src/tracks.c @@ -2646,6 +2646,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/twiddle.c b/apps/plugins/puzzles/src/twiddle.c index cbf6f76..600f445 100644 --- a/apps/plugins/puzzles/src/twiddle.c +++ b/apps/plugins/puzzles/src/twiddle.c @@ -1293,6 +1293,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/undead.c b/apps/plugins/puzzles/src/undead.c index df0735e..87e559a 100644 --- a/apps/plugins/puzzles/src/undead.c +++ b/apps/plugins/puzzles/src/undead.c @@ -1314,6 +1314,11 @@ void num2grid(int num, int width, int height, int *x, int *y) { return; } +static char *game_request_keys(const game_params *params) +{ + return dupstr("GVZ\b"); +} + static game_state *new_game(midend *me, const game_params *params, const char *desc) { @@ -2716,6 +2721,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + game_request_keys, game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/unequal.c b/apps/plugins/puzzles/src/unequal.c index cb477c9..d8204a4 100644 --- a/apps/plugins/puzzles/src/unequal.c +++ b/apps/plugins/puzzles/src/unequal.c @@ -1280,6 +1280,21 @@ fail: return NULL; } +static char *game_request_keys(const game_params *params) +{ + int order = params->order; + char off = (order > 9) ? '0' : '1'; + char *keys = smalloc(order + 2); + int i; + for(i = 0; i < order; i++) { + if (i==10) off = 'a'-10; + keys[i] = i + off; + } + keys[order] = '\b'; + keys[order+1] = '\0'; + return keys; +} + static game_state *new_game(midend *me, const game_params *params, const char *desc) { @@ -2011,6 +2026,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + game_request_keys, game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/unruly.c b/apps/plugins/puzzles/src/unruly.c index 9d2d592..b3057a7 100644 --- a/apps/plugins/puzzles/src/unruly.c +++ b/apps/plugins/puzzles/src/unruly.c @@ -1926,6 +1926,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, diff --git a/apps/plugins/puzzles/src/untangle.c b/apps/plugins/puzzles/src/untangle.c index 09c36fe..0bcd04d 100644 --- a/apps/plugins/puzzles/src/untangle.c +++ b/apps/plugins/puzzles/src/untangle.c @@ -1623,6 +1623,7 @@ const struct game thegame = { free_ui, encode_ui, decode_ui, + NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, |