diff options
Diffstat (limited to 'apps/plugins/puzzles/src/tracks.c')
-rw-r--r-- | apps/plugins/puzzles/src/tracks.c | 351 |
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 | ||
906 | struct solver_scratch { | 916 | struct solver_scratch { |
907 | int *dsf; | 917 | DSF *dsf; |
908 | }; | 918 | }; |
909 | 919 | ||
910 | static int solve_set_sflag(game_state *state, int x, int y, | 920 | static 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 | ||
1308 | static int solve_check_loop_sub(game_state *state, int x, int y, int dir, | 1318 | static 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, | |||
1358 | static int solve_check_loop(game_state *state) | 1368 | static 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 | ||
1777 | static void dsf_update_completion(game_state *state, int ax, int ay, | 1787 | static 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 | */ | ||
1839 | static 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 | |||
1823 | static bool check_completion(game_state *state, bool mark) | 1857 | static 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 | ||
1988 | static char *encode_ui(const game_ui *ui) | ||
1989 | { | ||
1990 | return NULL; | ||
1991 | } | ||
1992 | |||
1993 | static void decode_ui(game_ui *ui, const char *encoding) | ||
1994 | { | ||
1995 | } | ||
1996 | |||
1997 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | 2036 | static 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 | ||
2030 | struct game_drawstate { | 2070 | struct 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 | ||
2163 | static 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 | |||
2123 | static char *edge_flip_str(const game_state *state, int x, int y, int dir, bool notrack, char *buf) { | 2192 | static 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 | ||
2403 | static void game_compute_size(const game_params *params, int tilesize, | 2481 | static 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 | ||
2424 | enum { | 2513 | enum { |
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 | ||
2893 | static bool game_timing_state(const game_state *state, game_ui *ui) | 2979 | static void game_print_size(const game_params *params, const game_ui *ui, |
2894 | { | 2980 | float *x, float *y) |
2895 | return true; | ||
2896 | } | ||
2897 | |||
2898 | static 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 | ||
2908 | static void game_print(drawing *dr, const game_state *state, int tilesize) | 2990 | static 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 | ||
2950 | const struct game thegame = { | 3033 | const 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 | ||