summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/towers.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/towers.c')
-rw-r--r--apps/plugins/puzzles/src/towers.c199
1 files changed, 137 insertions, 62 deletions
diff --git a/apps/plugins/puzzles/src/towers.c b/apps/plugins/puzzles/src/towers.c
index 13c652b819..4dd9484ad9 100644
--- a/apps/plugins/puzzles/src/towers.c
+++ b/apps/plugins/puzzles/src/towers.c
@@ -23,7 +23,11 @@
23#include <string.h> 23#include <string.h>
24#include <assert.h> 24#include <assert.h>
25#include <ctype.h> 25#include <ctype.h>
26#include <math.h> 26#ifdef NO_TGMATH_H
27# include <math.h>
28#else
29# include <tgmath.h>
30#endif
27 31
28#include "puzzles.h" 32#include "puzzles.h"
29#include "latin.h" 33#include "latin.h"
@@ -385,12 +389,12 @@ static int solver_easy(struct latin_solver *solver, void *vctx)
385 return ret; 389 return ret;
386 390
387#ifdef STANDALONE_SOLVER 391#ifdef STANDALONE_SOLVER
388 if (solver_show_working) 392 if (solver_show_working)
389 sprintf(prefix, "%*slower bounds for clue %s %d:\n", 393 sprintf(prefix, "%*slower bounds for clue %s %d:\n",
390 solver_recurse_depth*4, "", 394 solver_recurse_depth*4, "",
391 cluepos[c/w], c%w+1); 395 cluepos[c/w], c%w+1);
392 else 396 else
393 prefix[0] = '\0'; /* placate optimiser */ 397 prefix[0] = '\0'; /* placate optimiser */
394#endif 398#endif
395 399
396 i = 0; 400 i = 0;
@@ -893,6 +897,7 @@ static const char *validate_desc(const game_params *params, const char *desc)
893 return "Too much data to fit in grid"; 897 return "Too much data to fit in grid";
894 } 898 }
895 899
900 if (*p) return "Rubbish at end of game description";
896 return NULL; 901 return NULL;
897} 902}
898 903
@@ -1154,16 +1159,59 @@ struct game_ui {
1154 * allowed on immutable squares. 1159 * allowed on immutable squares.
1155 */ 1160 */
1156 bool hcursor; 1161 bool hcursor;
1162
1163 /*
1164 * User preference option which can be set to FALSE to disable the
1165 * 3D graphical style, and instead just display the puzzle as if
1166 * it was a Sudoku variant, i.e. each square just has a digit in
1167 * it.
1168 *
1169 * I was initially a bit uncertain about whether the 3D style
1170 * would be the right thing, on the basis that it uses up space in
1171 * the cells and makes it hard to use many pencil marks. Actually
1172 * nobody seems to have complained, but having put in the option
1173 * while I was still being uncertain, it seems silly not to leave
1174 * it in just in case.
1175 */
1176 int three_d;
1177
1178 /*
1179 * User preference option: if the user right-clicks in a square
1180 * and presses a number key to add/remove a pencil mark, do we
1181 * hide the mouse highlight again afterwards?
1182 *
1183 * Historically our answer was yes. The Android port prefers no.
1184 * There are advantages both ways, depending how much you dislike
1185 * the highlight cluttering your view. So it's a preference.
1186 */
1187 bool pencil_keep_highlight;
1157}; 1188};
1158 1189
1190static void legacy_prefs_override(struct game_ui *ui_out)
1191{
1192 static bool initialised = false;
1193 static int three_d = -1;
1194
1195 if (!initialised) {
1196 initialised = true;
1197 three_d = getenv_bool("TOWERS_2D", -1);
1198 }
1199
1200 if (three_d != -1)
1201 ui_out->three_d = three_d;
1202}
1203
1159static game_ui *new_ui(const game_state *state) 1204static game_ui *new_ui(const game_state *state)
1160{ 1205{
1161 game_ui *ui = snew(game_ui); 1206 game_ui *ui = snew(game_ui);
1162 1207
1163 ui->hx = ui->hy = 0; 1208 ui->hx = ui->hy = 0;
1164 ui->hpencil = false; 1209 ui->hpencil = false;
1165 ui->hshow = false; 1210 ui->hshow = ui->hcursor = getenv_bool("PUZZLES_SHOW_CURSOR", false);
1166 ui->hcursor = false; 1211
1212 ui->three_d = true;
1213 ui->pencil_keep_highlight = false;
1214 legacy_prefs_override(ui);
1167 1215
1168 return ui; 1216 return ui;
1169} 1217}
@@ -1173,13 +1221,34 @@ static void free_ui(game_ui *ui)
1173 sfree(ui); 1221 sfree(ui);
1174} 1222}
1175 1223
1176static char *encode_ui(const game_ui *ui) 1224static config_item *get_prefs(game_ui *ui)
1177{ 1225{
1178 return NULL; 1226 config_item *ret;
1227
1228 ret = snewn(3, config_item);
1229
1230 ret[0].name = "Keep mouse highlight after changing a pencil mark";
1231 ret[0].kw = "pencil-keep-highlight";
1232 ret[0].type = C_BOOLEAN;
1233 ret[0].u.boolean.bval = ui->pencil_keep_highlight;
1234
1235 ret[1].name = "Puzzle appearance";
1236 ret[1].kw = "appearance";
1237 ret[1].type = C_CHOICES;
1238 ret[1].u.choices.choicenames = ":2D:3D";
1239 ret[1].u.choices.choicekws = ":2d:3d";
1240 ret[1].u.choices.selected = ui->three_d;
1241
1242 ret[2].name = NULL;
1243 ret[2].type = C_END;
1244
1245 return ret;
1179} 1246}
1180 1247
1181static void decode_ui(game_ui *ui, const char *encoding) 1248static void set_prefs(game_ui *ui, const config_item *cfg)
1182{ 1249{
1250 ui->pencil_keep_highlight = cfg[0].u.boolean.bval;
1251 ui->three_d = cfg[1].u.choices.selected;
1183} 1252}
1184 1253
1185static void game_changed_state(game_ui *ui, const game_state *oldstate, 1254static void game_changed_state(game_ui *ui, const game_state *oldstate,
@@ -1198,6 +1267,14 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
1198 } 1267 }
1199} 1268}
1200 1269
1270static const char *current_key_label(const game_ui *ui,
1271 const game_state *state, int button)
1272{
1273 if (ui->hshow && (button == CURSOR_SELECT))
1274 return ui->hpencil ? "Ink" : "Pencil";
1275 return "";
1276}
1277
1201#define PREFERRED_TILESIZE 48 1278#define PREFERRED_TILESIZE 48
1202#define TILESIZE (ds->tilesize) 1279#define TILESIZE (ds->tilesize)
1203#define BORDER (TILESIZE * 9 / 8) 1280#define BORDER (TILESIZE * 9 / 8)
@@ -1221,8 +1298,6 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
1221 1298
1222struct game_drawstate { 1299struct game_drawstate {
1223 int tilesize; 1300 int tilesize;
1224 bool three_d; /* default 3D graphics are user-disableable */
1225 bool started;
1226 long *tiles; /* (w+2)*(w+2) temp space */ 1301 long *tiles; /* (w+2)*(w+2) temp space */
1227 long *drawn; /* (w+2)*(w+2)*4: current drawn data */ 1302 long *drawn; /* (w+2)*(w+2)*4: current drawn data */
1228 bool *errtmp; 1303 bool *errtmp;
@@ -1349,12 +1424,12 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1349 int tx, ty; 1424 int tx, ty;
1350 char buf[80]; 1425 char buf[80];
1351 1426
1352 button &= ~MOD_MASK; 1427 button = STRIP_BUTTON_MODIFIERS(button);
1353 1428
1354 tx = FROMCOORD(x); 1429 tx = FROMCOORD(x);
1355 ty = FROMCOORD(y); 1430 ty = FROMCOORD(y);
1356 1431
1357 if (ds->three_d) { 1432 if (ui->three_d) {
1358 /* 1433 /*
1359 * In 3D mode, just locating the mouse click in the natural 1434 * In 3D mode, just locating the mouse click in the natural
1360 * square grid may not be sufficient to tell which tower the 1435 * square grid may not be sufficient to tell which tower the
@@ -1401,7 +1476,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1401 ui->hpencil = false; 1476 ui->hpencil = false;
1402 } 1477 }
1403 ui->hcursor = false; 1478 ui->hcursor = false;
1404 return UI_UPDATE; 1479 return MOVE_UI_UPDATE;
1405 } 1480 }
1406 if (button == RIGHT_BUTTON) { 1481 if (button == RIGHT_BUTTON) {
1407 /* 1482 /*
@@ -1421,7 +1496,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1421 ui->hshow = false; 1496 ui->hshow = false;
1422 } 1497 }
1423 ui->hcursor = false; 1498 ui->hcursor = false;
1424 return UI_UPDATE; 1499 return MOVE_UI_UPDATE;
1425 } 1500 }
1426 } else if (button == LEFT_BUTTON) { 1501 } else if (button == LEFT_BUTTON) {
1427 if (is_clue(state, tx, ty)) { 1502 if (is_clue(state, tx, ty)) {
@@ -1444,16 +1519,14 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1444 } 1519 }
1445 return NULL; 1520 return NULL;
1446 } 1521 }
1447 move_cursor(button, &ui->hx, &ui->hy, w, w, false);
1448 ui->hshow = true;
1449 ui->hcursor = true; 1522 ui->hcursor = true;
1450 return UI_UPDATE; 1523 return move_cursor(button, &ui->hx, &ui->hy, w, w, false, &ui->hshow);
1451 } 1524 }
1452 if (ui->hshow && 1525 if (ui->hshow &&
1453 (button == CURSOR_SELECT)) { 1526 (button == CURSOR_SELECT)) {
1454 ui->hpencil = !ui->hpencil; 1527 ui->hpencil = !ui->hpencil;
1455 ui->hcursor = true; 1528 ui->hcursor = true;
1456 return UI_UPDATE; 1529 return MOVE_UI_UPDATE;
1457 } 1530 }
1458 1531
1459 if (ui->hshow && 1532 if (ui->hshow &&
@@ -1476,10 +1549,31 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1476 if (state->clues->immutable[ui->hy*w+ui->hx]) 1549 if (state->clues->immutable[ui->hy*w+ui->hx])
1477 return NULL; 1550 return NULL;
1478 1551
1552 /*
1553 * If you ask to fill a square with what it already contains,
1554 * or blank it when it's already empty, that has no effect...
1555 */
1556 if ((!ui->hpencil || n == 0) && state->grid[ui->hy*w+ui->hx] == n &&
1557 state->pencil[ui->hy*w+ui->hx] == 0) {
1558 /* ... expect to remove the cursor in mouse mode. */
1559 if (!ui->hcursor) {
1560 ui->hshow = false;
1561 return MOVE_UI_UPDATE;
1562 }
1563 return NULL;
1564 }
1565
1479 sprintf(buf, "%c%d,%d,%d", 1566 sprintf(buf, "%c%d,%d,%d",
1480 (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n); 1567 (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n);
1481 1568
1482 if (!ui->hcursor) ui->hshow = false; 1569 /*
1570 * Hide the highlight after a keypress, if it was mouse-
1571 * generated. Also, don't hide it if this move has changed
1572 * pencil marks and the user preference says not to hide the
1573 * highlight in that situation.
1574 */
1575 if (!ui->hcursor && !(ui->hpencil && ui->pencil_keep_highlight))
1576 ui->hshow = false;
1483 1577
1484 return dupstr(buf); 1578 return dupstr(buf);
1485 } 1579 }
@@ -1558,7 +1652,7 @@ static game_state *execute_move(const game_state *from, const char *move)
1558#define SIZE(w) ((w) * TILESIZE + 2*BORDER) 1652#define SIZE(w) ((w) * TILESIZE + 2*BORDER)
1559 1653
1560static void game_compute_size(const game_params *params, int tilesize, 1654static void game_compute_size(const game_params *params, int tilesize,
1561 int *x, int *y) 1655 const game_ui *ui, int *x, int *y)
1562{ 1656{
1563 /* Ick: fake up `ds->tilesize' for macro expansion purposes */ 1657 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
1564 struct { int tilesize; } ads, *ds = &ads; 1658 struct { int tilesize; } ads, *ds = &ads;
@@ -1614,8 +1708,6 @@ static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
1614 int i; 1708 int i;
1615 1709
1616 ds->tilesize = 0; 1710 ds->tilesize = 0;
1617 ds->three_d = !getenv("TOWERS_2D");
1618 ds->started = false;
1619 ds->tiles = snewn((w+2)*(w+2), long); 1711 ds->tiles = snewn((w+2)*(w+2), long);
1620 ds->drawn = snewn((w+2)*(w+2)*4, long); 1712 ds->drawn = snewn((w+2)*(w+2)*4, long);
1621 for (i = 0; i < (w+2)*(w+2)*4; i++) 1713 for (i = 0; i < (w+2)*(w+2)*4; i++)
@@ -1633,8 +1725,8 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds)
1633 sfree(ds); 1725 sfree(ds);
1634} 1726}
1635 1727
1636static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues, 1728static void draw_tile(drawing *dr, game_drawstate *ds, const game_ui *ui,
1637 int x, int y, long tile) 1729 struct clues *clues, int x, int y, long tile)
1638{ 1730{
1639 int w = clues->w /* , a = w*w */; 1731 int w = clues->w /* , a = w*w */;
1640 int tx, ty, bg; 1732 int tx, ty, bg;
@@ -1646,7 +1738,7 @@ static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues,
1646 bg = (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : COL_BACKGROUND; 1738 bg = (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : COL_BACKGROUND;
1647 1739
1648 /* draw tower */ 1740 /* draw tower */
1649 if (ds->three_d && (tile & DF_PLAYAREA) && (tile & DF_DIGIT_MASK)) { 1741 if (ui->three_d && (tile & DF_PLAYAREA) && (tile & DF_DIGIT_MASK)) {
1650 int coords[8]; 1742 int coords[8];
1651 int xoff = X_3D_DISP(tile & DF_DIGIT_MASK, w); 1743 int xoff = X_3D_DISP(tile & DF_DIGIT_MASK, w);
1652 int yoff = Y_3D_DISP(tile & DF_DIGIT_MASK, w); 1744 int yoff = Y_3D_DISP(tile & DF_DIGIT_MASK, w);
@@ -1747,10 +1839,10 @@ static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues,
1747 * to put the pencil marks. 1839 * to put the pencil marks.
1748 */ 1840 */
1749 /* Start with the whole square, minus space for impinging towers */ 1841 /* Start with the whole square, minus space for impinging towers */
1750 pl = tx + (ds->three_d ? X_3D_DISP(w,w) : 0); 1842 pl = tx + (ui->three_d ? X_3D_DISP(w,w) : 0);
1751 pr = tx + TILESIZE; 1843 pr = tx + TILESIZE;
1752 pt = ty; 1844 pt = ty;
1753 pb = ty + TILESIZE - (ds->three_d ? Y_3D_DISP(w,w) : 0); 1845 pb = ty + TILESIZE - (ui->three_d ? Y_3D_DISP(w,w) : 0);
1754 1846
1755 /* 1847 /*
1756 * We arrange our pencil marks in a grid layout, with 1848 * We arrange our pencil marks in a grid layout, with
@@ -1821,20 +1913,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1821 int w = state->par.w /*, a = w*w */; 1913 int w = state->par.w /*, a = w*w */;
1822 int i, x, y; 1914 int i, x, y;
1823 1915
1824 if (!ds->started) {
1825 /*
1826 * The initial contents of the window are not guaranteed and
1827 * can vary with front ends. To be on the safe side, all
1828 * games should start by drawing a big background-colour
1829 * rectangle covering the whole window.
1830 */
1831 draw_rect(dr, 0, 0, SIZE(w), SIZE(w), COL_BACKGROUND);
1832
1833 draw_update(dr, 0, 0, SIZE(w), SIZE(w));
1834
1835 ds->started = true;
1836 }
1837
1838 check_errors(state, ds->errtmp); 1916 check_errors(state, ds->errtmp);
1839 1917
1840 /* 1918 /*
@@ -1900,13 +1978,13 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1900 ds->drawn[i*4+2] != bl || ds->drawn[i*4+3] != br) { 1978 ds->drawn[i*4+2] != bl || ds->drawn[i*4+3] != br) {
1901 clip(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE); 1979 clip(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE);
1902 1980
1903 draw_tile(dr, ds, state->clues, x-1, y-1, tr); 1981 draw_tile(dr, ds, ui, state->clues, x-1, y-1, tr);
1904 if (x > 0) 1982 if (x > 0)
1905 draw_tile(dr, ds, state->clues, x-2, y-1, tl); 1983 draw_tile(dr, ds, ui, state->clues, x-2, y-1, tl);
1906 if (y <= w) 1984 if (y <= w)
1907 draw_tile(dr, ds, state->clues, x-1, y, br); 1985 draw_tile(dr, ds, ui, state->clues, x-1, y, br);
1908 if (x > 0 && y <= w) 1986 if (x > 0 && y <= w)
1909 draw_tile(dr, ds, state->clues, x-2, y, bl); 1987 draw_tile(dr, ds, ui, state->clues, x-2, y, bl);
1910 1988
1911 unclip(dr); 1989 unclip(dr);
1912 draw_update(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE); 1990 draw_update(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE);
@@ -1953,26 +2031,21 @@ static int game_status(const game_state *state)
1953 return state->completed ? +1 : 0; 2031 return state->completed ? +1 : 0;
1954} 2032}
1955 2033
1956static bool game_timing_state(const game_state *state, game_ui *ui) 2034static void game_print_size(const game_params *params, const game_ui *ui,
1957{ 2035 float *x, float *y)
1958 if (state->completed)
1959 return false;
1960 return true;
1961}
1962
1963static void game_print_size(const game_params *params, float *x, float *y)
1964{ 2036{
1965 int pw, ph; 2037 int pw, ph;
1966 2038
1967 /* 2039 /*
1968 * We use 9mm squares by default, like Solo. 2040 * We use 9mm squares by default, like Solo.
1969 */ 2041 */
1970 game_compute_size(params, 900, &pw, &ph); 2042 game_compute_size(params, 900, ui, &pw, &ph);
1971 *x = pw / 100.0F; 2043 *x = pw / 100.0F;
1972 *y = ph / 100.0F; 2044 *y = ph / 100.0F;
1973} 2045}
1974 2046
1975static void game_print(drawing *dr, const game_state *state, int tilesize) 2047static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
2048 int tilesize)
1976{ 2049{
1977 int w = state->par.w; 2050 int w = state->par.w;
1978 int ink = print_mono_colour(dr, 0); 2051 int ink = print_mono_colour(dr, 0);
@@ -2058,12 +2131,14 @@ const struct game thegame = {
2058 free_game, 2131 free_game,
2059 true, solve_game, 2132 true, solve_game,
2060 true, game_can_format_as_text_now, game_text_format, 2133 true, game_can_format_as_text_now, game_text_format,
2134 get_prefs, set_prefs,
2061 new_ui, 2135 new_ui,
2062 free_ui, 2136 free_ui,
2063 encode_ui, 2137 NULL, /* encode_ui */
2064 decode_ui, 2138 NULL, /* decode_ui */
2065 game_request_keys, 2139 game_request_keys,
2066 game_changed_state, 2140 game_changed_state,
2141 current_key_label,
2067 interpret_move, 2142 interpret_move,
2068 execute_move, 2143 execute_move,
2069 PREFERRED_TILESIZE, game_compute_size, game_set_size, 2144 PREFERRED_TILESIZE, game_compute_size, game_set_size,
@@ -2077,7 +2152,7 @@ const struct game thegame = {
2077 game_status, 2152 game_status,
2078 true, false, game_print_size, game_print, 2153 true, false, game_print_size, game_print,
2079 false, /* wants_statusbar */ 2154 false, /* wants_statusbar */
2080 false, game_timing_state, 2155 false, NULL, /* timing_state */
2081 REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */ 2156 REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */
2082}; 2157};
2083 2158