Slant: use the new findloop for loop detection.

The old face-dsf based loop detector is gone, and now we just call
findloop instead.

This is just a code cleanup: it doesn't fix any bugs that I know of.
In principle, it provides the same futureproofing we gained by making
the same change in Net, but Slant as a puzzle is less adaptable to
topologically interesting surfaces - in particular, you _can't_ play
it on any edgeless surface like a torus or Klein bottle, because no
filled grid can be loop-free in the first place. (The only way a
connected component can avoid having a loop surrounding it is if it
connects to the grid edge, so there has to _be_ a grid edge.) But you
could play Slant on a Mobius strip, I think, so perhaps one day...
This commit is contained in:
Simon Tatham
2016-02-24 19:10:16 +00:00
parent e862d4a79b
commit a2380d277a
2 changed files with 54 additions and 80 deletions

View File

@ -1,6 +1,6 @@
# -*- makefile -*- # -*- makefile -*-
SLANT_EXTRA = dsf SLANT_EXTRA = dsf findloop
slant : [X] GTK COMMON slant SLANT_EXTRA slant-icon|no-icon slant : [X] GTK COMMON slant SLANT_EXTRA slant-icon|no-icon

130
slant.c
View File

@ -1352,95 +1352,69 @@ static int vertex_degree(int w, int h, signed char *soln, int x, int y,
return anti ? 4 - ret : ret; return anti ? 4 - ret : ret;
} }
struct slant_neighbour_ctx {
const game_state *state;
int i, n, neighbours[4];
};
static int slant_neighbour(int vertex, void *vctx)
{
struct slant_neighbour_ctx *ctx = (struct slant_neighbour_ctx *)vctx;
if (vertex >= 0) {
int w = ctx->state->p.w, h = ctx->state->p.h, W = w+1;
int x = vertex % W, y = vertex / W;
ctx->n = ctx->i = 0;
if (x < w && y < h && ctx->state->soln[y*w+x] < 0)
ctx->neighbours[ctx->n++] = (y+1)*W+(x+1);
if (x > 0 && y > 0 && ctx->state->soln[(y-1)*w+(x-1)] < 0)
ctx->neighbours[ctx->n++] = (y-1)*W+(x-1);
if (x > 0 && y < h && ctx->state->soln[y*w+(x-1)] > 0)
ctx->neighbours[ctx->n++] = (y+1)*W+(x-1);
if (x < w && y > 0 && ctx->state->soln[(y-1)*w+x] > 0)
ctx->neighbours[ctx->n++] = (y-1)*W+(x+1);
}
if (ctx->i < ctx->n)
return ctx->neighbours[ctx->i++];
else
return -1;
}
static int check_completion(game_state *state) static int check_completion(game_state *state)
{ {
int w = state->p.w, h = state->p.h, W = w+1, H = h+1; int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
int x, y, err = FALSE; int x, y, err = FALSE;
int *dsf;
memset(state->errors, 0, W*H); memset(state->errors, 0, W*H);
/* /*
* To detect loops in the grid, we iterate through each edge * Detect and error-highlight loops in the grid.
* building up a dsf of connected components of the space
* around the edges; if there's more than one such component,
* we have a loop, and in particular we can then easily
* identify and highlight every edge forming part of a loop
* because it separates two nonequivalent regions.
*
* We use the `tmpdsf' scratch space in the shared clues
* structure, to avoid mallocing too often.
*
* For these purposes, the grid is considered to be divided
* into diamond-shaped regions surrounding an orthogonal edge.
* This means we have W*h vertical edges and w*H horizontal
* ones; so our vertical edges are indexed in the dsf as
* (y*W+x) (0<=y<h, 0<=x<W), and the horizontal ones as (W*h +
* y*w+x) (0<=y<H, 0<=x<w), where (x,y) is the topmost or
* leftmost point on the edge.
*/ */
dsf = state->clues->tmpdsf; {
dsf_init(dsf, W*h + w*H); struct findloopstate *fls = findloop_new_state(W*H);
/* Start by identifying all the outer edges with each other. */ struct slant_neighbour_ctx ctx;
for (y = 0; y < h; y++) { ctx.state = state;
dsf_merge(dsf, 0, y*W+0);
dsf_merge(dsf, 0, y*W+w); if (findloop_run(fls, W*H, slant_neighbour, &ctx))
}
for (x = 0; x < w; x++) {
dsf_merge(dsf, 0, W*h + 0*w+x);
dsf_merge(dsf, 0, W*h + h*w+x);
}
/* Now go through the actual grid. */
for (y = 0; y < h; y++)
for (x = 0; x < w; x++) {
if (state->soln[y*w+x] >= 0) {
/*
* There isn't a \ in this square, so we can unify
* the top edge with the left, and the bottom with
* the right.
*/
dsf_merge(dsf, y*W+x, W*h + y*w+x);
dsf_merge(dsf, y*W+(x+1), W*h + (y+1)*w+x);
}
if (state->soln[y*w+x] <= 0) {
/*
* There isn't a / in this square, so we can unify
* the top edge with the right, and the bottom
* with the left.
*/
dsf_merge(dsf, y*W+x, W*h + (y+1)*w+x);
dsf_merge(dsf, y*W+(x+1), W*h + y*w+x);
}
}
/* Now go through again and mark the appropriate edges as erroneous. */
for (y = 0; y < h; y++)
for (x = 0; x < w; x++) {
int erroneous = 0;
if (state->soln[y*w+x] > 0) {
/*
* A / separates the top and left edges (which
* must already have been identified with each
* other) from the bottom and right (likewise).
* Hence it is erroneous if and only if the top
* and right edges are nonequivalent.
*/
erroneous = (dsf_canonify(dsf, y*W+(x+1)) !=
dsf_canonify(dsf, W*h + y*w+x));
} else if (state->soln[y*w+x] < 0) {
/*
* A \ separates the top and right edges (which
* must already have been identified with each
* other) from the bottom and left (likewise).
* Hence it is erroneous if and only if the top
* and left edges are nonequivalent.
*/
erroneous = (dsf_canonify(dsf, y*W+x) !=
dsf_canonify(dsf, W*h + y*w+x));
}
if (erroneous) {
state->errors[y*W+x] |= ERR_SQUARE;
err = TRUE; err = TRUE;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
int u, v;
if (state->soln[y*w+x] == 0) {
continue;
} else if (state->soln[y*w+x] > 0) {
u = y*W+(x+1);
v = (y+1)*W+x;
} else {
u = (y+1)*W+(x+1);
v = y*W+x;
} }
if (findloop_is_loop_edge(fls, u, v))
state->errors[y*W+x] |= ERR_SQUARE;
}
}
findloop_free_state(fls);
} }
/* /*