shithub: puzzles

Download patch

ref: 4f2f8a9d173f34097a1636a3205ca0d50a39efee
parent: b3098efbc489be685ff644cde8dd6844f0198479
author: Simon Tatham <anakin@pobox.com>
date: Wed Feb 26 01:07:18 EST 2020

Tracks: new neighbour-based deduction.

This is a deduction I've been using in my own head for years: if you
only have one remaining filled square to put in a row, then it can't
be any square that has two adjacent edges blocked, because if that
square contains anything at all then it would have to be a corner
piece, and a corner piece forces the square next to it to be filled as
well.

I ran across a puzzle today that this implementation couldn't solve,
but I solved it fine by hand and found the deduction I was using that
wasn't implemented here. Now it is.

--- a/tracks.c
+++ b/tracks.c
@@ -12,6 +12,7 @@
  * #113   8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1
  * #114   8x8:p5fAzkAb,1,6,3,3,3,S6,2,3,5,4,S3,3,5,1,5,1
  * #115   8x8:zi9d5tAb,1,3,4,5,3,S4,2,4,2,6,2,3,6,S3,3,1
+ * #942   8x8:n5iCfAzAe,2,2,S5,5,3,5,4,5,4,5,2,S5,3,4,5,3
  */
 
 #include <stdio.h>
@@ -1191,6 +1192,82 @@
     return did;
 }
 
+static bool solve_check_neighbours_count(
+    game_state *state, int start, int step, int n, int clueindex)
+{
+    int to_fill = state->numbers->numbers[clueindex];
+    int i;
+    for (i = 0; i < n; i++) {
+        int p = start + i*step;
+        if (state->sflags[p] & S_TRACK)
+            to_fill--;
+    }
+    return (to_fill == 1);
+}
+
+static int solve_check_neighbours_try(game_state *state, int x, int y,
+                                      int X, int Y, unsigned dir,
+                                      const char *what)
+{
+    int w = state->p.w, p = y*w+x, P = Y*w+X;
+
+    /*
+     * We're given a neighbouring pair of squares p,P, with 'dir'
+     * being the direction from the former to the latter. We aim to
+     * spot situations in which, if p is a track square, then P must
+     * also be one (because p doesn't have enough free exits to avoid
+     * using the one that goes towards P).
+     *
+     * Then, if the target number of track squares on their shared
+     * row/column says that there's only one track square left to
+     * place, it can't be p, because P would have to be one too,
+     * violating the clue. So in that situation we can mark p as
+     * unfilled.
+     */
+
+    if ((state->sflags[p] | state->sflags[P]) & (S_TRACK | S_NOTRACK))
+        return 0; /* no need: we already know something about these squares */
+
+    int possible_exits_except_dir = nbits[
+        ALLDIR & ~dir & ~S_E_DIRS(state, x, y, E_NOTRACK)];
+    if (possible_exits_except_dir >= 2)
+        return 0; /* square p need not connect to P, even if it is filled */
+
+    /* OK, now we know that if p is filled, P must be filled too.
+     * But at most one of them can be filled, so it can't be p. */
+    state->sflags[p] |= S_NOTRACK;
+    solverdebug(("square (%d,%d) -> NOTRACK: otherwise, that and (%d,%d) "
+                 "would make too many TRACK in %s", x, y, X, Y, what));
+    return 1;
+}
+
+static int solve_check_neighbours(game_state *state)
+{
+    int w = state->p.w, h = state->p.h, x, y, did = 0;
+
+    for (x = 0; x < w; x++) {
+        if (!solve_check_neighbours_count(state, x, w, h, x))
+            continue;
+        for (y = 0; y+1 < h; y++) {
+            did += solve_check_neighbours_try(state, x, y, x, y+1,
+                                              D, "column");
+            did += solve_check_neighbours_try(state, x, y+1, x, y,
+                                               U, "column");
+        }
+    }
+    for (y = 0; y < h; y++) {
+        if (!solve_check_neighbours_count(state, y*w, 1, w, w+y))
+            continue;
+        for (x = 0; x+1 < w; x++) {
+            did += solve_check_neighbours_try(state, x, y, x+1, y,
+                                              R, "row");
+            did += solve_check_neighbours_try(state, x+1, y, x, y,
+                                              L, "row");
+        }
+    }
+    return did;
+}
+
 static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
                                 int *dsf, int startc, int endc)
 {
@@ -1336,6 +1413,7 @@
 
         TRY(DIFF_TRICKY, solve_check_single(state));
         TRY(DIFF_TRICKY, solve_check_loose_ends(state));
+        TRY(DIFF_TRICKY, solve_check_neighbours(state));
 
 
 #undef TRY
@@ -1342,8 +1420,6 @@
 
         break;
     }
-
-    sfree(sc->dsf);
 
     if (max_diff_out)
         *max_diff_out = max_diff;