summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/pearl.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/pearl.c')
-rw-r--r--apps/plugins/puzzles/src/pearl.c499
1 files changed, 320 insertions, 179 deletions
diff --git a/apps/plugins/puzzles/src/pearl.c b/apps/plugins/puzzles/src/pearl.c
index 2657d45a67..95993172ca 100644
--- a/apps/plugins/puzzles/src/pearl.c
+++ b/apps/plugins/puzzles/src/pearl.c
@@ -35,7 +35,12 @@
35#include <string.h> 35#include <string.h>
36#include <assert.h> 36#include <assert.h>
37#include <ctype.h> 37#include <ctype.h>
38#include <math.h> 38#include <limits.h>
39#ifdef NO_TGMATH_H
40# include <math.h>
41#else
42# include <tgmath.h>
43#endif
39 44
40#include "puzzles.h" 45#include "puzzles.h"
41#include "grid.h" 46#include "grid.h"
@@ -272,8 +277,12 @@ static const char *validate_params(const game_params *params, bool full)
272{ 277{
273 if (params->w < 5) return "Width must be at least five"; 278 if (params->w < 5) return "Width must be at least five";
274 if (params->h < 5) return "Height must be at least five"; 279 if (params->h < 5) return "Height must be at least five";
280 if (params->w > INT_MAX / params->h)
281 return "Width times height must not be unreasonably large";
275 if (params->difficulty < 0 || params->difficulty >= DIFFCOUNT) 282 if (params->difficulty < 0 || params->difficulty >= DIFFCOUNT)
276 return "Unknown difficulty level"; 283 return "Unknown difficulty level";
284 if (params->difficulty >= DIFF_TRICKY && params->w + params->h < 11)
285 return "Width or height must be at least six for Tricky";
277 286
278 return NULL; 287 return NULL;
279} 288}
@@ -287,7 +296,8 @@ static int pearl_solve(int w, int h, char *clues, char *result,
287{ 296{
288 int W = 2*w+1, H = 2*h+1; 297 int W = 2*w+1, H = 2*h+1;
289 short *workspace; 298 short *workspace;
290 int *dsf, *dsfsize; 299 DSF *dsf;
300 int *dsfsize;
291 int x, y, b, d; 301 int x, y, b, d;
292 int ret = -1; 302 int ret = -1;
293 303
@@ -340,7 +350,7 @@ static int pearl_solve(int w, int h, char *clues, char *result,
340 * We maintain a dsf of connected squares, together with a 350 * We maintain a dsf of connected squares, together with a
341 * count of the size of each equivalence class. 351 * count of the size of each equivalence class.
342 */ 352 */
343 dsf = snewn(w*h, int); 353 dsf = dsf_new(w*h);
344 dsfsize = snewn(w*h, int); 354 dsfsize = snewn(w*h, int);
345 355
346 /* 356 /*
@@ -583,7 +593,7 @@ static int pearl_solve(int w, int h, char *clues, char *result,
583 { 593 {
584 int nonblanks, loopclass; 594 int nonblanks, loopclass;
585 595
586 dsf_init(dsf, w*h); 596 dsf_reinit(dsf);
587 for (x = 0; x < w*h; x++) 597 for (x = 0; x < w*h; x++)
588 dsfsize[x] = 1; 598 dsfsize[x] = 1;
589 599
@@ -850,10 +860,39 @@ cleanup:
850 if (ret == 1) assert(b < 0xD); /* we should have had a break by now */ 860 if (ret == 1) assert(b < 0xD); /* we should have had a break by now */
851 } 861 }
852 } 862 }
863
864 /*
865 * Ensure we haven't left the _data structure_ inconsistent,
866 * regardless of the consistency of the _puzzle_. In
867 * particular, we should never have marked one square as
868 * linked to its neighbour if the neighbour is not
869 * reciprocally linked back to the original square.
870 *
871 * This can happen if we get part way through solving an
872 * impossible puzzle and then give up trying to make further
873 * progress. So here we fix it up to avoid confusing the rest
874 * of the game.
875 */
876 for (y = 0; y < h; y++) {
877 for (x = 0; x < w; x++) {
878 for (d = 1; d <= 8; d += d) {
879 int nx = x + DX(d), ny = y + DY(d);
880 int rlink;
881 if (0 <= nx && nx < w && 0 <= ny && ny < h)
882 rlink = result[ny*w+nx] & F(d);
883 else
884 rlink = 0; /* off-board squares don't link back */
885
886 /* If other square doesn't link to us, don't link to it */
887 if (!rlink)
888 result[y*w+x] &= ~d;
889 }
890 }
891 }
853 } 892 }
854 893
855 sfree(dsfsize); 894 sfree(dsfsize);
856 sfree(dsf); 895 dsf_free(dsf);
857 sfree(workspace); 896 sfree(workspace);
858 assert(ret >= 0); 897 assert(ret >= 0);
859 return ret; 898 return ret;
@@ -941,9 +980,9 @@ static int pearl_loopgen_bias(void *vctx, char *board, int face)
941 * to reprocess the edges for this boundary. 980 * to reprocess the edges for this boundary.
942 */ 981 */
943 if (oldface == c || newface == c) { 982 if (oldface == c || newface == c) {
944 grid_face *f = &g->faces[face]; 983 grid_face *f = g->faces[face];
945 for (k = 0; k < f->order; k++) 984 for (k = 0; k < f->order; k++)
946 tdq_add(b->edges_todo, f->edges[k] - g->edges); 985 tdq_add(b->edges_todo, f->edges[k]->index);
947 } 986 }
948 } 987 }
949 } 988 }
@@ -959,15 +998,15 @@ static int pearl_loopgen_bias(void *vctx, char *board, int face)
959 * the vertextypes_todo list. 998 * the vertextypes_todo list.
960 */ 999 */
961 while ((j = tdq_remove(b->edges_todo)) >= 0) { 1000 while ((j = tdq_remove(b->edges_todo)) >= 0) {
962 grid_edge *e = &g->edges[j]; 1001 grid_edge *e = g->edges[j];
963 int fc1 = e->face1 ? board[e->face1 - g->faces] : FACE_BLACK; 1002 int fc1 = e->face1 ? board[e->face1->index] : FACE_BLACK;
964 int fc2 = e->face2 ? board[e->face2 - g->faces] : FACE_BLACK; 1003 int fc2 = e->face2 ? board[e->face2->index] : FACE_BLACK;
965 bool oldedge = b->edges[j]; 1004 bool oldedge = b->edges[j];
966 bool newedge = (fc1==c) ^ (fc2==c); 1005 bool newedge = (fc1==c) ^ (fc2==c);
967 if (oldedge != newedge) { 1006 if (oldedge != newedge) {
968 b->edges[j] = newedge; 1007 b->edges[j] = newedge;
969 tdq_add(b->vertextypes_todo, e->dot1 - g->dots); 1008 tdq_add(b->vertextypes_todo, e->dot1->index);
970 tdq_add(b->vertextypes_todo, e->dot2 - g->dots); 1009 tdq_add(b->vertextypes_todo, e->dot2->index);
971 } 1010 }
972 } 1011 }
973 1012
@@ -982,7 +1021,7 @@ static int pearl_loopgen_bias(void *vctx, char *board, int face)
982 * old neighbours. 1021 * old neighbours.
983 */ 1022 */
984 while ((j = tdq_remove(b->vertextypes_todo)) >= 0) { 1023 while ((j = tdq_remove(b->vertextypes_todo)) >= 0) {
985 grid_dot *d = &g->dots[j]; 1024 grid_dot *d = g->dots[j];
986 int neighbours[2], type = 0, n = 0; 1025 int neighbours[2], type = 0, n = 0;
987 1026
988 for (k = 0; k < d->order; k++) { 1027 for (k = 0; k < d->order; k++) {
@@ -990,10 +1029,10 @@ static int pearl_loopgen_bias(void *vctx, char *board, int face)
990 grid_dot *d2 = (e->dot1 == d ? e->dot2 : e->dot1); 1029 grid_dot *d2 = (e->dot1 == d ? e->dot2 : e->dot1);
991 /* dir == 0,1,2,3 for an edge going L,U,R,D */ 1030 /* dir == 0,1,2,3 for an edge going L,U,R,D */
992 int dir = (d->y == d2->y) + 2*(d->x+d->y > d2->x+d2->y); 1031 int dir = (d->y == d2->y) + 2*(d->x+d->y > d2->x+d2->y);
993 int ei = e - g->edges; 1032 int ei = e->index;
994 if (b->edges[ei]) { 1033 if (b->edges[ei]) {
995 type |= 1 << dir; 1034 type |= 1 << dir;
996 neighbours[n] = d2 - g->dots; 1035 neighbours[n] = d2->index;
997 n++; 1036 n++;
998 } 1037 }
999 } 1038 }
@@ -1048,9 +1087,8 @@ static int pearl_loopgen_bias(void *vctx, char *board, int face)
1048 return ctx->score; 1087 return ctx->score;
1049} 1088}
1050 1089
1051static void pearl_loopgen(int w, int h, char *lines, random_state *rs) 1090static void pearl_loopgen(int w, int h, char *lines, random_state *rs, grid *g)
1052{ 1091{
1053 grid *g = grid_new(GRID_SQUARE, w-1, h-1, NULL);
1054 char *board = snewn(g->num_faces, char); 1092 char *board = snewn(g->num_faces, char);
1055 int i, s = g->tilesize; 1093 int i, s = g->tilesize;
1056 struct pearl_loopgen_bias_ctx biasctx; 1094 struct pearl_loopgen_bias_ctx biasctx;
@@ -1102,7 +1140,7 @@ static void pearl_loopgen(int w, int h, char *lines, random_state *rs)
1102 } 1140 }
1103 1141
1104 for (i = 0; i < g->num_edges; i++) { 1142 for (i = 0; i < g->num_edges; i++) {
1105 grid_edge *e = g->edges + i; 1143 grid_edge *e = g->edges[i];
1106 enum face_colour c1 = FACE_COLOUR(e->face1); 1144 enum face_colour c1 = FACE_COLOUR(e->face1);
1107 enum face_colour c2 = FACE_COLOUR(e->face2); 1145 enum face_colour c2 = FACE_COLOUR(e->face2);
1108 assert(c1 != FACE_GREY); 1146 assert(c1 != FACE_GREY);
@@ -1130,7 +1168,6 @@ static void pearl_loopgen(int w, int h, char *lines, random_state *rs)
1130 } 1168 }
1131 } 1169 }
1132 1170
1133 grid_free(g);
1134 sfree(board); 1171 sfree(board);
1135 1172
1136#if defined LOOPGEN_DIAGNOSTICS && !defined GENERATION_DIAGNOSTICS 1173#if defined LOOPGEN_DIAGNOSTICS && !defined GENERATION_DIAGNOSTICS
@@ -1153,11 +1190,11 @@ static void pearl_loopgen(int w, int h, char *lines, random_state *rs)
1153} 1190}
1154 1191
1155static int new_clues(const game_params *params, random_state *rs, 1192static int new_clues(const game_params *params, random_state *rs,
1156 char *clues, char *grid) 1193 char *clues, char *grid_out)
1157{ 1194{
1158 int w = params->w, h = params->h, diff = params->difficulty; 1195 int w = params->w, h = params->h, diff = params->difficulty;
1159 int ngen = 0, x, y, d, ret, i; 1196 int ngen = 0, x, y, d, ret, i;
1160 1197 grid *g = grid_new(GRID_SQUARE, w-1, h-1, NULL);
1161 1198
1162 /* 1199 /*
1163 * Difficulty exception: 5x5 Tricky is not generable (the 1200 * Difficulty exception: 5x5 Tricky is not generable (the
@@ -1168,13 +1205,13 @@ static int new_clues(const game_params *params, random_state *rs,
1168 1205
1169 while (1) { 1206 while (1) {
1170 ngen++; 1207 ngen++;
1171 pearl_loopgen(w, h, grid, rs); 1208 pearl_loopgen(w, h, grid_out, rs, g);
1172 1209
1173#ifdef GENERATION_DIAGNOSTICS 1210#ifdef GENERATION_DIAGNOSTICS
1174 printf("grid array:\n"); 1211 printf("grid array:\n");
1175 for (y = 0; y < h; y++) { 1212 for (y = 0; y < h; y++) {
1176 for (x = 0; x < w; x++) { 1213 for (x = 0; x < w; x++) {
1177 int type = grid[y*w+x]; 1214 int type = grid_out[y*w+x];
1178 char s[5], *p = s; 1215 char s[5], *p = s;
1179 if (type & L) *p++ = 'L'; 1216 if (type & L) *p++ = 'L';
1180 if (type & R) *p++ = 'R'; 1217 if (type & R) *p++ = 'R';
@@ -1193,7 +1230,7 @@ static int new_clues(const game_params *params, random_state *rs,
1193 */ 1230 */
1194 for (y = 0; y < h; y++) 1231 for (y = 0; y < h; y++)
1195 for (x = 0; x < w; x++) { 1232 for (x = 0; x < w; x++) {
1196 int type = grid[y*w+x]; 1233 int type = grid_out[y*w+x];
1197 1234
1198 clues[y*w+x] = NOCLUE; 1235 clues[y*w+x] = NOCLUE;
1199 1236
@@ -1207,7 +1244,7 @@ static int new_clues(const game_params *params, random_state *rs,
1207 for (d = 1; d <= 8; d += d) if (type & d) { 1244 for (d = 1; d <= 8; d += d) if (type & d) {
1208 int xx = x + DX(d), yy = y + DY(d); 1245 int xx = x + DX(d), yy = y + DY(d);
1209 assert(xx >= 0 && xx < w && yy >= 0 && yy < h); 1246 assert(xx >= 0 && xx < w && yy >= 0 && yy < h);
1210 if ((bLU|bLD|bRU|bRD) & (1 << grid[yy*w+xx])) 1247 if ((bLU|bLD|bRU|bRD) & (1 << grid_out[yy*w+xx]))
1211 break; 1248 break;
1212 } 1249 }
1213 if (d <= 8) /* we found one */ 1250 if (d <= 8) /* we found one */
@@ -1221,7 +1258,7 @@ static int new_clues(const game_params *params, random_state *rs,
1221 for (d = 1; d <= 8; d += d) if (type & d) { 1258 for (d = 1; d <= 8; d += d) if (type & d) {
1222 int xx = x + DX(d), yy = y + DY(d); 1259 int xx = x + DX(d), yy = y + DY(d);
1223 assert(xx >= 0 && xx < w && yy >= 0 && yy < h); 1260 assert(xx >= 0 && xx < w && yy >= 0 && yy < h);
1224 if (!((bLR|bUD) & (1 << grid[yy*w+xx]))) 1261 if (!((bLR|bUD) & (1 << grid_out[yy*w+xx])))
1225 break; 1262 break;
1226 } 1263 }
1227 if (d > 8) /* we didn't find a counterexample */ 1264 if (d > 8) /* we didn't find a counterexample */
@@ -1247,7 +1284,7 @@ static int new_clues(const game_params *params, random_state *rs,
1247 /* 1284 /*
1248 * See if we can solve the puzzle just like this. 1285 * See if we can solve the puzzle just like this.
1249 */ 1286 */
1250 ret = pearl_solve(w, h, clues, grid, diff, false); 1287 ret = pearl_solve(w, h, clues, grid_out, diff, false);
1251 assert(ret > 0); /* shouldn't be inconsistent! */ 1288 assert(ret > 0); /* shouldn't be inconsistent! */
1252 if (ret != 1) 1289 if (ret != 1)
1253 continue; /* go round and try again */ 1290 continue; /* go round and try again */
@@ -1256,7 +1293,7 @@ static int new_clues(const game_params *params, random_state *rs,
1256 * Check this puzzle isn't too easy. 1293 * Check this puzzle isn't too easy.
1257 */ 1294 */
1258 if (diff > DIFF_EASY) { 1295 if (diff > DIFF_EASY) {
1259 ret = pearl_solve(w, h, clues, grid, diff-1, false); 1296 ret = pearl_solve(w, h, clues, grid_out, diff-1, false);
1260 assert(ret > 0); 1297 assert(ret > 0);
1261 if (ret == 1) 1298 if (ret == 1)
1262 continue; /* too easy: try again */ 1299 continue; /* too easy: try again */
@@ -1323,7 +1360,7 @@ static int new_clues(const game_params *params, random_state *rs,
1323 clue = clues[y*w+x]; 1360 clue = clues[y*w+x];
1324 clues[y*w+x] = 0; /* try removing this clue */ 1361 clues[y*w+x] = 0; /* try removing this clue */
1325 1362
1326 ret = pearl_solve(w, h, clues, grid, diff, false); 1363 ret = pearl_solve(w, h, clues, grid_out, diff, false);
1327 assert(ret > 0); 1364 assert(ret > 0);
1328 if (ret != 1) 1365 if (ret != 1)
1329 clues[y*w+x] = clue; /* oops, put it back again */ 1366 clues[y*w+x] = clue; /* oops, put it back again */
@@ -1344,6 +1381,7 @@ static int new_clues(const game_params *params, random_state *rs,
1344 1381
1345 break; /* got it */ 1382 break; /* got it */
1346 } 1383 }
1384 grid_free(g);
1347 1385
1348 debug(("%d %dx%d loops before finished puzzle.\n", ngen, w, h)); 1386 debug(("%d %dx%d loops before finished puzzle.\n", ngen, w, h));
1349 1387
@@ -1491,29 +1529,35 @@ static char nbits[16] = { 0, 1, 1, 2,
1491 1529
1492#define ERROR_CLUE 16 1530#define ERROR_CLUE 16
1493 1531
1494static void dsf_update_completion(game_state *state, int ax, int ay, char dir, 1532/* Returns false if the state is invalid. */
1495 int *dsf) 1533static bool dsf_update_completion(game_state *state, int ax, int ay, char dir,
1534 DSF *dsf)
1496{ 1535{
1497 int w = state->shared->w /*, h = state->shared->h */; 1536 int w = state->shared->w /*, h = state->shared->h */;
1498 int ac = ay*w+ax, bx, by, bc; 1537 int ac = ay*w+ax, bx, by, bc;
1499 1538
1500 if (!(state->lines[ac] & dir)) return; /* no link */ 1539 if (!(state->lines[ac] & dir)) return true; /* no link */
1501 bx = ax + DX(dir); by = ay + DY(dir); 1540 bx = ax + DX(dir); by = ay + DY(dir);
1502 1541
1503 assert(INGRID(state, bx, by)); /* should not have a link off grid */ 1542 if (!INGRID(state, bx, by))
1543 return false; /* should not have a link off grid */
1504 1544
1505 bc = by*w+bx; 1545 bc = by*w+bx;
1506 assert(state->lines[bc] & F(dir)); /* should have reciprocal link */ 1546 if (!(state->lines[bc] & F(dir)))
1507 if (!(state->lines[bc] & F(dir))) return; 1547 return false; /* should have reciprocal link */
1548 if (!(state->lines[bc] & F(dir))) return true;
1508 1549
1509 dsf_merge(dsf, ac, bc); 1550 dsf_merge(dsf, ac, bc);
1551 return true;
1510} 1552}
1511 1553
1512static void check_completion(game_state *state, bool mark) 1554/* Returns false if the state is invalid. */
1555static bool check_completion(game_state *state, bool mark)
1513{ 1556{
1514 int w = state->shared->w, h = state->shared->h, x, y, i, d; 1557 int w = state->shared->w, h = state->shared->h, x, y, i, d;
1515 bool had_error = false; 1558 bool had_error = false;
1516 int *dsf, *component_state; 1559 DSF *dsf;
1560 int *component_state;
1517 int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize; 1561 int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize;
1518 enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY }; 1562 enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY };
1519 1563
@@ -1532,13 +1576,16 @@ static void check_completion(game_state *state, bool mark)
1532 * same reasons, since Loopy and Pearl have basically the same 1576 * same reasons, since Loopy and Pearl have basically the same
1533 * form of expected solution. 1577 * form of expected solution.
1534 */ 1578 */
1535 dsf = snew_dsf(w*h); 1579 dsf = dsf_new(w*h);
1536 1580
1537 /* Build the dsf. */ 1581 /* Build the dsf. */
1538 for (x = 0; x < w; x++) { 1582 for (x = 0; x < w; x++) {
1539 for (y = 0; y < h; y++) { 1583 for (y = 0; y < h; y++) {
1540 dsf_update_completion(state, x, y, R, dsf); 1584 if (!dsf_update_completion(state, x, y, R, dsf) ||
1541 dsf_update_completion(state, x, y, D, dsf); 1585 !dsf_update_completion(state, x, y, D, dsf)) {
1586 dsf_free(dsf);
1587 return false;
1588 }
1542 } 1589 }
1543 } 1590 }
1544 1591
@@ -1619,7 +1666,7 @@ static void check_completion(game_state *state, bool mark)
1619 * part of a single loop, for which our counter variables 1666 * part of a single loop, for which our counter variables
1620 * nsilly,nloop,npath are enough. */ 1667 * nsilly,nloop,npath are enough. */
1621 sfree(component_state); 1668 sfree(component_state);
1622 sfree(dsf); 1669 dsf_free(dsf);
1623 1670
1624 /* 1671 /*
1625 * Check that no clues are contradicted. This code is similar to 1672 * Check that no clues are contradicted. This code is similar to
@@ -1693,6 +1740,7 @@ static void check_completion(game_state *state, bool mark)
1693 if (!had_error) 1740 if (!had_error)
1694 state->completed = true; 1741 state->completed = true;
1695 } 1742 }
1743 return true;
1696} 1744}
1697 1745
1698/* completion check: 1746/* completion check:
@@ -1810,18 +1858,53 @@ struct game_ui {
1810 1858
1811 int curx, cury; /* grid position of keyboard cursor */ 1859 int curx, cury; /* grid position of keyboard cursor */
1812 bool cursor_active; /* true iff cursor is shown */ 1860 bool cursor_active; /* true iff cursor is shown */
1861
1862 /*
1863 * User preference: general visual style of the GUI. GUI_MASYU is
1864 * how this puzzle is traditionally presented, with clue dots in
1865 * the middle of grid squares, and the solution loop connecting
1866 * square-centres. GUI_LOOPY shifts the grid by half a square in
1867 * each direction, so that the clue dots are at _vertices_ of the
1868 * grid and the solution loop follows the grid edges, which you
1869 * could argue is more logical.
1870 */
1871 enum { GUI_MASYU, GUI_LOOPY } gui_style;
1813}; 1872};
1814 1873
1874static void legacy_prefs_override(struct game_ui *ui_out)
1875{
1876 static bool initialised = false;
1877 static int gui_style = -1;
1878
1879 if (!initialised) {
1880 initialised = true;
1881
1882 switch (getenv_bool("PEARL_GUI_LOOPY", -1)) {
1883 case 0:
1884 gui_style = GUI_MASYU;
1885 break;
1886 case 1:
1887 gui_style = GUI_LOOPY;
1888 break;
1889 }
1890 }
1891
1892 if (gui_style != -1)
1893 ui_out->gui_style = gui_style;
1894}
1895
1815static game_ui *new_ui(const game_state *state) 1896static game_ui *new_ui(const game_state *state)
1816{ 1897{
1817 game_ui *ui = snew(game_ui); 1898 game_ui *ui = snew(game_ui);
1818 int sz = state->shared->sz;
1819 1899
1820 ui->ndragcoords = -1; 1900 ui->ndragcoords = -1;
1821 ui->dragcoords = snewn(sz, int); 1901 ui->dragcoords = state ? snewn(state->shared->sz, int) : NULL;
1822 ui->cursor_active = false; 1902 ui->cursor_active = getenv_bool("PUZZLES_SHOW_CURSOR", false);
1823 ui->curx = ui->cury = 0; 1903 ui->curx = ui->cury = 0;
1824 1904
1905 ui->gui_style = GUI_MASYU;
1906 legacy_prefs_override(ui);
1907
1825 return ui; 1908 return ui;
1826} 1909}
1827 1910
@@ -1831,13 +1914,28 @@ static void free_ui(game_ui *ui)
1831 sfree(ui); 1914 sfree(ui);
1832} 1915}
1833 1916
1834static char *encode_ui(const game_ui *ui) 1917static config_item *get_prefs(game_ui *ui)
1835{ 1918{
1836 return NULL; 1919 config_item *ret;
1920
1921 ret = snewn(2, config_item);
1922
1923 ret[0].name = "Puzzle appearance";
1924 ret[0].kw = "appearance";
1925 ret[0].type = C_CHOICES;
1926 ret[0].u.choices.choicenames = ":Traditional:Loopy-style";
1927 ret[0].u.choices.choicekws = ":traditional:loopy";
1928 ret[0].u.choices.selected = ui->gui_style;
1929
1930 ret[1].name = NULL;
1931 ret[1].type = C_END;
1932
1933 return ret;
1837} 1934}
1838 1935
1839static void decode_ui(game_ui *ui, const char *encoding) 1936static void set_prefs(game_ui *ui, const config_item *cfg)
1840{ 1937{
1938 ui->gui_style = cfg[0].u.choices.selected;
1841} 1939}
1842 1940
1843static void game_changed_state(game_ui *ui, const game_state *oldstate, 1941static void game_changed_state(game_ui *ui, const game_state *oldstate,
@@ -1845,11 +1943,25 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
1845{ 1943{
1846} 1944}
1847 1945
1946static const char *current_key_label(const game_ui *ui,
1947 const game_state *state, int button)
1948{
1949 if (IS_CURSOR_SELECT(button) && ui->cursor_active) {
1950 if (button == CURSOR_SELECT) {
1951 if (ui->ndragcoords == -1) return "Start";
1952 return "Stop";
1953 }
1954 if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0)
1955 return "Cancel";
1956 }
1957 return "";
1958}
1959
1848#define PREFERRED_TILE_SIZE 31 1960#define PREFERRED_TILE_SIZE 31
1849#define HALFSZ (ds->halfsz) 1961#define HALFSZ (ds->halfsz)
1850#define TILE_SIZE (ds->halfsz*2 + 1) 1962#define TILE_SIZE (ds->halfsz*2 + 1)
1851 1963
1852#define BORDER ((get_gui_style() == GUI_LOOPY) ? (TILE_SIZE/8) : (TILE_SIZE/2)) 1964#define BORDER ((ui->gui_style == GUI_LOOPY) ? (TILE_SIZE/8) : (TILE_SIZE/2))
1853 1965
1854#define BORDER_WIDTH (max(TILE_SIZE / 32, 1)) 1966#define BORDER_WIDTH (max(TILE_SIZE / 32, 1))
1855 1967
@@ -1865,22 +1977,6 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
1865#define DS_FLASH (1 << 21) 1977#define DS_FLASH (1 << 21)
1866#define DS_CURSOR (1 << 22) 1978#define DS_CURSOR (1 << 22)
1867 1979
1868enum { GUI_MASYU, GUI_LOOPY };
1869
1870static int get_gui_style(void)
1871{
1872 static int gui_style = -1;
1873
1874 if (gui_style == -1) {
1875 char *env = getenv("PEARL_GUI_LOOPY");
1876 if (env && (env[0] == 'y' || env[0] == 'Y'))
1877 gui_style = GUI_LOOPY;
1878 else
1879 gui_style = GUI_MASYU;
1880 }
1881 return gui_style;
1882}
1883
1884struct game_drawstate { 1980struct game_drawstate {
1885 int halfsz; 1981 int halfsz;
1886 bool started; 1982 bool started;
@@ -1891,6 +1987,56 @@ struct game_drawstate {
1891 char *draglines; /* size w*h; lines flipped by current drag */ 1987 char *draglines; /* size w*h; lines flipped by current drag */
1892}; 1988};
1893 1989
1990/*
1991 * Routine shared between multiple callers to work out the intended
1992 * effect of a drag path on the grid.
1993 *
1994 * Call it in a loop, like this:
1995 *
1996 * bool clearing = true;
1997 * for (i = 0; i < ui->ndragcoords - 1; i++) {
1998 * int sx, sy, dx, dy, dir, oldstate, newstate;
1999 * interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy,
2000 * &dir, &oldstate, &newstate);
2001 *
2002 * [do whatever is needed to handle the fact that the drag
2003 * wants the edge from sx,sy to dx,dy (heading in direction
2004 * 'dir' at the sx,sy end) to be changed from state oldstate
2005 * to state newstate, each of which equals either 0 or dir]
2006 * }
2007 */
2008static void interpret_ui_drag(const game_state *state, const game_ui *ui,
2009 bool *clearing, int i, int *sx, int *sy,
2010 int *dx, int *dy, int *dir,
2011 int *oldstate, int *newstate)
2012{
2013 int w = state->shared->w;
2014 int sp = ui->dragcoords[i], dp = ui->dragcoords[i+1];
2015 *sy = sp/w;
2016 *sx = sp%w;
2017 *dy = dp/w;
2018 *dx = dp%w;
2019 *dir = (*dy>*sy ? D : *dy<*sy ? U : *dx>*sx ? R : L);
2020 *oldstate = state->lines[sp] & *dir;
2021 if (*oldstate) {
2022 /*
2023 * The edge we've dragged over was previously
2024 * present. Set it to absent, unless we've already
2025 * stopped doing that.
2026 */
2027 *newstate = *clearing ? 0 : *dir;
2028 } else {
2029 /*
2030 * The edge we've dragged over was previously
2031 * absent. Set it to present, and cancel the
2032 * 'clearing' flag so that all subsequent edges in
2033 * the drag are set rather than cleared.
2034 */
2035 *newstate = *dir;
2036 *clearing = false;
2037 }
2038}
2039
1894static void update_ui_drag(const game_state *state, game_ui *ui, 2040static void update_ui_drag(const game_state *state, game_ui *ui,
1895 int gx, int gy) 2041 int gx, int gy)
1896{ 2042{
@@ -1919,13 +2065,42 @@ static void update_ui_drag(const game_state *state, game_ui *ui,
1919 * the drag path so far has the effect of truncating the path back 2065 * the drag path so far has the effect of truncating the path back
1920 * to that square, so a player can back out part of an uncommitted 2066 * to that square, so a player can back out part of an uncommitted
1921 * drag without having to let go of the mouse. 2067 * drag without having to let go of the mouse.
2068 *
2069 * An exception is that you're allowed to drag round in a loop
2070 * back to the very start of the drag, provided that doesn't
2071 * create a vertex of the wrong degree. This allows a player who's
2072 * after an extra challenge to draw the entire loop in a single
2073 * drag, without it cancelling itself just before release.
1922 */ 2074 */
1923 for (i = 0; i < ui->ndragcoords; i++) 2075 for (i = 1; i < ui->ndragcoords; i++)
1924 if (pos == ui->dragcoords[i]) { 2076 if (pos == ui->dragcoords[i]) {
1925 ui->ndragcoords = i+1; 2077 ui->ndragcoords = i+1;
1926 return; 2078 return;
1927 } 2079 }
1928 2080
2081 if (pos == ui->dragcoords[0]) {
2082 /* More complex check for a loop-shaped drag, which has to go
2083 * through interpret_ui_drag to decide on the final degree of
2084 * the start/end vertex. */
2085 ui->dragcoords[ui->ndragcoords] = pos;
2086 bool clearing = true;
2087 int lines = state->lines[pos] & (L|R|U|D);
2088 for (i = 0; i < ui->ndragcoords; i++) {
2089 int sx, sy, dx, dy, dir, oldstate, newstate;
2090 interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy,
2091 &dir, &oldstate, &newstate);
2092 if (sx == gx && sy == gy)
2093 lines ^= (oldstate ^ newstate);
2094 if (dx == gx && dy == gy)
2095 lines ^= (F(oldstate) ^ F(newstate));
2096 }
2097 if (NBITS(lines) > 2) {
2098 /* Bad vertex degree: fall back to the backtracking behaviour. */
2099 ui->ndragcoords = 1;
2100 return;
2101 }
2102 }
2103
1929 /* 2104 /*
1930 * Otherwise, dragging the mouse into a square that's a rook-move 2105 * Otherwise, dragging the mouse into a square that's a rook-move
1931 * away from the last one on the path extends the path. 2106 * away from the last one on the path extends the path.
@@ -1958,56 +2133,6 @@ static void update_ui_drag(const game_state *state, game_ui *ui,
1958 */ 2133 */
1959} 2134}
1960 2135
1961/*
1962 * Routine shared between interpret_move and game_redraw to work out
1963 * the intended effect of a drag path on the grid.
1964 *
1965 * Call it in a loop, like this:
1966 *
1967 * bool clearing = true;
1968 * for (i = 0; i < ui->ndragcoords - 1; i++) {
1969 * int sx, sy, dx, dy, dir, oldstate, newstate;
1970 * interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy,
1971 * &dir, &oldstate, &newstate);
1972 *
1973 * [do whatever is needed to handle the fact that the drag
1974 * wants the edge from sx,sy to dx,dy (heading in direction
1975 * 'dir' at the sx,sy end) to be changed from state oldstate
1976 * to state newstate, each of which equals either 0 or dir]
1977 * }
1978 */
1979static void interpret_ui_drag(const game_state *state, const game_ui *ui,
1980 bool *clearing, int i, int *sx, int *sy,
1981 int *dx, int *dy, int *dir,
1982 int *oldstate, int *newstate)
1983{
1984 int w = state->shared->w;
1985 int sp = ui->dragcoords[i], dp = ui->dragcoords[i+1];
1986 *sy = sp/w;
1987 *sx = sp%w;
1988 *dy = dp/w;
1989 *dx = dp%w;
1990 *dir = (*dy>*sy ? D : *dy<*sy ? U : *dx>*sx ? R : L);
1991 *oldstate = state->lines[sp] & *dir;
1992 if (*oldstate) {
1993 /*
1994 * The edge we've dragged over was previously
1995 * present. Set it to absent, unless we've already
1996 * stopped doing that.
1997 */
1998 *newstate = *clearing ? 0 : *dir;
1999 } else {
2000 /*
2001 * The edge we've dragged over was previously
2002 * absent. Set it to present, and cancel the
2003 * 'clearing' flag so that all subsequent edges in
2004 * the drag are set rather than cleared.
2005 */
2006 *newstate = *dir;
2007 *clearing = false;
2008 }
2009}
2010
2011static char *mark_in_direction(const game_state *state, int x, int y, int dir, 2136static char *mark_in_direction(const game_state *state, int x, int y, int dir,
2012 bool primary, char *buf) 2137 bool primary, char *buf)
2013{ 2138{
@@ -2018,11 +2143,11 @@ static char *mark_in_direction(const game_state *state, int x, int y, int dir,
2018 2143
2019 char ch = primary ? 'F' : 'M', *other; 2144 char ch = primary ? 'F' : 'M', *other;
2020 2145
2021 if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return UI_UPDATE; 2146 if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return MOVE_UI_UPDATE;
2022 2147
2023 /* disallow laying a mark over a line, or vice versa. */ 2148 /* disallow laying a mark over a line, or vice versa. */
2024 other = primary ? state->marks : state->lines; 2149 other = primary ? state->marks : state->lines;
2025 if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return UI_UPDATE; 2150 if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return MOVE_UI_UPDATE;
2026 2151
2027 sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2); 2152 sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2);
2028 return dupstr(buf); 2153 return dupstr(buf);
@@ -2042,26 +2167,26 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2042 char tmpbuf[80]; 2167 char tmpbuf[80];
2043 2168
2044 bool shift = button & MOD_SHFT, control = button & MOD_CTRL; 2169 bool shift = button & MOD_SHFT, control = button & MOD_CTRL;
2045 button &= ~MOD_MASK; 2170 button = STRIP_BUTTON_MODIFIERS(button);
2046 2171
2047 if (IS_MOUSE_DOWN(button)) { 2172 if (IS_MOUSE_DOWN(button)) {
2048 ui->cursor_active = false; 2173 ui->cursor_active = false;
2049 2174
2050 if (!INGRID(state, gx, gy)) { 2175 if (!INGRID(state, gx, gy)) {
2051 ui->ndragcoords = -1; 2176 ui->ndragcoords = -1;
2052 return NULL; 2177 return MOVE_UI_UPDATE;
2053 } 2178 }
2054 2179
2055 ui->clickx = x; ui->clicky = y; 2180 ui->clickx = x; ui->clicky = y;
2056 ui->dragcoords[0] = gy * w + gx; 2181 ui->dragcoords[0] = gy * w + gx;
2057 ui->ndragcoords = 0; /* will be 1 once drag is confirmed */ 2182 ui->ndragcoords = 0; /* will be 1 once drag is confirmed */
2058 2183
2059 return UI_UPDATE; 2184 return MOVE_UI_UPDATE;
2060 } 2185 }
2061 2186
2062 if (button == LEFT_DRAG && ui->ndragcoords >= 0) { 2187 if (button == LEFT_DRAG && ui->ndragcoords >= 0) {
2063 update_ui_drag(state, ui, gx, gy); 2188 update_ui_drag(state, ui, gx, gy);
2064 return UI_UPDATE; 2189 return MOVE_UI_UPDATE;
2065 } 2190 }
2066 2191
2067 if (IS_MOUSE_RELEASE(button)) release = true; 2192 if (IS_MOUSE_RELEASE(button)) release = true;
@@ -2071,42 +2196,48 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2071 ui->cursor_active = true; 2196 ui->cursor_active = true;
2072 } else if (control || shift) { 2197 } else if (control || shift) {
2073 char *move; 2198 char *move;
2074 if (ui->ndragcoords > 0) return NULL; 2199 if (ui->ndragcoords > 0) return MOVE_NO_EFFECT;
2075 ui->ndragcoords = -1; 2200 ui->ndragcoords = -1;
2076 move = mark_in_direction(state, ui->curx, ui->cury, 2201 move = mark_in_direction(state, ui->curx, ui->cury,
2077 KEY_DIRECTION(button), control, tmpbuf); 2202 KEY_DIRECTION(button), control, tmpbuf);
2078 if (control && !shift && *move) 2203 if (control && !shift && *move)
2079 move_cursor(button, &ui->curx, &ui->cury, w, h, false); 2204 move_cursor(button, &ui->curx, &ui->cury, w, h, false, NULL);
2080 return move; 2205 return move;
2081 } else { 2206 } else {
2082 move_cursor(button, &ui->curx, &ui->cury, w, h, false); 2207 move_cursor(button, &ui->curx, &ui->cury, w, h, false, NULL);
2083 if (ui->ndragcoords >= 0) 2208 if (ui->ndragcoords >= 0)
2084 update_ui_drag(state, ui, ui->curx, ui->cury); 2209 update_ui_drag(state, ui, ui->curx, ui->cury);
2085 } 2210 }
2086 return UI_UPDATE; 2211 return MOVE_UI_UPDATE;
2087 } 2212 }
2088 2213
2089 if (IS_CURSOR_SELECT(button)) { 2214 if (IS_CURSOR_SELECT(button)) {
2090 if (!ui->cursor_active) { 2215 if (!ui->cursor_active) {
2091 ui->cursor_active = true; 2216 ui->cursor_active = true;
2092 return UI_UPDATE; 2217 return MOVE_UI_UPDATE;
2093 } else if (button == CURSOR_SELECT) { 2218 } else if (button == CURSOR_SELECT) {
2094 if (ui->ndragcoords == -1) { 2219 if (ui->ndragcoords == -1) {
2095 ui->ndragcoords = 0; 2220 ui->ndragcoords = 0;
2096 ui->dragcoords[0] = ui->cury * w + ui->curx; 2221 ui->dragcoords[0] = ui->cury * w + ui->curx;
2097 ui->clickx = CENTERED_COORD(ui->curx); 2222 ui->clickx = CENTERED_COORD(ui->curx);
2098 ui->clicky = CENTERED_COORD(ui->cury); 2223 ui->clicky = CENTERED_COORD(ui->cury);
2099 return UI_UPDATE; 2224 return MOVE_UI_UPDATE;
2100 } else release = true; 2225 } else release = true;
2101 } else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) { 2226 } else if (button == CURSOR_SELECT2) {
2102 ui->ndragcoords = -1; 2227 if (ui->ndragcoords >= 0) {
2103 return UI_UPDATE; 2228 ui->ndragcoords = -1;
2104 } 2229 return MOVE_UI_UPDATE;
2230 }
2231 return MOVE_NO_EFFECT;
2232 }
2105 } 2233 }
2106 2234
2107 if (button == 27 || button == '\b') { 2235 if (button == 27 || button == '\b') {
2108 ui->ndragcoords = -1; 2236 if (ui->ndragcoords >= 0) {
2109 return UI_UPDATE; 2237 ui->ndragcoords = -1;
2238 return MOVE_UI_UPDATE;
2239 }
2240 return MOVE_NO_EFFECT;
2110 } 2241 }
2111 2242
2112 if (release) { 2243 if (release) {
@@ -2138,7 +2269,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2138 2269
2139 ui->ndragcoords = -1; 2270 ui->ndragcoords = -1;
2140 2271
2141 return buf ? buf : UI_UPDATE; 2272 return buf ? buf : MOVE_UI_UPDATE;
2142 } else if (ui->ndragcoords == 0) { 2273 } else if (ui->ndragcoords == 0) {
2143 /* Click (or tiny drag). Work out which edge we were 2274 /* Click (or tiny drag). Work out which edge we were
2144 * closest to. */ 2275 * closest to. */
@@ -2159,12 +2290,12 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2159 cx = CENTERED_COORD(gx); 2290 cx = CENTERED_COORD(gx);
2160 cy = CENTERED_COORD(gy); 2291 cy = CENTERED_COORD(gy);
2161 2292
2162 if (!INGRID(state, gx, gy)) return UI_UPDATE; 2293 if (!INGRID(state, gx, gy)) return MOVE_UI_UPDATE;
2163 2294
2164 if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) { 2295 if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) {
2165 /* TODO closer to centre of grid: process as a cell click not an edge click. */ 2296 /* TODO closer to centre of grid: process as a cell click not an edge click. */
2166 2297
2167 return UI_UPDATE; 2298 return MOVE_UI_UPDATE;
2168 } else { 2299 } else {
2169 int direction; 2300 int direction;
2170 if (abs(x-cx) < abs(y-cy)) { 2301 if (abs(x-cx) < abs(y-cy)) {
@@ -2183,7 +2314,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2183 if (button == 'H' || button == 'h') 2314 if (button == 'H' || button == 'h')
2184 return dupstr("H"); 2315 return dupstr("H");
2185 2316
2186 return NULL; 2317 return MOVE_UNUSED;
2187} 2318}
2188 2319
2189static game_state *execute_move(const game_state *state, const char *move) 2320static game_state *execute_move(const game_state *state, const char *move)
@@ -2246,7 +2377,7 @@ static game_state *execute_move(const game_state *state, const char *move)
2246 goto badmove; 2377 goto badmove;
2247 } 2378 }
2248 2379
2249 check_completion(ret, true); 2380 if (!check_completion(ret, true)) goto badmove;
2250 2381
2251 return ret; 2382 return ret;
2252 2383
@@ -2262,7 +2393,7 @@ badmove:
2262#define FLASH_TIME 0.5F 2393#define FLASH_TIME 0.5F
2263 2394
2264static void game_compute_size(const game_params *params, int tilesize, 2395static void game_compute_size(const game_params *params, int tilesize,
2265 int *x, int *y) 2396 const game_ui *ui, int *x, int *y)
2266{ 2397{
2267 /* Ick: fake up `ds->tilesize' for macro expansion purposes */ 2398 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
2268 struct { int halfsz; } ads, *ds = &ads; 2399 struct { int halfsz; } ads, *ds = &ads;
@@ -2340,8 +2471,8 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds)
2340} 2471}
2341 2472
2342static void draw_lines_specific(drawing *dr, game_drawstate *ds, 2473static void draw_lines_specific(drawing *dr, game_drawstate *ds,
2343 int x, int y, unsigned int lflags, 2474 const game_ui *ui, int x, int y,
2344 unsigned int shift, int c) 2475 unsigned int lflags, unsigned int shift, int c)
2345{ 2476{
2346 int ox = COORD(x), oy = COORD(y); 2477 int ox = COORD(x), oy = COORD(y);
2347 int t2 = HALFSZ, t16 = HALFSZ/4; 2478 int t2 = HALFSZ, t16 = HALFSZ/4;
@@ -2390,7 +2521,7 @@ static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui,
2390 COL_CURSOR_BACKGROUND : COL_BACKGROUND); 2521 COL_CURSOR_BACKGROUND : COL_BACKGROUND);
2391 2522
2392 2523
2393 if (get_gui_style() == GUI_LOOPY) { 2524 if (ui->gui_style == GUI_LOOPY) {
2394 /* Draw small dot, underneath any lines. */ 2525 /* Draw small dot, underneath any lines. */
2395 draw_circle(dr, cx, cy, t16, COL_GRID, COL_GRID); 2526 draw_circle(dr, cx, cy, t16, COL_GRID, COL_GRID);
2396 } else { 2527 } else {
@@ -2417,7 +2548,7 @@ static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui,
2417 draw_line(dr, mx-msz, my-msz, mx+msz, my+msz, COL_BLACK); 2548 draw_line(dr, mx-msz, my-msz, mx+msz, my+msz, COL_BLACK);
2418 draw_line(dr, mx-msz, my+msz, mx+msz, my-msz, COL_BLACK); 2549 draw_line(dr, mx-msz, my+msz, mx+msz, my-msz, COL_BLACK);
2419 } else { 2550 } else {
2420 if (get_gui_style() == GUI_LOOPY) { 2551 if (ui->gui_style == GUI_LOOPY) {
2421 /* draw grid lines connecting centre of cells */ 2552 /* draw grid lines connecting centre of cells */
2422 draw_line(dr, cx, cy, cx+xoff, cy+yoff, COL_GRID); 2553 draw_line(dr, cx, cy, cx+xoff, cy+yoff, COL_GRID);
2423 } 2554 }
@@ -2427,11 +2558,11 @@ static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui,
2427 /* Draw each of the four directions, where laid (or error, or drag, etc.) 2558 /* Draw each of the four directions, where laid (or error, or drag, etc.)
2428 * Order is important here, specifically for the eventual colours of the 2559 * Order is important here, specifically for the eventual colours of the
2429 * exposed end caps. */ 2560 * exposed end caps. */
2430 draw_lines_specific(dr, ds, x, y, lflags, 0, 2561 draw_lines_specific(dr, ds, ui, x, y, lflags, 0,
2431 (lflags & DS_FLASH ? COL_FLASH : COL_BLACK)); 2562 (lflags & DS_FLASH ? COL_FLASH : COL_BLACK));
2432 draw_lines_specific(dr, ds, x, y, lflags, DS_ESHIFT, COL_ERROR); 2563 draw_lines_specific(dr, ds, ui, x, y, lflags, DS_ESHIFT, COL_ERROR);
2433 draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGOFF); 2564 draw_lines_specific(dr, ds, ui, x, y, lflags, DS_DSHIFT, COL_DRAGOFF);
2434 draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGON); 2565 draw_lines_specific(dr, ds, ui, x, y, lflags, DS_DSHIFT, COL_DRAGON);
2435 2566
2436 /* Draw a clue, if present */ 2567 /* Draw a clue, if present */
2437 if (clue != NOCLUE) { 2568 if (clue != NOCLUE) {
@@ -2458,18 +2589,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
2458 bool force = false; 2589 bool force = false;
2459 2590
2460 if (!ds->started) { 2591 if (!ds->started) {
2461 /* 2592 if (ui->gui_style == GUI_MASYU) {
2462 * The initial contents of the window are not guaranteed and
2463 * can vary with front ends. To be on the safe side, all games
2464 * should start by drawing a big background-colour rectangle
2465 * covering the whole window.
2466 */
2467 draw_rect(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER,
2468 COL_BACKGROUND);
2469
2470 if (get_gui_style() == GUI_MASYU) {
2471 /* 2593 /*
2472 * Smaller black rectangle which is the main grid. 2594 * Black rectangle which is the main grid.
2473 */ 2595 */
2474 draw_rect(dr, BORDER - BORDER_WIDTH, BORDER - BORDER_WIDTH, 2596 draw_rect(dr, BORDER - BORDER_WIDTH, BORDER - BORDER_WIDTH,
2475 w*TILE_SIZE + 2*BORDER_WIDTH + 1, 2597 w*TILE_SIZE + 2*BORDER_WIDTH + 1,
@@ -2560,47 +2682,64 @@ static int game_status(const game_state *state)
2560 return state->completed ? +1 : 0; 2682 return state->completed ? +1 : 0;
2561} 2683}
2562 2684
2563static bool game_timing_state(const game_state *state, game_ui *ui) 2685static void game_print_size(const game_params *params, const game_ui *ui,
2564{ 2686 float *x, float *y)
2565 return true;
2566}
2567
2568static void game_print_size(const game_params *params, float *x, float *y)
2569{ 2687{
2570 int pw, ph; 2688 int pw, ph;
2571 2689
2572 /* 2690 /*
2573 * I'll use 6mm squares by default. 2691 * I'll use 6mm squares by default.
2574 */ 2692 */
2575 game_compute_size(params, 600, &pw, &ph); 2693 game_compute_size(params, 600, ui, &pw, &ph);
2576 *x = pw / 100.0F; 2694 *x = pw / 100.0F;
2577 *y = ph / 100.0F; 2695 *y = ph / 100.0F;
2578} 2696}
2579 2697
2580static void game_print(drawing *dr, const game_state *state, int tilesize) 2698static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
2699 int tilesize)
2581{ 2700{
2582 int w = state->shared->w, h = state->shared->h, x, y; 2701 int w = state->shared->w, h = state->shared->h, x, y;
2583 int black = print_mono_colour(dr, 0); 2702 int black = print_mono_colour(dr, 0);
2584 int white = print_mono_colour(dr, 1); 2703 int white = print_mono_colour(dr, 1);
2585 2704
2586 /* No GUI_LOOPY here: only use the familiar masyu style. */
2587
2588 /* Ick: fake up `ds->tilesize' for macro expansion purposes */ 2705 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
2589 game_drawstate *ds = game_new_drawstate(dr, state); 2706 game_drawstate *ds = game_new_drawstate(dr, state);
2590 game_set_size(dr, ds, NULL, tilesize); 2707 game_set_size(dr, ds, NULL, tilesize);
2591 2708
2592 /* Draw grid outlines (black). */ 2709 if (ui->gui_style == GUI_MASYU) {
2593 for (x = 0; x <= w; x++) 2710 /* Draw grid outlines (black). */
2594 draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), black); 2711 for (x = 0; x <= w; x++)
2595 for (y = 0; y <= h; y++) 2712 draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), black);
2596 draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), black); 2713 for (y = 0; y <= h; y++)
2714 draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), black);
2715 } else {
2716 /* Draw small dots, and dotted lines connecting them. For
2717 * added clarity, try to start and end the dotted lines a
2718 * little way away from the dots. */
2719 print_line_width(dr, TILE_SIZE / 40);
2720 print_line_dotted(dr, true);
2721 for (x = 0; x < w; x++) {
2722 for (y = 0; y < h; y++) {
2723 int cx = COORD(x) + HALFSZ, cy = COORD(y) + HALFSZ;
2724 draw_circle(dr, cx, cy, tilesize/10, black, black);
2725 if (x+1 < w)
2726 draw_line(dr, cx+tilesize/5, cy,
2727 cx+tilesize-tilesize/5, cy, black);
2728 if (y+1 < h)
2729 draw_line(dr, cx, cy+tilesize/5,
2730 cx, cy+tilesize-tilesize/5, black);
2731 }
2732 }
2733 print_line_dotted(dr, false);
2734 }
2597 2735
2598 for (x = 0; x < w; x++) { 2736 for (x = 0; x < w; x++) {
2599 for (y = 0; y < h; y++) { 2737 for (y = 0; y < h; y++) {
2600 int cx = COORD(x) + HALFSZ, cy = COORD(y) + HALFSZ; 2738 int cx = COORD(x) + HALFSZ, cy = COORD(y) + HALFSZ;
2601 int clue = state->shared->clues[y*w+x]; 2739 int clue = state->shared->clues[y*w+x];
2602 2740
2603 draw_lines_specific(dr, ds, x, y, state->lines[y*w+x], 0, black); 2741 draw_lines_specific(dr, ds, ui, x, y,
2742 state->lines[y*w+x], 0, black);
2604 2743
2605 if (clue != NOCLUE) { 2744 if (clue != NOCLUE) {
2606 int c = (clue == CORNER) ? black : white; 2745 int c = (clue == CORNER) ? black : white;
@@ -2633,12 +2772,14 @@ const struct game thegame = {
2633 free_game, 2772 free_game,
2634 true, solve_game, 2773 true, solve_game,
2635 true, game_can_format_as_text_now, game_text_format, 2774 true, game_can_format_as_text_now, game_text_format,
2775 get_prefs, set_prefs,
2636 new_ui, 2776 new_ui,
2637 free_ui, 2777 free_ui,
2638 encode_ui, 2778 NULL, /* encode_ui */
2639 decode_ui, 2779 NULL, /* decode_ui */
2640 NULL, /* game_request_keys */ 2780 NULL, /* game_request_keys */
2641 game_changed_state, 2781 game_changed_state,
2782 current_key_label,
2642 interpret_move, 2783 interpret_move,
2643 execute_move, 2784 execute_move,
2644 PREFERRED_TILE_SIZE, game_compute_size, game_set_size, 2785 PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
@@ -2652,7 +2793,7 @@ const struct game thegame = {
2652 game_status, 2793 game_status,
2653 true, false, game_print_size, game_print, 2794 true, false, game_print_size, game_print,
2654 false, /* wants_statusbar */ 2795 false, /* wants_statusbar */
2655 false, game_timing_state, 2796 false, NULL, /* timing_state */
2656 0, /* flags */ 2797 0, /* flags */
2657}; 2798};
2658 2799
@@ -2661,7 +2802,7 @@ const struct game thegame = {
2661#include <time.h> 2802#include <time.h>
2662#include <stdarg.h> 2803#include <stdarg.h>
2663 2804
2664const char *quis = NULL; 2805static const char *quis = NULL;
2665 2806
2666static void usage(FILE *out) { 2807static void usage(FILE *out) {
2667 fprintf(out, "usage: %s <params>\n", quis); 2808 fprintf(out, "usage: %s <params>\n", quis);
@@ -2720,7 +2861,7 @@ static void start_soak(game_params *p, random_state *rs, int nsecs)
2720 sfree(clues); 2861 sfree(clues);
2721} 2862}
2722 2863
2723int main(int argc, const char *argv[]) 2864int main(int argc, char *argv[])
2724{ 2865{
2725 game_params *p = NULL; 2866 game_params *p = NULL;
2726 random_state *rs = NULL; 2867 random_state *rs = NULL;