#include <stdio.h>
#include <stdlib.h>
+#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
COL_HIGHLIGHT,
COL_MISTAKE,
COL_SATISFIED,
+ COL_FAINT,
NCOLOURS
};
* YES, NO or UNKNOWN */
char *lines;
+ unsigned char *line_errors;
+
int solved;
int cheated;
};
/* ------ Solver state ------ */
-typedef struct normal {
- /* For each dline, store a bitmask for whether we know:
- * (bit 0) at least one is YES
- * (bit 1) at most one is YES */
- char *dlines;
-} normal_mode_state;
-
-typedef struct hard {
- int *linedsf;
-} hard_mode_state;
-
typedef struct solver_state {
game_state *state;
enum solver_status solver_status;
* looplen of 1 means there are no lines to a particular dot */
int *looplen;
+ /* Difficulty level of solver. Used by solver functions that want to
+ * vary their behaviour depending on the requested difficulty level. */
+ int diff;
+
/* caches */
char *dot_yes_count;
char *dot_no_count;
char *dot_solved, *face_solved;
int *dotdsf;
- normal_mode_state *normal;
- hard_mode_state *hard;
+ /* Information for Normal level deductions:
+ * For each dline, store a bitmask for whether we know:
+ * (bit 0) at least one is YES
+ * (bit 1) at most one is YES */
+ char *dlines;
+
+ /* Hard level information */
+ int *linedsf;
} solver_state;
/*
*/
#define DIFFLIST(A) \
- A(EASY,Easy,e,easy_mode_deductions) \
- A(NORMAL,Normal,n,normal_mode_deductions) \
- A(HARD,Hard,h,hard_mode_deductions)
-#define ENUM(upper,title,lower,fn) DIFF_ ## upper,
-#define TITLE(upper,title,lower,fn) #title,
-#define ENCODE(upper,title,lower,fn) #lower
-#define CONFIG(upper,title,lower,fn) ":" #title
-#define SOLVER_FN_DECL(upper,title,lower,fn) static int fn(solver_state *);
-#define SOLVER_FN(upper,title,lower,fn) &fn,
+ A(EASY,Easy,e) \
+ A(NORMAL,Normal,n) \
+ A(TRICKY,Tricky,t) \
+ A(HARD,Hard,h)
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
enum { DIFFLIST(ENUM) DIFF_MAX };
static char const *const diffnames[] = { DIFFLIST(TITLE) };
static char const diffchars[] = DIFFLIST(ENCODE);
#define DIFFCONFIG DIFFLIST(CONFIG)
-DIFFLIST(SOLVER_FN_DECL)
-static int (*(solver_fns[]))(solver_state *) = { DIFFLIST(SOLVER_FN) };
+
+/*
+ * Solver routines, sorted roughly in order of computational cost.
+ * The solver will run the faster deductions first, and slower deductions are
+ * only invoked when the faster deductions are unable to make progress.
+ * Each function is associated with a difficulty level, so that the generated
+ * puzzles are solvable by applying only the functions with the chosen
+ * difficulty level or lower.
+ */
+#define SOLVERLIST(A) \
+ A(trivial_deductions, DIFF_EASY) \
+ A(dline_deductions, DIFF_NORMAL) \
+ A(linedsf_deductions, DIFF_HARD) \
+ A(loop_deductions, DIFF_EASY)
+#define SOLVER_FN_DECL(fn,diff) static int fn(solver_state *);
+#define SOLVER_FN(fn,diff) &fn,
+#define SOLVER_DIFF(fn,diff) diff,
+SOLVERLIST(SOLVER_FN_DECL)
+static int (*(solver_fns[]))(solver_state *) = { SOLVERLIST(SOLVER_FN) };
+static int const solver_diffs[] = { SOLVERLIST(SOLVER_DIFF) };
+const int NUM_SOLVERS = sizeof(solver_diffs)/sizeof(*solver_diffs);
struct game_params {
int w, h;
grid *game_grid;
};
+/* line_drawstate is the same as line_state, but with the extra ERROR
+ * possibility. The drawing code copies line_state to line_drawstate,
+ * except in the case that the line is an error. */
enum line_state { LINE_YES, LINE_UNKNOWN, LINE_NO };
+enum line_drawstate { DS_LINE_YES, DS_LINE_UNKNOWN,
+ DS_LINE_NO, DS_LINE_ERROR };
#define OPP(line_state) \
(2 - line_state)
static char *validate_desc(game_params *params, char *desc);
static int dot_order(const game_state* state, int i, char line_type);
static int face_order(const game_state* state, int i, char line_type);
-static solver_state *solve_game_rec(const solver_state *sstate,
- int diff);
+static solver_state *solve_game_rec(const solver_state *sstate);
#ifdef DEBUG_CACHES
static void check_caches(const solver_state* sstate);
/* ------- List of grid generators ------- */
#define GRIDLIST(A) \
- A(Squares,grid_new_square) \
- A(Triangular,grid_new_triangular) \
- A(Honeycomb,grid_new_honeycomb) \
- A(Snub-Square,grid_new_snubsquare) \
- A(Cairo,grid_new_cairo) \
- A(Great-Hexagonal,grid_new_greathexagonal) \
- A(Octagonal,grid_new_octagonal) \
- A(Kites,grid_new_kites)
-
-#define GRID_NAME(title,fn) #title,
-#define GRID_CONFIG(title,fn) ":" #title
-#define GRID_FN(title,fn) &fn,
+ A(Squares,grid_new_square,3,3) \
+ A(Triangular,grid_new_triangular,3,3) \
+ A(Honeycomb,grid_new_honeycomb,3,3) \
+ A(Snub-Square,grid_new_snubsquare,3,3) \
+ A(Cairo,grid_new_cairo,3,4) \
+ A(Great-Hexagonal,grid_new_greathexagonal,3,3) \
+ A(Octagonal,grid_new_octagonal,3,3) \
+ A(Kites,grid_new_kites,3,3)
+
+#define GRID_NAME(title,fn,amin,omin) #title,
+#define GRID_CONFIG(title,fn,amin,omin) ":" #title
+#define GRID_FN(title,fn,amin,omin) &fn,
+#define GRID_SIZES(title,fn,amin,omin) \
+ {amin, omin, \
+ "Width and height for this grid type must both be at least " #amin, \
+ "At least one of width and height for this grid type must be at least " #omin,},
static char const *const gridnames[] = { GRIDLIST(GRID_NAME) };
#define GRID_CONFIGS GRIDLIST(GRID_CONFIG)
static grid * (*(grid_fns[]))(int w, int h) = { GRIDLIST(GRID_FN) };
#define NUM_GRID_TYPES (sizeof(grid_fns) / sizeof(grid_fns[0]))
+static const struct {
+ int amin, omin;
+ char *aerr, *oerr;
+} grid_size_limits[] = { GRIDLIST(GRID_SIZES) };
/* Generates a (dynamically allocated) new grid, according to the
* type and size requested in params. Does nothing if the grid is already
ret->lines = snewn(state->game_grid->num_edges, char);
memcpy(ret->lines, state->lines, state->game_grid->num_edges);
+ ret->line_errors = snewn(state->game_grid->num_edges, unsigned char);
+ memcpy(ret->line_errors, state->line_errors, state->game_grid->num_edges);
+
ret->grid_type = state->grid_type;
return ret;
}
grid_free(state->game_grid);
sfree(state->clues);
sfree(state->lines);
+ sfree(state->line_errors);
sfree(state);
}
}
ret->state = dup_game(state);
ret->solver_status = SOLVER_INCOMPLETE;
+ ret->diff = diff;
ret->dotdsf = snew_dsf(num_dots);
ret->looplen = snewn(num_dots, int);
memset(ret->face_no_count, 0, num_faces);
if (diff < DIFF_NORMAL) {
- ret->normal = NULL;
+ ret->dlines = NULL;
} else {
- ret->normal = snew(normal_mode_state);
- ret->normal->dlines = snewn(2*num_edges, char);
- memset(ret->normal->dlines, 0, 2*num_edges);
+ ret->dlines = snewn(2*num_edges, char);
+ memset(ret->dlines, 0, 2*num_edges);
}
if (diff < DIFF_HARD) {
- ret->hard = NULL;
+ ret->linedsf = NULL;
} else {
- ret->hard = snew(hard_mode_state);
- ret->hard->linedsf = snew_dsf(state->game_grid->num_edges);
+ ret->linedsf = snew_dsf(state->game_grid->num_edges);
}
return ret;
sfree(sstate->face_yes_count);
sfree(sstate->face_no_count);
- if (sstate->normal) {
- sfree(sstate->normal->dlines);
- sfree(sstate->normal);
- }
-
- if (sstate->hard) {
- sfree(sstate->hard->linedsf);
- sfree(sstate->hard);
- }
+ /* OK, because sfree(NULL) is a no-op */
+ sfree(sstate->dlines);
+ sfree(sstate->linedsf);
sfree(sstate);
}
ret->state = state = dup_game(sstate->state);
ret->solver_status = sstate->solver_status;
+ ret->diff = sstate->diff;
ret->dotdsf = snewn(num_dots, int);
ret->looplen = snewn(num_dots, int);
ret->face_no_count = snewn(num_faces, char);
memcpy(ret->face_no_count, sstate->face_no_count, num_faces);
- if (sstate->normal) {
- ret->normal = snew(normal_mode_state);
- ret->normal->dlines = snewn(2*num_edges, char);
- memcpy(ret->normal->dlines, sstate->normal->dlines,
+ if (sstate->dlines) {
+ ret->dlines = snewn(2*num_edges, char);
+ memcpy(ret->dlines, sstate->dlines,
2*num_edges);
} else {
- ret->normal = NULL;
+ ret->dlines = NULL;
}
- if (sstate->hard) {
- ret->hard = snew(hard_mode_state);
- ret->hard->linedsf = snewn(num_edges, int);
- memcpy(ret->hard->linedsf, sstate->hard->linedsf,
+ if (sstate->linedsf) {
+ ret->linedsf = snewn(num_edges, int);
+ memcpy(ret->linedsf, sstate->linedsf,
num_edges * sizeof(int));
} else {
- ret->hard = NULL;
+ ret->linedsf = NULL;
}
return ret;
static char *validate_params(game_params *params, int full)
{
- if (params->w < 3 || params->h < 3)
- return "Width and height must both be at least 3";
if (params->type < 0 || params->type >= NUM_GRID_TYPES)
return "Illegal grid type";
+ if (params->w < grid_size_limits[params->type].amin ||
+ params->h < grid_size_limits[params->type].amin)
+ return grid_size_limits[params->type].aerr;
+ if (params->w < grid_size_limits[params->type].omin &&
+ params->h < grid_size_limits[params->type].omin)
+ return grid_size_limits[params->type].oerr;
/*
* This shouldn't be able to happen at all, since decode_params
ret[COL_SATISFIED * 3 + 1] = 0.0F;
ret[COL_SATISFIED * 3 + 2] = 0.0F;
+ /* We want the faint lines to be a bit darker than the background.
+ * Except if the background is pretty dark already; then it ought to be a
+ * bit lighter. Oy vey.
+ */
+ ret[COL_FAINT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.9F;
+ ret[COL_FAINT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.9F;
+ ret[COL_FAINT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.9F;
+
*ncolours = NCOLOURS;
return ret;
}
assert(i < sstate->state->game_grid->num_edges);
assert(j < sstate->state->game_grid->num_edges);
- i = edsf_canonify(sstate->hard->linedsf, i, &inv_tmp);
+ i = edsf_canonify(sstate->linedsf, i, &inv_tmp);
inverse ^= inv_tmp;
- j = edsf_canonify(sstate->hard->linedsf, j, &inv_tmp);
+ j = edsf_canonify(sstate->linedsf, j, &inv_tmp);
inverse ^= inv_tmp;
- edsf_merge(sstate->hard->linedsf, i, j, inverse);
+ edsf_merge(sstate->linedsf, i, j, inverse);
#ifdef SHOW_WORKING
if (i != j) {
* 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;
}
/*
* 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));
+}
+
+static int black_sort_cmpfn(void *v1, void *v2)
+{
+ return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,black_score));
}
-enum { FACE_LIT, FACE_UNLIT };
+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;
+ grid_dot *starting_dot;
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.
* test_face->dots[i]->faces[j]
* We assume dots go clockwise around the test face,
* and faces go clockwise around dots. */
+
+ /*
+ * The end condition is slightly fiddly. In sufficiently strange
+ * degenerate grids, our test face may be adjacent to the same
+ * other face multiple times (typically if it's the exterior
+ * face). Consider this, in particular:
+ *
+ * +--+
+ * | |
+ * +--+--+
+ * | | |
+ * +--+--+
+ *
+ * The bottom left face there is adjacent to the exterior face
+ * twice, so we can't just terminate our iteration when we reach
+ * the same _face_ we started at. Furthermore, we can't
+ * condition on having the same (i,j) pair either, because
+ * several (i,j) pairs identify the bottom left contiguity with
+ * the exterior face! We canonicalise the (i,j) pair by taking
+ * one step around before we set the termination tracking.
+ */
+
i = j = 0;
- starting_face = test_face->dots[0]->faces[0];
- if (starting_face == test_face) {
+ current_face = test_face->dots[0]->faces[0];
+ if (current_face == test_face) {
j = 1;
- starting_face = test_face->dots[0]->faces[1];
+ current_face = test_face->dots[0]->faces[1];
}
- current_face = starting_face;
transitions = 0;
- current_state = FACE_LIT_STATE(current_face);
-
- do {
+ current_state = (FACE_COLOUR(current_face) == colour);
+ starting_dot = NULL;
+ starting_face = NULL;
+ while (TRUE) {
/* Advance to next face.
* Need to loop here because it might take several goes to
* find it. */
}
/* (i,j) are now advanced to next face */
current_face = test_face->dots[i]->faces[j];
- s = FACE_LIT_STATE(current_face);
- if (s != current_state) {
- ++transitions;
- current_state = s;
- if (transitions > 2)
- return FALSE; /* no point in continuing */
+ s = (FACE_COLOUR(current_face) == colour);
+ if (!starting_dot) {
+ starting_dot = test_face->dots[i];
+ starting_face = current_face;
+ current_state = s;
+ } else {
+ if (s != current_state) {
+ ++transitions;
+ current_state = s;
+ if (transitions > 2)
+ break;
+ }
+ if (test_face->dots[i] == starting_dot &&
+ current_face == starting_face)
+ break;
}
- } while (current_face != starting_face);
+ }
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);
+ face_scores[i].black_score = face_scores[i].white_score = 0;
+ }
+
+ /* 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
* 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 && c_darkable == 0) {
+ /* No more faces we can use at all. */
+ break;
+ }
+ assert(c_lightable != 0 && c_darkable != 0);
- 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]++;
}
solver_state *sstate_new;
solver_state *sstate = new_solver_state((game_state *)state, diff);
- sstate_new = solve_game_rec(sstate, diff);
+ sstate_new = solve_game_rec(sstate);
assert(sstate_new->solver_status != SOLVER_MISTAKE);
ret = (sstate_new->solver_status == SOLVER_SOLVED);
g->refcount++;
state->clues = snewn(g->num_faces, signed char);
state->lines = snewn(g->num_edges, char);
+ state->line_errors = snewn(g->num_edges, unsigned char);
state->grid_type = params->type;
newboard_please:
memset(state->lines, LINE_UNKNOWN, g->num_edges);
+ memset(state->line_errors, 0, g->num_edges);
state->solved = state->cheated = FALSE;
state->clues = snewn(num_faces, signed char);
state->lines = snewn(num_edges, char);
+ state->line_errors = snewn(num_edges, unsigned char);
state->solved = state->cheated = FALSE;
}
memset(state->lines, LINE_UNKNOWN, num_edges);
-
+ memset(state->line_errors, 0, num_edges);
return state;
}
-enum { LOOP_NONE=0, LOOP_SOLN, LOOP_NOT_SOLN };
+/* Calculates the line_errors data, and checks if the current state is a
+ * solution */
+static int check_completion(game_state *state)
+{
+ grid *g = state->game_grid;
+ int *dsf;
+ int num_faces = g->num_faces;
+ int i;
+ int infinite_area, finite_area;
+ int loops_found = 0;
+ int found_edge_not_in_loop = FALSE;
+
+ memset(state->line_errors, 0, g->num_edges);
+
+ /* LL implementation of SGT's idea:
+ * A loop will partition the grid into an inside and an outside.
+ * If there is more than one loop, the grid will be partitioned into
+ * even more distinct regions. We can therefore track equivalence of
+ * faces, by saying that two faces are equivalent when there is a non-YES
+ * edge between them.
+ * We could keep track of the number of connected components, by counting
+ * the number of dsf-merges that aren't no-ops.
+ * But we're only interested in 3 separate cases:
+ * no loops, one loop, more than one loop.
+ *
+ * No loops: all faces are equivalent to the infinite face.
+ * One loop: only two equivalence classes - finite and infinite.
+ * >= 2 loops: there are 2 distinct finite regions.
+ *
+ * So we simply make two passes through all the edges.
+ * In the first pass, we dsf-merge the two faces bordering each non-YES
+ * edge.
+ * In the second pass, we look for YES-edges bordering:
+ * a) two non-equivalent faces.
+ * b) two non-equivalent faces, and one of them is part of a different
+ * finite area from the first finite area we've seen.
+ *
+ * An occurrence of a) means there is at least one loop.
+ * An occurrence of b) means there is more than one loop.
+ * Edges satisfying a) are marked as errors.
+ *
+ * While we're at it, we set a flag if we find a YES edge that is not
+ * part of a loop.
+ * This information will help decide, if there's a single loop, whether it
+ * is a candidate for being a solution (that is, all YES edges are part of
+ * this loop).
+ *
+ * If there is a candidate loop, we then go through all clues and check
+ * they are all satisfied. If so, we have found a solution and we can
+ * unmark all line_errors.
+ */
+
+ /* Infinite face is at the end - its index is num_faces.
+ * This macro is just to make this obvious! */
+ #define INF_FACE num_faces
+ dsf = snewn(num_faces + 1, int);
+ dsf_init(dsf, num_faces + 1);
+
+ /* First pass */
+ for (i = 0; i < g->num_edges; i++) {
+ grid_edge *e = g->edges + i;
+ int f1 = e->face1 ? e->face1 - g->faces : INF_FACE;
+ int f2 = e->face2 ? e->face2 - g->faces : INF_FACE;
+ if (state->lines[i] != LINE_YES)
+ dsf_merge(dsf, f1, f2);
+ }
+
+ /* Second pass */
+ infinite_area = dsf_canonify(dsf, INF_FACE);
+ finite_area = -1;
+ for (i = 0; i < g->num_edges; i++) {
+ grid_edge *e = g->edges + i;
+ int f1 = e->face1 ? e->face1 - g->faces : INF_FACE;
+ int can1 = dsf_canonify(dsf, f1);
+ int f2 = e->face2 ? e->face2 - g->faces : INF_FACE;
+ int can2 = dsf_canonify(dsf, f2);
+ if (state->lines[i] != LINE_YES) continue;
+
+ if (can1 == can2) {
+ /* Faces are equivalent, so this edge not part of a loop */
+ found_edge_not_in_loop = TRUE;
+ continue;
+ }
+ state->line_errors[i] = TRUE;
+ if (loops_found == 0) loops_found = 1;
+
+ /* Don't bother with further checks if we've already found 2 loops */
+ if (loops_found == 2) continue;
+
+ if (finite_area == -1) {
+ /* Found our first finite area */
+ if (can1 != infinite_area)
+ finite_area = can1;
+ else
+ finite_area = can2;
+ }
+
+ /* Have we found a second area? */
+ if (finite_area != -1) {
+ if (can1 != infinite_area && can1 != finite_area) {
+ loops_found = 2;
+ continue;
+ }
+ if (can2 != infinite_area && can2 != finite_area) {
+ loops_found = 2;
+ }
+ }
+ }
+
+/*
+ printf("loops_found = %d\n", loops_found);
+ printf("found_edge_not_in_loop = %s\n",
+ found_edge_not_in_loop ? "TRUE" : "FALSE");
+*/
+
+ sfree(dsf); /* No longer need the dsf */
+
+ /* Have we found a candidate loop? */
+ if (loops_found == 1 && !found_edge_not_in_loop) {
+ /* Yes, so check all clues are satisfied */
+ int found_clue_violation = FALSE;
+ for (i = 0; i < num_faces; i++) {
+ int c = state->clues[i];
+ if (c >= 0) {
+ if (face_order(state, i, LINE_YES) != c) {
+ found_clue_violation = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!found_clue_violation) {
+ /* The loop is good */
+ memset(state->line_errors, 0, g->num_edges);
+ return TRUE; /* No need to bother checking for dot violations */
+ }
+ }
+
+ /* Check for dot violations */
+ for (i = 0; i < g->num_dots; i++) {
+ int yes = dot_order(state, i, LINE_YES);
+ int unknown = dot_order(state, i, LINE_UNKNOWN);
+ if ((yes == 1 && unknown == 0) || (yes >= 3)) {
+ /* violation, so mark all YES edges as errors */
+ grid_dot *d = g->dots + i;
+ int j;
+ for (j = 0; j < d->order; j++) {
+ int e = d->edges[j] - g->edges;
+ if (state->lines[e] == LINE_YES)
+ state->line_errors[e] = TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
/* ----------------------------------------------------------------------
* Solver logic
* Easy Mode
* Just implement the rules of the game.
*
- * Normal Mode
+ * Normal and Tricky Modes
* For each (adjacent) pair of lines through each dot we store a bit for
* whether at least one of them is on and whether at most one is on. (If we
* know both or neither is on that's already stored more directly.)
continue;
/* Found opposite UNKNOWNS and they're next to each other */
opp_dline_index = dline_index_from_dot(g, d, opp);
- return set_atleastone(sstate->normal->dlines, opp_dline_index);
+ return set_atleastone(sstate->dlines, opp_dline_index);
}
return FALSE;
}
continue;
/* Found two UNKNOWNS */
- can1 = edsf_canonify(sstate->hard->linedsf, line1_index, &inv1);
- can2 = edsf_canonify(sstate->hard->linedsf, line2_index, &inv2);
+ can1 = edsf_canonify(sstate->linedsf, line1_index, &inv1);
+ can2 = edsf_canonify(sstate->linedsf, line2_index, &inv2);
if (can1 == can2 && inv1 == inv2) {
solver_set_line(sstate, line1_index, line_new);
solver_set_line(sstate, line2_index, line_new);
{
game_state *state = sstate->state;
int diff = DIFF_MAX;
- int *linedsf = sstate->hard->linedsf;
+ int *linedsf = sstate->linedsf;
if (unknown_count == 2) {
/* Lines are known alike/opposite, depending on inv. */
* Answer: first all squares then all dots.
*/
-static int easy_mode_deductions(solver_state *sstate)
+static int trivial_deductions(solver_state *sstate)
{
int i, current_yes, current_no;
game_state *state = sstate->state;
return diff;
}
-static int normal_mode_deductions(solver_state *sstate)
+static int dline_deductions(solver_state *sstate)
{
game_state *state = sstate->state;
grid *g = state->game_grid;
- char *dlines = sstate->normal->dlines;
+ char *dlines = sstate->dlines;
int i;
int diff = DIFF_MAX;
diff = min(diff, DIFF_EASY);
}
- /* Now see if we can make dline deduction for edges{j,j+1} */
- e = f->edges[k];
- if (state->lines[e - g->edges] != LINE_UNKNOWN)
- /* Only worth doing this for an UNKNOWN,UNKNOWN pair.
- * Dlines where one of the edges is known, are handled in the
- * dot-deductions */
- continue;
-
- dline_index = dline_index_from_face(g, f, k);
- k++;
- if (k >= N) k = 0;
-
- /* minimum YESs in the complement of this dline */
- if (mins[k][j] > clue - 2) {
- /* Adding 2 YESs would break the clue */
- if (set_atmostone(dlines, dline_index))
- diff = min(diff, DIFF_NORMAL);
- }
- /* maximum YESs in the complement of this dline */
- if (maxs[k][j] < clue) {
- /* Adding 2 NOs would mean not enough YESs */
- if (set_atleastone(dlines, dline_index))
- diff = min(diff, DIFF_NORMAL);
+ /* More advanced deduction that allows propagation along diagonal
+ * chains of faces connected by dots, for example, 3-2-...-2-3
+ * in square grids. */
+ if (sstate->diff >= DIFF_TRICKY) {
+ /* Now see if we can make dline deduction for edges{j,j+1} */
+ e = f->edges[k];
+ if (state->lines[e - g->edges] != LINE_UNKNOWN)
+ /* Only worth doing this for an UNKNOWN,UNKNOWN pair.
+ * Dlines where one of the edges is known, are handled in the
+ * dot-deductions */
+ continue;
+
+ dline_index = dline_index_from_face(g, f, k);
+ k++;
+ if (k >= N) k = 0;
+
+ /* minimum YESs in the complement of this dline */
+ if (mins[k][j] > clue - 2) {
+ /* Adding 2 YESs would break the clue */
+ if (set_atmostone(dlines, dline_index))
+ diff = min(diff, DIFF_NORMAL);
+ }
+ /* maximum YESs in the complement of this dline */
+ if (maxs[k][j] < clue) {
+ /* Adding 2 NOs would mean not enough YESs */
+ if (set_atleastone(dlines, dline_index))
+ diff = min(diff, DIFF_NORMAL);
+ }
}
}
}
}
}
- /* If we have atleastone set for this dline, infer
- * atmostone for each "opposite" dline (that is, each
- * dline without edges in common with this one).
- * Again, this test is only worth doing if both these
- * lines are UNKNOWN. For if one of these lines were YES,
- * the (yes == 1) test above would kick in instead. */
- if (is_atleastone(dlines, dline_index)) {
- int opp;
- for (opp = 0; opp < N; opp++) {
- int opp_dline_index;
- if (opp == j || opp == j+1 || opp == j-1)
- continue;
- if (j == 0 && opp == N-1)
- continue;
- if (j == N-1 && opp == 0)
- continue;
- opp_dline_index = dline_index_from_dot(g, d, opp);
- if (set_atmostone(dlines, opp_dline_index))
- diff = min(diff, DIFF_NORMAL);
- }
-
- if (yes == 0 && is_atmostone(dlines, dline_index)) {
- /* This dline has *exactly* one YES and there are no
- * other YESs. This allows more deductions. */
- if (unknown == 3) {
- /* Third unknown must be YES */
- for (opp = 0; opp < N; opp++) {
- int opp_index;
- if (opp == j || opp == k)
- continue;
- opp_index = d->edges[opp] - g->edges;
- if (state->lines[opp_index] == LINE_UNKNOWN) {
- solver_set_line(sstate, opp_index, LINE_YES);
- diff = min(diff, DIFF_EASY);
+ /* More advanced deduction that allows propagation along diagonal
+ * chains of faces connected by dots, for example: 3-2-...-2-3
+ * in square grids. */
+ if (sstate->diff >= DIFF_TRICKY) {
+ /* If we have atleastone set for this dline, infer
+ * atmostone for each "opposite" dline (that is, each
+ * dline without edges in common with this one).
+ * Again, this test is only worth doing if both these
+ * lines are UNKNOWN. For if one of these lines were YES,
+ * the (yes == 1) test above would kick in instead. */
+ if (is_atleastone(dlines, dline_index)) {
+ int opp;
+ for (opp = 0; opp < N; opp++) {
+ int opp_dline_index;
+ if (opp == j || opp == j+1 || opp == j-1)
+ continue;
+ if (j == 0 && opp == N-1)
+ continue;
+ if (j == N-1 && opp == 0)
+ continue;
+ opp_dline_index = dline_index_from_dot(g, d, opp);
+ if (set_atmostone(dlines, opp_dline_index))
+ diff = min(diff, DIFF_NORMAL);
+ }
+ if (yes == 0 && is_atmostone(dlines, dline_index)) {
+ /* This dline has *exactly* one YES and there are no
+ * other YESs. This allows more deductions. */
+ if (unknown == 3) {
+ /* Third unknown must be YES */
+ for (opp = 0; opp < N; opp++) {
+ int opp_index;
+ if (opp == j || opp == k)
+ continue;
+ opp_index = d->edges[opp] - g->edges;
+ if (state->lines[opp_index] == LINE_UNKNOWN) {
+ solver_set_line(sstate, opp_index,
+ LINE_YES);
+ diff = min(diff, DIFF_EASY);
+ }
}
+ } else if (unknown == 4) {
+ /* Exactly one of opposite UNKNOWNS is YES. We've
+ * already set atmostone, so set atleastone as
+ * well.
+ */
+ if (dline_set_opp_atleastone(sstate, d, j))
+ diff = min(diff, DIFF_NORMAL);
}
- } else if (unknown == 4) {
- /* Exactly one of opposite UNKNOWNS is YES. We've
- * already set atmostone, so set atleastone as well.
- */
- if (dline_set_opp_atleastone(sstate, d, j))
- diff = min(diff, DIFF_NORMAL);
}
}
}
return diff;
}
-static int hard_mode_deductions(solver_state *sstate)
+static int linedsf_deductions(solver_state *sstate)
{
game_state *state = sstate->state;
grid *g = state->game_grid;
- char *dlines = sstate->normal->dlines;
+ char *dlines = sstate->dlines;
int i;
int diff = DIFF_MAX;
int diff_tmp;
if (state->lines[line2_index] != LINE_UNKNOWN)
continue;
/* Infer dline flags from linedsf */
- can1 = edsf_canonify(sstate->hard->linedsf, line1_index, &inv1);
- can2 = edsf_canonify(sstate->hard->linedsf, line2_index, &inv2);
+ can1 = edsf_canonify(sstate->linedsf, line1_index, &inv1);
+ can2 = edsf_canonify(sstate->linedsf, line2_index, &inv2);
if (can1 == can2 && inv1 != inv2) {
/* These are opposites, so set dline atmostone/atleastone */
if (set_atmostone(dlines, dline_index))
for (i = 0; i < g->num_edges; i++) {
int can, inv;
enum line_state s;
- can = edsf_canonify(sstate->hard->linedsf, i, &inv);
+ can = edsf_canonify(sstate->linedsf, i, &inv);
if (can == i)
continue;
s = sstate->state->lines[can];
/* This will return a dynamically allocated solver_state containing the (more)
* solved grid */
-static solver_state *solve_game_rec(const solver_state *sstate_start,
- int diff)
-{
- solver_state *sstate, *sstate_saved;
- int solver_progress;
- game_state *state;
-
- /* Indicates which solver we should call next. This is a sensible starting
- * point */
- int current_solver = DIFF_EASY, next_solver;
+static solver_state *solve_game_rec(const solver_state *sstate_start)
+{
+ solver_state *sstate;
+
+ /* Index of the solver we should call next. */
+ int i = 0;
+
+ /* As a speed-optimisation, we avoid re-running solvers that we know
+ * won't make any progress. This happens when a high-difficulty
+ * solver makes a deduction that can only help other high-difficulty
+ * solvers.
+ * For example: if a new 'dline' flag is set by dline_deductions, the
+ * trivial_deductions solver cannot do anything with this information.
+ * If we've already run the trivial_deductions solver (because it's
+ * earlier in the list), there's no point running it again.
+ *
+ * Therefore: if a solver is earlier in the list than "threshold_index",
+ * we don't bother running it if it's difficulty level is less than
+ * "threshold_diff".
+ */
+ int threshold_diff = 0;
+ int threshold_index = 0;
+
sstate = dup_solver_state(sstate_start);
- /* Cache the values of some variables for readability */
- state = sstate->state;
-
- sstate_saved = NULL;
-
- solver_progress = FALSE;
-
check_caches(sstate);
- do {
+ while (i < NUM_SOLVERS) {
if (sstate->solver_status == SOLVER_MISTAKE)
return sstate;
-
- next_solver = solver_fns[current_solver](sstate);
-
- if (next_solver == DIFF_MAX) {
- if (current_solver < diff && current_solver + 1 < DIFF_MAX) {
- /* Try next beefier solver */
- next_solver = current_solver + 1;
- } else {
- next_solver = loop_deductions(sstate);
- }
- }
-
if (sstate->solver_status == SOLVER_SOLVED ||
sstate->solver_status == SOLVER_AMBIGUOUS) {
-/* fprintf(stderr, "Solver completed\n"); */
+ /* solver finished */
break;
}
- /* Once we've looped over all permitted solvers then the loop
- * deductions without making any progress, we'll exit this while loop */
- current_solver = next_solver;
- } while (current_solver < DIFF_MAX);
+ if ((solver_diffs[i] >= threshold_diff || i >= threshold_index)
+ && solver_diffs[i] <= sstate->diff) {
+ /* current_solver is eligible, so use it */
+ int next_diff = solver_fns[i](sstate);
+ if (next_diff != DIFF_MAX) {
+ /* solver made progress, so use new thresholds and
+ * start again at top of list. */
+ threshold_diff = next_diff;
+ threshold_index = i;
+ i = 0;
+ continue;
+ }
+ }
+ /* current_solver is ineligible, or failed to make progress, so
+ * go to the next solver in the list */
+ i++;
+ }
if (sstate->solver_status == SOLVER_SOLVED ||
sstate->solver_status == SOLVER_AMBIGUOUS) {
solver_state *sstate, *new_sstate;
sstate = new_solver_state(state, DIFF_MAX);
- new_sstate = solve_game_rec(sstate, DIFF_MAX);
+ new_sstate = solve_game_rec(sstate);
if (new_sstate->solver_status == SOLVER_SOLVED) {
soln = encode_solve_move(new_sstate->state);
{
int i;
game_state *newstate = dup_game(state);
- grid *g = state->game_grid;
if (move[0] == 'S') {
move++;
while (*move) {
i = atoi(move);
+ if (i < 0 || i >= newstate->game_grid->num_edges)
+ goto fail;
move += strspn(move, "1234567890");
switch (*(move++)) {
case 'y':
/*
* Check for completion.
*/
- for (i = 0; i < g->num_edges; i++) {
- if (newstate->lines[i] == LINE_YES)
- break;
- }
- if (i < g->num_edges) {
- int looplen, count;
- grid_edge *start_edge = g->edges + i;
- grid_edge *e = start_edge;
- grid_dot *d = e->dot1;
- /*
- * We've found an edge i. Follow it round
- * to see if it's part of a loop.
- */
- looplen = 0;
- while (1) {
- int j;
- int order = dot_order(newstate, d - g->dots, LINE_YES);
- if (order != 2)
- goto completion_check_done;
-
- /* Find other edge around this dot */
- for (j = 0; j < d->order; j++) {
- grid_edge *e2 = d->edges[j];
- if (e2 != e && newstate->lines[e2 - g->edges] == LINE_YES)
- break;
- }
- assert(j != d->order); /* dot_order guarantees success */
-
- e = d->edges[j];
- d = (e->dot1 == d) ? e->dot2 : e->dot1;
- looplen++;
-
- if (e == start_edge)
- break;
- }
-
- /*
- * We've traced our way round a loop, and we know how many
- * line segments were involved. Count _all_ the line
- * segments in the grid, to see if the loop includes them
- * all.
- */
- count = 0;
- for (i = 0; i < g->num_edges; i++) {
- if (newstate->lines[i] == LINE_YES)
- count++;
- }
- assert(count >= looplen);
- if (count != looplen)
- goto completion_check_done;
-
- /*
- * The grid contains one closed loop and nothing else.
- * Check that all the clues are satisfied.
- */
- for (i = 0; i < g->num_faces; i++) {
- int c = newstate->clues[i];
- if (c >= 0) {
- if (face_order(newstate, i, LINE_YES) != c) {
- goto completion_check_done;
- }
- }
- }
-
- /*
- * Completed!
- */
+ if (check_completion(newstate))
newstate->solved = TRUE;
- }
- completion_check_done:
return newstate;
fail:
if (ds->started) {
const char redraw_flag = (char)(1<<7);
for (i = 0; i < g->num_edges; i++) {
+ char prev_ds = (ds->lines[i] & ~redraw_flag);
+ char new_ds = state->lines[i];
+ if (state->line_errors[i])
+ new_ds = DS_LINE_ERROR;
+
/* If we're changing state, AND
* the previous state was a coloured line */
- if ((state->lines[i] != (ds->lines[i] & ~redraw_flag)) &&
- ((ds->lines[i] & ~redraw_flag) != LINE_NO)) {
+ if ((prev_ds != new_ds) && (prev_ds != LINE_NO)) {
grid_edge *e = g->edges + i;
int x1 = e->dot1->x;
int y1 = e->dot1->y;
}
}
- /* I've also had a request to colour lines red if they make a non-solution
- * loop, or if more than two lines go into any point. I think that would
- * be good some time. */
-
/* Lines */
for (i = 0; i < g->num_edges; i++) {
grid_edge *e = g->edges + i;
int x1, x2, y1, y2;
int xmin, ymin, xmax, ymax;
- int need_draw = (state->lines[i] != ds->lines[i]) ? TRUE : FALSE;
+ char new_ds, need_draw;
+ new_ds = state->lines[i];
+ if (state->line_errors[i])
+ new_ds = DS_LINE_ERROR;
+ need_draw = (new_ds != ds->lines[i]) ? TRUE : FALSE;
if (flash_changed && (state->lines[i] == LINE_YES))
need_draw = TRUE;
if (!ds->started)
need_draw = TRUE; /* draw everything at the start */
- ds->lines[i] = state->lines[i];
+ ds->lines[i] = new_ds;
if (!need_draw)
continue;
- if (state->lines[i] == LINE_UNKNOWN)
+ if (state->line_errors[i])
+ line_colour = COL_MISTAKE;
+ else if (state->lines[i] == LINE_UNKNOWN)
line_colour = COL_LINEUNKNOWN;
else if (state->lines[i] == LINE_NO)
- line_colour = COL_BACKGROUND;
+ line_colour = COL_FAINT;
else if (ds->flashing)
line_colour = COL_HIGHLIGHT;
else
ymin = min(y1, y2);
ymax = max(y1, y2);
- if (line_colour != COL_BACKGROUND) {
+ if (line_colour == COL_FAINT) {
+ static int draw_faint_lines = -1;
+ if (draw_faint_lines < 0) {
+ char *env = getenv("LOOPY_FAINT_LINES");
+ draw_faint_lines = (!env || (env[0] == 'y' ||
+ env[0] == 'Y'));
+ }
+ if (draw_faint_lines)
+ draw_line(dr, x1, y1, x2, y2, line_colour);
+ } else {
/* (dx, dy) points roughly from (x1, y1) to (x2, y2).
* The line is then "fattened" in a (roughly) perpendicular
* direction to create a thin rectangle. */
FALSE, game_timing_state,
0, /* mouse_priorities */
};
+
+#ifdef STANDALONE_SOLVER
+
+/*
+ * Half-hearted standalone solver. It can't output the solution to
+ * anything but a square puzzle, and it can't log the deductions
+ * it makes either. But it can solve square puzzles, and more
+ * importantly it can use its solver to grade the difficulty of
+ * any puzzle you give it.
+ */
+
+#include <stdarg.h>
+
+int main(int argc, char **argv)
+{
+ game_params *p;
+ game_state *s;
+ char *id = NULL, *desc, *err;
+ int grade = FALSE;
+ int ret, diff;
+#if 0 /* verbose solver not supported here (yet) */
+ int really_verbose = FALSE;
+#endif
+
+ while (--argc > 0) {
+ char *p = *++argv;
+#if 0 /* verbose solver not supported here (yet) */
+ if (!strcmp(p, "-v")) {
+ really_verbose = TRUE;
+ } else
+#endif
+ if (!strcmp(p, "-g")) {
+ grade = TRUE;
+ } else if (*p == '-') {
+ fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+ return 1;
+ } else {
+ id = p;
+ }
+ }
+
+ if (!id) {
+ fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+ return 1;
+ }
+
+ desc = strchr(id, ':');
+ if (!desc) {
+ fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+ return 1;
+ }
+ *desc++ = '\0';
+
+ p = default_params();
+ decode_params(p, id);
+ err = validate_desc(p, desc);
+ if (err) {
+ fprintf(stderr, "%s: %s\n", argv[0], err);
+ return 1;
+ }
+ s = new_game(NULL, p, desc);
+
+ /*
+ * When solving an Easy puzzle, we don't want to bother the
+ * user with Hard-level deductions. For this reason, we grade
+ * the puzzle internally before doing anything else.
+ */
+ ret = -1; /* placate optimiser */
+ for (diff = 0; diff < DIFF_MAX; diff++) {
+ solver_state *sstate_new;
+ solver_state *sstate = new_solver_state((game_state *)s, diff);
+
+ sstate_new = solve_game_rec(sstate);
+
+ if (sstate_new->solver_status == SOLVER_MISTAKE)
+ ret = 0;
+ else if (sstate_new->solver_status == SOLVER_SOLVED)
+ ret = 1;
+ else
+ ret = 2;
+
+ free_solver_state(sstate_new);
+ free_solver_state(sstate);
+
+ if (ret < 2)
+ break;
+ }
+
+ if (diff == DIFF_MAX) {
+ if (grade)
+ printf("Difficulty rating: harder than Hard, or ambiguous\n");
+ else
+ printf("Unable to find a unique solution\n");
+ } else {
+ if (grade) {
+ if (ret == 0)
+ printf("Difficulty rating: impossible (no solution exists)\n");
+ else if (ret == 1)
+ printf("Difficulty rating: %s\n", diffnames[diff]);
+ } else {
+ solver_state *sstate_new;
+ solver_state *sstate = new_solver_state((game_state *)s, diff);
+
+ /* If we supported a verbose solver, we'd set verbosity here */
+
+ sstate_new = solve_game_rec(sstate);
+
+ if (sstate_new->solver_status == SOLVER_MISTAKE)
+ printf("Puzzle is inconsistent\n");
+ else {
+ assert(sstate_new->solver_status == SOLVER_SOLVED);
+ if (s->grid_type == 0) {
+ fputs(game_text_format(sstate_new->state), stdout);
+ } else {
+ printf("Unable to output non-square grids\n");
+ }
+ }
+
+ free_solver_state(sstate_new);
+ free_solver_state(sstate);
+ }
+ }
+
+ return 0;
+}
+
+#endif