simon-git: puzzles (master): Simon Tatham

Commits to Tartarus hosted VCS tartarus-commits at lists.tartarus.org
Wed Feb 26 06:34:58 GMT 2020


TL;DR:
  f8027fb Tracks: make solver return max difficulty used.
  b3098ef Tracks: add standalone solver program.
  4f2f8a9 Tracks: new neighbour-based deduction.
  d724e13 Tracks: new parity-based deduction.
  5e9dc42 Tracks: add reverse neighbour deduction in hard mode.
  d022a1c Tracks: fix a small memory leak.

Repository:     https://git.tartarus.org/simon/puzzles.git
On the web:     https://git.tartarus.org/?p=simon/puzzles.git
Branch updated: master
Committer:      Simon Tatham <anakin at pobox.com>
Date:           2020-02-26 06:34:58

commit f8027fb2e0032b362621e7314ef0ac6cdc2b8577
web diff https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=f8027fb2e0032b362621e7314ef0ac6cdc2b8577;hp=79a5378b5adc46ee33ba34d55738f916fb8adfc9
Author: Simon Tatham <anakin at pobox.com>
Date:   Wed Feb 26 06:03:35 2020 +0000

    Tracks: make solver return max difficulty used.
    
    This should speed up game generation, because now we don't have to run
    the solver _twice_ whenever we want to check that the grid has exactly
    the intended difficulty. Instead, we can just run it once and check
    the max_diff output.

 tracks.c | 72 +++++++++++++++++++++++++++++++++++-----------------------------
 1 file changed, 40 insertions(+), 32 deletions(-)

commit b3098efbc489be685ff644cde8dd6844f0198479
web diff https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=b3098efbc489be685ff644cde8dd6844f0198479;hp=f8027fb2e0032b362621e7314ef0ac6cdc2b8577
Author: Simon Tatham <anakin at pobox.com>
Date:   Wed Feb 26 06:05:00 2020 +0000

    Tracks: add standalone solver program.
    
    Having one of these makes it much easier to debug what's going on when
    the solver can't solve something. Also, now the solver can grade the
    difficulty of a puzzle, it's useful to expose that feature in a
    command-line tool.

 .gitignore |   1 +
 tracks.R   |   3 ++
 tracks.c   | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 116 insertions(+), 9 deletions(-)

commit 4f2f8a9d173f34097a1636a3205ca0d50a39efee
web diff https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=4f2f8a9d173f34097a1636a3205ca0d50a39efee;hp=b3098efbc489be685ff644cde8dd6844f0198479
Author: Simon Tatham <anakin at pobox.com>
Date:   Wed Feb 26 06:07:18 2020 +0000

    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.

 tracks.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 78 insertions(+), 2 deletions(-)

commit d724e136663ed028aa6429f0bc5cbf93585f5aa0
web diff https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=d724e136663ed028aa6429f0bc5cbf93585f5aa0;hp=4f2f8a9d173f34097a1636a3205ca0d50a39efee
Author: Simon Tatham <anakin at pobox.com>
Date:   Wed Feb 26 06:18:51 2020 +0000

    Tracks: new parity-based deduction.
    
    This is another deduction I've known about in principle for ages but
    the game didn't implement.
    
    In the simplest case, it's obvious: if you can draw a line across the
    grid that separates the track endpoints from each other, and the track
    doesn't yet cross that line at all, then it's going to have to cross
    it at _some_ point. So when you've narrowed down to only one possible
    crossing place, you can fill it in as definite.
    
    IF the track already crosses your line and goes back again, the same
    rule still applies: _some_ part of your track is on one side of the
    line, and needs to get to the other. A more sensible way of expressing
    this is to say that the track must cross the boundary an _odd_ number
    of times if the two endpoints are on opposite sides of it.
    
    And conversely, if you've drawn a line across the grid that both
    endpoints are on the _same_ side of, then the track must cross it an
    _even_ number of times - every time it goes to the 'wrong' side (where
    the endpoints aren't), it will have to come back again eventually.
    
    But this doesn't just apply to a _line_ across the grid. You can pick
    any subset you like of the squares of the grid, and imagine the closed
    loop bounding that subset as your 'line'. (Or the _set_ of closed
    loops, in the most general case, because your subset doesn't _have_ to
    be simply connected - or even connected at all - to make this argument
    valid.) If your boundary is a closed loop, then both endpoints are
    always on the same side of that boundary - namely, the outside - and
    so the track has to cross the boundary an even number of times. So any
    time you can identify such a subset in which all but one boundary edge
    is already filled in, you can fill in the last one by parity.
    
    (This most general boundary-based system takes in all the previous
    cases as special cases. In the original case where it looks as if you
    need odd parity for a line across the grid separating the endpoints,
    what you're really doing is drawing a closed loop around one half of
    the grid, and considering the actual endpoint itself to be the place
    where the track leaves that region again - so, looked at that way, the
    parity is back to even.)
    
    The tricky part of implementing this is to avoid having to iterate
    over all subsets of the grid looking for one whose boundary has the
    right property. Luckily, we don't have to: a nice way to look at it is
    to define a graph whose vertices are grid squares, with neighbouring
    squares joined by a _graph_ edge if the _grid_ edge between those
    squares is not yet in a known state. Then we're looking for an edge of
    that graph which if removed would break it up into more separate
    components than it's already in. That is, we want a _bridge_ in the
    graph - which we can find all of in linear time using Tarjan's
    bridge-finding algorithm, conveniently implemented already in this
    collection in findloop.c.
    
    Having found a bridge edge of that graph, you imagine removing it, and
    find one of the two connected components it's just broken its previous
    component up into. That's your subset of grid squares, and now you can
    count track crossings around the boundary and fill in the bridge edge
    by parity.
    
    When I actually came to implement this, it turned out that the very
    first puzzle it generated was actually hard for me to solve, because
    as it turns out, this general analysis is much better at identifying
    opportunities to use this deduction than I am. A straight line right
    across the grid is often obvious: a few squares tucked into a
    complicated half-solved piece of the worldl, not so much. So I'm
    introducing a new Hard difficulty level, and putting this solution
    technique in there.

 tracks.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 150 insertions(+), 3 deletions(-)

commit 5e9dc42e54e777dbd014b4fc6e9312061d000915
web diff https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=5e9dc42e54e777dbd014b4fc6e9312061d000915;hp=d724e136663ed028aa6429f0bc5cbf93585f5aa0
Author: Simon Tatham <anakin at pobox.com>
Date:   Wed Feb 26 06:26:36 2020 +0000

    Tracks: add reverse neighbour deduction in hard mode.
    
    This is the contrapositive of the deduction introduced in the previous
    commit. Previously I said: a square A can have some edges blocked in
    such a way that you know it can't be filled without a particular one
    of its neighbours B also being filled. And then, if you know the row
    containing A and B only has one filled square left to find, then it
    can't be A.
    
    This commit adds the obvious followup: if you know the row only has
    one _empty_ square left, then for the same reason, it can't be B!
    
    I'm putting this in at the new Hard difficulty, mostly out of
    guesswork rather than rigorous play-testing, because I don't remember
    ever having _observed_ myself making this deduction in the past. I'm
    open to changing the settings if someone has a good argument for it.

 tracks.c | 66 +++++++++++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 47 insertions(+), 19 deletions(-)

commit d022a1c11c185027fad937cbe9cd2c318025ee04
web diff https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=d022a1c11c185027fad937cbe9cd2c318025ee04;hp=5e9dc42e54e777dbd014b4fc6e9312061d000915
Author: Simon Tatham <anakin at pobox.com>
Date:   Wed Feb 26 06:19:26 2020 +0000

    Tracks: fix a small memory leak.
    
    Spotted by Leak Sanitiser while I was testing the standalone solver.

 tracks.c | 1 +
 1 file changed, 1 insertion(+)



More information about the tartarus-commits mailing list