Improve the Flood solver.

Previously it simply chose every move based on the static evaluation
function 'minimise the pair (longest shortest-path to any square,
number of squares at that distance)'. Now it looks three moves ahead
recursively, and I've also adjusted the evaluation function to tie-
break based on the number of squares brought to distance zero (i.e.
actually in control).

The result isn't an unconditional improvement on the old solver; in a
test run based on 'flood --generate 1000 12x12c6m0#12345' I found that
57 out of 1000 grids tested now had longer solutions. However, about
three quarters had shorter ones, and solutions are more than a move
shorter on average.
This commit is contained in:
Simon Tatham
2015-01-15 20:21:05 +00:00
parent 202e7fecfd
commit cca302c01b

218
flood.c
View File

@ -27,6 +27,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
@ -221,21 +222,54 @@ static char *validate_params(const game_params *params, int full)
return NULL;
}
#if 0
/*
* Bodge to permit varying the recursion depth for testing purposes.
To test two Floods against each other:
paste <(./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
and then run gnuplot and plot "z".
*/
static int rdepth = 0;
#define RECURSION_DEPTH (rdepth)
void check_recursion_depth(void)
{
if (!rdepth) {
const char *depthstr = getenv("FLOOD_DEPTH");
rdepth = depthstr ? atoi(depthstr) : 1;
rdepth = rdepth > 0 ? rdepth : 1;
}
}
#else
/*
* Last time I empirically checked this, depth 3 was a noticeable
* improvement on 2, but 4 only negligibly better than 3.
*/
#define RECURSION_DEPTH 3
#define check_recursion_depth() (void)0
#endif
struct solver_scratch {
int *queue[2];
int *dist;
char *grid, *grid2, *sparegrid;
char *grid, *grid2;
char *rgrids;
};
static struct solver_scratch *new_scratch(int w, int h)
{
int wh = w*h;
struct solver_scratch *scratch = snew(struct solver_scratch);
scratch->queue[0] = snewn(w*h, int);
scratch->queue[1] = snewn(w*h, int);
scratch->dist = snewn(w*h, int);
scratch->grid = snewn(w*h, char);
scratch->grid2 = snewn(w*h, char);
scratch->sparegrid = snewn(w*h, char);
check_recursion_depth();
scratch->queue[0] = snewn(wh, int);
scratch->queue[1] = snewn(wh, int);
scratch->dist = snewn(wh, int);
scratch->grid = snewn(wh, char);
scratch->grid2 = snewn(wh, char);
scratch->rgrids = snewn(wh * RECURSION_DEPTH, char);
return scratch;
}
@ -246,16 +280,24 @@ static void free_scratch(struct solver_scratch *scratch)
sfree(scratch->dist);
sfree(scratch->grid);
sfree(scratch->grid2);
sfree(scratch->sparegrid);
sfree(scratch->rgrids);
sfree(scratch);
}
#if 0
/* Diagnostic routines you can uncomment if you need them */
void dump_grid(int w, int h, const char *grid, const char *title)
void dump_grid(int w, int h, const char *grid, const char *titlefmt, ...)
{
int x, y;
printf("%s:\n", title ? title : "Grid");
if (titlefmt) {
va_list ap;
va_start(ap, titlefmt);
vprintf(titlefmt, ap);
va_end(ap);
printf(":\n");
} else {
printf("Grid:\n");
}
for (y = 0; y < h; y++) {
printf(" ");
for (x = 0; x < w; x++) {
@ -265,10 +307,18 @@ void dump_grid(int w, int h, const char *grid, const char *title)
}
}
void dump_dist(int w, int h, const int *dists, const char *title)
void dump_dist(int w, int h, const int *dists, const char *titlefmt, ...)
{
int x, y;
printf("%s:\n", title ? title : "Distances");
if (titlefmt) {
va_list ap;
va_start(ap, titlefmt);
vprintf(titlefmt, ap);
va_end(ap);
printf(":\n");
} else {
printf("Distances:\n");
}
for (y = 0; y < h; y++) {
printf(" ");
for (x = 0; x < w; x++) {
@ -281,10 +331,12 @@ void dump_dist(int w, int h, const int *dists, const char *title)
/*
* Search a grid to find the most distant square(s). Return their
* distance and the number of them.
* distance and the number of them, and also the number of squares in
* the current controlled set (i.e. at distance zero).
*/
static void search(int w, int h, char *grid, int x0, int y0,
struct solver_scratch *scratch, int *rdist, int *rnumber)
struct solver_scratch *scratch,
int *rdist, int *rnumber, int *rcontrol)
{
int wh = w*h;
int i, qcurr, qhead, qtail, qnext, currdist, remaining;
@ -304,6 +356,8 @@ static void search(int w, int h, char *grid, int x0, int y0,
while (1) {
if (qtail == qhead) {
/* Switch queues. */
if (currdist == 0)
*rcontrol = qhead;
currdist++;
qcurr ^= 1; /* switch queues */
qhead = qnext;
@ -351,6 +405,8 @@ static void search(int w, int h, char *grid, int x0, int y0,
*rdist = currdist;
*rnumber = qhead;
if (currdist == 0)
*rcontrol = qhead;
}
/*
@ -388,52 +444,6 @@ static void fill(int w, int h, char *grid, int x0, int y0, char newcolour,
}
}
/*
* Try out every possible move on a grid, and choose whichever one
* reduced the result of search() by the most.
*/
static char choosemove(int w, int h, char *grid, int x0, int y0,
int maxmove, struct solver_scratch *scratch)
{
int wh = w*h;
char move, bestmove;
int dist, number, bestdist, bestnumber;
bestdist = wh + 1;
bestnumber = 0;
bestmove = -1;
#if 0
dump_grid(w, h, grid, "before choosemove");
#endif
for (move = 0; move < maxmove; move++) {
char buf[256];
sprintf(buf, "after move %d", move);
if (grid[y0*w+x0] == move)
continue;
memcpy(scratch->sparegrid, grid, wh * sizeof(*grid));
fill(w, h, scratch->sparegrid, x0, y0, move, scratch->queue[0]);
#if 0
dump_grid(w, h, scratch->sparegrid, buf);
#endif
search(w, h, scratch->sparegrid, x0, y0, scratch, &dist, &number);
#if 0
dump_dist(w, h, scratch->dist, buf);
printf("move %d: %d at %d\n", move, number, dist);
#endif
if (dist < bestdist || (dist == bestdist && number < bestnumber)) {
bestdist = dist;
bestnumber = number;
bestmove = move;
}
}
#if 0
printf("best was %d\n", bestmove);
#endif
return bestmove;
}
/*
* Detect a completed grid.
*/
@ -449,6 +459,92 @@ static int completed(int w, int h, char *grid)
return TRUE;
}
/*
* Try out every possible move on a grid, and choose whichever one
* reduced the result of search() by the most.
*/
static char choosemove_recurse(int w, int h, char *grid, int x0, int y0,
int maxmove, struct solver_scratch *scratch,
int depth, int *rbestdist, int *rbestnumber, int *rbestcontrol)
{
int wh = w*h;
char move, bestmove;
int dist, number, control, bestdist, bestnumber, bestcontrol;
char *tmpgrid;
assert(0 <= depth && depth < RECURSION_DEPTH);
tmpgrid = scratch->rgrids + depth*wh;
bestdist = wh + 1;
bestnumber = 0;
bestcontrol = 0;
bestmove = -1;
#if 0
dump_grid(w, h, grid, "before choosemove_recurse %d", depth);
#endif
for (move = 0; move < maxmove; move++) {
if (grid[y0*w+x0] == move)
continue;
memcpy(tmpgrid, grid, wh * sizeof(*grid));
fill(w, h, tmpgrid, x0, y0, move, scratch->queue[0]);
if (completed(w, h, tmpgrid)) {
/*
* A move that wins is immediately the best, so stop
* searching. Record what depth of recursion that happened
* at, so that higher levels will choose a move that gets
* to a winning position sooner.
*/
*rbestdist = -1;
*rbestnumber = depth;
*rbestcontrol = wh;
return move;
}
if (depth < RECURSION_DEPTH-1) {
choosemove_recurse(w, h, tmpgrid, x0, y0, maxmove, scratch,
depth+1, &dist, &number, &control);
} else {
#if 0
dump_grid(w, h, tmpgrid, "after move %d at depth %d",
move, depth);
#endif
search(w, h, tmpgrid, x0, y0, scratch, &dist, &number, &control);
#if 0
dump_dist(w, h, scratch->dist, "after move %d at depth %d",
move, depth);
printf("move %d at depth %d: %d at %d\n",
depth, move, number, dist);
#endif
}
if (dist < bestdist ||
(dist == bestdist &&
(number < bestnumber ||
(number == bestnumber &&
(control > bestcontrol))))) {
bestdist = dist;
bestnumber = number;
bestcontrol = control;
bestmove = move;
}
}
#if 0
printf("best at depth %d was %d (%d at %d, %d controlled)\n",
depth, bestmove, bestnumber, bestdist, bestcontrol);
#endif
*rbestdist = bestdist;
*rbestnumber = bestnumber;
*rbestcontrol = bestcontrol;
return bestmove;
}
static char choosemove(int w, int h, char *grid, int x0, int y0,
int maxmove, struct solver_scratch *scratch)
{
int tmp0, tmp1, tmp2;
return choosemove_recurse(w, h, grid, x0, y0, maxmove, scratch,
0, &tmp0, &tmp1, &tmp2);
}
static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, int interactive)
{
@ -470,6 +566,7 @@ static char *new_game_desc(const game_params *params, random_state *rs,
*/
memcpy(scratch->grid2, scratch->grid, wh * sizeof(*scratch->grid2));
moves = 0;
check_recursion_depth();
while (!completed(w, h, scratch->grid2)) {
char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
params->colours, scratch);
@ -610,6 +707,7 @@ static char *solve_game(const game_state *state, const game_state *currstate,
nmoves = 0;
scratch = new_scratch(w, h);
memcpy(scratch->grid2, currstate->grid, wh * sizeof(*scratch->grid2));
check_recursion_depth();
while (!completed(w, h, scratch->grid2)) {
char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
currstate->colours, scratch);