diff options
Diffstat (limited to 'apps/plugins/puzzles/src/lightup.c')
-rw-r--r-- | apps/plugins/puzzles/src/lightup.c | 137 |
1 files changed, 95 insertions, 42 deletions
diff --git a/apps/plugins/puzzles/src/lightup.c b/apps/plugins/puzzles/src/lightup.c index ab4be9ec87..e779663e01 100644 --- a/apps/plugins/puzzles/src/lightup.c +++ b/apps/plugins/puzzles/src/lightup.c | |||
@@ -47,7 +47,12 @@ | |||
47 | #include <string.h> | 47 | #include <string.h> |
48 | #include <assert.h> | 48 | #include <assert.h> |
49 | #include <ctype.h> | 49 | #include <ctype.h> |
50 | #include <math.h> | 50 | #include <limits.h> |
51 | #ifdef NO_TGMATH_H | ||
52 | # include <math.h> | ||
53 | #else | ||
54 | # include <tgmath.h> | ||
55 | #endif | ||
51 | 56 | ||
52 | #include "puzzles.h" | 57 | #include "puzzles.h" |
53 | 58 | ||
@@ -58,7 +63,7 @@ | |||
58 | */ | 63 | */ |
59 | #if defined STANDALONE_SOLVER | 64 | #if defined STANDALONE_SOLVER |
60 | #define SOLVER_DIAGNOSTICS | 65 | #define SOLVER_DIAGNOSTICS |
61 | int verbose = 0; | 66 | static int verbose = 0; |
62 | #undef debug | 67 | #undef debug |
63 | #define debug(x) printf x | 68 | #define debug(x) printf x |
64 | #elif defined SOLVER_DIAGNOSTICS | 69 | #elif defined SOLVER_DIAGNOSTICS |
@@ -346,6 +351,8 @@ static const char *validate_params(const game_params *params, bool full) | |||
346 | { | 351 | { |
347 | if (params->w < 2 || params->h < 2) | 352 | if (params->w < 2 || params->h < 2) |
348 | return "Width and height must be at least 2"; | 353 | return "Width and height must be at least 2"; |
354 | if (params->w > INT_MAX / params->h) | ||
355 | return "Width times height must not be unreasonably large"; | ||
349 | if (full) { | 356 | if (full) { |
350 | if (params->blackpc < 5 || params->blackpc > 100) | 357 | if (params->blackpc < 5 || params->blackpc > 100) |
351 | return "Percentage of black squares must be between 5% and 100%"; | 358 | return "Percentage of black squares must be between 5% and 100%"; |
@@ -353,6 +360,8 @@ static const char *validate_params(const game_params *params, bool full) | |||
353 | if (params->symm == SYMM_ROT4) | 360 | if (params->symm == SYMM_ROT4) |
354 | return "4-fold symmetry is only available with square grids"; | 361 | return "4-fold symmetry is only available with square grids"; |
355 | } | 362 | } |
363 | if ((params->symm == SYMM_ROT4 || params->symm == SYMM_REF4) && params->w < 3 && params->h < 3) | ||
364 | return "Width or height must be at least 3 for 4-way symmetry"; | ||
356 | if (params->symm < 0 || params->symm >= SYMM_MAX) | 365 | if (params->symm < 0 || params->symm >= SYMM_MAX) |
357 | return "Unknown symmetry type"; | 366 | return "Unknown symmetry type"; |
358 | if (params->difficulty < 0 || params->difficulty > DIFFCOUNT) | 367 | if (params->difficulty < 0 || params->difficulty > DIFFCOUNT) |
@@ -411,6 +420,8 @@ static void debug_state(game_state *state) | |||
411 | int x, y; | 420 | int x, y; |
412 | char c = '?'; | 421 | char c = '?'; |
413 | 422 | ||
423 | (void)c; /* placate -Wunused-but-set-variable if debug() does nothing */ | ||
424 | |||
414 | for (y = 0; y < state->h; y++) { | 425 | for (y = 0; y < state->h; y++) { |
415 | for (x = 0; x < state->w; x++) { | 426 | for (x = 0; x < state->w; x++) { |
416 | c = '.'; | 427 | c = '.'; |
@@ -1822,30 +1833,65 @@ static char *game_text_format(const game_state *state) | |||
1822 | struct game_ui { | 1833 | struct game_ui { |
1823 | int cur_x, cur_y; | 1834 | int cur_x, cur_y; |
1824 | bool cur_visible; | 1835 | bool cur_visible; |
1836 | |||
1837 | /* | ||
1838 | * User preference: when a square contains both a black blob for | ||
1839 | * 'user is convinced this isn't a light' and a yellow highlight | ||
1840 | * for 'this square is lit by a light', both of which rule out it | ||
1841 | * being a light, should we still bother to show the blob? | ||
1842 | */ | ||
1843 | bool draw_blobs_when_lit; | ||
1825 | }; | 1844 | }; |
1826 | 1845 | ||
1846 | static void legacy_prefs_override(struct game_ui *ui_out) | ||
1847 | { | ||
1848 | static bool initialised = false; | ||
1849 | static int draw_blobs_when_lit = -1; | ||
1850 | |||
1851 | if (!initialised) { | ||
1852 | initialised = true; | ||
1853 | draw_blobs_when_lit = getenv_bool("LIGHTUP_LIT_BLOBS", -1); | ||
1854 | } | ||
1855 | |||
1856 | if (draw_blobs_when_lit != -1) | ||
1857 | ui_out->draw_blobs_when_lit = draw_blobs_when_lit; | ||
1858 | } | ||
1859 | |||
1827 | static game_ui *new_ui(const game_state *state) | 1860 | static game_ui *new_ui(const game_state *state) |
1828 | { | 1861 | { |
1829 | game_ui *ui = snew(game_ui); | 1862 | game_ui *ui = snew(game_ui); |
1830 | ui->cur_x = ui->cur_y = 0; | 1863 | ui->cur_x = ui->cur_y = 0; |
1831 | ui->cur_visible = false; | 1864 | ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); |
1865 | ui->draw_blobs_when_lit = true; | ||
1866 | legacy_prefs_override(ui); | ||
1832 | return ui; | 1867 | return ui; |
1833 | } | 1868 | } |
1834 | 1869 | ||
1835 | static void free_ui(game_ui *ui) | 1870 | static config_item *get_prefs(game_ui *ui) |
1836 | { | 1871 | { |
1837 | sfree(ui); | 1872 | config_item *ret; |
1873 | |||
1874 | ret = snewn(2, config_item); | ||
1875 | |||
1876 | ret[0].name = "Draw non-light marks even when lit"; | ||
1877 | ret[0].kw = "show-lit-blobs"; | ||
1878 | ret[0].type = C_BOOLEAN; | ||
1879 | ret[0].u.boolean.bval = ui->draw_blobs_when_lit; | ||
1880 | |||
1881 | ret[1].name = NULL; | ||
1882 | ret[1].type = C_END; | ||
1883 | |||
1884 | return ret; | ||
1838 | } | 1885 | } |
1839 | 1886 | ||
1840 | static char *encode_ui(const game_ui *ui) | 1887 | static void set_prefs(game_ui *ui, const config_item *cfg) |
1841 | { | 1888 | { |
1842 | /* nothing to encode. */ | 1889 | ui->draw_blobs_when_lit = cfg[0].u.boolean.bval; |
1843 | return NULL; | ||
1844 | } | 1890 | } |
1845 | 1891 | ||
1846 | static void decode_ui(game_ui *ui, const char *encoding) | 1892 | static void free_ui(game_ui *ui) |
1847 | { | 1893 | { |
1848 | /* nothing to decode. */ | 1894 | sfree(ui); |
1849 | } | 1895 | } |
1850 | 1896 | ||
1851 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | 1897 | static void game_changed_state(game_ui *ui, const game_state *oldstate, |
@@ -1855,6 +1901,26 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate, | |||
1855 | ui->cur_visible = false; | 1901 | ui->cur_visible = false; |
1856 | } | 1902 | } |
1857 | 1903 | ||
1904 | static const char *current_key_label(const game_ui *ui, | ||
1905 | const game_state *state, int button) | ||
1906 | { | ||
1907 | int cx = ui->cur_x, cy = ui->cur_y; | ||
1908 | unsigned int flags = GRID(state, flags, cx, cy); | ||
1909 | |||
1910 | if (!ui->cur_visible) return ""; | ||
1911 | if (button == CURSOR_SELECT) { | ||
1912 | if (flags & (F_BLACK | F_IMPOSSIBLE)) return ""; | ||
1913 | if (flags & F_LIGHT) return "Clear"; | ||
1914 | return "Light"; | ||
1915 | } | ||
1916 | if (button == CURSOR_SELECT2) { | ||
1917 | if (flags & (F_BLACK | F_LIGHT)) return ""; | ||
1918 | if (flags & F_IMPOSSIBLE) return "Clear"; | ||
1919 | return "Mark"; | ||
1920 | } | ||
1921 | return ""; | ||
1922 | } | ||
1923 | |||
1858 | #define DF_BLACK 1 /* black square */ | 1924 | #define DF_BLACK 1 /* black square */ |
1859 | #define DF_NUMBERED 2 /* black square with number */ | 1925 | #define DF_NUMBERED 2 /* black square with number */ |
1860 | #define DF_LIT 4 /* display (white) square lit up */ | 1926 | #define DF_LIT 4 /* display (white) square lit up */ |
@@ -1888,7 +1954,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
1888 | enum { NONE, FLIP_LIGHT, FLIP_IMPOSSIBLE } action = NONE; | 1954 | enum { NONE, FLIP_LIGHT, FLIP_IMPOSSIBLE } action = NONE; |
1889 | int cx = -1, cy = -1; | 1955 | int cx = -1, cy = -1; |
1890 | unsigned int flags; | 1956 | unsigned int flags; |
1891 | char buf[80], *nullret = UI_UPDATE, *empty = UI_UPDATE, c; | 1957 | char buf[80], *nullret = MOVE_NO_EFFECT, *empty = MOVE_UI_UPDATE, c; |
1892 | 1958 | ||
1893 | if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { | 1959 | if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { |
1894 | if (ui->cur_visible) | 1960 | if (ui->cur_visible) |
@@ -1898,8 +1964,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
1898 | cy = FROMCOORD(y); | 1964 | cy = FROMCOORD(y); |
1899 | action = (button == LEFT_BUTTON) ? FLIP_LIGHT : FLIP_IMPOSSIBLE; | 1965 | action = (button == LEFT_BUTTON) ? FLIP_LIGHT : FLIP_IMPOSSIBLE; |
1900 | } else if (IS_CURSOR_SELECT(button) || | 1966 | } else if (IS_CURSOR_SELECT(button) || |
1901 | button == 'i' || button == 'I' || | 1967 | button == 'i' || button == 'I') { |
1902 | button == ' ' || button == '\r' || button == '\n') { | ||
1903 | if (ui->cur_visible) { | 1968 | if (ui->cur_visible) { |
1904 | /* Only allow cursor-effect operations if the cursor is visible | 1969 | /* Only allow cursor-effect operations if the cursor is visible |
1905 | * (otherwise you have no idea which square it might be affecting) */ | 1970 | * (otherwise you have no idea which square it might be affecting) */ |
@@ -1910,11 +1975,10 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
1910 | } | 1975 | } |
1911 | ui->cur_visible = true; | 1976 | ui->cur_visible = true; |
1912 | } else if (IS_CURSOR_MOVE(button)) { | 1977 | } else if (IS_CURSOR_MOVE(button)) { |
1913 | move_cursor(button, &ui->cur_x, &ui->cur_y, state->w, state->h, false); | 1978 | nullret = move_cursor(button, &ui->cur_x, &ui->cur_y, |
1914 | ui->cur_visible = true; | 1979 | state->w, state->h, false, &ui->cur_visible); |
1915 | nullret = empty; | ||
1916 | } else | 1980 | } else |
1917 | return NULL; | 1981 | return MOVE_UNUSED; |
1918 | 1982 | ||
1919 | switch (action) { | 1983 | switch (action) { |
1920 | case FLIP_LIGHT: | 1984 | case FLIP_LIGHT: |
@@ -2002,7 +2066,7 @@ badmove: | |||
2002 | 2066 | ||
2003 | /* XXX entirely cloned from fifteen.c; separate out? */ | 2067 | /* XXX entirely cloned from fifteen.c; separate out? */ |
2004 | static void game_compute_size(const game_params *params, int tilesize, | 2068 | static void game_compute_size(const game_params *params, int tilesize, |
2005 | int *x, int *y) | 2069 | const game_ui *ui, int *x, int *y) |
2006 | { | 2070 | { |
2007 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | 2071 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ |
2008 | struct { int tilesize; } ads, *ds = &ads; | 2072 | struct { int tilesize; } ads, *ds = &ads; |
@@ -2111,7 +2175,7 @@ static unsigned int tile_flags(game_drawstate *ds, const game_state *state, | |||
2111 | return ret; | 2175 | return ret; |
2112 | } | 2176 | } |
2113 | 2177 | ||
2114 | static void tile_redraw(drawing *dr, game_drawstate *ds, | 2178 | static void tile_redraw(drawing *dr, game_drawstate *ds, const game_ui *ui, |
2115 | const game_state *state, int x, int y) | 2179 | const game_state *state, int x, int y) |
2116 | { | 2180 | { |
2117 | unsigned int ds_flags = GRID(ds, flags, x, y); | 2181 | unsigned int ds_flags = GRID(ds, flags, x, y); |
@@ -2141,13 +2205,7 @@ static void tile_redraw(drawing *dr, game_drawstate *ds, | |||
2141 | draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, TILE_RADIUS, | 2205 | draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, TILE_RADIUS, |
2142 | lcol, COL_BLACK); | 2206 | lcol, COL_BLACK); |
2143 | } else if ((ds_flags & DF_IMPOSSIBLE)) { | 2207 | } else if ((ds_flags & DF_IMPOSSIBLE)) { |
2144 | static int draw_blobs_when_lit = -1; | 2208 | if (!(ds_flags & DF_LIT) || ui->draw_blobs_when_lit) { |
2145 | if (draw_blobs_when_lit < 0) { | ||
2146 | char *env = getenv("LIGHTUP_LIT_BLOBS"); | ||
2147 | draw_blobs_when_lit = (!env || (env[0] == 'y' || | ||
2148 | env[0] == 'Y')); | ||
2149 | } | ||
2150 | if (!(ds_flags & DF_LIT) || draw_blobs_when_lit) { | ||
2151 | int rlen = TILE_SIZE / 4; | 2209 | int rlen = TILE_SIZE / 4; |
2152 | draw_rect(dr, dx + TILE_SIZE/2 - rlen/2, | 2210 | draw_rect(dr, dx + TILE_SIZE/2 - rlen/2, |
2153 | dy + TILE_SIZE/2 - rlen/2, | 2211 | dy + TILE_SIZE/2 - rlen/2, |
@@ -2176,10 +2234,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
2176 | if (flashtime) flashing = (int)(flashtime * 3 / FLASH_TIME) != 1; | 2234 | if (flashtime) flashing = (int)(flashtime * 3 / FLASH_TIME) != 1; |
2177 | 2235 | ||
2178 | if (!ds->started) { | 2236 | if (!ds->started) { |
2179 | draw_rect(dr, 0, 0, | ||
2180 | TILE_SIZE * ds->w + 2 * BORDER, | ||
2181 | TILE_SIZE * ds->h + 2 * BORDER, COL_BACKGROUND); | ||
2182 | |||
2183 | draw_rect_outline(dr, COORD(0)-1, COORD(0)-1, | 2237 | draw_rect_outline(dr, COORD(0)-1, COORD(0)-1, |
2184 | TILE_SIZE * ds->w + 2, | 2238 | TILE_SIZE * ds->w + 2, |
2185 | TILE_SIZE * ds->h + 2, | 2239 | TILE_SIZE * ds->h + 2, |
@@ -2196,7 +2250,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
2196 | unsigned int ds_flags = tile_flags(ds, state, ui, x, y, flashing); | 2250 | unsigned int ds_flags = tile_flags(ds, state, ui, x, y, flashing); |
2197 | if (ds_flags != GRID(ds, flags, x, y)) { | 2251 | if (ds_flags != GRID(ds, flags, x, y)) { |
2198 | GRID(ds, flags, x, y) = ds_flags; | 2252 | GRID(ds, flags, x, y) = ds_flags; |
2199 | tile_redraw(dr, ds, state, x, y); | 2253 | tile_redraw(dr, ds, ui, state, x, y); |
2200 | } | 2254 | } |
2201 | } | 2255 | } |
2202 | } | 2256 | } |
@@ -2235,24 +2289,21 @@ static int game_status(const game_state *state) | |||
2235 | return state->completed ? +1 : 0; | 2289 | return state->completed ? +1 : 0; |
2236 | } | 2290 | } |
2237 | 2291 | ||
2238 | static bool game_timing_state(const game_state *state, game_ui *ui) | 2292 | static void game_print_size(const game_params *params, const game_ui *ui, |
2239 | { | 2293 | float *x, float *y) |
2240 | return true; | ||
2241 | } | ||
2242 | |||
2243 | static void game_print_size(const game_params *params, float *x, float *y) | ||
2244 | { | 2294 | { |
2245 | int pw, ph; | 2295 | int pw, ph; |
2246 | 2296 | ||
2247 | /* | 2297 | /* |
2248 | * I'll use 6mm squares by default. | 2298 | * I'll use 6mm squares by default. |
2249 | */ | 2299 | */ |
2250 | game_compute_size(params, 600, &pw, &ph); | 2300 | game_compute_size(params, 600, ui, &pw, &ph); |
2251 | *x = pw / 100.0F; | 2301 | *x = pw / 100.0F; |
2252 | *y = ph / 100.0F; | 2302 | *y = ph / 100.0F; |
2253 | } | 2303 | } |
2254 | 2304 | ||
2255 | static void game_print(drawing *dr, const game_state *state, int tilesize) | 2305 | static void game_print(drawing *dr, const game_state *state, const game_ui *ui, |
2306 | int tilesize) | ||
2256 | { | 2307 | { |
2257 | int w = state->w, h = state->h; | 2308 | int w = state->w, h = state->h; |
2258 | int ink = print_mono_colour(dr, 0); | 2309 | int ink = print_mono_colour(dr, 0); |
@@ -2323,12 +2374,14 @@ const struct game thegame = { | |||
2323 | free_game, | 2374 | free_game, |
2324 | true, solve_game, | 2375 | true, solve_game, |
2325 | true, game_can_format_as_text_now, game_text_format, | 2376 | true, game_can_format_as_text_now, game_text_format, |
2377 | get_prefs, set_prefs, | ||
2326 | new_ui, | 2378 | new_ui, |
2327 | free_ui, | 2379 | free_ui, |
2328 | encode_ui, | 2380 | NULL, /* encode_ui */ |
2329 | decode_ui, | 2381 | NULL, /* decode_ui */ |
2330 | NULL, /* game_request_keys */ | 2382 | NULL, /* game_request_keys */ |
2331 | game_changed_state, | 2383 | game_changed_state, |
2384 | current_key_label, | ||
2332 | interpret_move, | 2385 | interpret_move, |
2333 | execute_move, | 2386 | execute_move, |
2334 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, | 2387 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, |
@@ -2342,7 +2395,7 @@ const struct game thegame = { | |||
2342 | game_status, | 2395 | game_status, |
2343 | true, false, game_print_size, game_print, | 2396 | true, false, game_print_size, game_print, |
2344 | false, /* wants_statusbar */ | 2397 | false, /* wants_statusbar */ |
2345 | false, game_timing_state, | 2398 | false, NULL, /* timing_state */ |
2346 | 0, /* flags */ | 2399 | 0, /* flags */ |
2347 | }; | 2400 | }; |
2348 | 2401 | ||