chiark / gitweb /
Patch from Lambros to improve the generality of path-generation. In
authorSimon Tatham <anakin@pobox.com>
Tue, 4 Nov 2008 21:39:59 +0000 (21:39 +0000)
committerSimon Tatham <anakin@pobox.com>
Tue, 4 Nov 2008 21:39:59 +0000 (21:39 +0000)
particular, Great Hexagonal tilings previously had virtually every
(if not _actually_ every) hexagon on the inside of the path, and now
don't.

[originally from svn r8277]

loopy.c

diff --git a/loopy.c b/loopy.c
index 0cb6921a7e220f57552ef641a82031a93f0cf2c7..39dec98bfd11398433f80070d454d64a0542c28e 100644 (file)
--- a/loopy.c
+++ b/loopy.c
@@ -73,6 +73,7 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stddef.h>
 #include <string.h>
 #include <assert.h>
 #include <ctype.h>
@@ -1219,33 +1220,34 @@ static int face_setall(solver_state *sstate, int face,
  * Loop generation and clue removal
  */
 
-/* We're going to store a list of current candidate faces for lighting.
+/* We're going to store lists of current candidate faces for colouring black
+ * or white.
  * Each face gets a 'score', which tells us how adding that face right
- * now would affect the length of the solution loop.  We're trying to
+ * now would affect the curliness of the solution loop.  We're trying to
  * maximise that quantity so will bias our random selection of faces to
- * light towards those with high scores */
-struct face {
-    int score;
+ * colour those with high scores */
+struct face_score {
+    int white_score;
+    int black_score;
     unsigned long random;
-    grid_face *f;
+    /* No need to store a grid_face* here.  The 'face_scores' array will
+     * be a list of 'face_score' objects, one for each face of the grid, so
+     * the position (index) within the 'face_scores' array will determine
+     * which face corresponds to a particular face_score.
+     * Having a single 'face_scores' array for all faces simplifies memory
+     * management, and probably improves performance, because we don't have to 
+     * malloc/free each individual face_score, and we don't have to maintain
+     * a mapping from grid_face* pointers to face_score* pointers.
+     */
 };
 
-static int get_face_cmpfn(void *v1, void *v2)
-{
-    struct face *f1 = v1;
-    struct face *f2 = v2;
-    /* These grid_face pointers always point into the same list of
-     * 'grid_face's, so it's valid to subtract them. */
-    return f1->f - f2->f;
-}
-
-static int face_sort_cmpfn(void *v1, void *v2)
+static int generic_sort_cmpfn(void *v1, void *v2, size_t offset)
 {
-    struct face *f1 = v1;
-    struct face *f2 = v2;
+    struct face_score *f1 = v1;
+    struct face_score *f2 = v2;
     int r;
 
-    r = f2->score - f1->score;
+    r = *(int *)((char *)f2 + offset) - *(int *)((char *)f1 + offset);
     if (r) {
         return r;
     }
@@ -1258,64 +1260,74 @@ static int face_sort_cmpfn(void *v1, void *v2)
     /*
      * It's _just_ possible that two faces might have been given
      * the same random value. In that situation, fall back to
-     * comparing based on the positions within the grid's face-list.
+     * comparing based on the positions within the face_scores list.
      * This introduces a tiny directional bias, but not a significant one.
      */
-    return get_face_cmpfn(f1, f2);
+    return f1 - f2;
+}
+
+static int white_sort_cmpfn(void *v1, void *v2)
+{
+    return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,white_score));
 }
 
-enum { FACE_LIT, FACE_UNLIT };
+static int black_sort_cmpfn(void *v1, void *v2)
+{
+    return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,black_score));
+}
+
+enum face_colour { FACE_WHITE, FACE_GREY, FACE_BLACK };
 
 /* face should be of type grid_face* here. */
-#define FACE_LIT_STATE(face) \
-    ( (face) == NULL ? FACE_UNLIT : \
+#define FACE_COLOUR(face) \
+    ( (face) == NULL ? FACE_BLACK : \
          board[(face) - g->faces] )
 
 /* 'board' is an array of these enums, indicating which faces are
- * currently lit.  Returns whether it's legal to light up the
- * given face. */
-static int can_light_face(grid *g, char* board, int face_index)
+ * currently black/white/grey.  'colour' is FACE_WHITE or FACE_BLACK.
+ * Returns whether it's legal to colour the given face with this colour. */
+static int can_colour_face(grid *g, char* board, int face_index,
+                           enum face_colour colour)
 {
     int i, j;
     grid_face *test_face = g->faces + face_index;
     grid_face *starting_face, *current_face;
     int transitions;
-    int current_state, s;
-    int found_lit_neighbour = FALSE;
-    assert(board[face_index] == FACE_UNLIT);
+    int current_state, s; /* booleans: equal or not-equal to 'colour' */
+    int found_same_coloured_neighbour = FALSE;
+    assert(board[face_index] != colour);
 
-    /* Can only consider a face for lighting if it's adjacent to an
-     * already lit face. */
+    /* Can only consider a face for colouring if it's adjacent to a face
+     * with the same colour. */
     for (i = 0; i < test_face->order; i++) {
         grid_edge *e = test_face->edges[i];
         grid_face *f = (e->face1 == test_face) ? e->face2 : e->face1;
-        if (FACE_LIT_STATE(f) == FACE_LIT) {
-            found_lit_neighbour = TRUE;
+        if (FACE_COLOUR(f) == colour) {
+            found_same_coloured_neighbour = TRUE;
             break;
         }
     }
-    if (!found_lit_neighbour)
+    if (!found_same_coloured_neighbour)
         return FALSE;
 
-    /* Need to avoid creating a loop of lit faces around some unlit faces.
-     * Also need to avoid meeting another lit face at a corner, with
-     * unlit faces in between.  Here's a simple test that (I believe) takes
-     * care of both these conditions:
+    /* Need to avoid creating a loop of faces of this colour around some
+     * differently-coloured faces.
+     * Also need to avoid meeting a same-coloured face at a corner, with
+     * other-coloured faces in between.  Here's a simple test that (I believe)
+     * takes care of both these conditions:
      *
      * Take the circular path formed by this face's edges, and inflate it
      * slightly outwards.  Imagine walking around this path and consider
      * the faces that you visit in sequence.  This will include all faces
      * touching the given face, either along an edge or just at a corner.
-     * Count the number of LIT/UNLIT transitions you encounter, as you walk
-     * along the complete loop.  This will obviously turn out to be an even
-     * number.
-     * If 0, we're either in a completely unlit zone, or this face is a hole
-     * in a completely lit zone.  If the former, we would create a brand new
-     * island by lighting this face.  And the latter ought to be impossible -
-     * it would mean there's already a lit loop, so something went wrong
-     * earlier.
-     * If 4 or greater, there are too many separate lit regions touching this
-     * face, and lighting it up would create a loop or a corner-violation.
+     * Count the number of 'colour'/not-'colour' transitions you encounter, as
+     * you walk along the complete loop.  This will obviously turn out to be
+     * an even number.
+     * If 0, we're either in the middle of an "island" of this colour (should
+     * be impossible as we're not supposed to create black or white loops),
+     * or we're about to start a new island - also not allowed.
+     * If 4 or greater, there are too many separate coloured regions touching
+     * this face, and colouring it would create a loop or a corner-violation.
      * The only allowed case is when the count is exactly 2. */
 
     /* i points to a dot around the test face.
@@ -1332,7 +1344,7 @@ static int can_light_face(grid *g, char* board, int face_index)
     }
     current_face = starting_face;
     transitions = 0;
-    current_state = FACE_LIT_STATE(current_face);
+    current_state = (FACE_COLOUR(current_face) == colour);
 
     do {
         /* Advance to next face.
@@ -1364,7 +1376,7 @@ static int can_light_face(grid *g, char* board, int face_index)
         }
         /* (i,j) are now advanced to next face */
         current_face = test_face->dots[i]->faces[j];
-        s = FACE_LIT_STATE(current_face);
+        s = (FACE_COLOUR(current_face) == colour);
         if (s != current_state) {
             ++transitions;
             current_state = s;
@@ -1376,53 +1388,121 @@ static int can_light_face(grid *g, char* board, int face_index)
     return (transitions == 2) ? TRUE : FALSE;
 }
 
-/* The 'score' of a face reflects its current desirability for selection
- * as the next face to light.  We want to encourage moving into uncharted
- * areas so we give scores according to how many of the face's neighbours
- * are currently unlit. */
-static int face_score(grid *g, char *board, grid_face *face)
+/* Count the number of neighbours of 'face', having colour 'colour' */
+static int face_num_neighbours(grid *g, char *board, grid_face *face,
+                               enum face_colour colour)
 {
-    /* Simple formula: score = neighbours unlit - neighbours lit */
-    int lit_count = 0, unlit_count = 0;
+    int colour_count = 0;
     int i;
     grid_face *f;
     grid_edge *e;
     for (i = 0; i < face->order; i++) {
         e = face->edges[i];
         f = (e->face1 == face) ? e->face2 : e->face1;
-        if (FACE_LIT_STATE(f) == FACE_LIT)
-            ++lit_count;
-        else
-            ++unlit_count;
+        if (FACE_COLOUR(f) == colour)
+            ++colour_count;
     }
-    return unlit_count - lit_count;
+    return colour_count;
 }
 
-/* Generate a new complete set of clues for the given game_state. */
+/* The 'score' of a face reflects its current desirability for selection
+ * as the next face to colour white or black.  We want to encourage moving
+ * into grey areas and increasing loopiness, so we give scores according to
+ * how many of the face's neighbours are currently coloured the same as the
+ * proposed colour. */
+static int face_score(grid *g, char *board, grid_face *face,
+                      enum face_colour colour)
+{
+    /* Simple formula: score = 0 - num. same-coloured neighbours,
+     * so a higher score means fewer same-coloured neighbours. */
+    return -face_num_neighbours(g, board, face, colour);
+}
+
+/* Generate a new complete set of clues for the given game_state.
+ * The method is to generate a WHITE/BLACK colouring of all the faces,
+ * such that the WHITE faces will define the inside of the path, and the
+ * BLACK faces define the outside.
+ * To do this, we initially colour all faces GREY.  The infinite space outside
+ * the grid is coloured BLACK, and we choose a random face to colour WHITE.
+ * Then we gradually grow the BLACK and the WHITE regions, eliminating GREY
+ * faces, until the grid is filled with BLACK/WHITE.  As we grow the regions,
+ * we avoid creating loops of a single colour, to preserve the topological
+ * shape of the WHITE and BLACK regions.
+ * We also try to make the boundary as loopy and twisty as possible, to avoid
+ * generating paths that are uninteresting.
+ * The algorithm works by choosing a BLACK/WHITE colour, then choosing a GREY
+ * face that can be coloured with that colour (without violating the
+ * topological shape of that region).  It's not obvious, but I think this
+ * algorithm is guaranteed to terminate without leaving any GREY faces behind.
+ * Indeed, if there are any GREY faces at all, both the WHITE and BLACK
+ * regions can be grown.
+ * This is checked using assert()ions, and I haven't seen any failures yet.
+ *
+ * Hand-wavy proof: imagine what can go wrong...
+ *
+ * Could the white faces get completely cut off by the black faces, and still
+ * leave some grey faces remaining?
+ * No, because then the black faces would form a loop around both the white
+ * faces and the grey faces, which is disallowed because we continually
+ * maintain the correct topological shape of the black region.
+ * Similarly, the black faces can never get cut off by the white faces.  That
+ * means both the WHITE and BLACK regions always have some room to grow into
+ * the GREY regions.
+ * Could it be that we can't colour some GREY face, because there are too many
+ * WHITE/BLACK transitions as we walk round the face? (see the
+ * can_colour_face() function for details)
+ * No.  Imagine otherwise, and we see WHITE/BLACK/WHITE/BLACK as we walk
+ * around the face.  The two WHITE faces would be connected by a WHITE path,
+ * and the BLACK faces would be connected by a BLACK path.  These paths would
+ * have to cross, which is impossible.
+ * Another thing that could go wrong: perhaps we can't find any GREY face to
+ * colour WHITE, because it would create a loop-violation or a corner-violation
+ * with the other WHITE faces?
+ * This is a little bit tricky to prove impossible.  Imagine you have such a
+ * GREY face (that is, if you coloured it WHITE, you would create a WHITE loop
+ * or corner violation).
+ * That would cut all the non-white area into two blobs.  One of those blobs
+ * must be free of BLACK faces (because the BLACK stuff is a connected blob).
+ * So we have a connected GREY area, completely surrounded by WHITE
+ * (including the GREY face we've tentatively coloured WHITE).
+ * A well-known result in graph theory says that you can always find a GREY
+ * face whose removal leaves the remaining GREY area connected.  And it says
+ * there are at least two such faces, so we can always choose the one that
+ * isn't the "tentative" GREY face.  Colouring that face WHITE leaves
+ * everything nice and connected, including that "tentative" GREY face which
+ * acts as a gateway to the rest of the non-WHITE grid.
+ */
 static void add_full_clues(game_state *state, random_state *rs)
 {
     signed char *clues = state->clues;
     char *board;
     grid *g = state->game_grid;
-    int i, j, c;
+    int i, j;
     int num_faces = g->num_faces;
-    int first_time = TRUE;
-
-    struct face *face, *tmpface;
-    struct face face_pos;
-
-    /* These will contain exactly the same information, sorted into different
-     * orders */
-    tree234 *lightable_faces_sorted, *lightable_faces_gettable;
-
-#define IS_LIGHTING_CANDIDATE(i) \
-    (board[i] == FACE_UNLIT && \
-        can_light_face(g, board, i))
+    struct face_score *face_scores; /* Array of face_score objects */
+    struct face_score *fs; /* Points somewhere in the above list */
+    struct grid_face *cur_face;
+    tree234 *lightable_faces_sorted;
+    tree234 *darkable_faces_sorted;
+    int *face_list;
+    int do_random_pass;
 
     board = snewn(num_faces, char);
 
     /* Make a board */
-    memset(board, FACE_UNLIT, num_faces);
+    memset(board, FACE_GREY, num_faces);
+    
+    /* Create and initialise the list of face_scores */
+    face_scores = snewn(num_faces, struct face_score);
+    for (i = 0; i < num_faces; i++) {
+        face_scores[i].random = random_bits(rs, 31);
+    }
+    
+    /* Colour a random, finite face white.  The infinite face is implicitly
+     * coloured black.  Together, they will seed the random growth process
+     * for the black and white areas. */
+    i = random_upto(rs, num_faces);
+    board[i] = FACE_WHITE;
 
     /* We need a way of favouring faces that will increase our loopiness.
      * We do this by maintaining a list of all candidate faces sorted by
@@ -1436,123 +1516,188 @@ static void add_full_clues(game_state *state, random_state *rs)
      * Yes, this means we will be biased towards particular random faces in
      * any one run but that doesn't actually matter. */
 
-    lightable_faces_sorted   = newtree234(face_sort_cmpfn);
-    lightable_faces_gettable = newtree234(get_face_cmpfn);
-#define ADD_FACE(f) \
-    do { \
-        struct face *x = add234(lightable_faces_sorted, f); \
-        assert(x == f); \
-        x = add234(lightable_faces_gettable, f); \
-        assert(x == f); \
-    } while (0)
+    lightable_faces_sorted = newtree234(white_sort_cmpfn);
+    darkable_faces_sorted = newtree234(black_sort_cmpfn);
 
-#define REMOVE_FACE(f) \
-    do { \
-        struct face *x = del234(lightable_faces_sorted, f); \
-        assert(x); \
-        x = del234(lightable_faces_gettable, f); \
-        assert(x); \
-    } while (0)
+    /* Initialise the lists of lightable and darkable faces.  This is
+     * slightly different from the code inside the while-loop, because we need
+     * to check every face of the board (the grid structure does not keep a
+     * list of the infinite face's neighbours). */
+    for (i = 0; i < num_faces; i++) {
+        grid_face *f = g->faces + i;
+        struct face_score *fs = face_scores + i;
+        if (board[i] != FACE_GREY) continue;
+        /* We need the full colourability check here, it's not enough simply
+         * to check neighbourhood.  On some grids, a neighbour of the infinite
+         * face is not necessarily darkable. */
+        if (can_colour_face(g, board, i, FACE_BLACK)) {
+            fs->black_score = face_score(g, board, f, FACE_BLACK);
+            add234(darkable_faces_sorted, fs);
+        }
+        if (can_colour_face(g, board, i, FACE_WHITE)) {
+            fs->white_score = face_score(g, board, f, FACE_WHITE);
+            add234(lightable_faces_sorted, fs);
+        }
+    }
 
-    /* Light faces one at a time until the board is interesting enough */
+    /* Colour faces one at a time until no more faces are colourable. */
     while (TRUE)
     {
-        if (first_time) {
-            first_time = FALSE;
-            /* lightable_faces_xxx are empty, so start the process by
-             * lighting up the middle face.  These tree234s should
-             * remain empty, consistent with what would happen if
-             * first_time were FALSE. */
-            board[g->middle_face - g->faces] = FACE_LIT;
-            face = snew(struct face);
-            face->f = g->middle_face;
-            /* No need to initialise any more of 'face' here, no other fields
-             * are used in this case. */
-        } else {
-            /* We have count234(lightable_faces_gettable) possibilities, and in
-             * lightable_faces_sorted they are sorted with the most desirable
-             * first. */
-            c = count234(lightable_faces_sorted);
-            if (c == 0)
-                break;
-            assert(c == count234(lightable_faces_gettable));
-
-            /* Check that the best face available is any good */
-            face = (struct face *)index234(lightable_faces_sorted, 0);
-            assert(face);
-
-            /*
-             * The situation for a general grid is slightly different from
-             * a square grid.  Decreasing the perimeter should be allowed
-             * sometimes (think about creating a hexagon of lit triangles,
-             * for example).  For if it were _never_ done, then the user would
-             * be able to illicitly deduce certain things.  So we do it
-             * sometimes but not always.
-             */
-            if (face->score <= 0 && random_upto(rs, 2) == 0) {
-                break;
-            }
+        enum face_colour colour;
+        struct face_score *fs_white, *fs_black;
+        int c_lightable = count234(lightable_faces_sorted);
+        int c_darkable = count234(darkable_faces_sorted);
+        if (c_lightable == 0) {
+            /* No more lightable faces.  Because of how the algorithm
+             * works, there should be no more darkable faces either. */
+            assert(c_darkable == 0);
+            break;
+        }
 
-            assert(face->f); /* not the infinite face */
-            assert(FACE_LIT_STATE(face->f) == FACE_UNLIT);
+        fs_white = (struct face_score *)index234(lightable_faces_sorted, 0);
+        fs_black = (struct face_score *)index234(darkable_faces_sorted, 0);
 
-            /* Update data structures */
-            /* Light up the face and remove it from the lists */
-            board[face->f - g->faces] = FACE_LIT;
-            REMOVE_FACE(face);
-        }
+        /* Choose a colour, and colour the best available face
+         * with that colour. */
+        colour = random_upto(rs, 2) ? FACE_WHITE : FACE_BLACK;
 
-        /* The face we've just lit up potentially affects the lightability
-         * of any neighbouring faces (touching at a corner or edge).  So the
-         * search needs to be conducted around all faces touching the one
-         * we've just lit.  Iterate over its corners, then over each corner's
-         * faces. */
-        for (i = 0; i < face->f->order; i++) {
-            grid_dot *d = face->f->dots[i];
+        if (colour == FACE_WHITE)
+            fs = fs_white;
+        else
+            fs = fs_black;
+        assert(fs);
+        i = fs - face_scores;
+        assert(board[i] == FACE_GREY);
+        board[i] = colour;
+
+        /* Remove this newly-coloured face from the lists.  These lists should
+         * only contain grey faces. */
+        del234(lightable_faces_sorted, fs);
+        del234(darkable_faces_sorted, fs);
+
+        /* Remember which face we've just coloured */
+        cur_face = g->faces + i;
+
+        /* The face we've just coloured potentially affects the colourability
+         * and the scores of any neighbouring faces (touching at a corner or
+         * edge).  So the search needs to be conducted around all faces
+         * touching the one we've just lit.  Iterate over its corners, then
+         * over each corner's faces.  For each such face, we remove it from
+         * the lists, recalculate any scores, then add it back to the lists
+         * (depending on whether it is lightable, darkable or both). */
+        for (i = 0; i < cur_face->order; i++) {
+            grid_dot *d = cur_face->dots[i];
             for (j = 0; j < d->order; j++) {
-                grid_face *f2 = d->faces[j];
-                if (f2 == NULL)
+                grid_face *f = d->faces[j];
+                int fi; /* face index of f */
+
+                if (f == NULL)
                     continue;
-                if (f2 == face->f)
+                if (f == cur_face)
                     continue;
-                face_pos.f = f2;
-                tmpface = find234(lightable_faces_gettable, &face_pos, NULL);
-                if (tmpface) {
-                    assert(tmpface->f == face_pos.f);
-                    assert(FACE_LIT_STATE(tmpface->f) == FACE_UNLIT);
-                    REMOVE_FACE(tmpface);
-                } else {
-                    tmpface = snew(struct face);
-                    tmpface->f = face_pos.f;
-                    tmpface->random = random_bits(rs, 31);
+                
+                /* If the face is already coloured, it won't be on our
+                 * lightable/darkable lists anyway, so we can skip it without 
+                 * bothering with the removal step. */
+                if (FACE_COLOUR(f) != FACE_GREY) continue; 
+
+                /* Find the face index and face_score* corresponding to f */
+                fi = f - g->faces;                
+                fs = face_scores + fi;
+
+                /* Remove from lightable list if it's in there.  We do this,
+                 * even if it is still lightable, because the score might
+                 * be different, and we need to remove-then-add to maintain
+                 * correct sort order. */
+                del234(lightable_faces_sorted, fs);
+                if (can_colour_face(g, board, fi, FACE_WHITE)) {
+                    fs->white_score = face_score(g, board, f, FACE_WHITE);
+                    add234(lightable_faces_sorted, fs);
                 }
-                tmpface->score = face_score(g, board, tmpface->f);
-
-                if (IS_LIGHTING_CANDIDATE(tmpface->f - g->faces)) {
-                    ADD_FACE(tmpface);
-                } else {
-                    sfree(tmpface);
+                /* Do the same for darkable list. */
+                del234(darkable_faces_sorted, fs);
+                if (can_colour_face(g, board, fi, FACE_BLACK)) {
+                    fs->black_score = face_score(g, board, f, FACE_BLACK);
+                    add234(darkable_faces_sorted, fs);
                 }
             }
         }
-        sfree(face);
     }
 
     /* Clean up */
-    while ((face = delpos234(lightable_faces_gettable, 0)) != NULL)
-        sfree(face);
-    freetree234(lightable_faces_gettable);
     freetree234(lightable_faces_sorted);
+    freetree234(darkable_faces_sorted);
+    sfree(face_scores);
+
+    /* The next step requires a shuffled list of all faces */
+    face_list = snewn(num_faces, int);
+    for (i = 0; i < num_faces; ++i) {
+        face_list[i] = i;
+    }
+    shuffle(face_list, num_faces, sizeof(int), rs);
+
+    /* The above loop-generation algorithm can often leave large clumps
+     * of faces of one colour.  In extreme cases, the resulting path can be 
+     * degenerate and not very satisfying to solve.
+     * This next step alleviates this problem:
+     * Go through the shuffled list, and flip the colour of any face we can
+     * legally flip, and which is adjacent to only one face of the opposite
+     * colour - this tends to grow 'tendrils' into any clumps.
+     * Repeat until we can find no more faces to flip.  This will
+     * eventually terminate, because each flip increases the loop's
+     * perimeter, which cannot increase for ever.
+     * The resulting path will have maximal loopiness (in the sense that it
+     * cannot be improved "locally".  Unfortunately, this allows a player to
+     * make some illicit deductions.  To combat this (and make the path more
+     * interesting), we do one final pass making random flips. */
+
+    /* Set to TRUE for final pass */
+    do_random_pass = FALSE;
+
+    while (TRUE) {
+        /* Remember whether a flip occurred during this pass */
+        int flipped = FALSE;
+
+        for (i = 0; i < num_faces; ++i) {
+            int j = face_list[i];
+            enum face_colour opp =
+                (board[j] == FACE_WHITE) ? FACE_BLACK : FACE_WHITE;
+            if (can_colour_face(g, board, j, opp)) {
+                grid_face *face = g->faces +j;
+                if (do_random_pass) {
+                    /* final random pass */
+                    if (!random_upto(rs, 10))
+                        board[j] = opp;
+                } else {
+                    /* normal pass - flip when neighbour count is 1 */
+                    if (face_num_neighbours(g, board, face, opp) == 1) {
+                        board[j] = opp;
+                        flipped = TRUE;
+                    }
+                }
+            }
+        }
+
+        if (do_random_pass) break;
+        if (!flipped) do_random_pass = TRUE;
+     }
+
+    sfree(face_list);
 
     /* Fill out all the clues by initialising to 0, then iterating over
      * all edges and incrementing each clue as we find edges that border
-     * between LIT/UNLIT faces */
+     * between BLACK/WHITE faces.  While we're at it, we verify that the
+     * algorithm does work, and there aren't any GREY faces still there. */
     memset(clues, 0, num_faces);
     for (i = 0; i < g->num_edges; i++) {
         grid_edge *e = g->edges + i;
         grid_face *f1 = e->face1;
         grid_face *f2 = e->face2;
-        if (FACE_LIT_STATE(f1) != FACE_LIT_STATE(f2)) {
+        enum face_colour c1 = FACE_COLOUR(f1);
+        enum face_colour c2 = FACE_COLOUR(f2);
+        assert(c1 != FACE_GREY);
+        assert(c2 != FACE_GREY);
+        if (c1 != c2) {
             if (f1) clues[f1 - g->faces]++;
             if (f2) clues[f2 - g->faces]++;
         }