A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/*
2 * flood.c: puzzle in which you make a grid all the same colour by
3 * repeatedly flood-filling the top left corner, and try to do so in
4 * as few moves as possible.
5 */
6
7/*
8 * Possible further work:
9 *
10 * - UI: perhaps we should only permit clicking on regions that can
11 * actually be reached by the next flood-fill - i.e. a click is
12 * only interpreted as a move if it would cause the clicked-on
13 * square to become part of the controlled area. This provides a
14 * hint in cases where you mistakenly thought that would happen,
15 * and protects you against typos in cases where you just
16 * mis-aimed.
17 *
18 * - UI: perhaps mark the fill square in some way? Or even mark the
19 * whole connected component _containing_ the fill square. Pro:
20 * that would make it easier to tell apart cases where almost all
21 * the yellow squares in the grid are part of the target component
22 * (hence, yellow is _done_ and you never have to fill in that
23 * colour again) from cases where there's still one yellow square
24 * that's only diagonally adjacent and hence will need coming back
25 * to. Con: but it would almost certainly be ugly.
26 */
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <stdarg.h>
31#include <string.h>
32#include <assert.h>
33#include <ctype.h>
34#include <limits.h>
35#ifdef NO_TGMATH_H
36# include <math.h>
37#else
38# include <tgmath.h>
39#endif
40
41#include "puzzles.h"
42
43enum {
44 COL_BACKGROUND, COL_SEPARATOR,
45 COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9, COL_10,
46 COL_HIGHLIGHT, COL_LOWLIGHT,
47 NCOLOURS
48};
49
50struct game_params {
51 int w, h;
52 int colours;
53 int leniency;
54};
55
56/* Just in case I want to make this changeable later, I'll put the
57 * coordinates of the flood-fill point here so that it'll be easy to
58 * find everywhere later that has to change. */
59#define FILLX 0
60#define FILLY 0
61
62/* Upper limit on the number of colours you can play with, derived
63 * from the fact that we only have that many actual RGB values. You
64 * could easily extend this, by adding COL_11, COL_12, ... */
65#define MAXCOLOURS 10
66
67typedef struct soln {
68 int refcount;
69 int nmoves;
70 char *moves;
71} soln;
72
73struct game_state {
74 int w, h, colours;
75 int moves, movelimit;
76 bool complete;
77 char *grid;
78 bool cheated;
79 int solnpos;
80 soln *soln;
81};
82
83static game_params *default_params(void)
84{
85 game_params *ret = snew(game_params);
86
87 ret->w = ret->h = 12;
88 ret->colours = 6;
89 ret->leniency = 5;
90
91 return ret;
92}
93
94static const struct {
95 struct game_params preset;
96 const char *name;
97} flood_presets[] = {
98 /* Default 12x12 size, three difficulty levels. */
99 {{12, 12, 6, 5}, "12x12 Easy"},
100 {{12, 12, 6, 2}, "12x12 Medium"},
101 {{12, 12, 6, 0}, "12x12 Hard"},
102 /* Larger puzzles, leaving off Easy in the expectation that people
103 * wanting a bigger grid will have played it enough to find Easy
104 * easy. */
105 {{16, 16, 6, 2}, "16x16 Medium"},
106 {{16, 16, 6, 0}, "16x16 Hard"},
107 /* A couple of different colour counts. It seems generally not too
108 * hard with fewer colours (probably because fewer choices), so no
109 * extra moves for these modes. */
110 {{12, 12, 3, 0}, "12x12, 3 colours"},
111 {{12, 12, 4, 0}, "12x12, 4 colours"},
112};
113
114static bool game_fetch_preset(int i, char **name, game_params **params)
115{
116 game_params *ret;
117
118 if (i < 0 || i >= lenof(flood_presets))
119 return false;
120
121 ret = snew(game_params);
122 *ret = flood_presets[i].preset;
123 *name = dupstr(flood_presets[i].name);
124 *params = ret;
125 return true;
126}
127
128static void free_params(game_params *params)
129{
130 sfree(params);
131}
132
133static game_params *dup_params(const game_params *params)
134{
135 game_params *ret = snew(game_params);
136 *ret = *params; /* structure copy */
137 return ret;
138}
139
140static void decode_params(game_params *ret, char const *string)
141{
142 ret->w = ret->h = atoi(string);
143 while (*string && isdigit((unsigned char)*string)) string++;
144 if (*string == 'x') {
145 string++;
146 ret->h = atoi(string);
147 while (*string && isdigit((unsigned char)*string)) string++;
148 }
149 while (*string) {
150 if (*string == 'c') {
151 string++;
152 ret->colours = atoi(string);
153 while (*string && isdigit((unsigned char)*string)) string++;
154 } else if (*string == 'm') {
155 string++;
156 ret->leniency = atoi(string);
157 while (*string && isdigit((unsigned char)*string)) string++;
158 } else
159 string++;
160 }
161}
162
163static char *encode_params(const game_params *params, bool full)
164{
165 char buf[256];
166 sprintf(buf, "%dx%d", params->w, params->h);
167 if (full)
168 sprintf(buf + strlen(buf), "c%dm%d",
169 params->colours, params->leniency);
170 return dupstr(buf);
171}
172
173static config_item *game_configure(const game_params *params)
174{
175 config_item *ret;
176 char buf[80];
177
178 ret = snewn(5, config_item);
179
180 ret[0].name = "Width";
181 ret[0].type = C_STRING;
182 sprintf(buf, "%d", params->w);
183 ret[0].u.string.sval = dupstr(buf);
184
185 ret[1].name = "Height";
186 ret[1].type = C_STRING;
187 sprintf(buf, "%d", params->h);
188 ret[1].u.string.sval = dupstr(buf);
189
190 ret[2].name = "Colours";
191 ret[2].type = C_STRING;
192 sprintf(buf, "%d", params->colours);
193 ret[2].u.string.sval = dupstr(buf);
194
195 ret[3].name = "Extra moves permitted";
196 ret[3].type = C_STRING;
197 sprintf(buf, "%d", params->leniency);
198 ret[3].u.string.sval = dupstr(buf);
199
200 ret[4].name = NULL;
201 ret[4].type = C_END;
202
203 return ret;
204}
205
206static game_params *custom_params(const config_item *cfg)
207{
208 game_params *ret = snew(game_params);
209
210 ret->w = atoi(cfg[0].u.string.sval);
211 ret->h = atoi(cfg[1].u.string.sval);
212 ret->colours = atoi(cfg[2].u.string.sval);
213 ret->leniency = atoi(cfg[3].u.string.sval);
214
215 return ret;
216}
217
218static const char *validate_params(const game_params *params, bool full)
219{
220 if (params->w * params->h < 2)
221 return "Grid must contain at least two squares";
222 if (params->w < 1 || params->h < 1)
223 return "Width and height must be at least one";
224 if (params->w > INT_MAX / params->h)
225 return "Width times height must not be unreasonably large";
226 if (params->colours < 3 || params->colours > MAXCOLOURS)
227 return "Must have between 3 and " STR(MAXCOLOURS) " colours";
228 if (params->leniency < 0)
229 return "Leniency must be non-negative";
230 return NULL;
231}
232
233#if 0
234/*
235 * Bodge to permit varying the recursion depth for testing purposes.
236
237To test two Floods against each other:
238
239paste <(./flood.1 --generate 100 12x12c6m0#12345 | cut -f2 -d,) <(./flood.2 --generate 100 12x12c6m0#12345 | cut -f2 -d,) | awk '{print $2-$1}' | sort -n | uniq -c | awk '{print $2,$1}' | tee z
240
241and then run gnuplot and plot "z".
242
243 */
244static int rdepth = 0;
245#define RECURSION_DEPTH (rdepth)
246void check_recursion_depth(void)
247{
248 if (!rdepth) {
249 const char *depthstr = getenv("FLOOD_DEPTH");
250 rdepth = depthstr ? atoi(depthstr) : 1;
251 rdepth = rdepth > 0 ? rdepth : 1;
252 }
253}
254#else
255/*
256 * Last time I empirically checked this, depth 3 was a noticeable
257 * improvement on 2, but 4 only negligibly better than 3.
258 */
259#define RECURSION_DEPTH 3
260#define check_recursion_depth() (void)0
261#endif
262
263struct solver_scratch {
264 int *queue[2];
265 int *dist;
266 char *grid, *grid2;
267 char *rgrids;
268};
269
270static struct solver_scratch *new_scratch(int w, int h)
271{
272 int wh = w*h;
273 struct solver_scratch *scratch = snew(struct solver_scratch);
274 check_recursion_depth();
275 scratch->queue[0] = snewn(wh, int);
276 scratch->queue[1] = snewn(wh, int);
277 scratch->dist = snewn(wh, int);
278 scratch->grid = snewn(wh, char);
279 scratch->grid2 = snewn(wh, char);
280 scratch->rgrids = snewn(wh * RECURSION_DEPTH, char);
281 return scratch;
282}
283
284static void free_scratch(struct solver_scratch *scratch)
285{
286 sfree(scratch->queue[0]);
287 sfree(scratch->queue[1]);
288 sfree(scratch->dist);
289 sfree(scratch->grid);
290 sfree(scratch->grid2);
291 sfree(scratch->rgrids);
292 sfree(scratch);
293}
294
295#if 0
296/* Diagnostic routines you can uncomment if you need them */
297void dump_grid(int w, int h, const char *grid, const char *titlefmt, ...)
298{
299 int x, y;
300 if (titlefmt) {
301 va_list ap;
302 va_start(ap, titlefmt);
303 vprintf(titlefmt, ap);
304 va_end(ap);
305 printf(":\n");
306 } else {
307 printf("Grid:\n");
308 }
309 for (y = 0; y < h; y++) {
310 printf(" ");
311 for (x = 0; x < w; x++) {
312 printf("%1x", grid[y*w+x]);
313 }
314 printf("\n");
315 }
316}
317
318void dump_dist(int w, int h, const int *dists, const char *titlefmt, ...)
319{
320 int x, y;
321 if (titlefmt) {
322 va_list ap;
323 va_start(ap, titlefmt);
324 vprintf(titlefmt, ap);
325 va_end(ap);
326 printf(":\n");
327 } else {
328 printf("Distances:\n");
329 }
330 for (y = 0; y < h; y++) {
331 printf(" ");
332 for (x = 0; x < w; x++) {
333 printf("%3d", dists[y*w+x]);
334 }
335 printf("\n");
336 }
337}
338#endif
339
340/*
341 * Search a grid to find the most distant square(s). Return their
342 * distance and the number of them, and also the number of squares in
343 * the current controlled set (i.e. at distance zero).
344 */
345static void search(int w, int h, char *grid, int x0, int y0,
346 struct solver_scratch *scratch,
347 int *rdist, int *rnumber, int *rcontrol)
348{
349 int wh = w*h;
350 int i, qcurr, qhead, qtail, qnext, currdist, remaining;
351
352 for (i = 0; i < wh; i++)
353 scratch->dist[i] = -1;
354 scratch->queue[0][0] = y0*w+x0;
355 scratch->queue[1][0] = y0*w+x0;
356 scratch->dist[y0*w+x0] = 0;
357 currdist = 0;
358 qcurr = 0;
359 qtail = 0;
360 qhead = 1;
361 qnext = 1;
362 remaining = wh - 1;
363
364 while (1) {
365 if (qtail == qhead) {
366 /* Switch queues. */
367 if (currdist == 0)
368 *rcontrol = qhead;
369 currdist++;
370 qcurr ^= 1; /* switch queues */
371 qhead = qnext;
372 qtail = 0;
373 qnext = 0;
374#if 0
375 printf("switch queue, new dist %d, queue %d\n", currdist, qhead);
376#endif
377 } else if (remaining == 0 && qnext == 0) {
378 break;
379 } else {
380 int pos = scratch->queue[qcurr][qtail++];
381 int y = pos / w;
382 int x = pos % w;
383#if 0
384 printf("checking neighbours of %d,%d\n", x, y);
385#endif
386 int dir;
387 for (dir = 0; dir < 4; dir++) {
388 int y1 = y + (dir == 1 ? 1 : dir == 3 ? -1 : 0);
389 int x1 = x + (dir == 0 ? 1 : dir == 2 ? -1 : 0);
390 if (0 <= x1 && x1 < w && 0 <= y1 && y1 < h) {
391 int pos1 = y1*w+x1;
392#if 0
393 printf("trying %d,%d: colours %d-%d dist %d\n", x1, y1,
394 grid[pos], grid[pos1], scratch->dist[pos]);
395#endif
396 if (scratch->dist[pos1] == -1 &&
397 ((grid[pos1] == grid[pos] &&
398 scratch->dist[pos] == currdist) ||
399 (grid[pos1] != grid[pos] &&
400 scratch->dist[pos] == currdist - 1))) {
401#if 0
402 printf("marking %d,%d dist %d\n", x1, y1, currdist);
403#endif
404 scratch->queue[qcurr][qhead++] = pos1;
405 scratch->queue[qcurr^1][qnext++] = pos1;
406 scratch->dist[pos1] = currdist;
407 remaining--;
408 }
409 }
410 }
411 }
412 }
413
414 *rdist = currdist;
415 *rnumber = qhead;
416 if (currdist == 0)
417 *rcontrol = qhead;
418}
419
420/*
421 * Enact a flood-fill move on a grid.
422 */
423static void fill(int w, int h, char *grid, int x0, int y0, char newcolour,
424 int *queue)
425{
426 char oldcolour;
427 int qhead, qtail;
428
429 oldcolour = grid[y0*w+x0];
430 assert(oldcolour != newcolour);
431 grid[y0*w+x0] = newcolour;
432 queue[0] = y0*w+x0;
433 qtail = 0;
434 qhead = 1;
435
436 while (qtail < qhead) {
437 int pos = queue[qtail++];
438 int y = pos / w;
439 int x = pos % w;
440 int dir;
441 for (dir = 0; dir < 4; dir++) {
442 int y1 = y + (dir == 1 ? 1 : dir == 3 ? -1 : 0);
443 int x1 = x + (dir == 0 ? 1 : dir == 2 ? -1 : 0);
444 if (0 <= x1 && x1 < w && 0 <= y1 && y1 < h) {
445 int pos1 = y1*w+x1;
446 if (grid[pos1] == oldcolour) {
447 grid[pos1] = newcolour;
448 queue[qhead++] = pos1;
449 }
450 }
451 }
452 }
453}
454
455/*
456 * Detect a completed grid.
457 */
458static bool completed(int w, int h, char *grid)
459{
460 int wh = w*h;
461 int i;
462
463 for (i = 1; i < wh; i++)
464 if (grid[i] != grid[0])
465 return false;
466
467 return true;
468}
469
470/*
471 * Try out every possible move on a grid, and choose whichever one
472 * reduced the result of search() by the most.
473 */
474static char choosemove_recurse(int w, int h, char *grid, int x0, int y0,
475 int maxmove, struct solver_scratch *scratch,
476 int depth, int *rbestdist, int *rbestnumber, int *rbestcontrol)
477{
478 int wh = w*h;
479 char move, bestmove;
480 int dist, number, control, bestdist, bestnumber, bestcontrol;
481 char *tmpgrid;
482
483 assert(0 <= depth && depth < RECURSION_DEPTH);
484 tmpgrid = scratch->rgrids + depth*wh;
485
486 bestdist = wh + 1;
487 bestnumber = 0;
488 bestcontrol = 0;
489 bestmove = -1;
490
491#if 0
492 dump_grid(w, h, grid, "before choosemove_recurse %d", depth);
493#endif
494 for (move = 0; move < maxmove; move++) {
495 if (grid[y0*w+x0] == move)
496 continue;
497 memcpy(tmpgrid, grid, wh * sizeof(*grid));
498 fill(w, h, tmpgrid, x0, y0, move, scratch->queue[0]);
499 if (completed(w, h, tmpgrid)) {
500 /*
501 * A move that wins is immediately the best, so stop
502 * searching. Record what depth of recursion that happened
503 * at, so that higher levels will choose a move that gets
504 * to a winning position sooner.
505 */
506 *rbestdist = -1;
507 *rbestnumber = depth;
508 *rbestcontrol = wh;
509 return move;
510 }
511 if (depth < RECURSION_DEPTH-1) {
512 choosemove_recurse(w, h, tmpgrid, x0, y0, maxmove, scratch,
513 depth+1, &dist, &number, &control);
514 } else {
515#if 0
516 dump_grid(w, h, tmpgrid, "after move %d at depth %d",
517 move, depth);
518#endif
519 search(w, h, tmpgrid, x0, y0, scratch, &dist, &number, &control);
520#if 0
521 dump_dist(w, h, scratch->dist, "after move %d at depth %d",
522 move, depth);
523 printf("move %d at depth %d: %d at %d\n",
524 depth, move, number, dist);
525#endif
526 }
527 if (dist < bestdist ||
528 (dist == bestdist &&
529 (number < bestnumber ||
530 (number == bestnumber &&
531 (control > bestcontrol))))) {
532 bestdist = dist;
533 bestnumber = number;
534 bestcontrol = control;
535 bestmove = move;
536 }
537 }
538#if 0
539 printf("best at depth %d was %d (%d at %d, %d controlled)\n",
540 depth, bestmove, bestnumber, bestdist, bestcontrol);
541#endif
542
543 *rbestdist = bestdist;
544 *rbestnumber = bestnumber;
545 *rbestcontrol = bestcontrol;
546 return bestmove;
547}
548static char choosemove(int w, int h, char *grid, int x0, int y0,
549 int maxmove, struct solver_scratch *scratch)
550{
551 int tmp0, tmp1, tmp2;
552 return choosemove_recurse(w, h, grid, x0, y0, maxmove, scratch,
553 0, &tmp0, &tmp1, &tmp2);
554}
555
556static char *new_game_desc(const game_params *params, random_state *rs,
557 char **aux, bool interactive)
558{
559 int w = params->w, h = params->h, wh = w*h;
560 int i, moves;
561 char *desc;
562 struct solver_scratch *scratch;
563
564 scratch = new_scratch(w, h);
565
566 /*
567 * Invent a random grid.
568 */
569 do {
570 for (i = 0; i < wh; i++)
571 scratch->grid[i] = random_upto(rs, params->colours);
572 } while (completed(w, h, scratch->grid));
573
574 /*
575 * Run the solver, and count how many moves it uses.
576 */
577 memcpy(scratch->grid2, scratch->grid, wh * sizeof(*scratch->grid2));
578 moves = 0;
579 check_recursion_depth();
580 while (!completed(w, h, scratch->grid2)) {
581 char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
582 params->colours, scratch);
583 fill(w, h, scratch->grid2, FILLX, FILLY, move, scratch->queue[0]);
584 moves++;
585 }
586
587 /*
588 * Adjust for difficulty.
589 */
590 moves += params->leniency;
591
592 /*
593 * Encode the game id.
594 */
595 desc = snewn(wh + 40, char);
596 for (i = 0; i < wh; i++) {
597 char colour = scratch->grid[i];
598 char textcolour = (colour > 9 ? 'A' : '0') + colour;
599 desc[i] = textcolour;
600 }
601 sprintf(desc+i, ",%d", moves);
602
603 free_scratch(scratch);
604
605 return desc;
606}
607
608static const char *validate_desc(const game_params *params, const char *desc)
609{
610 int w = params->w, h = params->h, wh = w*h;
611 int i;
612 for (i = 0; i < wh; i++) {
613 char c = *desc++;
614 if (c == 0)
615 return "Not enough data in grid description";
616 if (c >= '0' && c <= '9')
617 c -= '0';
618 else if (c >= 'A' && c <= 'Z')
619 c = 10 + (c - 'A');
620 else
621 return "Bad character in grid description";
622 if ((unsigned)c >= MAXCOLOURS)
623 return "Colour out of range in grid description";
624 }
625 if (*desc != ',')
626 return "Expected ',' after grid description";
627 desc++;
628 if (desc[strspn(desc, "0123456789")])
629 return "Badly formatted move limit after grid description";
630 return NULL;
631}
632
633static game_state *new_game(midend *me, const game_params *params,
634 const char *desc)
635{
636 int w = params->w, h = params->h, wh = w*h;
637 game_state *state = snew(game_state);
638 int i;
639
640 state->w = w;
641 state->h = h;
642 state->colours = 0;
643 state->moves = 0;
644 state->grid = snewn(wh, char);
645
646 for (i = 0; i < wh; i++) {
647 char c = *desc++;
648 assert(c);
649 if (c >= '0' && c <= '9')
650 c -= '0';
651 else if (c >= 'A' && c <= 'Z')
652 c = 10 + (c - 'A');
653 else
654 assert(!"bad colour");
655 assert(c >= 0);
656 assert(c < MAXCOLOURS);
657 state->grid[i] = c;
658 if (c >= state->colours)
659 state->colours = c + 1;
660 }
661 assert(*desc == ',');
662 desc++;
663
664 state->movelimit = atoi(desc);
665 state->complete = false;
666 state->cheated = false;
667 state->solnpos = 0;
668 state->soln = NULL;
669
670 return state;
671}
672
673static game_state *dup_game(const game_state *state)
674{
675 game_state *ret = snew(game_state);
676
677 ret->w = state->w;
678 ret->h = state->h;
679 ret->colours = state->colours;
680 ret->moves = state->moves;
681 ret->movelimit = state->movelimit;
682 ret->complete = state->complete;
683 ret->grid = snewn(state->w * state->h, char);
684 memcpy(ret->grid, state->grid, state->w * state->h * sizeof(*ret->grid));
685
686 ret->cheated = state->cheated;
687 ret->soln = state->soln;
688 if (ret->soln)
689 ret->soln->refcount++;
690 ret->solnpos = state->solnpos;
691
692 return ret;
693}
694
695static void free_game(game_state *state)
696{
697 if (state->soln && --state->soln->refcount == 0) {
698 sfree(state->soln->moves);
699 sfree(state->soln);
700 }
701 sfree(state->grid);
702 sfree(state);
703}
704
705static char *solve_game(const game_state *state, const game_state *currstate,
706 const char *aux, const char **error)
707{
708 int w = state->w, h = state->h, wh = w*h;
709 char *moves, *ret, *p;
710 int i, len, nmoves;
711 char buf[256];
712 struct solver_scratch *scratch;
713
714 if (currstate->complete) {
715 *error = "Puzzle is already solved";
716 return NULL;
717 }
718
719 /*
720 * Find the best solution our solver can give.
721 */
722 moves = snewn(wh, char); /* sure to be enough */
723 nmoves = 0;
724 scratch = new_scratch(w, h);
725 memcpy(scratch->grid2, currstate->grid, wh * sizeof(*scratch->grid2));
726 check_recursion_depth();
727 while (!completed(w, h, scratch->grid2)) {
728 char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
729 currstate->colours, scratch);
730 fill(w, h, scratch->grid2, FILLX, FILLY, move, scratch->queue[0]);
731 assert(nmoves < wh);
732 moves[nmoves++] = move;
733 }
734 free_scratch(scratch);
735
736 /*
737 * Encode it as a move string.
738 */
739 len = 1; /* trailing NUL */
740 for (i = 0; i < nmoves; i++)
741 len += sprintf(buf, ",%d", moves[i]);
742 ret = snewn(len, char);
743 p = ret;
744 for (i = 0; i < nmoves; i++)
745 p += sprintf(p, "%c%d", (i==0 ? 'S' : ','), moves[i]);
746 assert(p - ret == len - 1);
747
748 sfree(moves);
749 return ret;
750}
751
752static bool game_can_format_as_text_now(const game_params *params)
753{
754 return true;
755}
756
757static char *game_text_format(const game_state *state)
758{
759 int w = state->w, h = state->h;
760 char *ret, *p;
761 int x, y, len;
762
763 len = h * (w+1); /* +1 for newline after each row */
764 ret = snewn(len+1, char); /* and +1 for terminating \0 */
765 p = ret;
766
767 for (y = 0; y < h; y++) {
768 for (x = 0; x < w; x++) {
769 char colour = state->grid[y*w+x];
770 char textcolour = (colour > 9 ? 'A' : '0') + colour;
771 *p++ = textcolour;
772 }
773 *p++ = '\n';
774 }
775
776 assert(p - ret == len);
777 *p = '\0';
778
779 return ret;
780}
781
782struct game_ui {
783 bool cursor_visible;
784 int cx, cy;
785 enum { VICTORY, DEFEAT } flash_type;
786};
787
788static game_ui *new_ui(const game_state *state)
789{
790 struct game_ui *ui = snew(struct game_ui);
791 ui->cursor_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
792 ui->cx = FILLX;
793 ui->cy = FILLY;
794 return ui;
795}
796
797static void free_ui(game_ui *ui)
798{
799 sfree(ui);
800}
801
802static void game_changed_state(game_ui *ui, const game_state *oldstate,
803 const game_state *newstate)
804{
805}
806
807static const char *current_key_label(const game_ui *ui,
808 const game_state *state, int button)
809{
810 if (button == CURSOR_SELECT &&
811 state->grid[0] != state->grid[ui->cy*state->w+ui->cx])
812 return "Fill";
813 if (button == CURSOR_SELECT2 &&
814 state->soln && state->solnpos < state->soln->nmoves)
815 return "Advance";
816 return "";
817}
818
819struct game_drawstate {
820 bool started;
821 int tilesize;
822 int *grid;
823};
824
825#define TILESIZE (ds->tilesize)
826#define PREFERRED_TILESIZE 32
827#define BORDER (TILESIZE / 2)
828#define SEP_WIDTH (TILESIZE / 32)
829#define CURSOR_INSET (TILESIZE / 8)
830#define HIGHLIGHT_WIDTH (TILESIZE / 10)
831#define COORD(x) ( (x) * TILESIZE + BORDER )
832#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
833#define VICTORY_FLASH_FRAME 0.03F
834#define DEFEAT_FLASH_FRAME 0.10F
835
836static char *interpret_move(const game_state *state, game_ui *ui,
837 const game_drawstate *ds,
838 int x, int y, int button)
839{
840 int w = state->w, h = state->h;
841 int tx = -1, ty = -1, move = -1;
842 char *nullret = MOVE_NO_EFFECT;
843
844 if (button == LEFT_BUTTON) {
845 tx = FROMCOORD(x);
846 ty = FROMCOORD(y);
847 if (ui->cursor_visible) {
848 ui->cursor_visible = false;
849 nullret = MOVE_UI_UPDATE;
850 }
851 } else if (IS_CURSOR_MOVE(button)) {
852 return move_cursor(button, &ui->cx, &ui->cy, w, h, false,
853 &ui->cursor_visible);
854 } else if (button == CURSOR_SELECT) {
855 tx = ui->cx;
856 ty = ui->cy;
857 } else if (button == CURSOR_SELECT2) {
858 if (state->soln && state->solnpos < state->soln->nmoves)
859 move = state->soln->moves[state->solnpos];
860 } else {
861 return MOVE_UNUSED;
862 }
863
864 if (tx >= 0 && tx < w && ty >= 0 && ty < h &&
865 state->grid[0] != state->grid[ty*w+tx])
866 move = state->grid[ty*w+tx];
867
868 if (move >= 0 && !state->complete) {
869 char buf[256];
870 sprintf(buf, "M%d", move);
871 return dupstr(buf);
872 }
873
874 return nullret;
875}
876
877static game_state *execute_move(const game_state *state, const char *move)
878{
879 game_state *ret;
880 int c;
881
882 if (move[0] == 'M' &&
883 sscanf(move+1, "%d", &c) == 1 &&
884 c >= 0 && c < state->colours &&
885 c != state->grid[FILLY * state->w + FILLX] &&
886 !state->complete) {
887 int *queue = snewn(state->w * state->h, int);
888 ret = dup_game(state);
889 fill(ret->w, ret->h, ret->grid, FILLX, FILLY, c, queue);
890 ret->moves++;
891 ret->complete = completed(ret->w, ret->h, ret->grid);
892
893 if (ret->soln) {
894 /*
895 * If this move is the correct next one in the stored
896 * solution path, advance solnpos.
897 */
898 if (c == ret->soln->moves[ret->solnpos] &&
899 ret->solnpos+1 < ret->soln->nmoves) {
900 ret->solnpos++;
901 } else {
902 /*
903 * Otherwise, the user has strayed from the path or
904 * else the path has come to an end; either way, the
905 * path is no longer valid.
906 */
907 ret->soln->refcount--;
908 assert(ret->soln->refcount > 0);/* `state' at least still exists */
909 ret->soln = NULL;
910 ret->solnpos = 0;
911 }
912 }
913
914 sfree(queue);
915 return ret;
916 } else if (*move == 'S') {
917 soln *sol;
918 const char *p;
919 int i;
920
921 /*
922 * This is a solve move, so we don't actually _change_ the
923 * grid but merely set up a stored solution path.
924 */
925 move++;
926 sol = snew(soln);
927
928 sol->nmoves = 1;
929 for (p = move; *p; p++) {
930 if (*p == ',')
931 sol->nmoves++;
932 }
933
934 sol->moves = snewn(sol->nmoves, char);
935 for (i = 0, p = move; i < sol->nmoves; i++) {
936 if (!*p) {
937 badsolve:
938 sfree(sol->moves);
939 sfree(sol);
940 return NULL;
941 };
942 sol->moves[i] = atoi(p);
943 if (sol->moves[i] < 0 || sol->moves[i] >= state->colours ||
944 (i == 0 ?
945 sol->moves[i] == state->grid[FILLY * state->w + FILLX] :
946 sol->moves[i] == sol->moves[i-1]))
947 /* Solution contains a fill with an invalid colour or
948 * the current colour. */
949 goto badsolve;
950 p += strspn(p, "0123456789");
951 if (*p) {
952 if (*p != ',') goto badsolve;
953 p++;
954 }
955 }
956
957 ret = dup_game(state);
958 ret->cheated = true;
959 if (ret->soln && --ret->soln->refcount == 0) {
960 sfree(ret->soln->moves);
961 sfree(ret->soln);
962 }
963 ret->soln = sol;
964 ret->solnpos = 0;
965 sol->refcount = 1;
966 return ret;
967 }
968
969 return NULL;
970}
971
972/* ----------------------------------------------------------------------
973 * Drawing routines.
974 */
975
976static void game_compute_size(const game_params *params, int tilesize,
977 const game_ui *ui, int *x, int *y)
978{
979 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
980 struct { int tilesize; } ads, *ds = &ads;
981 ads.tilesize = tilesize;
982
983 *x = BORDER * 2 + TILESIZE * params->w;
984 *y = BORDER * 2 + TILESIZE * params->h;
985}
986
987static void game_set_size(drawing *dr, game_drawstate *ds,
988 const game_params *params, int tilesize)
989{
990 ds->tilesize = tilesize;
991}
992
993static float *game_colours(frontend *fe, int *ncolours)
994{
995 float *ret = snewn(3 * NCOLOURS, float);
996
997 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
998
999 ret[COL_SEPARATOR * 3 + 0] = 0.0F;
1000 ret[COL_SEPARATOR * 3 + 1] = 0.0F;
1001 ret[COL_SEPARATOR * 3 + 2] = 0.0F;
1002
1003 /* red */
1004 ret[COL_1 * 3 + 0] = 1.0F;
1005 ret[COL_1 * 3 + 1] = 0.0F;
1006 ret[COL_1 * 3 + 2] = 0.0F;
1007
1008 /* yellow */
1009 ret[COL_2 * 3 + 0] = 1.0F;
1010 ret[COL_2 * 3 + 1] = 1.0F;
1011 ret[COL_2 * 3 + 2] = 0.0F;
1012
1013 /* green */
1014 ret[COL_3 * 3 + 0] = 0.0F;
1015 ret[COL_3 * 3 + 1] = 1.0F;
1016 ret[COL_3 * 3 + 2] = 0.0F;
1017
1018 /* blue */
1019 ret[COL_4 * 3 + 0] = 0.2F;
1020 ret[COL_4 * 3 + 1] = 0.3F;
1021 ret[COL_4 * 3 + 2] = 1.0F;
1022
1023 /* orange */
1024 ret[COL_5 * 3 + 0] = 1.0F;
1025 ret[COL_5 * 3 + 1] = 0.5F;
1026 ret[COL_5 * 3 + 2] = 0.0F;
1027
1028 /* purple */
1029 ret[COL_6 * 3 + 0] = 0.5F;
1030 ret[COL_6 * 3 + 1] = 0.0F;
1031 ret[COL_6 * 3 + 2] = 0.7F;
1032
1033 /* brown */
1034 ret[COL_7 * 3 + 0] = 0.5F;
1035 ret[COL_7 * 3 + 1] = 0.3F;
1036 ret[COL_7 * 3 + 2] = 0.3F;
1037
1038 /* light blue */
1039 ret[COL_8 * 3 + 0] = 0.4F;
1040 ret[COL_8 * 3 + 1] = 0.8F;
1041 ret[COL_8 * 3 + 2] = 1.0F;
1042
1043 /* light green */
1044 ret[COL_9 * 3 + 0] = 0.7F;
1045 ret[COL_9 * 3 + 1] = 1.0F;
1046 ret[COL_9 * 3 + 2] = 0.7F;
1047
1048 /* pink */
1049 ret[COL_10 * 3 + 0] = 1.0F;
1050 ret[COL_10 * 3 + 1] = 0.6F;
1051 ret[COL_10 * 3 + 2] = 1.0F;
1052
1053 *ncolours = NCOLOURS;
1054 return ret;
1055}
1056
1057static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
1058{
1059 struct game_drawstate *ds = snew(struct game_drawstate);
1060 int w = state->w, h = state->h, wh = w*h;
1061 int i;
1062
1063 ds->started = false;
1064 ds->tilesize = 0;
1065 ds->grid = snewn(wh, int);
1066 for (i = 0; i < wh; i++)
1067 ds->grid[i] = -1;
1068
1069 return ds;
1070}
1071
1072static void game_free_drawstate(drawing *dr, game_drawstate *ds)
1073{
1074 sfree(ds->grid);
1075 sfree(ds);
1076}
1077
1078#define BORDER_L 0x001
1079#define BORDER_R 0x002
1080#define BORDER_U 0x004
1081#define BORDER_D 0x008
1082#define CORNER_UL 0x010
1083#define CORNER_UR 0x020
1084#define CORNER_DL 0x040
1085#define CORNER_DR 0x080
1086#define CURSOR 0x100
1087#define BADFLASH 0x200
1088#define SOLNNEXT 0x400
1089#define COLOUR_SHIFT 11
1090
1091static void draw_tile(drawing *dr, game_drawstate *ds,
1092 int x, int y, int tile)
1093{
1094 int colour;
1095 int tx = COORD(x), ty = COORD(y);
1096
1097 colour = tile >> COLOUR_SHIFT;
1098 if (tile & BADFLASH)
1099 colour = COL_SEPARATOR;
1100 else
1101 colour += COL_1;
1102 draw_rect(dr, tx, ty, TILESIZE, TILESIZE, colour);
1103
1104 if (SEP_WIDTH > 0) {
1105 if (tile & BORDER_L)
1106 draw_rect(dr, tx, ty,
1107 SEP_WIDTH, TILESIZE, COL_SEPARATOR);
1108 if (tile & BORDER_R)
1109 draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty,
1110 SEP_WIDTH, TILESIZE, COL_SEPARATOR);
1111 if (tile & BORDER_U)
1112 draw_rect(dr, tx, ty,
1113 TILESIZE, SEP_WIDTH, COL_SEPARATOR);
1114 if (tile & BORDER_D)
1115 draw_rect(dr, tx, ty + TILESIZE - SEP_WIDTH,
1116 TILESIZE, SEP_WIDTH, COL_SEPARATOR);
1117
1118 if (tile & CORNER_UL)
1119 draw_rect(dr, tx, ty,
1120 SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
1121 if (tile & CORNER_UR)
1122 draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty,
1123 SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
1124 if (tile & CORNER_DL)
1125 draw_rect(dr, tx, ty + TILESIZE - SEP_WIDTH,
1126 SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
1127 if (tile & CORNER_DR)
1128 draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty + TILESIZE - SEP_WIDTH,
1129 SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
1130 }
1131
1132 if (tile & CURSOR)
1133 draw_rect_outline(dr, tx + CURSOR_INSET, ty + CURSOR_INSET,
1134 TILESIZE - 1 - CURSOR_INSET * 2,
1135 TILESIZE - 1 - CURSOR_INSET * 2,
1136 COL_SEPARATOR);
1137
1138 if (tile & SOLNNEXT) {
1139 draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, TILESIZE/6,
1140 COL_SEPARATOR, COL_SEPARATOR);
1141 }
1142
1143 draw_update(dr, tx, ty, TILESIZE, TILESIZE);
1144}
1145
1146static void game_redraw(drawing *dr, game_drawstate *ds,
1147 const game_state *oldstate, const game_state *state,
1148 int dir, const game_ui *ui,
1149 float animtime, float flashtime)
1150{
1151 int w = state->w, h = state->h, wh = w*h;
1152 int x, y, flashframe, solnmove;
1153 char *grid;
1154
1155 /* This was entirely cloned from fifteen.c; it should probably be
1156 * moved into some generic 'draw-recessed-rectangle' utility fn. */
1157 if (!ds->started) {
1158 int coords[10];
1159
1160 /*
1161 * Recessed area containing the whole puzzle.
1162 */
1163 coords[0] = COORD(w) + HIGHLIGHT_WIDTH - 1;
1164 coords[1] = COORD(h) + HIGHLIGHT_WIDTH - 1;
1165 coords[2] = COORD(w) + HIGHLIGHT_WIDTH - 1;
1166 coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
1167 coords[4] = coords[2] - TILESIZE;
1168 coords[5] = coords[3] + TILESIZE;
1169 coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
1170 coords[9] = COORD(h) + HIGHLIGHT_WIDTH - 1;
1171 coords[6] = coords[8] + TILESIZE;
1172 coords[7] = coords[9] - TILESIZE;
1173 draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
1174
1175 coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
1176 coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
1177 draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
1178
1179 draw_rect(dr, COORD(0) - SEP_WIDTH, COORD(0) - SEP_WIDTH,
1180 TILESIZE * w + 2 * SEP_WIDTH, TILESIZE * h + 2 * SEP_WIDTH,
1181 COL_SEPARATOR);
1182
1183 ds->started = true;
1184 }
1185
1186 if (flashtime > 0) {
1187 float frame = (ui->flash_type == VICTORY ?
1188 VICTORY_FLASH_FRAME : DEFEAT_FLASH_FRAME);
1189 flashframe = (int)(flashtime / frame);
1190 } else {
1191 flashframe = -1;
1192 }
1193
1194 grid = snewn(wh, char);
1195 memcpy(grid, state->grid, wh * sizeof(*grid));
1196
1197 if (state->soln && state->solnpos < state->soln->nmoves) {
1198 int i, *queue;
1199
1200 /*
1201 * Highlight as 'next auto-solver move' every square of the
1202 * target colour which is adjacent to the currently controlled
1203 * region. We do this by first enacting the actual move, then
1204 * flood-filling again in a nonexistent colour, and finally
1205 * reverting to the original grid anything in the new colour
1206 * that was part of the original controlled region. Then
1207 * regions coloured in the dummy colour should be displayed as
1208 * soln_move with the SOLNNEXT flag.
1209 */
1210 solnmove = state->soln->moves[state->solnpos];
1211
1212 queue = snewn(wh, int);
1213 fill(w, h, grid, FILLX, FILLY, solnmove, queue);
1214 fill(w, h, grid, FILLX, FILLY, state->colours, queue);
1215 sfree(queue);
1216
1217 for (i = 0; i < wh; i++)
1218 if (grid[i] == state->colours && state->grid[i] != solnmove)
1219 grid[i] = state->grid[i];
1220 } else {
1221 solnmove = 0; /* placate optimiser */
1222 }
1223
1224 if (flashframe >= 0 && ui->flash_type == VICTORY) {
1225 /*
1226 * Modify the display grid by superimposing our rainbow flash
1227 * on it.
1228 */
1229 for (x = 0; x < w; x++) {
1230 for (y = 0; y < h; y++) {
1231 int flashpos = flashframe - (abs(x - FILLX) + abs(y - FILLY));
1232 if (flashpos >= 0 && flashpos < state->colours)
1233 grid[y*w+x] = flashpos;
1234 }
1235 }
1236 }
1237
1238 for (x = 0; x < w; x++) {
1239 for (y = 0; y < h; y++) {
1240 int pos = y*w+x;
1241 int tile;
1242
1243 if (grid[pos] == state->colours) {
1244 tile = (solnmove << COLOUR_SHIFT) | SOLNNEXT;
1245 } else {
1246 tile = (int)grid[pos] << COLOUR_SHIFT;
1247 }
1248
1249 if (x == 0 || grid[pos-1] != grid[pos])
1250 tile |= BORDER_L;
1251 if (x==w-1 || grid[pos+1] != grid[pos])
1252 tile |= BORDER_R;
1253 if (y == 0 || grid[pos-w] != grid[pos])
1254 tile |= BORDER_U;
1255 if (y==h-1 || grid[pos+w] != grid[pos])
1256 tile |= BORDER_D;
1257 if (x == 0 || y == 0 || grid[pos-w-1] != grid[pos])
1258 tile |= CORNER_UL;
1259 if (x==w-1 || y == 0 || grid[pos-w+1] != grid[pos])
1260 tile |= CORNER_UR;
1261 if (x == 0 || y==h-1 || grid[pos+w-1] != grid[pos])
1262 tile |= CORNER_DL;
1263 if (x==w-1 || y==h-1 || grid[pos+w+1] != grid[pos])
1264 tile |= CORNER_DR;
1265 if (ui->cursor_visible && ui->cx == x && ui->cy == y)
1266 tile |= CURSOR;
1267
1268 if (flashframe >= 0 && ui->flash_type == DEFEAT && flashframe != 1)
1269 tile |= BADFLASH;
1270
1271 if (ds->grid[pos] != tile) {
1272 draw_tile(dr, ds, x, y, tile);
1273 ds->grid[pos] = tile;
1274 }
1275 }
1276 }
1277
1278 sfree(grid);
1279
1280 {
1281 char status[255];
1282
1283 sprintf(status, "%s%d / %d moves",
1284 (state->complete && state->moves <= state->movelimit ?
1285 (state->cheated ? "Auto-solved. " : "COMPLETED! ") :
1286 state->moves >= state->movelimit ? "FAILED! " :
1287 state->cheated ? "Auto-solver used. " :
1288 ""),
1289 state->moves,
1290 state->movelimit);
1291
1292 status_bar(dr, status);
1293 }
1294}
1295
1296static float game_anim_length(const game_state *oldstate,
1297 const game_state *newstate, int dir, game_ui *ui)
1298{
1299 return 0.0F;
1300}
1301
1302static void game_get_cursor_location(const game_ui *ui,
1303 const game_drawstate *ds,
1304 const game_state *state,
1305 const game_params *params,
1306 int *x, int *y, int *w, int *h)
1307{
1308 if(ui->cursor_visible)
1309 {
1310 *x = COORD(ui->cx);
1311 *y = COORD(ui->cy);
1312 *w = *h = TILESIZE;
1313 }
1314}
1315
1316static int game_status(const game_state *state)
1317{
1318 if (state->complete && state->moves <= state->movelimit) {
1319 return +1; /* victory! */
1320 } else if (state->moves >= state->movelimit) {
1321 return -1; /* defeat */
1322 } else {
1323 return 0; /* still playing */
1324 }
1325}
1326
1327static float game_flash_length(const game_state *oldstate,
1328 const game_state *newstate, int dir, game_ui *ui)
1329{
1330 if (dir == +1) {
1331 int old_status = game_status(oldstate);
1332 int new_status = game_status(newstate);
1333 if (old_status != new_status) {
1334 assert(old_status == 0);
1335
1336 if (new_status == +1) {
1337 int frames = newstate->w + newstate->h + newstate->colours - 2;
1338 ui->flash_type = VICTORY;
1339 return VICTORY_FLASH_FRAME * frames;
1340 } else {
1341 ui->flash_type = DEFEAT;
1342 return DEFEAT_FLASH_FRAME * 3;
1343 }
1344 }
1345 }
1346 return 0.0F;
1347}
1348
1349#ifdef COMBINED
1350#define thegame flood
1351#endif
1352
1353const struct game thegame = {
1354 "Flood", "games.flood", "flood",
1355 default_params,
1356 game_fetch_preset, NULL,
1357 decode_params,
1358 encode_params,
1359 free_params,
1360 dup_params,
1361 true, game_configure, custom_params,
1362 validate_params,
1363 new_game_desc,
1364 validate_desc,
1365 new_game,
1366 dup_game,
1367 free_game,
1368 true, solve_game,
1369 true, game_can_format_as_text_now, game_text_format,
1370 NULL, NULL, /* get_prefs, set_prefs */
1371 new_ui,
1372 free_ui,
1373 NULL, /* encode_ui */
1374 NULL, /* decode_ui */
1375 NULL, /* game_request_keys */
1376 game_changed_state,
1377 current_key_label,
1378 interpret_move,
1379 execute_move,
1380 PREFERRED_TILESIZE, game_compute_size, game_set_size,
1381 game_colours,
1382 game_new_drawstate,
1383 game_free_drawstate,
1384 game_redraw,
1385 game_anim_length,
1386 game_flash_length,
1387 game_get_cursor_location,
1388 game_status,
1389 false, false, NULL, NULL, /* print_size, print */
1390 true, /* wants_statusbar */
1391 false, NULL, /* timing_state */
1392 0, /* flags */
1393};