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