Patch from Lambros to improve the generality of path-generation. In

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]
This commit is contained in:
Simon Tatham
2008-11-04 21:39:59 +00:00
parent a35c660284
commit f4bd45e7b9

483
loopy.c
View File

@ -73,6 +73,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stddef.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
@ -1219,33 +1220,34 @@ static int face_setall(solver_state *sstate, int face,
* Loop generation and clue removal * 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 * 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 * maximise that quantity so will bias our random selection of faces to
* light towards those with high scores */ * colour those with high scores */
struct face { struct face_score {
int score; int white_score;
int black_score;
unsigned long random; 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) static int generic_sort_cmpfn(void *v1, void *v2, size_t offset)
{ {
struct face *f1 = v1; struct face_score *f1 = v1;
struct face *f2 = v2; struct face_score *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)
{
struct face *f1 = v1;
struct face *f2 = v2;
int r; int r;
r = f2->score - f1->score; r = *(int *)((char *)f2 + offset) - *(int *)((char *)f1 + offset);
if (r) { if (r) {
return 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 * It's _just_ possible that two faces might have been given
* the same random value. In that situation, fall back to * 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. * This introduces a tiny directional bias, but not a significant one.
*/ */
return get_face_cmpfn(f1, f2); return f1 - f2;
} }
enum { FACE_LIT, FACE_UNLIT }; static int white_sort_cmpfn(void *v1, void *v2)
{
return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,white_score));
}
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. */ /* face should be of type grid_face* here. */
#define FACE_LIT_STATE(face) \ #define FACE_COLOUR(face) \
( (face) == NULL ? FACE_UNLIT : \ ( (face) == NULL ? FACE_BLACK : \
board[(face) - g->faces] ) board[(face) - g->faces] )
/* 'board' is an array of these enums, indicating which faces are /* 'board' is an array of these enums, indicating which faces are
* currently lit. Returns whether it's legal to light up the * currently black/white/grey. 'colour' is FACE_WHITE or FACE_BLACK.
* given face. */ * Returns whether it's legal to colour the given face with this colour. */
static int can_light_face(grid *g, char* board, int face_index) static int can_colour_face(grid *g, char* board, int face_index,
enum face_colour colour)
{ {
int i, j; int i, j;
grid_face *test_face = g->faces + face_index; grid_face *test_face = g->faces + face_index;
grid_face *starting_face, *current_face; grid_face *starting_face, *current_face;
int transitions; int transitions;
int current_state, s; int current_state, s; /* booleans: equal or not-equal to 'colour' */
int found_lit_neighbour = FALSE; int found_same_coloured_neighbour = FALSE;
assert(board[face_index] == FACE_UNLIT); assert(board[face_index] != colour);
/* Can only consider a face for lighting if it's adjacent to an /* Can only consider a face for colouring if it's adjacent to a face
* already lit face. */ * with the same colour. */
for (i = 0; i < test_face->order; i++) { for (i = 0; i < test_face->order; i++) {
grid_edge *e = test_face->edges[i]; grid_edge *e = test_face->edges[i];
grid_face *f = (e->face1 == test_face) ? e->face2 : e->face1; grid_face *f = (e->face1 == test_face) ? e->face2 : e->face1;
if (FACE_LIT_STATE(f) == FACE_LIT) { if (FACE_COLOUR(f) == colour) {
found_lit_neighbour = TRUE; found_same_coloured_neighbour = TRUE;
break; break;
} }
} }
if (!found_lit_neighbour) if (!found_same_coloured_neighbour)
return FALSE; return FALSE;
/* Need to avoid creating a loop of lit faces around some unlit faces. /* Need to avoid creating a loop of faces of this colour around some
* Also need to avoid meeting another lit face at a corner, with * differently-coloured faces.
* unlit faces in between. Here's a simple test that (I believe) takes * Also need to avoid meeting a same-coloured face at a corner, with
* care of both these conditions: * 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 * Take the circular path formed by this face's edges, and inflate it
* slightly outwards. Imagine walking around this path and consider * slightly outwards. Imagine walking around this path and consider
* the faces that you visit in sequence. This will include all faces * 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. * 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 * Count the number of 'colour'/not-'colour' transitions you encounter, as
* along the complete loop. This will obviously turn out to be an even * you walk along the complete loop. This will obviously turn out to be
* number. * an even number.
* If 0, we're either in a completely unlit zone, or this face is a hole * If 0, we're either in the middle of an "island" of this colour (should
* in a completely lit zone. If the former, we would create a brand new * be impossible as we're not supposed to create black or white loops),
* island by lighting this face. And the latter ought to be impossible - * or we're about to start a new island - also not allowed.
* it would mean there's already a lit loop, so something went wrong * If 4 or greater, there are too many separate coloured regions touching
* earlier. * this face, and colouring it would create a loop or a corner-violation.
* 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.
* The only allowed case is when the count is exactly 2. */ * The only allowed case is when the count is exactly 2. */
/* i points to a dot around the test face. /* 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; current_face = starting_face;
transitions = 0; transitions = 0;
current_state = FACE_LIT_STATE(current_face); current_state = (FACE_COLOUR(current_face) == colour);
do { do {
/* Advance to next face. /* 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 */ /* (i,j) are now advanced to next face */
current_face = test_face->dots[i]->faces[j]; current_face = test_face->dots[i]->faces[j];
s = FACE_LIT_STATE(current_face); s = (FACE_COLOUR(current_face) == colour);
if (s != current_state) { if (s != current_state) {
++transitions; ++transitions;
current_state = s; current_state = s;
@ -1376,53 +1388,121 @@ static int can_light_face(grid *g, char* board, int face_index)
return (transitions == 2) ? TRUE : FALSE; return (transitions == 2) ? TRUE : FALSE;
} }
/* The 'score' of a face reflects its current desirability for selection /* Count the number of neighbours of 'face', having colour 'colour' */
* as the next face to light. We want to encourage moving into uncharted static int face_num_neighbours(grid *g, char *board, grid_face *face,
* areas so we give scores according to how many of the face's neighbours enum face_colour colour)
* are currently unlit. */
static int face_score(grid *g, char *board, grid_face *face)
{ {
/* Simple formula: score = neighbours unlit - neighbours lit */ int colour_count = 0;
int lit_count = 0, unlit_count = 0;
int i; int i;
grid_face *f; grid_face *f;
grid_edge *e; grid_edge *e;
for (i = 0; i < face->order; i++) { for (i = 0; i < face->order; i++) {
e = face->edges[i]; e = face->edges[i];
f = (e->face1 == face) ? e->face2 : e->face1; f = (e->face1 == face) ? e->face2 : e->face1;
if (FACE_LIT_STATE(f) == FACE_LIT) if (FACE_COLOUR(f) == colour)
++lit_count; ++colour_count;
else
++unlit_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) static void add_full_clues(game_state *state, random_state *rs)
{ {
signed char *clues = state->clues; signed char *clues = state->clues;
char *board; char *board;
grid *g = state->game_grid; grid *g = state->game_grid;
int i, j, c; int i, j;
int num_faces = g->num_faces; int num_faces = g->num_faces;
int first_time = TRUE; struct face_score *face_scores; /* Array of face_score objects */
struct face_score *fs; /* Points somewhere in the above list */
struct face *face, *tmpface; struct grid_face *cur_face;
struct face face_pos; tree234 *lightable_faces_sorted;
tree234 *darkable_faces_sorted;
/* These will contain exactly the same information, sorted into different int *face_list;
* orders */ int do_random_pass;
tree234 *lightable_faces_sorted, *lightable_faces_gettable;
#define IS_LIGHTING_CANDIDATE(i) \
(board[i] == FACE_UNLIT && \
can_light_face(g, board, i))
board = snewn(num_faces, char); board = snewn(num_faces, char);
/* Make a board */ /* 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 need a way of favouring faces that will increase our loopiness.
* We do this by maintaining a list of all candidate faces sorted by * 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 * Yes, this means we will be biased towards particular random faces in
* any one run but that doesn't actually matter. */ * any one run but that doesn't actually matter. */
lightable_faces_sorted = newtree234(face_sort_cmpfn); lightable_faces_sorted = newtree234(white_sort_cmpfn);
lightable_faces_gettable = newtree234(get_face_cmpfn); darkable_faces_sorted = newtree234(black_sort_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)
#define REMOVE_FACE(f) \ /* Initialise the lists of lightable and darkable faces. This is
do { \ * slightly different from the code inside the while-loop, because we need
struct face *x = del234(lightable_faces_sorted, f); \ * to check every face of the board (the grid structure does not keep a
assert(x); \ * list of the infinite face's neighbours). */
x = del234(lightable_faces_gettable, f); \ for (i = 0; i < num_faces; i++) {
assert(x); \ grid_face *f = g->faces + i;
} while (0) 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) while (TRUE)
{ {
if (first_time) { enum face_colour colour;
first_time = FALSE; struct face_score *fs_white, *fs_black;
/* lightable_faces_xxx are empty, so start the process by int c_lightable = count234(lightable_faces_sorted);
* lighting up the middle face. These tree234s should int c_darkable = count234(darkable_faces_sorted);
* remain empty, consistent with what would happen if if (c_lightable == 0) {
* first_time were FALSE. */ /* No more lightable faces. Because of how the algorithm
board[g->middle_face - g->faces] = FACE_LIT; * works, there should be no more darkable faces either. */
face = snew(struct face); assert(c_darkable == 0);
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; break;
} }
assert(face->f); /* not the infinite face */ fs_white = (struct face_score *)index234(lightable_faces_sorted, 0);
assert(FACE_LIT_STATE(face->f) == FACE_UNLIT); fs_black = (struct face_score *)index234(darkable_faces_sorted, 0);
/* Update data structures */ /* Choose a colour, and colour the best available face
/* Light up the face and remove it from the lists */ * with that colour. */
board[face->f - g->faces] = FACE_LIT; colour = random_upto(rs, 2) ? FACE_WHITE : FACE_BLACK;
REMOVE_FACE(face);
}
/* The face we've just lit up potentially affects the lightability if (colour == FACE_WHITE)
* of any neighbouring faces (touching at a corner or edge). So the fs = fs_white;
* search needs to be conducted around all faces touching the one else
* we've just lit. Iterate over its corners, then over each corner's fs = fs_black;
* faces. */ assert(fs);
for (i = 0; i < face->f->order; i++) { i = fs - face_scores;
grid_dot *d = face->f->dots[i]; 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++) { for (j = 0; j < d->order; j++) {
grid_face *f2 = d->faces[j]; grid_face *f = d->faces[j];
if (f2 == NULL) int fi; /* face index of f */
continue;
if (f2 == face->f)
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);
}
tmpface->score = face_score(g, board, tmpface->f);
if (IS_LIGHTING_CANDIDATE(tmpface->f - g->faces)) { if (f == NULL)
ADD_FACE(tmpface); continue;
} else { if (f == cur_face)
sfree(tmpface); continue;
/* 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);
}
/* 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 */ /* Clean up */
while ((face = delpos234(lightable_faces_gettable, 0)) != NULL)
sfree(face);
freetree234(lightable_faces_gettable);
freetree234(lightable_faces_sorted); 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 /* Fill out all the clues by initialising to 0, then iterating over
* all edges and incrementing each clue as we find edges that border * 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); memset(clues, 0, num_faces);
for (i = 0; i < g->num_edges; i++) { for (i = 0; i < g->num_edges; i++) {
grid_edge *e = g->edges + i; grid_edge *e = g->edges + i;
grid_face *f1 = e->face1; grid_face *f1 = e->face1;
grid_face *f2 = e->face2; 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 (f1) clues[f1 - g->faces]++;
if (f2) clues[f2 - g->faces]++; if (f2) clues[f2 - g->faces]++;
} }