diff options
Diffstat (limited to 'apps/plugins/puzzles/src/dominosa.c')
-rw-r--r-- | apps/plugins/puzzles/src/dominosa.c | 1748 |
1 files changed, 1748 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/dominosa.c b/apps/plugins/puzzles/src/dominosa.c new file mode 100644 index 0000000000..c86ba19dfa --- /dev/null +++ b/apps/plugins/puzzles/src/dominosa.c | |||
@@ -0,0 +1,1748 @@ | |||
1 | /* | ||
2 | * dominosa.c: Domino jigsaw puzzle. Aim to place one of every | ||
3 | * possible domino within a rectangle in such a way that the number | ||
4 | * on each square matches the provided clue. | ||
5 | */ | ||
6 | |||
7 | /* | ||
8 | * TODO: | ||
9 | * | ||
10 | * - improve solver so as to use more interesting forms of | ||
11 | * deduction | ||
12 | * | ||
13 | * * rule out a domino placement if it would divide an unfilled | ||
14 | * region such that at least one resulting region had an odd | ||
15 | * area | ||
16 | * + use b.f.s. to determine the area of an unfilled region | ||
17 | * + a square is unfilled iff it has at least two possible | ||
18 | * placements, and two adjacent unfilled squares are part | ||
19 | * of the same region iff the domino placement joining | ||
20 | * them is possible | ||
21 | * | ||
22 | * * perhaps set analysis | ||
23 | * + look at all unclaimed squares containing a given number | ||
24 | * + for each one, find the set of possible numbers that it | ||
25 | * can connect to (i.e. each neighbouring tile such that | ||
26 | * the placement between it and that neighbour has not yet | ||
27 | * been ruled out) | ||
28 | * + now proceed similarly to Solo set analysis: try to find | ||
29 | * a subset of the squares such that the union of their | ||
30 | * possible numbers is the same size as the subset. If so, | ||
31 | * rule out those possible numbers for all other squares. | ||
32 | * * important wrinkle: the double dominoes complicate | ||
33 | * matters. Connecting a number to itself uses up _two_ | ||
34 | * of the unclaimed squares containing a number. Thus, | ||
35 | * when finding the initial subset we must never | ||
36 | * include two adjacent squares; and also, when ruling | ||
37 | * things out after finding the subset, we must be | ||
38 | * careful that we don't rule out precisely the domino | ||
39 | * placement that was _included_ in our set! | ||
40 | */ | ||
41 | |||
42 | #include <stdio.h> | ||
43 | #include <stdlib.h> | ||
44 | #include <string.h> | ||
45 | #include <assert.h> | ||
46 | #include <ctype.h> | ||
47 | #include <math.h> | ||
48 | |||
49 | #include "puzzles.h" | ||
50 | |||
51 | /* nth triangular number */ | ||
52 | #define TRI(n) ( (n) * ((n) + 1) / 2 ) | ||
53 | /* number of dominoes for value n */ | ||
54 | #define DCOUNT(n) TRI((n)+1) | ||
55 | /* map a pair of numbers to a unique domino index from 0 upwards. */ | ||
56 | #define DINDEX(n1,n2) ( TRI(max(n1,n2)) + min(n1,n2) ) | ||
57 | |||
58 | #define FLASH_TIME 0.13F | ||
59 | |||
60 | enum { | ||
61 | COL_BACKGROUND, | ||
62 | COL_TEXT, | ||
63 | COL_DOMINO, | ||
64 | COL_DOMINOCLASH, | ||
65 | COL_DOMINOTEXT, | ||
66 | COL_EDGE, | ||
67 | COL_HIGHLIGHT_1, | ||
68 | COL_HIGHLIGHT_2, | ||
69 | NCOLOURS | ||
70 | }; | ||
71 | |||
72 | struct game_params { | ||
73 | int n; | ||
74 | int unique; | ||
75 | }; | ||
76 | |||
77 | struct game_numbers { | ||
78 | int refcount; | ||
79 | int *numbers; /* h x w */ | ||
80 | }; | ||
81 | |||
82 | #define EDGE_L 0x100 | ||
83 | #define EDGE_R 0x200 | ||
84 | #define EDGE_T 0x400 | ||
85 | #define EDGE_B 0x800 | ||
86 | |||
87 | struct game_state { | ||
88 | game_params params; | ||
89 | int w, h; | ||
90 | struct game_numbers *numbers; | ||
91 | int *grid; | ||
92 | unsigned short *edges; /* h x w */ | ||
93 | int completed, cheated; | ||
94 | }; | ||
95 | |||
96 | static game_params *default_params(void) | ||
97 | { | ||
98 | game_params *ret = snew(game_params); | ||
99 | |||
100 | ret->n = 6; | ||
101 | ret->unique = TRUE; | ||
102 | |||
103 | return ret; | ||
104 | } | ||
105 | |||
106 | static int game_fetch_preset(int i, char **name, game_params **params) | ||
107 | { | ||
108 | game_params *ret; | ||
109 | int n; | ||
110 | char buf[80]; | ||
111 | |||
112 | switch (i) { | ||
113 | case 0: n = 3; break; | ||
114 | case 1: n = 4; break; | ||
115 | case 2: n = 5; break; | ||
116 | case 3: n = 6; break; | ||
117 | case 4: n = 7; break; | ||
118 | case 5: n = 8; break; | ||
119 | case 6: n = 9; break; | ||
120 | default: return FALSE; | ||
121 | } | ||
122 | |||
123 | sprintf(buf, "Up to double-%d", n); | ||
124 | *name = dupstr(buf); | ||
125 | |||
126 | *params = ret = snew(game_params); | ||
127 | ret->n = n; | ||
128 | ret->unique = TRUE; | ||
129 | |||
130 | return TRUE; | ||
131 | } | ||
132 | |||
133 | static void free_params(game_params *params) | ||
134 | { | ||
135 | sfree(params); | ||
136 | } | ||
137 | |||
138 | static game_params *dup_params(const game_params *params) | ||
139 | { | ||
140 | game_params *ret = snew(game_params); | ||
141 | *ret = *params; /* structure copy */ | ||
142 | return ret; | ||
143 | } | ||
144 | |||
145 | static void decode_params(game_params *params, char const *string) | ||
146 | { | ||
147 | params->n = atoi(string); | ||
148 | while (*string && isdigit((unsigned char)*string)) string++; | ||
149 | if (*string == 'a') | ||
150 | params->unique = FALSE; | ||
151 | } | ||
152 | |||
153 | static char *encode_params(const game_params *params, int full) | ||
154 | { | ||
155 | char buf[80]; | ||
156 | sprintf(buf, "%d", params->n); | ||
157 | if (full && !params->unique) | ||
158 | strcat(buf, "a"); | ||
159 | return dupstr(buf); | ||
160 | } | ||
161 | |||
162 | static config_item *game_configure(const game_params *params) | ||
163 | { | ||
164 | config_item *ret; | ||
165 | char buf[80]; | ||
166 | |||
167 | ret = snewn(3, config_item); | ||
168 | |||
169 | ret[0].name = "Maximum number on dominoes"; | ||
170 | ret[0].type = C_STRING; | ||
171 | sprintf(buf, "%d", params->n); | ||
172 | ret[0].sval = dupstr(buf); | ||
173 | ret[0].ival = 0; | ||
174 | |||
175 | ret[1].name = "Ensure unique solution"; | ||
176 | ret[1].type = C_BOOLEAN; | ||
177 | ret[1].sval = NULL; | ||
178 | ret[1].ival = params->unique; | ||
179 | |||
180 | ret[2].name = NULL; | ||
181 | ret[2].type = C_END; | ||
182 | ret[2].sval = NULL; | ||
183 | ret[2].ival = 0; | ||
184 | |||
185 | return ret; | ||
186 | } | ||
187 | |||
188 | static game_params *custom_params(const config_item *cfg) | ||
189 | { | ||
190 | game_params *ret = snew(game_params); | ||
191 | |||
192 | ret->n = atoi(cfg[0].sval); | ||
193 | ret->unique = cfg[1].ival; | ||
194 | |||
195 | return ret; | ||
196 | } | ||
197 | |||
198 | static char *validate_params(const game_params *params, int full) | ||
199 | { | ||
200 | if (params->n < 1) | ||
201 | return "Maximum face number must be at least one"; | ||
202 | return NULL; | ||
203 | } | ||
204 | |||
205 | /* ---------------------------------------------------------------------- | ||
206 | * Solver. | ||
207 | */ | ||
208 | |||
209 | static int find_overlaps(int w, int h, int placement, int *set) | ||
210 | { | ||
211 | int x, y, n; | ||
212 | |||
213 | n = 0; /* number of returned placements */ | ||
214 | |||
215 | x = placement / 2; | ||
216 | y = x / w; | ||
217 | x %= w; | ||
218 | |||
219 | if (placement & 1) { | ||
220 | /* | ||
221 | * Horizontal domino, indexed by its left end. | ||
222 | */ | ||
223 | if (x > 0) | ||
224 | set[n++] = placement-2; /* horizontal domino to the left */ | ||
225 | if (y > 0) | ||
226 | set[n++] = placement-2*w-1;/* vertical domino above left side */ | ||
227 | if (y+1 < h) | ||
228 | set[n++] = placement-1; /* vertical domino below left side */ | ||
229 | if (x+2 < w) | ||
230 | set[n++] = placement+2; /* horizontal domino to the right */ | ||
231 | if (y > 0) | ||
232 | set[n++] = placement-2*w+2-1;/* vertical domino above right side */ | ||
233 | if (y+1 < h) | ||
234 | set[n++] = placement+2-1; /* vertical domino below right side */ | ||
235 | } else { | ||
236 | /* | ||
237 | * Vertical domino, indexed by its top end. | ||
238 | */ | ||
239 | if (y > 0) | ||
240 | set[n++] = placement-2*w; /* vertical domino above */ | ||
241 | if (x > 0) | ||
242 | set[n++] = placement-2+1; /* horizontal domino left of top */ | ||
243 | if (x+1 < w) | ||
244 | set[n++] = placement+1; /* horizontal domino right of top */ | ||
245 | if (y+2 < h) | ||
246 | set[n++] = placement+2*w; /* vertical domino below */ | ||
247 | if (x > 0) | ||
248 | set[n++] = placement-2+2*w+1;/* horizontal domino left of bottom */ | ||
249 | if (x+1 < w) | ||
250 | set[n++] = placement+2*w+1;/* horizontal domino right of bottom */ | ||
251 | } | ||
252 | |||
253 | return n; | ||
254 | } | ||
255 | |||
256 | /* | ||
257 | * Returns 0, 1 or 2 for number of solutions. 2 means `any number | ||
258 | * more than one', or more accurately `we were unable to prove | ||
259 | * there was only one'. | ||
260 | * | ||
261 | * Outputs in a `placements' array, indexed the same way as the one | ||
262 | * within this function (see below); entries in there are <0 for a | ||
263 | * placement ruled out, 0 for an uncertain placement, and 1 for a | ||
264 | * definite one. | ||
265 | */ | ||
266 | static int solver(int w, int h, int n, int *grid, int *output) | ||
267 | { | ||
268 | int wh = w*h, dc = DCOUNT(n); | ||
269 | int *placements, *heads; | ||
270 | int i, j, x, y, ret; | ||
271 | |||
272 | /* | ||
273 | * This array has one entry for every possible domino | ||
274 | * placement. Vertical placements are indexed by their top | ||
275 | * half, at (y*w+x)*2; horizontal placements are indexed by | ||
276 | * their left half at (y*w+x)*2+1. | ||
277 | * | ||
278 | * This array is used to link domino placements together into | ||
279 | * linked lists, so that we can track all the possible | ||
280 | * placements of each different domino. It's also used as a | ||
281 | * quick means of looking up an individual placement to see | ||
282 | * whether we still think it's possible. Actual values stored | ||
283 | * in this array are -2 (placement not possible at all), -1 | ||
284 | * (end of list), or the array index of the next item. | ||
285 | * | ||
286 | * Oh, and -3 for `not even valid', used for array indices | ||
287 | * which don't even represent a plausible placement. | ||
288 | */ | ||
289 | placements = snewn(2*wh, int); | ||
290 | for (i = 0; i < 2*wh; i++) | ||
291 | placements[i] = -3; /* not even valid */ | ||
292 | |||
293 | /* | ||
294 | * This array has one entry for every domino, and it is an | ||
295 | * index into `placements' denoting the head of the placement | ||
296 | * list for that domino. | ||
297 | */ | ||
298 | heads = snewn(dc, int); | ||
299 | for (i = 0; i < dc; i++) | ||
300 | heads[i] = -1; | ||
301 | |||
302 | /* | ||
303 | * Set up the initial possibility lists by scanning the grid. | ||
304 | */ | ||
305 | for (y = 0; y < h-1; y++) | ||
306 | for (x = 0; x < w; x++) { | ||
307 | int di = DINDEX(grid[y*w+x], grid[(y+1)*w+x]); | ||
308 | placements[(y*w+x)*2] = heads[di]; | ||
309 | heads[di] = (y*w+x)*2; | ||
310 | } | ||
311 | for (y = 0; y < h; y++) | ||
312 | for (x = 0; x < w-1; x++) { | ||
313 | int di = DINDEX(grid[y*w+x], grid[y*w+(x+1)]); | ||
314 | placements[(y*w+x)*2+1] = heads[di]; | ||
315 | heads[di] = (y*w+x)*2+1; | ||
316 | } | ||
317 | |||
318 | #ifdef SOLVER_DIAGNOSTICS | ||
319 | printf("before solver:\n"); | ||
320 | for (i = 0; i <= n; i++) | ||
321 | for (j = 0; j <= i; j++) { | ||
322 | int k, m; | ||
323 | m = 0; | ||
324 | printf("%2d [%d %d]:", DINDEX(i, j), i, j); | ||
325 | for (k = heads[DINDEX(i,j)]; k >= 0; k = placements[k]) | ||
326 | printf(" %3d [%d,%d,%c]", k, k/2%w, k/2/w, k%2?'h':'v'); | ||
327 | printf("\n"); | ||
328 | } | ||
329 | #endif | ||
330 | |||
331 | while (1) { | ||
332 | int done_something = FALSE; | ||
333 | |||
334 | /* | ||
335 | * For each domino, look at its possible placements, and | ||
336 | * for each placement consider the placements (of any | ||
337 | * domino) it overlaps. Any placement overlapped by all | ||
338 | * placements of this domino can be ruled out. | ||
339 | * | ||
340 | * Each domino placement overlaps only six others, so we | ||
341 | * need not do serious set theory to work this out. | ||
342 | */ | ||
343 | for (i = 0; i < dc; i++) { | ||
344 | int permset[6], permlen = 0, p; | ||
345 | |||
346 | |||
347 | if (heads[i] == -1) { /* no placement for this domino */ | ||
348 | ret = 0; /* therefore puzzle is impossible */ | ||
349 | goto done; | ||
350 | } | ||
351 | for (j = heads[i]; j >= 0; j = placements[j]) { | ||
352 | assert(placements[j] != -2); | ||
353 | |||
354 | if (j == heads[i]) { | ||
355 | permlen = find_overlaps(w, h, j, permset); | ||
356 | } else { | ||
357 | int tempset[6], templen, m, n, k; | ||
358 | |||
359 | templen = find_overlaps(w, h, j, tempset); | ||
360 | |||
361 | /* | ||
362 | * Pathetically primitive set intersection | ||
363 | * algorithm, which I'm only getting away with | ||
364 | * because I know my sets are bounded by a very | ||
365 | * small size. | ||
366 | */ | ||
367 | for (m = n = 0; m < permlen; m++) { | ||
368 | for (k = 0; k < templen; k++) | ||
369 | if (tempset[k] == permset[m]) | ||
370 | break; | ||
371 | if (k < templen) | ||
372 | permset[n++] = permset[m]; | ||
373 | } | ||
374 | permlen = n; | ||
375 | } | ||
376 | } | ||
377 | for (p = 0; p < permlen; p++) { | ||
378 | j = permset[p]; | ||
379 | if (placements[j] != -2) { | ||
380 | int p1, p2, di; | ||
381 | |||
382 | done_something = TRUE; | ||
383 | |||
384 | /* | ||
385 | * Rule out this placement. First find what | ||
386 | * domino it is... | ||
387 | */ | ||
388 | p1 = j / 2; | ||
389 | p2 = (j & 1) ? p1 + 1 : p1 + w; | ||
390 | di = DINDEX(grid[p1], grid[p2]); | ||
391 | #ifdef SOLVER_DIAGNOSTICS | ||
392 | printf("considering domino %d: ruling out placement %d" | ||
393 | " for %d\n", i, j, di); | ||
394 | #endif | ||
395 | |||
396 | /* | ||
397 | * ... then walk that domino's placement list, | ||
398 | * removing this placement when we find it. | ||
399 | */ | ||
400 | if (heads[di] == j) | ||
401 | heads[di] = placements[j]; | ||
402 | else { | ||
403 | int k = heads[di]; | ||
404 | while (placements[k] != -1 && placements[k] != j) | ||
405 | k = placements[k]; | ||
406 | assert(placements[k] == j); | ||
407 | placements[k] = placements[j]; | ||
408 | } | ||
409 | placements[j] = -2; | ||
410 | } | ||
411 | } | ||
412 | } | ||
413 | |||
414 | /* | ||
415 | * For each square, look at the available placements | ||
416 | * involving that square. If all of them are for the same | ||
417 | * domino, then rule out any placements for that domino | ||
418 | * _not_ involving this square. | ||
419 | */ | ||
420 | for (i = 0; i < wh; i++) { | ||
421 | int list[4], k, n, adi; | ||
422 | |||
423 | x = i % w; | ||
424 | y = i / w; | ||
425 | |||
426 | j = 0; | ||
427 | if (x > 0) | ||
428 | list[j++] = 2*(i-1)+1; | ||
429 | if (x+1 < w) | ||
430 | list[j++] = 2*i+1; | ||
431 | if (y > 0) | ||
432 | list[j++] = 2*(i-w); | ||
433 | if (y+1 < h) | ||
434 | list[j++] = 2*i; | ||
435 | |||
436 | for (n = k = 0; k < j; k++) | ||
437 | if (placements[list[k]] >= -1) | ||
438 | list[n++] = list[k]; | ||
439 | |||
440 | adi = -1; | ||
441 | |||
442 | for (j = 0; j < n; j++) { | ||
443 | int p1, p2, di; | ||
444 | k = list[j]; | ||
445 | |||
446 | p1 = k / 2; | ||
447 | p2 = (k & 1) ? p1 + 1 : p1 + w; | ||
448 | di = DINDEX(grid[p1], grid[p2]); | ||
449 | |||
450 | if (adi == -1) | ||
451 | adi = di; | ||
452 | if (adi != di) | ||
453 | break; | ||
454 | } | ||
455 | |||
456 | if (j == n) { | ||
457 | int nn; | ||
458 | |||
459 | assert(adi >= 0); | ||
460 | /* | ||
461 | * We've found something. All viable placements | ||
462 | * involving this square are for domino `adi'. If | ||
463 | * the current placement list for that domino is | ||
464 | * longer than n, reduce it to precisely this | ||
465 | * placement list and we've done something. | ||
466 | */ | ||
467 | nn = 0; | ||
468 | for (k = heads[adi]; k >= 0; k = placements[k]) | ||
469 | nn++; | ||
470 | if (nn > n) { | ||
471 | done_something = TRUE; | ||
472 | #ifdef SOLVER_DIAGNOSTICS | ||
473 | printf("considering square %d,%d: reducing placements " | ||
474 | "of domino %d\n", x, y, adi); | ||
475 | #endif | ||
476 | /* | ||
477 | * Set all other placements on the list to | ||
478 | * impossible. | ||
479 | */ | ||
480 | k = heads[adi]; | ||
481 | while (k >= 0) { | ||
482 | int tmp = placements[k]; | ||
483 | placements[k] = -2; | ||
484 | k = tmp; | ||
485 | } | ||
486 | /* | ||
487 | * Set up the new list. | ||
488 | */ | ||
489 | heads[adi] = list[0]; | ||
490 | for (k = 0; k < n; k++) | ||
491 | placements[list[k]] = (k+1 == n ? -1 : list[k+1]); | ||
492 | } | ||
493 | } | ||
494 | } | ||
495 | |||
496 | if (!done_something) | ||
497 | break; | ||
498 | } | ||
499 | |||
500 | #ifdef SOLVER_DIAGNOSTICS | ||
501 | printf("after solver:\n"); | ||
502 | for (i = 0; i <= n; i++) | ||
503 | for (j = 0; j <= i; j++) { | ||
504 | int k, m; | ||
505 | m = 0; | ||
506 | printf("%2d [%d %d]:", DINDEX(i, j), i, j); | ||
507 | for (k = heads[DINDEX(i,j)]; k >= 0; k = placements[k]) | ||
508 | printf(" %3d [%d,%d,%c]", k, k/2%w, k/2/w, k%2?'h':'v'); | ||
509 | printf("\n"); | ||
510 | } | ||
511 | #endif | ||
512 | |||
513 | ret = 1; | ||
514 | for (i = 0; i < wh*2; i++) { | ||
515 | if (placements[i] == -2) { | ||
516 | if (output) | ||
517 | output[i] = -1; /* ruled out */ | ||
518 | } else if (placements[i] != -3) { | ||
519 | int p1, p2, di; | ||
520 | |||
521 | p1 = i / 2; | ||
522 | p2 = (i & 1) ? p1 + 1 : p1 + w; | ||
523 | di = DINDEX(grid[p1], grid[p2]); | ||
524 | |||
525 | if (i == heads[di] && placements[i] == -1) { | ||
526 | if (output) | ||
527 | output[i] = 1; /* certain */ | ||
528 | } else { | ||
529 | if (output) | ||
530 | output[i] = 0; /* uncertain */ | ||
531 | ret = 2; | ||
532 | } | ||
533 | } | ||
534 | } | ||
535 | |||
536 | done: | ||
537 | /* | ||
538 | * Free working data. | ||
539 | */ | ||
540 | sfree(placements); | ||
541 | sfree(heads); | ||
542 | |||
543 | return ret; | ||
544 | } | ||
545 | |||
546 | /* ---------------------------------------------------------------------- | ||
547 | * End of solver code. | ||
548 | */ | ||
549 | |||
550 | static char *new_game_desc(const game_params *params, random_state *rs, | ||
551 | char **aux, int interactive) | ||
552 | { | ||
553 | int n = params->n, w = n+2, h = n+1, wh = w*h; | ||
554 | int *grid, *grid2, *list; | ||
555 | int i, j, k, len; | ||
556 | char *ret; | ||
557 | |||
558 | /* | ||
559 | * Allocate space in which to lay the grid out. | ||
560 | */ | ||
561 | grid = snewn(wh, int); | ||
562 | grid2 = snewn(wh, int); | ||
563 | list = snewn(2*wh, int); | ||
564 | |||
565 | /* | ||
566 | * I haven't been able to think of any particularly clever | ||
567 | * techniques for generating instances of Dominosa with a | ||
568 | * unique solution. Many of the deductions used in this puzzle | ||
569 | * are based on information involving half the grid at a time | ||
570 | * (`of all the 6s, exactly one is next to a 3'), so a strategy | ||
571 | * of partially solving the grid and then perturbing the place | ||
572 | * where the solver got stuck seems particularly likely to | ||
573 | * accidentally destroy the information which the solver had | ||
574 | * used in getting that far. (Contrast with, say, Mines, in | ||
575 | * which most deductions are local so this is an excellent | ||
576 | * strategy.) | ||
577 | * | ||
578 | * Therefore I resort to the basest of brute force methods: | ||
579 | * generate a random grid, see if it's solvable, throw it away | ||
580 | * and try again if not. My only concession to sophistication | ||
581 | * and cleverness is to at least _try_ not to generate obvious | ||
582 | * 2x2 ambiguous sections (see comment below in the domino- | ||
583 | * flipping section). | ||
584 | * | ||
585 | * During tests performed on 2005-07-15, I found that the brute | ||
586 | * force approach without that tweak had to throw away about 87 | ||
587 | * grids on average (at the default n=6) before finding a | ||
588 | * unique one, or a staggering 379 at n=9; good job the | ||
589 | * generator and solver are fast! When I added the | ||
590 | * ambiguous-section avoidance, those numbers came down to 19 | ||
591 | * and 26 respectively, which is a lot more sensible. | ||
592 | */ | ||
593 | |||
594 | do { | ||
595 | domino_layout_prealloc(w, h, rs, grid, grid2, list); | ||
596 | |||
597 | /* | ||
598 | * Now we have a complete layout covering the whole | ||
599 | * rectangle with dominoes. So shuffle the actual domino | ||
600 | * values and fill the rectangle with numbers. | ||
601 | */ | ||
602 | k = 0; | ||
603 | for (i = 0; i <= params->n; i++) | ||
604 | for (j = 0; j <= i; j++) { | ||
605 | list[k++] = i; | ||
606 | list[k++] = j; | ||
607 | } | ||
608 | shuffle(list, k/2, 2*sizeof(*list), rs); | ||
609 | j = 0; | ||
610 | for (i = 0; i < wh; i++) | ||
611 | if (grid[i] > i) { | ||
612 | /* Optionally flip the domino round. */ | ||
613 | int flip = -1; | ||
614 | |||
615 | if (params->unique) { | ||
616 | int t1, t2; | ||
617 | /* | ||
618 | * If we're after a unique solution, we can do | ||
619 | * something here to improve the chances. If | ||
620 | * we're placing a domino so that it forms a | ||
621 | * 2x2 rectangle with one we've already placed, | ||
622 | * and if that domino and this one share a | ||
623 | * number, we can try not to put them so that | ||
624 | * the identical numbers are diagonally | ||
625 | * separated, because that automatically causes | ||
626 | * non-uniqueness: | ||
627 | * | ||
628 | * +---+ +-+-+ | ||
629 | * |2 3| |2|3| | ||
630 | * +---+ -> | | | | ||
631 | * |4 2| |4|2| | ||
632 | * +---+ +-+-+ | ||
633 | */ | ||
634 | t1 = i; | ||
635 | t2 = grid[i]; | ||
636 | if (t2 == t1 + w) { /* this domino is vertical */ | ||
637 | if (t1 % w > 0 &&/* and not on the left hand edge */ | ||
638 | grid[t1-1] == t2-1 &&/* alongside one to left */ | ||
639 | (grid2[t1-1] == list[j] || /* and has a number */ | ||
640 | grid2[t1-1] == list[j+1] || /* in common */ | ||
641 | grid2[t2-1] == list[j] || | ||
642 | grid2[t2-1] == list[j+1])) { | ||
643 | if (grid2[t1-1] == list[j] || | ||
644 | grid2[t2-1] == list[j+1]) | ||
645 | flip = 0; | ||
646 | else | ||
647 | flip = 1; | ||
648 | } | ||
649 | } else { /* this domino is horizontal */ | ||
650 | if (t1 / w > 0 &&/* and not on the top edge */ | ||
651 | grid[t1-w] == t2-w &&/* alongside one above */ | ||
652 | (grid2[t1-w] == list[j] || /* and has a number */ | ||
653 | grid2[t1-w] == list[j+1] || /* in common */ | ||
654 | grid2[t2-w] == list[j] || | ||
655 | grid2[t2-w] == list[j+1])) { | ||
656 | if (grid2[t1-w] == list[j] || | ||
657 | grid2[t2-w] == list[j+1]) | ||
658 | flip = 0; | ||
659 | else | ||
660 | flip = 1; | ||
661 | } | ||
662 | } | ||
663 | } | ||
664 | |||
665 | if (flip < 0) | ||
666 | flip = random_upto(rs, 2); | ||
667 | |||
668 | grid2[i] = list[j + flip]; | ||
669 | grid2[grid[i]] = list[j + 1 - flip]; | ||
670 | j += 2; | ||
671 | } | ||
672 | assert(j == k); | ||
673 | } while (params->unique && solver(w, h, n, grid2, NULL) > 1); | ||
674 | |||
675 | #ifdef GENERATION_DIAGNOSTICS | ||
676 | for (j = 0; j < h; j++) { | ||
677 | for (i = 0; i < w; i++) { | ||
678 | putchar('0' + grid2[j*w+i]); | ||
679 | } | ||
680 | putchar('\n'); | ||
681 | } | ||
682 | putchar('\n'); | ||
683 | #endif | ||
684 | |||
685 | /* | ||
686 | * Encode the resulting game state. | ||
687 | * | ||
688 | * Our encoding is a string of digits. Any number greater than | ||
689 | * 9 is represented by a decimal integer within square | ||
690 | * brackets. We know there are n+2 of every number (it's paired | ||
691 | * with each number from 0 to n inclusive, and one of those is | ||
692 | * itself so that adds another occurrence), so we can work out | ||
693 | * the string length in advance. | ||
694 | */ | ||
695 | |||
696 | /* | ||
697 | * To work out the total length of the decimal encodings of all | ||
698 | * the numbers from 0 to n inclusive: | ||
699 | * - every number has a units digit; total is n+1. | ||
700 | * - all numbers above 9 have a tens digit; total is max(n+1-10,0). | ||
701 | * - all numbers above 99 have a hundreds digit; total is max(n+1-100,0). | ||
702 | * - and so on. | ||
703 | */ | ||
704 | len = n+1; | ||
705 | for (i = 10; i <= n; i *= 10) | ||
706 | len += max(n + 1 - i, 0); | ||
707 | /* Now add two square brackets for each number above 9. */ | ||
708 | len += 2 * max(n + 1 - 10, 0); | ||
709 | /* And multiply by n+2 for the repeated occurrences of each number. */ | ||
710 | len *= n+2; | ||
711 | |||
712 | /* | ||
713 | * Now actually encode the string. | ||
714 | */ | ||
715 | ret = snewn(len+1, char); | ||
716 | j = 0; | ||
717 | for (i = 0; i < wh; i++) { | ||
718 | k = grid2[i]; | ||
719 | if (k < 10) | ||
720 | ret[j++] = '0' + k; | ||
721 | else | ||
722 | j += sprintf(ret+j, "[%d]", k); | ||
723 | assert(j <= len); | ||
724 | } | ||
725 | assert(j == len); | ||
726 | ret[j] = '\0'; | ||
727 | |||
728 | /* | ||
729 | * Encode the solved state as an aux_info. | ||
730 | */ | ||
731 | { | ||
732 | char *auxinfo = snewn(wh+1, char); | ||
733 | |||
734 | for (i = 0; i < wh; i++) { | ||
735 | int v = grid[i]; | ||
736 | auxinfo[i] = (v == i+1 ? 'L' : v == i-1 ? 'R' : | ||
737 | v == i+w ? 'T' : v == i-w ? 'B' : '.'); | ||
738 | } | ||
739 | auxinfo[wh] = '\0'; | ||
740 | |||
741 | *aux = auxinfo; | ||
742 | } | ||
743 | |||
744 | sfree(list); | ||
745 | sfree(grid2); | ||
746 | sfree(grid); | ||
747 | |||
748 | return ret; | ||
749 | } | ||
750 | |||
751 | static char *validate_desc(const game_params *params, const char *desc) | ||
752 | { | ||
753 | int n = params->n, w = n+2, h = n+1, wh = w*h; | ||
754 | int *occurrences; | ||
755 | int i, j; | ||
756 | char *ret; | ||
757 | |||
758 | ret = NULL; | ||
759 | occurrences = snewn(n+1, int); | ||
760 | for (i = 0; i <= n; i++) | ||
761 | occurrences[i] = 0; | ||
762 | |||
763 | for (i = 0; i < wh; i++) { | ||
764 | if (!*desc) { | ||
765 | ret = ret ? ret : "Game description is too short"; | ||
766 | } else { | ||
767 | if (*desc >= '0' && *desc <= '9') | ||
768 | j = *desc++ - '0'; | ||
769 | else if (*desc == '[') { | ||
770 | desc++; | ||
771 | j = atoi(desc); | ||
772 | while (*desc && isdigit((unsigned char)*desc)) desc++; | ||
773 | if (*desc != ']') | ||
774 | ret = ret ? ret : "Missing ']' in game description"; | ||
775 | else | ||
776 | desc++; | ||
777 | } else { | ||
778 | j = -1; | ||
779 | ret = ret ? ret : "Invalid syntax in game description"; | ||
780 | } | ||
781 | if (j < 0 || j > n) | ||
782 | ret = ret ? ret : "Number out of range in game description"; | ||
783 | else | ||
784 | occurrences[j]++; | ||
785 | } | ||
786 | } | ||
787 | |||
788 | if (*desc) | ||
789 | ret = ret ? ret : "Game description is too long"; | ||
790 | |||
791 | if (!ret) { | ||
792 | for (i = 0; i <= n; i++) | ||
793 | if (occurrences[i] != n+2) | ||
794 | ret = "Incorrect number balance in game description"; | ||
795 | } | ||
796 | |||
797 | sfree(occurrences); | ||
798 | |||
799 | return ret; | ||
800 | } | ||
801 | |||
802 | static game_state *new_game(midend *me, const game_params *params, | ||
803 | const char *desc) | ||
804 | { | ||
805 | int n = params->n, w = n+2, h = n+1, wh = w*h; | ||
806 | game_state *state = snew(game_state); | ||
807 | int i, j; | ||
808 | |||
809 | state->params = *params; | ||
810 | state->w = w; | ||
811 | state->h = h; | ||
812 | |||
813 | state->grid = snewn(wh, int); | ||
814 | for (i = 0; i < wh; i++) | ||
815 | state->grid[i] = i; | ||
816 | |||
817 | state->edges = snewn(wh, unsigned short); | ||
818 | for (i = 0; i < wh; i++) | ||
819 | state->edges[i] = 0; | ||
820 | |||
821 | state->numbers = snew(struct game_numbers); | ||
822 | state->numbers->refcount = 1; | ||
823 | state->numbers->numbers = snewn(wh, int); | ||
824 | |||
825 | for (i = 0; i < wh; i++) { | ||
826 | assert(*desc); | ||
827 | if (*desc >= '0' && *desc <= '9') | ||
828 | j = *desc++ - '0'; | ||
829 | else { | ||
830 | assert(*desc == '['); | ||
831 | desc++; | ||
832 | j = atoi(desc); | ||
833 | while (*desc && isdigit((unsigned char)*desc)) desc++; | ||
834 | assert(*desc == ']'); | ||
835 | desc++; | ||
836 | } | ||
837 | assert(j >= 0 && j <= n); | ||
838 | state->numbers->numbers[i] = j; | ||
839 | } | ||
840 | |||
841 | state->completed = state->cheated = FALSE; | ||
842 | |||
843 | return state; | ||
844 | } | ||
845 | |||
846 | static game_state *dup_game(const game_state *state) | ||
847 | { | ||
848 | int n = state->params.n, w = n+2, h = n+1, wh = w*h; | ||
849 | game_state *ret = snew(game_state); | ||
850 | |||
851 | ret->params = state->params; | ||
852 | ret->w = state->w; | ||
853 | ret->h = state->h; | ||
854 | ret->grid = snewn(wh, int); | ||
855 | memcpy(ret->grid, state->grid, wh * sizeof(int)); | ||
856 | ret->edges = snewn(wh, unsigned short); | ||
857 | memcpy(ret->edges, state->edges, wh * sizeof(unsigned short)); | ||
858 | ret->numbers = state->numbers; | ||
859 | ret->numbers->refcount++; | ||
860 | ret->completed = state->completed; | ||
861 | ret->cheated = state->cheated; | ||
862 | |||
863 | return ret; | ||
864 | } | ||
865 | |||
866 | static void free_game(game_state *state) | ||
867 | { | ||
868 | sfree(state->grid); | ||
869 | sfree(state->edges); | ||
870 | if (--state->numbers->refcount <= 0) { | ||
871 | sfree(state->numbers->numbers); | ||
872 | sfree(state->numbers); | ||
873 | } | ||
874 | sfree(state); | ||
875 | } | ||
876 | |||
877 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
878 | const char *aux, char **error) | ||
879 | { | ||
880 | int n = state->params.n, w = n+2, h = n+1, wh = w*h; | ||
881 | int *placements; | ||
882 | char *ret; | ||
883 | int retlen, retsize; | ||
884 | int i, v; | ||
885 | char buf[80]; | ||
886 | int extra; | ||
887 | |||
888 | if (aux) { | ||
889 | retsize = 256; | ||
890 | ret = snewn(retsize, char); | ||
891 | retlen = sprintf(ret, "S"); | ||
892 | |||
893 | for (i = 0; i < wh; i++) { | ||
894 | if (aux[i] == 'L') | ||
895 | extra = sprintf(buf, ";D%d,%d", i, i+1); | ||
896 | else if (aux[i] == 'T') | ||
897 | extra = sprintf(buf, ";D%d,%d", i, i+w); | ||
898 | else | ||
899 | continue; | ||
900 | |||
901 | if (retlen + extra + 1 >= retsize) { | ||
902 | retsize = retlen + extra + 256; | ||
903 | ret = sresize(ret, retsize, char); | ||
904 | } | ||
905 | strcpy(ret + retlen, buf); | ||
906 | retlen += extra; | ||
907 | } | ||
908 | |||
909 | } else { | ||
910 | |||
911 | placements = snewn(wh*2, int); | ||
912 | for (i = 0; i < wh*2; i++) | ||
913 | placements[i] = -3; | ||
914 | solver(w, h, n, state->numbers->numbers, placements); | ||
915 | |||
916 | /* | ||
917 | * First make a pass putting in edges for -1, then make a pass | ||
918 | * putting in dominoes for +1. | ||
919 | */ | ||
920 | retsize = 256; | ||
921 | ret = snewn(retsize, char); | ||
922 | retlen = sprintf(ret, "S"); | ||
923 | |||
924 | for (v = -1; v <= +1; v += 2) | ||
925 | for (i = 0; i < wh*2; i++) | ||
926 | if (placements[i] == v) { | ||
927 | int p1 = i / 2; | ||
928 | int p2 = (i & 1) ? p1+1 : p1+w; | ||
929 | |||
930 | extra = sprintf(buf, ";%c%d,%d", | ||
931 | (int)(v==-1 ? 'E' : 'D'), p1, p2); | ||
932 | |||
933 | if (retlen + extra + 1 >= retsize) { | ||
934 | retsize = retlen + extra + 256; | ||
935 | ret = sresize(ret, retsize, char); | ||
936 | } | ||
937 | strcpy(ret + retlen, buf); | ||
938 | retlen += extra; | ||
939 | } | ||
940 | |||
941 | sfree(placements); | ||
942 | } | ||
943 | |||
944 | return ret; | ||
945 | } | ||
946 | |||
947 | static int game_can_format_as_text_now(const game_params *params) | ||
948 | { | ||
949 | return params->n < 1000; | ||
950 | } | ||
951 | |||
952 | static void draw_domino(char *board, int start, char corner, | ||
953 | int dshort, int nshort, char cshort, | ||
954 | int dlong, int nlong, char clong) | ||
955 | { | ||
956 | int go_short = nshort*dshort, go_long = nlong*dlong, i; | ||
957 | |||
958 | board[start] = corner; | ||
959 | board[start + go_short] = corner; | ||
960 | board[start + go_long] = corner; | ||
961 | board[start + go_short + go_long] = corner; | ||
962 | |||
963 | for (i = 1; i < nshort; ++i) { | ||
964 | int j = start + i*dshort, k = start + i*dshort + go_long; | ||
965 | if (board[j] != corner) board[j] = cshort; | ||
966 | if (board[k] != corner) board[k] = cshort; | ||
967 | } | ||
968 | |||
969 | for (i = 1; i < nlong; ++i) { | ||
970 | int j = start + i*dlong, k = start + i*dlong + go_short; | ||
971 | if (board[j] != corner) board[j] = clong; | ||
972 | if (board[k] != corner) board[k] = clong; | ||
973 | } | ||
974 | } | ||
975 | |||
976 | static char *game_text_format(const game_state *state) | ||
977 | { | ||
978 | int w = state->w, h = state->h, r, c; | ||
979 | int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh; | ||
980 | char *board = snewn(len + 1, char); | ||
981 | |||
982 | memset(board, ' ', len); | ||
983 | |||
984 | for (r = 0; r < h; ++r) { | ||
985 | for (c = 0; c < w; ++c) { | ||
986 | int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2; | ||
987 | int i = r*w + c, num = state->numbers->numbers[i]; | ||
988 | |||
989 | if (num < 100) { | ||
990 | board[center] = '0' + num % 10; | ||
991 | if (num >= 10) board[center - 1] = '0' + num / 10; | ||
992 | } else { | ||
993 | board[center+1] = '0' + num % 10; | ||
994 | board[center] = '0' + num / 10 % 10; | ||
995 | board[center-1] = '0' + num / 100; | ||
996 | } | ||
997 | |||
998 | if (state->edges[i] & EDGE_L) board[center - cw/2] = '|'; | ||
999 | if (state->edges[i] & EDGE_R) board[center + cw/2] = '|'; | ||
1000 | if (state->edges[i] & EDGE_T) board[center - gw] = '-'; | ||
1001 | if (state->edges[i] & EDGE_B) board[center + gw] = '-'; | ||
1002 | |||
1003 | if (state->grid[i] == i) continue; /* no domino pairing */ | ||
1004 | if (state->grid[i] < i) continue; /* already done */ | ||
1005 | assert (state->grid[i] == i + 1 || state->grid[i] == i + w); | ||
1006 | if (state->grid[i] == i + 1) | ||
1007 | draw_domino(board, cell, '+', gw, ch, '|', +1, 2*cw, '-'); | ||
1008 | else if (state->grid[i] == i + w) | ||
1009 | draw_domino(board, cell, '+', +1, cw, '-', gw, 2*ch, '|'); | ||
1010 | } | ||
1011 | board[r*ch*gw + gw - 1] = '\n'; | ||
1012 | board[r*ch*gw + gw + gw - 1] = '\n'; | ||
1013 | } | ||
1014 | board[len - 1] = '\n'; | ||
1015 | board[len] = '\0'; | ||
1016 | return board; | ||
1017 | } | ||
1018 | |||
1019 | struct game_ui { | ||
1020 | int cur_x, cur_y, cur_visible, highlight_1, highlight_2; | ||
1021 | }; | ||
1022 | |||
1023 | static game_ui *new_ui(const game_state *state) | ||
1024 | { | ||
1025 | game_ui *ui = snew(game_ui); | ||
1026 | ui->cur_x = ui->cur_y = 0; | ||
1027 | ui->cur_visible = 0; | ||
1028 | ui->highlight_1 = ui->highlight_2 = -1; | ||
1029 | return ui; | ||
1030 | } | ||
1031 | |||
1032 | static void free_ui(game_ui *ui) | ||
1033 | { | ||
1034 | sfree(ui); | ||
1035 | } | ||
1036 | |||
1037 | static char *encode_ui(const game_ui *ui) | ||
1038 | { | ||
1039 | return NULL; | ||
1040 | } | ||
1041 | |||
1042 | static void decode_ui(game_ui *ui, const char *encoding) | ||
1043 | { | ||
1044 | } | ||
1045 | |||
1046 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
1047 | const game_state *newstate) | ||
1048 | { | ||
1049 | if (!oldstate->completed && newstate->completed) | ||
1050 | ui->cur_visible = 0; | ||
1051 | } | ||
1052 | |||
1053 | #define PREFERRED_TILESIZE 32 | ||
1054 | #define TILESIZE (ds->tilesize) | ||
1055 | #define BORDER (TILESIZE * 3 / 4) | ||
1056 | #define DOMINO_GUTTER (TILESIZE / 16) | ||
1057 | #define DOMINO_RADIUS (TILESIZE / 8) | ||
1058 | #define DOMINO_COFFSET (DOMINO_GUTTER + DOMINO_RADIUS) | ||
1059 | #define CURSOR_RADIUS (TILESIZE / 4) | ||
1060 | |||
1061 | #define COORD(x) ( (x) * TILESIZE + BORDER ) | ||
1062 | #define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 ) | ||
1063 | |||
1064 | struct game_drawstate { | ||
1065 | int started; | ||
1066 | int w, h, tilesize; | ||
1067 | unsigned long *visible; | ||
1068 | }; | ||
1069 | |||
1070 | static char *interpret_move(const game_state *state, game_ui *ui, | ||
1071 | const game_drawstate *ds, | ||
1072 | int x, int y, int button) | ||
1073 | { | ||
1074 | int w = state->w, h = state->h; | ||
1075 | char buf[80]; | ||
1076 | |||
1077 | /* | ||
1078 | * A left-click between two numbers toggles a domino covering | ||
1079 | * them. A right-click toggles an edge. | ||
1080 | */ | ||
1081 | if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { | ||
1082 | int tx = FROMCOORD(x), ty = FROMCOORD(y), t = ty*w+tx; | ||
1083 | int dx, dy; | ||
1084 | int d1, d2; | ||
1085 | |||
1086 | if (tx < 0 || tx >= w || ty < 0 || ty >= h) | ||
1087 | return NULL; | ||
1088 | |||
1089 | /* | ||
1090 | * Now we know which square the click was in, decide which | ||
1091 | * edge of the square it was closest to. | ||
1092 | */ | ||
1093 | dx = 2 * (x - COORD(tx)) - TILESIZE; | ||
1094 | dy = 2 * (y - COORD(ty)) - TILESIZE; | ||
1095 | |||
1096 | if (abs(dx) > abs(dy) && dx < 0 && tx > 0) | ||
1097 | d1 = t - 1, d2 = t; /* clicked in right side of domino */ | ||
1098 | else if (abs(dx) > abs(dy) && dx > 0 && tx+1 < w) | ||
1099 | d1 = t, d2 = t + 1; /* clicked in left side of domino */ | ||
1100 | else if (abs(dy) > abs(dx) && dy < 0 && ty > 0) | ||
1101 | d1 = t - w, d2 = t; /* clicked in bottom half of domino */ | ||
1102 | else if (abs(dy) > abs(dx) && dy > 0 && ty+1 < h) | ||
1103 | d1 = t, d2 = t + w; /* clicked in top half of domino */ | ||
1104 | else | ||
1105 | return NULL; | ||
1106 | |||
1107 | /* | ||
1108 | * We can't mark an edge next to any domino. | ||
1109 | */ | ||
1110 | if (button == RIGHT_BUTTON && | ||
1111 | (state->grid[d1] != d1 || state->grid[d2] != d2)) | ||
1112 | return NULL; | ||
1113 | |||
1114 | ui->cur_visible = 0; | ||
1115 | sprintf(buf, "%c%d,%d", (int)(button == RIGHT_BUTTON ? 'E' : 'D'), d1, d2); | ||
1116 | return dupstr(buf); | ||
1117 | } else if (IS_CURSOR_MOVE(button)) { | ||
1118 | ui->cur_visible = 1; | ||
1119 | |||
1120 | move_cursor(button, &ui->cur_x, &ui->cur_y, 2*w-1, 2*h-1, 0); | ||
1121 | |||
1122 | return ""; | ||
1123 | } else if (IS_CURSOR_SELECT(button)) { | ||
1124 | int d1, d2; | ||
1125 | |||
1126 | if (!((ui->cur_x ^ ui->cur_y) & 1)) | ||
1127 | return NULL; /* must have exactly one dimension odd */ | ||
1128 | d1 = (ui->cur_y / 2) * w + (ui->cur_x / 2); | ||
1129 | d2 = ((ui->cur_y+1) / 2) * w + ((ui->cur_x+1) / 2); | ||
1130 | |||
1131 | /* | ||
1132 | * We can't mark an edge next to any domino. | ||
1133 | */ | ||
1134 | if (button == CURSOR_SELECT2 && | ||
1135 | (state->grid[d1] != d1 || state->grid[d2] != d2)) | ||
1136 | return NULL; | ||
1137 | |||
1138 | sprintf(buf, "%c%d,%d", (int)(button == CURSOR_SELECT2 ? 'E' : 'D'), d1, d2); | ||
1139 | return dupstr(buf); | ||
1140 | } else if (isdigit(button)) { | ||
1141 | int n = state->params.n, num = button - '0'; | ||
1142 | if (num > n) { | ||
1143 | return NULL; | ||
1144 | } else if (ui->highlight_1 == num) { | ||
1145 | ui->highlight_1 = -1; | ||
1146 | } else if (ui->highlight_2 == num) { | ||
1147 | ui->highlight_2 = -1; | ||
1148 | } else if (ui->highlight_1 == -1) { | ||
1149 | ui->highlight_1 = num; | ||
1150 | } else if (ui->highlight_2 == -1) { | ||
1151 | ui->highlight_2 = num; | ||
1152 | } else { | ||
1153 | return NULL; | ||
1154 | } | ||
1155 | return ""; | ||
1156 | } | ||
1157 | |||
1158 | return NULL; | ||
1159 | } | ||
1160 | |||
1161 | static game_state *execute_move(const game_state *state, const char *move) | ||
1162 | { | ||
1163 | int n = state->params.n, w = n+2, h = n+1, wh = w*h; | ||
1164 | int d1, d2, d3, p; | ||
1165 | game_state *ret = dup_game(state); | ||
1166 | |||
1167 | while (*move) { | ||
1168 | if (move[0] == 'S') { | ||
1169 | int i; | ||
1170 | |||
1171 | ret->cheated = TRUE; | ||
1172 | |||
1173 | /* | ||
1174 | * Clear the existing edges and domino placements. We | ||
1175 | * expect the S to be followed by other commands. | ||
1176 | */ | ||
1177 | for (i = 0; i < wh; i++) { | ||
1178 | ret->grid[i] = i; | ||
1179 | ret->edges[i] = 0; | ||
1180 | } | ||
1181 | move++; | ||
1182 | } else if (move[0] == 'D' && | ||
1183 | sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 && | ||
1184 | d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2) { | ||
1185 | |||
1186 | /* | ||
1187 | * Toggle domino presence between d1 and d2. | ||
1188 | */ | ||
1189 | if (ret->grid[d1] == d2) { | ||
1190 | assert(ret->grid[d2] == d1); | ||
1191 | ret->grid[d1] = d1; | ||
1192 | ret->grid[d2] = d2; | ||
1193 | } else { | ||
1194 | /* | ||
1195 | * Erase any dominoes that might overlap the new one. | ||
1196 | */ | ||
1197 | d3 = ret->grid[d1]; | ||
1198 | if (d3 != d1) | ||
1199 | ret->grid[d3] = d3; | ||
1200 | d3 = ret->grid[d2]; | ||
1201 | if (d3 != d2) | ||
1202 | ret->grid[d3] = d3; | ||
1203 | /* | ||
1204 | * Place the new one. | ||
1205 | */ | ||
1206 | ret->grid[d1] = d2; | ||
1207 | ret->grid[d2] = d1; | ||
1208 | |||
1209 | /* | ||
1210 | * Destroy any edges lurking around it. | ||
1211 | */ | ||
1212 | if (ret->edges[d1] & EDGE_L) { | ||
1213 | assert(d1 - 1 >= 0); | ||
1214 | ret->edges[d1 - 1] &= ~EDGE_R; | ||
1215 | } | ||
1216 | if (ret->edges[d1] & EDGE_R) { | ||
1217 | assert(d1 + 1 < wh); | ||
1218 | ret->edges[d1 + 1] &= ~EDGE_L; | ||
1219 | } | ||
1220 | if (ret->edges[d1] & EDGE_T) { | ||
1221 | assert(d1 - w >= 0); | ||
1222 | ret->edges[d1 - w] &= ~EDGE_B; | ||
1223 | } | ||
1224 | if (ret->edges[d1] & EDGE_B) { | ||
1225 | assert(d1 + 1 < wh); | ||
1226 | ret->edges[d1 + w] &= ~EDGE_T; | ||
1227 | } | ||
1228 | ret->edges[d1] = 0; | ||
1229 | if (ret->edges[d2] & EDGE_L) { | ||
1230 | assert(d2 - 1 >= 0); | ||
1231 | ret->edges[d2 - 1] &= ~EDGE_R; | ||
1232 | } | ||
1233 | if (ret->edges[d2] & EDGE_R) { | ||
1234 | assert(d2 + 1 < wh); | ||
1235 | ret->edges[d2 + 1] &= ~EDGE_L; | ||
1236 | } | ||
1237 | if (ret->edges[d2] & EDGE_T) { | ||
1238 | assert(d2 - w >= 0); | ||
1239 | ret->edges[d2 - w] &= ~EDGE_B; | ||
1240 | } | ||
1241 | if (ret->edges[d2] & EDGE_B) { | ||
1242 | assert(d2 + 1 < wh); | ||
1243 | ret->edges[d2 + w] &= ~EDGE_T; | ||
1244 | } | ||
1245 | ret->edges[d2] = 0; | ||
1246 | } | ||
1247 | |||
1248 | move += p+1; | ||
1249 | } else if (move[0] == 'E' && | ||
1250 | sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 && | ||
1251 | d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2 && | ||
1252 | ret->grid[d1] == d1 && ret->grid[d2] == d2) { | ||
1253 | |||
1254 | /* | ||
1255 | * Toggle edge presence between d1 and d2. | ||
1256 | */ | ||
1257 | if (d2 == d1 + 1) { | ||
1258 | ret->edges[d1] ^= EDGE_R; | ||
1259 | ret->edges[d2] ^= EDGE_L; | ||
1260 | } else { | ||
1261 | ret->edges[d1] ^= EDGE_B; | ||
1262 | ret->edges[d2] ^= EDGE_T; | ||
1263 | } | ||
1264 | |||
1265 | move += p+1; | ||
1266 | } else { | ||
1267 | free_game(ret); | ||
1268 | return NULL; | ||
1269 | } | ||
1270 | |||
1271 | if (*move) { | ||
1272 | if (*move != ';') { | ||
1273 | free_game(ret); | ||
1274 | return NULL; | ||
1275 | } | ||
1276 | move++; | ||
1277 | } | ||
1278 | } | ||
1279 | |||
1280 | /* | ||
1281 | * After modifying the grid, check completion. | ||
1282 | */ | ||
1283 | if (!ret->completed) { | ||
1284 | int i, ok = 0; | ||
1285 | unsigned char *used = snewn(TRI(n+1), unsigned char); | ||
1286 | |||
1287 | memset(used, 0, TRI(n+1)); | ||
1288 | for (i = 0; i < wh; i++) | ||
1289 | if (ret->grid[i] > i) { | ||
1290 | int n1, n2, di; | ||
1291 | |||
1292 | n1 = ret->numbers->numbers[i]; | ||
1293 | n2 = ret->numbers->numbers[ret->grid[i]]; | ||
1294 | |||
1295 | di = DINDEX(n1, n2); | ||
1296 | assert(di >= 0 && di < TRI(n+1)); | ||
1297 | |||
1298 | if (!used[di]) { | ||
1299 | used[di] = 1; | ||
1300 | ok++; | ||
1301 | } | ||
1302 | } | ||
1303 | |||
1304 | sfree(used); | ||
1305 | if (ok == DCOUNT(n)) | ||
1306 | ret->completed = TRUE; | ||
1307 | } | ||
1308 | |||
1309 | return ret; | ||
1310 | } | ||
1311 | |||
1312 | /* ---------------------------------------------------------------------- | ||
1313 | * Drawing routines. | ||
1314 | */ | ||
1315 | |||
1316 | static void game_compute_size(const game_params *params, int tilesize, | ||
1317 | int *x, int *y) | ||
1318 | { | ||
1319 | int n = params->n, w = n+2, h = n+1; | ||
1320 | |||
1321 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | ||
1322 | struct { int tilesize; } ads, *ds = &ads; | ||
1323 | ads.tilesize = tilesize; | ||
1324 | |||
1325 | *x = w * TILESIZE + 2*BORDER; | ||
1326 | *y = h * TILESIZE + 2*BORDER; | ||
1327 | } | ||
1328 | |||
1329 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
1330 | const game_params *params, int tilesize) | ||
1331 | { | ||
1332 | ds->tilesize = tilesize; | ||
1333 | } | ||
1334 | |||
1335 | static float *game_colours(frontend *fe, int *ncolours) | ||
1336 | { | ||
1337 | float *ret = snewn(3 * NCOLOURS, float); | ||
1338 | |||
1339 | frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); | ||
1340 | |||
1341 | ret[COL_TEXT * 3 + 0] = 0.0F; | ||
1342 | ret[COL_TEXT * 3 + 1] = 0.0F; | ||
1343 | ret[COL_TEXT * 3 + 2] = 0.0F; | ||
1344 | |||
1345 | ret[COL_DOMINO * 3 + 0] = 0.0F; | ||
1346 | ret[COL_DOMINO * 3 + 1] = 0.0F; | ||
1347 | ret[COL_DOMINO * 3 + 2] = 0.0F; | ||
1348 | |||
1349 | ret[COL_DOMINOCLASH * 3 + 0] = 0.5F; | ||
1350 | ret[COL_DOMINOCLASH * 3 + 1] = 0.0F; | ||
1351 | ret[COL_DOMINOCLASH * 3 + 2] = 0.0F; | ||
1352 | |||
1353 | ret[COL_DOMINOTEXT * 3 + 0] = 1.0F; | ||
1354 | ret[COL_DOMINOTEXT * 3 + 1] = 1.0F; | ||
1355 | ret[COL_DOMINOTEXT * 3 + 2] = 1.0F; | ||
1356 | |||
1357 | ret[COL_EDGE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2 / 3; | ||
1358 | ret[COL_EDGE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2 / 3; | ||
1359 | ret[COL_EDGE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2 / 3; | ||
1360 | |||
1361 | ret[COL_HIGHLIGHT_1 * 3 + 0] = 0.85; | ||
1362 | ret[COL_HIGHLIGHT_1 * 3 + 1] = 0.20; | ||
1363 | ret[COL_HIGHLIGHT_1 * 3 + 2] = 0.20; | ||
1364 | |||
1365 | ret[COL_HIGHLIGHT_2 * 3 + 0] = 0.30; | ||
1366 | ret[COL_HIGHLIGHT_2 * 3 + 1] = 0.85; | ||
1367 | ret[COL_HIGHLIGHT_2 * 3 + 2] = 0.20; | ||
1368 | |||
1369 | *ncolours = NCOLOURS; | ||
1370 | return ret; | ||
1371 | } | ||
1372 | |||
1373 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | ||
1374 | { | ||
1375 | struct game_drawstate *ds = snew(struct game_drawstate); | ||
1376 | int i; | ||
1377 | |||
1378 | ds->started = FALSE; | ||
1379 | ds->w = state->w; | ||
1380 | ds->h = state->h; | ||
1381 | ds->visible = snewn(ds->w * ds->h, unsigned long); | ||
1382 | ds->tilesize = 0; /* not decided yet */ | ||
1383 | for (i = 0; i < ds->w * ds->h; i++) | ||
1384 | ds->visible[i] = 0xFFFF; | ||
1385 | |||
1386 | return ds; | ||
1387 | } | ||
1388 | |||
1389 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
1390 | { | ||
1391 | sfree(ds->visible); | ||
1392 | sfree(ds); | ||
1393 | } | ||
1394 | |||
1395 | enum { | ||
1396 | TYPE_L, | ||
1397 | TYPE_R, | ||
1398 | TYPE_T, | ||
1399 | TYPE_B, | ||
1400 | TYPE_BLANK, | ||
1401 | TYPE_MASK = 0x0F | ||
1402 | }; | ||
1403 | |||
1404 | /* These flags must be disjoint with: | ||
1405 | * the above enum (TYPE_*) [0x000 -- 0x00F] | ||
1406 | * EDGE_* [0x100 -- 0xF00] | ||
1407 | * and must fit into an unsigned long (32 bits). | ||
1408 | */ | ||
1409 | #define DF_HIGHLIGHT_1 0x10 | ||
1410 | #define DF_HIGHLIGHT_2 0x20 | ||
1411 | #define DF_FLASH 0x40 | ||
1412 | #define DF_CLASH 0x80 | ||
1413 | |||
1414 | #define DF_CURSOR 0x01000 | ||
1415 | #define DF_CURSOR_USEFUL 0x02000 | ||
1416 | #define DF_CURSOR_XBASE 0x10000 | ||
1417 | #define DF_CURSOR_XMASK 0x30000 | ||
1418 | #define DF_CURSOR_YBASE 0x40000 | ||
1419 | #define DF_CURSOR_YMASK 0xC0000 | ||
1420 | |||
1421 | #define CEDGE_OFF (TILESIZE / 8) | ||
1422 | #define IS_EMPTY(s,x,y) ((s)->grid[(y)*(s)->w+(x)] == ((y)*(s)->w+(x))) | ||
1423 | |||
1424 | static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state, | ||
1425 | int x, int y, int type, int highlight_1, int highlight_2) | ||
1426 | { | ||
1427 | int w = state->w /*, h = state->h */; | ||
1428 | int cx = COORD(x), cy = COORD(y); | ||
1429 | int nc; | ||
1430 | char str[80]; | ||
1431 | int flags; | ||
1432 | |||
1433 | clip(dr, cx, cy, TILESIZE, TILESIZE); | ||
1434 | draw_rect(dr, cx, cy, TILESIZE, TILESIZE, COL_BACKGROUND); | ||
1435 | |||
1436 | flags = type &~ TYPE_MASK; | ||
1437 | type &= TYPE_MASK; | ||
1438 | |||
1439 | if (type != TYPE_BLANK) { | ||
1440 | int i, bg; | ||
1441 | |||
1442 | /* | ||
1443 | * Draw one end of a domino. This is composed of: | ||
1444 | * | ||
1445 | * - two filled circles (rounded corners) | ||
1446 | * - two rectangles | ||
1447 | * - a slight shift in the number | ||
1448 | */ | ||
1449 | |||
1450 | if (flags & DF_CLASH) | ||
1451 | bg = COL_DOMINOCLASH; | ||
1452 | else | ||
1453 | bg = COL_DOMINO; | ||
1454 | nc = COL_DOMINOTEXT; | ||
1455 | |||
1456 | if (flags & DF_FLASH) { | ||
1457 | int tmp = nc; | ||
1458 | nc = bg; | ||
1459 | bg = tmp; | ||
1460 | } | ||
1461 | |||
1462 | if (type == TYPE_L || type == TYPE_T) | ||
1463 | draw_circle(dr, cx+DOMINO_COFFSET, cy+DOMINO_COFFSET, | ||
1464 | DOMINO_RADIUS, bg, bg); | ||
1465 | if (type == TYPE_R || type == TYPE_T) | ||
1466 | draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET, cy+DOMINO_COFFSET, | ||
1467 | DOMINO_RADIUS, bg, bg); | ||
1468 | if (type == TYPE_L || type == TYPE_B) | ||
1469 | draw_circle(dr, cx+DOMINO_COFFSET, cy+TILESIZE-1-DOMINO_COFFSET, | ||
1470 | DOMINO_RADIUS, bg, bg); | ||
1471 | if (type == TYPE_R || type == TYPE_B) | ||
1472 | draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET, | ||
1473 | cy+TILESIZE-1-DOMINO_COFFSET, | ||
1474 | DOMINO_RADIUS, bg, bg); | ||
1475 | |||
1476 | for (i = 0; i < 2; i++) { | ||
1477 | int x1, y1, x2, y2; | ||
1478 | |||
1479 | x1 = cx + (i ? DOMINO_GUTTER : DOMINO_COFFSET); | ||
1480 | y1 = cy + (i ? DOMINO_COFFSET : DOMINO_GUTTER); | ||
1481 | x2 = cx + TILESIZE-1 - (i ? DOMINO_GUTTER : DOMINO_COFFSET); | ||
1482 | y2 = cy + TILESIZE-1 - (i ? DOMINO_COFFSET : DOMINO_GUTTER); | ||
1483 | if (type == TYPE_L) | ||
1484 | x2 = cx + TILESIZE + TILESIZE/16; | ||
1485 | else if (type == TYPE_R) | ||
1486 | x1 = cx - TILESIZE/16; | ||
1487 | else if (type == TYPE_T) | ||
1488 | y2 = cy + TILESIZE + TILESIZE/16; | ||
1489 | else if (type == TYPE_B) | ||
1490 | y1 = cy - TILESIZE/16; | ||
1491 | |||
1492 | draw_rect(dr, x1, y1, x2-x1+1, y2-y1+1, bg); | ||
1493 | } | ||
1494 | } else { | ||
1495 | if (flags & EDGE_T) | ||
1496 | draw_rect(dr, cx+DOMINO_GUTTER, cy, | ||
1497 | TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE); | ||
1498 | if (flags & EDGE_B) | ||
1499 | draw_rect(dr, cx+DOMINO_GUTTER, cy+TILESIZE-1, | ||
1500 | TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE); | ||
1501 | if (flags & EDGE_L) | ||
1502 | draw_rect(dr, cx, cy+DOMINO_GUTTER, | ||
1503 | 1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE); | ||
1504 | if (flags & EDGE_R) | ||
1505 | draw_rect(dr, cx+TILESIZE-1, cy+DOMINO_GUTTER, | ||
1506 | 1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE); | ||
1507 | nc = COL_TEXT; | ||
1508 | } | ||
1509 | |||
1510 | if (flags & DF_CURSOR) { | ||
1511 | int curx = ((flags & DF_CURSOR_XMASK) / DF_CURSOR_XBASE) & 3; | ||
1512 | int cury = ((flags & DF_CURSOR_YMASK) / DF_CURSOR_YBASE) & 3; | ||
1513 | int ox = cx + curx*TILESIZE/2; | ||
1514 | int oy = cy + cury*TILESIZE/2; | ||
1515 | |||
1516 | draw_rect_corners(dr, ox, oy, CURSOR_RADIUS, nc); | ||
1517 | if (flags & DF_CURSOR_USEFUL) | ||
1518 | draw_rect_corners(dr, ox, oy, CURSOR_RADIUS+1, nc); | ||
1519 | } | ||
1520 | |||
1521 | if (flags & DF_HIGHLIGHT_1) { | ||
1522 | nc = COL_HIGHLIGHT_1; | ||
1523 | } else if (flags & DF_HIGHLIGHT_2) { | ||
1524 | nc = COL_HIGHLIGHT_2; | ||
1525 | } | ||
1526 | |||
1527 | sprintf(str, "%d", state->numbers->numbers[y*w+x]); | ||
1528 | draw_text(dr, cx+TILESIZE/2, cy+TILESIZE/2, FONT_VARIABLE, TILESIZE/2, | ||
1529 | ALIGN_HCENTRE | ALIGN_VCENTRE, nc, str); | ||
1530 | |||
1531 | draw_update(dr, cx, cy, TILESIZE, TILESIZE); | ||
1532 | unclip(dr); | ||
1533 | } | ||
1534 | |||
1535 | static void game_redraw(drawing *dr, game_drawstate *ds, | ||
1536 | const game_state *oldstate, const game_state *state, | ||
1537 | int dir, const game_ui *ui, | ||
1538 | float animtime, float flashtime) | ||
1539 | { | ||
1540 | int n = state->params.n, w = state->w, h = state->h, wh = w*h; | ||
1541 | int x, y, i; | ||
1542 | unsigned char *used; | ||
1543 | |||
1544 | if (!ds->started) { | ||
1545 | int pw, ph; | ||
1546 | game_compute_size(&state->params, TILESIZE, &pw, &ph); | ||
1547 | draw_rect(dr, 0, 0, pw, ph, COL_BACKGROUND); | ||
1548 | draw_update(dr, 0, 0, pw, ph); | ||
1549 | ds->started = TRUE; | ||
1550 | } | ||
1551 | |||
1552 | /* | ||
1553 | * See how many dominoes of each type there are, so we can | ||
1554 | * highlight clashes in red. | ||
1555 | */ | ||
1556 | used = snewn(TRI(n+1), unsigned char); | ||
1557 | memset(used, 0, TRI(n+1)); | ||
1558 | for (i = 0; i < wh; i++) | ||
1559 | if (state->grid[i] > i) { | ||
1560 | int n1, n2, di; | ||
1561 | |||
1562 | n1 = state->numbers->numbers[i]; | ||
1563 | n2 = state->numbers->numbers[state->grid[i]]; | ||
1564 | |||
1565 | di = DINDEX(n1, n2); | ||
1566 | assert(di >= 0 && di < TRI(n+1)); | ||
1567 | |||
1568 | if (used[di] < 2) | ||
1569 | used[di]++; | ||
1570 | } | ||
1571 | |||
1572 | for (y = 0; y < h; y++) | ||
1573 | for (x = 0; x < w; x++) { | ||
1574 | int n = y*w+x; | ||
1575 | int n1, n2, di; | ||
1576 | unsigned long c; | ||
1577 | |||
1578 | if (state->grid[n] == n-1) | ||
1579 | c = TYPE_R; | ||
1580 | else if (state->grid[n] == n+1) | ||
1581 | c = TYPE_L; | ||
1582 | else if (state->grid[n] == n-w) | ||
1583 | c = TYPE_B; | ||
1584 | else if (state->grid[n] == n+w) | ||
1585 | c = TYPE_T; | ||
1586 | else | ||
1587 | c = TYPE_BLANK; | ||
1588 | |||
1589 | n1 = state->numbers->numbers[n]; | ||
1590 | if (c != TYPE_BLANK) { | ||
1591 | n2 = state->numbers->numbers[state->grid[n]]; | ||
1592 | di = DINDEX(n1, n2); | ||
1593 | if (used[di] > 1) | ||
1594 | c |= DF_CLASH; /* highlight a clash */ | ||
1595 | } else { | ||
1596 | c |= state->edges[n]; | ||
1597 | } | ||
1598 | |||
1599 | if (n1 == ui->highlight_1) | ||
1600 | c |= DF_HIGHLIGHT_1; | ||
1601 | if (n1 == ui->highlight_2) | ||
1602 | c |= DF_HIGHLIGHT_2; | ||
1603 | |||
1604 | if (flashtime != 0) | ||
1605 | c |= DF_FLASH; /* we're flashing */ | ||
1606 | |||
1607 | if (ui->cur_visible) { | ||
1608 | unsigned curx = (unsigned)(ui->cur_x - (2*x-1)); | ||
1609 | unsigned cury = (unsigned)(ui->cur_y - (2*y-1)); | ||
1610 | if (curx < 3 && cury < 3) { | ||
1611 | c |= (DF_CURSOR | | ||
1612 | (curx * DF_CURSOR_XBASE) | | ||
1613 | (cury * DF_CURSOR_YBASE)); | ||
1614 | if ((ui->cur_x ^ ui->cur_y) & 1) | ||
1615 | c |= DF_CURSOR_USEFUL; | ||
1616 | } | ||
1617 | } | ||
1618 | |||
1619 | if (ds->visible[n] != c) { | ||
1620 | draw_tile(dr, ds, state, x, y, c, | ||
1621 | ui->highlight_1, ui->highlight_2); | ||
1622 | ds->visible[n] = c; | ||
1623 | } | ||
1624 | } | ||
1625 | |||
1626 | sfree(used); | ||
1627 | } | ||
1628 | |||
1629 | static float game_anim_length(const game_state *oldstate, | ||
1630 | const game_state *newstate, int dir, game_ui *ui) | ||
1631 | { | ||
1632 | return 0.0F; | ||
1633 | } | ||
1634 | |||
1635 | static float game_flash_length(const game_state *oldstate, | ||
1636 | const game_state *newstate, int dir, game_ui *ui) | ||
1637 | { | ||
1638 | if (!oldstate->completed && newstate->completed && | ||
1639 | !oldstate->cheated && !newstate->cheated) | ||
1640 | { | ||
1641 | ui->highlight_1 = ui->highlight_2 = -1; | ||
1642 | return FLASH_TIME; | ||
1643 | } | ||
1644 | return 0.0F; | ||
1645 | } | ||
1646 | |||
1647 | static int game_status(const game_state *state) | ||
1648 | { | ||
1649 | return state->completed ? +1 : 0; | ||
1650 | } | ||
1651 | |||
1652 | static int game_timing_state(const game_state *state, game_ui *ui) | ||
1653 | { | ||
1654 | return TRUE; | ||
1655 | } | ||
1656 | |||
1657 | static void game_print_size(const game_params *params, float *x, float *y) | ||
1658 | { | ||
1659 | int pw, ph; | ||
1660 | |||
1661 | /* | ||
1662 | * I'll use 6mm squares by default. | ||
1663 | */ | ||
1664 | game_compute_size(params, 600, &pw, &ph); | ||
1665 | *x = pw / 100.0F; | ||
1666 | *y = ph / 100.0F; | ||
1667 | } | ||
1668 | |||
1669 | static void game_print(drawing *dr, const game_state *state, int tilesize) | ||
1670 | { | ||
1671 | int w = state->w, h = state->h; | ||
1672 | int c, x, y; | ||
1673 | |||
1674 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | ||
1675 | game_drawstate ads, *ds = &ads; | ||
1676 | game_set_size(dr, ds, NULL, tilesize); | ||
1677 | |||
1678 | c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND); | ||
1679 | c = print_mono_colour(dr, 0); assert(c == COL_TEXT); | ||
1680 | c = print_mono_colour(dr, 0); assert(c == COL_DOMINO); | ||
1681 | c = print_mono_colour(dr, 0); assert(c == COL_DOMINOCLASH); | ||
1682 | c = print_mono_colour(dr, 1); assert(c == COL_DOMINOTEXT); | ||
1683 | c = print_mono_colour(dr, 0); assert(c == COL_EDGE); | ||
1684 | |||
1685 | for (y = 0; y < h; y++) | ||
1686 | for (x = 0; x < w; x++) { | ||
1687 | int n = y*w+x; | ||
1688 | unsigned long c; | ||
1689 | |||
1690 | if (state->grid[n] == n-1) | ||
1691 | c = TYPE_R; | ||
1692 | else if (state->grid[n] == n+1) | ||
1693 | c = TYPE_L; | ||
1694 | else if (state->grid[n] == n-w) | ||
1695 | c = TYPE_B; | ||
1696 | else if (state->grid[n] == n+w) | ||
1697 | c = TYPE_T; | ||
1698 | else | ||
1699 | c = TYPE_BLANK; | ||
1700 | |||
1701 | draw_tile(dr, ds, state, x, y, c, -1, -1); | ||
1702 | } | ||
1703 | } | ||
1704 | |||
1705 | #ifdef COMBINED | ||
1706 | #define thegame dominosa | ||
1707 | #endif | ||
1708 | |||
1709 | const struct game thegame = { | ||
1710 | "Dominosa", "games.dominosa", "dominosa", | ||
1711 | default_params, | ||
1712 | game_fetch_preset, NULL, | ||
1713 | decode_params, | ||
1714 | encode_params, | ||
1715 | free_params, | ||
1716 | dup_params, | ||
1717 | TRUE, game_configure, custom_params, | ||
1718 | validate_params, | ||
1719 | new_game_desc, | ||
1720 | validate_desc, | ||
1721 | new_game, | ||
1722 | dup_game, | ||
1723 | free_game, | ||
1724 | TRUE, solve_game, | ||
1725 | TRUE, game_can_format_as_text_now, game_text_format, | ||
1726 | new_ui, | ||
1727 | free_ui, | ||
1728 | encode_ui, | ||
1729 | decode_ui, | ||
1730 | game_changed_state, | ||
1731 | interpret_move, | ||
1732 | execute_move, | ||
1733 | PREFERRED_TILESIZE, game_compute_size, game_set_size, | ||
1734 | game_colours, | ||
1735 | game_new_drawstate, | ||
1736 | game_free_drawstate, | ||
1737 | game_redraw, | ||
1738 | game_anim_length, | ||
1739 | game_flash_length, | ||
1740 | game_status, | ||
1741 | TRUE, FALSE, game_print_size, game_print, | ||
1742 | FALSE, /* wants_statusbar */ | ||
1743 | FALSE, game_timing_state, | ||
1744 | 0, /* flags */ | ||
1745 | }; | ||
1746 | |||
1747 | /* vim: set shiftwidth=4 :set textwidth=80: */ | ||
1748 | |||