shithub: puzzles

Download patch

ref: 05c536e50d0c3114e1de5283eac97ff22bad0fc7
parent: 5030d87903191d581586ecda2382ad5bcd70f63d
author: Simon Tatham <[email protected]>
date: Sun Feb 5 06:19:30 EST 2023

Pearl: fix assertion failure on bad puzzle.

Similarly to the previous commit, if you started Pearl with at least
some kinds of invalid puzzle (e.g. "6x6:abBfWcWWrBa") and then pressed
'h' to get hints, you could provoke an assertion failure. But this
time the assertion wasn't in the solver itself; the solver gave up
gracefully and didn't crash, but it _did_ leave the links between
squares in the game_state in an inconsistent state, in that one square
was marked as linking to its neighbour without the neighbour also
linking back to it. This caused the /* should have reciprocal link */
assertion in dsf_update_completion to fail, when that was called from
check_completion after the solver had finished, to decide whether the
puzzle was now solved.

In this commit, I arrange that whether or not pearl_solve returns a
grid layout that's legal by the rules of the _puzzle_, it at least
returns one that's legal by the rules of the _data representation_, in
that every link between squares is either bidirectional or absent.

This is a better solution than just removing the assertion, because if
the inconsistent data were allowed to persist, it would lead to
further problems in gameplay. For example, if you just remove that
assertion instead of this fix and press 'h' on the example puzzle id
above, you'll find that the non-reciprocal links are actually visible,
in the form of several thick lines that stop at a grid square boundary
instead of connecting two square-centres. (It looks even sillier if
you set PEARL_GUI_LOOPY=y.)

That's a situation that can't be created by a normal move, and if you
try to make normal moves after it (e.g. click one of the weird edges),
you'll find that both sides of the half-link get toggled, so now it's
a half-link the other way round. So not only can't you _create_ this
situation in normal play, you can't get rid of it either!

That assertion in dsf_update_completion was commented out at one
point, and I put it back in commit c5500926bf7458a saying that if it
failed I'd like to know about it. And indeed, I'm glad I did, because
this kind of unfixable wrongness in the resulting game_state was worth
noticing and getting rid of!

--- a/pearl.c
+++ b/pearl.c
@@ -855,6 +855,35 @@
                if (ret == 1) assert(b < 0xD); /* we should have had a break by now */
             }
         }
+
+        /*
+         * Ensure we haven't left the _data structure_ inconsistent,
+         * regardless of the consistency of the _puzzle_. In
+         * particular, we should never have marked one square as
+         * linked to its neighbour if the neighbour is not
+         * reciprocally linked back to the original square.
+         *
+         * This can happen if we get part way through solving an
+         * impossible puzzle and then give up trying to make further
+         * progress. So here we fix it up to avoid confusing the rest
+         * of the game.
+         */
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w; x++) {
+                for (d = 1; d <= 8; d += d) {
+                    int nx = x + DX(d), ny = y + DY(d);
+                    int rlink;
+                    if (0 <= nx && nx < w && 0 <= ny && ny < w)
+                        rlink = result[ny*w+nx] & F(d);
+                    else
+                        rlink = 0;     /* off-board squares don't link back */
+
+                    /* If other square doesn't link to us, don't link to it */
+                    if (!rlink)
+                        result[y*w+x] &= ~d;
+                }
+            }
+        }
     }
 
     sfree(dsfsize);