diff options
Diffstat (limited to 'apps/plugins/puzzles/src/unfinished/group.c')
-rw-r--r-- | apps/plugins/puzzles/src/unfinished/group.c | 2497 |
1 files changed, 2497 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/unfinished/group.c b/apps/plugins/puzzles/src/unfinished/group.c new file mode 100644 index 0000000000..faffa89485 --- /dev/null +++ b/apps/plugins/puzzles/src/unfinished/group.c | |||
@@ -0,0 +1,2497 @@ | |||
1 | /* | ||
2 | * group.c: a Latin-square puzzle, but played with groups' Cayley | ||
3 | * tables. That is, you are given a Cayley table of a group with | ||
4 | * most elements blank and a few clues, and you must fill it in | ||
5 | * so as to preserve the group axioms. | ||
6 | * | ||
7 | * This is a perfectly playable and fully working puzzle, but I'm | ||
8 | * leaving it for the moment in the 'unfinished' directory because | ||
9 | * it's just too esoteric (not to mention _hard_) for me to be | ||
10 | * comfortable presenting it to the general public as something they | ||
11 | * might (implicitly) actually want to play. | ||
12 | * | ||
13 | * TODO: | ||
14 | * | ||
15 | * - more solver techniques? | ||
16 | * * Inverses: once we know that gh = e, we can immediately | ||
17 | * deduce hg = e as well; then for any gx=y we can deduce | ||
18 | * hy=x, and for any xg=y we have yh=x. | ||
19 | * * Hard-mode associativity: we currently deduce based on | ||
20 | * definite numbers in the grid, but we could also winnow | ||
21 | * based on _possible_ numbers. | ||
22 | * * My overambitious original thoughts included wondering if we | ||
23 | * could infer that there must be elements of certain orders | ||
24 | * (e.g. a group of order divisible by 5 must contain an | ||
25 | * element of order 5), but I think in fact this is probably | ||
26 | * silly. | ||
27 | */ | ||
28 | |||
29 | #include <stdio.h> | ||
30 | #include <stdlib.h> | ||
31 | #include <string.h> | ||
32 | #include <assert.h> | ||
33 | #include <ctype.h> | ||
34 | #ifdef NO_TGMATH_H | ||
35 | # include <math.h> | ||
36 | #else | ||
37 | # include <tgmath.h> | ||
38 | #endif | ||
39 | |||
40 | #include "puzzles.h" | ||
41 | #include "latin.h" | ||
42 | |||
43 | /* | ||
44 | * Difficulty levels. I do some macro ickery here to ensure that my | ||
45 | * enum and the various forms of my name list always match up. | ||
46 | */ | ||
47 | #define DIFFLIST(A) \ | ||
48 | A(TRIVIAL,Trivial,NULL,t) \ | ||
49 | A(NORMAL,Normal,solver_normal,n) \ | ||
50 | A(HARD,Hard,solver_hard,h) \ | ||
51 | A(EXTREME,Extreme,NULL,x) \ | ||
52 | A(UNREASONABLE,Unreasonable,NULL,u) | ||
53 | #define ENUM(upper,title,func,lower) DIFF_ ## upper, | ||
54 | #define TITLE(upper,title,func,lower) #title, | ||
55 | #define ENCODE(upper,title,func,lower) #lower | ||
56 | #define CONFIG(upper,title,func,lower) ":" #title | ||
57 | enum { DIFFLIST(ENUM) DIFFCOUNT }; | ||
58 | static char const *const group_diffnames[] = { DIFFLIST(TITLE) }; | ||
59 | static char const group_diffchars[] = DIFFLIST(ENCODE); | ||
60 | #define DIFFCONFIG DIFFLIST(CONFIG) | ||
61 | |||
62 | enum { | ||
63 | COL_BACKGROUND, | ||
64 | COL_GRID, | ||
65 | COL_USER, | ||
66 | COL_HIGHLIGHT, | ||
67 | COL_ERROR, | ||
68 | COL_PENCIL, | ||
69 | COL_DIAGONAL, | ||
70 | NCOLOURS | ||
71 | }; | ||
72 | |||
73 | /* | ||
74 | * In identity mode, we number the elements e,a,b,c,d,f,g,h,... | ||
75 | * Otherwise, they're a,b,c,d,e,f,g,h,... in the obvious way. | ||
76 | */ | ||
77 | #define E_TO_FRONT(c,id) ( (id) && (c)<=5 ? (c) % 5 + 1 : (c) ) | ||
78 | #define E_FROM_FRONT(c,id) ( (id) && (c)<=5 ? ((c) + 3) % 5 + 1 : (c) ) | ||
79 | |||
80 | #define FROMCHAR(c,id) E_TO_FRONT((((c)-('A'-1)) & ~0x20), id) | ||
81 | #define ISCHAR(c) (((c)>='A'&&(c)<='Z') || ((c)>='a'&&(c)<='z')) | ||
82 | #define TOCHAR(c,id) (E_FROM_FRONT(c,id) + ('a'-1)) | ||
83 | |||
84 | struct game_params { | ||
85 | int w, diff; | ||
86 | bool id; | ||
87 | }; | ||
88 | |||
89 | typedef struct group_common { | ||
90 | int refcount; | ||
91 | bool *immutable; | ||
92 | } group_common; | ||
93 | |||
94 | struct game_state { | ||
95 | game_params par; | ||
96 | digit *grid; | ||
97 | int *pencil; /* bitmaps using bits 1<<1..1<<n */ | ||
98 | group_common *common; | ||
99 | bool completed, cheated; | ||
100 | digit *sequence; /* sequence of group elements shown */ | ||
101 | |||
102 | /* | ||
103 | * This array indicates thick lines separating rows and columns | ||
104 | * placed and unplaced manually by the user as a visual aid, e.g. | ||
105 | * to delineate a subgroup and its cosets. | ||
106 | * | ||
107 | * When a line is placed, it's deemed to be between the two | ||
108 | * particular group elements that are on either side of it at the | ||
109 | * time; dragging those two away from each other automatically | ||
110 | * gets rid of the line. Hence, for a given element i, dividers[i] | ||
111 | * is either -1 (indicating no divider to the right of i), or some | ||
112 | * other element (indicating a divider to the right of i iff that | ||
113 | * element is the one right of it). These are eagerly cleared | ||
114 | * during drags. | ||
115 | */ | ||
116 | int *dividers; /* thick lines between rows/cols */ | ||
117 | }; | ||
118 | |||
119 | static game_params *default_params(void) | ||
120 | { | ||
121 | game_params *ret = snew(game_params); | ||
122 | |||
123 | ret->w = 6; | ||
124 | ret->diff = DIFF_NORMAL; | ||
125 | ret->id = true; | ||
126 | |||
127 | return ret; | ||
128 | } | ||
129 | |||
130 | static const struct game_params group_presets[] = { | ||
131 | { 6, DIFF_NORMAL, true }, | ||
132 | { 6, DIFF_NORMAL, false }, | ||
133 | { 8, DIFF_NORMAL, true }, | ||
134 | { 8, DIFF_NORMAL, false }, | ||
135 | { 8, DIFF_HARD, true }, | ||
136 | { 8, DIFF_HARD, false }, | ||
137 | { 12, DIFF_NORMAL, true }, | ||
138 | }; | ||
139 | |||
140 | static bool game_fetch_preset(int i, char **name, game_params **params) | ||
141 | { | ||
142 | game_params *ret; | ||
143 | char buf[80]; | ||
144 | |||
145 | if (i < 0 || i >= lenof(group_presets)) | ||
146 | return false; | ||
147 | |||
148 | ret = snew(game_params); | ||
149 | *ret = group_presets[i]; /* structure copy */ | ||
150 | |||
151 | sprintf(buf, "%dx%d %s%s", ret->w, ret->w, group_diffnames[ret->diff], | ||
152 | ret->id ? "" : ", identity hidden"); | ||
153 | |||
154 | *name = dupstr(buf); | ||
155 | *params = ret; | ||
156 | return true; | ||
157 | } | ||
158 | |||
159 | static void free_params(game_params *params) | ||
160 | { | ||
161 | sfree(params); | ||
162 | } | ||
163 | |||
164 | static game_params *dup_params(const game_params *params) | ||
165 | { | ||
166 | game_params *ret = snew(game_params); | ||
167 | *ret = *params; /* structure copy */ | ||
168 | return ret; | ||
169 | } | ||
170 | |||
171 | static void decode_params(game_params *params, char const *string) | ||
172 | { | ||
173 | char const *p = string; | ||
174 | |||
175 | params->w = atoi(p); | ||
176 | while (*p && isdigit((unsigned char)*p)) p++; | ||
177 | params->diff = DIFF_NORMAL; | ||
178 | params->id = true; | ||
179 | |||
180 | while (*p) { | ||
181 | if (*p == 'd') { | ||
182 | int i; | ||
183 | p++; | ||
184 | params->diff = DIFFCOUNT+1; /* ...which is invalid */ | ||
185 | if (*p) { | ||
186 | for (i = 0; i < DIFFCOUNT; i++) { | ||
187 | if (*p == group_diffchars[i]) | ||
188 | params->diff = i; | ||
189 | } | ||
190 | p++; | ||
191 | } | ||
192 | } else if (*p == 'i') { | ||
193 | params->id = false; | ||
194 | p++; | ||
195 | } else { | ||
196 | /* unrecognised character */ | ||
197 | p++; | ||
198 | } | ||
199 | } | ||
200 | } | ||
201 | |||
202 | static char *encode_params(const game_params *params, bool full) | ||
203 | { | ||
204 | char ret[80]; | ||
205 | |||
206 | sprintf(ret, "%d", params->w); | ||
207 | if (full) | ||
208 | sprintf(ret + strlen(ret), "d%c", group_diffchars[params->diff]); | ||
209 | if (!params->id) | ||
210 | sprintf(ret + strlen(ret), "i"); | ||
211 | |||
212 | return dupstr(ret); | ||
213 | } | ||
214 | |||
215 | static config_item *game_configure(const game_params *params) | ||
216 | { | ||
217 | config_item *ret; | ||
218 | char buf[80]; | ||
219 | |||
220 | ret = snewn(4, config_item); | ||
221 | |||
222 | ret[0].name = "Grid size"; | ||
223 | ret[0].type = C_STRING; | ||
224 | sprintf(buf, "%d", params->w); | ||
225 | ret[0].u.string.sval = dupstr(buf); | ||
226 | |||
227 | ret[1].name = "Difficulty"; | ||
228 | ret[1].type = C_CHOICES; | ||
229 | ret[1].u.choices.choicenames = DIFFCONFIG; | ||
230 | ret[1].u.choices.selected = params->diff; | ||
231 | |||
232 | ret[2].name = "Show identity"; | ||
233 | ret[2].type = C_BOOLEAN; | ||
234 | ret[2].u.boolean.bval = params->id; | ||
235 | |||
236 | ret[3].name = NULL; | ||
237 | ret[3].type = C_END; | ||
238 | |||
239 | return ret; | ||
240 | } | ||
241 | |||
242 | static game_params *custom_params(const config_item *cfg) | ||
243 | { | ||
244 | game_params *ret = snew(game_params); | ||
245 | |||
246 | ret->w = atoi(cfg[0].u.string.sval); | ||
247 | ret->diff = cfg[1].u.choices.selected; | ||
248 | ret->id = cfg[2].u.boolean.bval; | ||
249 | |||
250 | return ret; | ||
251 | } | ||
252 | |||
253 | static const char *validate_params(const game_params *params, bool full) | ||
254 | { | ||
255 | if (params->w < 3 || params->w > 26) | ||
256 | return "Grid size must be between 3 and 26"; | ||
257 | if (params->diff >= DIFFCOUNT) | ||
258 | return "Unknown difficulty rating"; | ||
259 | if (!params->id && params->diff == DIFF_TRIVIAL) { | ||
260 | /* | ||
261 | * We can't have a Trivial-difficulty puzzle (i.e. latin | ||
262 | * square deductions only) without a clear identity, because | ||
263 | * identityless puzzles always have two rows and two columns | ||
264 | * entirely blank, and no latin-square deduction permits the | ||
265 | * distinguishing of two such rows. | ||
266 | */ | ||
267 | return "Trivial puzzles must have an identity"; | ||
268 | } | ||
269 | if (!params->id && params->w == 3) { | ||
270 | /* | ||
271 | * We can't have a 3x3 puzzle without an identity either, | ||
272 | * because 3x3 puzzles can't ever be harder than Trivial | ||
273 | * (there are no 3x3 latin squares which aren't also valid | ||
274 | * group tables, so enabling group-based deductions doesn't | ||
275 | * rule out any possible solutions) and - as above - Trivial | ||
276 | * puzzles can't not have an identity. | ||
277 | */ | ||
278 | return "3x3 puzzles must have an identity"; | ||
279 | } | ||
280 | return NULL; | ||
281 | } | ||
282 | |||
283 | /* ---------------------------------------------------------------------- | ||
284 | * Solver. | ||
285 | */ | ||
286 | |||
287 | static int find_identity(struct latin_solver *solver) | ||
288 | { | ||
289 | int w = solver->o; | ||
290 | digit *grid = solver->grid; | ||
291 | int i, j; | ||
292 | |||
293 | for (i = 0; i < w; i++) | ||
294 | for (j = 0; j < w; j++) { | ||
295 | if (grid[i*w+j] == i+1) | ||
296 | return j+1; | ||
297 | if (grid[i*w+j] == j+1) | ||
298 | return i+1; | ||
299 | } | ||
300 | |||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | static int solver_normal(struct latin_solver *solver, void *vctx) | ||
305 | { | ||
306 | int w = solver->o; | ||
307 | #ifdef STANDALONE_SOLVER | ||
308 | char **names = solver->names; | ||
309 | #endif | ||
310 | digit *grid = solver->grid; | ||
311 | int i, j, k; | ||
312 | |||
313 | /* | ||
314 | * Deduce using associativity: (ab)c = a(bc). | ||
315 | * | ||
316 | * So we pick any a,b,c we like; then if we know ab, bc, and | ||
317 | * (ab)c we can fill in a(bc). | ||
318 | */ | ||
319 | for (i = 0; i < w; i++) | ||
320 | for (j = 0; j < w; j++) | ||
321 | for (k = 0; k < w; k++) { | ||
322 | if (!grid[i*w+j] || !grid[j*w+k]) | ||
323 | continue; | ||
324 | if (grid[(grid[i*w+j]-1)*w+k] && | ||
325 | !grid[i*w+(grid[j*w+k]-1)]) { | ||
326 | int x = grid[j*w+k]-1, y = i; | ||
327 | int n = grid[(grid[i*w+j]-1)*w+k]; | ||
328 | #ifdef STANDALONE_SOLVER | ||
329 | if (solver_show_working) { | ||
330 | printf("%*sassociativity on %s,%s,%s: %s*%s = %s*%s\n", | ||
331 | solver_recurse_depth*4, "", | ||
332 | names[i], names[j], names[k], | ||
333 | names[grid[i*w+j]-1], names[k], | ||
334 | names[i], names[grid[j*w+k]-1]); | ||
335 | printf("%*s placing %s at (%d,%d)\n", | ||
336 | solver_recurse_depth*4, "", | ||
337 | names[n-1], x+1, y+1); | ||
338 | } | ||
339 | #endif | ||
340 | if (solver->cube[(x*w+y)*w+n-1]) { | ||
341 | latin_solver_place(solver, x, y, n); | ||
342 | return 1; | ||
343 | } else { | ||
344 | #ifdef STANDALONE_SOLVER | ||
345 | if (solver_show_working) | ||
346 | printf("%*s contradiction!\n", | ||
347 | solver_recurse_depth*4, ""); | ||
348 | return -1; | ||
349 | #endif | ||
350 | } | ||
351 | } | ||
352 | if (!grid[(grid[i*w+j]-1)*w+k] && | ||
353 | grid[i*w+(grid[j*w+k]-1)]) { | ||
354 | int x = k, y = grid[i*w+j]-1; | ||
355 | int n = grid[i*w+(grid[j*w+k]-1)]; | ||
356 | #ifdef STANDALONE_SOLVER | ||
357 | if (solver_show_working) { | ||
358 | printf("%*sassociativity on %s,%s,%s: %s*%s = %s*%s\n", | ||
359 | solver_recurse_depth*4, "", | ||
360 | names[i], names[j], names[k], | ||
361 | names[grid[i*w+j]-1], names[k], | ||
362 | names[i], names[grid[j*w+k]-1]); | ||
363 | printf("%*s placing %s at (%d,%d)\n", | ||
364 | solver_recurse_depth*4, "", | ||
365 | names[n-1], x+1, y+1); | ||
366 | } | ||
367 | #endif | ||
368 | if (solver->cube[(x*w+y)*w+n-1]) { | ||
369 | latin_solver_place(solver, x, y, n); | ||
370 | return 1; | ||
371 | } else { | ||
372 | #ifdef STANDALONE_SOLVER | ||
373 | if (solver_show_working) | ||
374 | printf("%*s contradiction!\n", | ||
375 | solver_recurse_depth*4, ""); | ||
376 | return -1; | ||
377 | #endif | ||
378 | } | ||
379 | } | ||
380 | } | ||
381 | |||
382 | /* | ||
383 | * Fill in the row and column for the group identity, if it's not | ||
384 | * already known and if we've just found out what it is. | ||
385 | */ | ||
386 | i = find_identity(solver); | ||
387 | if (i) { | ||
388 | bool done_something = false; | ||
389 | for (j = 1; j <= w; j++) { | ||
390 | if (!grid[(i-1)*w+(j-1)] || !grid[(j-1)*w+(i-1)]) { | ||
391 | done_something = true; | ||
392 | } | ||
393 | } | ||
394 | if (done_something) { | ||
395 | #ifdef STANDALONE_SOLVER | ||
396 | if (solver_show_working) { | ||
397 | printf("%*s%s is the group identity\n", | ||
398 | solver_recurse_depth*4, "", names[i-1]); | ||
399 | } | ||
400 | #endif | ||
401 | for (j = 1; j <= w; j++) { | ||
402 | if (!grid[(j-1)*w+(i-1)]) { | ||
403 | if (!cube(i-1, j-1, j)) { | ||
404 | #ifdef STANDALONE_SOLVER | ||
405 | if (solver_show_working) { | ||
406 | printf("%*s but %s cannot go at (%d,%d) - " | ||
407 | "contradiction!\n", | ||
408 | solver_recurse_depth*4, "", | ||
409 | names[j-1], i, j); | ||
410 | } | ||
411 | #endif | ||
412 | return -1; | ||
413 | } | ||
414 | #ifdef STANDALONE_SOLVER | ||
415 | if (solver_show_working) { | ||
416 | printf("%*s placing %s at (%d,%d)\n", | ||
417 | solver_recurse_depth*4, "", | ||
418 | names[j-1], i, j); | ||
419 | } | ||
420 | #endif | ||
421 | latin_solver_place(solver, i-1, j-1, j); | ||
422 | } | ||
423 | if (!grid[(i-1)*w+(j-1)]) { | ||
424 | if (!cube(j-1, i-1, j)) { | ||
425 | #ifdef STANDALONE_SOLVER | ||
426 | if (solver_show_working) { | ||
427 | printf("%*s but %s cannot go at (%d,%d) - " | ||
428 | "contradiction!\n", | ||
429 | solver_recurse_depth*4, "", | ||
430 | names[j-1], j, i); | ||
431 | } | ||
432 | #endif | ||
433 | return -1; | ||
434 | } | ||
435 | #ifdef STANDALONE_SOLVER | ||
436 | if (solver_show_working) { | ||
437 | printf("%*s placing %s at (%d,%d)\n", | ||
438 | solver_recurse_depth*4, "", | ||
439 | names[j-1], j, i); | ||
440 | } | ||
441 | #endif | ||
442 | latin_solver_place(solver, j-1, i-1, j); | ||
443 | } | ||
444 | } | ||
445 | return 1; | ||
446 | } | ||
447 | } | ||
448 | |||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | static int solver_hard(struct latin_solver *solver, void *vctx) | ||
453 | { | ||
454 | bool done_something = false; | ||
455 | int w = solver->o; | ||
456 | #ifdef STANDALONE_SOLVER | ||
457 | char **names = solver->names; | ||
458 | #endif | ||
459 | int i, j; | ||
460 | |||
461 | /* | ||
462 | * In identity-hidden mode, systematically rule out possibilities | ||
463 | * for the group identity. | ||
464 | * | ||
465 | * In solver_normal, we used the fact that any filled square in | ||
466 | * the grid whose contents _does_ match one of the elements it's | ||
467 | * the product of - that is, ab=a or ab=b - tells you immediately | ||
468 | * that the other element is the identity. | ||
469 | * | ||
470 | * Here, we use the flip side of that: any filled square in the | ||
471 | * grid whose contents does _not_ match either its row or column - | ||
472 | * that is, if ab is neither a nor b - tells you immediately that | ||
473 | * _neither_ of those elements is the identity. And if that's | ||
474 | * true, then we can also immediately rule out the possibility | ||
475 | * that it acts as the identity on any element at all. | ||
476 | */ | ||
477 | for (i = 0; i < w; i++) { | ||
478 | bool i_can_be_id = true; | ||
479 | #ifdef STANDALONE_SOLVER | ||
480 | char title[80]; | ||
481 | #endif | ||
482 | |||
483 | for (j = 0; j < w; j++) { | ||
484 | if (grid(i,j) && grid(i,j) != j+1) { | ||
485 | #ifdef STANDALONE_SOLVER | ||
486 | if (solver_show_working) | ||
487 | sprintf(title, "%s cannot be the identity: " | ||
488 | "%s%s = %s =/= %s", names[i], names[i], names[j], | ||
489 | names[grid(i,j)-1], names[j]); | ||
490 | #endif | ||
491 | i_can_be_id = false; | ||
492 | break; | ||
493 | } | ||
494 | if (grid(j,i) && grid(j,i) != j+1) { | ||
495 | #ifdef STANDALONE_SOLVER | ||
496 | if (solver_show_working) | ||
497 | sprintf(title, "%s cannot be the identity: " | ||
498 | "%s%s = %s =/= %s", names[i], names[j], names[i], | ||
499 | names[grid(j,i)-1], names[j]); | ||
500 | #endif | ||
501 | i_can_be_id = false; | ||
502 | break; | ||
503 | } | ||
504 | } | ||
505 | |||
506 | if (!i_can_be_id) { | ||
507 | /* Now rule out ij=j or ji=j for all j. */ | ||
508 | for (j = 0; j < w; j++) { | ||
509 | if (cube(i, j, j+1)) { | ||
510 | #ifdef STANDALONE_SOLVER | ||
511 | if (solver_show_working) { | ||
512 | if (title[0]) { | ||
513 | printf("%*s%s\n", solver_recurse_depth*4, "", | ||
514 | title); | ||
515 | title[0] = '\0'; | ||
516 | } | ||
517 | printf("%*s ruling out %s at (%d,%d)\n", | ||
518 | solver_recurse_depth*4, "", names[j], i, j); | ||
519 | } | ||
520 | #endif | ||
521 | cube(i, j, j+1) = false; | ||
522 | } | ||
523 | if (cube(j, i, j+1)) { | ||
524 | #ifdef STANDALONE_SOLVER | ||
525 | if (solver_show_working) { | ||
526 | if (title[0]) { | ||
527 | printf("%*s%s\n", solver_recurse_depth*4, "", | ||
528 | title); | ||
529 | title[0] = '\0'; | ||
530 | } | ||
531 | printf("%*s ruling out %s at (%d,%d)\n", | ||
532 | solver_recurse_depth*4, "", names[j], j, i); | ||
533 | } | ||
534 | #endif | ||
535 | cube(j, i, j+1) = false; | ||
536 | } | ||
537 | } | ||
538 | } | ||
539 | } | ||
540 | |||
541 | return done_something; | ||
542 | } | ||
543 | |||
544 | #define SOLVER(upper,title,func,lower) func, | ||
545 | static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) }; | ||
546 | |||
547 | static bool group_valid(struct latin_solver *solver, void *ctx) | ||
548 | { | ||
549 | int w = solver->o; | ||
550 | #ifdef STANDALONE_SOLVER | ||
551 | char **names = solver->names; | ||
552 | #endif | ||
553 | int i, j, k; | ||
554 | |||
555 | for (i = 0; i < w; i++) | ||
556 | for (j = 0; j < w; j++) | ||
557 | for (k = 0; k < w; k++) { | ||
558 | int ij = grid(i, j) - 1; | ||
559 | int jk = grid(j, k) - 1; | ||
560 | int ij_k = grid(ij, k) - 1; | ||
561 | int i_jk = grid(i, jk) - 1; | ||
562 | if (ij_k != i_jk) { | ||
563 | #ifdef STANDALONE_SOLVER | ||
564 | if (solver_show_working) { | ||
565 | printf("%*sfailure of associativity: " | ||
566 | "(%s%s)%s = %s%s = %s but " | ||
567 | "%s(%s%s) = %s%s = %s\n", | ||
568 | solver_recurse_depth*4, "", | ||
569 | names[i], names[j], names[k], | ||
570 | names[ij], names[k], names[ij_k], | ||
571 | names[i], names[j], names[k], | ||
572 | names[i], names[jk], names[i_jk]); | ||
573 | } | ||
574 | #endif | ||
575 | return false; | ||
576 | } | ||
577 | } | ||
578 | |||
579 | return true; | ||
580 | } | ||
581 | |||
582 | static int solver(const game_params *params, digit *grid, int maxdiff) | ||
583 | { | ||
584 | int w = params->w; | ||
585 | int ret; | ||
586 | struct latin_solver solver; | ||
587 | |||
588 | #ifdef STANDALONE_SOLVER | ||
589 | char *p, text[100], *names[50]; | ||
590 | int i; | ||
591 | |||
592 | for (i = 0, p = text; i < w; i++) { | ||
593 | names[i] = p; | ||
594 | *p++ = TOCHAR(i+1, params->id); | ||
595 | *p++ = '\0'; | ||
596 | } | ||
597 | solver.names = names; | ||
598 | #endif | ||
599 | |||
600 | if (latin_solver_alloc(&solver, grid, w)) | ||
601 | ret = latin_solver_main(&solver, maxdiff, | ||
602 | DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME, | ||
603 | DIFF_EXTREME, DIFF_UNREASONABLE, | ||
604 | group_solvers, group_valid, NULL, NULL, NULL); | ||
605 | else | ||
606 | ret = diff_impossible; | ||
607 | |||
608 | latin_solver_free(&solver); | ||
609 | |||
610 | return ret; | ||
611 | } | ||
612 | |||
613 | /* ---------------------------------------------------------------------- | ||
614 | * Grid generation. | ||
615 | */ | ||
616 | |||
617 | static char *encode_grid(char *desc, digit *grid, int area) | ||
618 | { | ||
619 | int run, i; | ||
620 | char *p = desc; | ||
621 | |||
622 | run = 0; | ||
623 | for (i = 0; i <= area; i++) { | ||
624 | int n = (i < area ? grid[i] : -1); | ||
625 | |||
626 | if (!n) | ||
627 | run++; | ||
628 | else { | ||
629 | if (run) { | ||
630 | while (run > 0) { | ||
631 | int c = 'a' - 1 + run; | ||
632 | if (run > 26) | ||
633 | c = 'z'; | ||
634 | *p++ = c; | ||
635 | run -= c - ('a' - 1); | ||
636 | } | ||
637 | } else { | ||
638 | /* | ||
639 | * If there's a number in the very top left or | ||
640 | * bottom right, there's no point putting an | ||
641 | * unnecessary _ before or after it. | ||
642 | */ | ||
643 | if (p > desc && n > 0) | ||
644 | *p++ = '_'; | ||
645 | } | ||
646 | if (n > 0) | ||
647 | p += sprintf(p, "%d", n); | ||
648 | run = 0; | ||
649 | } | ||
650 | } | ||
651 | return p; | ||
652 | } | ||
653 | |||
654 | /* ----- data generated by group.gap begins ----- */ | ||
655 | |||
656 | struct group { | ||
657 | unsigned long autosize; | ||
658 | int order, ngens; | ||
659 | const char *gens; | ||
660 | }; | ||
661 | struct groups { | ||
662 | int ngroups; | ||
663 | const struct group *groups; | ||
664 | }; | ||
665 | |||
666 | static const struct group groupdata[] = { | ||
667 | /* order 2 */ | ||
668 | {1L, 2, 1, "BA"}, | ||
669 | /* order 3 */ | ||
670 | {2L, 3, 1, "BCA"}, | ||
671 | /* order 4 */ | ||
672 | {2L, 4, 1, "BCDA"}, | ||
673 | {6L, 4, 2, "BADC" "CDAB"}, | ||
674 | /* order 5 */ | ||
675 | {4L, 5, 1, "BCDEA"}, | ||
676 | /* order 6 */ | ||
677 | {6L, 6, 2, "CFEBAD" "BADCFE"}, | ||
678 | {2L, 6, 1, "DCFEBA"}, | ||
679 | /* order 7 */ | ||
680 | {6L, 7, 1, "BCDEFGA"}, | ||
681 | /* order 8 */ | ||
682 | {4L, 8, 1, "BCEFDGHA"}, | ||
683 | {8L, 8, 2, "BDEFGAHC" "EGBHDCFA"}, | ||
684 | {8L, 8, 2, "EGBHDCFA" "BAEFCDHG"}, | ||
685 | {24L, 8, 2, "BDEFGAHC" "CHDGBEAF"}, | ||
686 | {168L, 8, 3, "BAEFCDHG" "CEAGBHDF" "DFGAHBCE"}, | ||
687 | /* order 9 */ | ||
688 | {6L, 9, 1, "BDECGHFIA"}, | ||
689 | {48L, 9, 2, "BDEAGHCIF" "CEFGHAIBD"}, | ||
690 | /* order 10 */ | ||
691 | {20L, 10, 2, "CJEBGDIFAH" "BADCFEHGJI"}, | ||
692 | {4L, 10, 1, "DCFEHGJIBA"}, | ||
693 | /* order 11 */ | ||
694 | {10L, 11, 1, "BCDEFGHIJKA"}, | ||
695 | /* order 12 */ | ||
696 | {12L, 12, 2, "GLDKJEHCBIAF" "BCEFAGIJDKLH"}, | ||
697 | {4L, 12, 1, "EHIJKCBLDGFA"}, | ||
698 | {24L, 12, 2, "BEFGAIJKCDLH" "FJBKHLEGDCIA"}, | ||
699 | {12L, 12, 2, "GLDKJEHCBIAF" "BAEFCDIJGHLK"}, | ||
700 | {12L, 12, 2, "FDIJGHLBKAEC" "GIDKFLHCJEAB"}, | ||
701 | /* order 13 */ | ||
702 | {12L, 13, 1, "BCDEFGHIJKLMA"}, | ||
703 | /* order 14 */ | ||
704 | {42L, 14, 2, "ELGNIBKDMFAHCJ" "BADCFEHGJILKNM"}, | ||
705 | {6L, 14, 1, "FEHGJILKNMBADC"}, | ||
706 | /* order 15 */ | ||
707 | {8L, 15, 1, "EGHCJKFMNIOBLDA"}, | ||
708 | /* order 16 */ | ||
709 | {8L, 16, 1, "MKNPFOADBGLCIEHJ"}, | ||
710 | {96L, 16, 2, "ILKCONFPEDJHGMAB" "BDFGHIAKLMNCOEPJ"}, | ||
711 | {32L, 16, 2, "MIHPFDCONBLAKJGE" "BEFGHJKALMNOCDPI"}, | ||
712 | {32L, 16, 2, "IFACOGLMDEJBNPKH" "BEFGHJKALMNOCDPI"}, | ||
713 | {16L, 16, 2, "MOHPFKCINBLADJGE" "BDFGHIEKLMNJOAPC"}, | ||
714 | {16L, 16, 2, "MIHPFDJONBLEKCGA" "BDFGHIEKLMNJOAPC"}, | ||
715 | {32L, 16, 2, "MOHPFDCINBLEKJGA" "BAFGHCDELMNIJKPO"}, | ||
716 | {16L, 16, 2, "MIHPFKJONBLADCGE" "GDPHNOEKFLBCIAMJ"}, | ||
717 | {32L, 16, 2, "MIBPFDJOGHLEKCNA" "CLEIJGMPKAOHNFDB"}, | ||
718 | {192L, 16, 3, | ||
719 | "MCHPFAIJNBLDEOGK" "BEFGHJKALMNOCDPI" "GKLBNOEDFPHJIAMC"}, | ||
720 | {64L, 16, 3, "MCHPFAIJNBLDEOGK" "LOGFPKJIBNMEDCHA" "CMAIJHPFDEONBLKG"}, | ||
721 | {192L, 16, 3, | ||
722 | "IPKCOGMLEDJBNFAH" "BEFGHJKALMNOCDPI" "CMEIJBPFKAOGHLDN"}, | ||
723 | {48L, 16, 3, "IPDJONFLEKCBGMAH" "FJBLMEOCGHPKAIND" "DGIEKLHNJOAMPBCF"}, | ||
724 | {20160L, 16, 4, | ||
725 | "EHJKAMNBOCDPFGIL" "BAFGHCDELMNIJKPO" "CFAIJBLMDEOGHPKN" | ||
726 | "DGIAKLBNCOEFPHJM"}, | ||
727 | /* order 17 */ | ||
728 | {16L, 17, 1, "EFGHIJKLMNOPQABCD"}, | ||
729 | /* order 18 */ | ||
730 | {54L, 18, 2, "MKIQOPNAGLRECDBJHF" "BAEFCDJKLGHIOPMNRQ"}, | ||
731 | {6L, 18, 1, "ECJKGHFOPDMNLRIQBA"}, | ||
732 | {12L, 18, 2, "ECJKGHBOPAMNFRDQLI" "KNOPQCFREIGHLJAMBD"}, | ||
733 | {432L, 18, 3, | ||
734 | "IFNAKLQCDOPBGHREMJ" "NOQCFRIGHKLJAMPBDE" "BAEFCDJKLGHIOPMNRQ"}, | ||
735 | {48L, 18, 2, "ECJKGHBOPAMNFRDQLI" "FDKLHIOPBMNAREQCJG"}, | ||
736 | /* order 19 */ | ||
737 | {18L, 19, 1, "EFGHIJKLMNOPQRSABCD"}, | ||
738 | /* order 20 */ | ||
739 | {40L, 20, 2, "GTDKREHOBILSFMPCJQAN" "EABICDFMGHJQKLNTOPRS"}, | ||
740 | {8L, 20, 1, "EHIJLCMNPGQRSKBTDOFA"}, | ||
741 | {20L, 20, 2, "DJSHQNCLTRGPEBKAIFOM" "EABICDFMGHJQKLNTOPRS"}, | ||
742 | {40L, 20, 2, "GTDKREHOBILSFMPCJQAN" "ECBIAGFMDKJQHONTLSRP"}, | ||
743 | {24L, 20, 2, "IGFMDKJQHONTLSREPCBA" "FDIJGHMNKLQROPTBSAEC"}, | ||
744 | /* order 21 */ | ||
745 | {42L, 21, 2, "ITLSBOUERDHAGKCJNFMQP" "EJHLMKOPNRSQAUTCDBFGI"}, | ||
746 | {12L, 21, 1, "EGHCJKFMNIPQLSTOUBRDA"}, | ||
747 | /* order 22 */ | ||
748 | {110L, 22, 2, "ETGVIBKDMFOHQJSLUNAPCR" "BADCFEHGJILKNMPORQTSVU"}, | ||
749 | {10L, 22, 1, "FEHGJILKNMPORQTSVUBADC"}, | ||
750 | /* order 23 */ | ||
751 | {22L, 23, 1, "EFGHIJKLMNOPQRSTUVWABCD"}, | ||
752 | /* order 24 */ | ||
753 | {24L, 24, 2, "QXEJWPUMKLRIVBFTSACGHNDO" "HRNOPSWCTUVBLDIJXFGAKQME"}, | ||
754 | {8L, 24, 1, "MQBTUDRWFGHXJELINOPKSAVC"}, | ||
755 | {24L, 24, 2, "IOQRBEUVFWGHKLAXMNPSCDTJ" "NJXOVGDKSMTFIPQELCURBWAH"}, | ||
756 | {48L, 24, 2, "QUEJWVXFKLRIPGMNSACBOTDH" "HSNOPWLDTUVBRIAKXFGCQEMJ"}, | ||
757 | {24L, 24, 2, "QXEJWPUMKLRIVBFTSACGHNDO" "TWHNXLRIOPUMSACQVBFDEJGK"}, | ||
758 | {48L, 24, 2, "QUEJWVXFKLRIPGMNSACBOTDH" "BAFGHCDEMNOPIJKLTUVQRSXW"}, | ||
759 | {48L, 24, 3, | ||
760 | "QXKJWVUMESRIPGFTLDCBONAH" "JUEQRPXFKLWCVBMNSAIGHTDO" | ||
761 | "HSNOPWLDTUVBRIAKXFGCQEMJ"}, | ||
762 | {24L, 24, 3, | ||
763 | "QUKJWPXFESRIVBMNLDCGHTAO" "JXEQRVUMKLWCPGFTSAIBONDH" | ||
764 | "TRONXLWCHVUMSAIJPGFDEQBK"}, | ||
765 | {16L, 24, 2, "MRGTULWIOPFXSDJQBVNEKCHA" "VKXHOQASNTPBCWDEUFGIJLMR"}, | ||
766 | {16L, 24, 2, "MRGTULWIOPFXSDJQBVNEKCHA" "RMLWIGTUSDJQOPFXEKCBVNAH"}, | ||
767 | {48L, 24, 2, "IULQRGXMSDCWOPNTEKJBVFAH" "GLMOPRSDTUBVWIEKFXHJQANC"}, | ||
768 | {24L, 24, 2, "UJPXMRCSNHGTLWIKFVBEDQOA" "NRUFVLWIPXMOJEDQHGTCSABK"}, | ||
769 | {24L, 24, 2, "MIBTUAQRFGHXCDEWNOPJKLVS" "OKXVFWSCGUTNDRQJBPMALIHE"}, | ||
770 | {144L, 24, 3, | ||
771 | "QXKJWVUMESRIPGFTLDCBONAH" "JUEQRPXFKLWCVBMNSAIGHTDO" | ||
772 | "BAFGHCDEMNOPIJKLTUVQRSXW"}, | ||
773 | {336L, 24, 3, | ||
774 | "QTKJWONXESRIHVUMLDCPGFAB" "JNEQRHTUKLWCOPXFSAIVBMDG" | ||
775 | "HENOPJKLTUVBQRSAXFGWCDMI"}, | ||
776 | /* order 25 */ | ||
777 | {20L, 25, 1, "EHILMNPQRSFTUVBJWXDOYGAKC"}, | ||
778 | {480L, 25, 2, "EHILMNPQRSCTUVBFWXDJYGOKA" "BDEGHIKLMNAPQRSCTUVFWXJYO"}, | ||
779 | /* order 26 */ | ||
780 | {156L, 26, 2, | ||
781 | "EXGZIBKDMFOHQJSLUNWPYRATCV" "BADCFEHGJILKNMPORQTSVUXWZY"}, | ||
782 | {12L, 26, 1, "FEHGJILKNMPORQTSVUXWZYBADC"}, | ||
783 | }; | ||
784 | |||
785 | static const struct groups groups[] = { | ||
786 | {0, NULL}, /* trivial case: 0 */ | ||
787 | {0, NULL}, /* trivial case: 1 */ | ||
788 | {1, groupdata + 0}, /* 2 */ | ||
789 | {1, groupdata + 1}, /* 3 */ | ||
790 | {2, groupdata + 2}, /* 4 */ | ||
791 | {1, groupdata + 4}, /* 5 */ | ||
792 | {2, groupdata + 5}, /* 6 */ | ||
793 | {1, groupdata + 7}, /* 7 */ | ||
794 | {5, groupdata + 8}, /* 8 */ | ||
795 | {2, groupdata + 13}, /* 9 */ | ||
796 | {2, groupdata + 15}, /* 10 */ | ||
797 | {1, groupdata + 17}, /* 11 */ | ||
798 | {5, groupdata + 18}, /* 12 */ | ||
799 | {1, groupdata + 23}, /* 13 */ | ||
800 | {2, groupdata + 24}, /* 14 */ | ||
801 | {1, groupdata + 26}, /* 15 */ | ||
802 | {14, groupdata + 27}, /* 16 */ | ||
803 | {1, groupdata + 41}, /* 17 */ | ||
804 | {5, groupdata + 42}, /* 18 */ | ||
805 | {1, groupdata + 47}, /* 19 */ | ||
806 | {5, groupdata + 48}, /* 20 */ | ||
807 | {2, groupdata + 53}, /* 21 */ | ||
808 | {2, groupdata + 55}, /* 22 */ | ||
809 | {1, groupdata + 57}, /* 23 */ | ||
810 | {15, groupdata + 58}, /* 24 */ | ||
811 | {2, groupdata + 73}, /* 25 */ | ||
812 | {2, groupdata + 75}, /* 26 */ | ||
813 | }; | ||
814 | |||
815 | /* ----- data generated by group.gap ends ----- */ | ||
816 | |||
817 | static char *new_game_desc(const game_params *params, random_state *rs, | ||
818 | char **aux, bool interactive) | ||
819 | { | ||
820 | int w = params->w, a = w*w; | ||
821 | digit *grid, *soln, *soln2; | ||
822 | int *indices; | ||
823 | int i, j, k, qh, qt; | ||
824 | int diff = params->diff; | ||
825 | const struct group *group; | ||
826 | char *desc, *p; | ||
827 | |||
828 | /* | ||
829 | * Difficulty exceptions: some combinations of size and | ||
830 | * difficulty cannot be satisfied, because all puzzles of at | ||
831 | * most that difficulty are actually even easier. | ||
832 | * | ||
833 | * Remember to re-test this whenever a change is made to the | ||
834 | * solver logic! | ||
835 | * | ||
836 | * I tested it using the following shell command: | ||
837 | |||
838 | for d in t n h x u; do | ||
839 | for id in '' i; do | ||
840 | for i in {3..9}; do | ||
841 | echo -n "./group --generate 1 ${i}d${d}${id}: " | ||
842 | perl -e 'alarm 30; exec @ARGV' \ | ||
843 | ./group --generate 1 ${i}d${d}${id} >/dev/null && echo ok | ||
844 | done | ||
845 | done | ||
846 | done | ||
847 | |||
848 | * Of course, it's better to do that after taking the exceptions | ||
849 | * _out_, so as to detect exceptions that should be removed as | ||
850 | * well as those which should be added. | ||
851 | */ | ||
852 | if (w < 5 && diff == DIFF_UNREASONABLE) | ||
853 | diff--; | ||
854 | if ((w < 5 || ((w == 6 || w == 8) && params->id)) && diff == DIFF_EXTREME) | ||
855 | diff--; | ||
856 | if ((w < 6 || (w == 6 && params->id)) && diff == DIFF_HARD) | ||
857 | diff--; | ||
858 | if ((w < 4 || (w == 4 && params->id)) && diff == DIFF_NORMAL) | ||
859 | diff--; | ||
860 | |||
861 | grid = snewn(a, digit); | ||
862 | soln = snewn(a, digit); | ||
863 | soln2 = snewn(a, digit); | ||
864 | indices = snewn(a, int); | ||
865 | |||
866 | while (1) { | ||
867 | /* | ||
868 | * Construct a valid group table, by picking a group from | ||
869 | * the above data table, decompressing it into a full | ||
870 | * representation by BFS, and then randomly permuting its | ||
871 | * non-identity elements. | ||
872 | * | ||
873 | * We build the canonical table in 'soln' (and use 'grid' as | ||
874 | * our BFS queue), then transfer the table into 'grid' | ||
875 | * having shuffled the rows. | ||
876 | */ | ||
877 | assert(w >= 2); | ||
878 | assert(w < lenof(groups)); | ||
879 | group = groups[w].groups + random_upto(rs, groups[w].ngroups); | ||
880 | assert(group->order == w); | ||
881 | memset(soln, 0, a); | ||
882 | for (i = 0; i < w; i++) | ||
883 | soln[i] = i+1; | ||
884 | qh = qt = 0; | ||
885 | grid[qt++] = 1; | ||
886 | while (qh < qt) { | ||
887 | digit *row, *newrow; | ||
888 | |||
889 | i = grid[qh++]; | ||
890 | row = soln + (i-1)*w; | ||
891 | |||
892 | for (j = 0; j < group->ngens; j++) { | ||
893 | int nri; | ||
894 | const char *gen = group->gens + j*w; | ||
895 | |||
896 | /* | ||
897 | * Apply each group generator to row, constructing a | ||
898 | * new row. | ||
899 | */ | ||
900 | nri = gen[row[0]-1] - 'A' + 1; /* which row is it? */ | ||
901 | newrow = soln + (nri-1)*w; | ||
902 | if (!newrow[0]) { /* not done yet */ | ||
903 | for (k = 0; k < w; k++) | ||
904 | newrow[k] = gen[row[k]-1] - 'A' + 1; | ||
905 | grid[qt++] = nri; | ||
906 | } | ||
907 | } | ||
908 | } | ||
909 | /* That's got the canonical table. Now shuffle it. */ | ||
910 | for (i = 0; i < w; i++) | ||
911 | soln2[i] = i; | ||
912 | if (params->id) /* do we shuffle in the identity? */ | ||
913 | shuffle(soln2+1, w-1, sizeof(*soln2), rs); | ||
914 | else | ||
915 | shuffle(soln2, w, sizeof(*soln2), rs); | ||
916 | for (i = 0; i < w; i++) | ||
917 | for (j = 0; j < w; j++) | ||
918 | grid[(soln2[i])*w+(soln2[j])] = soln2[soln[i*w+j]-1]+1; | ||
919 | |||
920 | /* | ||
921 | * Remove entries one by one while the puzzle is still | ||
922 | * soluble at the appropriate difficulty level. | ||
923 | */ | ||
924 | memcpy(soln, grid, a); | ||
925 | if (!params->id) { | ||
926 | /* | ||
927 | * Start by blanking the entire identity row and column, | ||
928 | * and also another row and column so that the player | ||
929 | * can't trivially determine which element is the | ||
930 | * identity. | ||
931 | */ | ||
932 | |||
933 | j = 1 + random_upto(rs, w-1); /* pick a second row/col to blank */ | ||
934 | for (i = 0; i < w; i++) { | ||
935 | grid[(soln2[0])*w+i] = grid[i*w+(soln2[0])] = 0; | ||
936 | grid[(soln2[j])*w+i] = grid[i*w+(soln2[j])] = 0; | ||
937 | } | ||
938 | |||
939 | memcpy(soln2, grid, a); | ||
940 | if (solver(params, soln2, diff) > diff) | ||
941 | continue; /* go round again if that didn't work */ | ||
942 | } | ||
943 | |||
944 | k = 0; | ||
945 | for (i = (params->id ? 1 : 0); i < w; i++) | ||
946 | for (j = (params->id ? 1 : 0); j < w; j++) | ||
947 | if (grid[i*w+j]) | ||
948 | indices[k++] = i*w+j; | ||
949 | shuffle(indices, k, sizeof(*indices), rs); | ||
950 | |||
951 | for (i = 0; i < k; i++) { | ||
952 | memcpy(soln2, grid, a); | ||
953 | soln2[indices[i]] = 0; | ||
954 | if (solver(params, soln2, diff) <= diff) | ||
955 | grid[indices[i]] = 0; | ||
956 | } | ||
957 | |||
958 | /* | ||
959 | * Make sure the puzzle isn't too easy. | ||
960 | */ | ||
961 | if (diff > 0) { | ||
962 | memcpy(soln2, grid, a); | ||
963 | if (solver(params, soln2, diff-1) < diff) | ||
964 | continue; /* go round and try again */ | ||
965 | } | ||
966 | |||
967 | /* | ||
968 | * Done. | ||
969 | */ | ||
970 | break; | ||
971 | } | ||
972 | |||
973 | /* | ||
974 | * Encode the puzzle description. | ||
975 | */ | ||
976 | desc = snewn(a*20, char); | ||
977 | p = encode_grid(desc, grid, a); | ||
978 | *p++ = '\0'; | ||
979 | desc = sresize(desc, p - desc, char); | ||
980 | |||
981 | /* | ||
982 | * Encode the solution. | ||
983 | */ | ||
984 | *aux = snewn(a+2, char); | ||
985 | (*aux)[0] = 'S'; | ||
986 | for (i = 0; i < a; i++) | ||
987 | (*aux)[i+1] = TOCHAR(soln[i], params->id); | ||
988 | (*aux)[a+1] = '\0'; | ||
989 | |||
990 | sfree(grid); | ||
991 | sfree(soln); | ||
992 | sfree(soln2); | ||
993 | sfree(indices); | ||
994 | |||
995 | return desc; | ||
996 | } | ||
997 | |||
998 | /* ---------------------------------------------------------------------- | ||
999 | * Gameplay. | ||
1000 | */ | ||
1001 | |||
1002 | static const char *validate_grid_desc(const char **pdesc, int range, int area) | ||
1003 | { | ||
1004 | const char *desc = *pdesc; | ||
1005 | int squares = 0; | ||
1006 | while (*desc && *desc != ',') { | ||
1007 | int n = *desc++; | ||
1008 | if (n >= 'a' && n <= 'z') { | ||
1009 | squares += n - 'a' + 1; | ||
1010 | } else if (n == '_') { | ||
1011 | /* do nothing */; | ||
1012 | } else if (n > '0' && n <= '9') { | ||
1013 | int val = atoi(desc-1); | ||
1014 | if (val < 1 || val > range) | ||
1015 | return "Out-of-range number in game description"; | ||
1016 | squares++; | ||
1017 | while (*desc >= '0' && *desc <= '9') | ||
1018 | desc++; | ||
1019 | } else | ||
1020 | return "Invalid character in game description"; | ||
1021 | } | ||
1022 | |||
1023 | if (squares < area) | ||
1024 | return "Not enough data to fill grid"; | ||
1025 | |||
1026 | if (squares > area) | ||
1027 | return "Too much data to fit in grid"; | ||
1028 | *pdesc = desc; | ||
1029 | return NULL; | ||
1030 | } | ||
1031 | |||
1032 | static const char *validate_desc(const game_params *params, const char *desc) | ||
1033 | { | ||
1034 | int w = params->w, a = w*w; | ||
1035 | const char *p = desc; | ||
1036 | |||
1037 | return validate_grid_desc(&p, w, a); | ||
1038 | } | ||
1039 | |||
1040 | static const char *spec_to_grid(const char *desc, digit *grid, int area) | ||
1041 | { | ||
1042 | int i = 0; | ||
1043 | while (*desc && *desc != ',') { | ||
1044 | int n = *desc++; | ||
1045 | if (n >= 'a' && n <= 'z') { | ||
1046 | int run = n - 'a' + 1; | ||
1047 | assert(i + run <= area); | ||
1048 | while (run-- > 0) | ||
1049 | grid[i++] = 0; | ||
1050 | } else if (n == '_') { | ||
1051 | /* do nothing */; | ||
1052 | } else if (n > '0' && n <= '9') { | ||
1053 | assert(i < area); | ||
1054 | grid[i++] = atoi(desc-1); | ||
1055 | while (*desc >= '0' && *desc <= '9') | ||
1056 | desc++; | ||
1057 | } else { | ||
1058 | assert(!"We can't get here"); | ||
1059 | } | ||
1060 | } | ||
1061 | assert(i == area); | ||
1062 | return desc; | ||
1063 | } | ||
1064 | |||
1065 | static game_state *new_game(midend *me, const game_params *params, | ||
1066 | const char *desc) | ||
1067 | { | ||
1068 | int w = params->w, a = w*w; | ||
1069 | game_state *state = snew(game_state); | ||
1070 | int i; | ||
1071 | |||
1072 | state->par = *params; /* structure copy */ | ||
1073 | state->grid = snewn(a, digit); | ||
1074 | state->common = snew(group_common); | ||
1075 | state->common->refcount = 1; | ||
1076 | state->common->immutable = snewn(a, bool); | ||
1077 | state->pencil = snewn(a, int); | ||
1078 | for (i = 0; i < a; i++) { | ||
1079 | state->grid[i] = 0; | ||
1080 | state->common->immutable[i] = false; | ||
1081 | state->pencil[i] = 0; | ||
1082 | } | ||
1083 | state->sequence = snewn(w, digit); | ||
1084 | state->dividers = snewn(w, int); | ||
1085 | for (i = 0; i < w; i++) { | ||
1086 | state->sequence[i] = i; | ||
1087 | state->dividers[i] = -1; | ||
1088 | } | ||
1089 | |||
1090 | desc = spec_to_grid(desc, state->grid, a); | ||
1091 | for (i = 0; i < a; i++) | ||
1092 | if (state->grid[i] != 0) | ||
1093 | state->common->immutable[i] = true; | ||
1094 | |||
1095 | state->completed = false; | ||
1096 | state->cheated = false; | ||
1097 | |||
1098 | return state; | ||
1099 | } | ||
1100 | |||
1101 | static game_state *dup_game(const game_state *state) | ||
1102 | { | ||
1103 | int w = state->par.w, a = w*w; | ||
1104 | game_state *ret = snew(game_state); | ||
1105 | |||
1106 | ret->par = state->par; /* structure copy */ | ||
1107 | |||
1108 | ret->grid = snewn(a, digit); | ||
1109 | ret->common = state->common; | ||
1110 | ret->common->refcount++; | ||
1111 | ret->pencil = snewn(a, int); | ||
1112 | ret->sequence = snewn(w, digit); | ||
1113 | ret->dividers = snewn(w, int); | ||
1114 | memcpy(ret->grid, state->grid, a*sizeof(digit)); | ||
1115 | memcpy(ret->pencil, state->pencil, a*sizeof(int)); | ||
1116 | memcpy(ret->sequence, state->sequence, w*sizeof(digit)); | ||
1117 | memcpy(ret->dividers, state->dividers, w*sizeof(int)); | ||
1118 | |||
1119 | ret->completed = state->completed; | ||
1120 | ret->cheated = state->cheated; | ||
1121 | |||
1122 | return ret; | ||
1123 | } | ||
1124 | |||
1125 | static void free_game(game_state *state) | ||
1126 | { | ||
1127 | sfree(state->grid); | ||
1128 | if (--state->common->refcount == 0) { | ||
1129 | sfree(state->common->immutable); | ||
1130 | sfree(state->common); | ||
1131 | } | ||
1132 | sfree(state->pencil); | ||
1133 | sfree(state->sequence); | ||
1134 | sfree(state); | ||
1135 | } | ||
1136 | |||
1137 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
1138 | const char *aux, const char **error) | ||
1139 | { | ||
1140 | int w = state->par.w, a = w*w; | ||
1141 | int i, ret; | ||
1142 | digit *soln; | ||
1143 | char *out; | ||
1144 | |||
1145 | if (aux) | ||
1146 | return dupstr(aux); | ||
1147 | |||
1148 | soln = snewn(a, digit); | ||
1149 | memcpy(soln, state->grid, a*sizeof(digit)); | ||
1150 | |||
1151 | ret = solver(&state->par, soln, DIFFCOUNT-1); | ||
1152 | |||
1153 | if (ret == diff_impossible) { | ||
1154 | *error = "No solution exists for this puzzle"; | ||
1155 | out = NULL; | ||
1156 | } else if (ret == diff_ambiguous) { | ||
1157 | *error = "Multiple solutions exist for this puzzle"; | ||
1158 | out = NULL; | ||
1159 | } else { | ||
1160 | out = snewn(a+2, char); | ||
1161 | out[0] = 'S'; | ||
1162 | for (i = 0; i < a; i++) | ||
1163 | out[i+1] = TOCHAR(soln[i], state->par.id); | ||
1164 | out[a+1] = '\0'; | ||
1165 | } | ||
1166 | |||
1167 | sfree(soln); | ||
1168 | return out; | ||
1169 | } | ||
1170 | |||
1171 | static bool game_can_format_as_text_now(const game_params *params) | ||
1172 | { | ||
1173 | return true; | ||
1174 | } | ||
1175 | |||
1176 | static char *game_text_format(const game_state *state) | ||
1177 | { | ||
1178 | int w = state->par.w; | ||
1179 | int x, y; | ||
1180 | char *ret, *p, ch; | ||
1181 | |||
1182 | ret = snewn(2*w*w+1, char); /* leave room for terminating NUL */ | ||
1183 | |||
1184 | p = ret; | ||
1185 | for (y = 0; y < w; y++) { | ||
1186 | for (x = 0; x < w; x++) { | ||
1187 | digit d = state->grid[y*w+x]; | ||
1188 | |||
1189 | if (d == 0) { | ||
1190 | ch = '.'; | ||
1191 | } else { | ||
1192 | ch = TOCHAR(d, state->par.id); | ||
1193 | } | ||
1194 | |||
1195 | *p++ = ch; | ||
1196 | if (x == w-1) { | ||
1197 | *p++ = '\n'; | ||
1198 | } else { | ||
1199 | *p++ = ' '; | ||
1200 | } | ||
1201 | } | ||
1202 | } | ||
1203 | |||
1204 | assert(p - ret == 2*w*w); | ||
1205 | *p = '\0'; | ||
1206 | return ret; | ||
1207 | } | ||
1208 | |||
1209 | struct game_ui { | ||
1210 | /* | ||
1211 | * These are the coordinates of the primary highlighted square on | ||
1212 | * the grid, if hshow = 1. | ||
1213 | */ | ||
1214 | int hx, hy; | ||
1215 | /* | ||
1216 | * These are the coordinates hx,hy _before_ they go through | ||
1217 | * state->sequence. | ||
1218 | */ | ||
1219 | int ohx, ohy; | ||
1220 | /* | ||
1221 | * These variables give the length and displacement of a diagonal | ||
1222 | * sequence of highlighted squares starting at ohx,ohy (still if | ||
1223 | * hshow = 1). To find the squares' real coordinates, for 0<=i<dn, | ||
1224 | * compute ohx+i*odx and ohy+i*ody and then map through | ||
1225 | * state->sequence. | ||
1226 | */ | ||
1227 | int odx, ody, odn; | ||
1228 | /* | ||
1229 | * This indicates whether the current highlight is a | ||
1230 | * pencil-mark one or a real one. | ||
1231 | */ | ||
1232 | bool hpencil; | ||
1233 | /* | ||
1234 | * This indicates whether or not we're showing the highlight | ||
1235 | * (used to be hx = hy = -1); important so that when we're | ||
1236 | * using the cursor keys it doesn't keep coming back at a | ||
1237 | * fixed position. When hshow = 1, pressing a valid number | ||
1238 | * or letter key or Space will enter that number or letter in the grid. | ||
1239 | */ | ||
1240 | bool hshow; | ||
1241 | /* | ||
1242 | * This indicates whether we're using the highlight as a cursor; | ||
1243 | * it means that it doesn't vanish on a keypress, and that it is | ||
1244 | * allowed on immutable squares. | ||
1245 | */ | ||
1246 | bool hcursor; | ||
1247 | /* | ||
1248 | * This indicates whether we're dragging a table header to | ||
1249 | * reposition an entire row or column. | ||
1250 | */ | ||
1251 | int drag; /* 0=none 1=row 2=col */ | ||
1252 | int dragnum; /* element being dragged */ | ||
1253 | int dragpos; /* its current position */ | ||
1254 | int edgepos; | ||
1255 | |||
1256 | /* | ||
1257 | * User preference option: if the user right-clicks in a square | ||
1258 | * and presses a letter key to add/remove a pencil mark, do we | ||
1259 | * hide the mouse highlight again afterwards? | ||
1260 | * | ||
1261 | * Historically our answer was yes. The Android port prefers no. | ||
1262 | * There are advantages both ways, depending how much you dislike | ||
1263 | * the highlight cluttering your view. So it's a preference. | ||
1264 | */ | ||
1265 | bool pencil_keep_highlight; | ||
1266 | }; | ||
1267 | |||
1268 | static game_ui *new_ui(const game_state *state) | ||
1269 | { | ||
1270 | game_ui *ui = snew(game_ui); | ||
1271 | |||
1272 | ui->hx = ui->hy = 0; | ||
1273 | ui->hpencil = false; | ||
1274 | ui->hshow = false; | ||
1275 | ui->hcursor = false; | ||
1276 | ui->drag = 0; | ||
1277 | |||
1278 | ui->pencil_keep_highlight = false; | ||
1279 | |||
1280 | return ui; | ||
1281 | } | ||
1282 | |||
1283 | static void free_ui(game_ui *ui) | ||
1284 | { | ||
1285 | sfree(ui); | ||
1286 | } | ||
1287 | |||
1288 | static config_item *get_prefs(game_ui *ui) | ||
1289 | { | ||
1290 | config_item *ret; | ||
1291 | |||
1292 | ret = snewn(2, config_item); | ||
1293 | |||
1294 | ret[0].name = "Keep mouse highlight after changing a pencil mark"; | ||
1295 | ret[0].kw = "pencil-keep-highlight"; | ||
1296 | ret[0].type = C_BOOLEAN; | ||
1297 | ret[0].u.boolean.bval = ui->pencil_keep_highlight; | ||
1298 | |||
1299 | ret[1].name = NULL; | ||
1300 | ret[1].type = C_END; | ||
1301 | |||
1302 | return ret; | ||
1303 | } | ||
1304 | |||
1305 | static void set_prefs(game_ui *ui, const config_item *cfg) | ||
1306 | { | ||
1307 | ui->pencil_keep_highlight = cfg[0].u.boolean.bval; | ||
1308 | } | ||
1309 | |||
1310 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
1311 | const game_state *newstate) | ||
1312 | { | ||
1313 | int w = newstate->par.w; | ||
1314 | /* | ||
1315 | * We prevent pencil-mode highlighting of a filled square, unless | ||
1316 | * we're using the cursor keys. So if the user has just filled in | ||
1317 | * a square which we had a pencil-mode highlight in (by Undo, or | ||
1318 | * by Redo, or by Solve), then we cancel the highlight. | ||
1319 | */ | ||
1320 | if (ui->hshow && ui->hpencil && !ui->hcursor && | ||
1321 | newstate->grid[ui->hy * w + ui->hx] != 0) { | ||
1322 | ui->hshow = false; | ||
1323 | } | ||
1324 | if (ui->hshow && ui->odn > 1) { | ||
1325 | /* | ||
1326 | * Reordering of rows or columns within the range of a | ||
1327 | * multifill selection cancels the multifill and deselects | ||
1328 | * everything. | ||
1329 | */ | ||
1330 | int i; | ||
1331 | for (i = 0; i < ui->odn; i++) { | ||
1332 | if (oldstate->sequence[ui->ohx + i*ui->odx] != | ||
1333 | newstate->sequence[ui->ohx + i*ui->odx]) { | ||
1334 | ui->hshow = false; | ||
1335 | break; | ||
1336 | } | ||
1337 | if (oldstate->sequence[ui->ohy + i*ui->ody] != | ||
1338 | newstate->sequence[ui->ohy + i*ui->ody]) { | ||
1339 | ui->hshow = false; | ||
1340 | break; | ||
1341 | } | ||
1342 | } | ||
1343 | } else if (ui->hshow && | ||
1344 | (newstate->sequence[ui->ohx] != ui->hx || | ||
1345 | newstate->sequence[ui->ohy] != ui->hy)) { | ||
1346 | /* | ||
1347 | * Otherwise, reordering of the row or column containing the | ||
1348 | * selection causes the selection to move with it. | ||
1349 | */ | ||
1350 | int i; | ||
1351 | for (i = 0; i < w; i++) { | ||
1352 | if (newstate->sequence[i] == ui->hx) | ||
1353 | ui->ohx = i; | ||
1354 | if (newstate->sequence[i] == ui->hy) | ||
1355 | ui->ohy = i; | ||
1356 | } | ||
1357 | } | ||
1358 | } | ||
1359 | |||
1360 | static const char *current_key_label(const game_ui *ui, | ||
1361 | const game_state *state, int button) | ||
1362 | { | ||
1363 | if (ui->hshow && button == CURSOR_SELECT) | ||
1364 | return ui->hpencil ? "Ink" : "Pencil"; | ||
1365 | if (ui->hshow && button == CURSOR_SELECT2) { | ||
1366 | int w = state->par.w; | ||
1367 | int i; | ||
1368 | for (i = 0; i < ui->odn; i++) { | ||
1369 | int x = state->sequence[ui->ohx + i*ui->odx]; | ||
1370 | int y = state->sequence[ui->ohy + i*ui->ody]; | ||
1371 | int index = y*w+x; | ||
1372 | if (ui->hpencil && state->grid[index]) return ""; | ||
1373 | if (state->common->immutable[index]) return ""; | ||
1374 | } | ||
1375 | return "Clear"; | ||
1376 | } | ||
1377 | return ""; | ||
1378 | } | ||
1379 | |||
1380 | #define PREFERRED_TILESIZE 48 | ||
1381 | #define TILESIZE (ds->tilesize) | ||
1382 | #define BORDER (TILESIZE / 2) | ||
1383 | #define LEGEND (TILESIZE) | ||
1384 | #define GRIDEXTRA max((TILESIZE / 32),1) | ||
1385 | #define COORD(x) ((x)*TILESIZE + BORDER + LEGEND) | ||
1386 | #define FROMCOORD(x) (((x)+(TILESIZE-BORDER-LEGEND)) / TILESIZE - 1) | ||
1387 | |||
1388 | #define FLASH_TIME 0.4F | ||
1389 | |||
1390 | #define DF_DIVIDER_TOP 0x1000 | ||
1391 | #define DF_DIVIDER_BOT 0x2000 | ||
1392 | #define DF_DIVIDER_LEFT 0x4000 | ||
1393 | #define DF_DIVIDER_RIGHT 0x8000 | ||
1394 | #define DF_HIGHLIGHT 0x0400 | ||
1395 | #define DF_HIGHLIGHT_PENCIL 0x0200 | ||
1396 | #define DF_IMMUTABLE 0x0100 | ||
1397 | #define DF_LEGEND 0x0080 | ||
1398 | #define DF_DIGIT_MASK 0x001F | ||
1399 | |||
1400 | #define EF_DIGIT_SHIFT 5 | ||
1401 | #define EF_DIGIT_MASK ((1 << EF_DIGIT_SHIFT) - 1) | ||
1402 | #define EF_LEFT_SHIFT 0 | ||
1403 | #define EF_RIGHT_SHIFT (3*EF_DIGIT_SHIFT) | ||
1404 | #define EF_LEFT_MASK ((1UL << (3*EF_DIGIT_SHIFT)) - 1UL) | ||
1405 | #define EF_RIGHT_MASK (EF_LEFT_MASK << EF_RIGHT_SHIFT) | ||
1406 | #define EF_LATIN (1UL << (6*EF_DIGIT_SHIFT)) | ||
1407 | |||
1408 | struct game_drawstate { | ||
1409 | game_params par; | ||
1410 | int w, tilesize; | ||
1411 | bool started; | ||
1412 | long *tiles, *legend, *pencil, *errors; | ||
1413 | long *errtmp; | ||
1414 | digit *sequence; | ||
1415 | }; | ||
1416 | |||
1417 | static bool check_errors(const game_state *state, long *errors) | ||
1418 | { | ||
1419 | int w = state->par.w, a = w*w; | ||
1420 | digit *grid = state->grid; | ||
1421 | int i, j, k, x, y; | ||
1422 | bool errs = false; | ||
1423 | |||
1424 | /* | ||
1425 | * To verify that we have a valid group table, it suffices to | ||
1426 | * test latin-square-hood and associativity only. All the other | ||
1427 | * group axioms follow from those two. | ||
1428 | * | ||
1429 | * Proof: | ||
1430 | * | ||
1431 | * Associativity is given; closure is obvious from latin- | ||
1432 | * square-hood. We need to show that an identity exists and that | ||
1433 | * every element has an inverse. | ||
1434 | * | ||
1435 | * Identity: take any element a. There will be some element e | ||
1436 | * such that ea=a (in a latin square, every element occurs in | ||
1437 | * every row and column, so a must occur somewhere in the a | ||
1438 | * column, say on row e). For any other element b, there must | ||
1439 | * exist x such that ax=b (same argument from latin-square-hood | ||
1440 | * again), and then associativity gives us eb = e(ax) = (ea)x = | ||
1441 | * ax = b. Hence eb=b for all b, i.e. e is a left-identity. A | ||
1442 | * similar argument tells us that there must be some f which is | ||
1443 | * a right-identity, and then we show they are the same element | ||
1444 | * by observing that ef must simultaneously equal e and equal f. | ||
1445 | * | ||
1446 | * Inverses: given any a, by the latin-square argument again, | ||
1447 | * there must exist p and q such that pa=e and aq=e (i.e. left- | ||
1448 | * and right-inverses). We can show these are equal by | ||
1449 | * associativity: p = pe = p(aq) = (pa)q = eq = q. [] | ||
1450 | */ | ||
1451 | |||
1452 | if (errors) | ||
1453 | for (i = 0; i < a; i++) | ||
1454 | errors[i] = 0; | ||
1455 | |||
1456 | for (y = 0; y < w; y++) { | ||
1457 | unsigned long mask = 0, errmask = 0; | ||
1458 | for (x = 0; x < w; x++) { | ||
1459 | unsigned long bit = 1UL << grid[y*w+x]; | ||
1460 | errmask |= (mask & bit); | ||
1461 | mask |= bit; | ||
1462 | } | ||
1463 | |||
1464 | if (mask != (1 << (w+1)) - (1 << 1)) { | ||
1465 | errs = true; | ||
1466 | errmask &= ~1UL; | ||
1467 | if (errors) { | ||
1468 | for (x = 0; x < w; x++) | ||
1469 | if (errmask & (1UL << grid[y*w+x])) | ||
1470 | errors[y*w+x] |= EF_LATIN; | ||
1471 | } | ||
1472 | } | ||
1473 | } | ||
1474 | |||
1475 | for (x = 0; x < w; x++) { | ||
1476 | unsigned long mask = 0, errmask = 0; | ||
1477 | for (y = 0; y < w; y++) { | ||
1478 | unsigned long bit = 1UL << grid[y*w+x]; | ||
1479 | errmask |= (mask & bit); | ||
1480 | mask |= bit; | ||
1481 | } | ||
1482 | |||
1483 | if (mask != (1 << (w+1)) - (1 << 1)) { | ||
1484 | errs = true; | ||
1485 | errmask &= ~1UL; | ||
1486 | if (errors) { | ||
1487 | for (y = 0; y < w; y++) | ||
1488 | if (errmask & (1UL << grid[y*w+x])) | ||
1489 | errors[y*w+x] |= EF_LATIN; | ||
1490 | } | ||
1491 | } | ||
1492 | } | ||
1493 | |||
1494 | for (i = 1; i < w; i++) | ||
1495 | for (j = 1; j < w; j++) | ||
1496 | for (k = 1; k < w; k++) | ||
1497 | if (grid[i*w+j] && grid[j*w+k] && | ||
1498 | grid[(grid[i*w+j]-1)*w+k] && | ||
1499 | grid[i*w+(grid[j*w+k]-1)] && | ||
1500 | grid[(grid[i*w+j]-1)*w+k] != grid[i*w+(grid[j*w+k]-1)]) { | ||
1501 | if (errors) { | ||
1502 | int a = i+1, b = j+1, c = k+1; | ||
1503 | int ab = grid[i*w+j], bc = grid[j*w+k]; | ||
1504 | int left = (ab-1)*w+(c-1), right = (a-1)*w+(bc-1); | ||
1505 | /* | ||
1506 | * If the appropriate error slot is already | ||
1507 | * used for one of the squares, we don't | ||
1508 | * fill either of them. | ||
1509 | */ | ||
1510 | if (!(errors[left] & EF_LEFT_MASK) && | ||
1511 | !(errors[right] & EF_RIGHT_MASK)) { | ||
1512 | long err; | ||
1513 | err = a; | ||
1514 | err = (err << EF_DIGIT_SHIFT) | b; | ||
1515 | err = (err << EF_DIGIT_SHIFT) | c; | ||
1516 | errors[left] |= err << EF_LEFT_SHIFT; | ||
1517 | errors[right] |= err << EF_RIGHT_SHIFT; | ||
1518 | } | ||
1519 | } | ||
1520 | errs = true; | ||
1521 | } | ||
1522 | |||
1523 | return errs; | ||
1524 | } | ||
1525 | |||
1526 | static int find_in_sequence(digit *seq, int len, digit n) | ||
1527 | { | ||
1528 | int i; | ||
1529 | |||
1530 | for (i = 0; i < len; i++) | ||
1531 | if (seq[i] == n) | ||
1532 | return i; | ||
1533 | |||
1534 | assert(!"Should never get here"); | ||
1535 | return -1; | ||
1536 | } | ||
1537 | |||
1538 | static char *interpret_move(const game_state *state, game_ui *ui, | ||
1539 | const game_drawstate *ds, | ||
1540 | int x, int y, int button) | ||
1541 | { | ||
1542 | int w = state->par.w; | ||
1543 | int tx, ty; | ||
1544 | char buf[80]; | ||
1545 | |||
1546 | button = STRIP_BUTTON_MODIFIERS(button); | ||
1547 | |||
1548 | tx = FROMCOORD(x); | ||
1549 | ty = FROMCOORD(y); | ||
1550 | |||
1551 | if (ui->drag) { | ||
1552 | if (IS_MOUSE_DRAG(button)) { | ||
1553 | int tcoord = ((ui->drag &~ 4) == 1 ? ty : tx); | ||
1554 | ui->drag |= 4; /* some movement has happened */ | ||
1555 | if (tcoord >= 0 && tcoord < w) { | ||
1556 | ui->dragpos = tcoord; | ||
1557 | return MOVE_UI_UPDATE; | ||
1558 | } | ||
1559 | } else if (IS_MOUSE_RELEASE(button)) { | ||
1560 | if (ui->drag & 4) { | ||
1561 | ui->drag = 0; /* end drag */ | ||
1562 | if (state->sequence[ui->dragpos] == ui->dragnum) | ||
1563 | return MOVE_UI_UPDATE; /* drag was a no-op overall */ | ||
1564 | sprintf(buf, "D%d,%d", ui->dragnum, ui->dragpos); | ||
1565 | return dupstr(buf); | ||
1566 | } else { | ||
1567 | ui->drag = 0; /* end 'drag' */ | ||
1568 | if (ui->edgepos > 0 && ui->edgepos < w) { | ||
1569 | sprintf(buf, "V%d,%d", | ||
1570 | state->sequence[ui->edgepos-1], | ||
1571 | state->sequence[ui->edgepos]); | ||
1572 | return dupstr(buf); | ||
1573 | } else | ||
1574 | return MOVE_UI_UPDATE; /* no-op */ | ||
1575 | } | ||
1576 | } | ||
1577 | } else if (IS_MOUSE_DOWN(button)) { | ||
1578 | if (tx >= 0 && tx < w && ty >= 0 && ty < w) { | ||
1579 | int otx = tx, oty = ty; | ||
1580 | tx = state->sequence[tx]; | ||
1581 | ty = state->sequence[ty]; | ||
1582 | if (button == LEFT_BUTTON) { | ||
1583 | if (tx == ui->hx && ty == ui->hy && | ||
1584 | ui->hshow && !ui->hpencil) { | ||
1585 | ui->hshow = false; | ||
1586 | } else { | ||
1587 | ui->hx = tx; | ||
1588 | ui->hy = ty; | ||
1589 | ui->ohx = otx; | ||
1590 | ui->ohy = oty; | ||
1591 | ui->odx = ui->ody = 0; | ||
1592 | ui->odn = 1; | ||
1593 | ui->hshow = !state->common->immutable[ty*w+tx]; | ||
1594 | ui->hpencil = false; | ||
1595 | } | ||
1596 | ui->hcursor = false; | ||
1597 | return MOVE_UI_UPDATE; | ||
1598 | } | ||
1599 | if (button == RIGHT_BUTTON) { | ||
1600 | /* | ||
1601 | * Pencil-mode highlighting for non filled squares. | ||
1602 | */ | ||
1603 | if (state->grid[ty*w+tx] == 0) { | ||
1604 | if (tx == ui->hx && ty == ui->hy && | ||
1605 | ui->hshow && ui->hpencil) { | ||
1606 | ui->hshow = false; | ||
1607 | } else { | ||
1608 | ui->hpencil = true; | ||
1609 | ui->hx = tx; | ||
1610 | ui->hy = ty; | ||
1611 | ui->ohx = otx; | ||
1612 | ui->ohy = oty; | ||
1613 | ui->odx = ui->ody = 0; | ||
1614 | ui->odn = 1; | ||
1615 | ui->hshow = true; | ||
1616 | } | ||
1617 | } else { | ||
1618 | ui->hshow = false; | ||
1619 | } | ||
1620 | ui->hcursor = false; | ||
1621 | return MOVE_UI_UPDATE; | ||
1622 | } | ||
1623 | } else if (tx >= 0 && tx < w && ty == -1) { | ||
1624 | ui->drag = 2; | ||
1625 | ui->dragnum = state->sequence[tx]; | ||
1626 | ui->dragpos = tx; | ||
1627 | ui->edgepos = FROMCOORD(x + TILESIZE/2); | ||
1628 | return MOVE_UI_UPDATE; | ||
1629 | } else if (ty >= 0 && ty < w && tx == -1) { | ||
1630 | ui->drag = 1; | ||
1631 | ui->dragnum = state->sequence[ty]; | ||
1632 | ui->dragpos = ty; | ||
1633 | ui->edgepos = FROMCOORD(y + TILESIZE/2); | ||
1634 | return MOVE_UI_UPDATE; | ||
1635 | } | ||
1636 | } else if (IS_MOUSE_DRAG(button)) { | ||
1637 | if (!ui->hpencil && | ||
1638 | tx >= 0 && tx < w && ty >= 0 && ty < w && | ||
1639 | abs(tx - ui->ohx) == abs(ty - ui->ohy)) { | ||
1640 | ui->odn = abs(tx - ui->ohx) + 1; | ||
1641 | ui->odx = (tx < ui->ohx ? -1 : +1); | ||
1642 | ui->ody = (ty < ui->ohy ? -1 : +1); | ||
1643 | } else { | ||
1644 | ui->odx = ui->ody = 0; | ||
1645 | ui->odn = 1; | ||
1646 | } | ||
1647 | return MOVE_UI_UPDATE; | ||
1648 | } | ||
1649 | |||
1650 | if (IS_CURSOR_MOVE(button)) { | ||
1651 | int cx = find_in_sequence(state->sequence, w, ui->hx); | ||
1652 | int cy = find_in_sequence(state->sequence, w, ui->hy); | ||
1653 | move_cursor(button, &cx, &cy, w, w, false, NULL); | ||
1654 | ui->hx = state->sequence[cx]; | ||
1655 | ui->hy = state->sequence[cy]; | ||
1656 | ui->hshow = true; | ||
1657 | ui->hcursor = true; | ||
1658 | ui->ohx = cx; | ||
1659 | ui->ohy = cy; | ||
1660 | ui->odx = ui->ody = 0; | ||
1661 | ui->odn = 1; | ||
1662 | return MOVE_UI_UPDATE; | ||
1663 | } | ||
1664 | if (ui->hshow && | ||
1665 | (button == CURSOR_SELECT)) { | ||
1666 | ui->hpencil = !ui->hpencil; | ||
1667 | ui->hcursor = true; | ||
1668 | return MOVE_UI_UPDATE; | ||
1669 | } | ||
1670 | |||
1671 | if (ui->hshow && | ||
1672 | ((ISCHAR(button) && FROMCHAR(button, state->par.id) <= w) || | ||
1673 | button == CURSOR_SELECT2 || button == '\b')) { | ||
1674 | int n = FROMCHAR(button, state->par.id); | ||
1675 | int i, buflen; | ||
1676 | char *movebuf; | ||
1677 | |||
1678 | if (button == CURSOR_SELECT2 || button == '\b') | ||
1679 | n = 0; | ||
1680 | |||
1681 | for (i = 0; i < ui->odn; i++) { | ||
1682 | int x = state->sequence[ui->ohx + i*ui->odx]; | ||
1683 | int y = state->sequence[ui->ohy + i*ui->ody]; | ||
1684 | int index = y*w+x; | ||
1685 | |||
1686 | /* | ||
1687 | * Can't make pencil marks in a filled square. This can only | ||
1688 | * become highlighted if we're using cursor keys. | ||
1689 | */ | ||
1690 | if (ui->hpencil && state->grid[index]) | ||
1691 | return NULL; | ||
1692 | |||
1693 | /* | ||
1694 | * Can't do anything to an immutable square. Exception: | ||
1695 | * trying to set it to what it already was is OK (so that | ||
1696 | * multifilling can set a whole diagonal to a without | ||
1697 | * having to detour round the one immutable square in the | ||
1698 | * middle that already said a). | ||
1699 | */ | ||
1700 | if (!ui->hpencil && state->grid[index] == n) | ||
1701 | /* OK even if it is immutable */; | ||
1702 | else if (state->common->immutable[index]) | ||
1703 | return NULL; | ||
1704 | } | ||
1705 | |||
1706 | movebuf = snewn(80 * ui->odn, char); | ||
1707 | buflen = sprintf(movebuf, "%c%d,%d,%d", | ||
1708 | (char)(ui->hpencil && n > 0 ? 'P' : 'R'), | ||
1709 | ui->hx, ui->hy, n); | ||
1710 | for (i = 1; i < ui->odn; i++) { | ||
1711 | assert(buflen < i*80); | ||
1712 | buflen += sprintf(movebuf + buflen, "+%d,%d", | ||
1713 | state->sequence[ui->ohx + i*ui->odx], | ||
1714 | state->sequence[ui->ohy + i*ui->ody]); | ||
1715 | } | ||
1716 | movebuf = sresize(movebuf, buflen+1, char); | ||
1717 | |||
1718 | /* | ||
1719 | * Hide the highlight after a keypress, if it was mouse- | ||
1720 | * generated. Also, don't hide it if this move has changed | ||
1721 | * pencil marks and the user preference says not to hide the | ||
1722 | * highlight in that situation. | ||
1723 | */ | ||
1724 | if (!ui->hcursor && !(ui->hpencil && ui->pencil_keep_highlight)) | ||
1725 | ui->hshow = false; | ||
1726 | |||
1727 | return movebuf; | ||
1728 | } | ||
1729 | |||
1730 | if (button == 'M' || button == 'm') | ||
1731 | return dupstr("M"); | ||
1732 | |||
1733 | return NULL; | ||
1734 | } | ||
1735 | |||
1736 | static game_state *execute_move(const game_state *from, const char *move) | ||
1737 | { | ||
1738 | int w = from->par.w, a = w*w; | ||
1739 | game_state *ret; | ||
1740 | int x, y, i, j, n, pos; | ||
1741 | |||
1742 | if (move[0] == 'S') { | ||
1743 | ret = dup_game(from); | ||
1744 | ret->completed = ret->cheated = true; | ||
1745 | |||
1746 | for (i = 0; i < a; i++) { | ||
1747 | if (!ISCHAR(move[i+1]) || FROMCHAR(move[i+1], from->par.id) > w) { | ||
1748 | free_game(ret); | ||
1749 | return NULL; | ||
1750 | } | ||
1751 | ret->grid[i] = FROMCHAR(move[i+1], from->par.id); | ||
1752 | ret->pencil[i] = 0; | ||
1753 | } | ||
1754 | |||
1755 | if (move[a+1] != '\0') { | ||
1756 | free_game(ret); | ||
1757 | return NULL; | ||
1758 | } | ||
1759 | |||
1760 | return ret; | ||
1761 | } else if ((move[0] == 'P' || move[0] == 'R') && | ||
1762 | sscanf(move+1, "%d,%d,%d%n", &x, &y, &n, &pos) == 3 && | ||
1763 | n >= 0 && n <= w) { | ||
1764 | const char *mp = move + 1 + pos; | ||
1765 | bool pencil = (move[0] == 'P'); | ||
1766 | ret = dup_game(from); | ||
1767 | |||
1768 | while (1) { | ||
1769 | if (x < 0 || x >= w || y < 0 || y >= w) { | ||
1770 | free_game(ret); | ||
1771 | return NULL; | ||
1772 | } | ||
1773 | if (from->common->immutable[y*w+x] && | ||
1774 | !(!pencil && from->grid[y*w+x] == n)) | ||
1775 | return NULL; | ||
1776 | |||
1777 | if (move[0] == 'P' && n > 0) { | ||
1778 | ret->pencil[y*w+x] ^= 1 << n; | ||
1779 | } else { | ||
1780 | ret->grid[y*w+x] = n; | ||
1781 | ret->pencil[y*w+x] = 0; | ||
1782 | } | ||
1783 | |||
1784 | if (!*mp) | ||
1785 | break; | ||
1786 | |||
1787 | if (*mp != '+') | ||
1788 | return NULL; | ||
1789 | if (sscanf(mp, "+%d,%d%n", &x, &y, &pos) < 2) | ||
1790 | return NULL; | ||
1791 | mp += pos; | ||
1792 | } | ||
1793 | |||
1794 | if (!ret->completed && !check_errors(ret, NULL)) | ||
1795 | ret->completed = true; | ||
1796 | |||
1797 | return ret; | ||
1798 | } else if (move[0] == 'M') { | ||
1799 | /* | ||
1800 | * Fill in absolutely all pencil marks everywhere. (I | ||
1801 | * wouldn't use this for actual play, but it's a handy | ||
1802 | * starting point when following through a set of | ||
1803 | * diagnostics output by the standalone solver.) | ||
1804 | */ | ||
1805 | ret = dup_game(from); | ||
1806 | for (i = 0; i < a; i++) { | ||
1807 | if (!ret->grid[i]) | ||
1808 | ret->pencil[i] = (1 << (w+1)) - (1 << 1); | ||
1809 | } | ||
1810 | return ret; | ||
1811 | } else if (move[0] == 'D' && | ||
1812 | sscanf(move+1, "%d,%d", &x, &y) == 2) { | ||
1813 | /* | ||
1814 | * Reorder the rows and columns so that digit x is in position | ||
1815 | * y. | ||
1816 | */ | ||
1817 | ret = dup_game(from); | ||
1818 | for (i = j = 0; i < w; i++) { | ||
1819 | if (i == y) { | ||
1820 | ret->sequence[i] = x; | ||
1821 | } else { | ||
1822 | if (from->sequence[j] == x) | ||
1823 | j++; | ||
1824 | ret->sequence[i] = from->sequence[j++]; | ||
1825 | } | ||
1826 | } | ||
1827 | /* | ||
1828 | * Eliminate any obsoleted dividers. | ||
1829 | */ | ||
1830 | for (x = 0; x < w; x++) { | ||
1831 | int i = ret->sequence[x]; | ||
1832 | int j = (x+1 < w ? ret->sequence[x+1] : -1); | ||
1833 | if (ret->dividers[i] != j) | ||
1834 | ret->dividers[i] = -1; | ||
1835 | } | ||
1836 | return ret; | ||
1837 | } else if (move[0] == 'V' && | ||
1838 | sscanf(move+1, "%d,%d", &i, &j) == 2) { | ||
1839 | ret = dup_game(from); | ||
1840 | if (ret->dividers[i] == j) | ||
1841 | ret->dividers[i] = -1; | ||
1842 | else | ||
1843 | ret->dividers[i] = j; | ||
1844 | return ret; | ||
1845 | } else | ||
1846 | return NULL; /* couldn't parse move string */ | ||
1847 | } | ||
1848 | |||
1849 | /* ---------------------------------------------------------------------- | ||
1850 | * Drawing routines. | ||
1851 | */ | ||
1852 | |||
1853 | #define SIZE(w) ((w) * TILESIZE + 2*BORDER + LEGEND) | ||
1854 | |||
1855 | static void game_compute_size(const game_params *params, int tilesize, | ||
1856 | const game_ui *ui, int *x, int *y) | ||
1857 | { | ||
1858 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | ||
1859 | struct { int tilesize; } ads, *ds = &ads; | ||
1860 | ads.tilesize = tilesize; | ||
1861 | |||
1862 | *x = *y = SIZE(params->w); | ||
1863 | } | ||
1864 | |||
1865 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
1866 | const game_params *params, int tilesize) | ||
1867 | { | ||
1868 | ds->tilesize = tilesize; | ||
1869 | } | ||
1870 | |||
1871 | static float *game_colours(frontend *fe, int *ncolours) | ||
1872 | { | ||
1873 | float *ret = snewn(3 * NCOLOURS, float); | ||
1874 | |||
1875 | frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); | ||
1876 | |||
1877 | ret[COL_GRID * 3 + 0] = 0.0F; | ||
1878 | ret[COL_GRID * 3 + 1] = 0.0F; | ||
1879 | ret[COL_GRID * 3 + 2] = 0.0F; | ||
1880 | |||
1881 | ret[COL_USER * 3 + 0] = 0.0F; | ||
1882 | ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1]; | ||
1883 | ret[COL_USER * 3 + 2] = 0.0F; | ||
1884 | |||
1885 | ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0]; | ||
1886 | ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1]; | ||
1887 | ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2]; | ||
1888 | |||
1889 | ret[COL_ERROR * 3 + 0] = 1.0F; | ||
1890 | ret[COL_ERROR * 3 + 1] = 0.0F; | ||
1891 | ret[COL_ERROR * 3 + 2] = 0.0F; | ||
1892 | |||
1893 | ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; | ||
1894 | ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; | ||
1895 | ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; | ||
1896 | |||
1897 | ret[COL_DIAGONAL * 3 + 0] = 0.95F * ret[COL_BACKGROUND * 3 + 0]; | ||
1898 | ret[COL_DIAGONAL * 3 + 1] = 0.95F * ret[COL_BACKGROUND * 3 + 1]; | ||
1899 | ret[COL_DIAGONAL * 3 + 2] = 0.95F * ret[COL_BACKGROUND * 3 + 2]; | ||
1900 | |||
1901 | *ncolours = NCOLOURS; | ||
1902 | return ret; | ||
1903 | } | ||
1904 | |||
1905 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | ||
1906 | { | ||
1907 | int w = state->par.w, a = w*w; | ||
1908 | struct game_drawstate *ds = snew(struct game_drawstate); | ||
1909 | int i; | ||
1910 | |||
1911 | ds->w = w; | ||
1912 | ds->par = state->par; /* structure copy */ | ||
1913 | ds->tilesize = 0; | ||
1914 | ds->started = false; | ||
1915 | ds->tiles = snewn(a, long); | ||
1916 | ds->legend = snewn(w, long); | ||
1917 | ds->pencil = snewn(a, long); | ||
1918 | ds->errors = snewn(a, long); | ||
1919 | ds->sequence = snewn(a, digit); | ||
1920 | for (i = 0; i < a; i++) | ||
1921 | ds->tiles[i] = ds->pencil[i] = -1; | ||
1922 | for (i = 0; i < w; i++) | ||
1923 | ds->legend[i] = -1; | ||
1924 | ds->errtmp = snewn(a, long); | ||
1925 | |||
1926 | return ds; | ||
1927 | } | ||
1928 | |||
1929 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
1930 | { | ||
1931 | sfree(ds->tiles); | ||
1932 | sfree(ds->pencil); | ||
1933 | sfree(ds->errors); | ||
1934 | sfree(ds->errtmp); | ||
1935 | sfree(ds->sequence); | ||
1936 | sfree(ds); | ||
1937 | } | ||
1938 | |||
1939 | static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, long tile, | ||
1940 | long pencil, long error) | ||
1941 | { | ||
1942 | int w = ds->w /* , a = w*w */; | ||
1943 | int tx, ty, tw, th; | ||
1944 | int cx, cy, cw, ch; | ||
1945 | char str[64]; | ||
1946 | |||
1947 | tx = BORDER + LEGEND + x * TILESIZE + 1; | ||
1948 | ty = BORDER + LEGEND + y * TILESIZE + 1; | ||
1949 | |||
1950 | cx = tx; | ||
1951 | cy = ty; | ||
1952 | cw = tw = TILESIZE-1; | ||
1953 | ch = th = TILESIZE-1; | ||
1954 | |||
1955 | if (tile & DF_LEGEND) { | ||
1956 | cx += TILESIZE/10; | ||
1957 | cy += TILESIZE/10; | ||
1958 | cw -= TILESIZE/5; | ||
1959 | ch -= TILESIZE/5; | ||
1960 | tile |= DF_IMMUTABLE; | ||
1961 | } | ||
1962 | |||
1963 | clip(dr, cx, cy, cw, ch); | ||
1964 | |||
1965 | /* background needs erasing */ | ||
1966 | draw_rect(dr, cx, cy, cw, ch, | ||
1967 | (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : | ||
1968 | (x == y) ? COL_DIAGONAL : COL_BACKGROUND); | ||
1969 | |||
1970 | /* dividers */ | ||
1971 | if (tile & DF_DIVIDER_TOP) | ||
1972 | draw_rect(dr, cx, cy, cw, 1, COL_GRID); | ||
1973 | if (tile & DF_DIVIDER_BOT) | ||
1974 | draw_rect(dr, cx, cy+ch-1, cw, 1, COL_GRID); | ||
1975 | if (tile & DF_DIVIDER_LEFT) | ||
1976 | draw_rect(dr, cx, cy, 1, ch, COL_GRID); | ||
1977 | if (tile & DF_DIVIDER_RIGHT) | ||
1978 | draw_rect(dr, cx+cw-1, cy, 1, ch, COL_GRID); | ||
1979 | |||
1980 | /* pencil-mode highlight */ | ||
1981 | if (tile & DF_HIGHLIGHT_PENCIL) { | ||
1982 | int coords[6]; | ||
1983 | coords[0] = cx; | ||
1984 | coords[1] = cy; | ||
1985 | coords[2] = cx+cw/2; | ||
1986 | coords[3] = cy; | ||
1987 | coords[4] = cx; | ||
1988 | coords[5] = cy+ch/2; | ||
1989 | draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); | ||
1990 | } | ||
1991 | |||
1992 | /* new number needs drawing? */ | ||
1993 | if (tile & DF_DIGIT_MASK) { | ||
1994 | str[1] = '\0'; | ||
1995 | str[0] = TOCHAR(tile & DF_DIGIT_MASK, ds->par.id); | ||
1996 | draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2, | ||
1997 | FONT_VARIABLE, TILESIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, | ||
1998 | (error & EF_LATIN) ? COL_ERROR : | ||
1999 | (tile & DF_IMMUTABLE) ? COL_GRID : COL_USER, str); | ||
2000 | |||
2001 | if (error & EF_LEFT_MASK) { | ||
2002 | int a = (error >> (EF_LEFT_SHIFT+2*EF_DIGIT_SHIFT))&EF_DIGIT_MASK; | ||
2003 | int b = (error >> (EF_LEFT_SHIFT+1*EF_DIGIT_SHIFT))&EF_DIGIT_MASK; | ||
2004 | int c = (error >> (EF_LEFT_SHIFT ))&EF_DIGIT_MASK; | ||
2005 | char buf[10]; | ||
2006 | sprintf(buf, "(%c%c)%c", TOCHAR(a, ds->par.id), | ||
2007 | TOCHAR(b, ds->par.id), TOCHAR(c, ds->par.id)); | ||
2008 | draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/6, | ||
2009 | FONT_VARIABLE, TILESIZE/6, ALIGN_VCENTRE | ALIGN_HCENTRE, | ||
2010 | COL_ERROR, buf); | ||
2011 | } | ||
2012 | if (error & EF_RIGHT_MASK) { | ||
2013 | int a = (error >> (EF_RIGHT_SHIFT+2*EF_DIGIT_SHIFT))&EF_DIGIT_MASK; | ||
2014 | int b = (error >> (EF_RIGHT_SHIFT+1*EF_DIGIT_SHIFT))&EF_DIGIT_MASK; | ||
2015 | int c = (error >> (EF_RIGHT_SHIFT ))&EF_DIGIT_MASK; | ||
2016 | char buf[10]; | ||
2017 | sprintf(buf, "%c(%c%c)", TOCHAR(a, ds->par.id), | ||
2018 | TOCHAR(b, ds->par.id), TOCHAR(c, ds->par.id)); | ||
2019 | draw_text(dr, tx + TILESIZE/2, ty + TILESIZE - TILESIZE/6, | ||
2020 | FONT_VARIABLE, TILESIZE/6, ALIGN_VCENTRE | ALIGN_HCENTRE, | ||
2021 | COL_ERROR, buf); | ||
2022 | } | ||
2023 | } else { | ||
2024 | int i, j, npencil; | ||
2025 | int pl, pr, pt, pb; | ||
2026 | float bestsize; | ||
2027 | int pw, ph, minph, pbest, fontsize; | ||
2028 | |||
2029 | /* Count the pencil marks required. */ | ||
2030 | for (i = 1, npencil = 0; i <= w; i++) | ||
2031 | if (pencil & (1 << i)) | ||
2032 | npencil++; | ||
2033 | if (npencil) { | ||
2034 | |||
2035 | minph = 2; | ||
2036 | |||
2037 | /* | ||
2038 | * Determine the bounding rectangle within which we're going | ||
2039 | * to put the pencil marks. | ||
2040 | */ | ||
2041 | /* Start with the whole square */ | ||
2042 | pl = tx + GRIDEXTRA; | ||
2043 | pr = pl + TILESIZE - GRIDEXTRA; | ||
2044 | pt = ty + GRIDEXTRA; | ||
2045 | pb = pt + TILESIZE - GRIDEXTRA; | ||
2046 | |||
2047 | /* | ||
2048 | * We arrange our pencil marks in a grid layout, with | ||
2049 | * the number of rows and columns adjusted to allow the | ||
2050 | * maximum font size. | ||
2051 | * | ||
2052 | * So now we work out what the grid size ought to be. | ||
2053 | */ | ||
2054 | bestsize = 0.0; | ||
2055 | pbest = 0; | ||
2056 | /* Minimum */ | ||
2057 | for (pw = 3; pw < max(npencil,4); pw++) { | ||
2058 | float fw, fh, fs; | ||
2059 | |||
2060 | ph = (npencil + pw - 1) / pw; | ||
2061 | ph = max(ph, minph); | ||
2062 | fw = (pr - pl) / (float)pw; | ||
2063 | fh = (pb - pt) / (float)ph; | ||
2064 | fs = min(fw, fh); | ||
2065 | if (fs > bestsize) { | ||
2066 | bestsize = fs; | ||
2067 | pbest = pw; | ||
2068 | } | ||
2069 | } | ||
2070 | assert(pbest > 0); | ||
2071 | pw = pbest; | ||
2072 | ph = (npencil + pw - 1) / pw; | ||
2073 | ph = max(ph, minph); | ||
2074 | |||
2075 | /* | ||
2076 | * Now we've got our grid dimensions, work out the pixel | ||
2077 | * size of a grid element, and round it to the nearest | ||
2078 | * pixel. (We don't want rounding errors to make the | ||
2079 | * grid look uneven at low pixel sizes.) | ||
2080 | */ | ||
2081 | fontsize = min((pr - pl) / pw, (pb - pt) / ph); | ||
2082 | |||
2083 | /* | ||
2084 | * Centre the resulting figure in the square. | ||
2085 | */ | ||
2086 | pl = tx + (TILESIZE - fontsize * pw) / 2; | ||
2087 | pt = ty + (TILESIZE - fontsize * ph) / 2; | ||
2088 | |||
2089 | /* | ||
2090 | * Now actually draw the pencil marks. | ||
2091 | */ | ||
2092 | for (i = 1, j = 0; i <= w; i++) | ||
2093 | if (pencil & (1 << i)) { | ||
2094 | int dx = j % pw, dy = j / pw; | ||
2095 | |||
2096 | str[1] = '\0'; | ||
2097 | str[0] = TOCHAR(i, ds->par.id); | ||
2098 | draw_text(dr, pl + fontsize * (2*dx+1) / 2, | ||
2099 | pt + fontsize * (2*dy+1) / 2, | ||
2100 | FONT_VARIABLE, fontsize, | ||
2101 | ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str); | ||
2102 | j++; | ||
2103 | } | ||
2104 | } | ||
2105 | } | ||
2106 | |||
2107 | unclip(dr); | ||
2108 | |||
2109 | draw_update(dr, cx, cy, cw, ch); | ||
2110 | } | ||
2111 | |||
2112 | static void game_redraw(drawing *dr, game_drawstate *ds, | ||
2113 | const game_state *oldstate, const game_state *state, | ||
2114 | int dir, const game_ui *ui, | ||
2115 | float animtime, float flashtime) | ||
2116 | { | ||
2117 | int w = state->par.w /*, a = w*w */; | ||
2118 | int x, y, i, j; | ||
2119 | |||
2120 | if (!ds->started) { | ||
2121 | /* | ||
2122 | * Big containing rectangle. | ||
2123 | */ | ||
2124 | draw_rect(dr, COORD(0) - GRIDEXTRA, COORD(0) - GRIDEXTRA, | ||
2125 | w*TILESIZE+1+GRIDEXTRA*2, w*TILESIZE+1+GRIDEXTRA*2, | ||
2126 | COL_GRID); | ||
2127 | |||
2128 | draw_update(dr, 0, 0, SIZE(w), SIZE(w)); | ||
2129 | |||
2130 | ds->started = true; | ||
2131 | } | ||
2132 | |||
2133 | check_errors(state, ds->errtmp); | ||
2134 | |||
2135 | /* | ||
2136 | * Construct a modified version of state->sequence which takes | ||
2137 | * into account an unfinished drag operation. | ||
2138 | */ | ||
2139 | if (ui->drag) { | ||
2140 | x = ui->dragnum; | ||
2141 | y = ui->dragpos; | ||
2142 | } else { | ||
2143 | x = y = -1; | ||
2144 | } | ||
2145 | for (i = j = 0; i < w; i++) { | ||
2146 | if (i == y) { | ||
2147 | ds->sequence[i] = x; | ||
2148 | } else { | ||
2149 | if (state->sequence[j] == x) | ||
2150 | j++; | ||
2151 | ds->sequence[i] = state->sequence[j++]; | ||
2152 | } | ||
2153 | } | ||
2154 | |||
2155 | /* | ||
2156 | * Draw the table legend. | ||
2157 | */ | ||
2158 | for (x = 0; x < w; x++) { | ||
2159 | int sx = ds->sequence[x]; | ||
2160 | long tile = (sx+1) | DF_LEGEND; | ||
2161 | if (ds->legend[x] != tile) { | ||
2162 | ds->legend[x] = tile; | ||
2163 | draw_tile(dr, ds, -1, x, tile, 0, 0); | ||
2164 | draw_tile(dr, ds, x, -1, tile, 0, 0); | ||
2165 | } | ||
2166 | } | ||
2167 | |||
2168 | for (y = 0; y < w; y++) { | ||
2169 | int sy = ds->sequence[y]; | ||
2170 | for (x = 0; x < w; x++) { | ||
2171 | long tile = 0L, pencil = 0L, error; | ||
2172 | int sx = ds->sequence[x]; | ||
2173 | |||
2174 | if (state->grid[sy*w+sx]) | ||
2175 | tile = state->grid[sy*w+sx]; | ||
2176 | else | ||
2177 | pencil = (long)state->pencil[sy*w+sx]; | ||
2178 | |||
2179 | if (state->common->immutable[sy*w+sx]) | ||
2180 | tile |= DF_IMMUTABLE; | ||
2181 | |||
2182 | if ((ui->drag == 5 && ui->dragnum == sy) || | ||
2183 | (ui->drag == 6 && ui->dragnum == sx)) { | ||
2184 | tile |= DF_HIGHLIGHT; | ||
2185 | } else if (ui->hshow) { | ||
2186 | int i = abs(x - ui->ohx); | ||
2187 | bool highlight = false; | ||
2188 | if (ui->odn > 1) { | ||
2189 | /* | ||
2190 | * When a diagonal multifill selection is shown, | ||
2191 | * we show it in its original grid position | ||
2192 | * regardless of in-progress row/col drags. Moving | ||
2193 | * every square about would be horrible. | ||
2194 | */ | ||
2195 | if (i >= 0 && i < ui->odn && | ||
2196 | x == ui->ohx + i*ui->odx && | ||
2197 | y == ui->ohy + i*ui->ody) | ||
2198 | highlight = true; | ||
2199 | } else { | ||
2200 | /* | ||
2201 | * For a single square, we move its highlight | ||
2202 | * around with the drag. | ||
2203 | */ | ||
2204 | highlight = (ui->hx == sx && ui->hy == sy); | ||
2205 | } | ||
2206 | if (highlight) | ||
2207 | tile |= (ui->hpencil ? DF_HIGHLIGHT_PENCIL : DF_HIGHLIGHT); | ||
2208 | } | ||
2209 | |||
2210 | if (flashtime > 0 && | ||
2211 | (flashtime <= FLASH_TIME/3 || | ||
2212 | flashtime >= FLASH_TIME*2/3)) | ||
2213 | tile |= DF_HIGHLIGHT; /* completion flash */ | ||
2214 | |||
2215 | if (y <= 0 || state->dividers[ds->sequence[y-1]] == sy) | ||
2216 | tile |= DF_DIVIDER_TOP; | ||
2217 | if (y+1 >= w || state->dividers[sy] == ds->sequence[y+1]) | ||
2218 | tile |= DF_DIVIDER_BOT; | ||
2219 | if (x <= 0 || state->dividers[ds->sequence[x-1]] == sx) | ||
2220 | tile |= DF_DIVIDER_LEFT; | ||
2221 | if (x+1 >= w || state->dividers[sx] == ds->sequence[x+1]) | ||
2222 | tile |= DF_DIVIDER_RIGHT; | ||
2223 | |||
2224 | error = ds->errtmp[sy*w+sx]; | ||
2225 | |||
2226 | if (ds->tiles[y*w+x] != tile || | ||
2227 | ds->pencil[y*w+x] != pencil || | ||
2228 | ds->errors[y*w+x] != error) { | ||
2229 | ds->tiles[y*w+x] = tile; | ||
2230 | ds->pencil[y*w+x] = pencil; | ||
2231 | ds->errors[y*w+x] = error; | ||
2232 | draw_tile(dr, ds, x, y, tile, pencil, error); | ||
2233 | } | ||
2234 | } | ||
2235 | } | ||
2236 | } | ||
2237 | |||
2238 | static float game_anim_length(const game_state *oldstate, | ||
2239 | const game_state *newstate, int dir, game_ui *ui) | ||
2240 | { | ||
2241 | return 0.0F; | ||
2242 | } | ||
2243 | |||
2244 | static float game_flash_length(const game_state *oldstate, | ||
2245 | const game_state *newstate, int dir, game_ui *ui) | ||
2246 | { | ||
2247 | if (!oldstate->completed && newstate->completed && | ||
2248 | !oldstate->cheated && !newstate->cheated) | ||
2249 | return FLASH_TIME; | ||
2250 | return 0.0F; | ||
2251 | } | ||
2252 | |||
2253 | static void game_get_cursor_location(const game_ui *ui, | ||
2254 | const game_drawstate *ds, | ||
2255 | const game_state *state, | ||
2256 | const game_params *params, | ||
2257 | int *x, int *y, int *w, int *h) | ||
2258 | { | ||
2259 | } | ||
2260 | |||
2261 | static int game_status(const game_state *state) | ||
2262 | { | ||
2263 | return state->completed ? +1 : 0; | ||
2264 | } | ||
2265 | |||
2266 | static bool game_timing_state(const game_state *state, game_ui *ui) | ||
2267 | { | ||
2268 | if (state->completed) | ||
2269 | return false; | ||
2270 | return true; | ||
2271 | } | ||
2272 | |||
2273 | static void game_print_size(const game_params *params, const game_ui *ui, | ||
2274 | float *x, float *y) | ||
2275 | { | ||
2276 | int pw, ph; | ||
2277 | |||
2278 | /* | ||
2279 | * We use 9mm squares by default, like Solo. | ||
2280 | */ | ||
2281 | game_compute_size(params, 900, ui, &pw, &ph); | ||
2282 | *x = pw / 100.0F; | ||
2283 | *y = ph / 100.0F; | ||
2284 | } | ||
2285 | |||
2286 | static void game_print(drawing *dr, const game_state *state, const game_ui *ui, | ||
2287 | int tilesize) | ||
2288 | { | ||
2289 | int w = state->par.w; | ||
2290 | int ink = print_mono_colour(dr, 0); | ||
2291 | int x, y; | ||
2292 | |||
2293 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | ||
2294 | game_drawstate ads, *ds = &ads; | ||
2295 | game_set_size(dr, ds, NULL, tilesize); | ||
2296 | |||
2297 | /* | ||
2298 | * Border. | ||
2299 | */ | ||
2300 | print_line_width(dr, 3 * TILESIZE / 40); | ||
2301 | draw_rect_outline(dr, BORDER + LEGEND, BORDER + LEGEND, | ||
2302 | w*TILESIZE, w*TILESIZE, ink); | ||
2303 | |||
2304 | /* | ||
2305 | * Legend on table. | ||
2306 | */ | ||
2307 | for (x = 0; x < w; x++) { | ||
2308 | char str[2]; | ||
2309 | str[1] = '\0'; | ||
2310 | str[0] = TOCHAR(x+1, state->par.id); | ||
2311 | draw_text(dr, BORDER+LEGEND + x*TILESIZE + TILESIZE/2, | ||
2312 | BORDER + TILESIZE/2, | ||
2313 | FONT_VARIABLE, TILESIZE/2, | ||
2314 | ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); | ||
2315 | draw_text(dr, BORDER + TILESIZE/2, | ||
2316 | BORDER+LEGEND + x*TILESIZE + TILESIZE/2, | ||
2317 | FONT_VARIABLE, TILESIZE/2, | ||
2318 | ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); | ||
2319 | } | ||
2320 | |||
2321 | /* | ||
2322 | * Main grid. | ||
2323 | */ | ||
2324 | for (x = 1; x < w; x++) { | ||
2325 | print_line_width(dr, TILESIZE / 40); | ||
2326 | draw_line(dr, BORDER+LEGEND+x*TILESIZE, BORDER+LEGEND, | ||
2327 | BORDER+LEGEND+x*TILESIZE, BORDER+LEGEND+w*TILESIZE, ink); | ||
2328 | } | ||
2329 | for (y = 1; y < w; y++) { | ||
2330 | print_line_width(dr, TILESIZE / 40); | ||
2331 | draw_line(dr, BORDER+LEGEND, BORDER+LEGEND+y*TILESIZE, | ||
2332 | BORDER+LEGEND+w*TILESIZE, BORDER+LEGEND+y*TILESIZE, ink); | ||
2333 | } | ||
2334 | |||
2335 | /* | ||
2336 | * Numbers. | ||
2337 | */ | ||
2338 | for (y = 0; y < w; y++) | ||
2339 | for (x = 0; x < w; x++) | ||
2340 | if (state->grid[y*w+x]) { | ||
2341 | char str[2]; | ||
2342 | str[1] = '\0'; | ||
2343 | str[0] = TOCHAR(state->grid[y*w+x], state->par.id); | ||
2344 | draw_text(dr, BORDER+LEGEND + x*TILESIZE + TILESIZE/2, | ||
2345 | BORDER+LEGEND + y*TILESIZE + TILESIZE/2, | ||
2346 | FONT_VARIABLE, TILESIZE/2, | ||
2347 | ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); | ||
2348 | } | ||
2349 | } | ||
2350 | |||
2351 | #ifdef COMBINED | ||
2352 | #define thegame group | ||
2353 | #endif | ||
2354 | |||
2355 | const struct game thegame = { | ||
2356 | "Group", NULL, NULL, | ||
2357 | default_params, | ||
2358 | game_fetch_preset, NULL, | ||
2359 | decode_params, | ||
2360 | encode_params, | ||
2361 | free_params, | ||
2362 | dup_params, | ||
2363 | true, game_configure, custom_params, | ||
2364 | validate_params, | ||
2365 | new_game_desc, | ||
2366 | validate_desc, | ||
2367 | new_game, | ||
2368 | dup_game, | ||
2369 | free_game, | ||
2370 | true, solve_game, | ||
2371 | true, game_can_format_as_text_now, game_text_format, | ||
2372 | get_prefs, set_prefs, | ||
2373 | new_ui, | ||
2374 | free_ui, | ||
2375 | NULL, /* encode_ui */ | ||
2376 | NULL, /* decode_ui */ | ||
2377 | NULL, /* game_request_keys */ | ||
2378 | game_changed_state, | ||
2379 | current_key_label, | ||
2380 | interpret_move, | ||
2381 | execute_move, | ||
2382 | PREFERRED_TILESIZE, game_compute_size, game_set_size, | ||
2383 | game_colours, | ||
2384 | game_new_drawstate, | ||
2385 | game_free_drawstate, | ||
2386 | game_redraw, | ||
2387 | game_anim_length, | ||
2388 | game_flash_length, | ||
2389 | game_get_cursor_location, | ||
2390 | game_status, | ||
2391 | true, false, game_print_size, game_print, | ||
2392 | false, /* wants_statusbar */ | ||
2393 | false, game_timing_state, | ||
2394 | REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */ | ||
2395 | }; | ||
2396 | |||
2397 | #ifdef STANDALONE_SOLVER | ||
2398 | |||
2399 | #include <stdarg.h> | ||
2400 | |||
2401 | int main(int argc, char **argv) | ||
2402 | { | ||
2403 | game_params *p; | ||
2404 | game_state *s; | ||
2405 | char *id = NULL, *desc; | ||
2406 | const char *err; | ||
2407 | digit *grid; | ||
2408 | bool grade = false; | ||
2409 | int ret, diff; | ||
2410 | bool really_show_working = false; | ||
2411 | |||
2412 | while (--argc > 0) { | ||
2413 | char *p = *++argv; | ||
2414 | if (!strcmp(p, "-v")) { | ||
2415 | really_show_working = true; | ||
2416 | } else if (!strcmp(p, "-g")) { | ||
2417 | grade = true; | ||
2418 | } else if (*p == '-') { | ||
2419 | fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); | ||
2420 | return 1; | ||
2421 | } else { | ||
2422 | id = p; | ||
2423 | } | ||
2424 | } | ||
2425 | |||
2426 | if (!id) { | ||
2427 | fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]); | ||
2428 | return 1; | ||
2429 | } | ||
2430 | |||
2431 | desc = strchr(id, ':'); | ||
2432 | if (!desc) { | ||
2433 | fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); | ||
2434 | return 1; | ||
2435 | } | ||
2436 | *desc++ = '\0'; | ||
2437 | |||
2438 | p = default_params(); | ||
2439 | decode_params(p, id); | ||
2440 | err = validate_desc(p, desc); | ||
2441 | if (err) { | ||
2442 | fprintf(stderr, "%s: %s\n", argv[0], err); | ||
2443 | return 1; | ||
2444 | } | ||
2445 | s = new_game(NULL, p, desc); | ||
2446 | |||
2447 | grid = snewn(p->w * p->w, digit); | ||
2448 | |||
2449 | /* | ||
2450 | * When solving a Normal puzzle, we don't want to bother the | ||
2451 | * user with Hard-level deductions. For this reason, we grade | ||
2452 | * the puzzle internally before doing anything else. | ||
2453 | */ | ||
2454 | ret = -1; /* placate optimiser */ | ||
2455 | solver_show_working = 0; | ||
2456 | for (diff = 0; diff < DIFFCOUNT; diff++) { | ||
2457 | memcpy(grid, s->grid, p->w * p->w); | ||
2458 | ret = solver(&s->par, grid, diff); | ||
2459 | if (ret <= diff) | ||
2460 | break; | ||
2461 | } | ||
2462 | |||
2463 | if (diff == DIFFCOUNT) { | ||
2464 | if (really_show_working) { | ||
2465 | solver_show_working = true; | ||
2466 | memcpy(grid, s->grid, p->w * p->w); | ||
2467 | ret = solver(&s->par, grid, DIFFCOUNT - 1); | ||
2468 | } | ||
2469 | if (grade) | ||
2470 | printf("Difficulty rating: ambiguous\n"); | ||
2471 | else | ||
2472 | printf("Unable to find a unique solution\n"); | ||
2473 | } else { | ||
2474 | if (grade) { | ||
2475 | if (ret == diff_impossible) | ||
2476 | printf("Difficulty rating: impossible (no solution exists)\n"); | ||
2477 | else | ||
2478 | printf("Difficulty rating: %s\n", group_diffnames[ret]); | ||
2479 | } else { | ||
2480 | solver_show_working = really_show_working; | ||
2481 | memcpy(grid, s->grid, p->w * p->w); | ||
2482 | ret = solver(&s->par, grid, diff); | ||
2483 | if (ret != diff) | ||
2484 | printf("Puzzle is inconsistent\n"); | ||
2485 | else { | ||
2486 | memcpy(s->grid, grid, p->w * p->w); | ||
2487 | fputs(game_text_format(s), stdout); | ||
2488 | } | ||
2489 | } | ||
2490 | } | ||
2491 | |||
2492 | return 0; | ||
2493 | } | ||
2494 | |||
2495 | #endif | ||
2496 | |||
2497 | /* vim: set shiftwidth=4 tabstop=8: */ | ||