summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/mosaic.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/mosaic.c')
-rw-r--r--apps/plugins/puzzles/src/mosaic.c1626
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
48enum {
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
62enum 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
76struct game_params {
77 int width;
78 int height;
79 bool aggressive;
80};
81
82typedef struct board_state board_state;
83
84typedef struct needed_list_item needed_list_item;
85
86struct needed_list_item {
87 int x, y;
88 needed_list_item *next;
89};
90
91struct 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
100struct board_state {
101 unsigned int references;
102 struct board_cell *actual_board;
103};
104
105struct board_cell {
106 signed char clue;
107 bool shown;
108};
109
110struct solution_cell {
111 signed char cell;
112 bool solved;
113 bool needed;
114};
115
116struct desc_cell {
117 char clue;
118 bool shown;
119 bool value;
120 bool full;
121 bool empty;
122};
123
124struct 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
132struct game_drawstate {
133 int tilesize;
134 int *state;
135};
136
137static 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
148static 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
167static void free_params(game_params *params)
168{
169 sfree(params);
170}
171
172static 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
179static 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
195static 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
207static 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
231static 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
240static 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
251static 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
262static 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
335static 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
360static 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
384static 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
406static 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
424static 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
493static 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
576static 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
615static 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
682static 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
693static 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
706static 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
717static 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
830static 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
852static 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
902static 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
919static 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
933static 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(&param, 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
976static bool game_can_format_as_text_now(const game_params *params)
977{
978 return true;
979}
980
981static 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
1003static 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
1015static void free_ui(game_ui *ui)
1016{
1017 sfree(ui);
1018}
1019
1020static void game_changed_state(game_ui *ui, const game_state *oldstate,
1021 const game_state *newstate)
1022{
1023}
1024
1025static 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
1045static 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
1209static 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
1243static 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
1391static 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
1398static 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
1407static 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
1432static 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
1446static void game_free_drawstate(drawing *dr, game_drawstate *ds)
1447{
1448 sfree(ds->state);
1449 sfree(ds);
1450}
1451
1452static 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
1500static 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
1557static 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
1564static 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
1575static 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
1586const 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};