diff options
Diffstat (limited to 'apps/plugins/puzzles/src/pearl.c')
-rw-r--r-- | apps/plugins/puzzles/src/pearl.c | 499 |
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 | ||
1051 | static void pearl_loopgen(int w, int h, char *lines, random_state *rs) | 1090 | static 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 | ||
1155 | static int new_clues(const game_params *params, random_state *rs, | 1192 | static 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 | ||
1494 | static void dsf_update_completion(game_state *state, int ax, int ay, char dir, | 1532 | /* Returns false if the state is invalid. */ |
1495 | int *dsf) | 1533 | static 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 | ||
1512 | static void check_completion(game_state *state, bool mark) | 1554 | /* Returns false if the state is invalid. */ |
1555 | static 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 | ||
1874 | static 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 | |||
1815 | static game_ui *new_ui(const game_state *state) | 1896 | static 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 | ||
1834 | static char *encode_ui(const game_ui *ui) | 1917 | static 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 | ||
1839 | static void decode_ui(game_ui *ui, const char *encoding) | 1936 | static 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 | ||
1843 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | 1941 | static 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 | ||
1946 | static 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 | ||
1868 | enum { GUI_MASYU, GUI_LOOPY }; | ||
1869 | |||
1870 | static 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 | |||
1884 | struct game_drawstate { | 1980 | struct 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 | */ | ||
2008 | static 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 | |||
1894 | static void update_ui_drag(const game_state *state, game_ui *ui, | 2040 | static 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 | */ | ||
1979 | static 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 | |||
2011 | static char *mark_in_direction(const game_state *state, int x, int y, int dir, | 2136 | static 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 | ||
2189 | static game_state *execute_move(const game_state *state, const char *move) | 2320 | static 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 | ||
2264 | static void game_compute_size(const game_params *params, int tilesize, | 2395 | static 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 | ||
2342 | static void draw_lines_specific(drawing *dr, game_drawstate *ds, | 2473 | static 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 | ||
2563 | static bool game_timing_state(const game_state *state, game_ui *ui) | 2685 | static void game_print_size(const game_params *params, const game_ui *ui, |
2564 | { | 2686 | float *x, float *y) |
2565 | return true; | ||
2566 | } | ||
2567 | |||
2568 | static 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 | ||
2580 | static void game_print(drawing *dr, const game_state *state, int tilesize) | 2698 | static 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 | ||
2664 | const char *quis = NULL; | 2805 | static const char *quis = NULL; |
2665 | 2806 | ||
2666 | static void usage(FILE *out) { | 2807 | static 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 | ||
2723 | int main(int argc, const char *argv[]) | 2864 | int 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; |