Bridges: use the new findloop for loop detection.

Bridges only needs a loop detector for its non-default 'don't allow
loops' mode. But the one it had was using the graph-pruning strategy,
which means it had the dumb-bell bug - two loops joined by a path
would highlight the path as well as the loops. Switching to the new
findloop system fixes that bug.

A side effect is that I've been able to remove the 'scratch' array
from the game_state, which was only used by the old loop finder, so
that should save memory.
This commit is contained in:
Simon Tatham
2016-02-24 19:01:42 +00:00
parent 1add03f7b8
commit deff331e5f
2 changed files with 64 additions and 81 deletions

View File

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

109
bridges.c
View File

@ -156,7 +156,7 @@ struct island {
struct game_state { struct game_state {
int w, h, completed, solved, allowloops, maxb; int w, h, completed, solved, allowloops, maxb;
grid_type *grid, *scratch; grid_type *grid;
struct island *islands; struct island *islands;
int n_islands, n_islands_alloc; int n_islands, n_islands_alloc;
game_params params; /* used by the aux solver. */ game_params params; /* used by the aux solver. */
@ -175,7 +175,6 @@ struct game_state {
#define INDEX(s,g,x,y) ((s)->g[(y)*((s)->w) + (x)]) #define INDEX(s,g,x,y) ((s)->g[(y)*((s)->w) + (x)])
#define IDX(s,g,i) ((s)->g[(i)]) #define IDX(s,g,i) ((s)->g[(i)])
#define GRID(s,x,y) INDEX(s,grid,x,y) #define GRID(s,x,y) INDEX(s,grid,x,y)
#define SCRATCH(s,x,y) INDEX(s,scratch,x,y)
#define POSSIBLES(s,dx,x,y) ((dx) ? (INDEX(s,possh,x,y)) : (INDEX(s,possv,x,y))) #define POSSIBLES(s,dx,x,y) ((dx) ? (INDEX(s,possh,x,y)) : (INDEX(s,possv,x,y)))
#define MAXIMUM(s,dx,x,y) ((dx) ? (INDEX(s,maxh,x,y)) : (INDEX(s,maxv,x,y))) #define MAXIMUM(s,dx,x,y) ((dx) ? (INDEX(s,maxh,x,y)) : (INDEX(s,maxv,x,y)))
@ -1051,23 +1050,30 @@ static void map_find_orthogonal(game_state *state)
} }
} }
static int grid_degree(game_state *state, int x, int y, int *nx_r, int *ny_r) struct bridges_neighbour_ctx {
game_state *state;
int i, n, neighbours[4];
};
static int bridges_neighbour(int vertex, void *vctx)
{ {
grid_type grid = SCRATCH(state, x, y), gline = grid & G_LINE; struct bridges_neighbour_ctx *ctx = (struct bridges_neighbour_ctx *)vctx;
if (vertex >= 0) {
game_state *state = ctx->state;
int w = state->w, x = vertex % w, y = vertex / w;
grid_type grid = GRID(state, x, y), gline = grid & G_LINE;
struct island *is; struct island *is;
int x1, y1, x2, y2, c = 0, i, nx, ny; int x1, y1, x2, y2, i;
ctx->i = ctx->n = 0;
nx = ny = -1; /* placate optimiser */
is = INDEX(state, gridi, x, y); is = INDEX(state, gridi, x, y);
if (is) { if (is) {
for (i = 0; i < is->adj.npoints; i++) { for (i = 0; i < is->adj.npoints; i++) {
gline = is->adj.points[i].dx ? G_LINEH : G_LINEV; gline = is->adj.points[i].dx ? G_LINEH : G_LINEV;
if (SCRATCH(state, if (GRID(state, is->adj.points[i].x,
is->adj.points[i].x,
is->adj.points[i].y) & gline) { is->adj.points[i].y) & gline) {
nx = is->adj.points[i].x; ctx->neighbours[ctx->n++] =
ny = is->adj.points[i].y; (is->adj.points[i].y * w + is->adj.points[i].x);
c++;
} }
} }
} else if (gline) { } else if (gline) {
@ -1078,68 +1084,50 @@ static int grid_degree(game_state *state, int x, int y, int *nx_r, int *ny_r)
x1 = x-1; x2 = x+1; x1 = x-1; x2 = x+1;
y1 = y2 = y; y1 = y2 = y;
} }
/* Non-island squares with edges in should never be pointing off the /* Non-island squares with edges in should never be
* edge of the grid. */ * pointing off the edge of the grid. */
assert(INGRID(state, x1, y1)); assert(INGRID(state, x1, y1));
assert(INGRID(state, x2, y2)); assert(INGRID(state, x2, y2));
if (SCRATCH(state, x1, y1) & (gline | G_ISLAND)) { if (GRID(state, x1, y1) & (gline | G_ISLAND))
nx = x1; ny = y1; c++; ctx->neighbours[ctx->n++] = y1 * w + x1;
} if (GRID(state, x2, y2) & (gline | G_ISLAND))
if (SCRATCH(state, x2, y2) & (gline | G_ISLAND)) { ctx->neighbours[ctx->n++] = y2 * w + x2;
nx = x2; ny = y2; c++;
} }
} }
if (c == 1) {
assert(nx != -1 && ny != -1); /* paranoia */ if (ctx->i < ctx->n)
*nx_r = nx; *ny_r = ny; return ctx->neighbours[ctx->i++];
} else
return c; return -1;
} }
static int map_hasloops(game_state *state, int mark) static int map_hasloops(game_state *state, int mark)
{ {
int x, y, ox, oy, nx = 0, ny = 0, loop = 0; int x, y;
struct findloopstate *fls;
struct bridges_neighbour_ctx ctx;
int ret;
memcpy(state->scratch, state->grid, GRIDSZ(state)); fls = findloop_new_state(state->w * state->h);
ctx.state = state;
ret = findloop_run(fls, state->w * state->h, bridges_neighbour, &ctx);
/* This algorithm is actually broken; if there are two loops connected
* by bridges this will also highlight bridges. The correct algorithm
* uses a dsf and a two-pass edge-detection algorithm (see check_correct
* in slant.c); this is BALGE for now, especially since disallow-loops
* is not the default for this puzzle. If we want to fix this later then
* copy the alg in slant.c to the empty statement in map_group. */
/* Remove all 1-degree edges. */
for (y = 0; y < state->h; y++) {
for (x = 0; x < state->w; x++) {
ox = x; oy = y;
while (grid_degree(state, ox, oy, &nx, &ny) == 1) {
/*debug(("hasloops: removing 1-degree at (%d,%d).\n", ox, oy));*/
SCRATCH(state, ox, oy) &= ~(G_LINE|G_ISLAND);
ox = nx; oy = ny;
}
}
}
/* Mark any remaining edges as G_WARN, if required. */
for (x = 0; x < state->w; x++) {
for (y = 0; y < state->h; y++) {
if (GRID(state,x,y) & G_ISLAND) continue;
if (SCRATCH(state, x, y) & G_LINE) {
if (mark) { if (mark) {
/*debug(("hasloops: marking loop square at (%d,%d).\n", for (y = 0; y < state->h; y++) {
x, y));*/ for (x = 0; x < state->w; x++) {
int u, v;
u = y * state->w + x;
for (v = bridges_neighbour(u, &ctx); v >= 0;
v = bridges_neighbour(-1, &ctx))
if (findloop_is_loop_edge(fls, u, v))
GRID(state,x,y) |= G_WARN; GRID(state,x,y) |= G_WARN;
loop = 1;
} else
return 1; /* short-cut as soon as we find one */
} else {
if (mark)
GRID(state,x,y) &= ~G_WARN;
} }
} }
} }
return loop;
findloop_free_state(fls);
return ret;
} }
static void map_group(game_state *state) static void map_group(game_state *state)
@ -1743,8 +1731,6 @@ static game_state *new_state(const game_params *params)
ret->grid = snewn(wh, grid_type); ret->grid = snewn(wh, grid_type);
memset(ret->grid, 0, GRIDSZ(ret)); memset(ret->grid, 0, GRIDSZ(ret));
ret->scratch = snewn(wh, grid_type);
memset(ret->scratch, 0, GRIDSZ(ret));
ret->wha = snewn(wh*N_WH_ARRAYS, char); ret->wha = snewn(wh*N_WH_ARRAYS, char);
memset(ret->wha, 0, wh*N_WH_ARRAYS*sizeof(char)); memset(ret->wha, 0, wh*N_WH_ARRAYS*sizeof(char));
@ -1789,8 +1775,6 @@ static game_state *dup_game(const game_state *state)
ret->grid = snewn(wh, grid_type); ret->grid = snewn(wh, grid_type);
memcpy(ret->grid, state->grid, GRIDSZ(ret)); memcpy(ret->grid, state->grid, GRIDSZ(ret));
ret->scratch = snewn(wh, grid_type);
memcpy(ret->scratch, state->scratch, GRIDSZ(ret));
ret->wha = snewn(wh*N_WH_ARRAYS, char); ret->wha = snewn(wh*N_WH_ARRAYS, char);
memcpy(ret->wha, state->wha, wh*N_WH_ARRAYS*sizeof(char)); memcpy(ret->wha, state->wha, wh*N_WH_ARRAYS*sizeof(char));
@ -1830,7 +1814,6 @@ static void free_game(game_state *state)
sfree(state->wha); sfree(state->wha);
sfree(state->scratch);
sfree(state->grid); sfree(state->grid);
sfree(state); sfree(state);
} }