summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/tracks.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/tracks.c')
-rw-r--r--apps/plugins/puzzles/src/tracks.c351
1 files changed, 218 insertions, 133 deletions
diff --git a/apps/plugins/puzzles/src/tracks.c b/apps/plugins/puzzles/src/tracks.c
index 3cf5df70a1..a2d4560907 100644
--- a/apps/plugins/puzzles/src/tracks.c
+++ b/apps/plugins/puzzles/src/tracks.c
@@ -20,7 +20,12 @@
20#include <string.h> 20#include <string.h>
21#include <assert.h> 21#include <assert.h>
22#include <ctype.h> 22#include <ctype.h>
23#include <math.h> 23#include <limits.h>
24#ifdef NO_TGMATH_H
25# include <math.h>
26#else
27# include <tgmath.h>
28#endif
24 29
25#include "puzzles.h" 30#include "puzzles.h"
26 31
@@ -196,6 +201,8 @@ static const char *validate_params(const game_params *params, bool full)
196 */ 201 */
197 if (params->w < 4 || params->h < 4) 202 if (params->w < 4 || params->h < 4)
198 return "Width and height must both be at least four"; 203 return "Width and height must both be at least four";
204 if (params->w > INT_MAX / params->h)
205 return "Width times height must not be unreasonably large";
199 return NULL; 206 return NULL;
200} 207}
201 208
@@ -242,6 +249,9 @@ static const int nbits[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
242#define S_CLUE 8 249#define S_CLUE 8
243#define S_MARK 16 250#define S_MARK 16
244 251
252#define S_FLASH_SHIFT 8 /* Position of tile in solved track */
253#define S_FLASH_WIDTH 8 /* Width of above sub-field */
254#define S_FLASH_MASK ((1 << S_FLASH_WIDTH) - 1)
245#define S_TRACK_SHIFT 16 /* U/D/L/R flags for edge track indicators */ 255#define S_TRACK_SHIFT 16 /* U/D/L/R flags for edge track indicators */
246#define S_NOTRACK_SHIFT 20 /* U/D/L/R flags for edge no-track indicators */ 256#define S_NOTRACK_SHIFT 20 /* U/D/L/R flags for edge no-track indicators */
247 257
@@ -904,7 +914,7 @@ static game_state *new_game(midend *me, const game_params *params, const char *d
904} 914}
905 915
906struct solver_scratch { 916struct solver_scratch {
907 int *dsf; 917 DSF *dsf;
908}; 918};
909 919
910static int solve_set_sflag(game_state *state, int x, int y, 920static int solve_set_sflag(game_state *state, int x, int y,
@@ -919,8 +929,8 @@ static int solve_set_sflag(game_state *state, int x, int y,
919 if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) { 929 if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) {
920 solverdebug(("opposite flag already set there, marking IMPOSSIBLE")); 930 solverdebug(("opposite flag already set there, marking IMPOSSIBLE"));
921 state->impossible = true; 931 state->impossible = true;
922 } 932 } else
923 state->sflags[i] |= f; 933 state->sflags[i] |= f;
924 return 1; 934 return 1;
925} 935}
926 936
@@ -937,8 +947,8 @@ static int solve_set_eflag(game_state *state, int x, int y, int d,
937 if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) { 947 if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) {
938 solverdebug(("opposite flag already set there, marking IMPOSSIBLE")); 948 solverdebug(("opposite flag already set there, marking IMPOSSIBLE"));
939 state->impossible = true; 949 state->impossible = true;
940 } 950 } else
941 S_E_SET(state, x, y, d, f); 951 S_E_SET(state, x, y, d, f);
942 return 1; 952 return 1;
943} 953}
944 954
@@ -1306,7 +1316,7 @@ static int solve_check_neighbours(game_state *state, bool both_ways)
1306} 1316}
1307 1317
1308static int solve_check_loop_sub(game_state *state, int x, int y, int dir, 1318static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
1309 int *dsf, int startc, int endc) 1319 DSF *dsf, int startc, int endc)
1310{ 1320{
1311 int w = state->p.w, h = state->p.h, i = y*w+x, j, k; 1321 int w = state->p.w, h = state->p.h, i = y*w+x, j, k;
1312 bool satisfied = true; 1322 bool satisfied = true;
@@ -1358,13 +1368,13 @@ static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
1358static int solve_check_loop(game_state *state) 1368static int solve_check_loop(game_state *state)
1359{ 1369{
1360 int w = state->p.w, h = state->p.h, x, y, i, j, did = 0; 1370 int w = state->p.w, h = state->p.h, x, y, i, j, did = 0;
1361 int *dsf, startc, endc; 1371 DSF *dsf;
1372 int startc, endc;
1362 1373
1363 /* TODO eventually we should pull this out into a solver struct and keep it 1374 /* TODO eventually we should pull this out into a solver struct and keep it
1364 updated as we connect squares. For now we recreate it every time we try 1375 updated as we connect squares. For now we recreate it every time we try
1365 this particular solver step. */ 1376 this particular solver step. */
1366 dsf = snewn(w*h, int); 1377 dsf = dsf_new(w*h);
1367 dsf_init(dsf, w*h);
1368 1378
1369 /* Work out the connectedness of the current loop set. */ 1379 /* Work out the connectedness of the current loop set. */
1370 for (x = 0; x < w; x++) { 1380 for (x = 0; x < w; x++) {
@@ -1402,7 +1412,7 @@ static int solve_check_loop(game_state *state)
1402 } 1412 }
1403 } 1413 }
1404 1414
1405 sfree(dsf); 1415 dsf_free(dsf);
1406 1416
1407 return did; 1417 return did;
1408} 1418}
@@ -1455,8 +1465,8 @@ static int solve_bridge_sub(game_state *state, int x, int y, int d,
1455 assert(d == D || d == R); 1465 assert(d == D || d == R);
1456 1466
1457 if (!sc->dsf) 1467 if (!sc->dsf)
1458 sc->dsf = snew_dsf(wh); 1468 sc->dsf = dsf_new(wh);
1459 dsf_init(sc->dsf, wh); 1469 dsf_reinit(sc->dsf);
1460 1470
1461 for (xi = 0; xi < w; xi++) { 1471 for (xi = 0; xi < w; xi++) {
1462 for (yi = 0; yi < h; yi++) { 1472 for (yi = 0; yi < h; yi++) {
@@ -1595,7 +1605,7 @@ static int tracks_solve(game_state *state, int diff, int *max_diff_out)
1595 break; 1605 break;
1596 } 1606 }
1597 1607
1598 sfree(sc->dsf); 1608 dsf_free(sc->dsf);
1599 1609
1600 if (max_diff_out) 1610 if (max_diff_out)
1601 *max_diff_out = max_diff; 1611 *max_diff_out = max_diff;
@@ -1775,7 +1785,7 @@ static void debug_state(game_state *state, const char *what) {
1775} 1785}
1776 1786
1777static void dsf_update_completion(game_state *state, int ax, int ay, 1787static void dsf_update_completion(game_state *state, int ax, int ay,
1778 char dir, int *dsf) 1788 char dir, DSF *dsf)
1779{ 1789{
1780 int w = state->p.w, ai = ay*w+ax, bx, by, bi; 1790 int w = state->p.w, ai = ay*w+ax, bx, by, bi;
1781 1791
@@ -1820,12 +1830,37 @@ static int tracks_neighbour(int vertex, void *vctx)
1820 return -1; 1830 return -1;
1821} 1831}
1822 1832
1833/*
1834 * The completion flash moves along the track, so we want to label
1835 * each tile with how far along the track it is. This is represented
1836 * as an eight-bit field, which is more than enough when the
1837 * completion flash is only 0.5 s long.
1838 */
1839static void set_flash_data(game_state *state)
1840{
1841 int ntrack = 0, x, y, n, d;
1842 const int w = state->p.w;
1843
1844 for (x = 0; x < w; x++)
1845 ntrack += state->numbers->numbers[x];
1846 n = 0; x = 0; y = state->numbers->row_s; d = R;
1847 do {
1848 state->sflags[y*w + x] &= ~(S_FLASH_MASK << S_FLASH_SHIFT);
1849 state->sflags[y*w + x] |=
1850 n++ * (S_FLASH_MASK / (ntrack - 1)) << S_FLASH_SHIFT;
1851 d = F(d); /* Find the direction we just arrived from. */
1852 d = S_E_DIRS(state, x, y, E_TRACK) & ~d; /* Other track from here. */
1853 x += DX(d); y += DY(d); /* Move to the next tile. */
1854 } while (INGRID(state, x, y));
1855}
1856
1823static bool check_completion(game_state *state, bool mark) 1857static bool check_completion(game_state *state, bool mark)
1824{ 1858{
1825 int w = state->p.w, h = state->p.h, x, y, i, target; 1859 int w = state->p.w, h = state->p.h, x, y, i, target;
1826 bool ret = true; 1860 bool ret = true, pathret;
1827 int ntrack, nnotrack, ntrackcomplete; 1861 int ntrack, nnotrack, ntrackcomplete;
1828 int *dsf, pathclass; 1862 DSF *dsf;
1863 int pathclass;
1829 struct findloopstate *fls; 1864 struct findloopstate *fls;
1830 struct tracks_neighbour_ctx ctx; 1865 struct tracks_neighbour_ctx ctx;
1831 1866
@@ -1844,60 +1879,7 @@ static bool check_completion(game_state *state, bool mark)
1844 } 1879 }
1845 } 1880 }
1846 1881
1847 /* A cell is 'complete', for the purposes of marking the game as 1882 dsf = dsf_new(w*h);
1848 * finished, if it has two edges marked as TRACK. But it only has
1849 * to have one edge marked as TRACK, or be filled in as trackful
1850 * without any specific edges known, to count towards checking
1851 * row/column clue errors. */
1852 for (x = 0; x < w; x++) {
1853 target = state->numbers->numbers[x];
1854 ntrack = nnotrack = ntrackcomplete = 0;
1855 for (y = 0; y < h; y++) {
1856 if (S_E_COUNT(state, x, y, E_TRACK) > 0 ||
1857 state->sflags[y*w+x] & S_TRACK)
1858 ntrack++;
1859 if (S_E_COUNT(state, x, y, E_TRACK) == 2)
1860 ntrackcomplete++;
1861 if (state->sflags[y*w+x] & S_NOTRACK)
1862 nnotrack++;
1863 }
1864 if (mark) {
1865 if (ntrack > target || nnotrack > (h-target)) {
1866 debug(("col %d error: target %d, track %d, notrack %d",
1867 x, target, ntrack, nnotrack));
1868 state->num_errors[x] = 1;
1869 ret = false;
1870 }
1871 }
1872 if (ntrackcomplete != target)
1873 ret = false;
1874 }
1875 for (y = 0; y < h; y++) {
1876 target = state->numbers->numbers[w+y];
1877 ntrack = nnotrack = ntrackcomplete = 0;
1878 for (x = 0; x < w; x++) {
1879 if (S_E_COUNT(state, x, y, E_TRACK) > 0 ||
1880 state->sflags[y*w+x] & S_TRACK)
1881 ntrack++;
1882 if (S_E_COUNT(state, x, y, E_TRACK) == 2)
1883 ntrackcomplete++;
1884 if (state->sflags[y*w+x] & S_NOTRACK)
1885 nnotrack++;
1886 }
1887 if (mark) {
1888 if (ntrack > target || nnotrack > (w-target)) {
1889 debug(("row %d error: target %d, track %d, notrack %d",
1890 y, target, ntrack, nnotrack));
1891 state->num_errors[w+y] = 1;
1892 ret = false;
1893 }
1894 }
1895 if (ntrackcomplete != target)
1896 ret = false;
1897 }
1898
1899 dsf = snewn(w*h, int);
1900 dsf_init(dsf, w*h);
1901 1883
1902 for (x = 0; x < w; x++) { 1884 for (x = 0; x < w; x++) {
1903 for (y = 0; y < h; y++) { 1885 for (y = 0; y < h; y++) {
@@ -1949,9 +1931,75 @@ static bool check_completion(game_state *state, bool mark)
1949 } 1931 }
1950 } 1932 }
1951 1933
1952 if (mark) 1934 /*
1935 * A cell is 'complete', for the purposes of marking the game as
1936 * finished, if it has two edges marked as TRACK. But it only has
1937 * to have one edge marked as TRACK, or be filled in as trackful
1938 * without any specific edges known, to count towards checking
1939 * row/column clue errors.
1940 *
1941 * This changes if we haven't found any other errors by this
1942 * point, so the player has constructed a route from A to B. In
1943 * that case, we highlight any row/column where the actually laid
1944 * tracks don't match the clue.
1945 */
1946 pathret = ret; /* Do we have a plausible solution so far? */
1947 for (x = 0; x < w; x++) {
1948 target = state->numbers->numbers[x];
1949 ntrack = nnotrack = ntrackcomplete = 0;
1950 for (y = 0; y < h; y++) {
1951 if (S_E_COUNT(state, x, y, E_TRACK) > 0 ||
1952 state->sflags[y*w+x] & S_TRACK)
1953 ntrack++;
1954 if (S_E_COUNT(state, x, y, E_TRACK) == 2)
1955 ntrackcomplete++;
1956 if (state->sflags[y*w+x] & S_NOTRACK)
1957 nnotrack++;
1958 }
1959 if (mark) {
1960 if (ntrack > target || nnotrack > (h-target) ||
1961 (pathret && ntrackcomplete != target)) {
1962 debug(("col %d error: target %d, track %d, notrack %d, "
1963 "pathret %d, trackcomplete %d",
1964 x, target, ntrack, nnotrack, pathret, ntrackcomplete));
1965 state->num_errors[x] = 1;
1966 ret = false;
1967 }
1968 }
1969 if (ntrackcomplete != target)
1970 ret = false;
1971 }
1972 for (y = 0; y < h; y++) {
1973 target = state->numbers->numbers[w+y];
1974 ntrack = nnotrack = ntrackcomplete = 0;
1975 for (x = 0; x < w; x++) {
1976 if (S_E_COUNT(state, x, y, E_TRACK) > 0 ||
1977 state->sflags[y*w+x] & S_TRACK)
1978 ntrack++;
1979 if (S_E_COUNT(state, x, y, E_TRACK) == 2)
1980 ntrackcomplete++;
1981 if (state->sflags[y*w+x] & S_NOTRACK)
1982 nnotrack++;
1983 }
1984 if (mark) {
1985 if (ntrack > target || nnotrack > (w-target) ||
1986 (pathret && ntrackcomplete != target)) {
1987 debug(("row %d error: target %d, track %d, notrack %d, "
1988 "pathret %d, trackcomplete %d",
1989 y, target, ntrack, nnotrack, pathret, ntrackcomplete));
1990 state->num_errors[w+y] = 1;
1991 ret = false;
1992 }
1993 }
1994 if (ntrackcomplete != target)
1995 ret = false;
1996 }
1997
1998 if (mark) {
1953 state->completed = ret; 1999 state->completed = ret;
1954 sfree(dsf); 2000 if (ret) set_flash_data(state);
2001 }
2002 dsf_free(dsf);
1955 return ret; 2003 return ret;
1956} 2004}
1957 2005
@@ -1974,7 +2022,7 @@ static game_ui *new_ui(const game_state *state)
1974 ui->notrack = false; 2022 ui->notrack = false;
1975 ui->dragging = false; 2023 ui->dragging = false;
1976 ui->drag_sx = ui->drag_sy = ui->drag_ex = ui->drag_ey = -1; 2024 ui->drag_sx = ui->drag_sy = ui->drag_ex = ui->drag_ey = -1;
1977 ui->cursor_active = false; 2025 ui->cursor_active = getenv_bool("PUZZLES_SHOW_CURSOR", false);
1978 ui->curx = ui->cury = 1; 2026 ui->curx = ui->cury = 1;
1979 2027
1980 return ui; 2028 return ui;
@@ -1985,27 +2033,19 @@ static void free_ui(game_ui *ui)
1985 sfree(ui); 2033 sfree(ui);
1986} 2034}
1987 2035
1988static char *encode_ui(const game_ui *ui)
1989{
1990 return NULL;
1991}
1992
1993static void decode_ui(game_ui *ui, const char *encoding)
1994{
1995}
1996
1997static void game_changed_state(game_ui *ui, const game_state *oldstate, 2036static void game_changed_state(game_ui *ui, const game_state *oldstate,
1998 const game_state *newstate) 2037 const game_state *newstate)
1999{ 2038{
2000} 2039}
2001 2040
2002#define PREFERRED_TILE_SIZE 30 2041#define PREFERRED_TILE_SIZE 33
2003#define HALFSZ (ds->sz6*3) 2042#define HALFSZ (ds->sz6*3)
2004#define THIRDSZ (ds->sz6*2) 2043#define THIRDSZ (ds->sz6*2)
2005#define TILE_SIZE (ds->sz6*6) 2044#define TILE_SIZE (ds->sz6*6)
2006 2045
2007#define BORDER (TILE_SIZE/8) 2046#define MAX_BORDER (TILE_SIZE/8)
2008#define LINE_THICK (TILE_SIZE/16) 2047#define LINE_THICK (TILE_SIZE/16)
2048#define BORDER (ds->border)
2009#define GRID_LINE_TL (ds->grid_line_tl) 2049#define GRID_LINE_TL (ds->grid_line_tl)
2010#define GRID_LINE_BR (ds->grid_line_br) 2050#define GRID_LINE_BR (ds->grid_line_br)
2011#define GRID_LINE_ALL (ds->grid_line_all) 2051#define GRID_LINE_ALL (ds->grid_line_all)
@@ -2028,7 +2068,7 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
2028#define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */ 2068#define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */
2029 2069
2030struct game_drawstate { 2070struct game_drawstate {
2031 int sz6, grid_line_all, grid_line_tl, grid_line_br; 2071 int sz6, border, grid_line_all, grid_line_tl, grid_line_br;
2032 bool started; 2072 bool started;
2033 2073
2034 int w, h, sz; 2074 int w, h, sz;
@@ -2120,6 +2160,35 @@ static bool ui_can_flip_square(const game_state *state, int x, int y, bool notra
2120 return true; 2160 return true;
2121} 2161}
2122 2162
2163static const char *current_key_label(const game_ui *ui,
2164 const game_state *state, int button)
2165{
2166 if (IS_CURSOR_SELECT(button) && ui->cursor_active) {
2167 int gx = ui->curx / 2, gy = ui->cury / 2;
2168 int w = state->p.w;
2169 int direction =
2170 ((ui->curx % 2) == 0) ? L : ((ui->cury % 2) == 0) ? U : 0;
2171 if (direction &&
2172 ui_can_flip_edge(state, gx, gy, direction,
2173 button == CURSOR_SELECT2)) {
2174 unsigned ef = S_E_FLAGS(state, gx, gy, direction);
2175 switch (button) {
2176 case CURSOR_SELECT: return (ef & E_TRACK) ? "Clear" : "Track";
2177 case CURSOR_SELECT2: return (ef & E_NOTRACK) ? "Clear" : "X";
2178 }
2179 }
2180 if (!direction &&
2181 ui_can_flip_square(state, gx, gy, button == CURSOR_SELECT2)) {
2182 unsigned sf = state->sflags[gy*w+gx];
2183 switch (button) {
2184 case CURSOR_SELECT: return (sf & S_TRACK) ? "Clear" : "Track";
2185 case CURSOR_SELECT2: return (sf & S_NOTRACK) ? "Clear" : "X";
2186 }
2187 }
2188 }
2189 return "";
2190}
2191
2123static char *edge_flip_str(const game_state *state, int x, int y, int dir, bool notrack, char *buf) { 2192static char *edge_flip_str(const game_state *state, int x, int y, int dir, bool notrack, char *buf) {
2124 unsigned ef = S_E_FLAGS(state, x, y, dir); 2193 unsigned ef = S_E_FLAGS(state, x, y, dir);
2125 char c; 2194 char c;
@@ -2193,6 +2262,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2193 2262
2194 if (!INGRID(state, gx, gy)) { 2263 if (!INGRID(state, gx, gy)) {
2195 /* can't drag from off grid */ 2264 /* can't drag from off grid */
2265 ui->drag_sx = ui->drag_sy = -1;
2196 return NULL; 2266 return NULL;
2197 } 2267 }
2198 2268
@@ -2209,13 +2279,13 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2209 ui->drag_sx = ui->drag_ex = gx; 2279 ui->drag_sx = ui->drag_ex = gx;
2210 ui->drag_sy = ui->drag_ey = gy; 2280 ui->drag_sy = ui->drag_ey = gy;
2211 2281
2212 return UI_UPDATE; 2282 return MOVE_UI_UPDATE;
2213 } 2283 }
2214 2284
2215 if (IS_MOUSE_DRAG(button)) { 2285 if (IS_MOUSE_DRAG(button)) {
2216 ui->cursor_active = false; 2286 ui->cursor_active = false;
2217 update_ui_drag(state, ui, gx, gy); 2287 update_ui_drag(state, ui, gx, gy);
2218 return UI_UPDATE; 2288 return MOVE_UI_UPDATE;
2219 } 2289 }
2220 2290
2221 if (IS_MOUSE_RELEASE(button)) { 2291 if (IS_MOUSE_RELEASE(button)) {
@@ -2252,12 +2322,12 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2252 cy = CENTERED_COORD(gy); 2322 cy = CENTERED_COORD(gy);
2253 2323
2254 if (!INGRID(state, gx, gy) || FROMCOORD(x) != gx || FROMCOORD(y) != gy) 2324 if (!INGRID(state, gx, gy) || FROMCOORD(x) != gx || FROMCOORD(y) != gy)
2255 return UI_UPDATE; 2325 return MOVE_UI_UPDATE;
2256 2326
2257 if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) { 2327 if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) {
2258 if (ui_can_flip_square(state, gx, gy, button == RIGHT_RELEASE)) 2328 if (ui_can_flip_square(state, gx, gy, button == RIGHT_RELEASE))
2259 return square_flip_str(state, gx, gy, button == RIGHT_RELEASE, tmpbuf); 2329 return square_flip_str(state, gx, gy, button == RIGHT_RELEASE, tmpbuf);
2260 return UI_UPDATE; 2330 return MOVE_UI_UPDATE;
2261 } else { 2331 } else {
2262 if (abs(x-cx) < abs(y-cy)) { 2332 if (abs(x-cx) < abs(y-cy)) {
2263 /* Closest to top/bottom edge. */ 2333 /* Closest to top/bottom edge. */
@@ -2271,7 +2341,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2271 return edge_flip_str(state, gx, gy, direction, 2341 return edge_flip_str(state, gx, gy, direction,
2272 button == RIGHT_RELEASE, tmpbuf); 2342 button == RIGHT_RELEASE, tmpbuf);
2273 else 2343 else
2274 return UI_UPDATE; 2344 return MOVE_UI_UPDATE;
2275 } 2345 }
2276 } 2346 }
2277 } 2347 }
@@ -2284,7 +2354,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2284 2354
2285 if (!ui->cursor_active) { 2355 if (!ui->cursor_active) {
2286 ui->cursor_active = true; 2356 ui->cursor_active = true;
2287 return UI_UPDATE; 2357 return MOVE_UI_UPDATE;
2288 } 2358 }
2289 2359
2290 ui->curx = ui->curx + dx; 2360 ui->curx = ui->curx + dx;
@@ -2295,17 +2365,17 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2295 } 2365 }
2296 ui->curx = min(max(ui->curx, 1), 2*w-1); 2366 ui->curx = min(max(ui->curx, 1), 2*w-1);
2297 ui->cury = min(max(ui->cury, 1), 2*h-1); 2367 ui->cury = min(max(ui->cury, 1), 2*h-1);
2298 return UI_UPDATE; 2368 return MOVE_UI_UPDATE;
2299 } 2369 }
2300 2370
2301 if (IS_CURSOR_SELECT(button)) { 2371 if (IS_CURSOR_SELECT(button)) {
2302 if (!ui->cursor_active) { 2372 if (!ui->cursor_active) {
2303 ui->cursor_active = true; 2373 ui->cursor_active = true;
2304 return UI_UPDATE; 2374 return MOVE_UI_UPDATE;
2305 } 2375 }
2306 /* click on square corner does nothing (shouldn't get here) */ 2376 /* click on square corner does nothing (shouldn't get here) */
2307 if ((ui->curx % 2) == 0 && (ui->cury % 2 == 0)) 2377 if ((ui->curx % 2) == 0 && (ui->cury % 2 == 0))
2308 return UI_UPDATE; 2378 return MOVE_UI_UPDATE;
2309 2379
2310 gx = ui->curx / 2; 2380 gx = ui->curx / 2;
2311 gy = ui->cury / 2; 2381 gy = ui->cury / 2;
@@ -2317,7 +2387,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2317 else if (!direction && 2387 else if (!direction &&
2318 ui_can_flip_square(state, gx, gy, button == CURSOR_SELECT2)) 2388 ui_can_flip_square(state, gx, gy, button == CURSOR_SELECT2))
2319 return square_flip_str(state, gx, gy, button == CURSOR_SELECT2, tmpbuf); 2389 return square_flip_str(state, gx, gy, button == CURSOR_SELECT2, tmpbuf);
2320 return UI_UPDATE; 2390 return MOVE_UI_UPDATE;
2321 } 2391 }
2322 2392
2323#if 0 2393#if 0
@@ -2334,6 +2404,7 @@ static game_state *execute_move(const game_state *state, const char *move)
2334 int w = state->p.w, x, y, n, i; 2404 int w = state->p.w, x, y, n, i;
2335 char c, d; 2405 char c, d;
2336 unsigned f; 2406 unsigned f;
2407 bool move_is_solve = false;
2337 game_state *ret = dup_game(state); 2408 game_state *ret = dup_game(state);
2338 2409
2339 /* this is breaking the bank on GTK, which vsprintf's into a fixed-size buffer 2410 /* this is breaking the bank on GTK, which vsprintf's into a fixed-size buffer
@@ -2344,6 +2415,7 @@ static game_state *execute_move(const game_state *state, const char *move)
2344 c = *move; 2415 c = *move;
2345 if (c == 'S') { 2416 if (c == 'S') {
2346 ret->used_solve = true; 2417 ret->used_solve = true;
2418 move_is_solve = true;
2347 move++; 2419 move++;
2348 } else if (c == 'T' || c == 't' || c == 'N' || c == 'n') { 2420 } else if (c == 'T' || c == 't' || c == 'N' || c == 'n') {
2349 /* set track, clear track; set notrack, clear notrack */ 2421 /* set track, clear track; set notrack, clear notrack */
@@ -2355,6 +2427,9 @@ static game_state *execute_move(const game_state *state, const char *move)
2355 f = (c == 'T' || c == 't') ? S_TRACK : S_NOTRACK; 2427 f = (c == 'T' || c == 't') ? S_TRACK : S_NOTRACK;
2356 2428
2357 if (d == 'S') { 2429 if (d == 'S') {
2430 if (!ui_can_flip_square(ret, x, y, f == S_NOTRACK) &&
2431 !move_is_solve)
2432 goto badmove;
2358 if (c == 'T' || c == 'N') 2433 if (c == 'T' || c == 'N')
2359 ret->sflags[y*w+x] |= f; 2434 ret->sflags[y*w+x] |= f;
2360 else 2435 else
@@ -2364,6 +2439,9 @@ static game_state *execute_move(const game_state *state, const char *move)
2364 unsigned df = 1<<i; 2439 unsigned df = 1<<i;
2365 2440
2366 if (MOVECHAR(df) == d) { 2441 if (MOVECHAR(df) == d) {
2442 if (!ui_can_flip_edge(ret, x, y, df, f == S_NOTRACK) &&
2443 !move_is_solve)
2444 goto badmove;
2367 if (c == 'T' || c == 'N') 2445 if (c == 'T' || c == 'N')
2368 S_E_SET(ret, x, y, df, f); 2446 S_E_SET(ret, x, y, df, f);
2369 else 2447 else
@@ -2401,13 +2479,21 @@ static game_state *execute_move(const game_state *state, const char *move)
2401#define FLASH_TIME 0.5F 2479#define FLASH_TIME 0.5F
2402 2480
2403static void game_compute_size(const game_params *params, int tilesize, 2481static void game_compute_size(const game_params *params, int tilesize,
2404 int *x, int *y) 2482 const game_ui *ui, int *x, int *y)
2405{ 2483{
2406 /* Ick: fake up `ds->tilesize' for macro expansion purposes */ 2484 /* Ick: fake up `ds->sz6' and `ds->border` for macro expansion purposes */
2407 struct { 2485 struct {
2408 int sz6; 2486 int sz6, border;
2409 } ads, *ds = &ads; 2487 } ads, *ds = &ads;
2410 ads.sz6 = tilesize/6; 2488 ads.sz6 = tilesize/6;
2489 ads.border = MAX_BORDER;
2490 /*
2491 * Allow a reduced border at small tile sizes because the steps
2492 * are quite large and it's better to have a thin border than
2493 * to go down to a smaller tile size.
2494 */
2495 if (ads.border <= 5)
2496 ads.border = min(tilesize % 6, MAX_BORDER);
2411 *x = (params->w+2) * TILE_SIZE + 2 * BORDER; 2497 *x = (params->w+2) * TILE_SIZE + 2 * BORDER;
2412 *y = (params->h+2) * TILE_SIZE + 2 * BORDER; 2498 *y = (params->h+2) * TILE_SIZE + 2 * BORDER;
2413} 2499}
@@ -2416,14 +2502,16 @@ static void game_set_size(drawing *dr, game_drawstate *ds,
2416 const game_params *params, int tilesize) 2502 const game_params *params, int tilesize)
2417{ 2503{
2418 ds->sz6 = tilesize/6; 2504 ds->sz6 = tilesize/6;
2505 ds->border = MAX_BORDER;
2506 if (ds->border <= 5)
2507 ds->border = min(tilesize % 6, MAX_BORDER);
2419 ds->grid_line_all = max(LINE_THICK, 1); 2508 ds->grid_line_all = max(LINE_THICK, 1);
2420 ds->grid_line_br = ds->grid_line_all / 2; 2509 ds->grid_line_br = ds->grid_line_all / 2;
2421 ds->grid_line_tl = ds->grid_line_all - ds->grid_line_br; 2510 ds->grid_line_tl = ds->grid_line_all - ds->grid_line_br;
2422} 2511}
2423 2512
2424enum { 2513enum {
2425 COL_BACKGROUND, COL_LOWLIGHT, COL_HIGHLIGHT, 2514 COL_BACKGROUND, COL_TRACK_BACKGROUND,
2426 COL_TRACK_BACKGROUND = COL_LOWLIGHT,
2427 COL_GRID, COL_CLUE, COL_CURSOR, 2515 COL_GRID, COL_CLUE, COL_CURSOR,
2428 COL_TRACK, COL_TRACK_CLUE, COL_SLEEPER, 2516 COL_TRACK, COL_TRACK_CLUE, COL_SLEEPER,
2429 COL_DRAGON, COL_DRAGOFF, 2517 COL_DRAGON, COL_DRAGOFF,
@@ -2436,14 +2524,15 @@ static float *game_colours(frontend *fe, int *ncolours)
2436 float *ret = snewn(3 * NCOLOURS, float); 2524 float *ret = snewn(3 * NCOLOURS, float);
2437 int i; 2525 int i;
2438 2526
2439 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); 2527 game_mkhighlight(fe, ret, COL_BACKGROUND, -1, COL_TRACK_BACKGROUND);
2528 colour_mix(&ret[COL_BACKGROUND*3], &ret[COL_TRACK_BACKGROUND*3], 0.5F,
2529 &ret[COL_GRID*3]);
2440 2530
2441 for (i = 0; i < 3; i++) { 2531 for (i = 0; i < 3; i++) {
2442 ret[COL_TRACK_CLUE * 3 + i] = 0.0F; 2532 ret[COL_TRACK_CLUE * 3 + i] = 0.0F;
2443 ret[COL_TRACK * 3 + i] = 0.5F; 2533 ret[COL_TRACK * 3 + i] = 0.5F;
2444 ret[COL_CLUE * 3 + i] = 0.0F; 2534 ret[COL_CLUE * 3 + i] = 0.0F;
2445 ret[COL_GRID * 3 + i] = 0.75F; 2535 ret[COL_CURSOR * 3 + i] = 0.3F;
2446 ret[COL_CURSOR * 3 + i] = 0.6F;
2447 ret[COL_ERROR_BACKGROUND * 3 + i] = 1.0F; 2536 ret[COL_ERROR_BACKGROUND * 3 + i] = 1.0F;
2448 } 2537 }
2449 2538
@@ -2535,7 +2624,8 @@ static void draw_thick_circle_outline(drawing *dr, float thickness,
2535 x2 = cx + r*(float)cos(th2); 2624 x2 = cx + r*(float)cos(th2);
2536 y1 = cy + r*(float)sin(th); 2625 y1 = cy + r*(float)sin(th);
2537 y2 = cy + r*(float)sin(th2); 2626 y2 = cy + r*(float)sin(th2);
2538 debug(("circ outline: x=%.2f -> %.2f, thick=%.2f", x1, x2, thickness)); 2627 debug(("circ outline: x=%.2f -> %.2f, thick=%.2f\n",
2628 x1, x2, thickness));
2539 draw_thick_line(dr, thickness, x1, y1, x2, y2, colour); 2629 draw_thick_line(dr, thickness, x1, y1, x2, y2, colour);
2540 } 2630 }
2541} 2631}
@@ -2665,7 +2755,7 @@ static void draw_square(drawing *dr, game_drawstate *ds,
2665 curx = ox + TILE_SIZE - off; curw = 2*off + 1; 2755 curx = ox + TILE_SIZE - off; curw = 2*off + 1;
2666 } 2756 }
2667 2757
2668 draw_rect_outline(dr, curx, cury, curw, curh, COL_GRID); 2758 draw_rect_outline(dr, curx, cury, curw, curh, COL_CURSOR);
2669 } 2759 }
2670 2760
2671 /* Draw tracks themselves */ 2761 /* Draw tracks themselves */
@@ -2777,20 +2867,11 @@ static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldst
2777 const game_state *state, int dir, const game_ui *ui, 2867 const game_state *state, int dir, const game_ui *ui,
2778 float animtime, float flashtime) 2868 float animtime, float flashtime)
2779{ 2869{
2780 int i, x, y, flashing = 0, w = ds->w, h = ds->h; 2870 int i, x, y, flashing, w = ds->w, h = ds->h;
2781 bool force = false; 2871 bool force = false;
2782 game_state *drag_state = NULL; 2872 game_state *drag_state = NULL;
2783 2873
2784 if (!ds->started) { 2874 if (!ds->started) {
2785 /*
2786 * The initial contents of the window are not guaranteed and
2787 * can vary with front ends. To be on the safe side, all games
2788 * should start by drawing a big background-colour rectangle
2789 * covering the whole window.
2790 */
2791 draw_rect(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER,
2792 COL_BACKGROUND);
2793
2794 draw_loop_ends(dr, ds, state, COL_CLUE); 2875 draw_loop_ends(dr, ds, state, COL_CLUE);
2795 2876
2796 draw_rect(dr, COORD(0) - GRID_LINE_BR, COORD(0) - GRID_LINE_BR, 2877 draw_rect(dr, COORD(0) - GRID_LINE_BR, COORD(0) - GRID_LINE_BR,
@@ -2812,11 +2893,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldst
2812 } 2893 }
2813 } 2894 }
2814 2895
2815 if (flashtime > 0 &&
2816 (flashtime <= FLASH_TIME/3 ||
2817 flashtime >= FLASH_TIME*2/3))
2818 flashing = DS_FLASH;
2819
2820 if (ui->dragging) 2896 if (ui->dragging)
2821 drag_state = copy_and_apply_drag(state, ui); 2897 drag_state = copy_and_apply_drag(state, ui);
2822 2898
@@ -2824,6 +2900,16 @@ static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldst
2824 for (y = 0; y < h; y++) { 2900 for (y = 0; y < h; y++) {
2825 unsigned int f, f_d; 2901 unsigned int f, f_d;
2826 2902
2903 flashing = 0;
2904 if (flashtime > 0) {
2905 float flashpos =
2906 (state->sflags[y*w+x] >> S_FLASH_SHIFT & S_FLASH_MASK) /
2907 (float)S_FLASH_MASK;
2908 if (flashtime > FLASH_TIME / 2 * flashpos &&
2909 flashtime <= FLASH_TIME / 2 * (flashpos + 1.0F))
2910 flashing = DS_FLASH;
2911 }
2912
2827 f = s2d_flags(state, x, y, ui) | flashing; 2913 f = s2d_flags(state, x, y, ui) | flashing;
2828 f_d = drag_state ? s2d_flags(drag_state, x, y, ui) : f; 2914 f_d = drag_state ? s2d_flags(drag_state, x, y, ui) : f;
2829 2915
@@ -2890,22 +2976,19 @@ static int game_status(const game_state *state)
2890 return state->completed ? +1 : 0; 2976 return state->completed ? +1 : 0;
2891} 2977}
2892 2978
2893static bool game_timing_state(const game_state *state, game_ui *ui) 2979static void game_print_size(const game_params *params, const game_ui *ui,
2894{ 2980 float *x, float *y)
2895 return true;
2896}
2897
2898static void game_print_size(const game_params *params, float *x, float *y)
2899{ 2981{
2900 int pw, ph; 2982 int pw, ph;
2901 2983
2902 /* The Times uses 7mm squares */ 2984 /* The Times uses 7mm squares */
2903 game_compute_size(params, 700, &pw, &ph); 2985 game_compute_size(params, 700, ui, &pw, &ph);
2904 *x = pw / 100.0F; 2986 *x = pw / 100.0F;
2905 *y = ph / 100.0F; 2987 *y = ph / 100.0F;
2906} 2988}
2907 2989
2908static void game_print(drawing *dr, const game_state *state, int tilesize) 2990static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
2991 int tilesize)
2909{ 2992{
2910 int w = state->p.w, h = state->p.h; 2993 int w = state->p.w, h = state->p.h;
2911 int black = print_mono_colour(dr, 0), grey = print_grey_colour(dr, 0.5F); 2994 int black = print_mono_colour(dr, 0), grey = print_grey_colour(dr, 0.5F);
@@ -2948,7 +3031,7 @@ static void game_print(drawing *dr, const game_state *state, int tilesize)
2948#endif 3031#endif
2949 3032
2950const struct game thegame = { 3033const struct game thegame = {
2951 "Tracks", "games.tracks", "tracks", 3034 "Train Tracks", "games.tracks", "tracks",
2952 default_params, 3035 default_params,
2953 game_fetch_preset, NULL, 3036 game_fetch_preset, NULL,
2954 decode_params, 3037 decode_params,
@@ -2964,12 +3047,14 @@ const struct game thegame = {
2964 free_game, 3047 free_game,
2965 true, solve_game, 3048 true, solve_game,
2966 true, game_can_format_as_text_now, game_text_format, 3049 true, game_can_format_as_text_now, game_text_format,
3050 NULL, NULL, /* get_prefs, set_prefs */
2967 new_ui, 3051 new_ui,
2968 free_ui, 3052 free_ui,
2969 encode_ui, 3053 NULL, /* encode_ui */
2970 decode_ui, 3054 NULL, /* decode_ui */
2971 NULL, /* game_request_keys */ 3055 NULL, /* game_request_keys */
2972 game_changed_state, 3056 game_changed_state,
3057 current_key_label,
2973 interpret_move, 3058 interpret_move,
2974 execute_move, 3059 execute_move,
2975 PREFERRED_TILE_SIZE, game_compute_size, game_set_size, 3060 PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
@@ -2983,7 +3068,7 @@ const struct game thegame = {
2983 game_status, 3068 game_status,
2984 true, false, game_print_size, game_print, 3069 true, false, game_print_size, game_print,
2985 false, /* wants_statusbar */ 3070 false, /* wants_statusbar */
2986 false, game_timing_state, 3071 false, NULL, /* timing_state */
2987 0, /* flags */ 3072 0, /* flags */
2988}; 3073};
2989 3074