puzzles: resync with upstream
This brings the puzzles source to upstream commit e2135d5. (I've made my own
changes on top of that.)
This brings in a couple bugfixes and a new solver for Dominosa.
Change-Id: I11d46b43171787832330a5e2e0d2f353f36f727d
diff --git a/apps/plugins/puzzles/SOURCES b/apps/plugins/puzzles/SOURCES
index c961154..5301cee 100644
--- a/apps/plugins/puzzles/SOURCES
+++ b/apps/plugins/puzzles/SOURCES
@@ -19,6 +19,7 @@
src/penrose.c
src/printing.c
src/random.c
+src/sort.c
src/tdq.c
src/tree234.c
src/version.c
diff --git a/apps/plugins/puzzles/src/benchmark.pl b/apps/plugins/puzzles/src/benchmark.pl
index 9876385..7ac48ab 100755
--- a/apps/plugins/puzzles/src/benchmark.pl
+++ b/apps/plugins/puzzles/src/benchmark.pl
@@ -9,7 +9,7 @@
my %presets = ();
my $maxval = 0;
-while (<>) {
+while (<<>>) {
chomp;
if (/^(.*)(#.*): ([\d\.]+)$/) {
push @presets, $1 unless defined $presets{$1};
diff --git a/apps/plugins/puzzles/src/dominosa.R b/apps/plugins/puzzles/src/dominosa.R
index 9921836..b85e7dc 100644
--- a/apps/plugins/puzzles/src/dominosa.R
+++ b/apps/plugins/puzzles/src/dominosa.R
@@ -1,6 +1,6 @@
# -*- makefile -*-
-DOMINOSA_EXTRA = laydomino
+DOMINOSA_EXTRA = laydomino dsf sort findloop
dominosa : [X] GTK COMMON dominosa DOMINOSA_EXTRA dominosa-icon|no-icon
@@ -8,6 +8,9 @@
ALL += dominosa[COMBINED] DOMINOSA_EXTRA
+dominosasolver : [U] dominosa[STANDALONE_SOLVER] DOMINOSA_EXTRA STANDALONE
+dominosasolver : [C] dominosa[STANDALONE_SOLVER] DOMINOSA_EXTRA STANDALONE
+
!begin am gtk
GAMES += dominosa
!end
diff --git a/apps/plugins/puzzles/src/dominosa.c b/apps/plugins/puzzles/src/dominosa.c
index 5f035e9..67a1d69 100644
--- a/apps/plugins/puzzles/src/dominosa.c
+++ b/apps/plugins/puzzles/src/dominosa.c
@@ -5,65 +5,41 @@
*/
/*
- * TODO:
- *
- * - improve solver so as to use more interesting forms of
- * deduction
+ * Further possible deduction types in the solver:
*
- * * rule out a domino placement if it would divide an unfilled
- * region such that at least one resulting region had an odd
- * area
- * + Tarjan's bridge-finding algorithm would be a way to find
- * domino placements that split a connected region in two:
- * form the graph whose vertices are unpaired squares and
- * whose edges are potential (not placed but also not ruled
- * out) dominoes covering two of them, and any bridge in that
- * graph is a candidate.
- * + Then, finding any old spanning forest of the unfilled
- * squares should be sufficient to determine the area parity
- * of the region that any such placement would cut off.
+ * * possibly an advanced form of deduce_parity via 2-connectedness.
+ * We currently deal with areas of the graph with exactly one way
+ * in and out; but if you have an area with exactly _two_ routes in
+ * and out of it, then you can at least decide on the _relative_
+ * parity of the two (either 'these two edges both bisect dominoes
+ * or neither do', or 'exactly one of these edges bisects a
+ * domino'). And occasionally that can be enough to let you rule
+ * out one of the two remaining choices.
+ * + For example, if both those edges bisect a domino, then those
+ * two dominoes would also be both the same.
+ * + Or perhaps between them they rule out all possibilities for
+ * some other square.
+ * + Or perhaps they themselves would be duplicates!
+ * + Or perhaps, on purely geometric grounds, they would box in a
+ * square to the point where it ended up having to be an
+ * isolated singleton.
+ * + The tricky part of this is how you do the graph theory.
+ * Perhaps a modified form of Tarjan's bridge-finding algorithm
+ * would work, but I haven't thought through the details.
*
- * * set analysis
- * + look at all unclaimed squares containing a given number
- * + for each one, find the set of possible numbers that it
- * can connect to (i.e. each neighbouring tile such that
- * the placement between it and that neighbour has not yet
- * been ruled out)
- * + now proceed similarly to Solo set analysis: try to find
- * a subset of the squares such that the union of their
- * possible numbers is the same size as the subset. If so,
- * rule out those possible numbers for all other squares.
- * * important wrinkle: the double dominoes complicate
- * matters. Connecting a number to itself uses up _two_
- * of the unclaimed squares containing a number. Thus,
- * when finding the initial subset we must never
- * include two adjacent squares; and also, when ruling
- * things out after finding the subset, we must be
- * careful that we don't rule out precisely the domino
- * placement that was _included_ in our set!
- *
- * * playing off the two ends of one potential domino, by
- * considering the alternatives to that domino that each end
- * might otherwise be part of.
- * + if not playing this domino would require each end to be
- * part of an identical domino, play it. (e.g. the middle of
- * 5-4-4-5)
- * + if not playing this domino would guarantee that the two
- * ends between them used up all of some other square's
- * choices, play it. (e.g. the middle of 2-3-3-1 if another 3
- * cell can only link to a 2 or a 1)
- *
- * * identify 'forcing chains', in the sense of any path of cells
- * each of which has only two possible dominoes to be part of,
- * and each of those rules out one of the choices for the next
- * cell. Such a chain has the property that either all the odd
- * dominoes are placed, or all the even ones are placed; so if
- * either set of those introduces a conflict (e.g. a dupe within
- * the chain, or using up all of some other square's choices),
- * then the whole set can be ruled out, and the other set played
- * immediately.
- * + this is of course a generalisation of the previous idea,
- * which is simply a forcing chain of length 3.
+ * * possibly an advanced version of set analysis which doesn't have
+ * to start from squares all having the same number? For example,
+ * if you have three mutually non-adjacent squares labelled 1,2,3
+ * such that the numbers adjacent to each are precisely the other
+ * two, then set analysis can work just fine in principle, and
+ * tells you that those three squares must overlap the three
+ * dominoes 1-2, 2-3 and 1-3 in some order, so you can rule out any
+ * placements of those elsewhere.
+ * + the difficulty with this is how you avoid it going painfully
+ * exponential-time. You can't iterate over all the subsets, so
+ * you'd need some kind of more sophisticated directed search.
+ * + and the adjacency allowance has to be similarly accounted
+ * for, which could get tricky to keep track of.
*/
#include <stdio.h>
@@ -84,6 +60,26 @@
#define FLASH_TIME 0.13F
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+#define DIFFLIST(X) \
+ X(TRIVIAL,Trivial,t) \
+ X(BASIC,Basic,b) \
+ X(HARD,Hard,h) \
+ X(EXTREME,Extreme,e) \
+ X(AMBIGUOUS,Ambiguous,a) \
+ /* end of list */
+#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) DIFFCOUNT };
+static char const *const dominosa_diffnames[] = { DIFFLIST(TITLE) };
+static char const dominosa_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
enum {
COL_BACKGROUND,
COL_TEXT,
@@ -98,7 +94,7 @@
struct game_params {
int n;
- bool unique;
+ int diff;
};
struct game_numbers {
@@ -125,35 +121,41 @@
game_params *ret = snew(game_params);
ret->n = 6;
- ret->unique = true;
+ ret->diff = DIFF_BASIC;
return ret;
}
-static bool game_fetch_preset(int i, char **name, game_params **params)
+static const struct game_params dominosa_presets[] = {
+ { 3, DIFF_TRIVIAL },
+ { 4, DIFF_TRIVIAL },
+ { 5, DIFF_TRIVIAL },
+ { 6, DIFF_TRIVIAL },
+ { 4, DIFF_BASIC },
+ { 5, DIFF_BASIC },
+ { 6, DIFF_BASIC },
+ { 7, DIFF_BASIC },
+ { 8, DIFF_BASIC },
+ { 9, DIFF_BASIC },
+ { 6, DIFF_HARD },
+ { 6, DIFF_EXTREME },
+};
+
+static bool game_fetch_preset(int i, char **name, game_params **params_out)
{
- game_params *ret;
- int n;
+ game_params *params;
char buf[80];
- switch (i) {
- case 0: n = 3; break;
- case 1: n = 4; break;
- case 2: n = 5; break;
- case 3: n = 6; break;
- case 4: n = 7; break;
- case 5: n = 8; break;
- case 6: n = 9; break;
- default: return false;
- }
+ if (i < 0 || i >= lenof(dominosa_presets))
+ return false;
- sprintf(buf, "Up to double-%d", n);
+ params = snew(game_params);
+ *params = dominosa_presets[i]; /* structure copy */
+
+ sprintf(buf, "Order %d, %s", params->n, dominosa_diffnames[params->diff]);
+
*name = dupstr(buf);
-
- *params = ret = snew(game_params);
- ret->n = n;
- ret->unique = true;
-
+ *params_out = params;
return true;
}
@@ -171,18 +173,36 @@
static void decode_params(game_params *params, char const *string)
{
- params->n = atoi(string);
- while (*string && isdigit((unsigned char)*string)) string++;
- if (*string == 'a')
- params->unique = false;
+ const char *p = string;
+
+ params->n = atoi(p);
+ while (*p && isdigit((unsigned char)*p)) p++;
+
+ while (*p) {
+ char c = *p++;
+ if (c == 'a') {
+ /* Legacy encoding from before the difficulty system */
+ params->diff = DIFF_AMBIGUOUS;
+ } else if (c == 'd') {
+ int i;
+ params->diff = DIFFCOUNT+1; /* ...which is invalid */
+ if (*p) {
+ for (i = 0; i < DIFFCOUNT; i++) {
+ if (*p == dominosa_diffchars[i])
+ params->diff = i;
+ }
+ p++;
+ }
+ }
+ }
}
static char *encode_params(const game_params *params, bool full)
{
char buf[80];
- sprintf(buf, "%d", params->n);
- if (full && !params->unique)
- strcat(buf, "a");
+ int len = sprintf(buf, "%d", params->n);
+ if (full)
+ len += sprintf(buf + len, "d%c", dominosa_diffchars[params->diff]);
return dupstr(buf);
}
@@ -198,9 +218,10 @@
sprintf(buf, "%d", params->n);
ret[0].u.string.sval = dupstr(buf);
- ret[1].name = "Ensure unique solution";
- ret[1].type = C_BOOLEAN;
- ret[1].u.boolean.bval = params->unique;
+ ret[1].name = "Difficulty";
+ ret[1].type = C_CHOICES;
+ ret[1].u.choices.choicenames = DIFFCONFIG;
+ ret[1].u.choices.selected = params->diff;
ret[2].name = NULL;
ret[2].type = C_END;
@@ -213,7 +234,7 @@
game_params *ret = snew(game_params);
ret->n = atoi(cfg[0].u.string.sval);
- ret->unique = cfg[1].u.boolean.bval;
+ ret->diff = cfg[1].u.choices.selected;
return ret;
}
@@ -222,6 +243,8 @@
{
if (params->n < 1)
return "Maximum face number must be at least one";
+ if (params->diff >= DIFFCOUNT)
+ return "Unknown difficulty rating";
return NULL;
}
@@ -229,361 +252,1993 @@
* Solver.
*/
-static int find_overlaps(int w, int h, int placement, int *set)
+#ifdef STANDALONE_SOLVER
+#define SOLVER_DIAGNOSTICS
+bool solver_diagnostics = false;
+#elif defined SOLVER_DIAGNOSTICS
+const bool solver_diagnostics = true;
+#endif
+
+struct solver_domino;
+struct solver_placement;
+
+/*
+ * Information about a particular domino.
+ */
+struct solver_domino {
+ /* The numbers on the domino, and its index in the dominoes array. */
+ int lo, hi, index;
+
+ /* List of placements not yet ruled out for this domino. */
+ int nplacements;
+ struct solver_placement **placements;
+
+#ifdef SOLVER_DIAGNOSTICS
+ /* A textual name we can easily reuse in solver diagnostics. */
+ char *name;
+#endif
+};
+
+/*
+ * Information about a particular 'placement' (i.e. specific location
+ * that a domino might go in).
+ */
+struct solver_placement {
+ /* The index of this placement in sc->placements. */
+ int index;
+
+ /* The two squares that make up this placement. */
+ struct solver_square *squares[2];
+
+ /* The domino that has to go in this position, if any. */
+ struct solver_domino *domino;
+
+ /* The index of this placement in each square's placements array,
+ * and in that of the domino. */
+ int spi[2], dpi;
+
+ /* Whether this is still considered a possible placement. */
+ bool active;
+
+ /* Other domino placements that overlap with this one. (Maximum 6:
+ * three overlapping each square of the placement.) */
+ int noverlaps;
+ struct solver_placement *overlaps[6];
+
+#ifdef SOLVER_DIAGNOSTICS
+ /* A textual name we can easily reuse in solver diagnostics. */
+ char *name;
+#endif
+};
+
+/*
+ * Information about a particular solver square.
+ */
+struct solver_square {
+ /* The coordinates of the square, and its index in a normal grid array. */
+ int x, y, index;
+
+ /* List of domino placements not yet ruled out for this square. */
+ int nplacements;
+ struct solver_placement *placements[4];
+
+ /* The number in the square. */
+ int number;
+
+#ifdef SOLVER_DIAGNOSTICS
+ /* A textual name we can easily reuse in solver diagnostics. */
+ char *name;
+#endif
+};
+
+struct solver_scratch {
+ int n, dc, pc, w, h, wh;
+ int max_diff_used;
+ struct solver_domino *dominoes;
+ struct solver_placement *placements;
+ struct solver_square *squares;
+ struct solver_placement **domino_placement_lists;
+ struct solver_square **squares_by_number;
+ struct findloopstate *fls;
+ bool squares_by_number_initialised;
+ int *wh_scratch, *pc_scratch, *pc_scratch2, *dc_scratch;
+};
+
+static struct solver_scratch *solver_make_scratch(int n)
{
- int x, y, n;
+ int dc = DCOUNT(n), w = n+2, h = n+1, wh = w*h;
+ int pc = (w-1)*h + w*(h-1);
+ struct solver_scratch *sc = snew(struct solver_scratch);
+ int hi, lo, di, x, y, pi, si;
- n = 0; /* number of returned placements */
+ sc->n = n;
+ sc->dc = dc;
+ sc->pc = pc;
+ sc->w = w;
+ sc->h = h;
+ sc->wh = wh;
- x = placement / 2;
- y = x / w;
- x %= w;
+ sc->dominoes = snewn(dc, struct solver_domino);
+ sc->placements = snewn(pc, struct solver_placement);
+ sc->squares = snewn(wh, struct solver_square);
+ sc->domino_placement_lists = snewn(pc, struct solver_placement *);
+ sc->fls = findloop_new_state(wh);
- if (placement & 1) {
- /*
- * Horizontal domino, indexed by its left end.
- */
- if (x > 0)
- set[n++] = placement-2; /* horizontal domino to the left */
- if (y > 0)
- set[n++] = placement-2*w-1;/* vertical domino above left side */
- if (y+1 < h)
- set[n++] = placement-1; /* vertical domino below left side */
- if (x+2 < w)
- set[n++] = placement+2; /* horizontal domino to the right */
- if (y > 0)
- set[n++] = placement-2*w+2-1;/* vertical domino above right side */
- if (y+1 < h)
- set[n++] = placement+2-1; /* vertical domino below right side */
- } else {
- /*
- * Vertical domino, indexed by its top end.
- */
- if (y > 0)
- set[n++] = placement-2*w; /* vertical domino above */
- if (x > 0)
- set[n++] = placement-2+1; /* horizontal domino left of top */
- if (x+1 < w)
- set[n++] = placement+1; /* horizontal domino right of top */
- if (y+2 < h)
- set[n++] = placement+2*w; /* vertical domino below */
- if (x > 0)
- set[n++] = placement-2+2*w+1;/* horizontal domino left of bottom */
- if (x+1 < w)
- set[n++] = placement+2*w+1;/* horizontal domino right of bottom */
+ for (di = hi = 0; hi <= n; hi++) {
+ for (lo = 0; lo <= hi; lo++) {
+ assert(di == DINDEX(hi, lo));
+ sc->dominoes[di].hi = hi;
+ sc->dominoes[di].lo = lo;
+ sc->dominoes[di].index = di;
+
+#ifdef SOLVER_DIAGNOSTICS
+ {
+ char buf[128];
+ sprintf(buf, "%d-%d", hi, lo);
+ sc->dominoes[di].name = dupstr(buf);
+ }
+#endif
+
+ di++;
+ }
}
- return n;
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ struct solver_square *sq = &sc->squares[y*w+x];
+ sq->x = x;
+ sq->y = y;
+ sq->index = y * w + x;
+ sq->nplacements = 0;
+
+#ifdef SOLVER_DIAGNOSTICS
+ {
+ char buf[128];
+ sprintf(buf, "(%d,%d)", x, y);
+ sq->name = dupstr(buf);
+ }
+#endif
+ }
+ }
+
+ pi = 0;
+ for (y = 0; y < h-1; y++) {
+ for (x = 0; x < w; x++) {
+ assert(pi < pc);
+ sc->placements[pi].squares[0] = &sc->squares[y*w+x];
+ sc->placements[pi].squares[1] = &sc->squares[(y+1)*w+x];
+#ifdef SOLVER_DIAGNOSTICS
+ {
+ char buf[128];
+ sprintf(buf, "(%d,%d-%d)", x, y, y+1);
+ sc->placements[pi].name = dupstr(buf);
+ }
+#endif
+ pi++;
+ }
+ }
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w-1; x++) {
+ assert(pi < pc);
+ sc->placements[pi].squares[0] = &sc->squares[y*w+x];
+ sc->placements[pi].squares[1] = &sc->squares[y*w+(x+1)];
+#ifdef SOLVER_DIAGNOSTICS
+ {
+ char buf[128];
+ sprintf(buf, "(%d-%d,%d)", x, x+1, y);
+ sc->placements[pi].name = dupstr(buf);
+ }
+#endif
+ pi++;
+ }
+ }
+ assert(pi == pc);
+
+ /* Set up the full placement lists for all squares, temporarily,
+ * so as to use them to calculate the overlap lists */
+ for (si = 0; si < wh; si++)
+ sc->squares[si].nplacements = 0;
+ for (pi = 0; pi < pc; pi++) {
+ struct solver_placement *p = &sc->placements[pi];
+ for (si = 0; si < 2; si++) {
+ struct solver_square *sq = p->squares[si];
+ p->spi[si] = sq->nplacements;
+ sq->placements[sq->nplacements++] = p;
+ }
+ }
+
+ /* Actually calculate the overlap lists */
+ for (pi = 0; pi < pc; pi++) {
+ struct solver_placement *p = &sc->placements[pi];
+ p->noverlaps = 0;
+ for (si = 0; si < 2; si++) {
+ struct solver_square *sq = p->squares[si];
+ int j;
+ for (j = 0; j < sq->nplacements; j++) {
+ struct solver_placement *q = sq->placements[j];
+ if (q != p)
+ p->overlaps[p->noverlaps++] = q;
+ }
+ }
+ }
+
+ /* Fill in the index field of the placements */
+ for (pi = 0; pi < pc; pi++)
+ sc->placements[pi].index = pi;
+
+ /* Lazily initialised by particular solver techniques that might
+ * never be needed */
+ sc->squares_by_number = NULL;
+ sc->squares_by_number_initialised = false;
+ sc->wh_scratch = NULL;
+ sc->pc_scratch = sc->pc_scratch2 = NULL;
+ sc->dc_scratch = NULL;
+
+ return sc;
+}
+
+static void solver_free_scratch(struct solver_scratch *sc)
+{
+#ifdef SOLVER_DIAGNOSTICS
+ {
+ int i;
+ for (i = 0; i < sc->dc; i++)
+ sfree(sc->dominoes[i].name);
+ for (i = 0; i < sc->pc; i++)
+ sfree(sc->placements[i].name);
+ for (i = 0; i < sc->wh; i++)
+ sfree(sc->squares[i].name);
+ }
+#endif
+ sfree(sc->dominoes);
+ sfree(sc->placements);
+ sfree(sc->squares);
+ sfree(sc->domino_placement_lists);
+ sfree(sc->squares_by_number);
+ findloop_free_state(sc->fls);
+ sfree(sc->wh_scratch);
+ sfree(sc->pc_scratch);
+ sfree(sc->pc_scratch2);
+ sfree(sc->dc_scratch);
+ sfree(sc);
+}
+
+static void solver_setup_grid(struct solver_scratch *sc, const int *numbers)
+{
+ int i, j;
+
+ for (i = 0; i < sc->wh; i++) {
+ sc->squares[i].nplacements = 0;
+ sc->squares[i].number = numbers[sc->squares[i].index];
+ }
+
+ for (i = 0; i < sc->pc; i++) {
+ struct solver_placement *p = &sc->placements[i];
+ int di = DINDEX(p->squares[0]->number, p->squares[1]->number);
+ p->domino = &sc->dominoes[di];
+ }
+
+ for (i = 0; i < sc->dc; i++)
+ sc->dominoes[i].nplacements = 0;
+ for (i = 0; i < sc->pc; i++)
+ sc->placements[i].domino->nplacements++;
+ for (i = j = 0; i < sc->dc; i++) {
+ sc->dominoes[i].placements = sc->domino_placement_lists + j;
+ j += sc->dominoes[i].nplacements;
+ sc->dominoes[i].nplacements = 0;
+ }
+ for (i = 0; i < sc->pc; i++) {
+ struct solver_placement *p = &sc->placements[i];
+ p->dpi = p->domino->nplacements;
+ p->domino->placements[p->domino->nplacements++] = p;
+ p->active = true;
+ }
+
+ for (i = 0; i < sc->wh; i++)
+ sc->squares[i].nplacements = 0;
+ for (i = 0; i < sc->pc; i++) {
+ struct solver_placement *p = &sc->placements[i];
+ for (j = 0; j < 2; j++) {
+ struct solver_square *sq = p->squares[j];
+ p->spi[j] = sq->nplacements;
+ sq->placements[sq->nplacements++] = p;
+ }
+ }
+
+ sc->max_diff_used = DIFF_TRIVIAL;
+ sc->squares_by_number_initialised = false;
+}
+
+/* Given two placements p,q that overlap, returns si such that
+ * p->squares[si] is the square also in q */
+static int common_square_index(struct solver_placement *p,
+ struct solver_placement *q)
+{
+ return (p->squares[0] == q->squares[0] ||
+ p->squares[0] == q->squares[1]) ? 0 : 1;
+}
+
+/* Sort function used to set up squares_by_number */
+static int squares_by_number_cmpfn(const void *av, const void *bv)
+{
+ struct solver_square *a = *(struct solver_square *const *)av;
+ struct solver_square *b = *(struct solver_square *const *)bv;
+ return (a->number < b->number ? -1 : a->number > b->number ? +1 :
+ a->index < b->index ? -1 : a->index > b->index ? +1 : 0);
+}
+
+static void rule_out_placement(
+ struct solver_scratch *sc, struct solver_placement *p)
+{
+ struct solver_domino *d = p->domino;
+ int i, j, si;
+
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics)
+ printf(" ruling out placement %s for domino %s\n", p->name, d->name);
+#endif
+
+ p->active = false;
+
+ i = p->dpi;
+ assert(d->placements[i] == p);
+ if (--d->nplacements != i) {
+ d->placements[i] = d->placements[d->nplacements];
+ d->placements[i]->dpi = i;
+ }
+
+ for (si = 0; si < 2; si++) {
+ struct solver_square *sq = p->squares[si];
+ i = p->spi[si];
+ assert(sq->placements[i] == p);
+ if (--sq->nplacements != i) {
+ sq->placements[i] = sq->placements[sq->nplacements];
+ j = (sq->placements[i]->squares[0] == sq ? 0 : 1);
+ sq->placements[i]->spi[j] = i;
+ }
+ }
}
/*
- * Returns 0, 1 or 2 for number of solutions. 2 means `any number
- * more than one', or more accurately `we were unable to prove
- * there was only one'.
- *
- * Outputs in a `placements' array, indexed the same way as the one
- * within this function (see below); entries in there are <0 for a
- * placement ruled out, 0 for an uncertain placement, and 1 for a
- * definite one.
+ * If a domino has only one placement remaining, rule out all other
+ * placements that overlap it.
*/
-static int solver(int w, int h, int n, int *grid, int *output)
+static bool deduce_domino_single_placement(struct solver_scratch *sc, int di)
{
- int wh = w*h, dc = DCOUNT(n);
- int *placements, *heads;
- int i, j, x, y, ret;
+ struct solver_domino *d = &sc->dominoes[di];
+ struct solver_placement *p, *q;
+ int oi;
+ bool done_something = false;
- /*
- * This array has one entry for every possible domino
- * placement. Vertical placements are indexed by their top
- * half, at (y*w+x)*2; horizontal placements are indexed by
- * their left half at (y*w+x)*2+1.
- *
- * This array is used to link domino placements together into
- * linked lists, so that we can track all the possible
- * placements of each different domino. It's also used as a
- * quick means of looking up an individual placement to see
- * whether we still think it's possible. Actual values stored
- * in this array are -2 (placement not possible at all), -1
- * (end of list), or the array index of the next item.
- *
- * Oh, and -3 for `not even valid', used for array indices
- * which don't even represent a plausible placement.
- */
- placements = snewn(2*wh, int);
- for (i = 0; i < 2*wh; i++)
- placements[i] = -3; /* not even valid */
+ if (d->nplacements != 1)
+ return false;
+ p = d->placements[0];
- /*
- * This array has one entry for every domino, and it is an
- * index into `placements' denoting the head of the placement
- * list for that domino.
- */
- heads = snewn(dc, int);
- for (i = 0; i < dc; i++)
- heads[i] = -1;
-
- /*
- * Set up the initial possibility lists by scanning the grid.
- */
- for (y = 0; y < h-1; y++)
- for (x = 0; x < w; x++) {
- int di = DINDEX(grid[y*w+x], grid[(y+1)*w+x]);
- placements[(y*w+x)*2] = heads[di];
- heads[di] = (y*w+x)*2;
+ for (oi = 0; oi < p->noverlaps; oi++) {
+ q = p->overlaps[oi];
+ if (q->active) {
+ if (!done_something) {
+ done_something = true;
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics)
+ printf("domino %s has unique placement %s\n",
+ d->name, p->name);
+#endif
+ }
+ rule_out_placement(sc, q);
}
- for (y = 0; y < h; y++)
- for (x = 0; x < w-1; x++) {
- int di = DINDEX(grid[y*w+x], grid[y*w+(x+1)]);
- placements[(y*w+x)*2+1] = heads[di];
- heads[di] = (y*w+x)*2+1;
- }
+ }
+
+ return done_something;
+}
+
+/*
+ * If a square has only one placement remaining, rule out all other
+ * placements of its domino.
+ */
+static bool deduce_square_single_placement(struct solver_scratch *sc, int si)
+{
+ struct solver_square *sq = &sc->squares[si];
+ struct solver_placement *p;
+ struct solver_domino *d;
+
+ if (sq->nplacements != 1)
+ return false;
+ p = sq->placements[0];
+ d = p->domino;
+
+ if (d->nplacements <= 1)
+ return false; /* we already knew everything this would tell us */
#ifdef SOLVER_DIAGNOSTICS
- printf("before solver:\n");
- for (i = 0; i <= n; i++)
- for (j = 0; j <= i; j++) {
- int k, m;
- m = 0;
- printf("%2d [%d %d]:", DINDEX(i, j), i, j);
- for (k = heads[DINDEX(i,j)]; k >= 0; k = placements[k])
- printf(" %3d [%d,%d,%c]", k, k/2%w, k/2/w, k%2?'h':'v');
+ if (solver_diagnostics)
+ printf("square %s has unique placement %s (domino %s)\n",
+ sq->name, p->name, p->domino->name);
+#endif
+
+ while (d->nplacements > 1)
+ rule_out_placement(
+ sc, d->placements[0] == p ? d->placements[1] : d->placements[0]);
+
+ return true;
+}
+
+/*
+ * If all placements for a square involve the same domino, rule out
+ * all other placements of that domino.
+ */
+static bool deduce_square_single_domino(struct solver_scratch *sc, int si)
+{
+ struct solver_square *sq = &sc->squares[si];
+ struct solver_domino *d;
+ int i;
+
+ /*
+ * We only bother with this if the square has at least _two_
+ * placements. If it only has one, then a simpler deduction will
+ * have handled it already, or will do so the next time round the
+ * main solver loop - and we should let the simpler deduction do
+ * it, because that will give a less overblown diagnostic.
+ */
+ if (sq->nplacements < 2)
+ return false;
+
+ d = sq->placements[0]->domino;
+ for (i = 1; i < sq->nplacements; i++)
+ if (sq->placements[i]->domino != d)
+ return false; /* not all the same domino */
+
+ if (d->nplacements <= sq->nplacements)
+ return false; /* no other placements of d to rule out */
+
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics)
+ printf("square %s can only contain domino %s\n", sq->name, d->name);
+#endif
+
+ for (i = d->nplacements; i-- > 0 ;) {
+ struct solver_placement *p = d->placements[i];
+ if (p->squares[0] != sq && p->squares[1] != sq)
+ rule_out_placement(sc, p);
+ }
+
+ return true;
+}
+
+/*
+ * If any placement is overlapped by _all_ possible placements of a
+ * given domino, rule that placement out.
+ */
+static bool deduce_domino_must_overlap(struct solver_scratch *sc, int di)
+{
+ struct solver_domino *d = &sc->dominoes[di];
+ struct solver_placement *intersection[6], *p;
+ int nintersection = 0;
+ int i, j, k;
+
+ /*
+ * As in deduce_square_single_domino, we only bother with this
+ * deduction if the domino has at least two placements.
+ */
+ if (d->nplacements < 2)
+ return false;
+
+ /* Initialise our set of overlapped placements with all the active
+ * ones overlapped by placements[0]. */
+ p = d->placements[0];
+ for (i = 0; i < p->noverlaps; i++)
+ if (p->overlaps[i]->active)
+ intersection[nintersection++] = p->overlaps[i];
+
+ /* Now loop over the other placements of d, winnowing that set. */
+ for (j = 1; j < d->nplacements; j++) {
+ int old_n;
+
+ p = d->placements[j];
+
+ old_n = nintersection;
+ nintersection = 0;
+
+ for (k = 0; k < old_n; k++) {
+ for (i = 0; i < p->noverlaps; i++)
+ if (p->overlaps[i] == intersection[k])
+ goto found;
+ /* If intersection[k] isn't in p->overlaps, exclude it
+ * from our set of placements overlapped by everything */
+ continue;
+ found:
+ intersection[nintersection++] = intersection[k];
+ }
+ }
+
+ if (nintersection == 0)
+ return false; /* no new exclusions */
+
+ for (i = 0; i < nintersection; i++) {
+ p = intersection[i];
+
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics) {
+ printf("placement %s of domino %s overlaps all placements "
+ "of domino %s:", p->name, p->domino->name, d->name);
+ for (j = 0; j < d->nplacements; j++)
+ printf(" %s", d->placements[j]->name);
printf("\n");
}
#endif
+ rule_out_placement(sc, p);
+ }
- while (1) {
- bool done_something = false;
+ return true;
+}
- /*
- * For each domino, look at its possible placements, and
- * for each placement consider the placements (of any
- * domino) it overlaps. Any placement overlapped by all
- * placements of this domino can be ruled out.
- *
- * Each domino placement overlaps only six others, so we
- * need not do serious set theory to work this out.
- */
- for (i = 0; i < dc; i++) {
- int permset[6], permlen = 0, p;
-
+/*
+ * If a placement of domino D overlaps the only remaining placement
+ * for some square S which is not also for domino D, then placing D
+ * here would require another copy of it in S, so we can rule it out.
+ */
+static bool deduce_local_duplicate(struct solver_scratch *sc, int pi)
+{
+ struct solver_placement *p = &sc->placements[pi];
+ struct solver_domino *d = p->domino;
+ int i, j;
- if (heads[i] == -1) { /* no placement for this domino */
- ret = 0; /* therefore puzzle is impossible */
- goto done;
- }
- for (j = heads[i]; j >= 0; j = placements[j]) {
- assert(placements[j] != -2);
+ if (!p->active)
+ return false;
- if (j == heads[i]) {
- permlen = find_overlaps(w, h, j, permset);
- } else {
- int tempset[6], templen, m, n, k;
+ for (i = 0; i < p->noverlaps; i++) {
+ struct solver_placement *q = p->overlaps[i];
+ struct solver_square *sq;
- templen = find_overlaps(w, h, j, tempset);
+ if (!q->active)
+ continue;
- /*
- * Pathetically primitive set intersection
- * algorithm, which I'm only getting away with
- * because I know my sets are bounded by a very
- * small size.
- */
- for (m = n = 0; m < permlen; m++) {
- for (k = 0; k < templen; k++)
- if (tempset[k] == permset[m])
- break;
- if (k < templen)
- permset[n++] = permset[m];
- }
- permlen = n;
- }
- }
- for (p = 0; p < permlen; p++) {
- j = permset[p];
- if (placements[j] != -2) {
- int p1, p2, di;
+ /* Find the square of q that _isn't_ part of p */
+ sq = q->squares[1 - common_square_index(q, p)];
- done_something = true;
+ for (j = 0; j < sq->nplacements; j++)
+ if (sq->placements[j] != q && sq->placements[j]->domino != d)
+ goto no;
- /*
- * Rule out this placement. First find what
- * domino it is...
- */
- p1 = j / 2;
- p2 = (j & 1) ? p1 + 1 : p1 + w;
- di = DINDEX(grid[p1], grid[p2]);
+ /* If we get here, every possible placement for sq is either q
+ * itself, or another copy of d. Success! We can rule out p. */
#ifdef SOLVER_DIAGNOSTICS
- printf("considering domino %d: ruling out placement %d"
- " for %d\n", i, j, di);
+ if (solver_diagnostics) {
+ printf("placement %s of domino %s would force another copy of %s "
+ "in square %s\n", p->name, d->name, d->name, sq->name);
+ }
#endif
- /*
- * ... then walk that domino's placement list,
- * removing this placement when we find it.
- */
- if (heads[di] == j)
- heads[di] = placements[j];
- else {
- int k = heads[di];
- while (placements[k] != -1 && placements[k] != j)
- k = placements[k];
- assert(placements[k] == j);
- placements[k] = placements[j];
- }
- placements[j] = -2;
- }
+ rule_out_placement(sc, p);
+ return true;
+
+ no:;
+ }
+
+ return false;
+}
+
+/*
+ * If placement P overlaps one placement for each of two squares S,T
+ * such that all the remaining placements for both S and T are the
+ * same domino D (and none of those placements joins S and T to each
+ * other), then P can't be placed, because it would leave S,T each
+ * having to be a copy of D, i.e. duplicates.
+ */
+static bool deduce_local_duplicate_2(struct solver_scratch *sc, int pi)
+{
+ struct solver_placement *p = &sc->placements[pi];
+ int i, j, k;
+
+ if (!p->active)
+ return false;
+
+ /*
+ * Iterate over pairs of placements qi,qj overlapping p.
+ */
+ for (i = 0; i < p->noverlaps; i++) {
+ struct solver_placement *qi = p->overlaps[i];
+ struct solver_square *sqi;
+ struct solver_domino *di = NULL;
+
+ if (!qi->active)
+ continue;
+
+ /* Find the square of qi that _isn't_ part of p */
+ sqi = qi->squares[1 - common_square_index(qi, p)];
+
+ /*
+ * Identify the unique domino involved in all possible
+ * placements of sqi other than qi. If there isn't a unique
+ * one (either too many or too few), move on and try the next
+ * qi.
+ */
+ for (k = 0; k < sqi->nplacements; k++) {
+ struct solver_placement *pk = sqi->placements[k];
+ if (sqi->placements[k] == qi)
+ continue; /* not counting qi itself */
+ if (!di)
+ di = pk->domino;
+ else if (di != pk->domino)
+ goto done_qi;
+ }
+ if (!di)
+ goto done_qi;
+
+ /*
+ * Now find an appropriate qj != qi.
+ */
+ for (j = 0; j < p->noverlaps; j++) {
+ struct solver_placement *qj = p->overlaps[j];
+ struct solver_square *sqj;
+ bool found_di = false;
+
+ if (j == i || !qj->active)
+ continue;
+
+ sqj = qj->squares[1 - common_square_index(qj, p)];
+
+ /*
+ * As above, we want the same domino di to be the only one
+ * sqj can be if placement qj is ruled out. But also we
+ * need no placement of sqj to overlap sqi.
+ */
+ for (k = 0; k < sqj->nplacements; k++) {
+ struct solver_placement *pk = sqj->placements[k];
+ if (pk == qj)
+ continue; /* not counting qj itself */
+ if (pk->domino != di)
+ goto done_qj; /* found a different domino */
+ if (pk->squares[0] == sqi || pk->squares[1] == sqi)
+ goto done_qj; /* sqi,sqj can be joined to each other */
+ found_di = true;
}
+ if (!found_di)
+ goto done_qj;
+
+ /* If we get here, then every placement for either of sqi
+ * and sqj is a copy of di, except for the ones that
+ * overlap p. Success! We can rule out p. */
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics) {
+ printf("placement %s of domino %s would force squares "
+ "%s and %s to both be domino %s\n",
+ p->name, p->domino->name,
+ sqi->name, sqj->name, di->name);
+ }
+#endif
+ rule_out_placement(sc, p);
+ return true;
+
+ done_qj:;
+ }
+
+ done_qi:;
+ }
+
+ return false;
+}
+
+struct parity_findloop_ctx {
+ struct solver_scratch *sc;
+ struct solver_square *sq;
+ int i;
+};
+
+int parity_neighbour(int vertex, void *vctx)
+{
+ struct parity_findloop_ctx *ctx = (struct parity_findloop_ctx *)vctx;
+ struct solver_placement *p;
+
+ if (vertex >= 0) {
+ ctx->sq = &ctx->sc->squares[vertex];
+ ctx->i = 0;
+ } else {
+ assert(ctx->sq);
+ }
+
+ if (ctx->i >= ctx->sq->nplacements) {
+ ctx->sq = NULL;
+ return -1;
+ }
+
+ p = ctx->sq->placements[ctx->i++];
+ return p->squares[0]->index + p->squares[1]->index - ctx->sq->index;
+}
+
+/*
+ * Look for dominoes whose placement would disconnect the unfilled
+ * area of the grid into pieces with odd area. Such a domino can't be
+ * placed, because then the area on each side of it would be
+ * untileable.
+ */
+static bool deduce_parity(struct solver_scratch *sc)
+{
+ struct parity_findloop_ctx pflctx;
+ bool done_something = false;
+ int pi;
+
+ /*
+ * Run findloop, aka Tarjan's bridge-finding algorithm, on the
+ * graph whose vertices are squares, with two vertices separated
+ * by an edge iff some not-yet-ruled-out domino placement covers
+ * them both. (So each edge itself corresponds to a domino
+ * placement.)
+ *
+ * The effect is that any bridge in this graph is a domino whose
+ * placement would separate two previously connected areas of the
+ * unfilled squares of the grid.
+ *
+ * Placing that domino would not just disconnect those areas from
+ * each other, but also use up one square of each. So if we want
+ * to avoid leaving two odd areas after placing the domino, it
+ * follows that we want to avoid the bridge having an _even_
+ * number of vertices on each side.
+ */
+ pflctx.sc = sc;
+ findloop_run(sc->fls, sc->wh, parity_neighbour, &pflctx);
+
+ for (pi = 0; pi < sc->pc; pi++) {
+ struct solver_placement *p = &sc->placements[pi];
+ int size0, size1;
+
+ if (!p->active)
+ continue;
+ if (!findloop_is_bridge(
+ sc->fls, p->squares[0]->index, p->squares[1]->index,
+ &size0, &size1))
+ continue;
+ /* To make a deduction, size0 and size1 must both be even,
+ * i.e. after placing this domino decrements each by 1 they
+ * would both become odd and untileable areas. */
+ if ((size0 | size1) & 1)
+ continue;
+
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics) {
+ printf("placement %s of domino %s would create two odd-sized "
+ "areas\n", p->name, p->domino->name);
+ }
+#endif
+ rule_out_placement(sc, p);
+ done_something = true;
+ }
+
+ return done_something;
+}
+
+/*
+ * Try to find a set of squares all containing the same number, such
+ * that the set of possible dominoes for all the squares in that set
+ * is small enough to let us rule out placements of those dominoes
+ * elsewhere.
+ */
+static bool deduce_set(struct solver_scratch *sc, bool doubles)
+{
+ struct solver_square **sqs, **sqp, **sqe;
+ int num, nsq, i, j;
+ unsigned long domino_sets[16], adjacent[16];
+ struct solver_domino *ds[16];
+ bool done_something = false;
+
+ if (!sc->squares_by_number)
+ sc->squares_by_number = snewn(sc->wh, struct solver_square *);
+ if (!sc->wh_scratch)
+ sc->wh_scratch = snewn(sc->wh, int);
+
+ if (!sc->squares_by_number_initialised) {
+ /*
+ * If this is the first call to this function for a given
+ * grid, start by sorting the squares by their containing
+ * number.
+ */
+ for (i = 0; i < sc->wh; i++)
+ sc->squares_by_number[i] = &sc->squares[i];
+ qsort(sc->squares_by_number, sc->wh, sizeof(*sc->squares_by_number),
+ squares_by_number_cmpfn);
+ }
+
+ sqp = sc->squares_by_number;
+ sqe = sc->squares_by_number + sc->wh;
+ for (num = 0; num <= sc->n; num++) {
+ unsigned long squares;
+ unsigned long squares_done;
+
+ /* Find the bounds of the subinterval of squares_by_number
+ * containing squares with this particular number. */
+ sqs = sqp;
+ while (sqp < sqe && (*sqp)->number == num)
+ sqp++;
+ nsq = sqp - sqs;
+
+ /*
+ * Now sqs[0], ..., sqs[nsq-1] are the squares containing 'num'.
+ */
+
+ if (nsq > lenof(domino_sets)) {
+ /*
+ * Abort this analysis if we're trying to enumerate all
+ * the subsets of a too-large base set.
+ *
+ * This _shouldn't_ happen, at the time of writing this
+ * code, because the largest puzzle we support is only
+ * supposed to have 10 instances of each number, and part
+ * of our input grid validation checks that each number
+ * does appear the right number of times. But just in case
+ * weird test input makes its way to this function, or the
+ * puzzle sizes are expanded later, it's easy enough to
+ * just rule out doing this analysis for overlarge sets of
+ * numbers.
+ */
+ continue;
}
/*
- * For each square, look at the available placements
- * involving that square. If all of them are for the same
- * domino, then rule out any placements for that domino
- * _not_ involving this square.
+ * Index the squares in wh_scratch, which we're using as a
+ * lookup table to map the official index of a square back to
+ * its value in our local indexing scheme.
*/
- for (i = 0; i < wh; i++) {
- int list[4], k, n, adi;
+ for (i = 0; i < nsq; i++)
+ sc->wh_scratch[sqs[i]->index] = i;
- x = i % w;
- y = i / w;
+ /*
+ * For each square, make a bit mask of the dominoes that can
+ * overlap it, by finding the number at the other end of each
+ * one.
+ *
+ * Also, for each square, make a bit mask of other squares in
+ * the current list that might occupy the _same_ domino
+ * (because a possible placement of a double overlaps both).
+ * We'll need that for evaluating whether sets are properly
+ * exhaustive.
+ */
+ for (i = 0; i < nsq; i++)
+ adjacent[i] = 0;
- j = 0;
- if (x > 0)
- list[j++] = 2*(i-1)+1;
- if (x+1 < w)
- list[j++] = 2*i+1;
- if (y > 0)
- list[j++] = 2*(i-w);
- if (y+1 < h)
- list[j++] = 2*i;
+ for (i = 0; i < nsq; i++) {
+ struct solver_square *sq = sqs[i];
+ unsigned long mask = 0;
- for (n = k = 0; k < j; k++)
- if (placements[list[k]] >= -1)
- list[n++] = list[k];
+ for (j = 0; j < sq->nplacements; j++) {
+ struct solver_placement *p = sq->placements[j];
+ int othernum = p->domino->lo + p->domino->hi - num;
+ mask |= 1UL << othernum;
+ ds[othernum] = p->domino; /* so we can find them later */
- adi = -1;
-
- for (j = 0; j < n; j++) {
- int p1, p2, di;
- k = list[j];
-
- p1 = k / 2;
- p2 = (k & 1) ? p1 + 1 : p1 + w;
- di = DINDEX(grid[p1], grid[p2]);
-
- if (adi == -1)
- adi = di;
- if (adi != di)
- break;
+ if (othernum == num) {
+ /*
+ * Special case: this is a double, so it gives
+ * rise to entries in adjacent[].
+ */
+ int i2 = sc->wh_scratch[p->squares[0]->index +
+ p->squares[1]->index - sq->index];
+ adjacent[i] |= 1UL << i2;
+ adjacent[i2] |= 1UL << i;
+ }
}
- if (j == n) {
- int nn;
+ domino_sets[i] = mask;
- assert(adi >= 0);
+ }
+
+ squares_done = 0;
+
+ for (squares = 0; squares < (1UL << nsq); squares++) {
+ unsigned long dominoes = 0;
+ int bitpos, nsquares, ndominoes;
+ bool got_adj_squares = false;
+ bool reported = false;
+ bool rule_out_nondoubles;
+ int min_nused_for_double;
+#ifdef SOLVER_DIAGNOSTICS
+ const char *rule_out_text;
+#endif
+
+ /*
+ * We don't do set analysis on the same square of the grid
+ * more than once in this loop. Otherwise you generate
+ * pointlessly overcomplicated diagnostics for simpler
+ * follow-up deductions. For example, suppose squares
+ * {A,B} must go with dominoes {X,Y}. So you rule out X,Y
+ * elsewhere, and then it turns out square C (from which
+ * one of those was eliminated) has only one remaining
+ * possibility Z. What you _don't_ want to do is
+ * triumphantly report a second case of set elimination
+ * where you say 'And also, squares {A,B,C} have to be
+ * {X,Y,Z}!' You'd prefer to give 'now C has to be Z' as a
+ * separate deduction later, more simply phrased.
+ */
+ if (squares & squares_done)
+ continue;
+
+ nsquares = 0;
+
+ /* Make the set of dominoes that these squares can inhabit. */
+ for (bitpos = 0; bitpos < nsq; bitpos++) {
+ if (!(1 & (squares >> bitpos)))
+ continue; /* this bit isn't set in the mask */
+
+ if (adjacent[bitpos] & squares)
+ got_adj_squares = true;
+
+ dominoes |= domino_sets[bitpos];
+ nsquares++;
+ }
+
+ /* Count them. */
+ ndominoes = 0;
+ for (bitpos = 0; bitpos < nsq; bitpos++)
+ ndominoes += 1 & (dominoes >> bitpos);
+
+ /*
+ * Do the two sets have the right relative size?
+ */
+ if (!got_adj_squares) {
/*
- * We've found something. All viable placements
- * involving this square are for domino `adi'. If
- * the current placement list for that domino is
- * longer than n, reduce it to precisely this
- * placement list and we've done something.
+ * The normal case, in which every possible domino
+ * placement involves at most _one_ of these squares.
+ *
+ * This is exactly analogous to the set analysis
+ * deductions in many other puzzles: if our N squares
+ * between them have to account for N distinct
+ * dominoes, with exactly one of those dominoes to
+ * each square, then all those dominoes correspond to
+ * all those squares and we can rule out any
+ * placements of the same dominoes appearing
+ * elsewhere.
*/
- nn = 0;
- for (k = heads[adi]; k >= 0; k = placements[k])
- nn++;
- if (nn > n) {
- done_something = true;
+ if (ndominoes != nsquares)
+ continue;
+ rule_out_nondoubles = true;
+ min_nused_for_double = 1;
#ifdef SOLVER_DIAGNOSTICS
- printf("considering square %d,%d: reducing placements "
- "of domino %d\n", x, y, adi);
+ rule_out_text = "all of them elsewhere";
#endif
+ } else {
+ if (!doubles)
+ continue; /* not at this difficulty level */
+
+ /*
+ * But in Dominosa, there's a special case if _two_
+ * squares in this set can possibly both be covered by
+ * the same double domino. (I.e. if they are adjacent,
+ * and moreover, the double-domino placement
+ * containing both is not yet ruled out.)
+ *
+ * In that situation, the simple argument doesn't hold
+ * up, because the N squares might be covered by N-1
+ * dominoes - or, put another way, if you list the
+ * containing domino for each of the squares, they
+ * might not be all distinct.
+ *
+ * In that situation, we can still do something, but
+ * the details vary, and there are two further cases.
+ */
+ if (ndominoes == nsquares-1) {
/*
- * Set all other placements on the list to
- * impossible.
+ * Suppose there is one _more_ square in our set
+ * than there are dominoes it can involve. For
+ * example, suppose we had four '0' squares which
+ * between them could contain only the 0-0, 0-1
+ * and 0-2 dominoes.
+ *
+ * Then that can only work at all if the 0-0
+ * covers two of those squares - and in that
+ * situation that _must_ be what's happened.
+ *
+ * So we can rule out the 0-1 and 0-2 dominoes (in
+ * this example) in any placement that doesn't use
+ * one of the squares in this set. And we can rule
+ * out a placement of the 0-0 even if it uses
+ * _one_ square from this set: in this situation,
+ * we have to insist on it using _two_.
*/
- k = heads[adi];
- while (k >= 0) {
- int tmp = placements[k];
- placements[k] = -2;
- k = tmp;
+ rule_out_nondoubles = true;
+ min_nused_for_double = 2;
+#ifdef SOLVER_DIAGNOSTICS
+ rule_out_text = "all of them elsewhere "
+ "(including the double if it fails to use both)";
+#endif
+ } else if (ndominoes == nsquares) {
+ /*
+ * A restricted form of the deduction is still
+ * possible if we have the same number of dominoes
+ * as squares.
+ *
+ * If we have _three_ '0' squares none of which
+ * can be any domino other than 0-0, 0-1 and 0-2,
+ * and there's still a possibility of an 0-0
+ * domino using up two of them, then we can't rule
+ * out 0-1 or 0-2 anywhere else, because it's
+ * possible that these three squares only use two
+ * of the dominoes between them.
+ *
+ * But we _can_ rule out the double 0-0, in any
+ * placement that uses _none_ of our three
+ * squares. Because we do know that _at least one_
+ * of our squares must be involved in the 0-0, or
+ * else the three of them would only have the
+ * other two dominoes left.
+ */
+ rule_out_nondoubles = false;
+ min_nused_for_double = 1;
+#ifdef SOLVER_DIAGNOSTICS
+ rule_out_text = "the double elsewhere";
+#endif
+ } else {
+ /*
+ * If none of those cases has happened, then our
+ * set admits no deductions at all.
+ */
+ continue;
+ }
+ }
+
+ /* Skip sets of size 1, or whose complement has size 1.
+ * Those can be handled by a simpler analysis, and should
+ * be, for more sensible solver diagnostics. */
+ if (ndominoes <= 1 || ndominoes >= nsq-1)
+ continue;
+
+ /*
+ * We've found a set! That means we can rule out any
+ * placement of any domino in that set which would leave
+ * the squares in the set with too few dominoes between
+ * them.
+ *
+ * We may or may not actually end up ruling anything out
+ * here. But even if we don't, we should record that these
+ * squares form a self-contained set, so that we don't
+ * pointlessly report a superset of them later which could
+ * instead be reported as just the other ones.
+ *
+ * Or rather, we do that for the main cases that let us
+ * rule out lots of dominoes. We only do this with the
+ * borderline case where we can only rule out a double if
+ * we _actually_ rule something out. Otherwise we'll never
+ * even _find_ a larger set with the same number of
+ * dominoes!
+ */
+ if (rule_out_nondoubles)
+ squares_done |= squares;
+
+ for (bitpos = 0; bitpos < nsq; bitpos++) {
+ struct solver_domino *d;
+
+ if (!(1 & (dominoes >> bitpos)))
+ continue;
+ d = ds[bitpos];
+
+ for (i = d->nplacements; i-- > 0 ;) {
+ struct solver_placement *p = d->placements[i];
+ int si, nused;
+
+ /* Count how many of our squares this placement uses. */
+ for (si = nused = 0; si < 2; si++) {
+ struct solver_square *sq2 = p->squares[si];
+ if (sq2->number == num &&
+ (1 & (squares >> sc->wh_scratch[sq2->index])))
+ nused++;
}
- /*
- * Set up the new list.
- */
- heads[adi] = list[0];
- for (k = 0; k < n; k++)
- placements[list[k]] = (k+1 == n ? -1 : list[k+1]);
+
+ /* See if that's too many to rule it out. */
+ if (d->lo == d->hi) {
+ if (nused >= min_nused_for_double)
+ continue;
+ } else {
+ if (nused > 0 || !rule_out_nondoubles)
+ continue;
+ }
+
+ if (!reported) {
+ reported = true;
+ done_something = true;
+
+ /* In case we didn't do this above */
+ squares_done |= squares;
+
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics) {
+ int b;
+ const char *sep;
+ printf("squares {");
+ for (sep = "", b = 0; b < nsq; b++)
+ if (1 & (squares >> b)) {
+ printf("%s%s", sep, sqs[b]->name);
+ sep = ",";
+ }
+ printf("} can contain only dominoes {");
+ for (sep = "", b = 0; b < nsq; b++)
+ if (1 & (dominoes >> b)) {
+ printf("%s%s", sep, ds[b]->name);
+ sep = ",";
+ }
+ printf("}, so rule out %s", rule_out_text);
+ printf("\n");
+ }
+#endif
+ }
+
+ rule_out_placement(sc, p);
}
}
}
- if (!done_something)
- break;
}
+ return done_something;
+}
+
+static int forcing_chain_dup_cmp(const void *av, const void *bv, void *ctx)
+{
+ struct solver_scratch *sc = (struct solver_scratch *)ctx;
+ int a = *(const int *)av, b = *(const int *)bv;
+ int ac, bc;
+
+ ac = sc->pc_scratch[a];
+ bc = sc->pc_scratch[b];
+ if (ac != bc) return ac > bc ? +1 : -1;
+
+ ac = sc->placements[a].domino->index;
+ bc = sc->placements[b].domino->index;
+ if (ac != bc) return ac > bc ? +1 : -1;
+
+ return 0;
+}
+
+static int forcing_chain_sq_cmp(const void *av, const void *bv, void *ctx)
+{
+ struct solver_scratch *sc = (struct solver_scratch *)ctx;
+ int a = *(const int *)av, b = *(const int *)bv;
+ int ac, bc;
+
+ ac = sc->placements[a].domino->index;
+ bc = sc->placements[b].domino->index;
+ if (ac != bc) return ac > bc ? +1 : -1;
+
+ ac = sc->pc_scratch[a];
+ bc = sc->pc_scratch[b];
+ if (ac != bc) return ac > bc ? +1 : -1;
+
+ return 0;
+}
+
+static bool deduce_forcing_chain(struct solver_scratch *sc)
+{
+ int si, pi, di, j, k, m;
+ bool done_something = false;
+
+ if (!sc->wh_scratch)
+ sc->wh_scratch = snewn(sc->wh, int);
+ if (!sc->pc_scratch)
+ sc->pc_scratch = snewn(sc->pc, int);
+ if (!sc->pc_scratch2)
+ sc->pc_scratch2 = snewn(sc->pc, int);
+ if (!sc->dc_scratch)
+ sc->dc_scratch = snewn(sc->dc, int);
+
+ /*
+ * Start by identifying chains of placements which must all occur
+ * together if any of them occurs. We do this by making
+ * pc_scratch2 an edsf binding the placements into an equivalence
+ * class for each entire forcing chain, with the two possible sets
+ * of dominoes for the chain listed as inverses.
+ */
+ dsf_init(sc->pc_scratch2, sc->pc);
+ for (si = 0; si < sc->wh; si++) {
+ struct solver_square *sq = &sc->squares[si];
+ if (sq->nplacements == 2)
+ edsf_merge(sc->pc_scratch2,
+ sq->placements[0]->index,
+ sq->placements[1]->index, true);
+ }
+ /*
+ * Now read out the whole dsf into pc_scratch, flattening its
+ * structured data into a simple integer id per chain of dominoes
+ * that must occur together.
+ *
+ * The integer ids have the property that any two that differ only
+ * in the lowest bit (i.e. of the form {2n,2n+1}) represent
+ * complementary chains, each of which rules out the other.
+ */
+ for (pi = 0; pi < sc->pc; pi++) {
+ bool inv;
+ int c = edsf_canonify(sc->pc_scratch2, pi, &inv);
+ sc->pc_scratch[pi] = c * 2 + (inv ? 1 : 0);
+ }
+
+ /*
+ * Identify chains that contain a duplicate domino, and rule them
+ * out. We do this by making a list of the placement indices in
+ * pc_scratch2, sorted by (chain id, domino id), so that dupes
+ * become adjacent.
+ */
+ for (pi = 0; pi < sc->pc; pi++)
+ sc->pc_scratch2[pi] = pi;
+ arraysort(sc->pc_scratch2, sc->pc, forcing_chain_dup_cmp, sc);
+
+ for (j = 0; j < sc->pc ;) {
+ struct solver_domino *duplicated_domino = NULL;
+
+ /*
+ * This loop iterates once per contiguous segment of the same
+ * value in pc_scratch2, i.e. once per chain.
+ */
+ int ci = sc->pc_scratch[sc->pc_scratch2[j]];
+ int climit, cstart = j;
+ while (j < sc->pc && sc->pc_scratch[sc->pc_scratch2[j]] == ci)
+ j++;
+ climit = j;
+
+ /*
+ * Now look for a duplicate domino within that chain.
+ */
+ for (k = cstart; k + 1 < climit; k++) {
+ struct solver_placement *p = &sc->placements[sc->pc_scratch2[k]];
+ struct solver_placement *q = &sc->placements[sc->pc_scratch2[k+1]];
+ if (p->domino == q->domino) {
+ duplicated_domino = p->domino;
+ break;
+ }
+ }
+
+ if (!duplicated_domino)
+ continue;
+
#ifdef SOLVER_DIAGNOSTICS
- printf("after solver:\n");
- for (i = 0; i <= n; i++)
- for (j = 0; j <= i; j++) {
- int k, m;
- m = 0;
- printf("%2d [%d %d]:", DINDEX(i, j), i, j);
- for (k = heads[DINDEX(i,j)]; k >= 0; k = placements[k])
- printf(" %3d [%d,%d,%c]", k, k/2%w, k/2/w, k%2?'h':'v');
+ if (solver_diagnostics) {
+ printf("domino %s occurs more than once in forced chain:",
+ duplicated_domino->name);
+ for (k = cstart; k < climit; k++)
+ printf(" %s", sc->placements[sc->pc_scratch2[k]].name);
printf("\n");
}
#endif
- ret = 1;
- for (i = 0; i < wh*2; i++) {
- if (placements[i] == -2) {
- if (output)
- output[i] = -1; /* ruled out */
- } else if (placements[i] != -3) {
- int p1, p2, di;
+ for (k = cstart; k < climit; k++)
+ rule_out_placement(sc, &sc->placements[sc->pc_scratch2[k]]);
- p1 = i / 2;
- p2 = (i & 1) ? p1 + 1 : p1 + w;
- di = DINDEX(grid[p1], grid[p2]);
+ done_something = true;
+ }
- if (i == heads[di] && placements[i] == -1) {
- if (output)
- output[i] = 1; /* certain */
- } else {
- if (output)
- output[i] = 0; /* uncertain */
- ret = 2;
+ if (done_something)
+ return true;
+
+ /*
+ * A second way in which a whole forcing chain can be ruled out is
+ * if it contains all the dominoes that can occupy some other
+ * square, so that if the domnioes in the chain were all laid, the
+ * other square would be left without any choices.
+ *
+ * To detect this, we sort the placements again, this time by
+ * (domino index, chain index), so that we can easily find a
+ * sorted list of chains per domino. That allows us to iterate
+ * over the squares and check for a chain id common to all the
+ * placements of that square.
+ */
+ for (pi = 0; pi < sc->pc; pi++)
+ sc->pc_scratch2[pi] = pi;
+ arraysort(sc->pc_scratch2, sc->pc, forcing_chain_sq_cmp, sc);
+
+ /* Store a lookup table of the first entry in pc_scratch2
+ * corresponding to each domino. */
+ for (di = j = 0; j < sc->pc; j++) {
+ while (di <= sc->placements[sc->pc_scratch2[j]].domino->index) {
+ assert(di < sc->dc);
+ sc->dc_scratch[di++] = j;
+ }
+ }
+ assert(di == sc->dc);
+
+ for (si = 0; si < sc->wh; si++) {
+ struct solver_square *sq = &sc->squares[si];
+ int listpos = 0, listsize = 0, listout = 0;
+ int exclude[4];
+ int n_exclude;
+
+ if (sq->nplacements < 2)
+ continue; /* too simple to be worth trying */
+
+ /*
+ * Start by checking for chains this square can actually form
+ * part of. We won't consider those. (The aim is to find a
+ * completely _different_ square whose placements are all
+ * ruled out by a chain.)
+ */
+ assert(sq->nplacements <= lenof(exclude));
+ for (j = n_exclude = 0; j < sq->nplacements; j++)
+ exclude[n_exclude++] = sc->pc_scratch[sq->placements[j]->index];
+
+ for (j = 0; j < sq->nplacements; j++) {
+ struct solver_domino *d = sq->placements[j]->domino;
+
+ listout = listpos = 0;
+
+ for (k = sc->dc_scratch[d->index];
+ k < sc->pc && sc->placements[sc->pc_scratch2[k]].domino == d;
+ k++) {
+ int chain = sc->pc_scratch[sc->pc_scratch2[k]];
+ bool keep;
+
+ if (!sc->placements[sc->pc_scratch2[k]].active)
+ continue;
+
+ if (j == 0) {
+ keep = true;
+ } else {
+ while (listpos < listsize &&
+ sc->wh_scratch[listpos] < chain)
+ listpos++;
+ keep = (listpos < listsize &&
+ sc->wh_scratch[listpos] == chain);
+ }
+
+ for (m = 0; m < n_exclude; m++)
+ if (chain == exclude[m])
+ keep = false;
+
+ if (keep)
+ sc->wh_scratch[listout++] = chain;
}
+
+ listsize = listout;
+ if (listsize == 0)
+ break; /* ruled out all chains; terminate loop early */
+ }
+
+ for (listpos = 0; listpos < listsize; listpos++) {
+ int chain = sc->wh_scratch[listpos];
+
+ /*
+ * We've found a chain we can rule out.
+ */
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics) {
+ printf("all choices for square %s would be ruled out "
+ "by forced chain:", sq->name);
+ for (pi = 0; pi < sc->pc; pi++)
+ if (sc->pc_scratch[pi] == chain)
+ printf(" %s", sc->placements[pi].name);
+ printf("\n");
+ }
+#endif
+
+ for (pi = 0; pi < sc->pc; pi++)
+ if (sc->pc_scratch[pi] == chain)
+ rule_out_placement(sc, &sc->placements[pi]);
+
+ done_something = true;
}
}
- done:
/*
- * Free working data.
+ * Another thing you can do with forcing chains, besides ruling
+ * out a whole one at a time, is to look at each pair of chains
+ * that overlap each other. Each such pair gives you two sets of
+ * domino placements, such that if either set is not placed, then
+ * the other one must be.
+ *
+ * This means that any domino which has a placement in _both_
+ * chains of a pair must occupy one of those two placements, i.e.
+ * we can rule that domino out anywhere else it might appear.
*/
- sfree(placements);
- sfree(heads);
+ for (di = 0; di < sc->dc; di++) {
+ struct solver_domino *d = &sc->dominoes[di];
- return ret;
+ if (d->nplacements <= 2)
+ continue; /* not enough placements to rule one out */
+
+ for (j = 0; j+1 < d->nplacements; j++) {
+ int ij = d->placements[j]->index;
+ int cj = sc->pc_scratch[ij];
+ for (k = j+1; k < d->nplacements; k++) {
+ int ik = d->placements[k]->index;
+ int ck = sc->pc_scratch[ik];
+ if ((cj ^ ck) == 1) {
+ /*
+ * Placements j,k of domino d are in complementary
+ * chains, so we can rule out all the others.
+ */
+ int i;
+
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics) {
+ printf("domino %s occurs in both complementary "
+ "forced chains:", d->name);
+ for (i = 0; i < sc->pc; i++)
+ if (sc->pc_scratch[i] == cj)
+ printf(" %s", sc->placements[i].name);
+ printf(" and");
+ for (i = 0; i < sc->pc; i++)
+ if (sc->pc_scratch[i] == ck)
+ printf(" %s", sc->placements[i].name);
+ printf("\n");
+ }
+#endif
+
+ for (i = d->nplacements; i-- > 0 ;)
+ if (i != j && i != k)
+ rule_out_placement(sc, d->placements[i]);
+
+ done_something = true;
+ goto done_this_domino;
+ }
+ }
+ }
+
+ done_this_domino:;
+ }
+
+ return done_something;
+}
+
+/*
+ * Run the solver until it can't make any more progress.
+ *
+ * Return value is:
+ * 0 = no solution exists (puzzle clues are unsatisfiable)
+ * 1 = unique solution found (success!)
+ * 2 = multiple possibilities remain (puzzle is ambiguous or solver is not
+ * smart enough)
+ */
+static int run_solver(struct solver_scratch *sc, int max_diff_allowed)
+{
+ int di, si, pi;
+ bool done_something;
+
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics) {
+ int di, j;
+ printf("Initial possible placements:\n");
+ for (di = 0; di < sc->dc; di++) {
+ struct solver_domino *d = &sc->dominoes[di];
+ printf(" %s:", d->name);
+ for (j = 0; j < d->nplacements; j++)
+ printf(" %s", d->placements[j]->name);
+ printf("\n");
+ }
+ }
+#endif
+
+ do {
+ done_something = false;
+
+ for (di = 0; di < sc->dc; di++)
+ if (deduce_domino_single_placement(sc, di))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_TRIVIAL);
+ continue;
+ }
+
+ for (si = 0; si < sc->wh; si++)
+ if (deduce_square_single_placement(sc, si))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_TRIVIAL);
+ continue;
+ }
+
+ if (max_diff_allowed <= DIFF_TRIVIAL)
+ continue;
+
+ for (si = 0; si < sc->wh; si++)
+ if (deduce_square_single_domino(sc, si))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
+ continue;
+ }
+
+ for (di = 0; di < sc->dc; di++)
+ if (deduce_domino_must_overlap(sc, di))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
+ continue;
+ }
+
+ for (pi = 0; pi < sc->pc; pi++)
+ if (deduce_local_duplicate(sc, pi))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
+ continue;
+ }
+
+ for (pi = 0; pi < sc->pc; pi++)
+ if (deduce_local_duplicate_2(sc, pi))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
+ continue;
+ }
+
+ if (deduce_parity(sc))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
+ continue;
+ }
+
+ if (max_diff_allowed <= DIFF_BASIC)
+ continue;
+
+ if (deduce_set(sc, false))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_HARD);
+ continue;
+ }
+
+ if (max_diff_allowed <= DIFF_HARD)
+ continue;
+
+ if (deduce_set(sc, true))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_EXTREME);
+ continue;
+ }
+
+ if (deduce_forcing_chain(sc))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_EXTREME);
+ continue;
+ }
+
+ } while (done_something);
+
+#ifdef SOLVER_DIAGNOSTICS
+ if (solver_diagnostics) {
+ int di, j;
+ printf("Final possible placements:\n");
+ for (di = 0; di < sc->dc; di++) {
+ struct solver_domino *d = &sc->dominoes[di];
+ printf(" %s:", d->name);
+ for (j = 0; j < d->nplacements; j++)
+ printf(" %s", d->placements[j]->name);
+ printf("\n");
+ }
+ }
+#endif
+
+ for (di = 0; di < sc->dc; di++)
+ if (sc->dominoes[di].nplacements == 0)
+ return 0;
+ for (di = 0; di < sc->dc; di++)
+ if (sc->dominoes[di].nplacements > 1)
+ return 2;
+ return 1;
}
/* ----------------------------------------------------------------------
- * End of solver code.
+ * Functions for generating a candidate puzzle (before we run the
+ * solver to check it's soluble at the right difficulty level).
*/
+struct alloc_val;
+struct alloc_loc;
+
+struct alloc_scratch {
+ /* Game parameters. */
+ int n, w, h, wh, dc;
+
+ /* The domino layout. Indexed by squares in the usual y*w+x raster
+ * order: layout[i] gives the index of the other square in the
+ * same domino as square i. */
+ int *layout;
+
+ /* The output array, containing a number in every square. */
+ int *numbers;
+
+ /* List of domino values (i.e. number pairs), indexed by DINDEX. */
+ struct alloc_val *vals;
+
+ /* List of domino locations, indexed arbitrarily. */
+ struct alloc_loc *locs;
+
+ /* Preallocated scratch spaces. */
+ int *wh_scratch; /* size wh */
+ int *wh2_scratch; /* size 2*wh */
+};
+
+struct alloc_val {
+ int lo, hi;
+ bool confounder;
+};
+
+struct alloc_loc {
+ int sq[2];
+};
+
+static struct alloc_scratch *alloc_make_scratch(int n)
+{
+ struct alloc_scratch *as = snew(struct alloc_scratch);
+ int lo, hi;
+
+ as->n = n;
+ as->w = n+2;
+ as->h = n+1;
+ as->wh = as->w * as->h;
+ as->dc = DCOUNT(n);
+
+ as->layout = snewn(as->wh, int);
+ as->numbers = snewn(as->wh, int);
+ as->vals = snewn(as->dc, struct alloc_val);
+ as->locs = snewn(as->dc, struct alloc_loc);
+ as->wh_scratch = snewn(as->wh, int);
+ as->wh2_scratch = snewn(as->wh * 2, int);
+
+ for (hi = 0; hi <= n; hi++)
+ for (lo = 0; lo <= hi; lo++) {
+ struct alloc_val *v = &as->vals[DINDEX(hi, lo)];
+ v->lo = lo;
+ v->hi = hi;
+ }
+
+ return as;
+}
+
+static void alloc_free_scratch(struct alloc_scratch *as)
+{
+ sfree(as->layout);
+ sfree(as->numbers);
+ sfree(as->vals);
+ sfree(as->locs);
+ sfree(as->wh_scratch);
+ sfree(as->wh2_scratch);
+ sfree(as);
+}
+
+static void alloc_make_layout(struct alloc_scratch *as, random_state *rs)
+{
+ int i, pos;
+
+ domino_layout_prealloc(as->w, as->h, rs,
+ as->layout, as->wh_scratch, as->wh2_scratch);
+
+ for (i = pos = 0; i < as->wh; i++) {
+ if (as->layout[i] > i) {
+ struct alloc_loc *loc;
+ assert(pos < as->dc);
+
+ loc = &as->locs[pos++];
+ loc->sq[0] = i;
+ loc->sq[1] = as->layout[i];
+ }
+ }
+ assert(pos == as->dc);
+}
+
+static void alloc_trivial(struct alloc_scratch *as, random_state *rs)
+{
+ int i;
+ for (i = 0; i < as->dc; i++)
+ as->wh_scratch[i] = i;
+ shuffle(as->wh_scratch, as->dc, sizeof(*as->wh_scratch), rs);
+
+ for (i = 0; i < as->dc; i++) {
+ struct alloc_val *val = &as->vals[as->wh_scratch[i]];
+ struct alloc_loc *loc = &as->locs[i];
+ int which_lo = random_upto(rs, 2), which_hi = 1 - which_lo;
+ as->numbers[loc->sq[which_lo]] = val->lo;
+ as->numbers[loc->sq[which_hi]] = val->hi;
+ }
+}
+
+/*
+ * Given a domino location in the form of two square indices, compute
+ * the square indices of the domino location that would lie on one
+ * side of it. Returns false if the location would be outside the
+ * grid, or if it isn't actually a domino in the layout.
+ */
+static bool alloc_find_neighbour(
+ struct alloc_scratch *as, int p0, int p1, int *n0, int *n1)
+{
+ int x0 = p0 % as->w, y0 = p0 / as->w, x1 = p1 % as->w, y1 = p1 / as->w;
+ int dy = y1-y0, dx = x1-x0;
+ int nx0 = x0 + dy, ny0 = y0 - dx, nx1 = x1 + dy, ny1 = y1 - dx;
+ int np0, np1;
+
+ if (!(nx0 >= 0 && nx0 < as->w && ny0 >= 0 && ny0 < as->h &&
+ nx1 >= 1 && nx1 < as->w && ny1 >= 1 && ny1 < as->h))
+ return false; /* out of bounds */
+
+ np0 = ny0 * as->w + nx0;
+ np1 = ny1 * as->w + nx1;
+ if (as->layout[np0] != np1)
+ return false; /* not a domino */
+
+ *n0 = np0;
+ *n1 = np1;
+ return true;
+}
+
+static bool alloc_try_unique(struct alloc_scratch *as, random_state *rs)
+{
+ int i;
+ for (i = 0; i < as->dc; i++)
+ as->wh_scratch[i] = i;
+ shuffle(as->wh_scratch, as->dc, sizeof(*as->wh_scratch), rs);
+ for (i = 0; i < as->dc; i++)
+ as->wh2_scratch[i] = i;
+ shuffle(as->wh2_scratch, as->dc, sizeof(*as->wh2_scratch), rs);
+
+ for (i = 0; i < as->wh; i++)
+ as->numbers[i] = -1;
+
+ for (i = 0; i < as->dc; i++) {
+ struct alloc_val *val = &as->vals[as->wh_scratch[i]];
+ struct alloc_loc *loc = &as->locs[as->wh2_scratch[i]];
+ int which_lo, which_hi;
+ bool can_lo_0 = true, can_lo_1 = true;
+ int n0, n1;
+
+ /*
+ * This is basically the same strategy as alloc_trivial:
+ * simply iterate through the locations and values in random
+ * relative order and pair them up. But we make sure to avoid
+ * the most common, and also simplest, cause of a non-unique
+ * solution:two dominoes side by side, sharing a number at
+ * opposite ends. Any section of that form automatically leads
+ * to an alternative solution:
+ *
+ * +-------+ +---+---+
+ * | 1 2 | | 1 | 2 |
+ * +-------+ <-> | | |
+ * | 2 3 | | 2 | 3 |
+ * +-------+ +---+---+
+ *
+ * So as we place each domino, we check for a neighbouring
+ * domino on each side, and if there is one, rule out any
+ * placement of _this_ domino that places a number diagonally
+ * opposite the same number in the neighbour.
+ *
+ * Sometimes this can fail completely, if a domino on each
+ * side is already placed and between them they rule out all
+ * placements of this one. But it happens rarely enough that
+ * it's fine to just abort and try the layout again.
+ */
+
+ if (alloc_find_neighbour(as, loc->sq[0], loc->sq[1], &n0, &n1) &&
+ (as->numbers[n0] == val->hi || as->numbers[n1] == val->lo))
+ can_lo_0 = false;
+ if (alloc_find_neighbour(as, loc->sq[1], loc->sq[0], &n0, &n1) &&
+ (as->numbers[n0] == val->hi || as->numbers[n1] == val->lo))
+ can_lo_1 = false;
+
+ if (!can_lo_0 && !can_lo_1)
+ return false; /* layout failed */
+ else if (can_lo_0 && can_lo_1)
+ which_lo = random_upto(rs, 2);
+ else
+ which_lo = can_lo_0 ? 0 : 1;
+
+ which_hi = 1 - which_lo;
+ as->numbers[loc->sq[which_lo]] = val->lo;
+ as->numbers[loc->sq[which_hi]] = val->hi;
+ }
+
+ return true;
+}
+
+static bool alloc_try_hard(struct alloc_scratch *as, random_state *rs)
+{
+ int i, x, y, hi, lo, vals, locs, confounders_needed;
+ bool ok;
+
+ for (i = 0; i < as->wh; i++)
+ as->numbers[i] = -1;
+
+ /*
+ * Shuffle the location indices.
+ */
+ for (i = 0; i < as->dc; i++)
+ as->wh2_scratch[i] = i;
+ shuffle(as->wh2_scratch, as->dc, sizeof(*as->wh2_scratch), rs);
+
+ /*
+ * Start by randomly placing the double dominoes, to give a
+ * starting instance of every number to try to put other things
+ * next to.
+ */
+ for (i = 0; i <= as->n; i++)
+ as->wh_scratch[i] = DINDEX(i, i);
+ shuffle(as->wh_scratch, i, sizeof(*as->wh_scratch), rs);
+ for (i = 0; i <= as->n; i++) {
+ struct alloc_loc *loc = &as->locs[as->wh2_scratch[i]];
+ as->numbers[loc->sq[0]] = as->numbers[loc->sq[1]] = i;
+ }
+
+ /*
+ * Find all the dominoes that don't yet have a _wrong_ placement
+ * somewhere in the grid.
+ */
+ for (i = 0; i < as->dc; i++)
+ as->vals[i].confounder = false;
+ for (y = 0; y < as->h; y++) {
+ for (x = 0; x < as->w; x++) {
+ int p = y * as->w + x;
+ if (as->numbers[p] == -1)
+ continue;
+
+ if (x+1 < as->w) {
+ int p1 = y * as->w + (x+1);
+ if (as->layout[p] != p1 && as->numbers[p1] != -1)
+ as->vals[DINDEX(as->numbers[p], as->numbers[p1])]
+ .confounder = true;
+ }
+ if (y+1 < as->h) {
+ int p1 = (y+1) * as->w + x;
+ if (as->layout[p] != p1 && as->numbers[p1] != -1)
+ as->vals[DINDEX(as->numbers[p], as->numbers[p1])]
+ .confounder = true;
+ }
+ }
+ }
+
+ for (i = confounders_needed = 0; i < as->dc; i++)
+ if (!as->vals[i].confounder)
+ confounders_needed++;
+
+ /*
+ * Make a shuffled list of all the unplaced dominoes, and go
+ * through it trying to find a placement for each one that also
+ * fills in at least one of the needed confounders.
+ */
+ vals = 0;
+ for (hi = 0; hi <= as->n; hi++)
+ for (lo = 0; lo < hi; lo++)
+ as->wh_scratch[vals++] = DINDEX(hi, lo);
+ shuffle(as->wh_scratch, vals, sizeof(*as->wh_scratch), rs);
+
+ locs = as->dc;
+
+ while (vals > 0) {
+ int valpos, valout, oldvals = vals;
+
+ for (valpos = valout = 0; valpos < vals; valpos++) {
+ int validx = as->wh_scratch[valpos];
+ struct alloc_val *val = &as->vals[validx];
+ struct alloc_loc *loc;
+ int locpos, si, which_lo;
+
+ for (locpos = 0; locpos < locs; locpos++) {
+ int locidx = as->wh2_scratch[locpos];
+ int wi, flip;
+
+ loc = &as->locs[locidx];
+ if (as->numbers[loc->sq[0]] != -1)
+ continue; /* this location is already filled */
+
+ flip = random_upto(rs, 2);
+
+ /* Try this location both ways round. */
+ for (wi = 0; wi < 2; wi++) {
+ int n0, n1;
+
+ which_lo = wi ^ flip;
+
+ /* First, do the same check as in alloc_try_unique, to
+ * avoid making an obviously insoluble puzzle. */
+ if (alloc_find_neighbour(as, loc->sq[which_lo],
+ loc->sq[1-which_lo], &n0, &n1) &&
+ (as->numbers[n0] == val->hi ||
+ as->numbers[n1] == val->lo))
+ break; /* can't place it this way round */
+
+ if (confounders_needed == 0)
+ goto place_ok;
+
+ /* Look to see if we're adding at least one
+ * previously absent confounder. */
+ for (si = 0; si < 2; si++) {
+ int x = loc->sq[si] % as->w, y = loc->sq[si] / as->w;
+ int n = (si == which_lo ? val->lo : val->hi);
+ int d;
+ for (d = 0; d < 4; d++) {
+ int dx = d==0 ? +1 : d==2 ? -1 : 0;
+ int dy = d==1 ? +1 : d==3 ? -1 : 0;
+ int x1 = x+dx, y1 = y+dy, p1 = y1 * as->w + x1;
+ if (x1 >= 0 && x1 < as->w &&
+ y1 >= 0 && y1 < as->h &&
+ as->numbers[p1] != -1 &&
+ !(as->vals[DINDEX(n, as->numbers[p1])]
+ .confounder)) {
+ /*
+ * Place this domino.
+ */
+ goto place_ok;
+ }
+ }
+ }
+ }
+ }
+
+ /* If we get here without executing 'goto place_ok', we
+ * didn't find anywhere useful to put this domino. Put it
+ * back on the list for the next pass. */
+ as->wh_scratch[valout++] = validx;
+ continue;
+
+ place_ok:;
+
+ /* We've found a domino to place. Place it, and fill in
+ * all the confounders it adds. */
+ as->numbers[loc->sq[which_lo]] = val->lo;
+ as->numbers[loc->sq[1 - which_lo]] = val->hi;
+
+ for (si = 0; si < 2; si++) {
+ int p = loc->sq[si];
+ int n = as->numbers[p];
+ int x = p % as->w, y = p / as->w;
+ int d;
+ for (d = 0; d < 4; d++) {
+ int dx = d==0 ? +1 : d==2 ? -1 : 0;
+ int dy = d==1 ? +1 : d==3 ? -1 : 0;
+ int x1 = x+dx, y1 = y+dy, p1 = y1 * as->w + x1;
+
+ if (x1 >= 0 && x1 < as->w && y1 >= 0 && y1 < as->h &&
+ p1 != loc->sq[1-si] && as->numbers[p1] != -1) {
+ int di = DINDEX(n, as->numbers[p1]);
+ if (!as->vals[di].confounder)
+ confounders_needed--;
+ as->vals[di].confounder = true;
+ }
+ }
+ }
+ }
+
+ vals = valout;
+
+ if (oldvals == vals)
+ break;
+ }
+
+ ok = true;
+
+ for (i = 0; i < as->dc; i++)
+ if (!as->vals[i].confounder)
+ ok = false;
+ for (i = 0; i < as->wh; i++)
+ if (as->numbers[i] == -1)
+ ok = false;
+
+ return ok;
+}
+
static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, bool interactive)
{
- int n = params->n, w = n+2, h = n+1, wh = w*h;
- int *grid, *grid2, *list;
+ int n = params->n, w = n+2, h = n+1, wh = w*h, diff = params->diff;
+ struct solver_scratch *sc;
+ struct alloc_scratch *as;
int i, j, k, len;
char *ret;
+#ifndef OMIT_DIFFICULTY_CAP
+ /*
+ * Cap the difficulty level for small puzzles which would
+ * otherwise become impossible to generate.
+ *
+ * Under an #ifndef, to make it easy to remove this cap for the
+ * purpose of re-testing what it ought to be.
+ */
+ if (diff != DIFF_AMBIGUOUS) {
+ if (n == 1 && diff > DIFF_TRIVIAL)
+ diff = DIFF_TRIVIAL;
+ if (n == 2 && diff > DIFF_BASIC)
+ diff = DIFF_BASIC;
+ }
+#endif /* OMIT_DIFFICULTY_CAP */
+
/*
* Allocate space in which to lay the grid out.
*/
- grid = snewn(wh, int);
- grid2 = snewn(wh, int);
- list = snewn(2*wh, int);
+ sc = solver_make_scratch(n);
+ as = alloc_make_scratch(n);
/*
* I haven't been able to think of any particularly clever
@@ -614,91 +2269,75 @@
* and 26 respectively, which is a lot more sensible.
*/
- do {
- domino_layout_prealloc(w, h, rs, grid, grid2, list);
+ while (1) {
+ alloc_make_layout(as, rs);
- /*
- * Now we have a complete layout covering the whole
- * rectangle with dominoes. So shuffle the actual domino
- * values and fill the rectangle with numbers.
- */
- k = 0;
- for (i = 0; i <= params->n; i++)
- for (j = 0; j <= i; j++) {
- list[k++] = i;
- list[k++] = j;
- }
- shuffle(list, k/2, 2*sizeof(*list), rs);
- j = 0;
- for (i = 0; i < wh; i++)
- if (grid[i] > i) {
- /* Optionally flip the domino round. */
- int flip = -1;
+ if (diff == DIFF_AMBIGUOUS) {
+ /* Just assign numbers to each domino completely at random. */
+ alloc_trivial(as, rs);
+ } else if (diff < DIFF_HARD) {
+ /* Try to rule out the most common case of a non-unique solution */
+ if (!alloc_try_unique(as, rs))
+ continue;
+ } else {
+ /*
+ * For Hard puzzles and above, we'd like there not to be
+ * any easy toehold to start with.
+ *
+ * Mostly, that's arranged by alloc_try_hard, which will
+ * ensure that no domino starts off with only one
+ * potential placement. But a few other deductions
+ * possible at Basic level can still sneak through the
+ * cracks - for example, if the only two placements of one
+ * domino overlap in a square, and you therefore rule out
+ * some other domino that can use that square, you might
+ * then find that _that_ domino now has only one
+ * placement, and you've made a start.
+ *
+ * Of course, the main difficulty-level check will still
+ * guarantee that you have to do a harder deduction
+ * _somewhere_ in the grid. But it's more elegant if
+ * there's nowhere obvious to get started at all.
+ */
+ int di;
+ bool ok;
- if (params->unique) {
- int t1, t2;
- /*
- * If we're after a unique solution, we can do
- * something here to improve the chances. If
- * we're placing a domino so that it forms a
- * 2x2 rectangle with one we've already placed,
- * and if that domino and this one share a
- * number, we can try not to put them so that
- * the identical numbers are diagonally
- * separated, because that automatically causes
- * non-uniqueness:
- *
- * +---+ +-+-+
- * |2 3| |2|3|
- * +---+ -> | | |
- * |4 2| |4|2|
- * +---+ +-+-+
- */
- t1 = i;
- t2 = grid[i];
- if (t2 == t1 + w) { /* this domino is vertical */
- if (t1 % w > 0 &&/* and not on the left hand edge */
- grid[t1-1] == t2-1 &&/* alongside one to left */
- (grid2[t1-1] == list[j] || /* and has a number */
- grid2[t1-1] == list[j+1] || /* in common */
- grid2[t2-1] == list[j] ||
- grid2[t2-1] == list[j+1])) {
- if (grid2[t1-1] == list[j] ||
- grid2[t2-1] == list[j+1])
- flip = 0;
- else
- flip = 1;
- }
- } else { /* this domino is horizontal */
- if (t1 / w > 0 &&/* and not on the top edge */
- grid[t1-w] == t2-w &&/* alongside one above */
- (grid2[t1-w] == list[j] || /* and has a number */
- grid2[t1-w] == list[j+1] || /* in common */
- grid2[t2-w] == list[j] ||
- grid2[t2-w] == list[j+1])) {
- if (grid2[t1-w] == list[j] ||
- grid2[t2-w] == list[j+1])
- flip = 0;
- else
- flip = 1;
- }
- }
+ if (!alloc_try_hard(as, rs))
+ continue;
+
+ solver_setup_grid(sc, as->numbers);
+ if (run_solver(sc, DIFF_BASIC) < 2)
+ continue;
+
+ ok = true;
+ for (di = 0; di < sc->dc; di++)
+ if (sc->dominoes[di].nplacements <= 1) {
+ ok = false;
+ break;
}
- if (flip < 0)
- flip = random_upto(rs, 2);
-
- grid2[i] = list[j + flip];
- grid2[grid[i]] = list[j + 1 - flip];
- j += 2;
+ if (!ok) {
+ continue;
}
- assert(j == k);
- } while (params->unique && solver(w, h, n, grid2, NULL) > 1);
+ }
+
+ if (diff != DIFF_AMBIGUOUS) {
+ int solver_result;
+ solver_setup_grid(sc, as->numbers);
+ solver_result = run_solver(sc, diff);
+ if (solver_result > 1)
+ continue; /* puzzle couldn't be solved at this difficulty */
+ if (sc->max_diff_used < diff)
+ continue; /* puzzle _could_ be solved at easier difficulty */
+ }
+
+ break;
+ }
#ifdef GENERATION_DIAGNOSTICS
for (j = 0; j < h; j++) {
for (i = 0; i < w; i++) {
- putchar('0' + grid2[j*w+i]);
+ putchar('0' + as->numbers[j*w+i]);
}
putchar('\n');
}
@@ -738,7 +2377,7 @@
ret = snewn(len+1, char);
j = 0;
for (i = 0; i < wh; i++) {
- k = grid2[i];
+ k = as->numbers[i];
if (k < 10)
ret[j++] = '0' + k;
else
@@ -755,7 +2394,7 @@
char *auxinfo = snewn(wh+1, char);
for (i = 0; i < wh; i++) {
- int v = grid[i];
+ int v = as->layout[i];
auxinfo[i] = (v == i+1 ? 'L' : v == i-1 ? 'R' :
v == i+w ? 'T' : v == i-w ? 'B' : '.');
}
@@ -764,9 +2403,8 @@
*aux = auxinfo;
}
- sfree(list);
- sfree(grid2);
- sfree(grid);
+ solver_free_scratch(sc);
+ alloc_free_scratch(as);
return ret;
}
@@ -898,14 +2536,62 @@
sfree(state);
}
+static char *solution_move_string(struct solver_scratch *sc)
+{
+ char *ret;
+ int retlen, retsize;
+ int i, pass;
+
+ /*
+ * First make a pass putting in edges for -1, then make a pass
+ * putting in dominoes for +1.
+ */
+ retsize = 256;
+ ret = snewn(retsize, char);
+ retlen = sprintf(ret, "S");
+
+ for (pass = 0; pass < 2; pass++) {
+ char type = "ED"[pass];
+
+ for (i = 0; i < sc->pc; i++) {
+ struct solver_placement *p = &sc->placements[i];
+ char buf[80];
+ int extra;
+
+ if (pass == 0) {
+ /* Emit a barrier if this placement is ruled out for
+ * the domino. */
+ if (p->active)
+ continue;
+ } else {
+ /* Emit a domino if this placement is the only one not
+ * ruled out. */
+ if (!p->active || p->domino->nplacements > 1)
+ continue;
+ }
+
+ extra = sprintf(buf, ";%c%d,%d", type,
+ p->squares[0]->index, p->squares[1]->index);
+
+ if (retlen + extra + 1 >= retsize) {
+ retsize = retlen + extra + 256;
+ ret = sresize(ret, retsize, char);
+ }
+ strcpy(ret + retlen, buf);
+ retlen += extra;
+ }
+ }
+
+ return ret;
+}
+
static char *solve_game(const game_state *state, const game_state *currstate,
const char *aux, const char **error)
{
int n = state->params.n, w = n+2, h = n+1, wh = w*h;
- int *placements;
char *ret;
int retlen, retsize;
- int i, v;
+ int i;
char buf[80];
int extra;
@@ -931,38 +2617,11 @@
}
} else {
-
- placements = snewn(wh*2, int);
- for (i = 0; i < wh*2; i++)
- placements[i] = -3;
- solver(w, h, n, state->numbers->numbers, placements);
-
- /*
- * First make a pass putting in edges for -1, then make a pass
- * putting in dominoes for +1.
- */
- retsize = 256;
- ret = snewn(retsize, char);
- retlen = sprintf(ret, "S");
-
- for (v = -1; v <= +1; v += 2)
- for (i = 0; i < wh*2; i++)
- if (placements[i] == v) {
- int p1 = i / 2;
- int p2 = (i & 1) ? p1+1 : p1+w;
-
- extra = sprintf(buf, ";%c%d,%d",
- (int)(v==-1 ? 'E' : 'D'), p1, p2);
-
- if (retlen + extra + 1 >= retsize) {
- retsize = retlen + extra + 256;
- ret = sresize(ret, retsize, char);
- }
- strcpy(ret + retlen, buf);
- retlen += extra;
- }
-
- sfree(placements);
+ struct solver_scratch *sc = solver_make_scratch(n);
+ solver_setup_grid(sc, state->numbers->numbers);
+ run_solver(sc, DIFFCOUNT);
+ ret = solution_move_string(sc);
+ solver_free_scratch(sc);
}
return ret;
@@ -1770,5 +3429,98 @@
0, /* flags */
};
+#ifdef STANDALONE_SOLVER
+
+int main(int argc, char **argv)
+{
+ game_params *p;
+ game_state *s, *s2;
+ char *id = NULL, *desc;
+ int maxdiff = DIFFCOUNT;
+ const char *err;
+ bool grade = false, diagnostics = false;
+ struct solver_scratch *sc;
+ int retd;
+
+ while (--argc > 0) {
+ char *p = *++argv;
+ if (!strcmp(p, "-v")) {
+ diagnostics = true;
+ } else if (!strcmp(p, "-g")) {
+ grade = true;
+ } else if (!strncmp(p, "-d", 2) && p[2] && !p[3]) {
+ int i;
+ bool bad = true;
+ for (i = 0; i < lenof(dominosa_diffchars); i++)
+ if (dominosa_diffchars[i] != DIFF_AMBIGUOUS &&
+ dominosa_diffchars[i] == p[2]) {
+ bad = false;
+ maxdiff = i;
+ break;
+ }
+ if (bad) {
+ fprintf(stderr, "%s: unrecognised difficulty `%c'\n",
+ argv[0], p[2]);
+ return 1;
+ }
+ } else if (*p == '-') {
+ fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+ return 1;
+ } else {
+ id = p;
+ }
+ }
+
+ if (!id) {
+ fprintf(stderr, "usage: %s [-v | -g] <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);
+
+ solver_diagnostics = diagnostics;
+ sc = solver_make_scratch(p->n);
+ solver_setup_grid(sc, s->numbers->numbers);
+ retd = run_solver(sc, maxdiff);
+ if (retd == 0) {
+ printf("Puzzle is inconsistent\n");
+ } else if (grade) {
+ printf("Difficulty rating: %s\n",
+ dominosa_diffnames[sc->max_diff_used]);
+ } else {
+ char *move, *text;
+ move = solution_move_string(sc);
+ s2 = execute_move(s, move);
+ text = game_text_format(s2);
+ sfree(move);
+ fputs(text, stdout);
+ sfree(text);
+ free_game(s2);
+ if (retd > 1)
+ printf("Could not deduce a unique solution\n");
+ }
+ solver_free_scratch(sc);
+ free_game(s);
+ free_params(p);
+
+ return 0;
+}
+
+#endif
+
/* vim: set shiftwidth=4 :set textwidth=80: */
diff --git a/apps/plugins/puzzles/src/emccpre.js b/apps/plugins/puzzles/src/emccpre.js
index 5082555..56f6972 100644
--- a/apps/plugins/puzzles/src/emccpre.js
+++ b/apps/plugins/puzzles/src/emccpre.js
@@ -212,28 +212,51 @@
// button down (our puzzles don't want those events).
mousedown = Module.cwrap('mousedown', 'void',
['number', 'number', 'number']);
- buttons_down = 0;
+
+ button_phys2log = [null, null, null];
+ buttons_down = function() {
+ var i, toret = 0;
+ for (i = 0; i < 3; i++)
+ if (button_phys2log[i] !== null)
+ toret |= 1 << button_phys2log[i];
+ return toret;
+ };
+
onscreen_canvas.onmousedown = function(event) {
+ if (event.button >= 3)
+ return;
+
var xy = relative_mouse_coords(event, onscreen_canvas);
- mousedown(xy.x, xy.y, event.button);
- buttons_down |= 1 << event.button;
+ var logbutton = event.button;
+ if (event.shiftKey)
+ logbutton = 1; // Shift-click overrides to middle button
+ else if (event.ctrlKey)
+ logbutton = 2; // Ctrl-click overrides to right button
+
+ mousedown(xy.x, xy.y, logbutton);
+ button_phys2log[event.button] = logbutton;
+
onscreen_canvas.setCapture(true);
};
mousemove = Module.cwrap('mousemove', 'void',
['number', 'number', 'number']);
onscreen_canvas.onmousemove = function(event) {
- if (buttons_down) {
+ var down = buttons_down();
+ if (down) {
var xy = relative_mouse_coords(event, onscreen_canvas);
- mousemove(xy.x, xy.y, buttons_down);
+ mousemove(xy.x, xy.y, down);
}
};
mouseup = Module.cwrap('mouseup', 'void',
['number', 'number', 'number']);
onscreen_canvas.onmouseup = function(event) {
- if (buttons_down & (1 << event.button)) {
- buttons_down ^= 1 << event.button;
+ if (event.button >= 3)
+ return;
+
+ if (button_phys2log[event.button] !== null) {
var xy = relative_mouse_coords(event, onscreen_canvas);
- mouseup(xy.x, xy.y, event.button);
+ mouseup(xy.x, xy.y, button_phys2log[event.button]);
+ button_phys2log[event.button] = null;
}
};
diff --git a/apps/plugins/puzzles/src/findloop.c b/apps/plugins/puzzles/src/findloop.c
index ffda12d..4ebdea1 100644
--- a/apps/plugins/puzzles/src/findloop.c
+++ b/apps/plugins/puzzles/src/findloop.c
@@ -14,7 +14,7 @@
#include "puzzles.h"
struct findloopstate {
- int parent, child, sibling;
+ int parent, child, sibling, component_root;
bool visited;
int index, minindex, maxindex;
int minreachable, maxreachable;
@@ -57,6 +57,33 @@
return !(pv[u].bridge == v || pv[v].bridge == u);
}
+static bool findloop_is_bridge_oneway(
+ struct findloopstate *pv, int u, int v, int *u_vertices, int *v_vertices)
+{
+ int r, total, below;
+
+ if (pv[u].bridge != v)
+ return false;
+
+ r = pv[u].component_root;
+ total = pv[r].maxindex - pv[r].minindex + 1;
+ below = pv[u].maxindex - pv[u].minindex + 1;
+
+ if (u_vertices)
+ *u_vertices = below;
+ if (v_vertices)
+ *v_vertices = total - below;
+
+ return true;
+}
+
+bool findloop_is_bridge(
+ struct findloopstate *pv, int u, int v, int *u_vertices, int *v_vertices)
+{
+ return (findloop_is_bridge_oneway(pv, u, v, u_vertices, v_vertices) ||
+ findloop_is_bridge_oneway(pv, v, u, v_vertices, u_vertices));
+}
+
bool findloop_run(struct findloopstate *pv, int nvertices,
neighbour_fn_t neighbour, void *ctx)
{
@@ -94,6 +121,7 @@
*/
pv[v].sibling = pv[root].child;
pv[root].child = v;
+ pv[v].component_root = v;
debug(("%d is new child of root\n", v));
u = v;
@@ -116,6 +144,7 @@
pv[w].child = -1;
pv[w].sibling = pv[u].child;
pv[w].parent = u;
+ pv[w].component_root = pv[u].component_root;
pv[u].child = w;
}
diff --git a/apps/plugins/puzzles/src/galaxies.c b/apps/plugins/puzzles/src/galaxies.c
index 0cc3198..fe7cd24 100644
--- a/apps/plugins/puzzles/src/galaxies.c
+++ b/apps/plugins/puzzles/src/galaxies.c
@@ -346,29 +346,44 @@
tile->x, tile->y, dot->x, dot->y, dot->nassoc));*/
}
-static void add_assoc_with_opposite(game_state *state, space *tile, space *dot) {
+static bool ok_to_add_assoc_with_opposite_internal(
+ const game_state *state, space *tile, space *opposite)
+{
int *colors;
- space *opposite = space_opposite_dot(state, tile, dot);
+ bool toret;
- if (opposite == NULL) {
- return;
- }
- if (opposite->flags & F_DOT) {
- return;
- }
+ if (tile->flags & F_DOT)
+ return false;
+ if (opposite == NULL)
+ return false;
+ if (opposite->flags & F_DOT)
+ return false;
+ toret = true;
colors = snewn(state->w * state->h, int);
check_complete(state, NULL, colors);
- if (colors[(tile->y - 1)/2 * state->w + (tile->x - 1)/2]) {
- sfree(colors);
- return;
- }
- if (colors[(opposite->y - 1)/2 * state->w + (opposite->x - 1)/2]) {
- sfree(colors);
- return;
- }
+
+ if (colors[(tile->y - 1)/2 * state->w + (tile->x - 1)/2])
+ toret = false;
+ if (colors[(opposite->y - 1)/2 * state->w + (opposite->x - 1)/2])
+ toret = false;
sfree(colors);
+ return toret;
+}
+
+static bool ok_to_add_assoc_with_opposite(
+ const game_state *state, space *tile, space *dot)
+{
+ space *opposite = space_opposite_dot(state, tile, dot);
+ return ok_to_add_assoc_with_opposite_internal(state, tile, opposite);
+}
+
+static void add_assoc_with_opposite(game_state *state, space *tile, space *dot) {
+ space *opposite = space_opposite_dot(state, tile, dot);
+
+ assert(ok_to_add_assoc_with_opposite_internal(state, tile, opposite));
+
remove_assoc_with_opposite(state, tile);
add_assoc(state, tile, dot);
remove_assoc_with_opposite(state, opposite);
@@ -2596,8 +2611,15 @@
*/
if (INUI(state, px, py)) {
sp = &SPACE(state, px, py);
+ dot = &SPACE(state, ui->dotx, ui->doty);
- if (!(sp->flags & F_DOT))
+ /*
+ * Exception: if it's not actually legal to add an arrow
+ * and its opposite at this position, we don't try,
+ * because otherwise we'd append an empty entry to the
+ * undo chain.
+ */
+ if (ok_to_add_assoc_with_opposite(state, sp, dot))
sprintf(buf + strlen(buf), "%sA%d,%d,%d,%d",
sep, px, py, ui->dotx, ui->doty);
}
diff --git a/apps/plugins/puzzles/src/midend.c b/apps/plugins/puzzles/src/midend.c
index 036c856..a8dd179 100644
--- a/apps/plugins/puzzles/src/midend.c
+++ b/apps/plugins/puzzles/src/midend.c
@@ -171,6 +171,8 @@
me->params = ourgame->default_params();
me->game_id_change_notify_function = NULL;
me->game_id_change_notify_ctx = NULL;
+ me->encoded_presets = NULL;
+ me->n_encoded_presets = 0;
/*
* Allow environment-based changing of the default settings by
@@ -261,8 +263,13 @@
void midend_free(midend *me)
{
+ int i;
+
midend_free_game(me);
+ for (i = 0; i < me->n_encoded_presets; i++)
+ sfree(me->encoded_presets[i]);
+ sfree(me->encoded_presets);
if (me->drawing)
drawing_free(me->drawing);
random_free(me->random);
diff --git a/apps/plugins/puzzles/src/pegs.c b/apps/plugins/puzzles/src/pegs.c
index 32673d5..db9caf2 100644
--- a/apps/plugins/puzzles/src/pegs.c
+++ b/apps/plugins/puzzles/src/pegs.c
@@ -792,6 +792,12 @@
* unoccupied.
*/
ui->dragging = false;
+
+ /*
+ * Also, cancel a keyboard-driven jump if one is half way to being
+ * input.
+ */
+ ui->cur_jumping = false;
}
#define PREFERRED_TILE_SIZE 33
diff --git a/apps/plugins/puzzles/src/puzzles.h b/apps/plugins/puzzles/src/puzzles.h
index 48d3d83..1732abe 100644
--- a/apps/plugins/puzzles/src/puzzles.h
+++ b/apps/plugins/puzzles/src/puzzles.h
@@ -595,6 +595,32 @@
bool findloop_is_loop_edge(struct findloopstate *state, int u, int v);
/*
+ * Alternative query function, which returns true if the u-v edge is a
+ * _bridge_, i.e. a non-loop edge, i.e. an edge whose removal would
+ * disconnect a currently connected component of the graph.
+ *
+ * If the return value is true, then the numbers of vertices that
+ * would be in the new components containing u and v are written into
+ * u_vertices and v_vertices respectively.
+ */
+bool findloop_is_bridge(
+ struct findloopstate *pv, int u, int v, int *u_vertices, int *v_vertices);
+
+/*
+ * Helper function to sort an array. Differs from standard qsort in
+ * that it takes a context parameter that is passed to the compare
+ * function.
+ *
+ * I wrap it in a macro so that you only need to give the element
+ * count of the array. The element size is determined by sizeof.
+ */
+typedef int (*arraysort_cmpfn_t)(const void *av, const void *bv, void *ctx);
+void arraysort_fn(void *array, size_t nmemb, size_t size,
+ arraysort_cmpfn_t cmp, void *ctx);
+#define arraysort(array, nmemb, cmp, ctx) \
+ arraysort_fn(array, nmemb, sizeof(*(array)), cmp, ctx)
+
+/*
* Data structure containing the function calls and data specific
* to a particular game. This is enclosed in a data structure so
* that a particular platform can choose, if it wishes, to compile
diff --git a/apps/plugins/puzzles/src/sort.c b/apps/plugins/puzzles/src/sort.c
new file mode 100644
index 0000000..d1897b6
--- /dev/null
+++ b/apps/plugins/puzzles/src/sort.c
@@ -0,0 +1,160 @@
+/*
+ * Implement arraysort() defined in puzzles.h.
+ *
+ * Strategy: heapsort.
+ */
+
+#include <stddef.h>
+#include <string.h>
+
+#include "puzzles.h"
+
+static void memswap(void *av, void *bv, size_t size)
+{
+ char t[4096];
+ char *a = (char *)av, *b = (char *)bv;
+
+ while (size > 0) {
+ size_t thissize = size < sizeof(t) ? size : sizeof(t);
+
+ memcpy(t, a, thissize);
+ memcpy(a, b, thissize);
+ memcpy(b, t, thissize);
+
+ size -= thissize;
+ a += thissize;
+ b += thissize;
+ }
+}
+
+#define PTR(i) ((char *)array + size * (i))
+#define SWAP(i,j) memswap(PTR(i), PTR(j), size)
+#define CMP(i,j) cmp(PTR(i), PTR(j), ctx)
+
+#define LCHILD(i) (2*(i)+1)
+#define RCHILD(i) (2*(i)+2)
+#define PARENT(i) (((i)-1)/2)
+
+static void downheap(void *array, size_t nmemb, size_t size,
+ arraysort_cmpfn_t cmp, void *ctx, size_t i)
+{
+ while (LCHILD(i) < nmemb) {
+ /* Identify the smallest element out of i and its children. */
+ size_t j = i;
+ if (CMP(j, LCHILD(i)) < 0)
+ j = LCHILD(i);
+ if (RCHILD(i) < nmemb &&
+ CMP(j, RCHILD(i)) < 0)
+ j = RCHILD(i);
+
+ if (j == i)
+ return; /* smallest element is already where it should be */
+
+ SWAP(j, i);
+ i = j;
+ }
+}
+
+void arraysort_fn(void *array, size_t nmemb, size_t size,
+ arraysort_cmpfn_t cmp, void *ctx)
+{
+ size_t i;
+
+ if (nmemb < 2)
+ return; /* trivial */
+
+ /*
+ * Stage 1: build the heap.
+ *
+ * Linear-time if we do it by downheaping the elements in
+ * decreasing order of index, instead of the more obvious approach
+ * of upheaping in increasing order. (Also, it means we don't need
+ * the upheap function at all.)
+ *
+ * We don't need to downheap anything in the second half of the
+ * array, because it can't have any children to swap with anyway.
+ */
+ for (i = PARENT(nmemb-1) + 1; i-- > 0 ;)
+ downheap(array, nmemb, size, cmp, ctx, i);
+
+ /*
+ * Stage 2: dismantle the heap by repeatedly swapping the root
+ * element (at index 0) into the last position and then
+ * downheaping the new root.
+ */
+ for (i = nmemb-1; i > 0; i--) {
+ SWAP(0, i);
+ downheap(array, i, size, cmp, ctx, 0);
+ }
+}
+
+#ifdef SORT_TEST
+
+#include <stdlib.h>
+#include <time.h>
+
+int testcmp(const void *av, const void *bv, void *ctx)
+{
+ int a = *(const int *)av, b = *(const int *)bv;
+ const int *keys = (const int *)ctx;
+ return keys[a] < keys[b] ? -1 : keys[a] > keys[b] ? +1 : 0;
+}
+
+int resetcmp(const void *av, const void *bv)
+{
+ int a = *(const int *)av, b = *(const int *)bv;
+ return a < b ? -1 : a > b ? +1 : 0;
+}
+
+int main(int argc, char **argv)
+{
+ typedef int Array[3723];
+ Array data, keys;
+ int iteration;
+ unsigned seed;
+
+ seed = (argc > 1 ? strtoul(argv[1], NULL, 0) : time(NULL));
+ printf("Random seed = %u\n", seed);
+ srand(seed);
+
+ for (iteration = 0; iteration < 10000; iteration++) {
+ int j;
+ const char *fail = NULL;
+
+ for (j = 0; j < lenof(data); j++) {
+ data[j] = j;
+ keys[j] = rand();
+ }
+
+ arraysort(data, lenof(data), testcmp, keys);
+
+ for (j = 1; j < lenof(data); j++) {
+ if (keys[data[j]] < keys[data[j-1]])
+ fail = "output misordered";
+ }
+ if (!fail) {
+ Array reset;
+ memcpy(reset, data, sizeof(data));
+ qsort(reset, lenof(reset), sizeof(*reset), resetcmp);
+ for (j = 0; j < lenof(reset); j++)
+ if (reset[j] != j)
+ fail = "output not permuted";
+ }
+
+ if (fail) {
+ printf("Failed at iteration %d: %s\n", iteration, fail);
+ printf("Key values:\n");
+ for (j = 0; j < lenof(keys); j++)
+ printf(" [%2d] %10d\n", j, keys[j]);
+ printf("Output sorted order:\n");
+ for (j = 0; j < lenof(data); j++)
+ printf(" [%2d] %10d\n", data[j], keys[data[j]]);
+ return 1;
+ }
+ }
+
+ printf("OK\n");
+ return 0;
+}
+
+#endif /* SORT_TEST */