ref: a2b70e2a6e90819df09812e96b153b1f6a408cde
dir: /dominosa.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: * * * rule out a domino placement if it would divide an unfilled * region such that at least one resulting region had an odd area * + Tarjan's bridge-finding algorithm would be a way to find * domino placements that split a connected region in two: form * the graph whose vertices are unpaired squares and whose edges * are potential (not placed but also not ruled out) dominoes * covering two of them, and any bridge in that graph is a * candidate. * + Then, finding any old spanning forest of the unfilled squares * should be sufficient to determine the area parity of the * region that any such placement would cut off. * + A more advanced form of this: if you have a region with _two_ * ways 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, maybe if both 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, on purely geometric grounds, they would box in * a square to the point where it ended up having to be an * isolated singleton. * * * 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 <math.h> #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->diff >= DIFFCOUNT) return "Unknown difficulty rating"; return NULL; } /* ---------------------------------------------------------------------- * Solver. */ #ifdef STANDALONE_SOLVER #define SOLVER_DIAGNOSTICS bool solver_diagnostics = false; #elif defined SOLVER_DIAGNOSTICS const bool solver_diagnostics = true; #endif struct solver_domino; struct solver_placement; /* * Information about a particular domino. */ struct solver_domino { /* The numbers on the domino, and its index in the dominoes array. */ int lo, hi, index; /* List of placements not yet ruled out for this domino. */ int nplacements; struct solver_placement **placements; #ifdef SOLVER_DIAGNOSTICS /* A textual name we can easily reuse in solver diagnostics. */ char *name; #endif }; /* * Information about a particular 'placement' (i.e. specific location * that a domino might go in). */ struct solver_placement { /* The index of this placement in sc->placements. */ int index; /* The two squares that make up this placement. */ struct solver_square *squares[2]; /* The domino that has to go in this position, if any. */ struct solver_domino *domino; /* The index of this placement in each square's placements array, * and in that of the domino. */ int spi[2], dpi; /* Whether this is still considered a possible placement. */ bool active; /* Other domino placements that overlap with this one. (Maximum 6: * three overlapping each square of the placement.) */ int noverlaps; struct solver_placement *overlaps[6]; #ifdef SOLVER_DIAGNOSTICS /* A textual name we can easily reuse in solver diagnostics. */ char *name; #endif }; /* * Information about a particular solver square. */ struct solver_square { /* The coordinates of the square, and its index in a normal grid array. */ int x, y, index; /* List of domino placements not yet ruled out for this square. */ int nplacements; struct solver_placement *placements[4]; /* The number in the square. */ int number; #ifdef SOLVER_DIAGNOSTICS /* A textual name we can easily reuse in solver diagnostics. */ char *name; #endif }; struct solver_scratch { int n, dc, pc, w, h, wh; int max_diff_used; struct solver_domino *dominoes; struct solver_placement *placements; struct solver_square *squares; struct solver_placement **domino_placement_lists; struct solver_square **squares_by_number; bool squares_by_number_initialised; int *wh_scratch, *pc_scratch, *pc_scratch2, *dc_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 *); 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; 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); sfree(sc->wh_scratch); sfree(sc->pc_scratch); sfree(sc->pc_scratch2); sfree(sc->dc_scratch); sfree(sc); } static void solver_setup_grid(struct solver_scratch *sc, const int *numbers) { int i, j; for (i = 0; i < sc->wh; i++) { sc->squares[i].nplacements = 0; sc->squares[i].number = numbers[sc->squares[i].index]; } for (i = 0; i < sc->pc; i++) { struct solver_placement *p = &sc->placements[i]; int di = DINDEX(p->squares[0]->number, p->squares[1]->number); p->domino = &sc->dominoes[di]; } for (i = 0; i < sc->dc; i++) sc->dominoes[i].nplacements = 0; for (i = 0; i < sc->pc; i++) sc->placements[i].domino->nplacements++; for (i = j = 0; i < sc->dc; i++) { sc->dominoes[i].placements = sc->domino_placement_lists + j; j += sc->dominoes[i].nplacements; sc->dominoes[i].nplacements = 0; } for (i = 0; i < sc->pc; i++) { struct solver_placement *p = &sc->placements[i]; p->dpi = p->domino->nplacements; p->domino->placements[p->domino->nplacements++] = p; p->active = true; } for (i = 0; i < sc->wh; i++) sc->squares[i].nplacements = 0; for (i = 0; i < sc->pc; i++) { struct solver_placement *p = &sc->placements[i]; for (j = 0; j < 2; j++) { struct solver_square *sq = p->squares[j]; p->spi[j] = sq->nplacements; sq->placements[sq->nplacements++] = p; } } sc->max_diff_used = DIFF_TRIVIAL; sc->squares_by_number_initialised = false; } /* Given two placements p,q that overlap, returns si such that * p->squares[si] is the square also in q */ static int common_square_index(struct solver_placement *p, struct solver_placement *q) { return (p->squares[0] == q->squares[0] || p->squares[0] == q->squares[1]) ? 0 : 1; } /* Sort function used to set up squares_by_number */ static int squares_by_number_cmpfn(const void *av, const void *bv) { struct solver_square *a = *(struct solver_square *const *)av; struct solver_square *b = *(struct solver_square *const *)bv; return (a->number < b->number ? -1 : a->number > b->number ? +1 : a->index < b->index ? -1 : a->index > b->index ? +1 : 0); } static void rule_out_placement( struct solver_scratch *sc, struct solver_placement *p) { struct solver_domino *d = p->domino; int i, j, si; #ifdef SOLVER_DIAGNOSTICS if (solver_diagnostics) printf(" ruling out placement %s for domino %s\n", p->name, d->name); #endif p->active = false; i = p->dpi; assert(d->placements[i] == p); if (--d->nplacements != i) { d->placements[i] = d->placements[d->nplacements]; d->placements[i]->dpi = i; } for (si = 0; si < 2; si++) { struct solver_square *sq = p->squares[si]; i = p->spi[si]; assert(sq->placements[i] == p); if (--sq->nplacements != i) { sq->placements[i] = sq->placements[sq->nplacements]; j = (sq->placements[i]->squares[0] == sq ? 0 : 1); sq->placements[i]->spi[j] = i; } } } /* * 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 we can find a set S of mutually non-adjacent squares all * containing the same number, such that the set of possible dominoes * for all those squares put together has the same size as S, then all * the dominoes in that set _must_ overlap a square of S and we can * rule out any other placements for them. */ static bool deduce_set_simple(struct solver_scratch *sc) { struct solver_square **sqs, **sqp, **sqe; int num, nsq, i, j; unsigned long domino_sets[16], adjacent[16]; struct solver_domino *ds[16]; bool done_something = false; if (!sc->squares_by_number) sc->squares_by_number = snewn(sc->wh, struct solver_square *); if (!sc->wh_scratch) sc->wh_scratch = snewn(sc->wh, int); if (!sc->squares_by_number_initialised) { /* * If this is the first call to this function for a given * grid, start by sorting the squares by their containing * number. */ for (i = 0; i < sc->wh; i++) sc->squares_by_number[i] = &sc->squares[i]; qsort(sc->squares_by_number, sc->wh, sizeof(*sc->squares_by_number), squares_by_number_cmpfn); } sqp = sc->squares_by_number; sqe = sc->squares_by_number + sc->wh; for (num = 0; num <= sc->n; num++) { unsigned long squares; unsigned long squares_done; /* Find the bounds of the subinterval of squares_by_number * containing squares with this particular number. */ sqs = sqp; while (sqp < sqe && (*sqp)->number == num) sqp++; nsq = sqp - sqs; /* * Now sqs[0], ..., sqs[nsq-1] are the squares containing 'num'. */ if (nsq > 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; /* * 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? * * In the normal case, analogous to set analysis in many * other puzzles, you want N squares which between them * have to account for N distinct dominoes, with a 1-1 * correspondence between them. * * But if any two squares in this set can possibly both be * covered by the same double domino (i.e. if they are * adjacent, and moreover, the placement containing both * is not yet ruled out), then that argument doesn't hold * up, because the N squares might be covered by N-1 * dominoes - or, put another way, if you list the * containing domino for each of the squares they aren't * all distinct. * * In that situation, we can only do the set analysis if * there is one _more_ square than there are dominoes. For * example, suppose we had four squares which between them * could contain only the 0-0, 0-1 and 0-2 dominoes. Then * that can only work at all if the 0-0 covers two of * those squares - and in that situation that _must_ be * what's happened, so we can rule out those three * dominoes anywhere else they might look possible. */ if (ndominoes != (got_adj_squares ? nsquares - 1 : nsquares)) continue; /* Skip sets of size 1, or whose complement has size 1. * Those can be handled by a simpler analysis, and should * be, for more sensible solver diagnostics. */ if (nsquares <= 1 || nsquares >= nsq-1) continue; /* * We've found a set! That means we can rule out any * placement of any of the dominoes in that set which do * not include one of our squares. * * We may or may not actually end up ruling anything out * here. But even if we don't, we should record that these * squares form a self-contained set, so that we don't * pointlessly report a superset of them later which could * instead be reported as just the other ones. */ squares_done |= squares; for (bitpos = 0; bitpos < nsq; bitpos++) { struct solver_domino *d; if (!(1 & (dominoes >> bitpos))) continue; d = ds[bitpos]; for (i = d->nplacements; i-- > 0 ;) { struct solver_placement *p = d->placements[i]; int si; for (si = 0; si < 2; si++) { struct solver_square *sq2 = p->squares[si]; if (sq2->number == num && (1 & (squares >> sc->wh_scratch[sq2->index]))) { /* This placement uses one of our squares. * Leave it in. */ goto skip_placement; } } if (!reported) { reported = true; done_something = true; #ifdef SOLVER_DIAGNOSTICS if (solver_diagnostics) { int b; const char *sep; printf("squares {"); for (sep = "", b = 0; b < nsq; b++) if (1 & (squares >> b)) { printf("%s%s", sep, sqs[b]->name); sep = ","; } printf("} have to contain dominoes {"); for (sep = "", b = 0; b < nsq; b++) if (1 & (dominoes >> b)) { printf("%s%s", sep, ds[b]->name); sep = ","; } printf("}"); if (got_adj_squares) printf(" (including both ends of the " "double)"); printf("\n"); } #endif } rule_out_placement(sc, p); skip_placement:; } } } } return done_something; } static int forcing_chain_dup_cmp(const void *av, const void *bv, void *ctx) { struct solver_scratch *sc = (struct solver_scratch *)ctx; int a = *(const int *)av, b = *(const int *)bv; int ac, bc; ac = sc->pc_scratch[a]; bc = sc->pc_scratch[b]; if (ac != bc) return ac > bc ? +1 : -1; ac = sc->placements[a].domino->index; bc = sc->placements[b].domino->index; if (ac != bc) return ac > bc ? +1 : -1; return 0; } static int forcing_chain_sq_cmp(const void *av, const void *bv, void *ctx) { struct solver_scratch *sc = (struct solver_scratch *)ctx; int a = *(const int *)av, b = *(const int *)bv; int ac, bc; ac = sc->placements[a].domino->index; bc = sc->placements[b].domino->index; if (ac != bc) return ac > bc ? +1 : -1; ac = sc->pc_scratch[a]; bc = sc->pc_scratch[b]; if (ac != bc) return ac > bc ? +1 : -1; return 0; } static bool deduce_forcing_chain(struct solver_scratch *sc) { int si, pi, di, j, k, m; bool done_something = false; if (!sc->wh_scratch) sc->wh_scratch = snewn(sc->wh, int); if (!sc->pc_scratch) sc->pc_scratch = snewn(sc->pc, int); if (!sc->pc_scratch2) sc->pc_scratch2 = snewn(sc->pc, int); if (!sc->dc_scratch) sc->dc_scratch = snewn(sc->dc, int); /* * Start by identifying chains of placements which must all occur * together if any of them occurs. We do this by making * pc_scratch2 an edsf binding the placements into an equivalence * class for each entire forcing chain, with the two possible sets * of dominoes for the chain listed as inverses. */ dsf_init(sc->pc_scratch2, sc->pc); for (si = 0; si < sc->wh; si++) { struct solver_square *sq = &sc->squares[si]; if (sq->nplacements == 2) edsf_merge(sc->pc_scratch2, sq->placements[0]->index, sq->placements[1]->index, true); } /* * Now read out the whole dsf into pc_scratch, flattening its * structured data into a simple integer id per chain of dominoes * that must occur together. */ for (pi = 0; pi < sc->pc; pi++) { bool inv; int c = edsf_canonify(sc->pc_scratch2, pi, &inv); sc->pc_scratch[pi] = c * 2 + (inv ? 1 : 0); } /* * Identify chains that contain a duplicate domino, and rule them * out. We do this by making a list of the placement indices in * pc_scratch2, sorted by (chain id, domino id), so that dupes * become adjacent. */ for (pi = 0; pi < sc->pc; pi++) sc->pc_scratch2[pi] = pi; arraysort(sc->pc_scratch2, sc->pc, forcing_chain_dup_cmp, sc); for (j = 0; j < sc->pc ;) { struct solver_domino *duplicated_domino = NULL; /* * This loop iterates once per contiguous segment of the same * value in pc_scratch2, i.e. once per chain. */ int ci = sc->pc_scratch[sc->pc_scratch2[j]]; int climit, cstart = j; while (j < sc->pc && sc->pc_scratch[sc->pc_scratch2[j]] == ci) j++; climit = j; /* * Now look for a duplicate domino within that chain. */ for (k = cstart; k + 1 < climit; k++) { struct solver_placement *p = &sc->placements[sc->pc_scratch2[k]]; struct solver_placement *q = &sc->placements[sc->pc_scratch2[k+1]]; if (p->domino == q->domino) { duplicated_domino = p->domino; break; } } if (!duplicated_domino) continue; #ifdef SOLVER_DIAGNOSTICS 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; } } 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; } if (max_diff_allowed <= DIFF_BASIC) continue; if (deduce_set_simple(sc)) done_something = true; if (done_something) { sc->max_diff_used = max(sc->max_diff_used, DIFF_HARD); continue; } if (max_diff_allowed <= DIFF_HARD) continue; if (deduce_forcing_chain(sc)) done_something = true; if (done_something) { sc->max_diff_used = max(sc->max_diff_used, DIFF_EXTREME); continue; } } while (done_something); #ifdef SOLVER_DIAGNOSTICS if (solver_diagnostics) { int di, j; printf("Final possible placements:\n"); for (di = 0; di < sc->dc; di++) { struct solver_domino *d = &sc->dominoes[di]; printf(" %s:", d->name); for (j = 0; j < d->nplacements; j++) printf(" %s", d->placements[j]->name); printf("\n"); } } #endif for (di = 0; di < sc->dc; di++) if (sc->dominoes[di].nplacements == 0) return 0; for (di = 0; di < sc->dc; di++) if (sc->dominoes[di].nplacements > 1) return 2; return 1; } /* ---------------------------------------------------------------------- * End of solver code. */ 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; int *grid, *grid2, *list; struct solver_scratch *sc; int i, j, k, len; char *ret; #ifndef OMIT_DIFFICULTY_CAP /* * Cap the difficulty level for small puzzles which would * otherwise become impossible to generate. * * Under an #ifndef, to make it easy to remove this cap for the * purpose of re-testing what it ought to be. */ if (diff != DIFF_AMBIGUOUS) { if (n == 1 && diff > DIFF_TRIVIAL) diff = DIFF_TRIVIAL; if (n == 2 && diff > DIFF_BASIC) diff = DIFF_BASIC; } #endif /* OMIT_DIFFICULTY_CAP */ /* * Allocate space in which to lay the grid out. */ grid = snewn(wh, int); grid2 = snewn(wh, int); list = snewn(2*wh, int); sc = solver_make_scratch(n); /* * 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. */ do { domino_layout_prealloc(w, h, rs, grid, grid2, list); /* * Now we have a complete layout covering the whole * rectangle with dominoes. So shuffle the actual domino * values and fill the rectangle with numbers. */ k = 0; for (i = 0; i <= params->n; i++) for (j = 0; j <= i; j++) { list[k++] = i; list[k++] = j; } shuffle(list, k/2, 2*sizeof(*list), rs); j = 0; for (i = 0; i < wh; i++) if (grid[i] > i) { /* Optionally flip the domino round. */ int flip = -1; if (diff != DIFF_AMBIGUOUS) { int t1, t2; /* * If we're after a unique solution, we can do * something here to improve the chances. If * we're placing a domino so that it forms a * 2x2 rectangle with one we've already placed, * and if that domino and this one share a * number, we can try not to put them so that * the identical numbers are diagonally * separated, because that automatically causes * non-uniqueness: * * +---+ +-+-+ * |2 3| |2|3| * +---+ -> | | | * |4 2| |4|2| * +---+ +-+-+ */ t1 = i; t2 = grid[i]; if (t2 == t1 + w) { /* this domino is vertical */ if (t1 % w > 0 &&/* and not on the left hand edge */ grid[t1-1] == t2-1 &&/* alongside one to left */ (grid2[t1-1] == list[j] || /* and has a number */ grid2[t1-1] == list[j+1] || /* in common */ grid2[t2-1] == list[j] || grid2[t2-1] == list[j+1])) { if (grid2[t1-1] == list[j] || grid2[t2-1] == list[j+1]) flip = 0; else flip = 1; } } else { /* this domino is horizontal */ if (t1 / w > 0 &&/* and not on the top edge */ grid[t1-w] == t2-w &&/* alongside one above */ (grid2[t1-w] == list[j] || /* and has a number */ grid2[t1-w] == list[j+1] || /* in common */ grid2[t2-w] == list[j] || grid2[t2-w] == list[j+1])) { if (grid2[t1-w] == list[j] || grid2[t2-w] == list[j+1]) flip = 0; else flip = 1; } } } if (flip < 0) flip = random_upto(rs, 2); grid2[i] = list[j + flip]; grid2[grid[i]] = list[j + 1 - flip]; j += 2; } assert(j == k); solver_setup_grid(sc, grid2); } while (diff != DIFF_AMBIGUOUS && (run_solver(sc, diff) > 1 || sc->max_diff_used < diff)); solver_free_scratch(sc); #ifdef GENERATION_DIAGNOSTICS for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { putchar('0' + grid2[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 = grid2[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 = grid[i]; auxinfo[i] = (v == i+1 ? 'L' : v == i-1 ? 'R' : v == i+w ? 'T' : v == i-w ? 'B' : '.'); } auxinfo[wh] = '\0'; *aux = auxinfo; } sfree(list); sfree(grid2); sfree(grid); 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 = false; ui->highlight_1 = ui->highlight_2 = -1; return ui; } static void free_ui(game_ui *ui) { sfree(ui); } static char *encode_ui(const game_ui *ui) { return NULL; } static void decode_ui(game_ui *ui, const char *encoding) { } 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; } #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 { bool started; 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 NULL; /* * 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 NULL; /* * We can't mark an edge next to any domino. */ if (button == RIGHT_BUTTON && (state->grid[d1] != d1 || state->grid[d2] != d2)) return NULL; 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)) { ui->cur_visible = true; move_cursor(button, &ui->cur_x, &ui->cur_y, 2*w-1, 2*h-1, false); return UI_UPDATE; } else if (IS_CURSOR_SELECT(button)) { int d1, d2; if (!((ui->cur_x ^ ui->cur_y) & 1)) return NULL; /* 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 NULL; 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 NULL; } 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 NULL; } return UI_UPDATE; } return NULL; } 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) { /* * 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) { /* * 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, 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->started = false; 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; if (!ds->started) { int pw, ph; game_compute_size(&state->params, TILESIZE, &pw, &ph); draw_rect(dr, 0, 0, pw, ph, COL_BACKGROUND); draw_update(dr, 0, 0, pw, ph); ds->started = true; } /* * 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 int game_status(const game_state *state) { return state->completed ? +1 : 0; } static bool game_timing_state(const game_state *state, game_ui *ui) { return true; } static void game_print_size(const game_params *params, float *x, float *y) { int pw, ph; /* * I'll use 6mm squares by default. */ game_compute_size(params, 600, &pw, &ph); *x = pw / 100.0F; *y = ph / 100.0F; } static void game_print(drawing *dr, const game_state *state, 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, new_ui, free_ui, encode_ui, decode_ui, NULL, /* game_request_keys */ game_changed_state, 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_status, true, false, game_print_size, game_print, false, /* wants_statusbar */ false, game_timing_state, 0, /* flags */ }; #ifdef STANDALONE_SOLVER int main(int argc, char **argv) { game_params *p; game_state *s, *s2; char *id = NULL, *desc; 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 (*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, DIFFCOUNT); 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: */