diff options
Diffstat (limited to 'apps/plugins/puzzles/src/mosaic.c')
-rw-r--r-- | apps/plugins/puzzles/src/mosaic.c | 1626 |
1 files changed, 1626 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/mosaic.c b/apps/plugins/puzzles/src/mosaic.c new file mode 100644 index 0000000000..05a339ded5 --- /dev/null +++ b/apps/plugins/puzzles/src/mosaic.c | |||
@@ -0,0 +1,1626 @@ | |||
1 | /* | ||
2 | * mosaic.c: A puzzle based on a square grid, with some of the tiles | ||
3 | * having clues as to how many black squares are around them. | ||
4 | * the purpose of the game is to find what should be on all tiles (black or | ||
5 | * unmarked) | ||
6 | * | ||
7 | * The game is also known as: ArtMosaico, Count and Darken, Cuenta Y Sombrea, | ||
8 | * Fill-a-Pix, Fill-In, Komsu Karala, Magipic, Majipiku, Mosaico, Mosaik, | ||
9 | * Mozaiek, Nampre Puzzle, Nurie-Puzzle, Oekaki-Pix, Voisimage. | ||
10 | * | ||
11 | * Implementation is loosely based on https://github.com/mordechaim/Mosaic, UI | ||
12 | * interaction is based on the range puzzle in the collection. | ||
13 | */ | ||
14 | |||
15 | #include <assert.h> | ||
16 | #include <ctype.h> | ||
17 | #ifdef NO_TGMATH_H | ||
18 | # include <math.h> | ||
19 | #else | ||
20 | # include <tgmath.h> | ||
21 | #endif | ||
22 | #include <stdio.h> | ||
23 | #include <stdlib.h> | ||
24 | #include <string.h> | ||
25 | |||
26 | #include "puzzles.h" | ||
27 | |||
28 | #define DEFAULT_SIZE 10 | ||
29 | #define DEFAULT_AGGRESSIVENESS true | ||
30 | #define MAX_TILES 10000 | ||
31 | #define MAX_TILES_ERROR "Maximum size is 10000 tiles" | ||
32 | #define DEFAULT_TILE_SIZE 32 | ||
33 | #define DEBUG_IMAGE 1 | ||
34 | #undef DEBUG_IMAGE | ||
35 | #define FLASH_TIME 0.5F | ||
36 | /* To enable debug prints define DEBUG_PRINTS */ | ||
37 | |||
38 | /* Getting the coordinates and returning NULL when out of scope | ||
39 | * The parentheses are needed to avoid order of operations issues | ||
40 | */ | ||
41 | #define get_coords(params, array, x, y) \ | ||
42 | (((x) >= 0 && (y) >= 0) && ((x) < params->width && (y) < params->height)) \ | ||
43 | ? array + ((y)*params->width) + x \ | ||
44 | : NULL | ||
45 | |||
46 | #define COORD_FROM_CELL(d) ((d * ds->tilesize) + ds->tilesize / 2) - 1 | ||
47 | |||
48 | enum { | ||
49 | COL_BACKGROUND = 0, | ||
50 | COL_UNMARKED, | ||
51 | COL_GRID, | ||
52 | COL_MARKED, | ||
53 | COL_BLANK, | ||
54 | COL_TEXT_SOLVED, | ||
55 | COL_ERROR, | ||
56 | COL_CURSOR, | ||
57 | NCOLOURS, | ||
58 | COL_TEXT_DARK = COL_MARKED, | ||
59 | COL_TEXT_LIGHT = COL_BLANK | ||
60 | }; | ||
61 | |||
62 | enum cell_state { | ||
63 | STATE_UNMARKED = 0, | ||
64 | STATE_MARKED = 1, | ||
65 | STATE_BLANK = 2, | ||
66 | STATE_SOLVED = 4, | ||
67 | STATE_ERROR = 8, | ||
68 | STATE_UNMARKED_ERROR = STATE_ERROR | STATE_UNMARKED, | ||
69 | STATE_MARKED_ERROR = STATE_ERROR | STATE_MARKED, | ||
70 | STATE_BLANK_ERROR = STATE_ERROR | STATE_BLANK, | ||
71 | STATE_BLANK_SOLVED = STATE_SOLVED | STATE_BLANK, | ||
72 | STATE_MARKED_SOLVED = STATE_MARKED | STATE_SOLVED, | ||
73 | STATE_OK_NUM = STATE_BLANK | STATE_MARKED | ||
74 | }; | ||
75 | |||
76 | struct game_params { | ||
77 | int width; | ||
78 | int height; | ||
79 | bool aggressive; | ||
80 | }; | ||
81 | |||
82 | typedef struct board_state board_state; | ||
83 | |||
84 | typedef struct needed_list_item needed_list_item; | ||
85 | |||
86 | struct needed_list_item { | ||
87 | int x, y; | ||
88 | needed_list_item *next; | ||
89 | }; | ||
90 | |||
91 | struct game_state { | ||
92 | bool cheating; | ||
93 | int not_completed_clues; | ||
94 | int width; | ||
95 | int height; | ||
96 | char *cells_contents; | ||
97 | board_state *board; | ||
98 | }; | ||
99 | |||
100 | struct board_state { | ||
101 | unsigned int references; | ||
102 | struct board_cell *actual_board; | ||
103 | }; | ||
104 | |||
105 | struct board_cell { | ||
106 | signed char clue; | ||
107 | bool shown; | ||
108 | }; | ||
109 | |||
110 | struct solution_cell { | ||
111 | signed char cell; | ||
112 | bool solved; | ||
113 | bool needed; | ||
114 | }; | ||
115 | |||
116 | struct desc_cell { | ||
117 | char clue; | ||
118 | bool shown; | ||
119 | bool value; | ||
120 | bool full; | ||
121 | bool empty; | ||
122 | }; | ||
123 | |||
124 | struct game_ui { | ||
125 | bool solved; | ||
126 | bool in_progress; | ||
127 | int last_x, last_y, last_state; | ||
128 | int cur_x, cur_y; | ||
129 | bool cur_visible; | ||
130 | }; | ||
131 | |||
132 | struct game_drawstate { | ||
133 | int tilesize; | ||
134 | int *state; | ||
135 | }; | ||
136 | |||
137 | static game_params *default_params(void) | ||
138 | { | ||
139 | game_params *ret = snew(game_params); | ||
140 | |||
141 | ret->width = DEFAULT_SIZE; | ||
142 | ret->height = DEFAULT_SIZE; | ||
143 | ret->aggressive = DEFAULT_AGGRESSIVENESS; | ||
144 | |||
145 | return ret; | ||
146 | } | ||
147 | |||
148 | static bool game_fetch_preset(int i, char **name, game_params **params) | ||
149 | { | ||
150 | const int sizes[6] = { 3, 5, 10, 15, 25, 50 }; | ||
151 | const bool aggressiveness[6] = { true, true, true, true, true, false }; | ||
152 | if (i < 0 || i > 5) { | ||
153 | return false; | ||
154 | } | ||
155 | game_params *res = snew(game_params); | ||
156 | res->height = sizes[i]; | ||
157 | res->width = sizes[i]; | ||
158 | res->aggressive = aggressiveness[i]; | ||
159 | *params = res; | ||
160 | |||
161 | char value[80]; | ||
162 | sprintf(value, "Size: %dx%d", sizes[i], sizes[i]); | ||
163 | *name = dupstr(value); | ||
164 | return true; | ||
165 | } | ||
166 | |||
167 | static void free_params(game_params *params) | ||
168 | { | ||
169 | sfree(params); | ||
170 | } | ||
171 | |||
172 | static game_params *dup_params(const game_params *params) | ||
173 | { | ||
174 | game_params *ret = snew(game_params); | ||
175 | *ret = *params; /* structure copy */ | ||
176 | return ret; | ||
177 | } | ||
178 | |||
179 | static void decode_params(game_params *params, char const *string) | ||
180 | { | ||
181 | params->width = params->height = atoi(string); | ||
182 | while (*string && isdigit((unsigned char)*string)) string++; | ||
183 | if (*string == 'x') { | ||
184 | string++; | ||
185 | params->height = atoi(string); | ||
186 | while (*string && isdigit((unsigned char)*string)) string++; | ||
187 | } | ||
188 | if (*string == 'h') { | ||
189 | string++; | ||
190 | params->aggressive = atoi(string); | ||
191 | while (*string && isdigit((unsigned char)*string)) string++; | ||
192 | } | ||
193 | } | ||
194 | |||
195 | static char *encode_params(const game_params *params, bool full) | ||
196 | { | ||
197 | char encoded[128]; | ||
198 | int pos = 0; | ||
199 | pos += sprintf(encoded + pos, "%dx%d", params->width, params->height); | ||
200 | if (full) { | ||
201 | if (params->aggressive != DEFAULT_AGGRESSIVENESS) | ||
202 | pos += sprintf(encoded + pos, "h%d", params->aggressive); | ||
203 | } | ||
204 | return dupstr(encoded); | ||
205 | } | ||
206 | |||
207 | static config_item *game_configure(const game_params *params) | ||
208 | { | ||
209 | config_item *config = snewn(4, config_item); | ||
210 | char value[80]; | ||
211 | |||
212 | config[0].type = C_STRING; | ||
213 | config[0].name = "Height"; | ||
214 | sprintf(value, "%d", params->height); | ||
215 | config[0].u.string.sval = dupstr(value); | ||
216 | |||
217 | config[1].type = C_STRING; | ||
218 | config[1].name = "Width"; | ||
219 | sprintf(value, "%d", params->width); | ||
220 | config[1].u.string.sval = dupstr(value); | ||
221 | |||
222 | config[2].name = "Aggressive generation (longer)"; | ||
223 | config[2].type = C_BOOLEAN; | ||
224 | config[2].u.boolean.bval = params->aggressive; | ||
225 | |||
226 | config[3].type = C_END; | ||
227 | |||
228 | return config; | ||
229 | } | ||
230 | |||
231 | static game_params *custom_params(const config_item *cfg) | ||
232 | { | ||
233 | game_params *res = snew(game_params); | ||
234 | res->height = atol(cfg[0].u.string.sval); | ||
235 | res->width = atol(cfg[1].u.string.sval); | ||
236 | res->aggressive = cfg[2].u.boolean.bval; | ||
237 | return res; | ||
238 | } | ||
239 | |||
240 | static const char *validate_params(const game_params *params, bool full) | ||
241 | { | ||
242 | if (params->height < 3 || params->width < 3) { | ||
243 | return "Minimal size is 3x3"; | ||
244 | } | ||
245 | if (params->height > MAX_TILES / params->width) { | ||
246 | return MAX_TILES_ERROR; | ||
247 | } | ||
248 | return NULL; | ||
249 | } | ||
250 | |||
251 | static bool get_pixel(const game_params *params, const bool *image, | ||
252 | const int x, const int y) | ||
253 | { | ||
254 | const bool *pixel; | ||
255 | pixel = get_coords(params, image, x, y); | ||
256 | if (pixel) { | ||
257 | return *pixel; | ||
258 | } | ||
259 | return 0; | ||
260 | } | ||
261 | |||
262 | static void populate_cell(const game_params *params, const bool *image, | ||
263 | const int x, const int y, bool edge, | ||
264 | struct desc_cell *desc) | ||
265 | { | ||
266 | int clue = 0; | ||
267 | bool xEdge = false; | ||
268 | bool yEdge = false; | ||
269 | if (edge) { | ||
270 | if (x > 0) { | ||
271 | clue += get_pixel(params, image, x - 1, y); | ||
272 | if (y > 0) { | ||
273 | clue += get_pixel(params, image, x - 1, y - 1); | ||
274 | } | ||
275 | if (y < params->height - 1) { | ||
276 | clue += get_pixel(params, image, x - 1, y + 1); | ||
277 | } | ||
278 | } else { | ||
279 | xEdge = true; | ||
280 | } | ||
281 | |||
282 | if (y > 0) { | ||
283 | clue += get_pixel(params, image, x, y - 1); | ||
284 | } else { | ||
285 | yEdge = true; | ||
286 | } | ||
287 | if (x < params->width - 1) { | ||
288 | clue += get_pixel(params, image, x + 1, y); | ||
289 | if (y > 0) { | ||
290 | clue += get_pixel(params, image, x + 1, y - 1); | ||
291 | } | ||
292 | if (y < params->height - 1) { | ||
293 | clue += get_pixel(params, image, x + 1, y + 1); | ||
294 | } | ||
295 | } else { | ||
296 | xEdge = true; | ||
297 | } | ||
298 | if (y < params->height - 1) { | ||
299 | clue += get_pixel(params, image, x, y + 1); | ||
300 | } else { | ||
301 | yEdge = true; | ||
302 | } | ||
303 | } else { | ||
304 | clue += get_pixel(params, image, x - 1, y - 1); | ||
305 | clue += get_pixel(params, image, x - 1, y); | ||
306 | clue += get_pixel(params, image, x - 1, y + 1); | ||
307 | clue += get_pixel(params, image, x, y - 1); | ||
308 | clue += get_pixel(params, image, x, y + 1); | ||
309 | clue += get_pixel(params, image, x + 1, y - 1); | ||
310 | clue += get_pixel(params, image, x + 1, y); | ||
311 | clue += get_pixel(params, image, x + 1, y + 1); | ||
312 | } | ||
313 | |||
314 | desc->value = get_pixel(params, image, x, y); | ||
315 | clue += desc->value; | ||
316 | if (clue == 0) { | ||
317 | desc->empty = true; | ||
318 | desc->full = false; | ||
319 | } else { | ||
320 | desc->empty = false; | ||
321 | /* setting the default */ | ||
322 | desc->full = false; | ||
323 | if (clue == 9) { | ||
324 | desc->full = true; | ||
325 | } else if (edge && ((xEdge && yEdge && clue == 4) || | ||
326 | ((xEdge || yEdge) && clue == 6))) { | ||
327 | |||
328 | desc->full = true; | ||
329 | } | ||
330 | } | ||
331 | desc->shown = true; | ||
332 | desc->clue = clue; | ||
333 | } | ||
334 | |||
335 | static void count_around(const game_params *params, | ||
336 | struct solution_cell *sol, int x, int y, | ||
337 | int *marked, int *blank, int *total) | ||
338 | { | ||
339 | int i, j; | ||
340 | struct solution_cell *curr = NULL; | ||
341 | (*total) = 0; | ||
342 | (*blank) = 0; | ||
343 | (*marked) = 0; | ||
344 | |||
345 | for (i = -1; i < 2; i++) { | ||
346 | for (j = -1; j < 2; j++) { | ||
347 | curr = get_coords(params, sol, x + i, y + j); | ||
348 | if (curr) { | ||
349 | (*total)++; | ||
350 | if ((curr->cell & STATE_BLANK) != 0) { | ||
351 | (*blank)++; | ||
352 | } else if ((curr->cell & STATE_MARKED) != 0) { | ||
353 | (*marked)++; | ||
354 | } | ||
355 | } | ||
356 | } | ||
357 | } | ||
358 | } | ||
359 | |||
360 | static void count_around_state(const game_state *state, int x, int y, | ||
361 | int *marked, int *blank, int *total) | ||
362 | { | ||
363 | int i, j; | ||
364 | char *curr = NULL; | ||
365 | (*total) = 0; | ||
366 | (*blank) = 0; | ||
367 | (*marked) = 0; | ||
368 | |||
369 | for (i = -1; i < 2; i++) { | ||
370 | for (j = -1; j < 2; j++) { | ||
371 | curr = get_coords(state, state->cells_contents, x + i, y + j); | ||
372 | if (curr) { | ||
373 | (*total)++; | ||
374 | if ((*curr & STATE_BLANK) != 0) { | ||
375 | (*blank)++; | ||
376 | } else if ((*curr & STATE_MARKED) != 0) { | ||
377 | (*marked)++; | ||
378 | } | ||
379 | } | ||
380 | } | ||
381 | } | ||
382 | } | ||
383 | |||
384 | static void count_clues_around(const game_params *params, | ||
385 | struct desc_cell *desc, int x, int y, | ||
386 | int *clues, int *total) | ||
387 | { | ||
388 | int i, j; | ||
389 | struct desc_cell *curr = NULL; | ||
390 | (*total) = 0; | ||
391 | (*clues) = 0; | ||
392 | |||
393 | for (i = -1; i < 2; i++) { | ||
394 | for (j = -1; j < 2; j++) { | ||
395 | curr = get_coords(params, desc, x + i, y + j); | ||
396 | if (curr) { | ||
397 | (*total)++; | ||
398 | if (curr->shown) { | ||
399 | (*clues)++; | ||
400 | } | ||
401 | } | ||
402 | } | ||
403 | } | ||
404 | } | ||
405 | |||
406 | static void mark_around(const game_params *params, | ||
407 | struct solution_cell *sol, int x, int y, int mark) | ||
408 | { | ||
409 | int i, j; | ||
410 | struct solution_cell *curr; | ||
411 | |||
412 | for (i = -1; i < 2; i++) { | ||
413 | for (j = -1; j < 2; j++) { | ||
414 | curr = get_coords(params, sol, x + i, y + j); | ||
415 | if (curr) { | ||
416 | if (curr->cell == STATE_UNMARKED) { | ||
417 | curr->cell = mark; | ||
418 | } | ||
419 | } | ||
420 | } | ||
421 | } | ||
422 | } | ||
423 | |||
424 | static char solve_cell(const game_params *params, struct desc_cell *desc, | ||
425 | struct board_cell *board, struct solution_cell *sol, | ||
426 | int x, int y) | ||
427 | { | ||
428 | struct desc_cell curr; | ||
429 | |||
430 | if (desc) { | ||
431 | curr.shown = desc[(y * params->width) + x].shown; | ||
432 | curr.clue = desc[(y * params->width) + x].clue; | ||
433 | curr.full = desc[(y * params->width) + x].full; | ||
434 | curr.empty = desc[(y * params->width) + x].empty; | ||
435 | } else { | ||
436 | curr.shown = board[(y * params->width) + x].shown; | ||
437 | curr.clue = board[(y * params->width) + x].clue; | ||
438 | curr.full = false; | ||
439 | curr.empty = false; | ||
440 | } | ||
441 | int marked = 0, total = 0, blank = 0; | ||
442 | |||
443 | if (sol[(y * params->width) + x].solved) { | ||
444 | return 0; | ||
445 | } | ||
446 | count_around(params, sol, x, y, &marked, &blank, &total); | ||
447 | if (curr.full && curr.shown) { | ||
448 | sol[(y * params->width) + x].solved = true; | ||
449 | if (marked + blank < total) { | ||
450 | sol[(y * params->width) + x].needed = true; | ||
451 | } | ||
452 | mark_around(params, sol, x, y, STATE_MARKED); | ||
453 | return 1; | ||
454 | } | ||
455 | if (curr.empty && curr.shown) { | ||
456 | sol[(y * params->width) + x].solved = true; | ||
457 | if (marked + blank < total) { | ||
458 | sol[(y * params->width) + x].needed = true; | ||
459 | } | ||
460 | mark_around(params, sol, x, y, STATE_BLANK); | ||
461 | return 1; | ||
462 | } | ||
463 | if (curr.shown) { | ||
464 | if (!sol[(y * params->width) + x].solved) { | ||
465 | if (marked == curr.clue) { | ||
466 | sol[(y * params->width) + x].solved = true; | ||
467 | if (total != marked + blank) { | ||
468 | sol[(y * params->width) + x].needed = true; | ||
469 | } | ||
470 | mark_around(params, sol, x, y, STATE_BLANK); | ||
471 | } else if (curr.clue == (total - blank)) { | ||
472 | sol[(y * params->width) + x].solved = true; | ||
473 | if (total != marked + blank) { | ||
474 | sol[(y * params->width) + x].needed = true; | ||
475 | } | ||
476 | mark_around(params, sol, x, y, STATE_MARKED); | ||
477 | } else if (total == marked + blank) { | ||
478 | return -1; | ||
479 | } else { | ||
480 | return 0; | ||
481 | } | ||
482 | return 1; | ||
483 | } | ||
484 | return 0; | ||
485 | } else if (total == marked + blank) { | ||
486 | sol[(y * params->width) + x].solved = true; | ||
487 | return 1; | ||
488 | } else { | ||
489 | return 0; | ||
490 | } | ||
491 | } | ||
492 | |||
493 | static bool solve_check(const game_params *params, struct desc_cell *desc, | ||
494 | random_state *rs, struct solution_cell **sol_return) | ||
495 | { | ||
496 | int x, y, i; | ||
497 | int board_size = params->height * params->width; | ||
498 | struct solution_cell *sol = snewn(board_size, struct solution_cell), | ||
499 | *curr_sol; | ||
500 | bool made_progress = true, error = false; | ||
501 | int solved = 0, curr = 0, shown = 0; | ||
502 | needed_list_item *head = NULL, *curr_needed, **needed_array; | ||
503 | struct desc_cell *curr_desc; | ||
504 | |||
505 | memset(sol, 0, board_size * sizeof(*sol)); | ||
506 | for (y = 0; y < params->height; y++) { | ||
507 | for (x = 0; x < params->width; x++) { | ||
508 | curr_desc = get_coords(params, desc, x, y); | ||
509 | if (curr_desc->shown) { | ||
510 | curr_needed = snew(needed_list_item); | ||
511 | curr_needed->next = head; | ||
512 | head = curr_needed; | ||
513 | curr_needed->x = x; | ||
514 | curr_needed->y = y; | ||
515 | shown++; | ||
516 | } | ||
517 | } | ||
518 | } | ||
519 | needed_array = snewn(shown, needed_list_item *); | ||
520 | curr_needed = head; | ||
521 | i = 0; | ||
522 | while (curr_needed) { | ||
523 | needed_array[i] = curr_needed; | ||
524 | curr_needed = curr_needed->next; | ||
525 | i++; | ||
526 | } | ||
527 | if (rs) { | ||
528 | shuffle(needed_array, shown, sizeof(*needed_array), rs); | ||
529 | } | ||
530 | solved = 0; | ||
531 | while (solved < shown && made_progress && !error) { | ||
532 | made_progress = false; | ||
533 | for (i = 0; i < shown; i++) { | ||
534 | curr = solve_cell(params, desc, NULL, sol, needed_array[i]->x, | ||
535 | needed_array[i]->y); | ||
536 | if (curr < 0) { | ||
537 | error = true; | ||
538 | #ifdef DEBUG_PRINTS | ||
539 | printf("error in cell x=%d, y=%d\n", needed_array[i]->x, | ||
540 | needed_array[i]->y); | ||
541 | #endif | ||
542 | break; | ||
543 | } | ||
544 | if (curr > 0) { | ||
545 | solved++; | ||
546 | made_progress = true; | ||
547 | } | ||
548 | } | ||
549 | } | ||
550 | while (head) { | ||
551 | curr_needed = head; | ||
552 | head = curr_needed->next; | ||
553 | sfree(curr_needed); | ||
554 | } | ||
555 | sfree(needed_array); | ||
556 | solved = 0; | ||
557 | /* verifying all the board is solved */ | ||
558 | if (made_progress) { | ||
559 | for (y = 0; y < params->height; y++) { | ||
560 | for (x = 0; x < params->width; x++) { | ||
561 | curr_sol = get_coords(params, sol, x, y); | ||
562 | if ((curr_sol->cell & (STATE_MARKED | STATE_BLANK)) > 0) { | ||
563 | solved++; | ||
564 | } | ||
565 | } | ||
566 | } | ||
567 | } | ||
568 | if (sol_return) { | ||
569 | *sol_return = sol; | ||
570 | } else { | ||
571 | sfree(sol); | ||
572 | } | ||
573 | return solved == board_size; | ||
574 | } | ||
575 | |||
576 | static bool solve_game_actual(const game_params *params, | ||
577 | struct board_cell *desc, | ||
578 | struct solution_cell **sol_return) | ||
579 | { | ||
580 | int x, y; | ||
581 | int board_size = params->height * params->width; | ||
582 | struct solution_cell *sol = snewn(board_size, struct solution_cell); | ||
583 | bool made_progress = true, error = false; | ||
584 | int solved = 0, curr = 0; | ||
585 | |||
586 | memset(sol, 0, params->height * params->width * sizeof(*sol)); | ||
587 | solved = 0; | ||
588 | while (solved < params->height * params->width && made_progress | ||
589 | && !error) { | ||
590 | for (y = 0; y < params->height; y++) { | ||
591 | for (x = 0; x < params->width; x++) { | ||
592 | curr = solve_cell(params, NULL, desc, sol, x, y); | ||
593 | if (curr < 0) { | ||
594 | error = true; | ||
595 | #ifdef DEBUG_PRINTS | ||
596 | printf("error in cell x=%d, y=%d\n", x, y); | ||
597 | #endif | ||
598 | break; | ||
599 | } | ||
600 | if (curr > 0) { | ||
601 | made_progress = true; | ||
602 | } | ||
603 | solved += curr; | ||
604 | } | ||
605 | } | ||
606 | } | ||
607 | if (sol_return) { | ||
608 | *sol_return = sol; | ||
609 | } else { | ||
610 | sfree(sol); | ||
611 | } | ||
612 | return solved == params->height * params->width; | ||
613 | } | ||
614 | |||
615 | static void hide_clues(const game_params *params, struct desc_cell *desc, | ||
616 | random_state *rs) | ||
617 | { | ||
618 | int shown, total, x, y, i; | ||
619 | int needed = 0; | ||
620 | struct desc_cell *curr; | ||
621 | struct solution_cell *sol = NULL, *curr_sol = NULL; | ||
622 | needed_list_item *head = NULL, *curr_needed, **needed_array; | ||
623 | |||
624 | #ifdef DEBUG_PRINTS | ||
625 | printf("Hiding clues\n"); | ||
626 | #endif | ||
627 | solve_check(params, desc, rs, &sol); | ||
628 | for (y = 0; y < params->height; y++) { | ||
629 | for (x = 0; x < params->width; x++) { | ||
630 | count_clues_around(params, desc, x, y, &shown, &total); | ||
631 | curr = get_coords(params, desc, x, y); | ||
632 | curr_sol = get_coords(params, sol, x, y); | ||
633 | if (curr_sol->needed && params->aggressive) { | ||
634 | curr_needed = snew(needed_list_item); | ||
635 | curr_needed->x = x; | ||
636 | curr_needed->y = y; | ||
637 | curr_needed->next = head; | ||
638 | head = curr_needed; | ||
639 | needed++; | ||
640 | } else if (!curr_sol->needed) { | ||
641 | curr->shown = false; | ||
642 | } | ||
643 | } | ||
644 | } | ||
645 | if (params->aggressive) { | ||
646 | curr_needed = head; | ||
647 | needed_array = snewn(needed, needed_list_item *); | ||
648 | memset(needed_array, 0, needed * sizeof(*needed_array)); | ||
649 | i = 0; | ||
650 | while (curr_needed) { | ||
651 | needed_array[i] = curr_needed; | ||
652 | curr_needed = curr_needed->next; | ||
653 | i++; | ||
654 | } | ||
655 | shuffle(needed_array, needed, sizeof(*needed_array), rs); | ||
656 | for (i = 0; i < needed; i++) { | ||
657 | curr_needed = needed_array[i]; | ||
658 | curr = | ||
659 | get_coords(params, desc, curr_needed->x, curr_needed->y); | ||
660 | if (curr) { | ||
661 | curr->shown = false; | ||
662 | if (!solve_check(params, desc, NULL, NULL)) { | ||
663 | #ifdef DEBUG_PRINTS | ||
664 | printf("Hiding cell %d, %d not possible.\n", | ||
665 | curr_needed->x, curr_needed->y); | ||
666 | #endif | ||
667 | curr->shown = true; | ||
668 | } | ||
669 | sfree(curr_needed); | ||
670 | needed_array[i] = NULL; | ||
671 | } | ||
672 | curr_needed = NULL; | ||
673 | } | ||
674 | sfree(needed_array); | ||
675 | } | ||
676 | #ifdef DEBUG_PRINTS | ||
677 | printf("needed %d\n", needed); | ||
678 | #endif | ||
679 | sfree(sol); | ||
680 | } | ||
681 | |||
682 | static bool start_point_check(size_t size, struct desc_cell *desc) | ||
683 | { | ||
684 | int i; | ||
685 | for (i = 0; i < size; i++) { | ||
686 | if (desc[i].empty || desc[i].full) { | ||
687 | return true; | ||
688 | } | ||
689 | } | ||
690 | return false; | ||
691 | } | ||
692 | |||
693 | static void game_get_cursor_location(const game_ui *ui, | ||
694 | const game_drawstate *ds, | ||
695 | const game_state *state, | ||
696 | const game_params *params, int *x, | ||
697 | int *y, int *w, int *h) | ||
698 | { | ||
699 | if (ui->cur_visible) { | ||
700 | *x = COORD_FROM_CELL(ui->cur_x); | ||
701 | *y = COORD_FROM_CELL(ui->cur_y); | ||
702 | *w = *h = ds->tilesize; | ||
703 | } | ||
704 | } | ||
705 | |||
706 | static void generate_image(const game_params *params, random_state *rs, | ||
707 | bool *image) | ||
708 | { | ||
709 | int x, y; | ||
710 | for (y = 0; y < params->height; y++) { | ||
711 | for (x = 0; x < params->width; x++) { | ||
712 | image[(y * params->width) + x] = random_bits(rs, 1); | ||
713 | } | ||
714 | } | ||
715 | } | ||
716 | |||
717 | static char *new_game_desc(const game_params *params, random_state *rs, | ||
718 | char **aux, bool interactive) | ||
719 | { | ||
720 | bool *image = snewn(params->height * params->width, bool); | ||
721 | bool valid = false; | ||
722 | char *desc_string = snewn((params->height * params->width) + 1, char); | ||
723 | char *compressed_desc = | ||
724 | snewn((params->height * params->width) + 1, char); | ||
725 | char space_count; | ||
726 | |||
727 | struct desc_cell *desc = | ||
728 | snewn(params->height * params->width, struct desc_cell); | ||
729 | int x, y, location_in_str; | ||
730 | |||
731 | while (!valid) { | ||
732 | generate_image(params, rs, image); | ||
733 | #ifdef DEBUG_IMAGE | ||
734 | image[0] = 1; | ||
735 | image[1] = 1; | ||
736 | image[2] = 0; | ||
737 | image[3] = 1; | ||
738 | image[4] = 1; | ||
739 | image[5] = 0; | ||
740 | image[6] = 0; | ||
741 | image[7] = 0; | ||
742 | image[8] = 0; | ||
743 | #endif | ||
744 | |||
745 | for (y = 0; y < params->height; y++) { | ||
746 | for (x = 0; x < params->width; x++) { | ||
747 | populate_cell(params, image, x, y, | ||
748 | x * y == 0 || y == params->height - 1 || | ||
749 | x == params->width - 1, | ||
750 | &desc[(y * params->width) + x]); | ||
751 | } | ||
752 | } | ||
753 | valid = | ||
754 | start_point_check((params->height - 1) * (params->width - 1), | ||
755 | desc); | ||
756 | if (!valid) { | ||
757 | #ifdef DEBUG_PRINTS | ||
758 | printf("Not valid, regenerating.\n"); | ||
759 | #endif | ||
760 | } else { | ||
761 | valid = solve_check(params, desc, rs, NULL); | ||
762 | if (!valid) { | ||
763 | #ifdef DEBUG_PRINTS | ||
764 | printf("Couldn't solve, regenerating."); | ||
765 | #endif | ||
766 | } else { | ||
767 | hide_clues(params, desc, rs); | ||
768 | } | ||
769 | } | ||
770 | } | ||
771 | location_in_str = 0; | ||
772 | for (y = 0; y < params->height; y++) { | ||
773 | for (x = 0; x < params->width; x++) { | ||
774 | if (desc[(y * params->width) + x].shown) { | ||
775 | #ifdef DEBUG_PRINTS | ||
776 | printf("%d(%d)", desc[(y * params->width) + x].value, | ||
777 | desc[(y * params->width) + x].clue); | ||
778 | #endif | ||
779 | sprintf(desc_string + location_in_str, "%d", | ||
780 | desc[(y * params->width) + x].clue); | ||
781 | } else { | ||
782 | #ifdef DEBUG_PRINTS | ||
783 | printf("%d( )", desc[(y * params->width) + x].value); | ||
784 | #endif | ||
785 | sprintf(desc_string + location_in_str, " "); | ||
786 | } | ||
787 | location_in_str += 1; | ||
788 | } | ||
789 | #ifdef DEBUG_PRINTS | ||
790 | printf("\n"); | ||
791 | #endif | ||
792 | } | ||
793 | location_in_str = 0; | ||
794 | space_count = 'a' - 1; | ||
795 | for (y = 0; y < params->height; y++) { | ||
796 | for (x = 0; x < params->width; x++) { | ||
797 | if (desc[(y * params->width) + x].shown) { | ||
798 | if (space_count >= 'a') { | ||
799 | sprintf(compressed_desc + location_in_str, "%c", | ||
800 | space_count); | ||
801 | location_in_str++; | ||
802 | space_count = 'a' - 1; | ||
803 | } | ||
804 | sprintf(compressed_desc + location_in_str, "%d", | ||
805 | desc[(y * params->width) + x].clue); | ||
806 | location_in_str++; | ||
807 | } else { | ||
808 | if (space_count <= 'z') { | ||
809 | space_count++; | ||
810 | } else { | ||
811 | sprintf(compressed_desc + location_in_str, "%c", | ||
812 | space_count); | ||
813 | location_in_str++; | ||
814 | space_count = 'a' - 1; | ||
815 | } | ||
816 | } | ||
817 | } | ||
818 | } | ||
819 | if (space_count >= 'a') { | ||
820 | sprintf(compressed_desc + location_in_str, "%c", space_count); | ||
821 | location_in_str++; | ||
822 | } | ||
823 | compressed_desc[location_in_str] = '\0'; | ||
824 | #ifdef DEBUG_PRINTS | ||
825 | printf("compressed_desc: %s\n", compressed_desc); | ||
826 | #endif | ||
827 | return compressed_desc; | ||
828 | } | ||
829 | |||
830 | static const char *validate_desc(const game_params *params, | ||
831 | const char *desc) | ||
832 | { | ||
833 | int size_dest = params->height * params->width; | ||
834 | int length; | ||
835 | length = 0; | ||
836 | |||
837 | while (*desc != '\0') { | ||
838 | if (*desc >= 'a' && *desc <= 'z') { | ||
839 | length += *desc - 'a'; | ||
840 | } else if (*desc < '0' || *desc > '9') | ||
841 | return "Invalid character in game description"; | ||
842 | length++; | ||
843 | desc++; | ||
844 | } | ||
845 | |||
846 | if (length != size_dest) { | ||
847 | return "Desc size mismatch"; | ||
848 | } | ||
849 | return NULL; | ||
850 | } | ||
851 | |||
852 | static game_state *new_game(midend *me, const game_params *params, | ||
853 | const char *desc) | ||
854 | { | ||
855 | game_state *state = snew(game_state); | ||
856 | char *curr_desc = dupstr(desc); | ||
857 | char *desc_base = curr_desc; | ||
858 | int dest_loc; | ||
859 | int spaces, total_spaces; | ||
860 | |||
861 | state->cheating = false; | ||
862 | state->not_completed_clues = 0; | ||
863 | dest_loc = 0; | ||
864 | state->height = params->height; | ||
865 | state->width = params->width; | ||
866 | state->cells_contents = snewn(params->height * params->width, char); | ||
867 | memset(state->cells_contents, 0, params->height * params->width); | ||
868 | state->board = snew(board_state); | ||
869 | state->board->references = 1; | ||
870 | state->board->actual_board = | ||
871 | snewn(params->height * params->width, struct board_cell); | ||
872 | |||
873 | while (*curr_desc != '\0') { | ||
874 | if (*curr_desc >= '0' && *curr_desc <= '9') { | ||
875 | state->board->actual_board[dest_loc].shown = true; | ||
876 | state->not_completed_clues++; | ||
877 | state->board->actual_board[dest_loc].clue = *curr_desc - '0'; | ||
878 | } else { | ||
879 | if (*curr_desc != ' ') { | ||
880 | total_spaces = *curr_desc - 'a' + 1; | ||
881 | } else { | ||
882 | total_spaces = 1; | ||
883 | } | ||
884 | spaces = 0; | ||
885 | while (spaces < total_spaces) { | ||
886 | state->board->actual_board[dest_loc].shown = false; | ||
887 | state->board->actual_board[dest_loc].clue = -1; | ||
888 | spaces++; | ||
889 | if (spaces < total_spaces) { | ||
890 | dest_loc++; | ||
891 | } | ||
892 | } | ||
893 | } | ||
894 | curr_desc++; | ||
895 | dest_loc++; | ||
896 | } | ||
897 | |||
898 | sfree(desc_base); | ||
899 | return state; | ||
900 | } | ||
901 | |||
902 | static game_state *dup_game(const game_state *state) | ||
903 | { | ||
904 | game_state *ret = snew(game_state); | ||
905 | |||
906 | ret->cheating = state->cheating; | ||
907 | ret->not_completed_clues = state->not_completed_clues; | ||
908 | ret->width = state->width; | ||
909 | ret->height = state->height; | ||
910 | ret->cells_contents = snewn(state->height * state->width, char); | ||
911 | memcpy(ret->cells_contents, state->cells_contents, | ||
912 | state->height * state->width); | ||
913 | ret->board = state->board; | ||
914 | ret->board->references++; | ||
915 | |||
916 | return ret; | ||
917 | } | ||
918 | |||
919 | static void free_game(game_state *state) | ||
920 | { | ||
921 | sfree(state->cells_contents); | ||
922 | state->cells_contents = NULL; | ||
923 | if (state->board->references <= 1) { | ||
924 | sfree(state->board->actual_board); | ||
925 | sfree(state->board); | ||
926 | state->board = NULL; | ||
927 | } else { | ||
928 | state->board->references--; | ||
929 | } | ||
930 | sfree(state); | ||
931 | } | ||
932 | |||
933 | static char *solve_game(const game_state *state, | ||
934 | const game_state *currstate, const char *aux, | ||
935 | const char **error) | ||
936 | { | ||
937 | struct solution_cell *sol = NULL; | ||
938 | game_params param; | ||
939 | bool solved; | ||
940 | char *ret = NULL; | ||
941 | unsigned int curr_ret; | ||
942 | int i, bits, ret_loc = 1; | ||
943 | int size = state->width * state->height; | ||
944 | |||
945 | param.width = state->width; | ||
946 | param.height = state->height; | ||
947 | solved = solve_game_actual(¶m, state->board->actual_board, &sol); | ||
948 | if (!solved) { | ||
949 | *error = dupstr("Could not solve this board"); | ||
950 | sfree(sol); | ||
951 | return NULL; | ||
952 | } | ||
953 | |||
954 | ret = snewn((size / 4) + 3, char); | ||
955 | |||
956 | ret[0] = 's'; | ||
957 | i = 0; | ||
958 | while (i < size) { | ||
959 | curr_ret = 0; | ||
960 | bits = 0; | ||
961 | while (bits < 8 && i < size) { | ||
962 | curr_ret <<= 1; | ||
963 | curr_ret |= sol[i].cell == STATE_MARKED; | ||
964 | i++; | ||
965 | bits++; | ||
966 | } | ||
967 | curr_ret <<= 8 - bits; | ||
968 | sprintf(ret + ret_loc, "%02x", curr_ret); | ||
969 | ret_loc += 2; | ||
970 | } | ||
971 | |||
972 | sfree(sol); | ||
973 | return ret; | ||
974 | } | ||
975 | |||
976 | static bool game_can_format_as_text_now(const game_params *params) | ||
977 | { | ||
978 | return true; | ||
979 | } | ||
980 | |||
981 | static char *game_text_format(const game_state *state) | ||
982 | { | ||
983 | char *desc_string = | ||
984 | snewn((state->height * state->width) * 3 + 1, char); | ||
985 | int location_in_str = 0, x, y; | ||
986 | for (y = 0; y < state->height; y++) { | ||
987 | for (x = 0; x < state->width; x++) { | ||
988 | if (state->board->actual_board[(y * state->width) + x].shown) { | ||
989 | sprintf(desc_string + location_in_str, "|%d|", | ||
990 | state->board->actual_board[(y * state->width) + | ||
991 | x].clue); | ||
992 | } else { | ||
993 | sprintf(desc_string + location_in_str, "| |"); | ||
994 | } | ||
995 | location_in_str += 3; | ||
996 | } | ||
997 | sprintf(desc_string + location_in_str, "\n"); | ||
998 | location_in_str += 1; | ||
999 | } | ||
1000 | return desc_string; | ||
1001 | } | ||
1002 | |||
1003 | static game_ui *new_ui(const game_state *state) | ||
1004 | { | ||
1005 | game_ui *ui = snew(game_ui); | ||
1006 | ui->last_x = -1; | ||
1007 | ui->last_y = -1; | ||
1008 | ui->last_state = 0; | ||
1009 | ui->solved = false; | ||
1010 | ui->cur_x = ui->cur_y = 0; | ||
1011 | ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); | ||
1012 | return ui; | ||
1013 | } | ||
1014 | |||
1015 | static void free_ui(game_ui *ui) | ||
1016 | { | ||
1017 | sfree(ui); | ||
1018 | } | ||
1019 | |||
1020 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
1021 | const game_state *newstate) | ||
1022 | { | ||
1023 | } | ||
1024 | |||
1025 | static const char *current_key_label(const game_ui *ui, | ||
1026 | const game_state *state, int button) | ||
1027 | { | ||
1028 | char *cell; | ||
1029 | |||
1030 | if (IS_CURSOR_SELECT(button)) { | ||
1031 | if (!ui->cur_visible || state->not_completed_clues == 0) return ""; | ||
1032 | cell = get_coords(state, state->cells_contents, ui->cur_x, ui->cur_y); | ||
1033 | switch (*cell & STATE_OK_NUM) { | ||
1034 | case STATE_UNMARKED: | ||
1035 | return button == CURSOR_SELECT ? "Black" : "White"; | ||
1036 | case STATE_MARKED: | ||
1037 | return button == CURSOR_SELECT ? "White" : "Empty"; | ||
1038 | case STATE_BLANK: | ||
1039 | return button == CURSOR_SELECT ? "Empty" : "Black"; | ||
1040 | } | ||
1041 | } | ||
1042 | return ""; | ||
1043 | } | ||
1044 | |||
1045 | static char *interpret_move(const game_state *state, game_ui *ui, | ||
1046 | const game_drawstate *ds, int x, int y, | ||
1047 | int button) | ||
1048 | { | ||
1049 | int srcX = ui->last_x, srcY = ui->last_y; | ||
1050 | int offsetX, offsetY, gameX, gameY, i; | ||
1051 | int dirX, dirY, diff; | ||
1052 | char move_type; | ||
1053 | char move_desc[80]; | ||
1054 | char *ret = NULL; | ||
1055 | const char *cell_state; | ||
1056 | bool changed = false; | ||
1057 | if (state->not_completed_clues == 0 && !IS_CURSOR_MOVE(button)) { | ||
1058 | return NULL; | ||
1059 | } | ||
1060 | offsetX = x - (ds->tilesize / 2); | ||
1061 | offsetY = y - (ds->tilesize / 2); | ||
1062 | gameX = offsetX / ds->tilesize; | ||
1063 | gameY = offsetY / ds->tilesize; | ||
1064 | if ((IS_MOUSE_DOWN(button) || IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) | ||
1065 | && ((offsetX < 0) || (offsetY < 0))) | ||
1066 | return NULL; | ||
1067 | if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { | ||
1068 | cell_state = | ||
1069 | get_coords(state, state->cells_contents, gameX, gameY); | ||
1070 | if (cell_state) { | ||
1071 | ui->last_state = *cell_state & (STATE_BLANK | STATE_MARKED); | ||
1072 | ui->last_state = | ||
1073 | (ui->last_state + | ||
1074 | ((button == | ||
1075 | RIGHT_BUTTON) ? 2 : 1)) % (STATE_BLANK | STATE_MARKED); | ||
1076 | } | ||
1077 | if (button == RIGHT_BUTTON) { | ||
1078 | /* Right button toggles twice */ | ||
1079 | move_type = 'T'; | ||
1080 | } else { | ||
1081 | move_type = 't'; | ||
1082 | } | ||
1083 | if (gameX >= 0 && gameY >= 0 && gameX < state->width && | ||
1084 | gameY < state->height) { | ||
1085 | sprintf(move_desc, "%c%d,%d", move_type, gameX, gameY); | ||
1086 | ui->last_x = gameX; | ||
1087 | ui->last_y = gameY; | ||
1088 | ret = dupstr(move_desc); | ||
1089 | } else { | ||
1090 | ui->last_x = -1; | ||
1091 | ui->last_y = -1; | ||
1092 | } | ||
1093 | changed = true; | ||
1094 | ui->cur_visible = false; | ||
1095 | } else if (button == LEFT_DRAG || button == RIGHT_DRAG) { | ||
1096 | move_type = 'd'; | ||
1097 | /* allowing only drags in straight lines */ | ||
1098 | if (gameX >= 0 && gameY >= 0 && gameX < state->width && | ||
1099 | gameY < state->height && ui->last_x >= 0 && ui->last_y >= 0 && | ||
1100 | (gameY == ui->last_y || gameX == ui->last_x)) { | ||
1101 | sprintf(move_desc, "%c%d,%d,%d,%d,%d", move_type, gameX, gameY, | ||
1102 | ui->last_x, ui->last_y, ui->last_state); | ||
1103 | if (srcX == gameX && srcY != gameY) { | ||
1104 | dirX = 0; | ||
1105 | diff = srcY - gameY; | ||
1106 | if (diff < 0) { | ||
1107 | dirY = -1; | ||
1108 | diff *= -1; | ||
1109 | } else { | ||
1110 | dirY = 1; | ||
1111 | } | ||
1112 | } else { | ||
1113 | diff = srcX - gameX; | ||
1114 | dirY = 0; | ||
1115 | if (diff < 0) { | ||
1116 | dirX = -1; | ||
1117 | diff *= -1; | ||
1118 | } else { | ||
1119 | dirX = 1; | ||
1120 | } | ||
1121 | } | ||
1122 | for (i = 0; i < diff; i++) { | ||
1123 | cell_state = get_coords(state, state->cells_contents, | ||
1124 | gameX + (dirX * i), | ||
1125 | gameY + (dirY * i)); | ||
1126 | if (cell_state && (*cell_state & STATE_OK_NUM) == 0 | ||
1127 | && ui->last_state > 0) { | ||
1128 | changed = true; | ||
1129 | break; | ||
1130 | } | ||
1131 | } | ||
1132 | ui->last_x = gameX; | ||
1133 | ui->last_y = gameY; | ||
1134 | if (changed) { | ||
1135 | ret = dupstr(move_desc); | ||
1136 | } | ||
1137 | } else { | ||
1138 | ui->last_x = -1; | ||
1139 | ui->last_y = -1; | ||
1140 | } | ||
1141 | ui->cur_visible = false; | ||
1142 | } else if (button == LEFT_RELEASE || button == RIGHT_RELEASE) { | ||
1143 | move_type = 'e'; | ||
1144 | if (gameX >= 0 && gameY >= 0 && gameX < state->width && | ||
1145 | gameY < state->height && ui->last_x >= 0 && ui->last_y >= 0 && | ||
1146 | (gameY == ui->last_y || gameX == ui->last_x)) { | ||
1147 | sprintf(move_desc, "%c%d,%d,%d,%d,%d", move_type, gameX, gameY, | ||
1148 | ui->last_x, ui->last_y, ui->last_state); | ||
1149 | if (srcX == gameX && srcY != gameY) { | ||
1150 | dirX = 0; | ||
1151 | diff = srcY - gameY; | ||
1152 | if (diff < 0) { | ||
1153 | dirY = -1; | ||
1154 | diff *= -1; | ||
1155 | } else { | ||
1156 | dirY = 1; | ||
1157 | } | ||
1158 | } else { | ||
1159 | diff = srcX - gameX; | ||
1160 | dirY = 0; | ||
1161 | if (diff < 0) { | ||
1162 | dirX = -1; | ||
1163 | diff *= -1; | ||
1164 | } else { | ||
1165 | dirX = 1; | ||
1166 | } | ||
1167 | } | ||
1168 | for (i = 0; i < diff; i++) { | ||
1169 | cell_state = get_coords(state, state->cells_contents, | ||
1170 | gameX + (dirX * i), | ||
1171 | gameY + (dirY * i)); | ||
1172 | if (cell_state && (*cell_state & STATE_OK_NUM) == 0 | ||
1173 | && ui->last_state > 0) { | ||
1174 | changed = true; | ||
1175 | break; | ||
1176 | } | ||
1177 | } | ||
1178 | if (changed) { | ||
1179 | ret = dupstr(move_desc); | ||
1180 | } | ||
1181 | } else { | ||
1182 | ui->last_x = -1; | ||
1183 | ui->last_y = -1; | ||
1184 | } | ||
1185 | ui->cur_visible = false; | ||
1186 | } else if (IS_CURSOR_MOVE(button)) { | ||
1187 | return move_cursor(button, &ui->cur_x, &ui->cur_y, state->width, | ||
1188 | state->height, false, &ui->cur_visible); | ||
1189 | } else if (IS_CURSOR_SELECT(button)) { | ||
1190 | if (!ui->cur_visible) { | ||
1191 | ui->cur_x = 0; | ||
1192 | ui->cur_y = 0; | ||
1193 | ui->cur_visible = true; | ||
1194 | return MOVE_UI_UPDATE; | ||
1195 | } | ||
1196 | |||
1197 | if (button == CURSOR_SELECT2) { | ||
1198 | sprintf(move_desc, "T%d,%d", ui->cur_x, ui->cur_y); | ||
1199 | ret = dupstr(move_desc); | ||
1200 | } else { | ||
1201 | /* Otherwise, treat as LEFT_BUTTON, for a single square. */ | ||
1202 | sprintf(move_desc, "t%d,%d", ui->cur_x, ui->cur_y); | ||
1203 | ret = dupstr(move_desc); | ||
1204 | } | ||
1205 | } | ||
1206 | return ret; | ||
1207 | } | ||
1208 | |||
1209 | static void update_board_state_around(game_state *state, int x, int y) | ||
1210 | { | ||
1211 | int i, j; | ||
1212 | struct board_cell *curr; | ||
1213 | char *curr_state; | ||
1214 | int total; | ||
1215 | int blank; | ||
1216 | int marked; | ||
1217 | |||
1218 | for (i = -1; i < 2; i++) { | ||
1219 | for (j = -1; j < 2; j++) { | ||
1220 | curr = | ||
1221 | get_coords(state, state->board->actual_board, x + i, | ||
1222 | y + j); | ||
1223 | if (curr && curr->shown) { | ||
1224 | curr_state = | ||
1225 | get_coords(state, state->cells_contents, x + i, y + j); | ||
1226 | count_around_state(state, x + i, y + j, &marked, &blank, | ||
1227 | &total); | ||
1228 | if (curr->clue == marked && (total - marked - blank) == 0) { | ||
1229 | *curr_state &= STATE_MARKED | STATE_BLANK; | ||
1230 | *curr_state |= STATE_SOLVED; | ||
1231 | } else if (curr->clue < marked | ||
1232 | || curr->clue > (total - blank)) { | ||
1233 | *curr_state &= STATE_MARKED | STATE_BLANK; | ||
1234 | *curr_state |= STATE_ERROR; | ||
1235 | } else { | ||
1236 | *curr_state &= STATE_MARKED | STATE_BLANK; | ||
1237 | } | ||
1238 | } | ||
1239 | } | ||
1240 | } | ||
1241 | } | ||
1242 | |||
1243 | static game_state *execute_move(const game_state *state, const char *move) | ||
1244 | { | ||
1245 | game_state *new_state = dup_game(state); | ||
1246 | int i = 0, x = -1, y = -1, clues_left = 0; | ||
1247 | int srcX = -1, srcY = -1, size = state->height * state->width; | ||
1248 | const char *p; | ||
1249 | char *cell, sol_char; | ||
1250 | int steps = 1, bits, sol_location, dirX, dirY, diff, | ||
1251 | last_state = STATE_UNMARKED; | ||
1252 | unsigned int sol_value; | ||
1253 | struct board_cell *curr_cell; | ||
1254 | char move_type; | ||
1255 | int nparams = 0, move_params[5]; | ||
1256 | |||
1257 | p = move; | ||
1258 | move_type = *p++; | ||
1259 | switch (move_type) { | ||
1260 | case 't': | ||
1261 | case 'T': | ||
1262 | nparams = 2; | ||
1263 | break; | ||
1264 | case 'd': | ||
1265 | case 'e': | ||
1266 | nparams = 5; | ||
1267 | break; | ||
1268 | } | ||
1269 | |||
1270 | for (i = 0; i < nparams; i++) { | ||
1271 | move_params[i] = atoi(p); | ||
1272 | while (*p && isdigit((unsigned char)*p)) p++; | ||
1273 | if (i+1 < nparams) { | ||
1274 | if (*p != ',') { | ||
1275 | free_game(new_state); | ||
1276 | return NULL; | ||
1277 | } | ||
1278 | p++; | ||
1279 | } | ||
1280 | } | ||
1281 | |||
1282 | if (move_type == 't' || move_type == 'T') { | ||
1283 | if (move_type == 'T') { | ||
1284 | steps++; | ||
1285 | } | ||
1286 | x = move_params[0]; | ||
1287 | y = move_params[1]; | ||
1288 | if (x == -1 || y == -1) { | ||
1289 | return new_state; | ||
1290 | } | ||
1291 | cell = get_coords(new_state, new_state->cells_contents, x, y); | ||
1292 | if (cell == NULL) { | ||
1293 | free_game(new_state); | ||
1294 | return NULL; | ||
1295 | } | ||
1296 | if (*cell >= STATE_OK_NUM) { | ||
1297 | *cell &= STATE_OK_NUM; | ||
1298 | } | ||
1299 | *cell = (*cell + steps) % STATE_OK_NUM; | ||
1300 | update_board_state_around(new_state, x, y); | ||
1301 | } else if (move_type == 's') { | ||
1302 | new_state->not_completed_clues = 0; | ||
1303 | new_state->cheating = true; | ||
1304 | sol_location = 0; | ||
1305 | bits = 0; | ||
1306 | i = 1; | ||
1307 | while (i < strlen(move)) { | ||
1308 | sol_value = 0; | ||
1309 | while (bits < 8) { | ||
1310 | sol_value <<= 4; | ||
1311 | sol_char = move[i]; | ||
1312 | if (sol_char >= '0' && sol_char <= '9') { | ||
1313 | sol_value |= sol_char - '0'; | ||
1314 | } else { | ||
1315 | sol_value |= (sol_char - 'a') + 10; | ||
1316 | } | ||
1317 | bits += 4; | ||
1318 | i++; | ||
1319 | } | ||
1320 | while (bits > 0 && sol_location < size) { | ||
1321 | if (sol_value & 0x80) { | ||
1322 | new_state->cells_contents[sol_location] = | ||
1323 | STATE_MARKED_SOLVED; | ||
1324 | } else { | ||
1325 | new_state->cells_contents[sol_location] = | ||
1326 | STATE_BLANK_SOLVED; | ||
1327 | } | ||
1328 | sol_value <<= 1; | ||
1329 | bits--; | ||
1330 | sol_location++; | ||
1331 | } | ||
1332 | } | ||
1333 | return new_state; | ||
1334 | } else if (move_type == 'd' || move_type == 'e') { | ||
1335 | x = move_params[0]; | ||
1336 | y = move_params[1]; | ||
1337 | srcX = move_params[2]; | ||
1338 | srcY = move_params[3]; | ||
1339 | last_state = move_params[4]; | ||
1340 | if (srcX == x && srcY != y) { | ||
1341 | dirX = 0; | ||
1342 | diff = srcY - y; | ||
1343 | if (diff < 0) { | ||
1344 | dirY = -1; | ||
1345 | diff *= -1; | ||
1346 | } else { | ||
1347 | dirY = 1; | ||
1348 | } | ||
1349 | } else { | ||
1350 | diff = srcX - x; | ||
1351 | dirY = 0; | ||
1352 | if (diff < 0) { | ||
1353 | dirX = -1; | ||
1354 | diff *= -1; | ||
1355 | } else { | ||
1356 | dirX = 1; | ||
1357 | } | ||
1358 | } | ||
1359 | for (i = 0; i < diff; i++) { | ||
1360 | cell = get_coords(new_state, new_state->cells_contents, | ||
1361 | x + (dirX * i), y + (dirY * i)); | ||
1362 | if (cell == NULL) { | ||
1363 | free_game(new_state); | ||
1364 | return NULL; | ||
1365 | } | ||
1366 | if ((*cell & STATE_OK_NUM) == 0) { | ||
1367 | *cell = last_state; | ||
1368 | update_board_state_around(new_state, x + (dirX * i), | ||
1369 | y + (dirY * i)); | ||
1370 | } | ||
1371 | } | ||
1372 | } | ||
1373 | for (y = 0; y < state->height; y++) { | ||
1374 | for (x = 0; x < state->width; x++) { | ||
1375 | cell = get_coords(new_state, new_state->cells_contents, x, y); | ||
1376 | curr_cell = get_coords(new_state, new_state->board->actual_board, | ||
1377 | x, y); | ||
1378 | if (curr_cell->shown && ((*cell & STATE_SOLVED) == 0)) { | ||
1379 | clues_left++; | ||
1380 | } | ||
1381 | } | ||
1382 | } | ||
1383 | new_state->not_completed_clues = clues_left; | ||
1384 | return new_state; | ||
1385 | } | ||
1386 | |||
1387 | /* ---------------------------------------------------------------------- | ||
1388 | * Drawing routines. | ||
1389 | */ | ||
1390 | |||
1391 | static void game_compute_size(const game_params *params, int tilesize, | ||
1392 | const game_ui *ui, int *x, int *y) | ||
1393 | { | ||
1394 | *x = (params->width + 1) * tilesize; | ||
1395 | *y = (params->height + 1) * tilesize; | ||
1396 | } | ||
1397 | |||
1398 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
1399 | const game_params *params, int tilesize) | ||
1400 | { | ||
1401 | ds->tilesize = tilesize; | ||
1402 | } | ||
1403 | |||
1404 | #define COLOUR(ret, i, r, g, b) \ | ||
1405 | ((ret[3 * (i) + 0] = (r)), (ret[3 * (i) + 1] = (g)), (ret[3 * (i) + 2] = (b))) | ||
1406 | |||
1407 | static float *game_colours(frontend *fe, int *ncolours) | ||
1408 | { | ||
1409 | float *ret = snewn(3 * NCOLOURS, float); | ||
1410 | |||
1411 | frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); | ||
1412 | COLOUR(ret, COL_GRID, 0.0F, 102 / 255.0F, 99 / 255.0F); | ||
1413 | COLOUR(ret, COL_ERROR, 1.0F, 0.0F, 0.0F); | ||
1414 | COLOUR(ret, COL_BLANK, 236 / 255.0F, 236 / 255.0F, 236 / 255.0F); | ||
1415 | COLOUR(ret, COL_MARKED, 20 / 255.0F, 20 / 255.0F, 20 / 255.0F); | ||
1416 | COLOUR(ret, COL_UNMARKED, 148 / 255.0F, 196 / 255.0F, 190 / 255.0F); | ||
1417 | COLOUR(ret, COL_TEXT_SOLVED, 100 / 255.0F, 100 / 255.0F, 100 / 255.0F); | ||
1418 | COLOUR(ret, COL_CURSOR, 255 / 255.0F, 200 / 255.0F, 200 / 255.0F); | ||
1419 | |||
1420 | *ncolours = NCOLOURS; | ||
1421 | return ret; | ||
1422 | } | ||
1423 | |||
1424 | /* Extra flags in game_drawstate entries, not in main game state */ | ||
1425 | #define DRAWFLAG_CURSOR 0x100 | ||
1426 | #define DRAWFLAG_CURSOR_U 0x200 | ||
1427 | #define DRAWFLAG_CURSOR_L 0x400 | ||
1428 | #define DRAWFLAG_CURSOR_UL 0x800 | ||
1429 | #define DRAWFLAG_MARGIN_R 0x1000 | ||
1430 | #define DRAWFLAG_MARGIN_D 0x2000 | ||
1431 | |||
1432 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | ||
1433 | { | ||
1434 | struct game_drawstate *ds = snew(game_drawstate); | ||
1435 | int i; | ||
1436 | |||
1437 | ds->tilesize = 0; | ||
1438 | ds->state = NULL; | ||
1439 | ds->state = snewn((state->width + 1) * (state->height + 1), int); | ||
1440 | for (i = 0; i < (state->width + 1) * (state->height + 1); i++) | ||
1441 | ds->state[i] = -1; | ||
1442 | |||
1443 | return ds; | ||
1444 | } | ||
1445 | |||
1446 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
1447 | { | ||
1448 | sfree(ds->state); | ||
1449 | sfree(ds); | ||
1450 | } | ||
1451 | |||
1452 | static void draw_cell(drawing *dr, int cell, int ts, signed char clue_val, | ||
1453 | int x, int y) | ||
1454 | { | ||
1455 | int startX = ((x * ts) + ts / 2) - 1, startY = ((y * ts) + ts / 2) - 1; | ||
1456 | int color, text_color = COL_TEXT_DARK; | ||
1457 | |||
1458 | clip(dr, startX - 1, startY - 1, ts, ts); | ||
1459 | if (!(cell & DRAWFLAG_MARGIN_R)) | ||
1460 | draw_rect(dr, startX - 1, startY - 1, ts, 1, | ||
1461 | (cell & (DRAWFLAG_CURSOR | DRAWFLAG_CURSOR_U) ? | ||
1462 | COL_CURSOR : COL_GRID)); | ||
1463 | if (!(cell & DRAWFLAG_MARGIN_D)) | ||
1464 | draw_rect(dr, startX - 1, startY - 1, 1, ts, | ||
1465 | (cell & (DRAWFLAG_CURSOR | DRAWFLAG_CURSOR_L) ? | ||
1466 | COL_CURSOR : COL_GRID)); | ||
1467 | if (cell & DRAWFLAG_CURSOR_UL) | ||
1468 | draw_rect(dr, startX - 1, startY - 1, 1, 1, COL_CURSOR); | ||
1469 | |||
1470 | if (!(cell & (DRAWFLAG_MARGIN_R | DRAWFLAG_MARGIN_D))) { | ||
1471 | if (cell & STATE_MARKED) { | ||
1472 | color = COL_MARKED; | ||
1473 | text_color = COL_TEXT_LIGHT; | ||
1474 | } else if (cell & STATE_BLANK) { | ||
1475 | text_color = COL_TEXT_DARK; | ||
1476 | color = COL_BLANK; | ||
1477 | } else { | ||
1478 | text_color = COL_TEXT_DARK; | ||
1479 | color = COL_UNMARKED; | ||
1480 | } | ||
1481 | if (cell & STATE_ERROR) { | ||
1482 | text_color = COL_ERROR; | ||
1483 | } else if (cell & STATE_SOLVED) { | ||
1484 | text_color = COL_TEXT_SOLVED; | ||
1485 | } | ||
1486 | |||
1487 | draw_rect(dr, startX, startY, ts - 1, ts - 1, color); | ||
1488 | if (clue_val >= 0) { | ||
1489 | char clue[80]; | ||
1490 | sprintf(clue, "%d", clue_val); | ||
1491 | draw_text(dr, startX + ts / 2, startY + ts / 2, 1, ts * 3 / 5, | ||
1492 | ALIGN_VCENTRE | ALIGN_HCENTRE, text_color, clue); | ||
1493 | } | ||
1494 | } | ||
1495 | |||
1496 | unclip(dr); | ||
1497 | draw_update(dr, startX - 1, startY - 1, ts, ts); | ||
1498 | } | ||
1499 | |||
1500 | static void game_redraw(drawing *dr, game_drawstate *ds, | ||
1501 | const game_state *oldstate, | ||
1502 | const game_state *state, int dir, | ||
1503 | const game_ui *ui, float animtime, | ||
1504 | float flashtime) | ||
1505 | { | ||
1506 | int x, y; | ||
1507 | char status[80]; | ||
1508 | signed char clue_val; | ||
1509 | bool flashing = (flashtime > 0 && (flashtime <= FLASH_TIME / 3 || | ||
1510 | flashtime > 2*FLASH_TIME / 3)); | ||
1511 | |||
1512 | for (y = 0; y <= state->height; y++) { | ||
1513 | for (x = 0; x <= state->width; x++) { | ||
1514 | bool inbounds = x < state->width && y < state->height; | ||
1515 | int cell = (inbounds ? | ||
1516 | state->cells_contents[(y * state->width) + x] : 0); | ||
1517 | if (x == state->width) | ||
1518 | cell |= DRAWFLAG_MARGIN_R; | ||
1519 | if (y == state->height) | ||
1520 | cell |= DRAWFLAG_MARGIN_D; | ||
1521 | if (flashing) | ||
1522 | cell ^= (STATE_BLANK | STATE_MARKED); | ||
1523 | if (ui->cur_visible) { | ||
1524 | if (ui->cur_x == x && ui->cur_y == y) | ||
1525 | cell |= DRAWFLAG_CURSOR; | ||
1526 | if (ui->cur_x == x-1 && ui->cur_y == y) | ||
1527 | cell |= DRAWFLAG_CURSOR_L; | ||
1528 | if (ui->cur_x == x && ui->cur_y == y-1) | ||
1529 | cell |= DRAWFLAG_CURSOR_U; | ||
1530 | if (ui->cur_x == x-1 && ui->cur_y == y-1) | ||
1531 | cell |= DRAWFLAG_CURSOR_UL; | ||
1532 | } | ||
1533 | |||
1534 | if (inbounds && | ||
1535 | state->board->actual_board[(y * state->width) + x].shown) { | ||
1536 | clue_val = state->board->actual_board[ | ||
1537 | (y * state->width) + x].clue; | ||
1538 | } else { | ||
1539 | clue_val = -1; | ||
1540 | } | ||
1541 | |||
1542 | if (ds->state[(y * (state->width+1)) + x] != cell) { | ||
1543 | draw_cell(dr, cell, ds->tilesize, clue_val, x, y); | ||
1544 | ds->state[(y * (state->width+1)) + x] = cell; | ||
1545 | } | ||
1546 | } | ||
1547 | } | ||
1548 | sprintf(status, "Clues left: %d", state->not_completed_clues); | ||
1549 | if (state->not_completed_clues == 0 && !state->cheating) { | ||
1550 | sprintf(status, "COMPLETED!"); | ||
1551 | } else if (state->not_completed_clues == 0 && state->cheating) { | ||
1552 | sprintf(status, "Auto solved"); | ||
1553 | } | ||
1554 | status_bar(dr, status); | ||
1555 | } | ||
1556 | |||
1557 | static float game_anim_length(const game_state *oldstate, | ||
1558 | const game_state *newstate, int dir, | ||
1559 | game_ui *ui) | ||
1560 | { | ||
1561 | return 0.0F; | ||
1562 | } | ||
1563 | |||
1564 | static float game_flash_length(const game_state *oldstate, | ||
1565 | const game_state *newstate, int dir, | ||
1566 | game_ui *ui) | ||
1567 | { | ||
1568 | if (!oldstate->cheating && oldstate->not_completed_clues > 0 && | ||
1569 | newstate->not_completed_clues == 0) { | ||
1570 | return FLASH_TIME; | ||
1571 | } | ||
1572 | return 0.0F; | ||
1573 | } | ||
1574 | |||
1575 | static int game_status(const game_state *state) | ||
1576 | { | ||
1577 | if (state->not_completed_clues == 0) | ||
1578 | return +1; | ||
1579 | return 0; | ||
1580 | } | ||
1581 | |||
1582 | #ifdef COMBINED | ||
1583 | #define thegame mosaic | ||
1584 | #endif | ||
1585 | |||
1586 | const struct game thegame = { | ||
1587 | "Mosaic", "games.mosaic", "mosaic", | ||
1588 | default_params, | ||
1589 | game_fetch_preset, NULL, | ||
1590 | decode_params, | ||
1591 | encode_params, | ||
1592 | free_params, | ||
1593 | dup_params, | ||
1594 | true, game_configure, custom_params, | ||
1595 | validate_params, | ||
1596 | new_game_desc, | ||
1597 | validate_desc, | ||
1598 | new_game, | ||
1599 | dup_game, | ||
1600 | free_game, | ||
1601 | true, solve_game, | ||
1602 | true, game_can_format_as_text_now, game_text_format, | ||
1603 | NULL, NULL, /* get_prefs, set_prefs */ | ||
1604 | new_ui, | ||
1605 | free_ui, | ||
1606 | NULL, /* encode_ui */ | ||
1607 | NULL, /* decode_ui */ | ||
1608 | NULL, /* game_request_keys */ | ||
1609 | game_changed_state, | ||
1610 | current_key_label, | ||
1611 | interpret_move, | ||
1612 | execute_move, | ||
1613 | DEFAULT_TILE_SIZE, game_compute_size, game_set_size, | ||
1614 | game_colours, | ||
1615 | game_new_drawstate, | ||
1616 | game_free_drawstate, | ||
1617 | game_redraw, | ||
1618 | game_anim_length, | ||
1619 | game_flash_length, | ||
1620 | game_get_cursor_location, | ||
1621 | game_status, | ||
1622 | false, false, NULL, NULL, /* print_size, print */ | ||
1623 | true, /* wants_statusbar */ | ||
1624 | false, NULL, /* timing_state */ | ||
1625 | 0, /* flags */ | ||
1626 | }; | ||