x = i%w;
y = i/w;
if (abs(ox-x) > 1 || abs(oy-y) > 1) {
- if (!state->sflags[i] & S_TRACK)
+ if (!(state->sflags[i] & S_TRACK))
did += solve_set_sflag(state, x, y, S_NOTRACK, what);
}
}
sfree(sstring);
}
-static void dsf_update_completion(game_state *state, int *loopclass,
- int ax, int ay, char dir,
- int *dsf)
+static void dsf_update_completion(game_state *state, int ax, int ay,
+ char dir, int *dsf)
{
- int w = state->p.w, ai = ay*w+ax, bx, by, bi, ac, bc;
+ int w = state->p.w, ai = ay*w+ax, bx, by, bi;
if (!(S_E_DIRS(state, ax, ay, E_TRACK) & dir)) return;
bx = ax + DX(dir);
if (!INGRID(state, bx, by)) return;
bi = by*w+bx;
- ac = dsf_canonify(dsf, ai);
- bc = dsf_canonify(dsf, bi);
+ dsf_merge(dsf, ai, bi);
+}
- if (ac == bc) {
- /* loop detected */
- *loopclass = ac;
- } else {
- dsf_merge(dsf, ai, bi);
+struct tracks_neighbour_ctx {
+ game_state *state;
+ int i, n, neighbours[4];
+};
+static int tracks_neighbour(int vertex, void *vctx)
+{
+ struct tracks_neighbour_ctx *ctx = (struct tracks_neighbour_ctx *)vctx;
+ if (vertex >= 0) {
+ game_state *state = ctx->state;
+ int w = state->p.w, x = vertex % w, y = vertex / w;
+ int dirs = S_E_DIRS(state, x, y, E_TRACK);
+ int j;
+
+ ctx->i = ctx->n = 0;
+
+ for (j = 0; j < 4; j++) {
+ int dir = 1<<j;
+ if (dirs & dir) {
+ int nx = x + DX(dir), ny = y + DY(dir);
+ if (INGRID(state, nx, ny))
+ ctx->neighbours[ctx->n++] = ny * w + nx;
+ }
+ }
}
+
+ if (ctx->i < ctx->n)
+ return ctx->neighbours[ctx->i++];
+ else
+ return -1;
}
static int check_completion(game_state *state, int mark)
{
int w = state->p.w, h = state->p.h, x, y, i, target, ret = TRUE;
int ntrack, nnotrack, ntrackcomplete;
- int *dsf, loopclass, pathclass;
+ int *dsf, pathclass;
+ struct findloopstate *fls;
+ struct tracks_neighbour_ctx ctx;
if (mark) {
for (i = 0; i < w+h; i++) {
for (i = 0; i < w*h; i++) {
state->sflags[i] &= ~S_ERROR;
if (S_E_COUNT(state, i%w, i/w, E_TRACK) > 0) {
- if (S_E_COUNT(state, i%w, i/w, E_TRACK) > 2)
+ if (S_E_COUNT(state, i%w, i/w, E_TRACK) > 2) {
+ ret = FALSE;
state->sflags[i] |= S_ERROR;
+ }
}
}
}
debug(("col %d error: target %d, track %d, notrack %d",
x, target, ntrack, nnotrack));
state->num_errors[x] = 1;
+ ret = FALSE;
}
}
if (ntrackcomplete != target)
debug(("row %d error: target %d, track %d, notrack %d",
y, target, ntrack, nnotrack));
state->num_errors[w+y] = 1;
+ ret = FALSE;
}
}
if (ntrackcomplete != target)
dsf = snewn(w*h, int);
dsf_init(dsf, w*h);
- loopclass = -1;
for (x = 0; x < w; x++) {
for (y = 0; y < h; y++) {
- dsf_update_completion(state, &loopclass, x, y, R, dsf);
- dsf_update_completion(state, &loopclass, x, y, D, dsf);
+ dsf_update_completion(state, x, y, R, dsf);
+ dsf_update_completion(state, x, y, D, dsf);
}
}
- if (loopclass != -1) {
+
+ fls = findloop_new_state(w*h);
+ ctx.state = state;
+ if (findloop_run(fls, w*h, tracks_neighbour, &ctx)) {
debug(("loop detected, not complete"));
ret = FALSE; /* no loop allowed */
if (mark) {
for (x = 0; x < w; x++) {
for (y = 0; y < h; y++) {
- /* TODO this will only highlight the first loop found */
- if (dsf_canonify(dsf, y*w + x) == loopclass) {
- state->sflags[y*w+x] |= S_ERROR;
- }
+ int u, v;
+
+ u = y*w + x;
+ for (v = tracks_neighbour(u, &ctx); v >= 0;
+ v = tracks_neighbour(-1, &ctx))
+ if (findloop_is_loop_edge(fls, u, v))
+ state->sflags[y*w+x] |= S_ERROR;
}
}
}
}
+ findloop_free_state(fls);
+
if (mark) {
pathclass = dsf_canonify(dsf, state->numbers->row_s*w);
if (pathclass == dsf_canonify(dsf, (h-1)*w + state->numbers->col_s)) {
for (i = 0; i < w*h; i++) {
if ((dsf_canonify(dsf, i) != pathclass) &&
((state->sflags[i] & S_TRACK) ||
- (S_E_COUNT(state, i%w, i/w, E_TRACK) > 0)))
+ (S_E_COUNT(state, i%w, i/w, E_TRACK) > 0))) {
+ ret = FALSE;
state->sflags[i] |= S_ERROR;
+ }
}
+ } else {
+ /* If we _don't_ have such a path, then certainly the game
+ * can't be in a winning state. So even if we're not
+ * highlighting any _errors_, we certainly shouldn't
+ * return true. */
+ ret = FALSE;
}
}
#define TILE_SIZE (ds->sz6*6)
#define BORDER (TILE_SIZE/8)
-#define BORDER_WIDTH (max(TILE_SIZE / 32, 1))
+#define LINE_THICK (TILE_SIZE/16)
+#define GRID_LINE_TL (ds->grid_line_tl)
+#define GRID_LINE_BR (ds->grid_line_br)
+#define GRID_LINE_ALL (ds->grid_line_all)
#define COORD(x) ( (x+1) * TILE_SIZE + BORDER )
#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 )
#define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */
struct game_drawstate {
- int sz6;
+ int sz6, grid_line_all, grid_line_tl, grid_line_br;
int started;
int w, h, sz;
int sz6;
} ads, *ds = &ads;
ads.sz6 = tilesize/6;
-
*x = (params->w+2) * TILE_SIZE + 2 * BORDER;
*y = (params->h+2) * TILE_SIZE + 2 * BORDER;
}
const game_params *params, int tilesize)
{
ds->sz6 = tilesize/6;
+ ds->grid_line_all = max(LINE_THICK, 1);
+ ds->grid_line_br = ds->grid_line_all / 2;
+ ds->grid_line_tl = ds->grid_line_all - ds->grid_line_br;
}
enum {
/* Clip to the grid square. */
clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
- /* Clear the square. */
+ /* Clear the square so that it's got an appropriately-sized border
+ * in COL_GRID and a central area in the right background colour. */
best_bits((flags & DS_TRACK) == DS_TRACK,
(flags_drag & DS_TRACK) == DS_TRACK, &bg);
- draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, bg);
-
- /* Draw outline of grid square */
- draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID);
- draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID);
+ draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_GRID);
+ draw_rect(dr, ox + GRID_LINE_TL, oy + GRID_LINE_TL,
+ TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL, bg);
/* More outlines for clue squares. */
if (flags & DS_CURSOR) {
(flags_drag & DS_NOTRACK) == DS_NOTRACK, &c);
if (flags_best) {
off = HALFSZ/2;
- draw_line(dr, cx - off, cy - off, cx + off, cy + off, c);
- draw_line(dr, cx - off, cy + off, cx + off, cy - off, c);
+ draw_thick_line(dr, LINE_THICK, cx - off, cy - off, cx + off, cy + off, c);
+ draw_thick_line(dr, LINE_THICK, cx - off, cy + off, cx + off, cy - off, c);
}
c = COL_TRACK;
cx += (d == R) ? t2 : (d == L) ? -t2 : 0;
cy += (d == D) ? t2 : (d == U) ? -t2 : 0;
- draw_line(dr, cx - off, cy - off, cx + off, cy + off, c);
- draw_line(dr, cx - off, cy + off, cx + off, cy - off, c);
+ draw_thick_line(dr, LINE_THICK, cx - off, cy - off, cx + off, cy + off, c);
+ draw_thick_line(dr, LINE_THICK, cx - off, cy + off, cx + off, cy - off, c);
}
}
cy = CENTERED_COORD(i-w);
}
- draw_rect(dr, cx - tsz + BORDER, cy - tsz + BORDER,
- TILE_SIZE - BORDER, TILE_SIZE - BORDER, COL_BACKGROUND);
+ draw_rect(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL,
+ TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL,
+ COL_BACKGROUND);
sprintf(buf, "%d", clue);
draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE,
col, buf);
- draw_update(dr, cx - tsz, cy - tsz, TILE_SIZE, TILE_SIZE);
+ draw_update(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL,
+ TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL);
}
static void draw_loop_ends(drawing *dr, game_drawstate *ds,
draw_loop_ends(dr, ds, state, COL_CLUE);
- draw_line(dr, COORD(ds->w), COORD(0), COORD(ds->w), COORD(ds->h), COL_GRID);
- draw_line(dr, COORD(0), COORD(ds->h), COORD(ds->w), COORD(ds->h), COL_GRID);
+ draw_rect(dr, COORD(0) - GRID_LINE_BR, COORD(0) - GRID_LINE_BR,
+ ds->w * TILE_SIZE + GRID_LINE_ALL,
+ ds->h * TILE_SIZE + GRID_LINE_ALL, COL_GRID);
draw_update(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER);
const struct game thegame = {
"Train Tracks", "games.tracks", "tracks",
default_params,
- game_fetch_preset,
+ game_fetch_preset, NULL,
decode_params,
encode_params,
free_params,