chiark / gitweb /
Forbid undo of new-game if it would change the params.
[sgt-puzzles.git] / tracks.c
index c6b3d8e148c70787dc917481712ff47cb23a16ef..e4e7b5ca9c8e4611759c0fad6f538f7cf5fb28cc 100644 (file)
--- a/tracks.c
+++ b/tracks.c
@@ -1072,7 +1072,7 @@ static int solve_check_single_sub(game_state *state, int si, int id, int n,
         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);
         }
     }
@@ -1490,11 +1490,10 @@ static void debug_state(game_state *state, const char *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);
@@ -1503,22 +1502,47 @@ static void dsf_update_completion(game_state *state, int *loopclass,
     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;
-    int *dsf, loopclass, pathclass;
+    int ntrack, nnotrack, ntrackcomplete;
+    int *dsf, pathclass;
+    struct findloopstate *fls;
+    struct tracks_neighbour_ctx ctx;
 
     if (mark) {
         for (i = 0; i < w+h; i++) {
@@ -1527,20 +1551,28 @@ static int check_completion(game_state *state, int mark)
         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;
+                }
             }
         }
     }
 
-    /* A cell is 'complete' if it has any edges marked as TRACK. */
+    /* A cell is 'complete', for the purposes of marking the game as
+     * finished, if it has two edges marked as TRACK. But it only has
+     * to have one edge marked as TRACK, or be filled in as trackful
+     * without any specific edges known, to count towards checking
+     * row/column clue errors. */
     for (x = 0; x < w; x++) {
         target = state->numbers->numbers[x];
-        ntrack = nnotrack = 0;
+        ntrack = nnotrack = ntrackcomplete = 0;
         for (y = 0; y < h; y++) {
             if (S_E_COUNT(state, x, y, E_TRACK) > 0 ||
                 state->sflags[y*w+x] & S_TRACK)
                 ntrack++;
+            if (S_E_COUNT(state, x, y, E_TRACK) == 2)
+                ntrackcomplete++;
             if (state->sflags[y*w+x] & S_NOTRACK)
                 nnotrack++;
         }
@@ -1549,18 +1581,21 @@ static int check_completion(game_state *state, int mark)
                 debug(("col %d error: target %d, track %d, notrack %d",
                        x, target, ntrack, nnotrack));
                 state->num_errors[x] = 1;
+                ret = FALSE;
             }
         }
-        if (ntrack != target)
+        if (ntrackcomplete != target)
             ret = FALSE;
     }
     for (y = 0; y < h; y++) {
         target = state->numbers->numbers[w+y];
-        ntrack = nnotrack = 0;
+        ntrack = nnotrack = ntrackcomplete = 0;
         for (x = 0; x < w; x++) {
             if (S_E_COUNT(state, x, y, E_TRACK) > 0 ||
                 state->sflags[y*w+x] & S_TRACK)
                 ntrack++;
+            if (S_E_COUNT(state, x, y, E_TRACK) == 2)
+                ntrackcomplete++;
             if (state->sflags[y*w+x] & S_NOTRACK)
                 nnotrack++;
         }
@@ -1569,36 +1604,44 @@ static int check_completion(game_state *state, int mark)
                 debug(("row %d error: target %d, track %d, notrack %d",
                        y, target, ntrack, nnotrack));
                 state->num_errors[w+y] = 1;
+                ret = FALSE;
             }
         }
-        if (ntrack != target)
+        if (ntrackcomplete != target)
             ret = FALSE;
     }
 
     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)) {
@@ -1607,9 +1650,17 @@ static int check_completion(game_state *state, int mark)
             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;
         }
     }
 
@@ -1667,7 +1718,10 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
 #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 )
@@ -1687,7 +1741,7 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
 #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;
@@ -2067,7 +2121,6 @@ static void game_compute_size(const game_params *params, int tilesize,
         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;
 }
@@ -2076,6 +2129,9 @@ static void game_set_size(drawing *dr, game_drawstate *ds,
                           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 {
@@ -2084,7 +2140,7 @@ enum {
     COL_GRID, COL_CLUE, COL_CURSOR,
     COL_TRACK, COL_TRACK_CLUE, COL_SLEEPER,
     COL_DRAGON, COL_DRAGOFF,
-    COL_ERROR, COL_FLASH,
+    COL_ERROR, COL_FLASH, COL_ERROR_BACKGROUND,
     NCOLOURS
 };
 
@@ -2096,11 +2152,12 @@ static float *game_colours(frontend *fe, int *ncolours)
     game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
 
     for (i = 0; i < 3; i++) {
-        ret[COL_TRACK_CLUE * 3 + i] = 0.0F;
-        ret[COL_TRACK * 3 + i] = 0.5F;
-        ret[COL_CLUE * 3 + i] = 0.0F;
-        ret[COL_GRID * 3 + i] = 0.75F;
-        ret[COL_CURSOR * 3 + i] = 0.6F;
+        ret[COL_TRACK_CLUE       * 3 + i] = 0.0F;
+        ret[COL_TRACK            * 3 + i] = 0.5F;
+        ret[COL_CLUE             * 3 + i] = 0.0F;
+        ret[COL_GRID             * 3 + i] = 0.75F;
+        ret[COL_CURSOR           * 3 + i] = 0.6F;
+        ret[COL_ERROR_BACKGROUND * 3 + i] = 1.0F;
     }
 
     ret[COL_SLEEPER * 3 + 0] = 0.5F;
@@ -2295,14 +2352,13 @@ static void draw_square(drawing *dr, game_drawstate *ds,
     /* 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) {
@@ -2338,8 +2394,8 @@ static void draw_square(drawing *dr, game_drawstate *ds,
                            (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;
@@ -2353,8 +2409,8 @@ static void draw_square(drawing *dr, game_drawstate *ds,
             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);
         }
     }
 
@@ -2362,7 +2418,7 @@ static void draw_square(drawing *dr, game_drawstate *ds,
     draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE);
 }
 
-static void draw_clue(drawing *dr, game_drawstate *ds, int w, int clue, int i, int col)
+static void draw_clue(drawing *dr, game_drawstate *ds, int w, int clue, int i, int col, int bg)
 {
     int cx, cy, tsz = TILE_SIZE/2;
     char buf[20];
@@ -2375,12 +2431,14 @@ static void draw_clue(drawing *dr, game_drawstate *ds, int w, int clue, int i, i
         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,
+              bg);
     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,
@@ -2447,8 +2505,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldst
 
         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);
 
@@ -2460,7 +2519,8 @@ static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldst
         if (force || (state->num_errors[i] != ds->num_errors[i])) {
             ds->num_errors[i] = state->num_errors[i];
             draw_clue(dr, ds, w, state->numbers->numbers[i], i,
-                      ds->num_errors[i] ? COL_ERROR : COL_CLUE);
+                      ds->num_errors[i] ? COL_ERROR : COL_CLUE,
+                     ds->num_errors[i] ? COL_ERROR_BACKGROUND : COL_BACKGROUND);
         }
     }
 
@@ -2550,7 +2610,8 @@ static void game_print(drawing *dr, const game_state *state, int tilesize)
 
     /* clue numbers, and loop ends */
     for (i = 0; i < w+h; i++)
-        draw_clue(dr, ds, w, state->numbers->numbers[i], i, black);
+        draw_clue(dr, ds, w, state->numbers->numbers[i], i,
+                 black, COL_BACKGROUND);
     draw_loop_ends(dr, ds, state, black);
 
     /* clue tracks / solution */
@@ -2571,7 +2632,7 @@ static void game_print(drawing *dr, const game_state *state, int tilesize)
 const struct game thegame = {
     "Train Tracks", "games.tracks", "tracks",
     default_params,
-    game_fetch_preset,
+    game_fetch_preset, NULL,
     decode_params,
     encode_params,
     free_params,