diff options
Diffstat (limited to 'apps/plugins/puzzles/pearl.c')
-rw-r--r-- | apps/plugins/puzzles/pearl.c | 2772 |
1 files changed, 2772 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/pearl.c b/apps/plugins/puzzles/pearl.c new file mode 100644 index 0000000000..59effeda40 --- /dev/null +++ b/apps/plugins/puzzles/pearl.c | |||
@@ -0,0 +1,2772 @@ | |||
1 | /* | ||
2 | * pearl.c: Nikoli's `Masyu' puzzle. | ||
3 | */ | ||
4 | |||
5 | /* | ||
6 | * TODO: | ||
7 | * | ||
8 | * - The current keyboard cursor mechanism works well on ordinary PC | ||
9 | * keyboards, but for platforms with only arrow keys and a select | ||
10 | * button or two, we may at some point need a simpler one which can | ||
11 | * handle 'x' markings without needing shift keys. For instance, a | ||
12 | * cursor with twice the grid resolution, so that it can range | ||
13 | * across face centres, edge centres and vertices; 'clicks' on face | ||
14 | * centres begin a drag as currently, clicks on edges toggle | ||
15 | * markings, and clicks on vertices are ignored (but it would be | ||
16 | * too confusing not to let the cursor rest on them). But I'm | ||
17 | * pretty sure that would be less pleasant to play on a full | ||
18 | * keyboard, so probably a #ifdef would be the thing. | ||
19 | * | ||
20 | * - Generation is still pretty slow, due to difficulty coming up in | ||
21 | * the first place with a loop that makes a soluble puzzle even | ||
22 | * with all possible clues filled in. | ||
23 | * + A possible alternative strategy to further tuning of the | ||
24 | * existing loop generator would be to throw the entire | ||
25 | * mechanism out and instead write a different generator from | ||
26 | * scratch which evolves the solution along with the puzzle: | ||
27 | * place a few clues, nail down a bit of the loop, place another | ||
28 | * clue, nail down some more, etc. However, I don't have a | ||
29 | * detailed plan for any such mechanism, so it may be a pipe | ||
30 | * dream. | ||
31 | */ | ||
32 | |||
33 | #include <stdio.h> | ||
34 | #include <stdlib.h> | ||
35 | #include <string.h> | ||
36 | #include "rbassert.h" | ||
37 | #include <ctype.h> | ||
38 | #include <math.h> | ||
39 | |||
40 | #include "puzzles.h" | ||
41 | #include "grid.h" | ||
42 | #include "loopgen.h" | ||
43 | |||
44 | #define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0) | ||
45 | |||
46 | #define NOCLUE 0 | ||
47 | #define CORNER 1 | ||
48 | #define STRAIGHT 2 | ||
49 | |||
50 | #define R 1 | ||
51 | #define U 2 | ||
52 | #define L 4 | ||
53 | #define D 8 | ||
54 | |||
55 | #define DX(d) ( ((d)==R) - ((d)==L) ) | ||
56 | #define DY(d) ( ((d)==D) - ((d)==U) ) | ||
57 | |||
58 | #define F(d) (((d << 2) | (d >> 2)) & 0xF) | ||
59 | #define C(d) (((d << 3) | (d >> 1)) & 0xF) | ||
60 | #define A(d) (((d << 1) | (d >> 3)) & 0xF) | ||
61 | |||
62 | #define LR (L | R) | ||
63 | #define RL (R | L) | ||
64 | #define UD (U | D) | ||
65 | #define DU (D | U) | ||
66 | #define LU (L | U) | ||
67 | #define UL (U | L) | ||
68 | #define LD (L | D) | ||
69 | #define DL (D | L) | ||
70 | #define RU (R | U) | ||
71 | #define UR (U | R) | ||
72 | #define RD (R | D) | ||
73 | #define DR (D | R) | ||
74 | #define BLANK 0 | ||
75 | #define UNKNOWN 15 | ||
76 | |||
77 | #define bLR (1 << LR) | ||
78 | #define bRL (1 << RL) | ||
79 | #define bUD (1 << UD) | ||
80 | #define bDU (1 << DU) | ||
81 | #define bLU (1 << LU) | ||
82 | #define bUL (1 << UL) | ||
83 | #define bLD (1 << LD) | ||
84 | #define bDL (1 << DL) | ||
85 | #define bRU (1 << RU) | ||
86 | #define bUR (1 << UR) | ||
87 | #define bRD (1 << RD) | ||
88 | #define bDR (1 << DR) | ||
89 | #define bBLANK (1 << BLANK) | ||
90 | |||
91 | enum { | ||
92 | COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT, | ||
93 | COL_CURSOR_BACKGROUND = COL_LOWLIGHT, | ||
94 | COL_BLACK, COL_WHITE, | ||
95 | COL_ERROR, COL_GRID, COL_FLASH, | ||
96 | COL_DRAGON, COL_DRAGOFF, | ||
97 | NCOLOURS | ||
98 | }; | ||
99 | |||
100 | /* Macro ickery copied from slant.c */ | ||
101 | #define DIFFLIST(A) \ | ||
102 | A(EASY,Easy,e) \ | ||
103 | A(TRICKY,Tricky,t) | ||
104 | #define ENUM(upper,title,lower) DIFF_ ## upper, | ||
105 | #define TITLE(upper,title,lower) #title, | ||
106 | #define ENCODE(upper,title,lower) #lower | ||
107 | #define CONFIG(upper,title,lower) ":" #title | ||
108 | enum { DIFFLIST(ENUM) DIFFCOUNT }; | ||
109 | static char const *const pearl_diffnames[] = { DIFFLIST(TITLE) "(count)" }; | ||
110 | static char const pearl_diffchars[] = DIFFLIST(ENCODE); | ||
111 | #define DIFFCONFIG DIFFLIST(CONFIG) | ||
112 | |||
113 | struct game_params { | ||
114 | int w, h; | ||
115 | int difficulty; | ||
116 | int nosolve; /* XXX remove me! */ | ||
117 | }; | ||
118 | |||
119 | struct shared_state { | ||
120 | int w, h, sz; | ||
121 | char *clues; /* size w*h */ | ||
122 | int refcnt; | ||
123 | }; | ||
124 | |||
125 | #define INGRID(state, gx, gy) ((gx) >= 0 && (gx) < (state)->shared->w && \ | ||
126 | (gy) >= 0 && (gy) < (state)->shared->h) | ||
127 | struct game_state { | ||
128 | struct shared_state *shared; | ||
129 | char *lines; /* size w*h: lines placed */ | ||
130 | char *errors; /* size w*h: errors detected */ | ||
131 | char *marks; /* size w*h: 'no line here' marks placed. */ | ||
132 | int completed, used_solve; | ||
133 | }; | ||
134 | |||
135 | #define DEFAULT_PRESET 3 | ||
136 | |||
137 | static const struct game_params pearl_presets[] = { | ||
138 | {6, 6, DIFF_EASY}, | ||
139 | {6, 6, DIFF_TRICKY}, | ||
140 | {8, 8, DIFF_EASY}, | ||
141 | {8, 8, DIFF_TRICKY}, | ||
142 | {10, 10, DIFF_EASY}, | ||
143 | {10, 10, DIFF_TRICKY}, | ||
144 | {12, 8, DIFF_EASY}, | ||
145 | {12, 8, DIFF_TRICKY}, | ||
146 | }; | ||
147 | |||
148 | static game_params *default_params(void) | ||
149 | { | ||
150 | game_params *ret = snew(game_params); | ||
151 | |||
152 | *ret = pearl_presets[DEFAULT_PRESET]; | ||
153 | ret->nosolve = FALSE; | ||
154 | |||
155 | return ret; | ||
156 | } | ||
157 | |||
158 | static int game_fetch_preset(int i, char **name, game_params **params) | ||
159 | { | ||
160 | game_params *ret; | ||
161 | char buf[64]; | ||
162 | |||
163 | if (i < 0 || i >= lenof(pearl_presets)) return FALSE; | ||
164 | |||
165 | ret = default_params(); | ||
166 | *ret = pearl_presets[i]; /* struct copy */ | ||
167 | *params = ret; | ||
168 | |||
169 | sprintf(buf, "%dx%d %s", | ||
170 | pearl_presets[i].w, pearl_presets[i].h, | ||
171 | pearl_diffnames[pearl_presets[i].difficulty]); | ||
172 | *name = dupstr(buf); | ||
173 | |||
174 | return TRUE; | ||
175 | } | ||
176 | |||
177 | static void free_params(game_params *params) | ||
178 | { | ||
179 | sfree(params); | ||
180 | } | ||
181 | |||
182 | static game_params *dup_params(const game_params *params) | ||
183 | { | ||
184 | game_params *ret = snew(game_params); | ||
185 | *ret = *params; /* structure copy */ | ||
186 | return ret; | ||
187 | } | ||
188 | |||
189 | static void decode_params(game_params *ret, char const *string) | ||
190 | { | ||
191 | ret->w = ret->h = atoi(string); | ||
192 | while (*string && isdigit((unsigned char) *string)) ++string; | ||
193 | if (*string == 'x') { | ||
194 | string++; | ||
195 | ret->h = atoi(string); | ||
196 | while (*string && isdigit((unsigned char)*string)) string++; | ||
197 | } | ||
198 | |||
199 | ret->difficulty = DIFF_EASY; | ||
200 | if (*string == 'd') { | ||
201 | int i; | ||
202 | string++; | ||
203 | for (i = 0; i < DIFFCOUNT; i++) | ||
204 | if (*string == pearl_diffchars[i]) | ||
205 | ret->difficulty = i; | ||
206 | if (*string) string++; | ||
207 | } | ||
208 | |||
209 | ret->nosolve = FALSE; | ||
210 | if (*string == 'n') { | ||
211 | ret->nosolve = TRUE; | ||
212 | string++; | ||
213 | } | ||
214 | } | ||
215 | |||
216 | static char *encode_params(const game_params *params, int full) | ||
217 | { | ||
218 | char buf[256]; | ||
219 | sprintf(buf, "%dx%d", params->w, params->h); | ||
220 | if (full) | ||
221 | sprintf(buf + strlen(buf), "d%c%s", | ||
222 | pearl_diffchars[params->difficulty], | ||
223 | params->nosolve ? "n" : ""); | ||
224 | return dupstr(buf); | ||
225 | } | ||
226 | |||
227 | static config_item *game_configure(const game_params *params) | ||
228 | { | ||
229 | config_item *ret; | ||
230 | char buf[64]; | ||
231 | |||
232 | ret = snewn(5, config_item); | ||
233 | |||
234 | ret[0].name = "Width"; | ||
235 | ret[0].type = C_STRING; | ||
236 | sprintf(buf, "%d", params->w); | ||
237 | ret[0].sval = dupstr(buf); | ||
238 | ret[0].ival = 0; | ||
239 | |||
240 | ret[1].name = "Height"; | ||
241 | ret[1].type = C_STRING; | ||
242 | sprintf(buf, "%d", params->h); | ||
243 | ret[1].sval = dupstr(buf); | ||
244 | ret[1].ival = 0; | ||
245 | |||
246 | ret[2].name = "Difficulty"; | ||
247 | ret[2].type = C_CHOICES; | ||
248 | ret[2].sval = DIFFCONFIG; | ||
249 | ret[2].ival = params->difficulty; | ||
250 | |||
251 | ret[3].name = "Allow unsoluble"; | ||
252 | ret[3].type = C_BOOLEAN; | ||
253 | ret[3].sval = NULL; | ||
254 | ret[3].ival = params->nosolve; | ||
255 | |||
256 | ret[4].name = NULL; | ||
257 | ret[4].type = C_END; | ||
258 | ret[4].sval = NULL; | ||
259 | ret[4].ival = 0; | ||
260 | |||
261 | return ret; | ||
262 | } | ||
263 | |||
264 | static game_params *custom_params(const config_item *cfg) | ||
265 | { | ||
266 | game_params *ret = snew(game_params); | ||
267 | |||
268 | ret->w = atoi(cfg[0].sval); | ||
269 | ret->h = atoi(cfg[1].sval); | ||
270 | ret->difficulty = cfg[2].ival; | ||
271 | ret->nosolve = cfg[3].ival; | ||
272 | |||
273 | return ret; | ||
274 | } | ||
275 | |||
276 | static char *validate_params(const game_params *params, int full) | ||
277 | { | ||
278 | if (params->w < 5) return "Width must be at least five"; | ||
279 | if (params->h < 5) return "Height must be at least five"; | ||
280 | if (params->difficulty < 0 || params->difficulty >= DIFFCOUNT) | ||
281 | return "Unknown difficulty level"; | ||
282 | |||
283 | return NULL; | ||
284 | } | ||
285 | |||
286 | /* ---------------------------------------------------------------------- | ||
287 | * Solver. | ||
288 | */ | ||
289 | |||
290 | int pearl_solve(int w, int h, char *clues, char *result, | ||
291 | int difficulty, int partial) | ||
292 | { | ||
293 | int W = 2*w+1, H = 2*h+1; | ||
294 | short *workspace; | ||
295 | int *dsf, *dsfsize; | ||
296 | int x, y, b, d; | ||
297 | int ret = -1; | ||
298 | |||
299 | /* | ||
300 | * workspace[(2*y+1)*W+(2*x+1)] indicates the possible nature | ||
301 | * of the square (x,y), as a logical OR of bitfields. | ||
302 | * | ||
303 | * workspace[(2*y)*W+(2*x+1)], for x odd and y even, indicates | ||
304 | * whether the horizontal edge between (x,y) and (x+1,y) is | ||
305 | * connected (1), disconnected (2) or unknown (3). | ||
306 | * | ||
307 | * workspace[(2*y+1)*W+(2*x)], indicates the same about the | ||
308 | * vertical edge between (x,y) and (x,y+1). | ||
309 | * | ||
310 | * Initially, every square is considered capable of being in | ||
311 | * any of the seven possible states (two straights, four | ||
312 | * corners and empty), except those corresponding to clue | ||
313 | * squares which are more restricted. | ||
314 | * | ||
315 | * Initially, all edges are unknown, except the ones around the | ||
316 | * grid border which are known to be disconnected. | ||
317 | */ | ||
318 | workspace = snewn(W*H, short); | ||
319 | for (x = 0; x < W*H; x++) | ||
320 | workspace[x] = 0; | ||
321 | /* Square states */ | ||
322 | for (y = 0; y < h; y++) | ||
323 | for (x = 0; x < w; x++) | ||
324 | switch (clues[y*w+x]) { | ||
325 | case CORNER: | ||
326 | workspace[(2*y+1)*W+(2*x+1)] = bLU|bLD|bRU|bRD; | ||
327 | break; | ||
328 | case STRAIGHT: | ||
329 | workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD; | ||
330 | break; | ||
331 | default: | ||
332 | workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD|bLU|bLD|bRU|bRD|bBLANK; | ||
333 | break; | ||
334 | } | ||
335 | /* Horizontal edges */ | ||
336 | for (y = 0; y <= h; y++) | ||
337 | for (x = 0; x < w; x++) | ||
338 | workspace[(2*y)*W+(2*x+1)] = (y==0 || y==h ? 2 : 3); | ||
339 | /* Vertical edges */ | ||
340 | for (y = 0; y < h; y++) | ||
341 | for (x = 0; x <= w; x++) | ||
342 | workspace[(2*y+1)*W+(2*x)] = (x==0 || x==w ? 2 : 3); | ||
343 | |||
344 | /* | ||
345 | * We maintain a dsf of connected squares, together with a | ||
346 | * count of the size of each equivalence class. | ||
347 | */ | ||
348 | dsf = snewn(w*h, int); | ||
349 | dsfsize = snewn(w*h, int); | ||
350 | |||
351 | /* | ||
352 | * Now repeatedly try to find something we can do. | ||
353 | */ | ||
354 | while (1) { | ||
355 | int done_something = FALSE; | ||
356 | |||
357 | #ifdef SOLVER_DIAGNOSTICS | ||
358 | for (y = 0; y < H; y++) { | ||
359 | for (x = 0; x < W; x++) | ||
360 | printf("%*x", (x&1) ? 5 : 2, workspace[y*W+x]); | ||
361 | printf("\n"); | ||
362 | } | ||
363 | #endif | ||
364 | |||
365 | /* | ||
366 | * Go through the square state words, and discard any | ||
367 | * square state which is inconsistent with known facts | ||
368 | * about the edges around the square. | ||
369 | */ | ||
370 | for (y = 0; y < h; y++) | ||
371 | for (x = 0; x < w; x++) { | ||
372 | for (b = 0; b < 0xD; b++) | ||
373 | if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) { | ||
374 | /* | ||
375 | * If any edge of this square is known to | ||
376 | * be connected when state b would require | ||
377 | * it disconnected, or vice versa, discard | ||
378 | * the state. | ||
379 | */ | ||
380 | for (d = 1; d <= 8; d += d) { | ||
381 | int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d); | ||
382 | if (workspace[ey*W+ex] == | ||
383 | ((b & d) ? 2 : 1)) { | ||
384 | workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<b); | ||
385 | #ifdef SOLVER_DIAGNOSTICS | ||
386 | printf("edge (%d,%d)-(%d,%d) rules out state" | ||
387 | " %d for square (%d,%d)\n", | ||
388 | ex/2, ey/2, (ex+1)/2, (ey+1)/2, | ||
389 | b, x, y); | ||
390 | #endif | ||
391 | done_something = TRUE; | ||
392 | break; | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | |||
397 | /* | ||
398 | * Consistency check: each square must have at | ||
399 | * least one state left! | ||
400 | */ | ||
401 | if (!workspace[(2*y+1)*W+(2*x+1)]) { | ||
402 | #ifdef SOLVER_DIAGNOSTICS | ||
403 | printf("edge check at (%d,%d): inconsistency\n", x, y); | ||
404 | #endif | ||
405 | ret = 0; | ||
406 | goto cleanup; | ||
407 | } | ||
408 | } | ||
409 | |||
410 | /* | ||
411 | * Now go through the states array again, and nail down any | ||
412 | * unknown edge if one of its neighbouring squares makes it | ||
413 | * known. | ||
414 | */ | ||
415 | for (y = 0; y < h; y++) | ||
416 | for (x = 0; x < w; x++) { | ||
417 | int edgeor = 0, edgeand = 15; | ||
418 | |||
419 | for (b = 0; b < 0xD; b++) | ||
420 | if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) { | ||
421 | edgeor |= b; | ||
422 | edgeand &= b; | ||
423 | } | ||
424 | |||
425 | /* | ||
426 | * Now any bit clear in edgeor marks a disconnected | ||
427 | * edge, and any bit set in edgeand marks a | ||
428 | * connected edge. | ||
429 | */ | ||
430 | |||
431 | /* First check consistency: neither bit is both! */ | ||
432 | if (edgeand & ~edgeor) { | ||
433 | #ifdef SOLVER_DIAGNOSTICS | ||
434 | printf("square check at (%d,%d): inconsistency\n", x, y); | ||
435 | #endif | ||
436 | ret = 0; | ||
437 | goto cleanup; | ||
438 | } | ||
439 | |||
440 | for (d = 1; d <= 8; d += d) { | ||
441 | int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d); | ||
442 | |||
443 | if (!(edgeor & d) && workspace[ey*W+ex] == 3) { | ||
444 | workspace[ey*W+ex] = 2; | ||
445 | done_something = TRUE; | ||
446 | #ifdef SOLVER_DIAGNOSTICS | ||
447 | printf("possible states of square (%d,%d) force edge" | ||
448 | " (%d,%d)-(%d,%d) to be disconnected\n", | ||
449 | x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2); | ||
450 | #endif | ||
451 | } else if ((edgeand & d) && workspace[ey*W+ex] == 3) { | ||
452 | workspace[ey*W+ex] = 1; | ||
453 | done_something = TRUE; | ||
454 | #ifdef SOLVER_DIAGNOSTICS | ||
455 | printf("possible states of square (%d,%d) force edge" | ||
456 | " (%d,%d)-(%d,%d) to be connected\n", | ||
457 | x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2); | ||
458 | #endif | ||
459 | } | ||
460 | } | ||
461 | } | ||
462 | |||
463 | if (done_something) | ||
464 | continue; | ||
465 | |||
466 | /* | ||
467 | * Now for longer-range clue-based deductions (using the | ||
468 | * rules that a corner clue must connect to two straight | ||
469 | * squares, and a straight clue must connect to at least | ||
470 | * one corner square). | ||
471 | */ | ||
472 | for (y = 0; y < h; y++) | ||
473 | for (x = 0; x < w; x++) | ||
474 | switch (clues[y*w+x]) { | ||
475 | case CORNER: | ||
476 | for (d = 1; d <= 8; d += d) { | ||
477 | int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d); | ||
478 | int fx = ex + DX(d), fy = ey + DY(d); | ||
479 | int type = d | F(d); | ||
480 | |||
481 | if (workspace[ey*W+ex] == 1) { | ||
482 | /* | ||
483 | * If a corner clue is connected on any | ||
484 | * edge, then we can immediately nail | ||
485 | * down the square beyond that edge as | ||
486 | * being a straight in the appropriate | ||
487 | * direction. | ||
488 | */ | ||
489 | if (workspace[fy*W+fx] != (1<<type)) { | ||
490 | workspace[fy*W+fx] = (1<<type); | ||
491 | done_something = TRUE; | ||
492 | #ifdef SOLVER_DIAGNOSTICS | ||
493 | printf("corner clue at (%d,%d) forces square " | ||
494 | "(%d,%d) into state %d\n", x, y, | ||
495 | fx/2, fy/2, type); | ||
496 | #endif | ||
497 | |||
498 | } | ||
499 | } else if (workspace[ey*W+ex] == 3) { | ||
500 | /* | ||
501 | * Conversely, if a corner clue is | ||
502 | * separated by an unknown edge from a | ||
503 | * square which _cannot_ be a straight | ||
504 | * in the appropriate direction, we can | ||
505 | * mark that edge as disconnected. | ||
506 | */ | ||
507 | if (!(workspace[fy*W+fx] & (1<<type))) { | ||
508 | workspace[ey*W+ex] = 2; | ||
509 | done_something = TRUE; | ||
510 | #ifdef SOLVER_DIAGNOSTICS | ||
511 | printf("corner clue at (%d,%d), plus square " | ||
512 | "(%d,%d) not being state %d, " | ||
513 | "disconnects edge (%d,%d)-(%d,%d)\n", | ||
514 | x, y, fx/2, fy/2, type, | ||
515 | ex/2, ey/2, (ex+1)/2, (ey+1)/2); | ||
516 | #endif | ||
517 | |||
518 | } | ||
519 | } | ||
520 | } | ||
521 | |||
522 | break; | ||
523 | case STRAIGHT: | ||
524 | /* | ||
525 | * If a straight clue is between two squares | ||
526 | * neither of which is capable of being a | ||
527 | * corner connected to it, then the straight | ||
528 | * clue cannot point in that direction. | ||
529 | */ | ||
530 | for (d = 1; d <= 2; d += d) { | ||
531 | int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d); | ||
532 | int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d); | ||
533 | int type = d | F(d); | ||
534 | |||
535 | if (!(workspace[(2*y+1)*W+(2*x+1)] & (1<<type))) | ||
536 | continue; | ||
537 | |||
538 | if (!(workspace[fy*W+fx] & ((1<<(F(d)|A(d))) | | ||
539 | (1<<(F(d)|C(d))))) && | ||
540 | !(workspace[gy*W+gx] & ((1<<( d |A(d))) | | ||
541 | (1<<( d |C(d)))))) { | ||
542 | workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<type); | ||
543 | done_something = TRUE; | ||
544 | #ifdef SOLVER_DIAGNOSTICS | ||
545 | printf("straight clue at (%d,%d) cannot corner at " | ||
546 | "(%d,%d) or (%d,%d) so is not state %d\n", | ||
547 | x, y, fx/2, fy/2, gx/2, gy/2, type); | ||
548 | #endif | ||
549 | } | ||
550 | |||
551 | } | ||
552 | |||
553 | /* | ||
554 | * If a straight clue with known direction is | ||
555 | * connected on one side to a known straight, | ||
556 | * then on the other side it must be a corner. | ||
557 | */ | ||
558 | for (d = 1; d <= 8; d += d) { | ||
559 | int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d); | ||
560 | int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d); | ||
561 | int type = d | F(d); | ||
562 | |||
563 | if (workspace[(2*y+1)*W+(2*x+1)] != (1<<type)) | ||
564 | continue; | ||
565 | |||
566 | if (!(workspace[fy*W+fx] &~ (bLR|bUD)) && | ||
567 | (workspace[gy*W+gx] &~ (bLU|bLD|bRU|bRD))) { | ||
568 | workspace[gy*W+gx] &= (bLU|bLD|bRU|bRD); | ||
569 | done_something = TRUE; | ||
570 | #ifdef SOLVER_DIAGNOSTICS | ||
571 | printf("straight clue at (%d,%d) connecting to " | ||
572 | "straight at (%d,%d) makes (%d,%d) a " | ||
573 | "corner\n", x, y, fx/2, fy/2, gx/2, gy/2); | ||
574 | #endif | ||
575 | } | ||
576 | |||
577 | } | ||
578 | break; | ||
579 | } | ||
580 | |||
581 | if (done_something) | ||
582 | continue; | ||
583 | |||
584 | /* | ||
585 | * Now detect shortcut loops. | ||
586 | */ | ||
587 | |||
588 | { | ||
589 | int nonblanks, loopclass; | ||
590 | |||
591 | dsf_init(dsf, w*h); | ||
592 | for (x = 0; x < w*h; x++) | ||
593 | dsfsize[x] = 1; | ||
594 | |||
595 | /* | ||
596 | * First go through the edge entries and update the dsf | ||
597 | * of which squares are connected to which others. We | ||
598 | * also track the number of squares in each equivalence | ||
599 | * class, and count the overall number of | ||
600 | * known-non-blank squares. | ||
601 | * | ||
602 | * In the process of doing this, we must notice if a | ||
603 | * loop has already been formed. If it has, we blank | ||
604 | * out any square which isn't part of that loop | ||
605 | * (failing a consistency check if any such square does | ||
606 | * not have BLANK as one of its remaining options) and | ||
607 | * exit the deduction loop with success. | ||
608 | */ | ||
609 | nonblanks = 0; | ||
610 | loopclass = -1; | ||
611 | for (y = 1; y < H-1; y++) | ||
612 | for (x = 1; x < W-1; x++) | ||
613 | if ((y ^ x) & 1) { | ||
614 | /* | ||
615 | * (x,y) are the workspace coordinates of | ||
616 | * an edge field. Compute the normal-space | ||
617 | * coordinates of the squares it connects. | ||
618 | */ | ||
619 | int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax; | ||
620 | int bx = x/2, by = y/2, bc = by*w+bx; | ||
621 | |||
622 | /* | ||
623 | * If the edge is connected, do the dsf | ||
624 | * thing. | ||
625 | */ | ||
626 | if (workspace[y*W+x] == 1) { | ||
627 | int ae, be; | ||
628 | |||
629 | ae = dsf_canonify(dsf, ac); | ||
630 | be = dsf_canonify(dsf, bc); | ||
631 | |||
632 | if (ae == be) { | ||
633 | /* | ||
634 | * We have a loop! | ||
635 | */ | ||
636 | if (loopclass != -1) { | ||
637 | /* | ||
638 | * In fact, we have two | ||
639 | * separate loops, which is | ||
640 | * doom. | ||
641 | */ | ||
642 | #ifdef SOLVER_DIAGNOSTICS | ||
643 | printf("two loops found in grid!\n"); | ||
644 | #endif | ||
645 | ret = 0; | ||
646 | goto cleanup; | ||
647 | } | ||
648 | loopclass = ae; | ||
649 | } else { | ||
650 | /* | ||
651 | * Merge the two equivalence | ||
652 | * classes. | ||
653 | */ | ||
654 | int size = dsfsize[ae] + dsfsize[be]; | ||
655 | dsf_merge(dsf, ac, bc); | ||
656 | ae = dsf_canonify(dsf, ac); | ||
657 | dsfsize[ae] = size; | ||
658 | } | ||
659 | } | ||
660 | } else if ((y & x) & 1) { | ||
661 | /* | ||
662 | * (x,y) are the workspace coordinates of a | ||
663 | * square field. If the square is | ||
664 | * definitely not blank, count it. | ||
665 | */ | ||
666 | if (!(workspace[y*W+x] & bBLANK)) | ||
667 | nonblanks++; | ||
668 | } | ||
669 | |||
670 | /* | ||
671 | * If we discovered an existing loop above, we must now | ||
672 | * blank every square not part of it, and exit the main | ||
673 | * deduction loop. | ||
674 | */ | ||
675 | if (loopclass != -1) { | ||
676 | #ifdef SOLVER_DIAGNOSTICS | ||
677 | printf("loop found in grid!\n"); | ||
678 | #endif | ||
679 | for (y = 0; y < h; y++) | ||
680 | for (x = 0; x < w; x++) | ||
681 | if (dsf_canonify(dsf, y*w+x) != loopclass) { | ||
682 | if (workspace[(y*2+1)*W+(x*2+1)] & bBLANK) { | ||
683 | workspace[(y*2+1)*W+(x*2+1)] = bBLANK; | ||
684 | } else { | ||
685 | /* | ||
686 | * This square is not part of the | ||
687 | * loop, but is known non-blank. We | ||
688 | * have goofed. | ||
689 | */ | ||
690 | #ifdef SOLVER_DIAGNOSTICS | ||
691 | printf("non-blank square (%d,%d) found outside" | ||
692 | " loop!\n", x, y); | ||
693 | #endif | ||
694 | ret = 0; | ||
695 | goto cleanup; | ||
696 | } | ||
697 | } | ||
698 | /* | ||
699 | * And we're done. | ||
700 | */ | ||
701 | ret = 1; | ||
702 | break; | ||
703 | } | ||
704 | |||
705 | /* Further deductions are considered 'tricky'. */ | ||
706 | if (difficulty == DIFF_EASY) goto done_deductions; | ||
707 | |||
708 | /* | ||
709 | * Now go through the workspace again and mark any edge | ||
710 | * which would cause a shortcut loop (i.e. would | ||
711 | * connect together two squares in the same equivalence | ||
712 | * class, and that equivalence class does not contain | ||
713 | * _all_ the known-non-blank squares currently in the | ||
714 | * grid) as disconnected. Also, mark any _square state_ | ||
715 | * which would cause a shortcut loop as disconnected. | ||
716 | */ | ||
717 | for (y = 1; y < H-1; y++) | ||
718 | for (x = 1; x < W-1; x++) | ||
719 | if ((y ^ x) & 1) { | ||
720 | /* | ||
721 | * (x,y) are the workspace coordinates of | ||
722 | * an edge field. Compute the normal-space | ||
723 | * coordinates of the squares it connects. | ||
724 | */ | ||
725 | int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax; | ||
726 | int bx = x/2, by = y/2, bc = by*w+bx; | ||
727 | |||
728 | /* | ||
729 | * If the edge is currently unknown, and | ||
730 | * sits between two squares in the same | ||
731 | * equivalence class, and the size of that | ||
732 | * class is less than nonblanks, then | ||
733 | * connecting this edge would be a shortcut | ||
734 | * loop and so we must not do so. | ||
735 | */ | ||
736 | if (workspace[y*W+x] == 3) { | ||
737 | int ae, be; | ||
738 | |||
739 | ae = dsf_canonify(dsf, ac); | ||
740 | be = dsf_canonify(dsf, bc); | ||
741 | |||
742 | if (ae == be) { | ||
743 | /* | ||
744 | * We have a loop. Is it a shortcut? | ||
745 | */ | ||
746 | if (dsfsize[ae] < nonblanks) { | ||
747 | /* | ||
748 | * Yes! Mark this edge disconnected. | ||
749 | */ | ||
750 | workspace[y*W+x] = 2; | ||
751 | done_something = TRUE; | ||
752 | #ifdef SOLVER_DIAGNOSTICS | ||
753 | printf("edge (%d,%d)-(%d,%d) would create" | ||
754 | " a shortcut loop, hence must be" | ||
755 | " disconnected\n", x/2, y/2, | ||
756 | (x+1)/2, (y+1)/2); | ||
757 | #endif | ||
758 | } | ||
759 | } | ||
760 | } | ||
761 | } else if ((y & x) & 1) { | ||
762 | /* | ||
763 | * (x,y) are the workspace coordinates of a | ||
764 | * square field. Go through its possible | ||
765 | * (non-blank) states and see if any gives | ||
766 | * rise to a shortcut loop. | ||
767 | * | ||
768 | * This is slightly fiddly, because we have | ||
769 | * to check whether this square is already | ||
770 | * part of the same equivalence class as | ||
771 | * the things it's joining. | ||
772 | */ | ||
773 | int ae = dsf_canonify(dsf, (y/2)*w+(x/2)); | ||
774 | |||
775 | for (b = 2; b < 0xD; b++) | ||
776 | if (workspace[y*W+x] & (1<<b)) { | ||
777 | /* | ||
778 | * Find the equivalence classes of | ||
779 | * the two squares this one would | ||
780 | * connect if it were in this | ||
781 | * state. | ||
782 | */ | ||
783 | int e = -1; | ||
784 | |||
785 | for (d = 1; d <= 8; d += d) if (b & d) { | ||
786 | int xx = x/2 + DX(d), yy = y/2 + DY(d); | ||
787 | int ee = dsf_canonify(dsf, yy*w+xx); | ||
788 | |||
789 | if (e == -1) | ||
790 | ee = e; | ||
791 | else if (e != ee) | ||
792 | e = -2; | ||
793 | } | ||
794 | |||
795 | if (e >= 0) { | ||
796 | /* | ||
797 | * This square state would form | ||
798 | * a loop on equivalence class | ||
799 | * e. Measure the size of that | ||
800 | * loop, and see if it's a | ||
801 | * shortcut. | ||
802 | */ | ||
803 | int loopsize = dsfsize[e]; | ||
804 | if (e != ae) | ||
805 | loopsize++;/* add the square itself */ | ||
806 | if (loopsize < nonblanks) { | ||
807 | /* | ||
808 | * It is! Mark this square | ||
809 | * state invalid. | ||
810 | */ | ||
811 | workspace[y*W+x] &= ~(1<<b); | ||
812 | done_something = TRUE; | ||
813 | #ifdef SOLVER_DIAGNOSTICS | ||
814 | printf("square (%d,%d) would create a " | ||
815 | "shortcut loop in state %d, " | ||
816 | "hence cannot be\n", | ||
817 | x/2, y/2, b); | ||
818 | #endif | ||
819 | } | ||
820 | } | ||
821 | } | ||
822 | } | ||
823 | } | ||
824 | |||
825 | done_deductions: | ||
826 | |||
827 | if (done_something) | ||
828 | continue; | ||
829 | |||
830 | /* | ||
831 | * If we reach here, there is nothing left we can do. | ||
832 | * Return 2 for ambiguous puzzle. | ||
833 | */ | ||
834 | ret = 2; | ||
835 | break; | ||
836 | } | ||
837 | |||
838 | cleanup: | ||
839 | |||
840 | /* | ||
841 | * If ret = 1 then we've successfully achieved a solution. This | ||
842 | * means that we expect every square to be nailed down to | ||
843 | * exactly one possibility. If this is the case, or if the caller | ||
844 | * asked for a partial solution anyway, transcribe those | ||
845 | * possibilities into the result array. | ||
846 | */ | ||
847 | if (ret == 1 || partial) { | ||
848 | for (y = 0; y < h; y++) { | ||
849 | for (x = 0; x < w; x++) { | ||
850 | for (b = 0; b < 0xD; b++) | ||
851 | if (workspace[(2*y+1)*W+(2*x+1)] == (1<<b)) { | ||
852 | result[y*w+x] = b; | ||
853 | break; | ||
854 | } | ||
855 | if (ret == 1) assert(b < 0xD); /* we should have had a break by now */ | ||
856 | } | ||
857 | } | ||
858 | } | ||
859 | |||
860 | sfree(dsfsize); | ||
861 | sfree(dsf); | ||
862 | sfree(workspace); | ||
863 | assert(ret >= 0); | ||
864 | return ret; | ||
865 | } | ||
866 | |||
867 | /* ---------------------------------------------------------------------- | ||
868 | * Loop generator. | ||
869 | */ | ||
870 | |||
871 | /* | ||
872 | * We use the loop generator code from loopy, hard-coding to a square | ||
873 | * grid of the appropriate size. Knowing the grid layout and the tile | ||
874 | * size we can shrink that to our small grid and then make our line | ||
875 | * layout from the face colour info. | ||
876 | * | ||
877 | * We provide a bias function to the loop generator which tries to | ||
878 | * bias in favour of loops with more scope for Pearl black clues. This | ||
879 | * seems to improve the success rate of the puzzle generator, in that | ||
880 | * such loops have a better chance of being soluble with all valid | ||
881 | * clues put in. | ||
882 | */ | ||
883 | |||
884 | struct pearl_loopgen_bias_ctx { | ||
885 | /* | ||
886 | * Our bias function counts the number of 'black clue' corners | ||
887 | * (i.e. corners adjacent to two straights) in both the | ||
888 | * BLACK/nonBLACK and WHITE/nonWHITE boundaries. In order to do | ||
889 | * this, we must: | ||
890 | * | ||
891 | * - track the edges that are part of each of those loops | ||
892 | * - track the types of vertex in each loop (corner, straight, | ||
893 | * none) | ||
894 | * - track the current black-clue status of each vertex in each | ||
895 | * loop. | ||
896 | * | ||
897 | * Each of these chunks of data is updated incrementally from the | ||
898 | * previous one, to avoid slowdown due to the bias function | ||
899 | * rescanning the whole grid every time it's called. | ||
900 | * | ||
901 | * So we need a lot of separate arrays, plus a tdq for each one, | ||
902 | * and we must repeat it all twice for the BLACK and WHITE | ||
903 | * boundaries. | ||
904 | */ | ||
905 | struct pearl_loopgen_bias_ctx_boundary { | ||
906 | int colour; /* FACE_WHITE or FACE_BLACK */ | ||
907 | |||
908 | char *edges; /* is each edge part of the loop? */ | ||
909 | tdq *edges_todo; | ||
910 | |||
911 | char *vertextypes; /* bits 0-3 == outgoing edge bitmap; | ||
912 | * bit 4 set iff corner clue. | ||
913 | * Hence, 0 means non-vertex; | ||
914 | * nonzero but bit 4 zero = straight. */ | ||
915 | int *neighbour[2]; /* indices of neighbour vertices in loop */ | ||
916 | tdq *vertextypes_todo; | ||
917 | |||
918 | char *blackclues; /* is each vertex a black clue site? */ | ||
919 | tdq *blackclues_todo; | ||
920 | } boundaries[2]; /* boundaries[0]=WHITE, [1]=BLACK */ | ||
921 | |||
922 | char *faces; /* remember last-seen colour of each face */ | ||
923 | tdq *faces_todo; | ||
924 | |||
925 | int score; | ||
926 | |||
927 | grid *g; | ||
928 | }; | ||
929 | int pearl_loopgen_bias(void *vctx, char *board, int face) | ||
930 | { | ||
931 | struct pearl_loopgen_bias_ctx *ctx = (struct pearl_loopgen_bias_ctx *)vctx; | ||
932 | grid *g = ctx->g; | ||
933 | int oldface, newface; | ||
934 | int i, j, k; | ||
935 | |||
936 | tdq_add(ctx->faces_todo, face); | ||
937 | while ((j = tdq_remove(ctx->faces_todo)) >= 0) { | ||
938 | oldface = ctx->faces[j]; | ||
939 | ctx->faces[j] = newface = board[j]; | ||
940 | for (i = 0; i < 2; i++) { | ||
941 | struct pearl_loopgen_bias_ctx_boundary *b = &ctx->boundaries[i]; | ||
942 | int c = b->colour; | ||
943 | |||
944 | /* | ||
945 | * If the face has changed either from or to colour c, we need | ||
946 | * to reprocess the edges for this boundary. | ||
947 | */ | ||
948 | if (oldface == c || newface == c) { | ||
949 | grid_face *f = &g->faces[face]; | ||
950 | for (k = 0; k < f->order; k++) | ||
951 | tdq_add(b->edges_todo, f->edges[k] - g->edges); | ||
952 | } | ||
953 | } | ||
954 | } | ||
955 | |||
956 | for (i = 0; i < 2; i++) { | ||
957 | struct pearl_loopgen_bias_ctx_boundary *b = &ctx->boundaries[i]; | ||
958 | int c = b->colour; | ||
959 | |||
960 | /* | ||
961 | * Go through the to-do list of edges. For each edge, decide | ||
962 | * anew whether it's part of this boundary or not. Any edge | ||
963 | * that changes state has to have both its endpoints put on | ||
964 | * the vertextypes_todo list. | ||
965 | */ | ||
966 | while ((j = tdq_remove(b->edges_todo)) >= 0) { | ||
967 | grid_edge *e = &g->edges[j]; | ||
968 | int fc1 = e->face1 ? board[e->face1 - g->faces] : FACE_BLACK; | ||
969 | int fc2 = e->face2 ? board[e->face2 - g->faces] : FACE_BLACK; | ||
970 | int oldedge = b->edges[j]; | ||
971 | int newedge = (fc1==c) ^ (fc2==c); | ||
972 | if (oldedge != newedge) { | ||
973 | b->edges[j] = newedge; | ||
974 | tdq_add(b->vertextypes_todo, e->dot1 - g->dots); | ||
975 | tdq_add(b->vertextypes_todo, e->dot2 - g->dots); | ||
976 | } | ||
977 | } | ||
978 | |||
979 | /* | ||
980 | * Go through the to-do list of vertices whose types need | ||
981 | * refreshing. For each one, decide whether it's a corner, a | ||
982 | * straight, or a vertex not in the loop, and in the former | ||
983 | * two cases also work out the indices of its neighbour | ||
984 | * vertices along the loop. Any vertex that changes state must | ||
985 | * be put back on the to-do list for deciding if it's a black | ||
986 | * clue site, and so must its two new neighbours _and_ its two | ||
987 | * old neighbours. | ||
988 | */ | ||
989 | while ((j = tdq_remove(b->vertextypes_todo)) >= 0) { | ||
990 | grid_dot *d = &g->dots[j]; | ||
991 | int neighbours[2], type = 0, n = 0; | ||
992 | |||
993 | for (k = 0; k < d->order; k++) { | ||
994 | grid_edge *e = d->edges[k]; | ||
995 | grid_dot *d2 = (e->dot1 == d ? e->dot2 : e->dot1); | ||
996 | /* dir == 0,1,2,3 for an edge going L,U,R,D */ | ||
997 | int dir = (d->y == d2->y) + 2*(d->x+d->y > d2->x+d2->y); | ||
998 | int ei = e - g->edges; | ||
999 | if (b->edges[ei]) { | ||
1000 | type |= 1 << dir; | ||
1001 | neighbours[n] = d2 - g->dots; | ||
1002 | n++; | ||
1003 | } | ||
1004 | } | ||
1005 | |||
1006 | /* | ||
1007 | * Decide if it's a corner, and set the corner flag if so. | ||
1008 | */ | ||
1009 | if (type != 0 && type != 0x5 && type != 0xA) | ||
1010 | type |= 0x10; | ||
1011 | |||
1012 | if (type != b->vertextypes[j]) { | ||
1013 | /* | ||
1014 | * Recompute old neighbours, if any. | ||
1015 | */ | ||
1016 | if (b->vertextypes[j]) { | ||
1017 | tdq_add(b->blackclues_todo, b->neighbour[0][j]); | ||
1018 | tdq_add(b->blackclues_todo, b->neighbour[1][j]); | ||
1019 | } | ||
1020 | /* | ||
1021 | * Recompute this vertex. | ||
1022 | */ | ||
1023 | tdq_add(b->blackclues_todo, j); | ||
1024 | b->vertextypes[j] = type; | ||
1025 | /* | ||
1026 | * Recompute new neighbours, if any. | ||
1027 | */ | ||
1028 | if (b->vertextypes[j]) { | ||
1029 | b->neighbour[0][j] = neighbours[0]; | ||
1030 | b->neighbour[1][j] = neighbours[1]; | ||
1031 | tdq_add(b->blackclues_todo, b->neighbour[0][j]); | ||
1032 | tdq_add(b->blackclues_todo, b->neighbour[1][j]); | ||
1033 | } | ||
1034 | } | ||
1035 | } | ||
1036 | |||
1037 | /* | ||
1038 | * Go through the list of vertices which we must check to see | ||
1039 | * if they're black clue sites. Each one is a black clue site | ||
1040 | * iff it is a corner and its loop neighbours are non-corners. | ||
1041 | * Adjust the running total of black clues we've counted. | ||
1042 | */ | ||
1043 | while ((j = tdq_remove(b->blackclues_todo)) >= 0) { | ||
1044 | ctx->score -= b->blackclues[j]; | ||
1045 | b->blackclues[j] = ((b->vertextypes[j] & 0x10) && | ||
1046 | !((b->vertextypes[b->neighbour[0][j]] | | ||
1047 | b->vertextypes[b->neighbour[1][j]]) | ||
1048 | & 0x10)); | ||
1049 | ctx->score += b->blackclues[j]; | ||
1050 | } | ||
1051 | } | ||
1052 | |||
1053 | return ctx->score; | ||
1054 | } | ||
1055 | |||
1056 | void pearl_loopgen(int w, int h, char *lines, random_state *rs) | ||
1057 | { | ||
1058 | grid *g = grid_new(GRID_SQUARE, w-1, h-1, NULL); | ||
1059 | char *board = snewn(g->num_faces, char); | ||
1060 | int i, s = g->tilesize; | ||
1061 | struct pearl_loopgen_bias_ctx biasctx; | ||
1062 | |||
1063 | memset(lines, 0, w*h); | ||
1064 | |||
1065 | /* | ||
1066 | * Initialise the context for the bias function. Initially we fill | ||
1067 | * all the to-do lists, so that the first call will scan | ||
1068 | * everything; thereafter the lists stay empty so we make | ||
1069 | * incremental changes. | ||
1070 | */ | ||
1071 | biasctx.g = g; | ||
1072 | biasctx.faces = snewn(g->num_faces, char); | ||
1073 | biasctx.faces_todo = tdq_new(g->num_faces); | ||
1074 | tdq_fill(biasctx.faces_todo); | ||
1075 | biasctx.score = 0; | ||
1076 | memset(biasctx.faces, FACE_GREY, g->num_faces); | ||
1077 | for (i = 0; i < 2; i++) { | ||
1078 | biasctx.boundaries[i].edges = snewn(g->num_edges, char); | ||
1079 | memset(biasctx.boundaries[i].edges, 0, g->num_edges); | ||
1080 | biasctx.boundaries[i].edges_todo = tdq_new(g->num_edges); | ||
1081 | tdq_fill(biasctx.boundaries[i].edges_todo); | ||
1082 | biasctx.boundaries[i].vertextypes = snewn(g->num_dots, char); | ||
1083 | memset(biasctx.boundaries[i].vertextypes, 0, g->num_dots); | ||
1084 | biasctx.boundaries[i].neighbour[0] = snewn(g->num_dots, int); | ||
1085 | biasctx.boundaries[i].neighbour[1] = snewn(g->num_dots, int); | ||
1086 | biasctx.boundaries[i].vertextypes_todo = tdq_new(g->num_dots); | ||
1087 | tdq_fill(biasctx.boundaries[i].vertextypes_todo); | ||
1088 | biasctx.boundaries[i].blackclues = snewn(g->num_dots, char); | ||
1089 | memset(biasctx.boundaries[i].blackclues, 0, g->num_dots); | ||
1090 | biasctx.boundaries[i].blackclues_todo = tdq_new(g->num_dots); | ||
1091 | tdq_fill(biasctx.boundaries[i].blackclues_todo); | ||
1092 | } | ||
1093 | biasctx.boundaries[0].colour = FACE_WHITE; | ||
1094 | biasctx.boundaries[1].colour = FACE_BLACK; | ||
1095 | generate_loop(g, board, rs, pearl_loopgen_bias, &biasctx); | ||
1096 | sfree(biasctx.faces); | ||
1097 | tdq_free(biasctx.faces_todo); | ||
1098 | for (i = 0; i < 2; i++) { | ||
1099 | sfree(biasctx.boundaries[i].edges); | ||
1100 | tdq_free(biasctx.boundaries[i].edges_todo); | ||
1101 | sfree(biasctx.boundaries[i].vertextypes); | ||
1102 | sfree(biasctx.boundaries[i].neighbour[0]); | ||
1103 | sfree(biasctx.boundaries[i].neighbour[1]); | ||
1104 | tdq_free(biasctx.boundaries[i].vertextypes_todo); | ||
1105 | sfree(biasctx.boundaries[i].blackclues); | ||
1106 | tdq_free(biasctx.boundaries[i].blackclues_todo); | ||
1107 | } | ||
1108 | |||
1109 | for (i = 0; i < g->num_edges; i++) { | ||
1110 | grid_edge *e = g->edges + i; | ||
1111 | enum face_colour c1 = FACE_COLOUR(e->face1); | ||
1112 | enum face_colour c2 = FACE_COLOUR(e->face2); | ||
1113 | assert(c1 != FACE_GREY); | ||
1114 | assert(c2 != FACE_GREY); | ||
1115 | if (c1 != c2) { | ||
1116 | /* This grid edge is on the loop: lay line along it */ | ||
1117 | int x1 = e->dot1->x/s, y1 = e->dot1->y/s; | ||
1118 | int x2 = e->dot2->x/s, y2 = e->dot2->y/s; | ||
1119 | |||
1120 | /* (x1,y1) and (x2,y2) are now in our grid coords (0-w,0-h). */ | ||
1121 | if (x1 == x2) { | ||
1122 | if (y1 > y2) SWAP(y1,y2); | ||
1123 | |||
1124 | assert(y1+1 == y2); | ||
1125 | lines[y1*w+x1] |= D; | ||
1126 | lines[y2*w+x1] |= U; | ||
1127 | } else if (y1 == y2) { | ||
1128 | if (x1 > x2) SWAP(x1,x2); | ||
1129 | |||
1130 | assert(x1+1 == x2); | ||
1131 | lines[y1*w+x1] |= R; | ||
1132 | lines[y1*w+x2] |= L; | ||
1133 | } else | ||
1134 | assert(!"grid with diagonal coords?!"); | ||
1135 | } | ||
1136 | } | ||
1137 | |||
1138 | grid_free(g); | ||
1139 | sfree(board); | ||
1140 | |||
1141 | #if defined LOOPGEN_DIAGNOSTICS && !defined GENERATION_DIAGNOSTICS | ||
1142 | printf("as returned:\n"); | ||
1143 | for (y = 0; y < h; y++) { | ||
1144 | for (x = 0; x < w; x++) { | ||
1145 | int type = lines[y*w+x]; | ||
1146 | char s[5], *p = s; | ||
1147 | if (type & L) *p++ = 'L'; | ||
1148 | if (type & R) *p++ = 'R'; | ||
1149 | if (type & U) *p++ = 'U'; | ||
1150 | if (type & D) *p++ = 'D'; | ||
1151 | *p = '\0'; | ||
1152 | printf("%3s", s); | ||
1153 | } | ||
1154 | printf("\n"); | ||
1155 | } | ||
1156 | printf("\n"); | ||
1157 | #endif | ||
1158 | } | ||
1159 | |||
1160 | static int new_clues(const game_params *params, random_state *rs, | ||
1161 | char *clues, char *grid) | ||
1162 | { | ||
1163 | int w = params->w, h = params->h, diff = params->difficulty; | ||
1164 | int ngen = 0, x, y, d, ret, i; | ||
1165 | |||
1166 | |||
1167 | /* | ||
1168 | * Difficulty exception: 5x5 Tricky is not generable (the | ||
1169 | * generator will spin forever trying) and so we fudge it to Easy. | ||
1170 | */ | ||
1171 | if (w == 5 && h == 5 && diff > DIFF_EASY) | ||
1172 | diff = DIFF_EASY; | ||
1173 | |||
1174 | while (1) { | ||
1175 | ngen++; | ||
1176 | pearl_loopgen(w, h, grid, rs); | ||
1177 | |||
1178 | #ifdef GENERATION_DIAGNOSTICS | ||
1179 | printf("grid array:\n"); | ||
1180 | for (y = 0; y < h; y++) { | ||
1181 | for (x = 0; x < w; x++) { | ||
1182 | int type = grid[y*w+x]; | ||
1183 | char s[5], *p = s; | ||
1184 | if (type & L) *p++ = 'L'; | ||
1185 | if (type & R) *p++ = 'R'; | ||
1186 | if (type & U) *p++ = 'U'; | ||
1187 | if (type & D) *p++ = 'D'; | ||
1188 | *p = '\0'; | ||
1189 | printf("%2s ", s); | ||
1190 | } | ||
1191 | printf("\n"); | ||
1192 | } | ||
1193 | printf("\n"); | ||
1194 | #endif | ||
1195 | |||
1196 | /* | ||
1197 | * Set up the maximal clue array. | ||
1198 | */ | ||
1199 | for (y = 0; y < h; y++) | ||
1200 | for (x = 0; x < w; x++) { | ||
1201 | int type = grid[y*w+x]; | ||
1202 | |||
1203 | clues[y*w+x] = NOCLUE; | ||
1204 | |||
1205 | if ((bLR|bUD) & (1 << type)) { | ||
1206 | /* | ||
1207 | * This is a straight; see if it's a viable | ||
1208 | * candidate for a straight clue. It qualifies if | ||
1209 | * at least one of the squares it connects to is a | ||
1210 | * corner. | ||
1211 | */ | ||
1212 | for (d = 1; d <= 8; d += d) if (type & d) { | ||
1213 | int xx = x + DX(d), yy = y + DY(d); | ||
1214 | assert(xx >= 0 && xx < w && yy >= 0 && yy < h); | ||
1215 | if ((bLU|bLD|bRU|bRD) & (1 << grid[yy*w+xx])) | ||
1216 | break; | ||
1217 | } | ||
1218 | if (d <= 8) /* we found one */ | ||
1219 | clues[y*w+x] = STRAIGHT; | ||
1220 | } else if ((bLU|bLD|bRU|bRD) & (1 << type)) { | ||
1221 | /* | ||
1222 | * This is a corner; see if it's a viable candidate | ||
1223 | * for a corner clue. It qualifies if all the | ||
1224 | * squares it connects to are straights. | ||
1225 | */ | ||
1226 | for (d = 1; d <= 8; d += d) if (type & d) { | ||
1227 | int xx = x + DX(d), yy = y + DY(d); | ||
1228 | assert(xx >= 0 && xx < w && yy >= 0 && yy < h); | ||
1229 | if (!((bLR|bUD) & (1 << grid[yy*w+xx]))) | ||
1230 | break; | ||
1231 | } | ||
1232 | if (d > 8) /* we didn't find a counterexample */ | ||
1233 | clues[y*w+x] = CORNER; | ||
1234 | } | ||
1235 | } | ||
1236 | |||
1237 | #ifdef GENERATION_DIAGNOSTICS | ||
1238 | printf("clue array:\n"); | ||
1239 | for (y = 0; y < h; y++) { | ||
1240 | for (x = 0; x < w; x++) { | ||
1241 | printf("%c", " *O"[(unsigned char)clues[y*w+x]]); | ||
1242 | } | ||
1243 | printf("\n"); | ||
1244 | } | ||
1245 | printf("\n"); | ||
1246 | #endif | ||
1247 | |||
1248 | if (!params->nosolve) { | ||
1249 | int *cluespace, *straights, *corners; | ||
1250 | int nstraights, ncorners, nstraightpos, ncornerpos; | ||
1251 | |||
1252 | /* | ||
1253 | * See if we can solve the puzzle just like this. | ||
1254 | */ | ||
1255 | ret = pearl_solve(w, h, clues, grid, diff, FALSE); | ||
1256 | assert(ret > 0); /* shouldn't be inconsistent! */ | ||
1257 | if (ret != 1) | ||
1258 | continue; /* go round and try again */ | ||
1259 | |||
1260 | /* | ||
1261 | * Check this puzzle isn't too easy. | ||
1262 | */ | ||
1263 | if (diff > DIFF_EASY) { | ||
1264 | ret = pearl_solve(w, h, clues, grid, diff-1, FALSE); | ||
1265 | assert(ret > 0); | ||
1266 | if (ret == 1) | ||
1267 | continue; /* too easy: try again */ | ||
1268 | } | ||
1269 | |||
1270 | /* | ||
1271 | * Now shuffle the grid points and gradually remove the | ||
1272 | * clues to find a minimal set which still leaves the | ||
1273 | * puzzle soluble. | ||
1274 | * | ||
1275 | * We preferentially attempt to remove whichever type of | ||
1276 | * clue is currently most numerous, to combat a general | ||
1277 | * tendency of plain random generation to bias in favour | ||
1278 | * of many white clues and few black. | ||
1279 | * | ||
1280 | * 'nstraights' and 'ncorners' count the number of clues | ||
1281 | * of each type currently remaining in the grid; | ||
1282 | * 'nstraightpos' and 'ncornerpos' count the clues of each | ||
1283 | * type we have left to try to remove. (Clues which we | ||
1284 | * have tried and failed to remove are counted by the | ||
1285 | * former but not the latter.) | ||
1286 | */ | ||
1287 | cluespace = snewn(w*h, int); | ||
1288 | straights = cluespace; | ||
1289 | nstraightpos = 0; | ||
1290 | for (i = 0; i < w*h; i++) | ||
1291 | if (clues[i] == STRAIGHT) | ||
1292 | straights[nstraightpos++] = i; | ||
1293 | corners = straights + nstraightpos; | ||
1294 | ncornerpos = 0; | ||
1295 | for (i = 0; i < w*h; i++) | ||
1296 | if (clues[i] == STRAIGHT) | ||
1297 | corners[ncornerpos++] = i; | ||
1298 | nstraights = nstraightpos; | ||
1299 | ncorners = ncornerpos; | ||
1300 | |||
1301 | shuffle(straights, nstraightpos, sizeof(*straights), rs); | ||
1302 | shuffle(corners, ncornerpos, sizeof(*corners), rs); | ||
1303 | while (nstraightpos > 0 || ncornerpos > 0) { | ||
1304 | int cluepos; | ||
1305 | int clue; | ||
1306 | |||
1307 | /* | ||
1308 | * Decide which clue to try to remove next. If both | ||
1309 | * types are available, we choose whichever kind is | ||
1310 | * currently overrepresented; otherwise we take | ||
1311 | * whatever we can get. | ||
1312 | */ | ||
1313 | if (nstraightpos > 0 && ncornerpos > 0) { | ||
1314 | if (nstraights >= ncorners) | ||
1315 | cluepos = straights[--nstraightpos]; | ||
1316 | else | ||
1317 | cluepos = straights[--ncornerpos]; | ||
1318 | } else { | ||
1319 | if (nstraightpos > 0) | ||
1320 | cluepos = straights[--nstraightpos]; | ||
1321 | else | ||
1322 | cluepos = straights[--ncornerpos]; | ||
1323 | } | ||
1324 | |||
1325 | y = cluepos / w; | ||
1326 | x = cluepos % w; | ||
1327 | |||
1328 | clue = clues[y*w+x]; | ||
1329 | clues[y*w+x] = 0; /* try removing this clue */ | ||
1330 | |||
1331 | ret = pearl_solve(w, h, clues, grid, diff, FALSE); | ||
1332 | assert(ret > 0); | ||
1333 | if (ret != 1) | ||
1334 | clues[y*w+x] = clue; /* oops, put it back again */ | ||
1335 | } | ||
1336 | sfree(cluespace); | ||
1337 | } | ||
1338 | |||
1339 | #ifdef FINISHED_PUZZLE | ||
1340 | printf("clue array:\n"); | ||
1341 | for (y = 0; y < h; y++) { | ||
1342 | for (x = 0; x < w; x++) { | ||
1343 | printf("%c", " *O"[(unsigned char)clues[y*w+x]]); | ||
1344 | } | ||
1345 | printf("\n"); | ||
1346 | } | ||
1347 | printf("\n"); | ||
1348 | #endif | ||
1349 | |||
1350 | break; /* got it */ | ||
1351 | } | ||
1352 | |||
1353 | debug(("%d %dx%d loops before finished puzzle.\n", ngen, w, h)); | ||
1354 | |||
1355 | return ngen; | ||
1356 | } | ||
1357 | |||
1358 | static char *new_game_desc(const game_params *params, random_state *rs, | ||
1359 | char **aux, int interactive) | ||
1360 | { | ||
1361 | char *grid, *clues; | ||
1362 | char *desc; | ||
1363 | int w = params->w, h = params->h, i, j; | ||
1364 | |||
1365 | grid = snewn(w*h, char); | ||
1366 | clues = snewn(w*h, char); | ||
1367 | |||
1368 | new_clues(params, rs, clues, grid); | ||
1369 | |||
1370 | desc = snewn(w * h + 1, char); | ||
1371 | for (i = j = 0; i < w*h; i++) { | ||
1372 | if (clues[i] == NOCLUE && j > 0 && | ||
1373 | desc[j-1] >= 'a' && desc[j-1] < 'z') | ||
1374 | desc[j-1]++; | ||
1375 | else if (clues[i] == NOCLUE) | ||
1376 | desc[j++] = 'a'; | ||
1377 | else if (clues[i] == CORNER) | ||
1378 | desc[j++] = 'B'; | ||
1379 | else if (clues[i] == STRAIGHT) | ||
1380 | desc[j++] = 'W'; | ||
1381 | } | ||
1382 | desc[j] = '\0'; | ||
1383 | |||
1384 | *aux = snewn(w*h+1, char); | ||
1385 | for (i = 0; i < w*h; i++) | ||
1386 | (*aux)[i] = (grid[i] < 10) ? (grid[i] + '0') : (grid[i] + 'A' - 10); | ||
1387 | (*aux)[w*h] = '\0'; | ||
1388 | |||
1389 | sfree(grid); | ||
1390 | sfree(clues); | ||
1391 | |||
1392 | return desc; | ||
1393 | } | ||
1394 | |||
1395 | static char *validate_desc(const game_params *params, const char *desc) | ||
1396 | { | ||
1397 | int i, sizesofar; | ||
1398 | const int totalsize = params->w * params->h; | ||
1399 | |||
1400 | sizesofar = 0; | ||
1401 | for (i = 0; desc[i]; i++) { | ||
1402 | if (desc[i] >= 'a' && desc[i] <= 'z') | ||
1403 | sizesofar += desc[i] - 'a' + 1; | ||
1404 | else if (desc[i] == 'B' || desc[i] == 'W') | ||
1405 | sizesofar++; | ||
1406 | else | ||
1407 | return "unrecognised character in string"; | ||
1408 | } | ||
1409 | |||
1410 | if (sizesofar > totalsize) | ||
1411 | return "string too long"; | ||
1412 | else if (sizesofar < totalsize) | ||
1413 | return "string too short"; | ||
1414 | |||
1415 | return NULL; | ||
1416 | } | ||
1417 | |||
1418 | static game_state *new_game(midend *me, const game_params *params, | ||
1419 | const char *desc) | ||
1420 | { | ||
1421 | game_state *state = snew(game_state); | ||
1422 | int i, j, sz = params->w*params->h; | ||
1423 | |||
1424 | state->completed = state->used_solve = FALSE; | ||
1425 | state->shared = snew(struct shared_state); | ||
1426 | |||
1427 | state->shared->w = params->w; | ||
1428 | state->shared->h = params->h; | ||
1429 | state->shared->sz = sz; | ||
1430 | state->shared->refcnt = 1; | ||
1431 | state->shared->clues = snewn(sz, char); | ||
1432 | for (i = j = 0; desc[i]; i++) { | ||
1433 | assert(j < sz); | ||
1434 | if (desc[i] >= 'a' && desc[i] <= 'z') { | ||
1435 | int n = desc[i] - 'a' + 1; | ||
1436 | assert(j + n <= sz); | ||
1437 | while (n-- > 0) | ||
1438 | state->shared->clues[j++] = NOCLUE; | ||
1439 | } else if (desc[i] == 'B') { | ||
1440 | state->shared->clues[j++] = CORNER; | ||
1441 | } else if (desc[i] == 'W') { | ||
1442 | state->shared->clues[j++] = STRAIGHT; | ||
1443 | } | ||
1444 | } | ||
1445 | |||
1446 | state->lines = snewn(sz, char); | ||
1447 | state->errors = snewn(sz, char); | ||
1448 | state->marks = snewn(sz, char); | ||
1449 | for (i = 0; i < sz; i++) | ||
1450 | state->lines[i] = state->errors[i] = state->marks[i] = BLANK; | ||
1451 | |||
1452 | return state; | ||
1453 | } | ||
1454 | |||
1455 | static game_state *dup_game(const game_state *state) | ||
1456 | { | ||
1457 | game_state *ret = snew(game_state); | ||
1458 | int sz = state->shared->sz, i; | ||
1459 | |||
1460 | ret->shared = state->shared; | ||
1461 | ret->completed = state->completed; | ||
1462 | ret->used_solve = state->used_solve; | ||
1463 | ++ret->shared->refcnt; | ||
1464 | |||
1465 | ret->lines = snewn(sz, char); | ||
1466 | ret->errors = snewn(sz, char); | ||
1467 | ret->marks = snewn(sz, char); | ||
1468 | for (i = 0; i < sz; i++) { | ||
1469 | ret->lines[i] = state->lines[i]; | ||
1470 | ret->errors[i] = state->errors[i]; | ||
1471 | ret->marks[i] = state->marks[i]; | ||
1472 | } | ||
1473 | |||
1474 | return ret; | ||
1475 | } | ||
1476 | |||
1477 | static void free_game(game_state *state) | ||
1478 | { | ||
1479 | assert(state); | ||
1480 | if (--state->shared->refcnt == 0) { | ||
1481 | sfree(state->shared->clues); | ||
1482 | sfree(state->shared); | ||
1483 | } | ||
1484 | sfree(state->lines); | ||
1485 | sfree(state->errors); | ||
1486 | sfree(state->marks); | ||
1487 | sfree(state); | ||
1488 | } | ||
1489 | |||
1490 | static char nbits[16] = { 0, 1, 1, 2, | ||
1491 | 1, 2, 2, 3, | ||
1492 | 1, 2, 2, 3, | ||
1493 | 2, 3, 3, 4 }; | ||
1494 | #define NBITS(l) ( ((l) < 0 || (l) > 15) ? 4 : nbits[l] ) | ||
1495 | |||
1496 | #define ERROR_CLUE 16 | ||
1497 | |||
1498 | static void dsf_update_completion(game_state *state, int ax, int ay, char dir, | ||
1499 | int *dsf) | ||
1500 | { | ||
1501 | int w = state->shared->w /*, h = state->shared->h */; | ||
1502 | int ac = ay*w+ax, bx, by, bc; | ||
1503 | |||
1504 | if (!(state->lines[ac] & dir)) return; /* no link */ | ||
1505 | bx = ax + DX(dir); by = ay + DY(dir); | ||
1506 | |||
1507 | assert(INGRID(state, bx, by)); /* should not have a link off grid */ | ||
1508 | |||
1509 | bc = by*w+bx; | ||
1510 | assert(state->lines[bc] & F(dir)); /* should have reciprocal link */ | ||
1511 | if (!(state->lines[bc] & F(dir))) return; | ||
1512 | |||
1513 | dsf_merge(dsf, ac, bc); | ||
1514 | } | ||
1515 | |||
1516 | static void check_completion(game_state *state, int mark) | ||
1517 | { | ||
1518 | int w = state->shared->w, h = state->shared->h, x, y, i, d; | ||
1519 | int had_error = FALSE; | ||
1520 | int *dsf, *component_state; | ||
1521 | int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize; | ||
1522 | enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY }; | ||
1523 | |||
1524 | if (mark) { | ||
1525 | for (i = 0; i < w*h; i++) { | ||
1526 | state->errors[i] = 0; | ||
1527 | } | ||
1528 | } | ||
1529 | |||
1530 | #define ERROR(x,y,e) do { had_error = TRUE; if (mark) state->errors[(y)*w+(x)] |= (e); } while(0) | ||
1531 | |||
1532 | /* | ||
1533 | * Analyse the solution into loops, paths and stranger things. | ||
1534 | * Basic strategy here is all the same as in Loopy - see the big | ||
1535 | * comment in loopy.c's check_completion() - and for exactly the | ||
1536 | * same reasons, since Loopy and Pearl have basically the same | ||
1537 | * form of expected solution. | ||
1538 | */ | ||
1539 | dsf = snew_dsf(w*h); | ||
1540 | |||
1541 | /* Build the dsf. */ | ||
1542 | for (x = 0; x < w; x++) { | ||
1543 | for (y = 0; y < h; y++) { | ||
1544 | dsf_update_completion(state, x, y, R, dsf); | ||
1545 | dsf_update_completion(state, x, y, D, dsf); | ||
1546 | } | ||
1547 | } | ||
1548 | |||
1549 | /* Initialise a state variable for each connected component. */ | ||
1550 | component_state = snewn(w*h, int); | ||
1551 | for (i = 0; i < w*h; i++) { | ||
1552 | if (dsf_canonify(dsf, i) == i) | ||
1553 | component_state[i] = COMP_LOOP; | ||
1554 | else | ||
1555 | component_state[i] = COMP_NONE; | ||
1556 | } | ||
1557 | |||
1558 | /* | ||
1559 | * Classify components, and mark errors where a square has more | ||
1560 | * than two line segments. | ||
1561 | */ | ||
1562 | for (x = 0; x < w; x++) { | ||
1563 | for (y = 0; y < h; y++) { | ||
1564 | int type = state->lines[y*w+x]; | ||
1565 | int degree = NBITS(type); | ||
1566 | int comp = dsf_canonify(dsf, y*w+x); | ||
1567 | if (degree > 2) { | ||
1568 | ERROR(x,y,type); | ||
1569 | component_state[comp] = COMP_SILLY; | ||
1570 | } else if (degree == 0) { | ||
1571 | component_state[comp] = COMP_EMPTY; | ||
1572 | } else if (degree == 1) { | ||
1573 | if (component_state[comp] != COMP_SILLY) | ||
1574 | component_state[comp] = COMP_PATH; | ||
1575 | } | ||
1576 | } | ||
1577 | } | ||
1578 | |||
1579 | /* Count the components, and find the largest sensible one. */ | ||
1580 | nsilly = nloop = npath = 0; | ||
1581 | total_pathsize = 0; | ||
1582 | largest_comp = largest_size = -1; | ||
1583 | for (i = 0; i < w*h; i++) { | ||
1584 | if (component_state[i] == COMP_SILLY) { | ||
1585 | nsilly++; | ||
1586 | } else if (component_state[i] == COMP_PATH) { | ||
1587 | total_pathsize += dsf_size(dsf, i); | ||
1588 | npath = 1; | ||
1589 | } else if (component_state[i] == COMP_LOOP) { | ||
1590 | int this_size; | ||
1591 | |||
1592 | nloop++; | ||
1593 | |||
1594 | if ((this_size = dsf_size(dsf, i)) > largest_size) { | ||
1595 | largest_comp = i; | ||
1596 | largest_size = this_size; | ||
1597 | } | ||
1598 | } | ||
1599 | } | ||
1600 | if (largest_size < total_pathsize) { | ||
1601 | largest_comp = -1; /* means the paths */ | ||
1602 | largest_size = total_pathsize; | ||
1603 | } | ||
1604 | |||
1605 | if (nloop > 0 && nloop + npath > 1) { | ||
1606 | /* | ||
1607 | * If there are at least two sensible components including at | ||
1608 | * least one loop, highlight every sensible component that is | ||
1609 | * not the largest one. | ||
1610 | */ | ||
1611 | for (i = 0; i < w*h; i++) { | ||
1612 | int comp = dsf_canonify(dsf, i); | ||
1613 | if (component_state[comp] == COMP_PATH) | ||
1614 | comp = -1; /* part of the 'all paths' quasi-component */ | ||
1615 | if ((component_state[comp] == COMP_PATH && | ||
1616 | -1 != largest_comp) || | ||
1617 | (component_state[comp] == COMP_LOOP && | ||
1618 | comp != largest_comp)) | ||
1619 | ERROR(i%w, i/w, state->lines[i]); | ||
1620 | } | ||
1621 | } | ||
1622 | |||
1623 | /* Now we've finished with the dsf and component states. The only | ||
1624 | * thing we'll need to remember later on is whether all edges were | ||
1625 | * part of a single loop, for which our counter variables | ||
1626 | * nsilly,nloop,npath are enough. */ | ||
1627 | sfree(component_state); | ||
1628 | sfree(dsf); | ||
1629 | |||
1630 | /* | ||
1631 | * Check that no clues are contradicted. This code is similar to | ||
1632 | * the code that sets up the maximal clue array for any given | ||
1633 | * loop. | ||
1634 | */ | ||
1635 | for (x = 0; x < w; x++) { | ||
1636 | for (y = 0; y < h; y++) { | ||
1637 | int type = state->lines[y*w+x]; | ||
1638 | if (state->shared->clues[y*w+x] == CORNER) { | ||
1639 | /* Supposed to be a corner: will find a contradiction if | ||
1640 | * it actually contains a straight line, or if it touches any | ||
1641 | * corners. */ | ||
1642 | if ((bLR|bUD) & (1 << type)) { | ||
1643 | ERROR(x,y,ERROR_CLUE); /* actually straight */ | ||
1644 | } | ||
1645 | for (d = 1; d <= 8; d += d) if (type & d) { | ||
1646 | int xx = x + DX(d), yy = y + DY(d); | ||
1647 | if (!INGRID(state, xx, yy)) { | ||
1648 | ERROR(x,y,d); /* leads off grid */ | ||
1649 | } else { | ||
1650 | if ((bLU|bLD|bRU|bRD) & (1 << state->lines[yy*w+xx])) { | ||
1651 | ERROR(x,y,ERROR_CLUE); /* touches corner */ | ||
1652 | } | ||
1653 | } | ||
1654 | } | ||
1655 | } else if (state->shared->clues[y*w+x] == STRAIGHT) { | ||
1656 | /* Supposed to be straight: will find a contradiction if | ||
1657 | * it actually contains a corner, or if it only touches | ||
1658 | * straight lines. */ | ||
1659 | if ((bLU|bLD|bRU|bRD) & (1 << type)) { | ||
1660 | ERROR(x,y,ERROR_CLUE); /* actually a corner */ | ||
1661 | } | ||
1662 | i = 0; | ||
1663 | for (d = 1; d <= 8; d += d) if (type & d) { | ||
1664 | int xx = x + DX(d), yy = y + DY(d); | ||
1665 | if (!INGRID(state, xx, yy)) { | ||
1666 | ERROR(x,y,d); /* leads off grid */ | ||
1667 | } else { | ||
1668 | if ((bLR|bUD) & (1 << state->lines[yy*w+xx])) | ||
1669 | i++; /* a straight */ | ||
1670 | } | ||
1671 | } | ||
1672 | if (i >= 2 && NBITS(type) >= 2) { | ||
1673 | ERROR(x,y,ERROR_CLUE); /* everything touched is straight */ | ||
1674 | } | ||
1675 | } | ||
1676 | } | ||
1677 | } | ||
1678 | |||
1679 | if (nloop == 1 && nsilly == 0 && npath == 0) { | ||
1680 | /* | ||
1681 | * If there's exactly one loop (so that the puzzle is at least | ||
1682 | * potentially complete), we need to ensure it hasn't left any | ||
1683 | * clue out completely. | ||
1684 | */ | ||
1685 | for (x = 0; x < w; x++) { | ||
1686 | for (y = 0; y < h; y++) { | ||
1687 | if (state->lines[y*w+x] == BLANK) { | ||
1688 | if (state->shared->clues[y*w+x] != NOCLUE) { | ||
1689 | /* the loop doesn't include this clue square! */ | ||
1690 | ERROR(x, y, ERROR_CLUE); | ||
1691 | } | ||
1692 | } | ||
1693 | } | ||
1694 | } | ||
1695 | |||
1696 | /* | ||
1697 | * But if not, then we're done! | ||
1698 | */ | ||
1699 | if (!had_error) | ||
1700 | state->completed = TRUE; | ||
1701 | } | ||
1702 | } | ||
1703 | |||
1704 | /* completion check: | ||
1705 | * | ||
1706 | * - no clues must be contradicted (highlight clue itself in error if so) | ||
1707 | * - if there is a closed loop it must include every line segment laid | ||
1708 | * - if there's a smaller closed loop then highlight whole loop as error | ||
1709 | * - no square must have more than 2 lines radiating from centre point | ||
1710 | * (highlight all lines in that square as error if so) | ||
1711 | */ | ||
1712 | |||
1713 | static char *solve_for_diff(game_state *state, char *old_lines, char *new_lines) | ||
1714 | { | ||
1715 | int w = state->shared->w, h = state->shared->h, i; | ||
1716 | char *move = snewn(w*h*40, char), *p = move; | ||
1717 | |||
1718 | *p++ = 'S'; | ||
1719 | for (i = 0; i < w*h; i++) { | ||
1720 | if (old_lines[i] != new_lines[i]) { | ||
1721 | p += sprintf(p, ";R%d,%d,%d", new_lines[i], i%w, i/w); | ||
1722 | } | ||
1723 | } | ||
1724 | *p++ = '\0'; | ||
1725 | move = sresize(move, p - move, char); | ||
1726 | |||
1727 | return move; | ||
1728 | } | ||
1729 | |||
1730 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
1731 | const char *aux, char **error) | ||
1732 | { | ||
1733 | game_state *solved = dup_game(state); | ||
1734 | int i, ret, sz = state->shared->sz; | ||
1735 | char *move; | ||
1736 | |||
1737 | if (aux) { | ||
1738 | for (i = 0; i < sz; i++) { | ||
1739 | if (aux[i] >= '0' && aux[i] <= '9') | ||
1740 | solved->lines[i] = aux[i] - '0'; | ||
1741 | else if (aux[i] >= 'A' && aux[i] <= 'F') | ||
1742 | solved->lines[i] = aux[i] - 'A' + 10; | ||
1743 | else { | ||
1744 | *error = "invalid char in aux"; | ||
1745 | move = NULL; | ||
1746 | goto done; | ||
1747 | } | ||
1748 | } | ||
1749 | ret = 1; | ||
1750 | } else { | ||
1751 | /* Try to solve with present (half-solved) state first: if there's no | ||
1752 | * solution from there go back to original state. */ | ||
1753 | ret = pearl_solve(currstate->shared->w, currstate->shared->h, | ||
1754 | currstate->shared->clues, solved->lines, | ||
1755 | DIFFCOUNT, FALSE); | ||
1756 | if (ret < 1) | ||
1757 | ret = pearl_solve(state->shared->w, state->shared->h, | ||
1758 | state->shared->clues, solved->lines, | ||
1759 | DIFFCOUNT, FALSE); | ||
1760 | |||
1761 | } | ||
1762 | |||
1763 | if (ret < 1) { | ||
1764 | *error = "Unable to find solution"; | ||
1765 | move = NULL; | ||
1766 | } else { | ||
1767 | move = solve_for_diff(solved, currstate->lines, solved->lines); | ||
1768 | } | ||
1769 | |||
1770 | done: | ||
1771 | free_game(solved); | ||
1772 | return move; | ||
1773 | } | ||
1774 | |||
1775 | static int game_can_format_as_text_now(const game_params *params) | ||
1776 | { | ||
1777 | return TRUE; | ||
1778 | } | ||
1779 | |||
1780 | static char *game_text_format(const game_state *state) | ||
1781 | { | ||
1782 | int w = state->shared->w, h = state->shared->h, cw = 4, ch = 2; | ||
1783 | int gw = cw*(w-1) + 2, gh = ch*(h-1) + 1, len = gw * gh, r, c, j; | ||
1784 | char *board = snewn(len + 1, char); | ||
1785 | |||
1786 | assert(board); | ||
1787 | memset(board, ' ', len); | ||
1788 | |||
1789 | for (r = 0; r < h; ++r) { | ||
1790 | for (c = 0; c < w; ++c) { | ||
1791 | int i = r*w + c, cell = r*ch*gw + c*cw; | ||
1792 | board[cell] = "+BW"[(unsigned char)state->shared->clues[i]]; | ||
1793 | if (c < w - 1 && (state->lines[i] & R || state->lines[i+1] & L)) | ||
1794 | memset(board + cell + 1, '-', cw - 1); | ||
1795 | if (r < h - 1 && (state->lines[i] & D || state->lines[i+w] & U)) | ||
1796 | for (j = 1; j < ch; ++j) board[cell + j*gw] = '|'; | ||
1797 | if (c < w - 1 && (state->marks[i] & R || state->marks[i+1] & L)) | ||
1798 | board[cell + cw/2] = 'x'; | ||
1799 | if (r < h - 1 && (state->marks[i] & D || state->marks[i+w] & U)) | ||
1800 | board[cell + (ch/2 * gw)] = 'x'; | ||
1801 | } | ||
1802 | |||
1803 | for (j = 0; j < (r == h - 1 ? 1 : ch); ++j) | ||
1804 | board[r*ch*gw + (gw - 1) + j*gw] = '\n'; | ||
1805 | } | ||
1806 | |||
1807 | board[len] = '\0'; | ||
1808 | return board; | ||
1809 | } | ||
1810 | |||
1811 | struct game_ui { | ||
1812 | int *dragcoords; /* list of (y*w+x) coords in drag so far */ | ||
1813 | int ndragcoords; /* number of entries in dragcoords. | ||
1814 | * 0 = click but no drag yet. -1 = no drag at all */ | ||
1815 | int clickx, clicky; /* pixel position of initial click */ | ||
1816 | |||
1817 | int curx, cury; /* grid position of keyboard cursor */ | ||
1818 | int cursor_active; /* TRUE iff cursor is shown */ | ||
1819 | }; | ||
1820 | |||
1821 | static game_ui *new_ui(const game_state *state) | ||
1822 | { | ||
1823 | game_ui *ui = snew(game_ui); | ||
1824 | int sz = state->shared->sz; | ||
1825 | |||
1826 | ui->ndragcoords = -1; | ||
1827 | ui->dragcoords = snewn(sz, int); | ||
1828 | ui->cursor_active = FALSE; | ||
1829 | ui->curx = ui->cury = 0; | ||
1830 | |||
1831 | return ui; | ||
1832 | } | ||
1833 | |||
1834 | static void free_ui(game_ui *ui) | ||
1835 | { | ||
1836 | sfree(ui->dragcoords); | ||
1837 | sfree(ui); | ||
1838 | } | ||
1839 | |||
1840 | static char *encode_ui(const game_ui *ui) | ||
1841 | { | ||
1842 | return NULL; | ||
1843 | } | ||
1844 | |||
1845 | static void decode_ui(game_ui *ui, const char *encoding) | ||
1846 | { | ||
1847 | } | ||
1848 | |||
1849 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
1850 | const game_state *newstate) | ||
1851 | { | ||
1852 | } | ||
1853 | |||
1854 | #define PREFERRED_TILE_SIZE 31 | ||
1855 | #define HALFSZ (ds->halfsz) | ||
1856 | #define TILE_SIZE (ds->halfsz*2 + 1) | ||
1857 | |||
1858 | #define BORDER ((get_gui_style() == GUI_LOOPY) ? (TILE_SIZE/8) : (TILE_SIZE/2)) | ||
1859 | |||
1860 | #define BORDER_WIDTH (max(TILE_SIZE / 32, 1)) | ||
1861 | |||
1862 | #define COORD(x) ( (x) * TILE_SIZE + BORDER ) | ||
1863 | #define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 ) | ||
1864 | #define FROMCOORD(x) ( ((x) < BORDER) ? -1 : ( ((x) - BORDER) / TILE_SIZE) ) | ||
1865 | |||
1866 | #define DS_ESHIFT 4 /* R/U/L/D shift, for error flags */ | ||
1867 | #define DS_DSHIFT 8 /* R/U/L/D shift, for drag-in-progress flags */ | ||
1868 | #define DS_MSHIFT 12 /* shift for no-line mark */ | ||
1869 | |||
1870 | #define DS_ERROR_CLUE (1 << 20) | ||
1871 | #define DS_FLASH (1 << 21) | ||
1872 | #define DS_CURSOR (1 << 22) | ||
1873 | |||
1874 | enum { GUI_MASYU, GUI_LOOPY }; | ||
1875 | |||
1876 | static int get_gui_style(void) | ||
1877 | { | ||
1878 | static int gui_style = -1; | ||
1879 | |||
1880 | if (gui_style == -1) { | ||
1881 | char *env = getenv("PEARL_GUI_LOOPY"); | ||
1882 | if (env && (env[0] == 'y' || env[0] == 'Y')) | ||
1883 | gui_style = GUI_LOOPY; | ||
1884 | else | ||
1885 | gui_style = GUI_MASYU; | ||
1886 | } | ||
1887 | return gui_style; | ||
1888 | } | ||
1889 | |||
1890 | struct game_drawstate { | ||
1891 | int halfsz; | ||
1892 | int started; | ||
1893 | |||
1894 | int w, h, sz; | ||
1895 | unsigned int *lflags; /* size w*h */ | ||
1896 | |||
1897 | char *draglines; /* size w*h; lines flipped by current drag */ | ||
1898 | }; | ||
1899 | |||
1900 | static void update_ui_drag(const game_state *state, game_ui *ui, | ||
1901 | int gx, int gy) | ||
1902 | { | ||
1903 | int /* sz = state->shared->sz, */ w = state->shared->w; | ||
1904 | int i, ox, oy, pos; | ||
1905 | int lastpos; | ||
1906 | |||
1907 | if (!INGRID(state, gx, gy)) | ||
1908 | return; /* square is outside grid */ | ||
1909 | |||
1910 | if (ui->ndragcoords < 0) | ||
1911 | return; /* drag not in progress anyway */ | ||
1912 | |||
1913 | pos = gy * w + gx; | ||
1914 | |||
1915 | lastpos = ui->dragcoords[ui->ndragcoords > 0 ? ui->ndragcoords-1 : 0]; | ||
1916 | if (pos == lastpos) | ||
1917 | return; /* same square as last visited one */ | ||
1918 | |||
1919 | /* Drag confirmed, if it wasn't already. */ | ||
1920 | if (ui->ndragcoords == 0) | ||
1921 | ui->ndragcoords = 1; | ||
1922 | |||
1923 | /* | ||
1924 | * Dragging the mouse into a square that's already been visited by | ||
1925 | * the drag path so far has the effect of truncating the path back | ||
1926 | * to that square, so a player can back out part of an uncommitted | ||
1927 | * drag without having to let go of the mouse. | ||
1928 | */ | ||
1929 | for (i = 0; i < ui->ndragcoords; i++) | ||
1930 | if (pos == ui->dragcoords[i]) { | ||
1931 | ui->ndragcoords = i+1; | ||
1932 | return; | ||
1933 | } | ||
1934 | |||
1935 | /* | ||
1936 | * Otherwise, dragging the mouse into a square that's a rook-move | ||
1937 | * away from the last one on the path extends the path. | ||
1938 | */ | ||
1939 | oy = ui->dragcoords[ui->ndragcoords-1] / w; | ||
1940 | ox = ui->dragcoords[ui->ndragcoords-1] % w; | ||
1941 | if (ox == gx || oy == gy) { | ||
1942 | int dx = (gx < ox ? -1 : gx > ox ? +1 : 0); | ||
1943 | int dy = (gy < oy ? -1 : gy > oy ? +1 : 0); | ||
1944 | int dir = (dy>0 ? D : dy<0 ? U : dx>0 ? R : L); | ||
1945 | while (ox != gx || oy != gy) { | ||
1946 | /* | ||
1947 | * If the drag attempts to cross a 'no line here' mark, | ||
1948 | * stop there. We physically don't allow the user to drag | ||
1949 | * over those marks. | ||
1950 | */ | ||
1951 | if (state->marks[oy*w+ox] & dir) | ||
1952 | break; | ||
1953 | ox += dx; | ||
1954 | oy += dy; | ||
1955 | ui->dragcoords[ui->ndragcoords++] = oy * w + ox; | ||
1956 | } | ||
1957 | } | ||
1958 | |||
1959 | /* | ||
1960 | * Failing that, we do nothing at all: if the user has dragged | ||
1961 | * diagonally across the board, they'll just have to return the | ||
1962 | * mouse to the last known position and do whatever they meant to | ||
1963 | * do again, more slowly and clearly. | ||
1964 | */ | ||
1965 | } | ||
1966 | |||
1967 | /* | ||
1968 | * Routine shared between interpret_move and game_redraw to work out | ||
1969 | * the intended effect of a drag path on the grid. | ||
1970 | * | ||
1971 | * Call it in a loop, like this: | ||
1972 | * | ||
1973 | * int clearing = TRUE; | ||
1974 | * for (i = 0; i < ui->ndragcoords - 1; i++) { | ||
1975 | * int sx, sy, dx, dy, dir, oldstate, newstate; | ||
1976 | * interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy, | ||
1977 | * &dir, &oldstate, &newstate); | ||
1978 | * | ||
1979 | * [do whatever is needed to handle the fact that the drag | ||
1980 | * wants the edge from sx,sy to dx,dy (heading in direction | ||
1981 | * 'dir' at the sx,sy end) to be changed from state oldstate | ||
1982 | * to state newstate, each of which equals either 0 or dir] | ||
1983 | * } | ||
1984 | */ | ||
1985 | static void interpret_ui_drag(const game_state *state, const game_ui *ui, | ||
1986 | int *clearing, int i, int *sx, int *sy, | ||
1987 | int *dx, int *dy, int *dir, | ||
1988 | int *oldstate, int *newstate) | ||
1989 | { | ||
1990 | int w = state->shared->w; | ||
1991 | int sp = ui->dragcoords[i], dp = ui->dragcoords[i+1]; | ||
1992 | *sy = sp/w; | ||
1993 | *sx = sp%w; | ||
1994 | *dy = dp/w; | ||
1995 | *dx = dp%w; | ||
1996 | *dir = (*dy>*sy ? D : *dy<*sy ? U : *dx>*sx ? R : L); | ||
1997 | *oldstate = state->lines[sp] & *dir; | ||
1998 | if (*oldstate) { | ||
1999 | /* | ||
2000 | * The edge we've dragged over was previously | ||
2001 | * present. Set it to absent, unless we've already | ||
2002 | * stopped doing that. | ||
2003 | */ | ||
2004 | *newstate = *clearing ? 0 : *dir; | ||
2005 | } else { | ||
2006 | /* | ||
2007 | * The edge we've dragged over was previously | ||
2008 | * absent. Set it to present, and cancel the | ||
2009 | * 'clearing' flag so that all subsequent edges in | ||
2010 | * the drag are set rather than cleared. | ||
2011 | */ | ||
2012 | *newstate = *dir; | ||
2013 | *clearing = FALSE; | ||
2014 | } | ||
2015 | } | ||
2016 | |||
2017 | static char *mark_in_direction(const game_state *state, int x, int y, int dir, | ||
2018 | int primary, char *buf) | ||
2019 | { | ||
2020 | int w = state->shared->w /*, h = state->shared->h, sz = state->shared->sz */; | ||
2021 | int x2 = x + DX(dir); | ||
2022 | int y2 = y + DY(dir); | ||
2023 | int dir2 = F(dir); | ||
2024 | |||
2025 | char ch = primary ? 'F' : 'M', *other; | ||
2026 | |||
2027 | if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return ""; | ||
2028 | |||
2029 | /* disallow laying a mark over a line, or vice versa. */ | ||
2030 | other = primary ? state->marks : state->lines; | ||
2031 | if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return ""; | ||
2032 | |||
2033 | sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2); | ||
2034 | return dupstr(buf); | ||
2035 | } | ||
2036 | |||
2037 | #define KEY_DIRECTION(btn) (\ | ||
2038 | (btn) == CURSOR_DOWN ? D : (btn) == CURSOR_UP ? U :\ | ||
2039 | (btn) == CURSOR_LEFT ? L : R) | ||
2040 | |||
2041 | static char *interpret_move(const game_state *state, game_ui *ui, | ||
2042 | const game_drawstate *ds, | ||
2043 | int x, int y, int button) | ||
2044 | { | ||
2045 | int w = state->shared->w, h = state->shared->h /*, sz = state->shared->sz */; | ||
2046 | int gx = FROMCOORD(x), gy = FROMCOORD(y), i; | ||
2047 | int release = FALSE; | ||
2048 | char tmpbuf[80]; | ||
2049 | |||
2050 | int shift = button & MOD_SHFT, control = button & MOD_CTRL; | ||
2051 | button &= ~MOD_MASK; | ||
2052 | |||
2053 | if (IS_MOUSE_DOWN(button)) { | ||
2054 | ui->cursor_active = FALSE; | ||
2055 | |||
2056 | if (!INGRID(state, gx, gy)) { | ||
2057 | ui->ndragcoords = -1; | ||
2058 | return NULL; | ||
2059 | } | ||
2060 | |||
2061 | ui->clickx = x; ui->clicky = y; | ||
2062 | ui->dragcoords[0] = gy * w + gx; | ||
2063 | ui->ndragcoords = 0; /* will be 1 once drag is confirmed */ | ||
2064 | |||
2065 | return ""; | ||
2066 | } | ||
2067 | |||
2068 | if (button == LEFT_DRAG && ui->ndragcoords >= 0) { | ||
2069 | update_ui_drag(state, ui, gx, gy); | ||
2070 | return ""; | ||
2071 | } | ||
2072 | |||
2073 | if (IS_MOUSE_RELEASE(button)) release = TRUE; | ||
2074 | |||
2075 | if (IS_CURSOR_MOVE(button)) { | ||
2076 | if (!ui->cursor_active) { | ||
2077 | ui->cursor_active = TRUE; | ||
2078 | } else if (control | shift) { | ||
2079 | char *move; | ||
2080 | if (ui->ndragcoords > 0) return NULL; | ||
2081 | ui->ndragcoords = -1; | ||
2082 | move = mark_in_direction(state, ui->curx, ui->cury, | ||
2083 | KEY_DIRECTION(button), control, tmpbuf); | ||
2084 | if (control && !shift && *move) | ||
2085 | move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE); | ||
2086 | return move; | ||
2087 | } else { | ||
2088 | move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE); | ||
2089 | if (ui->ndragcoords >= 0) | ||
2090 | update_ui_drag(state, ui, ui->curx, ui->cury); | ||
2091 | } | ||
2092 | return ""; | ||
2093 | } | ||
2094 | |||
2095 | if (IS_CURSOR_SELECT(button)) { | ||
2096 | if (!ui->cursor_active) { | ||
2097 | ui->cursor_active = TRUE; | ||
2098 | return ""; | ||
2099 | } else if (button == CURSOR_SELECT) { | ||
2100 | if (ui->ndragcoords == -1) { | ||
2101 | ui->ndragcoords = 0; | ||
2102 | ui->dragcoords[0] = ui->cury * w + ui->curx; | ||
2103 | ui->clickx = CENTERED_COORD(ui->curx); | ||
2104 | ui->clicky = CENTERED_COORD(ui->cury); | ||
2105 | return ""; | ||
2106 | } else release = TRUE; | ||
2107 | } else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) { | ||
2108 | ui->ndragcoords = -1; | ||
2109 | return ""; | ||
2110 | } | ||
2111 | } | ||
2112 | |||
2113 | if (button == 27 || button == '\b') { | ||
2114 | ui->ndragcoords = -1; | ||
2115 | return ""; | ||
2116 | } | ||
2117 | |||
2118 | if (release) { | ||
2119 | if (ui->ndragcoords > 0) { | ||
2120 | /* End of a drag: process the cached line data. */ | ||
2121 | int buflen = 0, bufsize = 256, tmplen; | ||
2122 | char *buf = NULL; | ||
2123 | const char *sep = ""; | ||
2124 | int clearing = TRUE; | ||
2125 | |||
2126 | for (i = 0; i < ui->ndragcoords - 1; i++) { | ||
2127 | int sx, sy, dx, dy, dir, oldstate, newstate; | ||
2128 | interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy, | ||
2129 | &dir, &oldstate, &newstate); | ||
2130 | |||
2131 | if (oldstate != newstate) { | ||
2132 | if (!buf) buf = snewn(bufsize, char); | ||
2133 | tmplen = sprintf(tmpbuf, "%sF%d,%d,%d;F%d,%d,%d", sep, | ||
2134 | dir, sx, sy, F(dir), dx, dy); | ||
2135 | if (buflen + tmplen >= bufsize) { | ||
2136 | bufsize = (buflen + tmplen) * 5 / 4 + 256; | ||
2137 | buf = sresize(buf, bufsize, char); | ||
2138 | } | ||
2139 | strcpy(buf + buflen, tmpbuf); | ||
2140 | buflen += tmplen; | ||
2141 | sep = ";"; | ||
2142 | } | ||
2143 | } | ||
2144 | |||
2145 | ui->ndragcoords = -1; | ||
2146 | |||
2147 | return buf ? buf : ""; | ||
2148 | } else if (ui->ndragcoords == 0) { | ||
2149 | /* Click (or tiny drag). Work out which edge we were | ||
2150 | * closest to. */ | ||
2151 | int cx, cy; | ||
2152 | |||
2153 | ui->ndragcoords = -1; | ||
2154 | |||
2155 | /* | ||
2156 | * We process clicks based on the mouse-down location, | ||
2157 | * because that's more natural for a user to carefully | ||
2158 | * control than the mouse-up. | ||
2159 | */ | ||
2160 | x = ui->clickx; | ||
2161 | y = ui->clicky; | ||
2162 | |||
2163 | gx = FROMCOORD(x); | ||
2164 | gy = FROMCOORD(y); | ||
2165 | cx = CENTERED_COORD(gx); | ||
2166 | cy = CENTERED_COORD(gy); | ||
2167 | |||
2168 | if (!INGRID(state, gx, gy)) return ""; | ||
2169 | |||
2170 | if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) { | ||
2171 | /* TODO closer to centre of grid: process as a cell click not an edge click. */ | ||
2172 | |||
2173 | return ""; | ||
2174 | } else { | ||
2175 | int direction; | ||
2176 | if (abs(x-cx) < abs(y-cy)) { | ||
2177 | /* Closest to top/bottom edge. */ | ||
2178 | direction = (y < cy) ? U : D; | ||
2179 | } else { | ||
2180 | /* Closest to left/right edge. */ | ||
2181 | direction = (x < cx) ? L : R; | ||
2182 | } | ||
2183 | return mark_in_direction(state, gx, gy, direction, | ||
2184 | (button == LEFT_RELEASE), tmpbuf); | ||
2185 | } | ||
2186 | } | ||
2187 | } | ||
2188 | |||
2189 | if (button == 'H' || button == 'h') | ||
2190 | return dupstr("H"); | ||
2191 | |||
2192 | return NULL; | ||
2193 | } | ||
2194 | |||
2195 | static game_state *execute_move(const game_state *state, const char *move) | ||
2196 | { | ||
2197 | int w = state->shared->w, h = state->shared->h; | ||
2198 | char c; | ||
2199 | int x, y, l, n; | ||
2200 | game_state *ret = dup_game(state); | ||
2201 | |||
2202 | debug(("move: %s\n", move)); | ||
2203 | |||
2204 | while (*move) { | ||
2205 | c = *move; | ||
2206 | if (c == 'S') { | ||
2207 | ret->used_solve = TRUE; | ||
2208 | move++; | ||
2209 | } else if (c == 'L' || c == 'N' || c == 'R' || c == 'F' || c == 'M') { | ||
2210 | /* 'line' or 'noline' or 'replace' or 'flip' or 'mark' */ | ||
2211 | move++; | ||
2212 | if (sscanf(move, "%d,%d,%d%n", &l, &x, &y, &n) != 3) | ||
2213 | goto badmove; | ||
2214 | if (!INGRID(state, x, y)) goto badmove; | ||
2215 | if (l < 0 || l > 15) goto badmove; | ||
2216 | |||
2217 | if (c == 'L') | ||
2218 | ret->lines[y*w + x] |= (char)l; | ||
2219 | else if (c == 'N') | ||
2220 | ret->lines[y*w + x] &= ~((char)l); | ||
2221 | else if (c == 'R') { | ||
2222 | ret->lines[y*w + x] = (char)l; | ||
2223 | ret->marks[y*w + x] &= ~((char)l); /* erase marks too */ | ||
2224 | } else if (c == 'F') | ||
2225 | ret->lines[y*w + x] ^= (char)l; | ||
2226 | else if (c == 'M') | ||
2227 | ret->marks[y*w + x] ^= (char)l; | ||
2228 | |||
2229 | /* | ||
2230 | * If we ended up trying to lay a line _over_ a mark, | ||
2231 | * that's a failed move: interpret_move() should have | ||
2232 | * ensured we never received a move string like that in | ||
2233 | * the first place. | ||
2234 | */ | ||
2235 | if ((ret->lines[y*w + x] & (char)l) && | ||
2236 | (ret->marks[y*w + x] & (char)l)) | ||
2237 | goto badmove; | ||
2238 | |||
2239 | move += n; | ||
2240 | } else if (strcmp(move, "H") == 0) { | ||
2241 | pearl_solve(ret->shared->w, ret->shared->h, | ||
2242 | ret->shared->clues, ret->lines, DIFFCOUNT, TRUE); | ||
2243 | for (n = 0; n < w*h; n++) | ||
2244 | ret->marks[n] &= ~ret->lines[n]; /* erase marks too */ | ||
2245 | move++; | ||
2246 | } else { | ||
2247 | goto badmove; | ||
2248 | } | ||
2249 | if (*move == ';') | ||
2250 | move++; | ||
2251 | else if (*move) | ||
2252 | goto badmove; | ||
2253 | } | ||
2254 | |||
2255 | check_completion(ret, TRUE); | ||
2256 | |||
2257 | return ret; | ||
2258 | |||
2259 | badmove: | ||
2260 | free_game(ret); | ||
2261 | return NULL; | ||
2262 | } | ||
2263 | |||
2264 | /* ---------------------------------------------------------------------- | ||
2265 | * Drawing routines. | ||
2266 | */ | ||
2267 | |||
2268 | #define FLASH_TIME 0.5F | ||
2269 | |||
2270 | static void game_compute_size(const game_params *params, int tilesize, | ||
2271 | int *x, int *y) | ||
2272 | { | ||
2273 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | ||
2274 | struct { int halfsz; } ads, *ds = &ads; | ||
2275 | ads.halfsz = (tilesize-1)/2; | ||
2276 | |||
2277 | *x = (params->w) * TILE_SIZE + 2 * BORDER; | ||
2278 | *y = (params->h) * TILE_SIZE + 2 * BORDER; | ||
2279 | } | ||
2280 | |||
2281 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
2282 | const game_params *params, int tilesize) | ||
2283 | { | ||
2284 | ds->halfsz = (tilesize-1)/2; | ||
2285 | } | ||
2286 | |||
2287 | static float *game_colours(frontend *fe, int *ncolours) | ||
2288 | { | ||
2289 | float *ret = snewn(3 * NCOLOURS, float); | ||
2290 | int i; | ||
2291 | |||
2292 | game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); | ||
2293 | |||
2294 | for (i = 0; i < 3; i++) { | ||
2295 | ret[COL_BLACK * 3 + i] = 0.0F; | ||
2296 | ret[COL_WHITE * 3 + i] = 1.0F; | ||
2297 | ret[COL_GRID * 3 + i] = 0.4F; | ||
2298 | } | ||
2299 | |||
2300 | ret[COL_ERROR * 3 + 0] = 1.0F; | ||
2301 | ret[COL_ERROR * 3 + 1] = 0.0F; | ||
2302 | ret[COL_ERROR * 3 + 2] = 0.0F; | ||
2303 | |||
2304 | ret[COL_DRAGON * 3 + 0] = 0.0F; | ||
2305 | ret[COL_DRAGON * 3 + 1] = 0.0F; | ||
2306 | ret[COL_DRAGON * 3 + 2] = 1.0F; | ||
2307 | |||
2308 | ret[COL_DRAGOFF * 3 + 0] = 0.8F; | ||
2309 | ret[COL_DRAGOFF * 3 + 1] = 0.8F; | ||
2310 | ret[COL_DRAGOFF * 3 + 2] = 1.0F; | ||
2311 | |||
2312 | ret[COL_FLASH * 3 + 0] = 1.0F; | ||
2313 | ret[COL_FLASH * 3 + 1] = 1.0F; | ||
2314 | ret[COL_FLASH * 3 + 2] = 1.0F; | ||
2315 | |||
2316 | *ncolours = NCOLOURS; | ||
2317 | |||
2318 | return ret; | ||
2319 | } | ||
2320 | |||
2321 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | ||
2322 | { | ||
2323 | struct game_drawstate *ds = snew(struct game_drawstate); | ||
2324 | int i; | ||
2325 | |||
2326 | ds->halfsz = 0; | ||
2327 | ds->started = FALSE; | ||
2328 | |||
2329 | ds->w = state->shared->w; | ||
2330 | ds->h = state->shared->h; | ||
2331 | ds->sz = state->shared->sz; | ||
2332 | ds->lflags = snewn(ds->sz, unsigned int); | ||
2333 | for (i = 0; i < ds->sz; i++) | ||
2334 | ds->lflags[i] = 0; | ||
2335 | |||
2336 | ds->draglines = snewn(ds->sz, char); | ||
2337 | |||
2338 | return ds; | ||
2339 | } | ||
2340 | |||
2341 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
2342 | { | ||
2343 | sfree(ds->draglines); | ||
2344 | sfree(ds->lflags); | ||
2345 | sfree(ds); | ||
2346 | } | ||
2347 | |||
2348 | static void draw_lines_specific(drawing *dr, game_drawstate *ds, | ||
2349 | int x, int y, unsigned int lflags, | ||
2350 | unsigned int shift, int c) | ||
2351 | { | ||
2352 | int ox = COORD(x), oy = COORD(y); | ||
2353 | int t2 = HALFSZ, t16 = HALFSZ/4; | ||
2354 | int cx = ox + t2, cy = oy + t2; | ||
2355 | int d; | ||
2356 | |||
2357 | /* Draw each of the four directions, where laid (or error, or drag, etc.) */ | ||
2358 | for (d = 1; d < 16; d *= 2) { | ||
2359 | int xoff = t2 * DX(d), yoff = t2 * DY(d); | ||
2360 | int xnudge = abs(t16 * DX(C(d))), ynudge = abs(t16 * DY(C(d))); | ||
2361 | |||
2362 | if ((lflags >> shift) & d) { | ||
2363 | int lx = cx + ((xoff < 0) ? xoff : 0) - xnudge; | ||
2364 | int ly = cy + ((yoff < 0) ? yoff : 0) - ynudge; | ||
2365 | |||
2366 | if (c == COL_DRAGOFF && !(lflags & d)) | ||
2367 | continue; | ||
2368 | if (c == COL_DRAGON && (lflags & d)) | ||
2369 | continue; | ||
2370 | |||
2371 | draw_rect(dr, lx, ly, | ||
2372 | abs(xoff)+2*xnudge+1, | ||
2373 | abs(yoff)+2*ynudge+1, c); | ||
2374 | /* end cap */ | ||
2375 | draw_rect(dr, cx - t16, cy - t16, 2*t16+1, 2*t16+1, c); | ||
2376 | } | ||
2377 | } | ||
2378 | } | ||
2379 | |||
2380 | static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui, | ||
2381 | int x, int y, unsigned int lflags, char clue) | ||
2382 | { | ||
2383 | int ox = COORD(x), oy = COORD(y); | ||
2384 | int t2 = HALFSZ, t16 = HALFSZ/4; | ||
2385 | int cx = ox + t2, cy = oy + t2; | ||
2386 | int d; | ||
2387 | |||
2388 | assert(dr); | ||
2389 | |||
2390 | /* Clip to the grid square. */ | ||
2391 | clip(dr, ox, oy, TILE_SIZE, TILE_SIZE); | ||
2392 | |||
2393 | /* Clear the square. */ | ||
2394 | draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, | ||
2395 | (lflags & DS_CURSOR) ? | ||
2396 | COL_CURSOR_BACKGROUND : COL_BACKGROUND); | ||
2397 | |||
2398 | |||
2399 | if (get_gui_style() == GUI_LOOPY) { | ||
2400 | /* Draw small dot, underneath any lines. */ | ||
2401 | draw_circle(dr, cx, cy, t16, COL_GRID, COL_GRID); | ||
2402 | } else { | ||
2403 | /* Draw outline of grid square */ | ||
2404 | draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID); | ||
2405 | draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID); | ||
2406 | } | ||
2407 | |||
2408 | /* Draw grid: either thin gridlines, or no-line marks. | ||
2409 | * We draw these first because the thick laid lines should be on top. */ | ||
2410 | for (d = 1; d < 16; d *= 2) { | ||
2411 | int xoff = t2 * DX(d), yoff = t2 * DY(d); | ||
2412 | |||
2413 | if ((x == 0 && d == L) || | ||
2414 | (y == 0 && d == U) || | ||
2415 | (x == ds->w-1 && d == R) || | ||
2416 | (y == ds->h-1 && d == D)) | ||
2417 | continue; /* no gridlines out to the border. */ | ||
2418 | |||
2419 | if ((lflags >> DS_MSHIFT) & d) { | ||
2420 | /* either a no-line mark ... */ | ||
2421 | int mx = cx + xoff, my = cy + yoff, msz = t16; | ||
2422 | |||
2423 | draw_line(dr, mx-msz, my-msz, mx+msz, my+msz, COL_BLACK); | ||
2424 | draw_line(dr, mx-msz, my+msz, mx+msz, my-msz, COL_BLACK); | ||
2425 | } else { | ||
2426 | if (get_gui_style() == GUI_LOOPY) { | ||
2427 | /* draw grid lines connecting centre of cells */ | ||
2428 | draw_line(dr, cx, cy, cx+xoff, cy+yoff, COL_GRID); | ||
2429 | } | ||
2430 | } | ||
2431 | } | ||
2432 | |||
2433 | /* Draw each of the four directions, where laid (or error, or drag, etc.) | ||
2434 | * Order is important here, specifically for the eventual colours of the | ||
2435 | * exposed end caps. */ | ||
2436 | draw_lines_specific(dr, ds, x, y, lflags, 0, | ||
2437 | (lflags & DS_FLASH ? COL_FLASH : COL_BLACK)); | ||
2438 | draw_lines_specific(dr, ds, x, y, lflags, DS_ESHIFT, COL_ERROR); | ||
2439 | draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGOFF); | ||
2440 | draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGON); | ||
2441 | |||
2442 | /* Draw a clue, if present */ | ||
2443 | if (clue != NOCLUE) { | ||
2444 | int c = (lflags & DS_FLASH) ? COL_FLASH : | ||
2445 | (clue == STRAIGHT) ? COL_WHITE : COL_BLACK; | ||
2446 | |||
2447 | if (lflags & DS_ERROR_CLUE) /* draw a bigger 'error' clue circle. */ | ||
2448 | draw_circle(dr, cx, cy, TILE_SIZE*3/8, COL_ERROR, COL_ERROR); | ||
2449 | |||
2450 | draw_circle(dr, cx, cy, TILE_SIZE/4, c, COL_BLACK); | ||
2451 | } | ||
2452 | |||
2453 | unclip(dr); | ||
2454 | draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE); | ||
2455 | } | ||
2456 | |||
2457 | static void game_redraw(drawing *dr, game_drawstate *ds, | ||
2458 | const game_state *oldstate, const game_state *state, | ||
2459 | int dir, const game_ui *ui, | ||
2460 | float animtime, float flashtime) | ||
2461 | { | ||
2462 | int w = state->shared->w, h = state->shared->h, sz = state->shared->sz; | ||
2463 | int x, y, force = 0, flashing = 0; | ||
2464 | |||
2465 | if (!ds->started) { | ||
2466 | /* | ||
2467 | * The initial contents of the window are not guaranteed and | ||
2468 | * can vary with front ends. To be on the safe side, all games | ||
2469 | * should start by drawing a big background-colour rectangle | ||
2470 | * covering the whole window. | ||
2471 | */ | ||
2472 | draw_rect(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER, | ||
2473 | COL_BACKGROUND); | ||
2474 | |||
2475 | if (get_gui_style() == GUI_MASYU) { | ||
2476 | /* | ||
2477 | * Smaller black rectangle which is the main grid. | ||
2478 | */ | ||
2479 | draw_rect(dr, BORDER - BORDER_WIDTH, BORDER - BORDER_WIDTH, | ||
2480 | w*TILE_SIZE + 2*BORDER_WIDTH + 1, | ||
2481 | h*TILE_SIZE + 2*BORDER_WIDTH + 1, | ||
2482 | COL_GRID); | ||
2483 | } | ||
2484 | |||
2485 | draw_update(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER); | ||
2486 | |||
2487 | ds->started = TRUE; | ||
2488 | force = 1; | ||
2489 | } | ||
2490 | |||
2491 | if (flashtime > 0 && | ||
2492 | (flashtime <= FLASH_TIME/3 || | ||
2493 | flashtime >= FLASH_TIME*2/3)) | ||
2494 | flashing = DS_FLASH; | ||
2495 | |||
2496 | memset(ds->draglines, 0, sz); | ||
2497 | if (ui->ndragcoords > 0) { | ||
2498 | int i, clearing = TRUE; | ||
2499 | for (i = 0; i < ui->ndragcoords - 1; i++) { | ||
2500 | int sx, sy, dx, dy, dir, oldstate, newstate; | ||
2501 | interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy, | ||
2502 | &dir, &oldstate, &newstate); | ||
2503 | ds->draglines[sy*w+sx] ^= (oldstate ^ newstate); | ||
2504 | ds->draglines[dy*w+dx] ^= (F(oldstate) ^ F(newstate)); | ||
2505 | } | ||
2506 | } | ||
2507 | |||
2508 | for (x = 0; x < w; x++) { | ||
2509 | for (y = 0; y < h; y++) { | ||
2510 | unsigned int f = (unsigned int)state->lines[y*w+x]; | ||
2511 | unsigned int eline = (unsigned int)(state->errors[y*w+x] & (R|U|L|D)); | ||
2512 | |||
2513 | f |= eline << DS_ESHIFT; | ||
2514 | f |= ((unsigned int)ds->draglines[y*w+x]) << DS_DSHIFT; | ||
2515 | f |= ((unsigned int)state->marks[y*w+x]) << DS_MSHIFT; | ||
2516 | |||
2517 | if (state->errors[y*w+x] & ERROR_CLUE) | ||
2518 | f |= DS_ERROR_CLUE; | ||
2519 | |||
2520 | f |= flashing; | ||
2521 | |||
2522 | if (ui->cursor_active && x == ui->curx && y == ui->cury) | ||
2523 | f |= DS_CURSOR; | ||
2524 | |||
2525 | if (f != ds->lflags[y*w+x] || force) { | ||
2526 | ds->lflags[y*w+x] = f; | ||
2527 | draw_square(dr, ds, ui, x, y, f, state->shared->clues[y*w+x]); | ||
2528 | } | ||
2529 | } | ||
2530 | } | ||
2531 | } | ||
2532 | |||
2533 | static float game_anim_length(const game_state *oldstate, | ||
2534 | const game_state *newstate, int dir, game_ui *ui) | ||
2535 | { | ||
2536 | return 0.0F; | ||
2537 | } | ||
2538 | |||
2539 | static float game_flash_length(const game_state *oldstate, | ||
2540 | const game_state *newstate, int dir, game_ui *ui) | ||
2541 | { | ||
2542 | if (!oldstate->completed && newstate->completed && | ||
2543 | !oldstate->used_solve && !newstate->used_solve) | ||
2544 | return FLASH_TIME; | ||
2545 | else | ||
2546 | return 0.0F; | ||
2547 | } | ||
2548 | |||
2549 | static int game_status(const game_state *state) | ||
2550 | { | ||
2551 | return state->completed ? +1 : 0; | ||
2552 | } | ||
2553 | |||
2554 | static int game_timing_state(const game_state *state, game_ui *ui) | ||
2555 | { | ||
2556 | return TRUE; | ||
2557 | } | ||
2558 | |||
2559 | static void game_print_size(const game_params *params, float *x, float *y) | ||
2560 | { | ||
2561 | int pw, ph; | ||
2562 | |||
2563 | /* | ||
2564 | * I'll use 6mm squares by default. | ||
2565 | */ | ||
2566 | game_compute_size(params, 600, &pw, &ph); | ||
2567 | *x = pw / 100.0F; | ||
2568 | *y = ph / 100.0F; | ||
2569 | } | ||
2570 | |||
2571 | static void game_print(drawing *dr, const game_state *state, int tilesize) | ||
2572 | { | ||
2573 | int w = state->shared->w, h = state->shared->h, x, y; | ||
2574 | int black = print_mono_colour(dr, 0); | ||
2575 | int white = print_mono_colour(dr, 1); | ||
2576 | |||
2577 | /* No GUI_LOOPY here: only use the familiar masyu style. */ | ||
2578 | |||
2579 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | ||
2580 | game_drawstate *ds = game_new_drawstate(dr, state); | ||
2581 | game_set_size(dr, ds, NULL, tilesize); | ||
2582 | |||
2583 | /* Draw grid outlines (black). */ | ||
2584 | for (x = 0; x <= w; x++) | ||
2585 | draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), black); | ||
2586 | for (y = 0; y <= h; y++) | ||
2587 | draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), black); | ||
2588 | |||
2589 | for (x = 0; x < w; x++) { | ||
2590 | for (y = 0; y < h; y++) { | ||
2591 | int cx = COORD(x) + HALFSZ, cy = COORD(y) + HALFSZ; | ||
2592 | int clue = state->shared->clues[y*w+x]; | ||
2593 | |||
2594 | draw_lines_specific(dr, ds, x, y, state->lines[y*w+x], 0, black); | ||
2595 | |||
2596 | if (clue != NOCLUE) { | ||
2597 | int c = (clue == CORNER) ? black : white; | ||
2598 | draw_circle(dr, cx, cy, TILE_SIZE/4, c, black); | ||
2599 | } | ||
2600 | } | ||
2601 | } | ||
2602 | |||
2603 | game_free_drawstate(dr, ds); | ||
2604 | } | ||
2605 | |||
2606 | #ifdef COMBINED | ||
2607 | #define thegame pearl | ||
2608 | #endif | ||
2609 | |||
2610 | const struct game thegame = { | ||
2611 | "Pearl", "games.pearl", "pearl", | ||
2612 | default_params, | ||
2613 | game_fetch_preset, | ||
2614 | decode_params, | ||
2615 | encode_params, | ||
2616 | free_params, | ||
2617 | dup_params, | ||
2618 | TRUE, game_configure, custom_params, | ||
2619 | validate_params, | ||
2620 | new_game_desc, | ||
2621 | validate_desc, | ||
2622 | new_game, | ||
2623 | dup_game, | ||
2624 | free_game, | ||
2625 | TRUE, solve_game, | ||
2626 | TRUE, game_can_format_as_text_now, game_text_format, | ||
2627 | new_ui, | ||
2628 | free_ui, | ||
2629 | encode_ui, | ||
2630 | decode_ui, | ||
2631 | game_changed_state, | ||
2632 | interpret_move, | ||
2633 | execute_move, | ||
2634 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, | ||
2635 | game_colours, | ||
2636 | game_new_drawstate, | ||
2637 | game_free_drawstate, | ||
2638 | game_redraw, | ||
2639 | game_anim_length, | ||
2640 | game_flash_length, | ||
2641 | game_status, | ||
2642 | TRUE, FALSE, game_print_size, game_print, | ||
2643 | FALSE, /* wants_statusbar */ | ||
2644 | FALSE, game_timing_state, | ||
2645 | 0, /* flags */ | ||
2646 | }; | ||
2647 | |||
2648 | #ifdef STANDALONE_SOLVER | ||
2649 | |||
2650 | #include <time.h> | ||
2651 | #include <stdarg.h> | ||
2652 | |||
2653 | const char *quis = NULL; | ||
2654 | |||
2655 | static void usage(FILE *out) { | ||
2656 | fprintf(out, "usage: %s <params>\n", quis); | ||
2657 | } | ||
2658 | |||
2659 | static void pnum(int n, int ntot, const char *desc) | ||
2660 | { | ||
2661 | printf("%2.1f%% (%d) %s", (double)n*100.0 / (double)ntot, n, desc); | ||
2662 | } | ||
2663 | |||
2664 | static void start_soak(game_params *p, random_state *rs, int nsecs) | ||
2665 | { | ||
2666 | time_t tt_start, tt_now, tt_last; | ||
2667 | int n = 0, nsolved = 0, nimpossible = 0, ret; | ||
2668 | char *grid, *clues; | ||
2669 | |||
2670 | tt_start = tt_last = time(NULL); | ||
2671 | |||
2672 | /* Currently this generates puzzles of any difficulty (trying to solve it | ||
2673 | * on the maximum difficulty level and not checking it's not too easy). */ | ||
2674 | printf("Soak-testing a %dx%d grid (any difficulty)", p->w, p->h); | ||
2675 | if (nsecs > 0) printf(" for %d seconds", nsecs); | ||
2676 | printf(".\n"); | ||
2677 | |||
2678 | p->nosolve = TRUE; | ||
2679 | |||
2680 | grid = snewn(p->w*p->h, char); | ||
2681 | clues = snewn(p->w*p->h, char); | ||
2682 | |||
2683 | while (1) { | ||
2684 | n += new_clues(p, rs, clues, grid); /* should be 1, with nosolve */ | ||
2685 | |||
2686 | ret = pearl_solve(p->w, p->h, clues, grid, DIFF_TRICKY, FALSE); | ||
2687 | if (ret <= 0) nimpossible++; | ||
2688 | if (ret == 1) nsolved++; | ||
2689 | |||
2690 | tt_now = time(NULL); | ||
2691 | if (tt_now > tt_last) { | ||
2692 | tt_last = tt_now; | ||
2693 | |||
2694 | printf("%d total, %3.1f/s, ", | ||
2695 | n, (double)n / ((double)tt_now - tt_start)); | ||
2696 | pnum(nsolved, n, "solved"); printf(", "); | ||
2697 | printf("%3.1f/s", (double)nsolved / ((double)tt_now - tt_start)); | ||
2698 | if (nimpossible > 0) | ||
2699 | pnum(nimpossible, n, "impossible"); | ||
2700 | printf("\n"); | ||
2701 | } | ||
2702 | if (nsecs > 0 && (tt_now - tt_start) > nsecs) { | ||
2703 | printf("\n"); | ||
2704 | break; | ||
2705 | } | ||
2706 | } | ||
2707 | |||
2708 | sfree(grid); | ||
2709 | sfree(clues); | ||
2710 | } | ||
2711 | |||
2712 | int main(int argc, const char *argv[]) | ||
2713 | { | ||
2714 | game_params *p = NULL; | ||
2715 | random_state *rs = NULL; | ||
2716 | time_t seed = time(NULL); | ||
2717 | char *id = NULL, *err; | ||
2718 | |||
2719 | setvbuf(stdout, NULL, _IONBF, 0); | ||
2720 | |||
2721 | quis = argv[0]; | ||
2722 | |||
2723 | while (--argc > 0) { | ||
2724 | char *p = (char*)(*++argv); | ||
2725 | if (!strcmp(p, "-e") || !strcmp(p, "--seed")) { | ||
2726 | seed = atoi(*++argv); | ||
2727 | argc--; | ||
2728 | } else if (*p == '-') { | ||
2729 | fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); | ||
2730 | usage(stderr); | ||
2731 | exit(1); | ||
2732 | } else { | ||
2733 | id = p; | ||
2734 | } | ||
2735 | } | ||
2736 | |||
2737 | rs = random_new((void*)&seed, sizeof(time_t)); | ||
2738 | p = default_params(); | ||
2739 | |||
2740 | if (id) { | ||
2741 | if (strchr(id, ':')) { | ||
2742 | fprintf(stderr, "soak takes params only.\n"); | ||
2743 | goto done; | ||
2744 | } | ||
2745 | |||
2746 | decode_params(p, id); | ||
2747 | err = validate_params(p, 1); | ||
2748 | if (err) { | ||
2749 | fprintf(stderr, "%s: %s", argv[0], err); | ||
2750 | goto done; | ||
2751 | } | ||
2752 | |||
2753 | start_soak(p, rs, 0); /* run forever */ | ||
2754 | } else { | ||
2755 | int i; | ||
2756 | |||
2757 | for (i = 5; i <= 12; i++) { | ||
2758 | p->w = p->h = i; | ||
2759 | start_soak(p, rs, 5); | ||
2760 | } | ||
2761 | } | ||
2762 | |||
2763 | done: | ||
2764 | free_params(p); | ||
2765 | random_free(rs); | ||
2766 | |||
2767 | return 0; | ||
2768 | } | ||
2769 | |||
2770 | #endif | ||
2771 | |||
2772 | /* vim: set shiftwidth=4 tabstop=8: */ | ||