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.
8 * Possible further work:
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
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.
38 COL_BACKGROUND, COL_SEPARATOR,
39 COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9, COL_10,
40 COL_HIGHLIGHT, COL_LOWLIGHT,
50 /* Just in case I want to make this changeable later, I'll put the
51 * coordinates of the flood-fill point here so that it'll be easy to
52 * find everywhere later that has to change. */
72 static game_params *default_params(void)
74 game_params *ret = snew(game_params);
83 static const struct game_params flood_presets[] = {
92 static int game_fetch_preset(int i, char **name, game_params **params)
97 if (i < 0 || i >= lenof(flood_presets))
100 ret = snew(game_params);
101 *ret = flood_presets[i];
103 sprintf(str, "%dx%d, %d colours, %d extra moves",
104 ret->w, ret->h, ret->colours, ret->leniency);
111 static void free_params(game_params *params)
116 static game_params *dup_params(const game_params *params)
118 game_params *ret = snew(game_params);
119 *ret = *params; /* structure copy */
123 static void decode_params(game_params *ret, char const *string)
125 ret->w = ret->h = atoi(string);
126 while (*string && isdigit((unsigned char)*string)) string++;
127 if (*string == 'x') {
129 ret->h = atoi(string);
130 while (*string && isdigit((unsigned char)*string)) string++;
133 if (*string == 'c') {
135 ret->colours = atoi(string);
136 while (string[1] && isdigit((unsigned char)string[1])) string++;
137 } else if (*string == 'm') {
139 ret->leniency = atoi(string);
140 while (string[1] && isdigit((unsigned char)string[1])) string++;
146 static char *encode_params(const game_params *params, int full)
149 sprintf(buf, "%dx%d", params->w, params->h);
151 sprintf(buf + strlen(buf), "c%dm%d",
152 params->colours, params->leniency);
156 static config_item *game_configure(const game_params *params)
161 ret = snewn(5, config_item);
163 ret[0].name = "Width";
164 ret[0].type = C_STRING;
165 sprintf(buf, "%d", params->w);
166 ret[0].sval = dupstr(buf);
169 ret[1].name = "Height";
170 ret[1].type = C_STRING;
171 sprintf(buf, "%d", params->h);
172 ret[1].sval = dupstr(buf);
175 ret[2].name = "Colours";
176 ret[2].type = C_STRING;
177 sprintf(buf, "%d", params->colours);
178 ret[2].sval = dupstr(buf);
181 ret[3].name = "Extra moves permitted";
182 ret[3].type = C_STRING;
183 sprintf(buf, "%d", params->leniency);
184 ret[3].sval = dupstr(buf);
195 static game_params *custom_params(const config_item *cfg)
197 game_params *ret = snew(game_params);
199 ret->w = atoi(cfg[0].sval);
200 ret->h = atoi(cfg[1].sval);
201 ret->colours = atoi(cfg[2].sval);
202 ret->leniency = atoi(cfg[3].sval);
207 static char *validate_params(const game_params *params, int full)
209 if (params->w < 2 && params->h < 2)
210 return "Grid must contain at least two squares";
211 if (params->colours < 3 || params->colours > 10)
212 return "Must have between 3 and 10 colours";
213 if (params->leniency < 0)
214 return "Leniency must be non-negative";
218 struct solver_scratch {
221 char *grid, *grid2, *sparegrid;
224 static struct solver_scratch *new_scratch(int w, int h)
226 struct solver_scratch *scratch = snew(struct solver_scratch);
227 scratch->queue[0] = snewn(w*h, int);
228 scratch->queue[1] = snewn(w*h, int);
229 scratch->dist = snewn(w*h, int);
230 scratch->grid = snewn(w*h, char);
231 scratch->grid2 = snewn(w*h, char);
232 scratch->sparegrid = snewn(w*h, char);
236 static void free_scratch(struct solver_scratch *scratch)
238 sfree(scratch->queue[0]);
239 sfree(scratch->queue[1]);
240 sfree(scratch->dist);
241 sfree(scratch->grid);
242 sfree(scratch->grid2);
243 sfree(scratch->sparegrid);
248 /* Diagnostic routines you can uncomment if you need them */
249 void dump_grid(int w, int h, const char *grid, const char *title)
252 printf("%s:\n", title ? title : "Grid");
253 for (y = 0; y < h; y++) {
255 for (x = 0; x < w; x++) {
256 printf("%1x", grid[y*w+x]);
262 void dump_dist(int w, int h, const int *dists, const char *title)
265 printf("%s:\n", title ? title : "Distances");
266 for (y = 0; y < h; y++) {
268 for (x = 0; x < w; x++) {
269 printf("%3d", dists[y*w+x]);
277 * Search a grid to find the most distant square(s). Return their
278 * distance and the number of them.
280 static void search(int w, int h, char *grid, int x0, int y0,
281 struct solver_scratch *scratch, int *rdist, int *rnumber)
284 int i, qcurr, qhead, qtail, qnext, currdist, remaining;
286 for (i = 0; i < wh; i++)
287 scratch->dist[i] = -1;
288 scratch->queue[0][0] = y0*w+x0;
289 scratch->queue[1][0] = y0*w+x0;
290 scratch->dist[y0*w+x0] = 0;
299 if (qtail == qhead) {
302 qcurr ^= 1; /* switch queues */
307 printf("switch queue, new dist %d, queue %d\n", currdist, qhead);
309 } else if (remaining == 0 && qnext == 0) {
312 int pos = scratch->queue[qcurr][qtail++];
316 printf("checking neighbours of %d,%d\n", x, y);
319 for (dir = 0; dir < 4; dir++) {
320 int y1 = y + (dir == 1 ? 1 : dir == 3 ? -1 : 0);
321 int x1 = x + (dir == 0 ? 1 : dir == 2 ? -1 : 0);
322 if (0 <= x1 && x1 < w && 0 <= y1 && y1 < h) {
325 printf("trying %d,%d: colours %d-%d dist %d\n", x1, y1,
326 grid[pos], grid[pos1], scratch->dist[pos]);
328 if (scratch->dist[pos1] == -1 &&
329 ((grid[pos1] == grid[pos] &&
330 scratch->dist[pos] == currdist) ||
331 (grid[pos1] != grid[pos] &&
332 scratch->dist[pos] == currdist - 1))) {
334 printf("marking %d,%d dist %d\n", x1, y1, currdist);
336 scratch->queue[qcurr][qhead++] = pos1;
337 scratch->queue[qcurr^1][qnext++] = pos1;
338 scratch->dist[pos1] = currdist;
351 * Enact a flood-fill move on a grid.
353 static void fill(int w, int h, char *grid, int x0, int y0, char newcolour,
359 oldcolour = grid[y0*w+x0];
360 assert(oldcolour != newcolour);
361 grid[y0*w+x0] = newcolour;
366 while (qtail < qhead) {
367 int pos = queue[qtail++];
371 for (dir = 0; dir < 4; dir++) {
372 int y1 = y + (dir == 1 ? 1 : dir == 3 ? -1 : 0);
373 int x1 = x + (dir == 0 ? 1 : dir == 2 ? -1 : 0);
374 if (0 <= x1 && x1 < w && 0 <= y1 && y1 < h) {
376 if (grid[pos1] == oldcolour) {
377 grid[pos1] = newcolour;
378 queue[qhead++] = pos1;
386 * Try out every possible move on a grid, and choose whichever one
387 * reduced the result of search() by the most.
389 static char choosemove(int w, int h, char *grid, int x0, int y0,
390 int maxmove, struct solver_scratch *scratch)
394 int dist, number, bestdist, bestnumber;
401 dump_grid(w, h, grid, "before choosemove");
403 for (move = 0; move < maxmove; move++) {
405 sprintf(buf, "after move %d", move);
406 if (grid[y0*w+x0] == move)
408 memcpy(scratch->sparegrid, grid, wh * sizeof(*grid));
409 fill(w, h, scratch->sparegrid, x0, y0, move, scratch->queue[0]);
411 dump_grid(w, h, scratch->sparegrid, buf);
413 search(w, h, scratch->sparegrid, x0, y0, scratch, &dist, &number);
415 dump_dist(w, h, scratch->dist, buf);
416 printf("move %d: %d at %d\n", move, number, dist);
418 if (dist < bestdist || (dist == bestdist && number < bestnumber)) {
425 printf("best was %d\n", bestmove);
432 * Detect a completed grid.
434 static int completed(int w, int h, char *grid)
439 for (i = 1; i < wh; i++)
440 if (grid[i] != grid[0])
446 static char *new_game_desc(const game_params *params, random_state *rs,
447 char **aux, int interactive)
449 int w = params->w, h = params->h, wh = w*h;
452 struct solver_scratch *scratch;
454 scratch = new_scratch(w, h);
457 * Invent a random grid.
459 for (i = 0; i < wh; i++)
460 scratch->grid[i] = random_upto(rs, params->colours);
463 * Run the solver, and count how many moves it uses.
465 memcpy(scratch->grid2, scratch->grid, wh * sizeof(*scratch->grid2));
467 while (!completed(w, h, scratch->grid2)) {
468 char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
469 params->colours, scratch);
470 fill(w, h, scratch->grid2, FILLX, FILLY, move, scratch->queue[0]);
475 * Adjust for difficulty.
477 moves += params->leniency;
480 * Encode the game id.
482 desc = snewn(wh + 40, char);
483 for (i = 0; i < wh; i++) {
484 char colour = scratch->grid[i];
485 char textcolour = (colour > 9 ? 'A' : '0') + colour;
486 desc[i] = textcolour;
488 sprintf(desc+i, ",%d", moves);
490 free_scratch(scratch);
495 static char *validate_desc(const game_params *params, const char *desc)
497 int w = params->w, h = params->h, wh = w*h;
499 for (i = 0; i < wh; i++) {
502 return "Not enough data in grid description";
503 if (c >= '0' && c <= '9')
505 else if (c >= 'A' && c <= 'Z')
508 return "Bad character in grid description";
509 if (c < 0 || c >= params->colours)
510 return "Colour out of range in grid description";
513 return "Expected ',' after grid description";
515 if (desc[strspn(desc, "0123456789")])
516 return "Badly formatted move limit after grid description";
520 static game_state *new_game(midend *me, const game_params *params,
523 int w = params->w, h = params->h, wh = w*h;
524 game_state *state = snew(game_state);
529 state->colours = params->colours;
531 state->grid = snewn(wh, char);
533 for (i = 0; i < wh; i++) {
536 if (c >= '0' && c <= '9')
538 else if (c >= 'A' && c <= 'Z')
541 assert(!"bad colour");
544 assert(*desc == ',');
547 state->movelimit = atoi(desc);
548 state->complete = FALSE;
549 state->cheated = FALSE;
556 static game_state *dup_game(const game_state *state)
558 game_state *ret = snew(game_state);
562 ret->colours = state->colours;
563 ret->moves = state->moves;
564 ret->movelimit = state->movelimit;
565 ret->complete = state->complete;
566 ret->grid = snewn(state->w * state->h, char);
567 memcpy(ret->grid, state->grid, state->w * state->h * sizeof(*ret->grid));
569 ret->cheated = state->cheated;
570 ret->soln = state->soln;
572 ret->soln->refcount++;
573 ret->solnpos = state->solnpos;
578 static void free_game(game_state *state)
580 if (state->soln && --state->soln->refcount == 0) {
581 sfree(state->soln->moves);
588 static char *solve_game(const game_state *state, const game_state *currstate,
589 const char *aux, char **error)
591 int w = state->w, h = state->h, wh = w*h;
592 char *moves, *ret, *p;
595 struct solver_scratch *scratch;
597 if (currstate->complete)
601 * Find the best solution our solver can give.
603 moves = snewn(wh, char); /* sure to be enough */
605 scratch = new_scratch(w, h);
606 memcpy(scratch->grid2, currstate->grid, wh * sizeof(*scratch->grid2));
607 while (!completed(w, h, scratch->grid2)) {
608 char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
609 currstate->colours, scratch);
610 fill(w, h, scratch->grid2, FILLX, FILLY, move, scratch->queue[0]);
612 moves[nmoves++] = move;
614 free_scratch(scratch);
617 * Encode it as a move string.
619 len = 1; /* trailing NUL */
620 for (i = 0; i < nmoves; i++)
621 len += sprintf(buf, ",%d", moves[i]);
622 ret = snewn(len, char);
624 for (i = 0; i < nmoves; i++)
625 p += sprintf(p, "%c%d", (i==0 ? 'S' : ','), moves[i]);
626 assert(p - ret == len - 1);
632 static int game_can_format_as_text_now(const game_params *params)
637 static char *game_text_format(const game_state *state)
639 int w = state->w, h = state->h;
643 len = h * (w+1); /* +1 for newline after each row */
644 ret = snewn(len+1, char); /* and +1 for terminating \0 */
647 for (y = 0; y < h; y++) {
648 for (x = 0; x < w; x++) {
649 char colour = state->grid[y*w+x];
650 char textcolour = (colour > 9 ? 'A' : '0') + colour;
656 assert(p - ret == len);
665 enum { VICTORY, DEFEAT } flash_type;
668 static game_ui *new_ui(const game_state *state)
670 struct game_ui *ui = snew(struct game_ui);
671 ui->cursor_visible = FALSE;
677 static void free_ui(game_ui *ui)
682 static char *encode_ui(const game_ui *ui)
687 static void decode_ui(game_ui *ui, const char *encoding)
691 static void game_changed_state(game_ui *ui, const game_state *oldstate,
692 const game_state *newstate)
696 struct game_drawstate {
702 #define TILESIZE (ds->tilesize)
703 #define PREFERRED_TILESIZE 32
704 #define BORDER (TILESIZE / 2)
705 #define SEP_WIDTH (TILESIZE / 32)
706 #define CURSOR_INSET (TILESIZE / 8)
707 #define HIGHLIGHT_WIDTH (TILESIZE / 10)
708 #define COORD(x) ( (x) * TILESIZE + BORDER )
709 #define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
710 #define VICTORY_FLASH_FRAME 0.03F
711 #define DEFEAT_FLASH_FRAME 0.10F
713 static char *interpret_move(const game_state *state, game_ui *ui,
714 const game_drawstate *ds,
715 int x, int y, int button)
717 int w = state->w, h = state->h;
718 int tx = -1, ty = -1, move = -1;
720 if (button == LEFT_BUTTON) {
723 ui->cursor_visible = FALSE;
724 } else if (button == CURSOR_LEFT && ui->cx > 0) {
726 ui->cursor_visible = TRUE;
728 } else if (button == CURSOR_RIGHT && ui->cx+1 < w) {
730 ui->cursor_visible = TRUE;
732 } else if (button == CURSOR_UP && ui->cy > 0) {
734 ui->cursor_visible = TRUE;
736 } else if (button == CURSOR_DOWN && ui->cy+1 < h) {
738 ui->cursor_visible = TRUE;
740 } else if (button == CURSOR_SELECT) {
743 } else if (button == CURSOR_SELECT2 &&
744 state->soln && state->solnpos < state->soln->nmoves) {
745 move = state->soln->moves[state->solnpos];
750 if (tx >= 0 && tx < w && ty >= 0 && ty < h &&
751 state->grid[0] != state->grid[ty*w+tx])
752 move = state->grid[ty*w+tx];
754 if (move >= 0 && !state->complete) {
756 sprintf(buf, "M%d", move);
763 static game_state *execute_move(const game_state *state, const char *move)
768 if (move[0] == 'M' &&
769 sscanf(move+1, "%d", &c) == 1 &&
772 int *queue = snewn(state->w * state->h, int);
773 ret = dup_game(state);
774 fill(ret->w, ret->h, ret->grid, FILLX, FILLY, c, queue);
776 ret->complete = completed(ret->w, ret->h, ret->grid);
780 * If this move is the correct next one in the stored
781 * solution path, advance solnpos.
783 if (c == ret->soln->moves[ret->solnpos] &&
784 ret->solnpos+1 < ret->soln->nmoves) {
788 * Otherwise, the user has strayed from the path or
789 * else the path has come to an end; either way, the
790 * path is no longer valid.
792 ret->soln->refcount--;
793 assert(ret->soln->refcount > 0);/* `state' at least still exists */
801 } else if (*move == 'S') {
807 * This is a solve move, so we don't actually _change_ the
808 * grid but merely set up a stored solution path.
814 for (p = move; *p; p++) {
819 sol->moves = snewn(sol->nmoves, char);
820 for (i = 0, p = move; i < sol->nmoves; i++) {
822 sol->moves[i] = atoi(p);
823 p += strspn(p, "0123456789");
830 ret = dup_game(state);
832 if (ret->soln && --ret->soln->refcount == 0) {
833 sfree(ret->soln->moves);
845 /* ----------------------------------------------------------------------
849 static void game_compute_size(const game_params *params, int tilesize,
852 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
853 struct { int tilesize; } ads, *ds = &ads;
854 ads.tilesize = tilesize;
856 *x = BORDER * 2 + TILESIZE * params->w;
857 *y = BORDER * 2 + TILESIZE * params->h;
860 static void game_set_size(drawing *dr, game_drawstate *ds,
861 const game_params *params, int tilesize)
863 ds->tilesize = tilesize;
866 static float *game_colours(frontend *fe, int *ncolours)
868 float *ret = snewn(3 * NCOLOURS, float);
870 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
872 ret[COL_SEPARATOR * 3 + 0] = 0.0F;
873 ret[COL_SEPARATOR * 3 + 1] = 0.0F;
874 ret[COL_SEPARATOR * 3 + 2] = 0.0F;
877 ret[COL_1 * 3 + 0] = 1.0F;
878 ret[COL_1 * 3 + 1] = 0.0F;
879 ret[COL_1 * 3 + 2] = 0.0F;
882 ret[COL_2 * 3 + 0] = 1.0F;
883 ret[COL_2 * 3 + 1] = 1.0F;
884 ret[COL_2 * 3 + 2] = 0.0F;
887 ret[COL_3 * 3 + 0] = 0.0F;
888 ret[COL_3 * 3 + 1] = 1.0F;
889 ret[COL_3 * 3 + 2] = 0.0F;
892 ret[COL_4 * 3 + 0] = 0.2F;
893 ret[COL_4 * 3 + 1] = 0.3F;
894 ret[COL_4 * 3 + 2] = 1.0F;
897 ret[COL_5 * 3 + 0] = 1.0F;
898 ret[COL_5 * 3 + 1] = 0.5F;
899 ret[COL_5 * 3 + 2] = 0.0F;
902 ret[COL_6 * 3 + 0] = 0.5F;
903 ret[COL_6 * 3 + 1] = 0.0F;
904 ret[COL_6 * 3 + 2] = 0.7F;
907 ret[COL_7 * 3 + 0] = 0.5F;
908 ret[COL_7 * 3 + 1] = 0.3F;
909 ret[COL_7 * 3 + 2] = 0.3F;
912 ret[COL_8 * 3 + 0] = 0.4F;
913 ret[COL_8 * 3 + 1] = 0.8F;
914 ret[COL_8 * 3 + 2] = 1.0F;
917 ret[COL_9 * 3 + 0] = 0.7F;
918 ret[COL_9 * 3 + 1] = 1.0F;
919 ret[COL_9 * 3 + 2] = 0.7F;
922 ret[COL_10 * 3 + 0] = 1.0F;
923 ret[COL_10 * 3 + 1] = 0.6F;
924 ret[COL_10 * 3 + 2] = 1.0F;
926 *ncolours = NCOLOURS;
930 static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
932 struct game_drawstate *ds = snew(struct game_drawstate);
933 int w = state->w, h = state->h, wh = w*h;
938 ds->grid = snewn(wh, int);
939 for (i = 0; i < wh; i++)
945 static void game_free_drawstate(drawing *dr, game_drawstate *ds)
951 #define BORDER_L 0x001
952 #define BORDER_R 0x002
953 #define BORDER_U 0x004
954 #define BORDER_D 0x008
955 #define CORNER_UL 0x010
956 #define CORNER_UR 0x020
957 #define CORNER_DL 0x040
958 #define CORNER_DR 0x080
960 #define BADFLASH 0x200
961 #define SOLNNEXT 0x400
962 #define COLOUR_SHIFT 11
964 static void draw_tile(drawing *dr, game_drawstate *ds,
965 int x, int y, int tile)
968 int tx = COORD(x), ty = COORD(y);
970 colour = tile >> COLOUR_SHIFT;
972 colour = COL_SEPARATOR;
975 draw_rect(dr, tx, ty, TILESIZE, TILESIZE, colour);
978 draw_rect(dr, tx, ty,
979 SEP_WIDTH, TILESIZE, COL_SEPARATOR);
981 draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty,
982 SEP_WIDTH, TILESIZE, COL_SEPARATOR);
984 draw_rect(dr, tx, ty,
985 TILESIZE, SEP_WIDTH, COL_SEPARATOR);
987 draw_rect(dr, tx, ty + TILESIZE - SEP_WIDTH,
988 TILESIZE, SEP_WIDTH, COL_SEPARATOR);
990 if (tile & CORNER_UL)
991 draw_rect(dr, tx, ty,
992 SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
993 if (tile & CORNER_UR)
994 draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty,
995 SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
996 if (tile & CORNER_DL)
997 draw_rect(dr, tx, ty + TILESIZE - SEP_WIDTH,
998 SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
999 if (tile & CORNER_DR)
1000 draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty + TILESIZE - SEP_WIDTH,
1001 SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
1004 draw_rect_outline(dr, tx + CURSOR_INSET, ty + CURSOR_INSET,
1005 TILESIZE - 1 - CURSOR_INSET * 2,
1006 TILESIZE - 1 - CURSOR_INSET * 2,
1009 if (tile & SOLNNEXT) {
1010 draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, TILESIZE/6,
1011 COL_SEPARATOR, COL_SEPARATOR);
1014 draw_update(dr, tx, ty, TILESIZE, TILESIZE);
1017 static void game_redraw(drawing *dr, game_drawstate *ds,
1018 const game_state *oldstate, const game_state *state,
1019 int dir, const game_ui *ui,
1020 float animtime, float flashtime)
1022 int w = state->w, h = state->h, wh = w*h;
1023 int x, y, flashframe, solnmove;
1026 /* This was entirely cloned from fifteen.c; it should probably be
1027 * moved into some generic 'draw-recessed-rectangle' utility fn. */
1032 TILESIZE * w + 2 * BORDER,
1033 TILESIZE * h + 2 * BORDER, COL_BACKGROUND);
1034 draw_update(dr, 0, 0,
1035 TILESIZE * w + 2 * BORDER,
1036 TILESIZE * h + 2 * BORDER);
1039 * Recessed area containing the whole puzzle.
1041 coords[0] = COORD(w) + HIGHLIGHT_WIDTH - 1;
1042 coords[1] = COORD(h) + HIGHLIGHT_WIDTH - 1;
1043 coords[2] = COORD(w) + HIGHLIGHT_WIDTH - 1;
1044 coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
1045 coords[4] = coords[2] - TILESIZE;
1046 coords[5] = coords[3] + TILESIZE;
1047 coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
1048 coords[9] = COORD(h) + HIGHLIGHT_WIDTH - 1;
1049 coords[6] = coords[8] + TILESIZE;
1050 coords[7] = coords[9] - TILESIZE;
1051 draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
1053 coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
1054 coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
1055 draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
1057 draw_rect(dr, COORD(0) - SEP_WIDTH, COORD(0) - SEP_WIDTH,
1058 TILESIZE * w + 2 * SEP_WIDTH, TILESIZE * h + 2 * SEP_WIDTH,
1064 if (flashtime > 0) {
1065 float frame = (ui->flash_type == VICTORY ?
1066 VICTORY_FLASH_FRAME : DEFEAT_FLASH_FRAME);
1067 flashframe = (int)(flashtime / frame);
1072 grid = snewn(wh, char);
1073 memcpy(grid, state->grid, wh * sizeof(*grid));
1075 if (state->soln && state->solnpos < state->soln->nmoves) {
1079 * Highlight as 'next auto-solver move' every square of the
1080 * target colour which is adjacent to the currently controlled
1081 * region. We do this by first enacting the actual move, then
1082 * flood-filling again in a nonexistent colour, and finally
1083 * reverting to the original grid anything in the new colour
1084 * that was part of the original controlled region. Then
1085 * regions coloured in the dummy colour should be displayed as
1086 * soln_move with the SOLNNEXT flag.
1088 solnmove = state->soln->moves[state->solnpos];
1090 queue = snewn(wh, int);
1091 fill(w, h, grid, FILLX, FILLY, solnmove, queue);
1092 fill(w, h, grid, FILLX, FILLY, state->colours, queue);
1095 for (i = 0; i < wh; i++)
1096 if (grid[i] == state->colours && state->grid[i] != solnmove)
1097 grid[i] = state->grid[i];
1099 solnmove = 0; /* placate optimiser */
1102 if (flashframe >= 0 && ui->flash_type == VICTORY) {
1104 * Modify the display grid by superimposing our rainbow flash
1107 for (x = 0; x < w; x++) {
1108 for (y = 0; y < h; y++) {
1109 int flashpos = flashframe - (abs(x - FILLX) + abs(y - FILLY));
1110 if (flashpos >= 0 && flashpos < state->colours)
1111 grid[y*w+x] = flashpos;
1116 for (x = 0; x < w; x++) {
1117 for (y = 0; y < h; y++) {
1121 if (grid[pos] == state->colours) {
1122 tile = (solnmove << COLOUR_SHIFT) | SOLNNEXT;
1124 tile = (int)grid[pos] << COLOUR_SHIFT;
1127 if (x == 0 || grid[pos-1] != grid[pos])
1129 if (x==w-1 || grid[pos+1] != grid[pos])
1131 if (y == 0 || grid[pos-w] != grid[pos])
1133 if (y==h-1 || grid[pos+w] != grid[pos])
1135 if (x == 0 || y == 0 || grid[pos-w-1] != grid[pos])
1137 if (x==w-1 || y == 0 || grid[pos-w+1] != grid[pos])
1139 if (x == 0 || y==h-1 || grid[pos+w-1] != grid[pos])
1141 if (x==w-1 || y==h-1 || grid[pos+w+1] != grid[pos])
1143 if (ui->cursor_visible && ui->cx == x && ui->cy == y)
1146 if (flashframe >= 0 && ui->flash_type == DEFEAT && flashframe != 1)
1149 if (ds->grid[pos] != tile) {
1150 draw_tile(dr, ds, x, y, tile);
1151 ds->grid[pos] = tile;
1161 sprintf(status, "%s%d / %d moves",
1162 (state->complete && state->moves <= state->movelimit ?
1163 (state->cheated ? "Auto-solved. " : "COMPLETED! ") :
1164 state->moves >= state->movelimit ? "FAILED! " :
1165 state->cheated ? "Auto-solver used. " :
1170 status_bar(dr, status);
1174 static float game_anim_length(const game_state *oldstate,
1175 const game_state *newstate, int dir, game_ui *ui)
1180 static int game_status(const game_state *state)
1182 if (state->complete && state->moves <= state->movelimit) {
1183 return +1; /* victory! */
1184 } else if (state->moves >= state->movelimit) {
1185 return -1; /* defeat */
1187 return 0; /* still playing */
1191 static float game_flash_length(const game_state *oldstate,
1192 const game_state *newstate, int dir, game_ui *ui)
1195 int old_status = game_status(oldstate);
1196 int new_status = game_status(newstate);
1197 if (old_status != new_status) {
1198 assert(old_status == 0);
1200 if (new_status == +1) {
1201 int frames = newstate->w + newstate->h + newstate->colours - 2;
1202 ui->flash_type = VICTORY;
1203 return VICTORY_FLASH_FRAME * frames;
1205 ui->flash_type = DEFEAT;
1206 return DEFEAT_FLASH_FRAME * 3;
1213 static int game_timing_state(const game_state *state, game_ui *ui)
1218 static void game_print_size(const game_params *params, float *x, float *y)
1222 static void game_print(drawing *dr, const game_state *state, int tilesize)
1227 #define thegame flood
1230 const struct game thegame = {
1231 "Flood", "games.flood", "flood",
1238 TRUE, game_configure, custom_params,
1246 TRUE, game_can_format_as_text_now, game_text_format,
1254 PREFERRED_TILESIZE, game_compute_size, game_set_size,
1257 game_free_drawstate,
1262 FALSE, FALSE, game_print_size, game_print,
1263 TRUE, /* wants_statusbar */
1264 FALSE, game_timing_state,