mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 08:01:30 -07:00
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).
This commit is contained in:
292
dominosa.c
292
dominosa.c
@ -23,25 +23,6 @@
|
|||||||
* squares should be sufficient to determine the area parity
|
* squares should be sufficient to determine the area parity
|
||||||
* of the region that any such placement would cut off.
|
* 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
|
* * identify 'forcing chains', in the sense of any path of cells
|
||||||
* each of which has only two possible dominoes to be part of,
|
* 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
|
* and each of those rules out one of the choices for the next
|
||||||
@ -78,6 +59,7 @@
|
|||||||
#define DIFFLIST(X) \
|
#define DIFFLIST(X) \
|
||||||
X(TRIVIAL,Trivial,t) \
|
X(TRIVIAL,Trivial,t) \
|
||||||
X(BASIC,Basic,b) \
|
X(BASIC,Basic,b) \
|
||||||
|
X(HARD,Hard,h) \
|
||||||
X(AMBIGUOUS,Ambiguous,a) \
|
X(AMBIGUOUS,Ambiguous,a) \
|
||||||
/* end of list */
|
/* end of list */
|
||||||
#define ENUM(upper,title,lower) DIFF_ ## upper,
|
#define ENUM(upper,title,lower) DIFF_ ## upper,
|
||||||
@ -340,6 +322,9 @@ struct solver_scratch {
|
|||||||
struct solver_placement *placements;
|
struct solver_placement *placements;
|
||||||
struct solver_square *squares;
|
struct solver_square *squares;
|
||||||
struct solver_placement **domino_placement_lists;
|
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)
|
static struct solver_scratch *solver_make_scratch(int n)
|
||||||
@ -458,6 +443,12 @@ static struct solver_scratch *solver_make_scratch(int n)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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;
|
return sc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,6 +469,8 @@ static void solver_free_scratch(struct solver_scratch *sc)
|
|||||||
sfree(sc->placements);
|
sfree(sc->placements);
|
||||||
sfree(sc->squares);
|
sfree(sc->squares);
|
||||||
sfree(sc->domino_placement_lists);
|
sfree(sc->domino_placement_lists);
|
||||||
|
sfree(sc->squares_by_number);
|
||||||
|
sfree(sc->wh_scratch);
|
||||||
sfree(sc);
|
sfree(sc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,6 +517,7 @@ static void solver_setup_grid(struct solver_scratch *sc, const int *numbers)
|
|||||||
}
|
}
|
||||||
|
|
||||||
sc->max_diff_used = DIFF_TRIVIAL;
|
sc->max_diff_used = DIFF_TRIVIAL;
|
||||||
|
sc->squares_by_number_initialised = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Given two placements p,q that overlap, returns si such that
|
/* Given two placements p,q that overlap, returns si such that
|
||||||
@ -535,6 +529,15 @@ static int common_square_index(struct solver_placement *p,
|
|||||||
p->squares[0] == q->squares[1]) ? 0 : 1;
|
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(
|
static void rule_out_placement(
|
||||||
struct solver_scratch *sc, struct solver_placement *p)
|
struct solver_scratch *sc, struct solver_placement *p)
|
||||||
{
|
{
|
||||||
@ -740,7 +743,6 @@ static bool deduce_domino_must_overlap(struct solver_scratch *sc, int di)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If a placement of domino D overlaps the only remaining placement
|
* 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
|
* for some square S which is not also for domino D, then placing D
|
||||||
@ -787,6 +789,246 @@ static bool deduce_local_duplicate(struct solver_scratch *sc, int pi)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
* Run the solver until it can't make any more progress.
|
||||||
*
|
*
|
||||||
@ -861,6 +1103,16 @@ static int run_solver(struct solver_scratch *sc, int max_diff_allowed)
|
|||||||
continue;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
} while (done_something);
|
} while (done_something);
|
||||||
|
|
||||||
#ifdef SOLVER_DIAGNOSTICS
|
#ifdef SOLVER_DIAGNOSTICS
|
||||||
|
Reference in New Issue
Block a user