ref: e2f52df5ec5a34ded78e95e9605c0bc810019bcd
parent: 2ec6daee32ad7b2967141aa52ee3327af8055e67
author: Simon Tatham <anakin@pobox.com>
date: Wed Apr 3 14:16:25 EDT 2019
Dominosa: add a Hard difficulty which can do set analysis. This is another thing I've been doing with my own brain for ages as a more interesting alternative to scouring the grid for the simpler deduction that must be there somewhere - but now the solver can understand it too, so it can generate puzzles that _need_ it (or at least need something beyond the simpler strategies it understands).
--- a/dominosa.c
+++ b/dominosa.c
@@ -23,25 +23,6 @@
* squares should be sufficient to determine the area parity
* of the region that any such placement would cut off.
*
- * * 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!
- *
* * 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
@@ -78,6 +59,7 @@
#define DIFFLIST(X) \
X(TRIVIAL,Trivial,t) \
X(BASIC,Basic,b) \
+ X(HARD,Hard,h) \
X(AMBIGUOUS,Ambiguous,a) \
/* end of list */
#define ENUM(upper,title,lower) DIFF_ ## upper,
@@ -340,6 +322,9 @@
struct solver_placement *placements;
struct solver_square *squares;
struct solver_placement **domino_placement_lists;
+ struct solver_square **squares_by_number;
+ bool squares_by_number_initialised;
+ int *wh_scratch;
};
static struct solver_scratch *solver_make_scratch(int n)
@@ -458,6 +443,12 @@
}
}
+ /* 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;
+
return sc;
}
@@ -478,6 +469,8 @@
sfree(sc->placements);
sfree(sc->squares);
sfree(sc->domino_placement_lists);
+ sfree(sc->squares_by_number);
+ sfree(sc->wh_scratch);
sfree(sc);
}
@@ -524,6 +517,7 @@
}
sc->max_diff_used = DIFF_TRIVIAL;
+ sc->squares_by_number_initialised = false;
}
/* Given two placements p,q that overlap, returns si such that
@@ -535,6 +529,15 @@
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)
{
@@ -740,7 +743,6 @@
return true;
}
-
/*
* 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
@@ -788,6 +790,246 @@
}
/*
+ * If we can find a set S of mutually non-adjacent squares all
+ * containing the same number, such that the set of possible dominoes
+ * for all those squares put together has the same size as S, then all
+ * the dominoes in that set _must_ overlap a square of S and we can
+ * rule out any other placements for them.
+ */
+static bool deduce_set_simple(struct solver_scratch *sc)
+{
+ 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 > sizeof(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;
+ }
+
+ /*
+ * 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 < nsq; i++)
+ sc->wh_scratch[sqs[i]->index] = i;
+
+ /*
+ * 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;
+
+ for (i = 0; i < nsq; i++) {
+ struct solver_square *sq = sqs[i];
+ unsigned long mask = 0;
+
+ 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 */
+
+ 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;
+ }
+ }
+
+ domino_sets[i] = mask;
+
+ }
+
+ squares_done = 0;
+
+ for (squares = 0; squares < (1UL << nsq); squares++) {
+ unsigned long dominoes = 0;
+ int bitpos, nsquares, ndominoes;
+ bool reported = false;
+
+ /*
+ * 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)
+ goto skip_subset; /* contains two adjacent squares */
+
+ dominoes |= domino_sets[bitpos];
+ nsquares++;
+ }
+
+ /* Count them. */
+ ndominoes = 0;
+ for (bitpos = 0; bitpos < nsq; bitpos++)
+ ndominoes += 1 & (dominoes >> bitpos);
+
+ /* Are the two sets equal in size? */
+ if (nsquares != ndominoes)
+ 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 (nsquares <= 1 || nsquares >= nsq-1)
+ continue;
+
+ /*
+ * We've found a set! That means we can rule out any
+ * placement of any of the dominoes in that set which do
+ * not include one of our squares.
+ *
+ * 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.
+ */
+ 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;
+
+ for (si = 0; si < 2; si++) {
+ struct solver_square *sq2 = p->squares[si];
+ if (sq2->number == num &&
+ (1 & (squares >> sc->wh_scratch[sq2->index]))) {
+ /* This placement uses one of our squares.
+ * Leave it in. */
+ goto skip_placement;
+ }
+ }
+
+ if (!reported) {
+ reported = true;
+ done_something = true;
+#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("} have to contain dominoes {");
+ for (sep = "", b = 0; b < nsq; b++)
+ if (1 & (dominoes >> b)) {
+ printf("%s%s", sep, ds[b]->name);
+ sep = ",";
+ }
+ printf("}\n");
+ }
+#endif
+ }
+
+ rule_out_placement(sc, p);
+
+ skip_placement:;
+ }
+ }
+
+ skip_subset:;
+ }
+
+ }
+
+ return done_something;
+}
+
+/*
* Run the solver until it can't make any more progress.
*
* Return value is:
@@ -858,6 +1100,16 @@
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_simple(sc))
+ done_something = true;
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_HARD);
continue;
}