mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 16:05:44 -07:00
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:
218
flood.c
218
flood.c
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
@ -221,21 +222,54 @@ static char *validate_params(const game_params *params, int full)
|
|||||||
return NULL;
|
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 {
|
struct solver_scratch {
|
||||||
int *queue[2];
|
int *queue[2];
|
||||||
int *dist;
|
int *dist;
|
||||||
char *grid, *grid2, *sparegrid;
|
char *grid, *grid2;
|
||||||
|
char *rgrids;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct solver_scratch *new_scratch(int w, int h)
|
static struct solver_scratch *new_scratch(int w, int h)
|
||||||
{
|
{
|
||||||
|
int wh = w*h;
|
||||||
struct solver_scratch *scratch = snew(struct solver_scratch);
|
struct solver_scratch *scratch = snew(struct solver_scratch);
|
||||||
scratch->queue[0] = snewn(w*h, int);
|
check_recursion_depth();
|
||||||
scratch->queue[1] = snewn(w*h, int);
|
scratch->queue[0] = snewn(wh, int);
|
||||||
scratch->dist = snewn(w*h, int);
|
scratch->queue[1] = snewn(wh, int);
|
||||||
scratch->grid = snewn(w*h, char);
|
scratch->dist = snewn(wh, int);
|
||||||
scratch->grid2 = snewn(w*h, char);
|
scratch->grid = snewn(wh, char);
|
||||||
scratch->sparegrid = snewn(w*h, char);
|
scratch->grid2 = snewn(wh, char);
|
||||||
|
scratch->rgrids = snewn(wh * RECURSION_DEPTH, char);
|
||||||
return scratch;
|
return scratch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,16 +280,24 @@ static void free_scratch(struct solver_scratch *scratch)
|
|||||||
sfree(scratch->dist);
|
sfree(scratch->dist);
|
||||||
sfree(scratch->grid);
|
sfree(scratch->grid);
|
||||||
sfree(scratch->grid2);
|
sfree(scratch->grid2);
|
||||||
sfree(scratch->sparegrid);
|
sfree(scratch->rgrids);
|
||||||
sfree(scratch);
|
sfree(scratch);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
/* Diagnostic routines you can uncomment if you need them */
|
/* 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;
|
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++) {
|
for (y = 0; y < h; y++) {
|
||||||
printf(" ");
|
printf(" ");
|
||||||
for (x = 0; x < w; x++) {
|
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;
|
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++) {
|
for (y = 0; y < h; y++) {
|
||||||
printf(" ");
|
printf(" ");
|
||||||
for (x = 0; x < w; x++) {
|
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
|
* 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,
|
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 wh = w*h;
|
||||||
int i, qcurr, qhead, qtail, qnext, currdist, remaining;
|
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) {
|
while (1) {
|
||||||
if (qtail == qhead) {
|
if (qtail == qhead) {
|
||||||
/* Switch queues. */
|
/* Switch queues. */
|
||||||
|
if (currdist == 0)
|
||||||
|
*rcontrol = qhead;
|
||||||
currdist++;
|
currdist++;
|
||||||
qcurr ^= 1; /* switch queues */
|
qcurr ^= 1; /* switch queues */
|
||||||
qhead = qnext;
|
qhead = qnext;
|
||||||
@ -351,6 +405,8 @@ static void search(int w, int h, char *grid, int x0, int y0,
|
|||||||
|
|
||||||
*rdist = currdist;
|
*rdist = currdist;
|
||||||
*rnumber = qhead;
|
*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.
|
* Detect a completed grid.
|
||||||
*/
|
*/
|
||||||
@ -449,6 +459,92 @@ static int completed(int w, int h, char *grid)
|
|||||||
return TRUE;
|
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,
|
static char *new_game_desc(const game_params *params, random_state *rs,
|
||||||
char **aux, int interactive)
|
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));
|
memcpy(scratch->grid2, scratch->grid, wh * sizeof(*scratch->grid2));
|
||||||
moves = 0;
|
moves = 0;
|
||||||
|
check_recursion_depth();
|
||||||
while (!completed(w, h, scratch->grid2)) {
|
while (!completed(w, h, scratch->grid2)) {
|
||||||
char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
|
char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
|
||||||
params->colours, scratch);
|
params->colours, scratch);
|
||||||
@ -610,6 +707,7 @@ static char *solve_game(const game_state *state, const game_state *currstate,
|
|||||||
nmoves = 0;
|
nmoves = 0;
|
||||||
scratch = new_scratch(w, h);
|
scratch = new_scratch(w, h);
|
||||||
memcpy(scratch->grid2, currstate->grid, wh * sizeof(*scratch->grid2));
|
memcpy(scratch->grid2, currstate->grid, wh * sizeof(*scratch->grid2));
|
||||||
|
check_recursion_depth();
|
||||||
while (!completed(w, h, scratch->grid2)) {
|
while (!completed(w, h, scratch->grid2)) {
|
||||||
char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
|
char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
|
||||||
currstate->colours, scratch);
|
currstate->colours, scratch);
|
||||||
|
Reference in New Issue
Block a user