mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-19 23:21:31 -07:00
Files
3562 lines
111 KiB
C
3562 lines
111 KiB
C
/*
|
|
* dominosa.c: Domino jigsaw puzzle. Aim to place one of every
|
|
* possible domino within a rectangle in such a way that the number
|
|
* on each square matches the provided clue.
|
|
*/
|
|
|
|
/*
|
|
* Further possible deduction types in the solver:
|
|
*
|
|
* * 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.
|
|
*
|
|
* * 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>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#ifdef NO_TGMATH_H
|
|
# include <math.h>
|
|
#else
|
|
# include <tgmath.h>
|
|
#endif
|
|
|
|
#include "puzzles.h"
|
|
|
|
/* nth triangular number */
|
|
#define TRI(n) ( (n) * ((n) + 1) / 2 )
|
|
/* number of dominoes for value n */
|
|
#define DCOUNT(n) TRI((n)+1)
|
|
/* map a pair of numbers to a unique domino index from 0 upwards. */
|
|
#define DINDEX(n1,n2) ( TRI(max(n1,n2)) + min(n1,n2) )
|
|
|
|
#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,
|
|
COL_DOMINO,
|
|
COL_DOMINOCLASH,
|
|
COL_DOMINOTEXT,
|
|
COL_EDGE,
|
|
COL_HIGHLIGHT_1,
|
|
COL_HIGHLIGHT_2,
|
|
NCOLOURS
|
|
};
|
|
|
|
struct game_params {
|
|
int n;
|
|
int diff;
|
|
};
|
|
|
|
struct game_numbers {
|
|
int refcount;
|
|
int *numbers; /* h x w */
|
|
};
|
|
|
|
#define EDGE_L 0x100
|
|
#define EDGE_R 0x200
|
|
#define EDGE_T 0x400
|
|
#define EDGE_B 0x800
|
|
|
|
struct game_state {
|
|
game_params params;
|
|
int w, h;
|
|
struct game_numbers *numbers;
|
|
int *grid;
|
|
unsigned short *edges; /* h x w */
|
|
bool completed, cheated;
|
|
};
|
|
|
|
static game_params *default_params(void)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
|
|
ret->n = 6;
|
|
ret->diff = DIFF_BASIC;
|
|
|
|
return ret;
|
|
}
|
|
|
|
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 *params;
|
|
char buf[80];
|
|
|
|
if (i < 0 || i >= lenof(dominosa_presets))
|
|
return false;
|
|
|
|
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_out = params;
|
|
return true;
|
|
}
|
|
|
|
static void free_params(game_params *params)
|
|
{
|
|
sfree(params);
|
|
}
|
|
|
|
static game_params *dup_params(const game_params *params)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
*ret = *params; /* structure copy */
|
|
return ret;
|
|
}
|
|
|
|
static void decode_params(game_params *params, char const *string)
|
|
{
|
|
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];
|
|
int len = sprintf(buf, "%d", params->n);
|
|
if (full)
|
|
len += sprintf(buf + len, "d%c", dominosa_diffchars[params->diff]);
|
|
return dupstr(buf);
|
|
}
|
|
|
|
static config_item *game_configure(const game_params *params)
|
|
{
|
|
config_item *ret;
|
|
char buf[80];
|
|
|
|
ret = snewn(3, config_item);
|
|
|
|
ret[0].name = "Maximum number on dominoes";
|
|
ret[0].type = C_STRING;
|
|
sprintf(buf, "%d", params->n);
|
|
ret[0].u.string.sval = dupstr(buf);
|
|
|
|
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;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static game_params *custom_params(const config_item *cfg)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
|
|
ret->n = atoi(cfg[0].u.string.sval);
|
|
ret->diff = cfg[1].u.choices.selected;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char *validate_params(const game_params *params, bool full)
|
|
{
|
|
if (params->n < 1)
|
|
return "Maximum face number must be at least one";
|
|
if (params->n > INT_MAX - 2 ||
|
|
params->n + 2 > INT_MAX / (params->n + 1))
|
|
return "Maximum face number must not be unreasonably large";
|
|
|
|
if (params->diff >= DIFFCOUNT)
|
|
return "Unknown difficulty rating";
|
|
return NULL;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* Solver.
|
|
*/
|
|
|
|
#ifdef STANDALONE_SOLVER
|
|
#define SOLVER_DIAGNOSTICS
|
|
static bool solver_diagnostics = false;
|
|
#elif defined SOLVER_DIAGNOSTICS
|
|
static 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;
|
|
DSF *dsf_scratch;
|
|
};
|
|
|
|
static struct solver_scratch *solver_make_scratch(int 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;
|
|
|
|
sc->n = n;
|
|
sc->dc = dc;
|
|
sc->pc = pc;
|
|
sc->w = w;
|
|
sc->h = h;
|
|
sc->wh = wh;
|
|
|
|
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);
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
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;
|
|
sc->dsf_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);
|
|
dsf_free(sc->dsf_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If a domino has only one placement remaining, rule out all other
|
|
* placements that overlap it.
|
|
*/
|
|
static bool deduce_domino_single_placement(struct solver_scratch *sc, int di)
|
|
{
|
|
struct solver_domino *d = &sc->dominoes[di];
|
|
struct solver_placement *p, *q;
|
|
int oi;
|
|
bool done_something = false;
|
|
|
|
if (d->nplacements != 1)
|
|
return false;
|
|
p = d->placements[0];
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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
|
|
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);
|
|
}
|
|
|
|
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
|
|
* 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 (!p->active)
|
|
return false;
|
|
|
|
for (i = 0; i < p->noverlaps; i++) {
|
|
struct solver_placement *q = p->overlaps[i];
|
|
struct solver_square *sq;
|
|
|
|
if (!q->active)
|
|
continue;
|
|
|
|
/* Find the square of q that _isn't_ part of p */
|
|
sq = q->squares[1 - common_square_index(q, p)];
|
|
|
|
for (j = 0; j < sq->nplacements; j++)
|
|
if (sq->placements[j] != q && sq->placements[j]->domino != d)
|
|
goto no;
|
|
|
|
/* 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
|
|
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
|
|
|
|
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;
|
|
};
|
|
|
|
static 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;
|
|
}
|
|
|
|
/*
|
|
* 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 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) {
|
|
/*
|
|
* 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 {
|
|
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) {
|
|
/*
|
|
* 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 (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++;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
if (!sc->dsf_scratch)
|
|
sc->dsf_scratch = dsf_new_flip(sc->pc);
|
|
|
|
/*
|
|
* Start by identifying chains of placements which must all occur
|
|
* together if any of them occurs. We do this by making
|
|
* dsf_scratch a flip dsf 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_reinit(sc->dsf_scratch);
|
|
for (si = 0; si < sc->wh; si++) {
|
|
struct solver_square *sq = &sc->squares[si];
|
|
if (sq->nplacements == 2)
|
|
dsf_merge_flip(sc->dsf_scratch,
|
|
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 = dsf_canonify_flip(sc->dsf_scratch, 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
|
|
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
|
|
|
|
for (k = cstart; k < climit; k++)
|
|
rule_out_placement(sc, &sc->placements[sc->pc_scratch2[k]]);
|
|
|
|
done_something = true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
for (di = 0; di < sc->dc; di++) {
|
|
struct solver_domino *d = &sc->dominoes[di];
|
|
|
|
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;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* 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, 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.
|
|
*/
|
|
sc = solver_make_scratch(n);
|
|
as = alloc_make_scratch(n);
|
|
|
|
/*
|
|
* I haven't been able to think of any particularly clever
|
|
* techniques for generating instances of Dominosa with a
|
|
* unique solution. Many of the deductions used in this puzzle
|
|
* are based on information involving half the grid at a time
|
|
* (`of all the 6s, exactly one is next to a 3'), so a strategy
|
|
* of partially solving the grid and then perturbing the place
|
|
* where the solver got stuck seems particularly likely to
|
|
* accidentally destroy the information which the solver had
|
|
* used in getting that far. (Contrast with, say, Mines, in
|
|
* which most deductions are local so this is an excellent
|
|
* strategy.)
|
|
*
|
|
* Therefore I resort to the basest of brute force methods:
|
|
* generate a random grid, see if it's solvable, throw it away
|
|
* and try again if not. My only concession to sophistication
|
|
* and cleverness is to at least _try_ not to generate obvious
|
|
* 2x2 ambiguous sections (see comment below in the domino-
|
|
* flipping section).
|
|
*
|
|
* During tests performed on 2005-07-15, I found that the brute
|
|
* force approach without that tweak had to throw away about 87
|
|
* grids on average (at the default n=6) before finding a
|
|
* unique one, or a staggering 379 at n=9; good job the
|
|
* generator and solver are fast! When I added the
|
|
* ambiguous-section avoidance, those numbers came down to 19
|
|
* and 26 respectively, which is a lot more sensible.
|
|
*/
|
|
|
|
while (1) {
|
|
alloc_make_layout(as, rs);
|
|
|
|
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 (!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 (!ok) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
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' + as->numbers[j*w+i]);
|
|
}
|
|
putchar('\n');
|
|
}
|
|
putchar('\n');
|
|
#endif
|
|
|
|
/*
|
|
* Encode the resulting game state.
|
|
*
|
|
* Our encoding is a string of digits. Any number greater than
|
|
* 9 is represented by a decimal integer within square
|
|
* brackets. We know there are n+2 of every number (it's paired
|
|
* with each number from 0 to n inclusive, and one of those is
|
|
* itself so that adds another occurrence), so we can work out
|
|
* the string length in advance.
|
|
*/
|
|
|
|
/*
|
|
* To work out the total length of the decimal encodings of all
|
|
* the numbers from 0 to n inclusive:
|
|
* - every number has a units digit; total is n+1.
|
|
* - all numbers above 9 have a tens digit; total is max(n+1-10,0).
|
|
* - all numbers above 99 have a hundreds digit; total is max(n+1-100,0).
|
|
* - and so on.
|
|
*/
|
|
len = n+1;
|
|
for (i = 10; i <= n; i *= 10)
|
|
len += max(n + 1 - i, 0);
|
|
/* Now add two square brackets for each number above 9. */
|
|
len += 2 * max(n + 1 - 10, 0);
|
|
/* And multiply by n+2 for the repeated occurrences of each number. */
|
|
len *= n+2;
|
|
|
|
/*
|
|
* Now actually encode the string.
|
|
*/
|
|
ret = snewn(len+1, char);
|
|
j = 0;
|
|
for (i = 0; i < wh; i++) {
|
|
k = as->numbers[i];
|
|
if (k < 10)
|
|
ret[j++] = '0' + k;
|
|
else
|
|
j += sprintf(ret+j, "[%d]", k);
|
|
assert(j <= len);
|
|
}
|
|
assert(j == len);
|
|
ret[j] = '\0';
|
|
|
|
/*
|
|
* Encode the solved state as an aux_info.
|
|
*/
|
|
{
|
|
char *auxinfo = snewn(wh+1, char);
|
|
|
|
for (i = 0; i < wh; i++) {
|
|
int v = as->layout[i];
|
|
auxinfo[i] = (v == i+1 ? 'L' : v == i-1 ? 'R' :
|
|
v == i+w ? 'T' : v == i-w ? 'B' : '.');
|
|
}
|
|
auxinfo[wh] = '\0';
|
|
|
|
*aux = auxinfo;
|
|
}
|
|
|
|
solver_free_scratch(sc);
|
|
alloc_free_scratch(as);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char *validate_desc(const game_params *params, const char *desc)
|
|
{
|
|
int n = params->n, w = n+2, h = n+1, wh = w*h;
|
|
int *occurrences;
|
|
int i, j;
|
|
const char *ret;
|
|
|
|
ret = NULL;
|
|
occurrences = snewn(n+1, int);
|
|
for (i = 0; i <= n; i++)
|
|
occurrences[i] = 0;
|
|
|
|
for (i = 0; i < wh; i++) {
|
|
if (!*desc) {
|
|
ret = ret ? ret : "Game description is too short";
|
|
} else {
|
|
if (*desc >= '0' && *desc <= '9')
|
|
j = *desc++ - '0';
|
|
else if (*desc == '[') {
|
|
desc++;
|
|
j = atoi(desc);
|
|
while (*desc && isdigit((unsigned char)*desc)) desc++;
|
|
if (*desc != ']')
|
|
ret = ret ? ret : "Missing ']' in game description";
|
|
else
|
|
desc++;
|
|
} else {
|
|
j = -1;
|
|
ret = ret ? ret : "Invalid syntax in game description";
|
|
}
|
|
if (j < 0 || j > n)
|
|
ret = ret ? ret : "Number out of range in game description";
|
|
else
|
|
occurrences[j]++;
|
|
}
|
|
}
|
|
|
|
if (*desc)
|
|
ret = ret ? ret : "Game description is too long";
|
|
|
|
if (!ret) {
|
|
for (i = 0; i <= n; i++)
|
|
if (occurrences[i] != n+2)
|
|
ret = "Incorrect number balance in game description";
|
|
}
|
|
|
|
sfree(occurrences);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static game_state *new_game(midend *me, const game_params *params,
|
|
const char *desc)
|
|
{
|
|
int n = params->n, w = n+2, h = n+1, wh = w*h;
|
|
game_state *state = snew(game_state);
|
|
int i, j;
|
|
|
|
state->params = *params;
|
|
state->w = w;
|
|
state->h = h;
|
|
|
|
state->grid = snewn(wh, int);
|
|
for (i = 0; i < wh; i++)
|
|
state->grid[i] = i;
|
|
|
|
state->edges = snewn(wh, unsigned short);
|
|
for (i = 0; i < wh; i++)
|
|
state->edges[i] = 0;
|
|
|
|
state->numbers = snew(struct game_numbers);
|
|
state->numbers->refcount = 1;
|
|
state->numbers->numbers = snewn(wh, int);
|
|
|
|
for (i = 0; i < wh; i++) {
|
|
assert(*desc);
|
|
if (*desc >= '0' && *desc <= '9')
|
|
j = *desc++ - '0';
|
|
else {
|
|
assert(*desc == '[');
|
|
desc++;
|
|
j = atoi(desc);
|
|
while (*desc && isdigit((unsigned char)*desc)) desc++;
|
|
assert(*desc == ']');
|
|
desc++;
|
|
}
|
|
assert(j >= 0 && j <= n);
|
|
state->numbers->numbers[i] = j;
|
|
}
|
|
|
|
state->completed = false;
|
|
state->cheated = false;
|
|
|
|
return state;
|
|
}
|
|
|
|
static game_state *dup_game(const game_state *state)
|
|
{
|
|
int n = state->params.n, w = n+2, h = n+1, wh = w*h;
|
|
game_state *ret = snew(game_state);
|
|
|
|
ret->params = state->params;
|
|
ret->w = state->w;
|
|
ret->h = state->h;
|
|
ret->grid = snewn(wh, int);
|
|
memcpy(ret->grid, state->grid, wh * sizeof(int));
|
|
ret->edges = snewn(wh, unsigned short);
|
|
memcpy(ret->edges, state->edges, wh * sizeof(unsigned short));
|
|
ret->numbers = state->numbers;
|
|
ret->numbers->refcount++;
|
|
ret->completed = state->completed;
|
|
ret->cheated = state->cheated;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void free_game(game_state *state)
|
|
{
|
|
sfree(state->grid);
|
|
sfree(state->edges);
|
|
if (--state->numbers->refcount <= 0) {
|
|
sfree(state->numbers->numbers);
|
|
sfree(state->numbers);
|
|
}
|
|
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;
|
|
char *ret;
|
|
int retlen, retsize;
|
|
int i;
|
|
char buf[80];
|
|
int extra;
|
|
|
|
if (aux) {
|
|
retsize = 256;
|
|
ret = snewn(retsize, char);
|
|
retlen = sprintf(ret, "S");
|
|
|
|
for (i = 0; i < wh; i++) {
|
|
if (aux[i] == 'L')
|
|
extra = sprintf(buf, ";D%d,%d", i, i+1);
|
|
else if (aux[i] == 'T')
|
|
extra = sprintf(buf, ";D%d,%d", i, i+w);
|
|
else
|
|
continue;
|
|
|
|
if (retlen + extra + 1 >= retsize) {
|
|
retsize = retlen + extra + 256;
|
|
ret = sresize(ret, retsize, char);
|
|
}
|
|
strcpy(ret + retlen, buf);
|
|
retlen += extra;
|
|
}
|
|
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
static bool game_can_format_as_text_now(const game_params *params)
|
|
{
|
|
return params->n < 1000;
|
|
}
|
|
|
|
static void draw_domino(char *board, int start, char corner,
|
|
int dshort, int nshort, char cshort,
|
|
int dlong, int nlong, char clong)
|
|
{
|
|
int go_short = nshort*dshort, go_long = nlong*dlong, i;
|
|
|
|
board[start] = corner;
|
|
board[start + go_short] = corner;
|
|
board[start + go_long] = corner;
|
|
board[start + go_short + go_long] = corner;
|
|
|
|
for (i = 1; i < nshort; ++i) {
|
|
int j = start + i*dshort, k = start + i*dshort + go_long;
|
|
if (board[j] != corner) board[j] = cshort;
|
|
if (board[k] != corner) board[k] = cshort;
|
|
}
|
|
|
|
for (i = 1; i < nlong; ++i) {
|
|
int j = start + i*dlong, k = start + i*dlong + go_short;
|
|
if (board[j] != corner) board[j] = clong;
|
|
if (board[k] != corner) board[k] = clong;
|
|
}
|
|
}
|
|
|
|
static char *game_text_format(const game_state *state)
|
|
{
|
|
int w = state->w, h = state->h, r, c;
|
|
int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh;
|
|
char *board = snewn(len + 1, char);
|
|
|
|
memset(board, ' ', len);
|
|
|
|
for (r = 0; r < h; ++r) {
|
|
for (c = 0; c < w; ++c) {
|
|
int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2;
|
|
int i = r*w + c, num = state->numbers->numbers[i];
|
|
|
|
if (num < 100) {
|
|
board[center] = '0' + num % 10;
|
|
if (num >= 10) board[center - 1] = '0' + num / 10;
|
|
} else {
|
|
board[center+1] = '0' + num % 10;
|
|
board[center] = '0' + num / 10 % 10;
|
|
board[center-1] = '0' + num / 100;
|
|
}
|
|
|
|
if (state->edges[i] & EDGE_L) board[center - cw/2] = '|';
|
|
if (state->edges[i] & EDGE_R) board[center + cw/2] = '|';
|
|
if (state->edges[i] & EDGE_T) board[center - gw] = '-';
|
|
if (state->edges[i] & EDGE_B) board[center + gw] = '-';
|
|
|
|
if (state->grid[i] == i) continue; /* no domino pairing */
|
|
if (state->grid[i] < i) continue; /* already done */
|
|
assert (state->grid[i] == i + 1 || state->grid[i] == i + w);
|
|
if (state->grid[i] == i + 1)
|
|
draw_domino(board, cell, '+', gw, ch, '|', +1, 2*cw, '-');
|
|
else if (state->grid[i] == i + w)
|
|
draw_domino(board, cell, '+', +1, cw, '-', gw, 2*ch, '|');
|
|
}
|
|
board[r*ch*gw + gw - 1] = '\n';
|
|
board[r*ch*gw + gw + gw - 1] = '\n';
|
|
}
|
|
board[len - 1] = '\n';
|
|
board[len] = '\0';
|
|
return board;
|
|
}
|
|
|
|
struct game_ui {
|
|
int cur_x, cur_y, highlight_1, highlight_2;
|
|
bool cur_visible;
|
|
};
|
|
|
|
static game_ui *new_ui(const game_state *state)
|
|
{
|
|
game_ui *ui = snew(game_ui);
|
|
ui->cur_x = ui->cur_y = 0;
|
|
ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
|
|
ui->highlight_1 = ui->highlight_2 = -1;
|
|
return ui;
|
|
}
|
|
|
|
static void free_ui(game_ui *ui)
|
|
{
|
|
sfree(ui);
|
|
}
|
|
|
|
static void game_changed_state(game_ui *ui, const game_state *oldstate,
|
|
const game_state *newstate)
|
|
{
|
|
if (!oldstate->completed && newstate->completed)
|
|
ui->cur_visible = false;
|
|
}
|
|
|
|
static const char *current_key_label(const game_ui *ui,
|
|
const game_state *state, int button)
|
|
{
|
|
if (IS_CURSOR_SELECT(button)) {
|
|
int d1, d2, w = state->w;
|
|
|
|
if (!((ui->cur_x ^ ui->cur_y) & 1))
|
|
return ""; /* must have exactly one dimension odd */
|
|
d1 = (ui->cur_y / 2) * w + (ui->cur_x / 2);
|
|
d2 = ((ui->cur_y+1) / 2) * w + ((ui->cur_x+1) / 2);
|
|
|
|
/* We can't mark an edge next to any domino. */
|
|
if (button == CURSOR_SELECT2 &&
|
|
(state->grid[d1] != d1 || state->grid[d2] != d2))
|
|
return "";
|
|
if (button == CURSOR_SELECT) {
|
|
if (state->grid[d1] == d2) return "Remove";
|
|
return "Place";
|
|
} else {
|
|
int edge = d2 == d1 + 1 ? EDGE_R : EDGE_B;
|
|
if (state->edges[d1] & edge) return "Remove";
|
|
return "Line";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
#define PREFERRED_TILESIZE 32
|
|
#define TILESIZE (ds->tilesize)
|
|
#define BORDER (TILESIZE * 3 / 4)
|
|
#define DOMINO_GUTTER (TILESIZE / 16)
|
|
#define DOMINO_RADIUS (TILESIZE / 8)
|
|
#define DOMINO_COFFSET (DOMINO_GUTTER + DOMINO_RADIUS)
|
|
#define CURSOR_RADIUS (TILESIZE / 4)
|
|
|
|
#define COORD(x) ( (x) * TILESIZE + BORDER )
|
|
#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
|
|
|
|
struct game_drawstate {
|
|
int w, h, tilesize;
|
|
unsigned long *visible;
|
|
};
|
|
|
|
static char *interpret_move(const game_state *state, game_ui *ui,
|
|
const game_drawstate *ds,
|
|
int x, int y, int button)
|
|
{
|
|
int w = state->w, h = state->h;
|
|
char buf[80];
|
|
|
|
/*
|
|
* A left-click between two numbers toggles a domino covering
|
|
* them. A right-click toggles an edge.
|
|
*/
|
|
if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
|
|
int tx = FROMCOORD(x), ty = FROMCOORD(y), t = ty*w+tx;
|
|
int dx, dy;
|
|
int d1, d2;
|
|
|
|
if (tx < 0 || tx >= w || ty < 0 || ty >= h)
|
|
return MOVE_UNUSED;
|
|
|
|
/*
|
|
* Now we know which square the click was in, decide which
|
|
* edge of the square it was closest to.
|
|
*/
|
|
dx = 2 * (x - COORD(tx)) - TILESIZE;
|
|
dy = 2 * (y - COORD(ty)) - TILESIZE;
|
|
|
|
if (abs(dx) > abs(dy) && dx < 0 && tx > 0)
|
|
d1 = t - 1, d2 = t; /* clicked in right side of domino */
|
|
else if (abs(dx) > abs(dy) && dx > 0 && tx+1 < w)
|
|
d1 = t, d2 = t + 1; /* clicked in left side of domino */
|
|
else if (abs(dy) > abs(dx) && dy < 0 && ty > 0)
|
|
d1 = t - w, d2 = t; /* clicked in bottom half of domino */
|
|
else if (abs(dy) > abs(dx) && dy > 0 && ty+1 < h)
|
|
d1 = t, d2 = t + w; /* clicked in top half of domino */
|
|
else
|
|
return MOVE_NO_EFFECT; /* clicked precisely on a diagonal */
|
|
|
|
/*
|
|
* We can't mark an edge next to any domino.
|
|
*/
|
|
if (button == RIGHT_BUTTON &&
|
|
(state->grid[d1] != d1 || state->grid[d2] != d2))
|
|
return MOVE_NO_EFFECT;
|
|
|
|
ui->cur_visible = false;
|
|
sprintf(buf, "%c%d,%d", (int)(button == RIGHT_BUTTON ? 'E' : 'D'), d1, d2);
|
|
return dupstr(buf);
|
|
} else if (IS_CURSOR_MOVE(button)) {
|
|
return move_cursor(button, &ui->cur_x, &ui->cur_y, 2*w-1, 2*h-1, false,
|
|
&ui->cur_visible);
|
|
} else if (IS_CURSOR_SELECT(button)) {
|
|
int d1, d2;
|
|
|
|
if (!((ui->cur_x ^ ui->cur_y) & 1))
|
|
return MOVE_NO_EFFECT; /* must have exactly one dimension odd */
|
|
d1 = (ui->cur_y / 2) * w + (ui->cur_x / 2);
|
|
d2 = ((ui->cur_y+1) / 2) * w + ((ui->cur_x+1) / 2);
|
|
|
|
/*
|
|
* We can't mark an edge next to any domino.
|
|
*/
|
|
if (button == CURSOR_SELECT2 &&
|
|
(state->grid[d1] != d1 || state->grid[d2] != d2))
|
|
return MOVE_NO_EFFECT;
|
|
|
|
sprintf(buf, "%c%d,%d", (int)(button == CURSOR_SELECT2 ? 'E' : 'D'), d1, d2);
|
|
return dupstr(buf);
|
|
} else if (isdigit(button)) {
|
|
int n = state->params.n, num = button - '0';
|
|
if (num > n) {
|
|
return MOVE_UNUSED;
|
|
} else if (ui->highlight_1 == num) {
|
|
ui->highlight_1 = -1;
|
|
} else if (ui->highlight_2 == num) {
|
|
ui->highlight_2 = -1;
|
|
} else if (ui->highlight_1 == -1) {
|
|
ui->highlight_1 = num;
|
|
} else if (ui->highlight_2 == -1) {
|
|
ui->highlight_2 = num;
|
|
} else {
|
|
return MOVE_NO_EFFECT;
|
|
}
|
|
return MOVE_UI_UPDATE;
|
|
}
|
|
|
|
return MOVE_UNUSED;
|
|
}
|
|
|
|
static game_state *execute_move(const game_state *state, const char *move)
|
|
{
|
|
int n = state->params.n, w = n+2, h = n+1, wh = w*h;
|
|
int d1, d2, d3, p;
|
|
game_state *ret = dup_game(state);
|
|
|
|
while (*move) {
|
|
if (move[0] == 'S') {
|
|
int i;
|
|
|
|
ret->cheated = true;
|
|
|
|
/*
|
|
* Clear the existing edges and domino placements. We
|
|
* expect the S to be followed by other commands.
|
|
*/
|
|
for (i = 0; i < wh; i++) {
|
|
ret->grid[i] = i;
|
|
ret->edges[i] = 0;
|
|
}
|
|
move++;
|
|
} else if (move[0] == 'D' &&
|
|
sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 &&
|
|
d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2 &&
|
|
(d2 - d1 == 1 || d2 - d1 == w)) {
|
|
|
|
/*
|
|
* Toggle domino presence between d1 and d2.
|
|
*/
|
|
if (ret->grid[d1] == d2) {
|
|
assert(ret->grid[d2] == d1);
|
|
ret->grid[d1] = d1;
|
|
ret->grid[d2] = d2;
|
|
} else {
|
|
/*
|
|
* Erase any dominoes that might overlap the new one.
|
|
*/
|
|
d3 = ret->grid[d1];
|
|
if (d3 != d1)
|
|
ret->grid[d3] = d3;
|
|
d3 = ret->grid[d2];
|
|
if (d3 != d2)
|
|
ret->grid[d3] = d3;
|
|
/*
|
|
* Place the new one.
|
|
*/
|
|
ret->grid[d1] = d2;
|
|
ret->grid[d2] = d1;
|
|
|
|
/*
|
|
* Destroy any edges lurking around it.
|
|
*/
|
|
if (ret->edges[d1] & EDGE_L) {
|
|
assert(d1 - 1 >= 0);
|
|
ret->edges[d1 - 1] &= ~EDGE_R;
|
|
}
|
|
if (ret->edges[d1] & EDGE_R) {
|
|
assert(d1 + 1 < wh);
|
|
ret->edges[d1 + 1] &= ~EDGE_L;
|
|
}
|
|
if (ret->edges[d1] & EDGE_T) {
|
|
assert(d1 - w >= 0);
|
|
ret->edges[d1 - w] &= ~EDGE_B;
|
|
}
|
|
if (ret->edges[d1] & EDGE_B) {
|
|
assert(d1 + 1 < wh);
|
|
ret->edges[d1 + w] &= ~EDGE_T;
|
|
}
|
|
ret->edges[d1] = 0;
|
|
if (ret->edges[d2] & EDGE_L) {
|
|
assert(d2 - 1 >= 0);
|
|
ret->edges[d2 - 1] &= ~EDGE_R;
|
|
}
|
|
if (ret->edges[d2] & EDGE_R) {
|
|
assert(d2 + 1 < wh);
|
|
ret->edges[d2 + 1] &= ~EDGE_L;
|
|
}
|
|
if (ret->edges[d2] & EDGE_T) {
|
|
assert(d2 - w >= 0);
|
|
ret->edges[d2 - w] &= ~EDGE_B;
|
|
}
|
|
if (ret->edges[d2] & EDGE_B) {
|
|
assert(d2 + 1 < wh);
|
|
ret->edges[d2 + w] &= ~EDGE_T;
|
|
}
|
|
ret->edges[d2] = 0;
|
|
}
|
|
|
|
move += p+1;
|
|
} else if (move[0] == 'E' &&
|
|
sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 &&
|
|
d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2 &&
|
|
ret->grid[d1] == d1 && ret->grid[d2] == d2 &&
|
|
(d2 - d1 == 1 || d2 - d1 == w)) {
|
|
|
|
/*
|
|
* Toggle edge presence between d1 and d2.
|
|
*/
|
|
if (d2 == d1 + 1) {
|
|
ret->edges[d1] ^= EDGE_R;
|
|
ret->edges[d2] ^= EDGE_L;
|
|
} else {
|
|
ret->edges[d1] ^= EDGE_B;
|
|
ret->edges[d2] ^= EDGE_T;
|
|
}
|
|
|
|
move += p+1;
|
|
} else {
|
|
free_game(ret);
|
|
return NULL;
|
|
}
|
|
|
|
if (*move) {
|
|
if (*move != ';') {
|
|
free_game(ret);
|
|
return NULL;
|
|
}
|
|
move++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* After modifying the grid, check completion.
|
|
*/
|
|
if (!ret->completed) {
|
|
int i, ok = 0;
|
|
bool *used = snewn(TRI(n+1), bool);
|
|
|
|
memset(used, 0, TRI(n+1));
|
|
for (i = 0; i < wh; i++)
|
|
if (ret->grid[i] > i) {
|
|
int n1, n2, di;
|
|
|
|
n1 = ret->numbers->numbers[i];
|
|
n2 = ret->numbers->numbers[ret->grid[i]];
|
|
|
|
di = DINDEX(n1, n2);
|
|
assert(di >= 0 && di < TRI(n+1));
|
|
|
|
if (!used[di]) {
|
|
used[di] = true;
|
|
ok++;
|
|
}
|
|
}
|
|
|
|
sfree(used);
|
|
if (ok == DCOUNT(n))
|
|
ret->completed = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* Drawing routines.
|
|
*/
|
|
|
|
static void game_compute_size(const game_params *params, int tilesize,
|
|
const game_ui *ui, int *x, int *y)
|
|
{
|
|
int n = params->n, w = n+2, h = n+1;
|
|
|
|
/* Ick: fake up `ds->tilesize' for macro expansion purposes */
|
|
struct { int tilesize; } ads, *ds = &ads;
|
|
ads.tilesize = tilesize;
|
|
|
|
*x = w * TILESIZE + 2*BORDER;
|
|
*y = h * TILESIZE + 2*BORDER;
|
|
}
|
|
|
|
static void game_set_size(drawing *dr, game_drawstate *ds,
|
|
const game_params *params, int tilesize)
|
|
{
|
|
ds->tilesize = tilesize;
|
|
}
|
|
|
|
static float *game_colours(frontend *fe, int *ncolours)
|
|
{
|
|
float *ret = snewn(3 * NCOLOURS, float);
|
|
|
|
frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
|
|
|
|
ret[COL_TEXT * 3 + 0] = 0.0F;
|
|
ret[COL_TEXT * 3 + 1] = 0.0F;
|
|
ret[COL_TEXT * 3 + 2] = 0.0F;
|
|
|
|
ret[COL_DOMINO * 3 + 0] = 0.0F;
|
|
ret[COL_DOMINO * 3 + 1] = 0.0F;
|
|
ret[COL_DOMINO * 3 + 2] = 0.0F;
|
|
|
|
ret[COL_DOMINOCLASH * 3 + 0] = 0.5F;
|
|
ret[COL_DOMINOCLASH * 3 + 1] = 0.0F;
|
|
ret[COL_DOMINOCLASH * 3 + 2] = 0.0F;
|
|
|
|
ret[COL_DOMINOTEXT * 3 + 0] = 1.0F;
|
|
ret[COL_DOMINOTEXT * 3 + 1] = 1.0F;
|
|
ret[COL_DOMINOTEXT * 3 + 2] = 1.0F;
|
|
|
|
ret[COL_EDGE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2 / 3;
|
|
ret[COL_EDGE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2 / 3;
|
|
ret[COL_EDGE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2 / 3;
|
|
|
|
ret[COL_HIGHLIGHT_1 * 3 + 0] = 0.85;
|
|
ret[COL_HIGHLIGHT_1 * 3 + 1] = 0.20;
|
|
ret[COL_HIGHLIGHT_1 * 3 + 2] = 0.20;
|
|
|
|
ret[COL_HIGHLIGHT_2 * 3 + 0] = 0.30;
|
|
ret[COL_HIGHLIGHT_2 * 3 + 1] = 0.85;
|
|
ret[COL_HIGHLIGHT_2 * 3 + 2] = 0.20;
|
|
|
|
*ncolours = NCOLOURS;
|
|
return ret;
|
|
}
|
|
|
|
static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
|
|
{
|
|
struct game_drawstate *ds = snew(struct game_drawstate);
|
|
int i;
|
|
|
|
ds->w = state->w;
|
|
ds->h = state->h;
|
|
ds->visible = snewn(ds->w * ds->h, unsigned long);
|
|
ds->tilesize = 0; /* not decided yet */
|
|
for (i = 0; i < ds->w * ds->h; i++)
|
|
ds->visible[i] = 0xFFFF;
|
|
|
|
return ds;
|
|
}
|
|
|
|
static void game_free_drawstate(drawing *dr, game_drawstate *ds)
|
|
{
|
|
sfree(ds->visible);
|
|
sfree(ds);
|
|
}
|
|
|
|
enum {
|
|
TYPE_L,
|
|
TYPE_R,
|
|
TYPE_T,
|
|
TYPE_B,
|
|
TYPE_BLANK,
|
|
TYPE_MASK = 0x0F
|
|
};
|
|
|
|
/* These flags must be disjoint with:
|
|
* the above enum (TYPE_*) [0x000 -- 0x00F]
|
|
* EDGE_* [0x100 -- 0xF00]
|
|
* and must fit into an unsigned long (32 bits).
|
|
*/
|
|
#define DF_HIGHLIGHT_1 0x10
|
|
#define DF_HIGHLIGHT_2 0x20
|
|
#define DF_FLASH 0x40
|
|
#define DF_CLASH 0x80
|
|
|
|
#define DF_CURSOR 0x01000
|
|
#define DF_CURSOR_USEFUL 0x02000
|
|
#define DF_CURSOR_XBASE 0x10000
|
|
#define DF_CURSOR_XMASK 0x30000
|
|
#define DF_CURSOR_YBASE 0x40000
|
|
#define DF_CURSOR_YMASK 0xC0000
|
|
|
|
#define CEDGE_OFF (TILESIZE / 8)
|
|
#define IS_EMPTY(s,x,y) ((s)->grid[(y)*(s)->w+(x)] == ((y)*(s)->w+(x)))
|
|
|
|
static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
|
|
int x, int y, int type, int highlight_1, int highlight_2)
|
|
{
|
|
int w = state->w /*, h = state->h */;
|
|
int cx = COORD(x), cy = COORD(y);
|
|
int nc;
|
|
char str[80];
|
|
int flags;
|
|
|
|
clip(dr, cx, cy, TILESIZE, TILESIZE);
|
|
draw_rect(dr, cx, cy, TILESIZE, TILESIZE, COL_BACKGROUND);
|
|
|
|
flags = type &~ TYPE_MASK;
|
|
type &= TYPE_MASK;
|
|
|
|
if (type != TYPE_BLANK) {
|
|
int i, bg;
|
|
|
|
/*
|
|
* Draw one end of a domino. This is composed of:
|
|
*
|
|
* - two filled circles (rounded corners)
|
|
* - two rectangles
|
|
* - a slight shift in the number
|
|
*/
|
|
|
|
if (flags & DF_CLASH)
|
|
bg = COL_DOMINOCLASH;
|
|
else
|
|
bg = COL_DOMINO;
|
|
nc = COL_DOMINOTEXT;
|
|
|
|
if (flags & DF_FLASH) {
|
|
int tmp = nc;
|
|
nc = bg;
|
|
bg = tmp;
|
|
}
|
|
|
|
if (type == TYPE_L || type == TYPE_T)
|
|
draw_circle(dr, cx+DOMINO_COFFSET, cy+DOMINO_COFFSET,
|
|
DOMINO_RADIUS, bg, bg);
|
|
if (type == TYPE_R || type == TYPE_T)
|
|
draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET, cy+DOMINO_COFFSET,
|
|
DOMINO_RADIUS, bg, bg);
|
|
if (type == TYPE_L || type == TYPE_B)
|
|
draw_circle(dr, cx+DOMINO_COFFSET, cy+TILESIZE-1-DOMINO_COFFSET,
|
|
DOMINO_RADIUS, bg, bg);
|
|
if (type == TYPE_R || type == TYPE_B)
|
|
draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET,
|
|
cy+TILESIZE-1-DOMINO_COFFSET,
|
|
DOMINO_RADIUS, bg, bg);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
int x1, y1, x2, y2;
|
|
|
|
x1 = cx + (i ? DOMINO_GUTTER : DOMINO_COFFSET);
|
|
y1 = cy + (i ? DOMINO_COFFSET : DOMINO_GUTTER);
|
|
x2 = cx + TILESIZE-1 - (i ? DOMINO_GUTTER : DOMINO_COFFSET);
|
|
y2 = cy + TILESIZE-1 - (i ? DOMINO_COFFSET : DOMINO_GUTTER);
|
|
if (type == TYPE_L)
|
|
x2 = cx + TILESIZE + TILESIZE/16;
|
|
else if (type == TYPE_R)
|
|
x1 = cx - TILESIZE/16;
|
|
else if (type == TYPE_T)
|
|
y2 = cy + TILESIZE + TILESIZE/16;
|
|
else if (type == TYPE_B)
|
|
y1 = cy - TILESIZE/16;
|
|
|
|
draw_rect(dr, x1, y1, x2-x1+1, y2-y1+1, bg);
|
|
}
|
|
} else {
|
|
if (flags & EDGE_T)
|
|
draw_rect(dr, cx+DOMINO_GUTTER, cy,
|
|
TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE);
|
|
if (flags & EDGE_B)
|
|
draw_rect(dr, cx+DOMINO_GUTTER, cy+TILESIZE-1,
|
|
TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE);
|
|
if (flags & EDGE_L)
|
|
draw_rect(dr, cx, cy+DOMINO_GUTTER,
|
|
1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE);
|
|
if (flags & EDGE_R)
|
|
draw_rect(dr, cx+TILESIZE-1, cy+DOMINO_GUTTER,
|
|
1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE);
|
|
nc = COL_TEXT;
|
|
}
|
|
|
|
if (flags & DF_CURSOR) {
|
|
int curx = ((flags & DF_CURSOR_XMASK) / DF_CURSOR_XBASE) & 3;
|
|
int cury = ((flags & DF_CURSOR_YMASK) / DF_CURSOR_YBASE) & 3;
|
|
int ox = cx + curx*TILESIZE/2;
|
|
int oy = cy + cury*TILESIZE/2;
|
|
|
|
draw_rect_corners(dr, ox, oy, CURSOR_RADIUS, nc);
|
|
if (flags & DF_CURSOR_USEFUL)
|
|
draw_rect_corners(dr, ox, oy, CURSOR_RADIUS+1, nc);
|
|
}
|
|
|
|
if (flags & DF_HIGHLIGHT_1) {
|
|
nc = COL_HIGHLIGHT_1;
|
|
} else if (flags & DF_HIGHLIGHT_2) {
|
|
nc = COL_HIGHLIGHT_2;
|
|
}
|
|
|
|
sprintf(str, "%d", state->numbers->numbers[y*w+x]);
|
|
draw_text(dr, cx+TILESIZE/2, cy+TILESIZE/2, FONT_VARIABLE, TILESIZE/2,
|
|
ALIGN_HCENTRE | ALIGN_VCENTRE, nc, str);
|
|
|
|
draw_update(dr, cx, cy, TILESIZE, TILESIZE);
|
|
unclip(dr);
|
|
}
|
|
|
|
static void game_redraw(drawing *dr, game_drawstate *ds,
|
|
const game_state *oldstate, const game_state *state,
|
|
int dir, const game_ui *ui,
|
|
float animtime, float flashtime)
|
|
{
|
|
int n = state->params.n, w = state->w, h = state->h, wh = w*h;
|
|
int x, y, i;
|
|
unsigned char *used;
|
|
|
|
/*
|
|
* See how many dominoes of each type there are, so we can
|
|
* highlight clashes in red.
|
|
*/
|
|
used = snewn(TRI(n+1), unsigned char);
|
|
memset(used, 0, TRI(n+1));
|
|
for (i = 0; i < wh; i++)
|
|
if (state->grid[i] > i) {
|
|
int n1, n2, di;
|
|
|
|
n1 = state->numbers->numbers[i];
|
|
n2 = state->numbers->numbers[state->grid[i]];
|
|
|
|
di = DINDEX(n1, n2);
|
|
assert(di >= 0 && di < TRI(n+1));
|
|
|
|
if (used[di] < 2)
|
|
used[di]++;
|
|
}
|
|
|
|
for (y = 0; y < h; y++)
|
|
for (x = 0; x < w; x++) {
|
|
int n = y*w+x;
|
|
int n1, n2, di;
|
|
unsigned long c;
|
|
|
|
if (state->grid[n] == n-1)
|
|
c = TYPE_R;
|
|
else if (state->grid[n] == n+1)
|
|
c = TYPE_L;
|
|
else if (state->grid[n] == n-w)
|
|
c = TYPE_B;
|
|
else if (state->grid[n] == n+w)
|
|
c = TYPE_T;
|
|
else
|
|
c = TYPE_BLANK;
|
|
|
|
n1 = state->numbers->numbers[n];
|
|
if (c != TYPE_BLANK) {
|
|
n2 = state->numbers->numbers[state->grid[n]];
|
|
di = DINDEX(n1, n2);
|
|
if (used[di] > 1)
|
|
c |= DF_CLASH; /* highlight a clash */
|
|
} else {
|
|
c |= state->edges[n];
|
|
}
|
|
|
|
if (n1 == ui->highlight_1)
|
|
c |= DF_HIGHLIGHT_1;
|
|
if (n1 == ui->highlight_2)
|
|
c |= DF_HIGHLIGHT_2;
|
|
|
|
if (flashtime != 0)
|
|
c |= DF_FLASH; /* we're flashing */
|
|
|
|
if (ui->cur_visible) {
|
|
unsigned curx = (unsigned)(ui->cur_x - (2*x-1));
|
|
unsigned cury = (unsigned)(ui->cur_y - (2*y-1));
|
|
if (curx < 3 && cury < 3) {
|
|
c |= (DF_CURSOR |
|
|
(curx * DF_CURSOR_XBASE) |
|
|
(cury * DF_CURSOR_YBASE));
|
|
if ((ui->cur_x ^ ui->cur_y) & 1)
|
|
c |= DF_CURSOR_USEFUL;
|
|
}
|
|
}
|
|
|
|
if (ds->visible[n] != c) {
|
|
draw_tile(dr, ds, state, x, y, c,
|
|
ui->highlight_1, ui->highlight_2);
|
|
ds->visible[n] = c;
|
|
}
|
|
}
|
|
|
|
sfree(used);
|
|
}
|
|
|
|
static float game_anim_length(const game_state *oldstate,
|
|
const game_state *newstate, int dir, game_ui *ui)
|
|
{
|
|
return 0.0F;
|
|
}
|
|
|
|
static float game_flash_length(const game_state *oldstate,
|
|
const game_state *newstate, int dir, game_ui *ui)
|
|
{
|
|
if (!oldstate->completed && newstate->completed &&
|
|
!oldstate->cheated && !newstate->cheated)
|
|
{
|
|
ui->highlight_1 = ui->highlight_2 = -1;
|
|
return FLASH_TIME;
|
|
}
|
|
return 0.0F;
|
|
}
|
|
|
|
static void game_get_cursor_location(const game_ui *ui,
|
|
const game_drawstate *ds,
|
|
const game_state *state,
|
|
const game_params *params,
|
|
int *x, int *y, int *w, int *h)
|
|
{
|
|
if(ui->cur_visible)
|
|
{
|
|
*x = BORDER + ((2 * ui->cur_x + 1) * TILESIZE) / 4;
|
|
*y = BORDER + ((2 * ui->cur_y + 1) * TILESIZE) / 4;
|
|
*w = *h = TILESIZE / 2 + 2;
|
|
}
|
|
}
|
|
|
|
static int game_status(const game_state *state)
|
|
{
|
|
return state->completed ? +1 : 0;
|
|
}
|
|
|
|
static void game_print_size(const game_params *params, const game_ui *ui,
|
|
float *x, float *y)
|
|
{
|
|
int pw, ph;
|
|
|
|
/*
|
|
* I'll use 6mm squares by default.
|
|
*/
|
|
game_compute_size(params, 600, ui, &pw, &ph);
|
|
*x = pw / 100.0F;
|
|
*y = ph / 100.0F;
|
|
}
|
|
|
|
static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
|
|
int tilesize)
|
|
{
|
|
int w = state->w, h = state->h;
|
|
int c, x, y;
|
|
|
|
/* Ick: fake up `ds->tilesize' for macro expansion purposes */
|
|
game_drawstate ads, *ds = &ads;
|
|
game_set_size(dr, ds, NULL, tilesize);
|
|
|
|
c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
|
|
c = print_mono_colour(dr, 0); assert(c == COL_TEXT);
|
|
c = print_mono_colour(dr, 0); assert(c == COL_DOMINO);
|
|
c = print_mono_colour(dr, 0); assert(c == COL_DOMINOCLASH);
|
|
c = print_mono_colour(dr, 1); assert(c == COL_DOMINOTEXT);
|
|
c = print_mono_colour(dr, 0); assert(c == COL_EDGE);
|
|
|
|
for (y = 0; y < h; y++)
|
|
for (x = 0; x < w; x++) {
|
|
int n = y*w+x;
|
|
unsigned long c;
|
|
|
|
if (state->grid[n] == n-1)
|
|
c = TYPE_R;
|
|
else if (state->grid[n] == n+1)
|
|
c = TYPE_L;
|
|
else if (state->grid[n] == n-w)
|
|
c = TYPE_B;
|
|
else if (state->grid[n] == n+w)
|
|
c = TYPE_T;
|
|
else
|
|
c = TYPE_BLANK;
|
|
|
|
draw_tile(dr, ds, state, x, y, c, -1, -1);
|
|
}
|
|
}
|
|
|
|
#ifdef COMBINED
|
|
#define thegame dominosa
|
|
#endif
|
|
|
|
const struct game thegame = {
|
|
"Dominosa", "games.dominosa", "dominosa",
|
|
default_params,
|
|
game_fetch_preset, NULL,
|
|
decode_params,
|
|
encode_params,
|
|
free_params,
|
|
dup_params,
|
|
true, game_configure, custom_params,
|
|
validate_params,
|
|
new_game_desc,
|
|
validate_desc,
|
|
new_game,
|
|
dup_game,
|
|
free_game,
|
|
true, solve_game,
|
|
true, game_can_format_as_text_now, game_text_format,
|
|
NULL, NULL, /* get_prefs, set_prefs */
|
|
new_ui,
|
|
free_ui,
|
|
NULL, /* encode_ui */
|
|
NULL, /* decode_ui */
|
|
NULL, /* game_request_keys */
|
|
game_changed_state,
|
|
current_key_label,
|
|
interpret_move,
|
|
execute_move,
|
|
PREFERRED_TILESIZE, game_compute_size, game_set_size,
|
|
game_colours,
|
|
game_new_drawstate,
|
|
game_free_drawstate,
|
|
game_redraw,
|
|
game_anim_length,
|
|
game_flash_length,
|
|
game_get_cursor_location,
|
|
game_status,
|
|
true, false, game_print_size, game_print,
|
|
false, /* wants_statusbar */
|
|
false, NULL, /* timing_state */
|
|
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: */
|
|
|