Convert Tents to use matching instead of maxflow.

Tents needs to construct maximal matchings in two different
situations. One is the completion check during play, in which the
existence of a perfect matching between tents and trees is part of the
win condition; the other is the initial grid generation, in which we
find a _maximal_ matching between the tents we've already placed and
all the possible neighbouring squares that are candidates for the tree
positions. Both of those are switched over.
This commit is contained in:
Simon Tatham
2018-04-21 16:51:03 +01:00
parent 000ebc5078
commit dcc4d82b23
2 changed files with 115 additions and 128 deletions

View File

@ -1,6 +1,6 @@
# -*- makefile -*- # -*- makefile -*-
TENTS_EXTRA = maxflow dsf TENTS_EXTRA = matching dsf
tents : [X] GTK COMMON tents TENTS_EXTRA tents-icon|no-icon tents : [X] GTK COMMON tents TENTS_EXTRA tents-icon|no-icon

241
tents.c
View File

@ -35,7 +35,7 @@
#include <math.h> #include <math.h>
#include "puzzles.h" #include "puzzles.h"
#include "maxflow.h" #include "matching.h"
/* /*
* Design discussion * Design discussion
@ -907,14 +907,17 @@ static char *new_game_desc(const game_params *params_in, random_state *rs,
char *puzzle = snewn(w*h, char); char *puzzle = snewn(w*h, char);
int *numbers = snewn(w+h, int); int *numbers = snewn(w+h, int);
char *soln = snewn(w*h, char); char *soln = snewn(w*h, char);
int *temp = snewn(2*w*h, int); int *order = snewn(w*h, int);
int *treemap = snewn(w*h, int);
int maxedges = ntrees*4 + w*h; int maxedges = ntrees*4 + w*h;
int *edges = snewn(2*maxedges, int); int *adjdata = snewn(maxedges, int);
int *capacity = snewn(maxedges, int); int **adjlists = snewn(ntrees, int *);
int *flow = snewn(maxedges, int); int *adjsizes = snewn(ntrees, int);
int *outr = snewn(4*ntrees, int);
struct solver_scratch *sc = new_scratch(w, h); struct solver_scratch *sc = new_scratch(w, h);
char *ret, *p; char *ret, *p;
int i, j, nedges; int i, j, nl, nr;
int *adjptr;
/* /*
* Since this puzzle has many global deductions and doesn't * Since this puzzle has many global deductions and doesn't
@ -940,7 +943,7 @@ static char *new_game_desc(const game_params *params_in, random_state *rs,
* would make the grids emptier and more boring. * would make the grids emptier and more boring.
* *
* Actually generating a grid is a matter of first placing the * Actually generating a grid is a matter of first placing the
* tents, and then placing the trees by the use of maxflow * tents, and then placing the trees by the use of matching.c
* (finding a distinct square adjacent to every tent). We do it * (finding a distinct square adjacent to every tent). We do it
* this way round because otherwise satisfying the tent * this way round because otherwise satisfying the tent
* separation condition would become onerous: most randomly * separation condition would become onerous: most randomly
@ -950,19 +953,12 @@ static char *new_game_desc(const game_params *params_in, random_state *rs,
* ensure they meet the separation criterion _before_ doing * ensure they meet the separation criterion _before_ doing
* lots of computation; this works much better. * lots of computation; this works much better.
* *
* The maxflow algorithm is not randomised, so employed naively
* it would give rise to grids with clear structure and
* directional bias. Hence, I assign the network nodes as seen
* by maxflow to be a _random_ permutation of the squares of
* the grid, so that any bias shown by maxflow towards
* low-numbered nodes is turned into a random bias.
*
* This generation strategy can fail at many points, including * This generation strategy can fail at many points, including
* as early as tent placement (if you get a bad random order in * as early as tent placement (if you get a bad random order in
* which to greedily try the grid squares, you won't even * which to greedily try the grid squares, you won't even
* manage to find enough mutually non-adjacent squares to put * manage to find enough mutually non-adjacent squares to put
* the tents in). Then it can fail if maxflow doesn't manage to * the tents in). Then it can fail if matching.c doesn't manage
* find a good enough matching (i.e. the tent placements don't * to find a good enough matching (i.e. the tent placements don't
* admit any adequate tree placements); and finally it can fail * admit any adequate tree placements); and finally it can fail
* if the solver finds that the problem has the wrong * if the solver finds that the problem has the wrong
* difficulty (including being actually non-unique). All of * difficulty (including being actually non-unique). All of
@ -975,23 +971,38 @@ static char *new_game_desc(const game_params *params_in, random_state *rs,
while (1) { while (1) {
/* /*
* Arrange the grid squares into a random order. * Make a list of grid squares which we'll permute as we pick
* the tent locations.
*
* We'll also need to index all the potential tree squares,
* i.e. the ones adjacent to the tents.
*/ */
for (i = 0; i < w*h; i++) for (i = 0; i < w*h; i++) {
temp[i] = i; order[i] = i;
shuffle(temp, w*h, sizeof(*temp), rs); treemap[i] = -1;
}
/* /*
* The first `ntrees' entries in temp which we can get * Place tents at random without making any two adjacent.
* without making two tents adjacent will be the tent
* locations.
*/ */
memset(grid, BLANK, w*h); memset(grid, BLANK, w*h);
j = ntrees; j = ntrees;
for (i = 0; i < w*h && j > 0; i++) { nr = 0;
int x = temp[i] % w, y = temp[i] / w; /* Loop end condition: either j==0 (we've placed all the
* tents), or the number of grid squares we have yet to try
* is too few to fit the remaining tents into. */
for (i = 0; j > 0 && i+j <= w*h; i++) {
int which, x, y, d, tmp;
int dy, dx, ok = TRUE; int dy, dx, ok = TRUE;
which = i + random_upto(rs, j);
tmp = order[which];
order[which] = order[i];
order[i] = tmp;
x = order[i] % w;
y = order[i] / w;
for (dy = -1; dy <= +1; dy++) for (dy = -1; dy <= +1; dy++)
for (dx = -1; dx <= +1; dx++) for (dx = -1; dx <= +1; dx++)
if (x+dx >= 0 && x+dx < w && if (x+dx >= 0 && x+dx < w &&
@ -1000,7 +1011,14 @@ static char *new_game_desc(const game_params *params_in, random_state *rs,
ok = FALSE; ok = FALSE;
if (ok) { if (ok) {
grid[temp[i]] = TENT; grid[order[i]] = TENT;
for (d = 1; d < MAXDIR; d++) {
int x2 = x + dx(d), y2 = y + dy(d);
if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h &&
treemap[y2*w+x2] == -1) {
treemap[y2*w+x2] = nr++;
}
}
j--; j--;
} }
} }
@ -1008,68 +1026,47 @@ static char *new_game_desc(const game_params *params_in, random_state *rs,
continue; /* couldn't place all the tents */ continue; /* couldn't place all the tents */
/* /*
* Now we build up the list of graph edges. * Build up the graph for matching.c.
*/ */
nedges = 0; adjptr = adjdata;
nl = 0;
for (i = 0; i < w*h; i++) { for (i = 0; i < w*h; i++) {
if (grid[temp[i]] == TENT) { if (grid[i] == TENT) {
for (j = 0; j < w*h; j++) { int d, x = i % w, y = i / w;
if (grid[temp[j]] != TENT) { adjlists[nl] = adjptr;
int xi = temp[i] % w, yi = temp[i] / w; for (d = 1; d < MAXDIR; d++) {
int xj = temp[j] % w, yj = temp[j] / w; int x2 = x + dx(d), y2 = y + dy(d);
if (abs(xi-xj) + abs(yi-yj) == 1) { if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h) {
edges[nedges*2] = i; assert(treemap[y2*w+x2] != -1);
edges[nedges*2+1] = j; *adjptr++ = treemap[y2*w+x2];
capacity[nedges] = 1;
nedges++;
}
} }
} }
} else { adjsizes[nl] = adjptr - adjlists[nl];
/* nl++;
* Special node w*h is the sink node; any non-tent node
* has an edge going to it.
*/
edges[nedges*2] = i;
edges[nedges*2+1] = w*h;
capacity[nedges] = 1;
nedges++;
} }
} }
/* /*
* Special node w*h+1 is the source node, with an edge going to * Call the matching algorithm to actually place the trees.
* every tent.
*/ */
for (i = 0; i < w*h; i++) { j = matching(ntrees, nr, adjlists, adjsizes, rs, NULL, outr);
if (grid[temp[i]] == TENT) {
edges[nedges*2] = w*h+1;
edges[nedges*2+1] = i;
capacity[nedges] = 1;
nedges++;
}
}
assert(nedges <= maxedges);
/*
* Now we're ready to call the maxflow algorithm to place the
* trees.
*/
j = maxflow(w*h+2, w*h+1, w*h, nedges, edges, capacity, flow, NULL);
if (j < ntrees) if (j < ntrees)
continue; /* couldn't place all the trees */ continue; /* couldn't place all the trees */
/* /*
* We've placed the trees. Now we need to work out _where_ * Fill in the trees in the grid, by cross-referencing treemap
* we've placed them, which is a matter of reading back out * (which maps a grid square to its index as known to
* from the `flow' array. * matching()) against the output from matching().
*
* Note that for these purposes we don't actually care _which_
* tent each potential tree square is assigned to - we only
* care whether it was assigned to any tent at all, in order
* to decide whether to put a tree in it.
*/ */
for (i = 0; i < nedges; i++) { for (i = 0; i < w*h; i++)
if (edges[2*i] < w*h && edges[2*i+1] < w*h && flow[i] > 0) if (treemap[i] != -1 && outr[treemap[i]] != -1)
grid[temp[edges[2*i+1]]] = TREE; grid[i] = TREE;
}
/* /*
* I think it looks ugly if there isn't at least one of * I think it looks ugly if there isn't at least one of
@ -1174,10 +1171,12 @@ static char *new_game_desc(const game_params *params_in, random_state *rs,
*aux = sresize(*aux, p - *aux, char); *aux = sresize(*aux, p - *aux, char);
free_scratch(sc); free_scratch(sc);
sfree(flow); sfree(outr);
sfree(capacity); sfree(adjdata);
sfree(edges); sfree(adjlists);
sfree(temp); sfree(adjsizes);
sfree(treemap);
sfree(order);
sfree(soln); sfree(soln);
sfree(numbers); sfree(numbers);
sfree(puzzle); sfree(puzzle);
@ -1748,7 +1747,7 @@ static game_state *execute_move(const game_state *state, const char *move)
m++; m++;
} }
if (n == m) { if (n == m) {
int nedges, maxedges, *edges, *capacity, *flow; int *gridids, *adjdata, **adjlists, *adjsizes, *adjptr;
/* /*
* We have the right number of tents, which is a * We have the right number of tents, which is a
@ -1800,28 +1799,33 @@ static game_state *execute_move(const game_state *state, const char *move)
* every tent is orthogonally adjacent to its tree. * every tent is orthogonally adjacent to its tree.
* *
* This bit is where the hard work comes in: we have to do * This bit is where the hard work comes in: we have to do
* it by finding such a matching using maxflow. * it by finding such a matching using matching.c.
*
* So we construct a network with one special source node,
* one special sink node, one node per tent, and one node
* per tree.
*/
maxedges = 6 * m;
edges = snewn(2 * maxedges, int);
capacity = snewn(maxedges, int);
flow = snewn(maxedges, int);
nedges = 0;
/*
* Node numbering:
*
* 0..w*h trees/tents
* w*h source
* w*h+1 sink
*/ */
gridids = snewn(w*h, int);
adjdata = snewn(m*4, int);
adjlists = snewn(m, int *);
adjsizes = snewn(m, int);
/* Assign each tent and tree a consecutive vertex id for
* matching(). */
for (i = n = 0; i < w*h; i++) {
if (ret->grid[i] == TENT)
gridids[i] = n++;
}
assert(n == m);
for (i = n = 0; i < w*h; i++) {
if (ret->grid[i] == TREE)
gridids[i] = n++;
}
assert(n == m);
/* Build the vertices' adjacency lists. */
adjptr = adjdata;
for (y = 0; y < h; y++) for (y = 0; y < h; y++)
for (x = 0; x < w; x++) for (x = 0; x < w; x++)
if (ret->grid[y*w+x] == TREE) { if (ret->grid[y*w+x] == TREE) {
int d; int d, treeid = gridids[y*w+x];
adjlists[treeid] = adjptr;
/* /*
* Here we use the direction enum declared for * Here we use the direction enum declared for
@ -1835,34 +1839,18 @@ static game_state *execute_move(const game_state *state, const char *move)
int x2 = x + dx(d), y2 = y + dy(d); int x2 = x + dx(d), y2 = y + dy(d);
if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h &&
ret->grid[y2*w+x2] == TENT) { ret->grid[y2*w+x2] == TENT) {
assert(nedges < maxedges); *adjptr++ = gridids[y2*w+x2];
edges[nedges*2] = y*w+x;
edges[nedges*2+1] = y2*w+x2;
capacity[nedges] = 1;
nedges++;
} }
} }
} else if (ret->grid[y*w+x] == TENT) { adjsizes[treeid] = adjptr - adjlists[treeid];
assert(nedges < maxedges);
edges[nedges*2] = y*w+x;
edges[nedges*2+1] = w*h+1; /* edge going to sink */
capacity[nedges] = 1;
nedges++;
} }
for (y = 0; y < h; y++)
for (x = 0; x < w; x++)
if (ret->grid[y*w+x] == TREE) {
assert(nedges < maxedges);
edges[nedges*2] = w*h; /* edge coming from source */
edges[nedges*2+1] = y*w+x;
capacity[nedges] = 1;
nedges++;
}
n = maxflow(w*h+2, w*h, w*h+1, nedges, edges, capacity, flow, NULL);
sfree(flow); n = matching(m, m, adjlists, adjsizes, NULL, NULL, NULL);
sfree(capacity);
sfree(edges); sfree(gridids);
sfree(adjdata);
sfree(adjlists);
sfree(adjsizes);
if (n != m) if (n != m)
goto completion_check_done; goto completion_check_done;
@ -2000,14 +1988,13 @@ static int *find_errors(const game_state *state, char *grid)
* tents. The difficult bit is highlighting failures in the * tents. The difficult bit is highlighting failures in the
* tent/tree matching criterion. * tent/tree matching criterion.
* *
* A natural approach would seem to be to apply the maxflow * A natural approach would seem to be to apply the matching.c
* algorithm to find the tent/tree matching; if this fails, it * algorithm to find the tent/tree matching; if this fails, it
* must necessarily terminate with a min-cut which can be * could be made to produce as a by-product some set of trees
* reinterpreted as some set of trees which have too few tents * which have too few tents between them (or vice versa). However,
* between them (or vice versa). However, it's bad for * it's bad for localising errors, because it's not easy to make
* localising errors, because it's not easy to make the * the algorithm narrow down to the _smallest_ such set of trees:
* algorithm narrow down to the _smallest_ such set of trees: if * if trees A and B have only one tent between them, for instance,
* trees A and B have only one tent between them, for instance,
* it might perfectly well highlight not only A and B but also * it might perfectly well highlight not only A and B but also
* trees C and D which are correctly matched on the far side of * trees C and D which are correctly matched on the far side of
* the grid, on the grounds that those four trees between them * the grid, on the grounds that those four trees between them