summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/lightup.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/lightup.c')
-rw-r--r--apps/plugins/puzzles/src/lightup.c137
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
61int verbose = 0; 66static 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)
1822struct game_ui { 1833struct 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
1846static 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
1827static game_ui *new_ui(const game_state *state) 1860static 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
1835static void free_ui(game_ui *ui) 1870static 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
1840static char *encode_ui(const game_ui *ui) 1887static 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
1846static void decode_ui(game_ui *ui, const char *encoding) 1892static void free_ui(game_ui *ui)
1847{ 1893{
1848 /* nothing to decode. */ 1894 sfree(ui);
1849} 1895}
1850 1896
1851static void game_changed_state(game_ui *ui, const game_state *oldstate, 1897static 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
1904static 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? */
2004static void game_compute_size(const game_params *params, int tilesize, 2068static 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
2114static void tile_redraw(drawing *dr, game_drawstate *ds, 2178static 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
2238static bool game_timing_state(const game_state *state, game_ui *ui) 2292static void game_print_size(const game_params *params, const game_ui *ui,
2239{ 2293 float *x, float *y)
2240 return true;
2241}
2242
2243static 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
2255static void game_print(drawing *dr, const game_state *state, int tilesize) 2305static 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