shithub: puzzles

Download patch

ref: 7ac48f9fe3ff827460b885b50d1e25f1ed2f7862
parent: 1e6e3a815eb67a0d0d369fd0971cf9f3fd9fbf9a
author: Simon Tatham <anakin@pobox.com>
date: Thu Apr 11 16:30:10 EDT 2019

Dominosa: further forms of set analysis.

I realised that even with the extra case for a double domino
potentially using two squares in a set, I'd missed two tricks.

Firstly, if the double domino is _required_ to use two of the squares,
you can rule out any placement in which it only uses one. But I was
only ruling out those in which it used _none_.

Secondly, if you have the same number of squares as dominoes, so that
the double domino _can_ but _need not_ use two of the squares, then I
previously thought there was no deduction you could make at all. But
there is! In that situation, the double does have to use _one_ of the
squares, or else there would be only the n-1 heterogeneous dominoes to
go round the n squares. So you can still rule out placements for the
double which fail to overlap any square in the set, even if you can't
(yet) do anything about the other dominoes involved.

--- a/dominosa.c
+++ b/dominosa.c
@@ -821,11 +821,10 @@
 }
 
 /*
- * 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.
+ * 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_simple(struct solver_scratch *sc)
 {
@@ -942,6 +941,11 @@
             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
@@ -981,43 +985,116 @@
 
             /*
              * Do the two sets have the right relative size?
-             *
-             * In the normal case, analogous to set analysis in many
-             * other puzzles, you want N squares which between them
-             * have to account for N distinct dominoes, with a 1-1
-             * correspondence between them.
-             *
-             * But if any two squares in this set can possibly both be
-             * covered by the same double domino (i.e. if they are
-             * adjacent, and moreover, the placement containing both
-             * is not yet ruled out), then that 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 aren't
-             * all distinct.
-             *
-             * In that situation, we can only do the set analysis if
-             * there is one _more_ square than there are dominoes. For
-             * example, suppose we had four 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 those three
-             * dominoes anywhere else they might look possible.
              */
-            if (ndominoes != (got_adj_squares ? nsquares - 1 : nsquares))
-                continue;
+            if (!got_adj_squares) {
+                /*
+                 * 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.
+                 */
+                if (ndominoes != nsquares)
+                    continue;
+                rule_out_nondoubles = true;
+                min_nused_for_double = 1;
+#ifdef SOLVER_DIAGNOSTICS
+                rule_out_text = "all of them elsewhere";
+#endif
+            } else {
+                /*
+                 * 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) {
+                    /*
+                     * 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_.
+                     */
+                    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 (nsquares <= 1 || nsquares >= nsq-1)
+            if (ndominoes <= 1 || ndominoes >= 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.
+             * 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
@@ -1024,8 +1101,16 @@
              * 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!
              */
-            squares_done |= squares;
+            if (rule_out_nondoubles)
+                squares_done |= squares;
 
             for (bitpos = 0; bitpos < nsq; bitpos++) {
                 struct solver_domino *d;
@@ -1036,22 +1121,32 @@
 
                 for (i = d->nplacements; i-- > 0 ;) {
                     struct solver_placement *p = d->placements[i];
-                    int si;
+                    int si, nused;
 
-                    for (si = 0; si < 2; si++) {
+                    /* 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]))) {
-                            /* This placement uses one of our squares.
-                             * Leave it in. */
-                            goto skip_placement;
-                        }
+                            (1 & (squares >> sc->wh_scratch[sq2->index])))
+                            nused++;
                     }
 
+                    /* 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;
@@ -1062,16 +1157,13 @@
                                     printf("%s%s", sep, sqs[b]->name);
                                     sep = ",";
                                 }
-                            printf("} have to contain dominoes {");
+                            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("}");
-                            if (got_adj_squares)
-                                printf(" (including both ends of the "
-                                       "double)");
+                            printf("}, so rule out %s", rule_out_text);
                             printf("\n");
                         }
 #endif
@@ -1078,8 +1170,6 @@
                     }
 
                     rule_out_placement(sc, p);
-
-                  skip_placement:;
                 }
             }
         }