Files
puzzles/solo.c
Simon Tatham 0564c4c4d0 Mines now follows the conventional approach of offering a completely
blank grid until you make the first click; to ensure solubility, it
does not generate the mine layout until that click, and then ensures
it is solvable starting from that position.

This has involved three infrastructure changes:

 - random.c now offers functions to encode and decode an entire
   random_state as a string
 - each puzzle's new_game() is now passed a pointer to the midend
   itself, which most of them ignore
 - there's a function in the midend which a game can call back to
   _rewrite_ its current game description.

So Mines now has two entirely separate forms of game ID. One
contains the generation-time parameters (n and unique) plus an
encoding of a random_state; the other actually encodes the grid once
it's been generated, and also contains the initial click position.
When called with the latter, new_game() does plausibly normal stuff.
When called with the former, it notes down all the details and waits
until the first square is opened, and _then_ does the grid
generation and updates the game description in the midend. So if,
_after_ your first click, you decide you want to share this
particular puzzle with someone else, you can do that fine.

Also in this checkin, the mine layout is no longer _copied_ between
all the game_states on the undo chain. Instead, it's in a separate
structure and all game_states share a pointer to it - and the
structure is reference-counted to ensure deallocation.

[originally from svn r5862]
2005-05-30 13:10:37 +00:00

2305 lines
65 KiB
C

/*
* solo.c: the number-placing puzzle most popularly known as `Sudoku'.
*
* TODO:
*
* - reports from users are that `Trivial'-mode puzzles are still
* rather hard compared to newspapers' easy ones, so some better
* low-end difficulty grading would be nice
* + it's possible that really easy puzzles always have
* _several_ things you can do, so don't make you hunt too
* hard for the one deduction you can currently make
* + it's also possible that easy puzzles require fewer
* cross-eliminations: perhaps there's a higher incidence of
* things you can deduce by looking only at (say) rows,
* rather than things you have to check both rows and columns
* for
* + but really, what I need to do is find some really easy
* puzzles and _play_ them, to see what's actually easy about
* them
* + while I'm revamping this area, filling in the _last_
* number in a nearly-full row or column should certainly be
* permitted even at the lowest difficulty level.
* + also Owen noticed that `Basic' grids requiring numeric
* elimination are actually very hard, so I wonder if a
* difficulty gradation between that and positional-
* elimination-only might be in order
* + but it's not good to have _too_ many difficulty levels, or
* it'll take too long to randomly generate a given level.
*
* - it might still be nice to do some prioritisation on the
* removal of numbers from the grid
* + one possibility is to try to minimise the maximum number
* of filled squares in any block, which in particular ought
* to enforce never leaving a completely filled block in the
* puzzle as presented.
*
* - alternative interface modes
* + sudoku.com's Windows program has a palette of possible
* entries; you select a palette entry first and then click
* on the square you want it to go in, thus enabling
* mouse-only play. Useful for PDAs! I don't think it's
* actually incompatible with the current highlight-then-type
* approach: you _either_ highlight a palette entry and then
* click, _or_ you highlight a square and then type. At most
* one thing is ever highlighted at a time, so there's no way
* to confuse the two.
* + then again, I don't actually like sudoku.com's interface;
* it's too much like a paint package whereas I prefer to
* think of Solo as a text editor.
* + another PDA-friendly possibility is a drag interface:
* _drag_ numbers from the palette into the grid squares.
* Thought experiments suggest I'd prefer that to the
* sudoku.com approach, but I haven't actually tried it.
*/
/*
* Solo puzzles need to be square overall (since each row and each
* column must contain one of every digit), but they need not be
* subdivided the same way internally. I am going to adopt a
* convention whereby I _always_ refer to `r' as the number of rows
* of _big_ divisions, and `c' as the number of columns of _big_
* divisions. Thus, a 2c by 3r puzzle looks something like this:
*
* 4 5 1 | 2 6 3
* 6 3 2 | 5 4 1
* ------+------ (Of course, you can't subdivide it the other way
* 1 4 5 | 6 3 2 or you'll get clashes; observe that the 4 in the
* 3 2 6 | 4 1 5 top left would conflict with the 4 in the second
* ------+------ box down on the left-hand side.)
* 5 1 4 | 3 2 6
* 2 6 3 | 1 5 4
*
* The need for a strong naming convention should now be clear:
* each small box is two rows of digits by three columns, while the
* overall puzzle has three rows of small boxes by two columns. So
* I will (hopefully) consistently use `r' to denote the number of
* rows _of small boxes_ (here 3), which is also the number of
* columns of digits in each small box; and `c' vice versa (here
* 2).
*
* I'm also going to choose arbitrarily to list c first wherever
* possible: the above is a 2x3 puzzle, not a 3x2 one.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <math.h>
#ifdef STANDALONE_SOLVER
#include <stdarg.h>
int solver_show_working;
#endif
#include "puzzles.h"
#define max(x,y) ((x)>(y)?(x):(y))
/*
* To save space, I store digits internally as unsigned char. This
* imposes a hard limit of 255 on the order of the puzzle. Since
* even a 5x5 takes unacceptably long to generate, I don't see this
* as a serious limitation unless something _really_ impressive
* happens in computing technology; but here's a typedef anyway for
* general good practice.
*/
typedef unsigned char digit;
#define ORDER_MAX 255
#define TILE_SIZE 32
#define BORDER 18
#define FLASH_TIME 0.4F
enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF4 };
enum { DIFF_BLOCK, DIFF_SIMPLE, DIFF_INTERSECT,
DIFF_SET, DIFF_RECURSIVE, DIFF_AMBIGUOUS, DIFF_IMPOSSIBLE };
enum {
COL_BACKGROUND,
COL_GRID,
COL_CLUE,
COL_USER,
COL_HIGHLIGHT,
COL_PENCIL,
NCOLOURS
};
struct game_params {
int c, r, symm, diff;
};
struct game_state {
int c, r;
digit *grid;
unsigned char *pencil; /* c*r*c*r elements */
unsigned char *immutable; /* marks which digits are clues */
int completed, cheated;
};
static game_params *default_params(void)
{
game_params *ret = snew(game_params);
ret->c = ret->r = 3;
ret->symm = SYMM_ROT2; /* a plausible default */
ret->diff = DIFF_BLOCK; /* so is this */
return ret;
}
static void free_params(game_params *params)
{
sfree(params);
}
static game_params *dup_params(game_params *params)
{
game_params *ret = snew(game_params);
*ret = *params; /* structure copy */
return ret;
}
static int game_fetch_preset(int i, char **name, game_params **params)
{
static struct {
char *title;
game_params params;
} presets[] = {
{ "2x2 Trivial", { 2, 2, SYMM_ROT2, DIFF_BLOCK } },
{ "2x3 Basic", { 2, 3, SYMM_ROT2, DIFF_SIMPLE } },
{ "3x3 Trivial", { 3, 3, SYMM_ROT2, DIFF_BLOCK } },
{ "3x3 Basic", { 3, 3, SYMM_ROT2, DIFF_SIMPLE } },
{ "3x3 Intermediate", { 3, 3, SYMM_ROT2, DIFF_INTERSECT } },
{ "3x3 Advanced", { 3, 3, SYMM_ROT2, DIFF_SET } },
{ "3x3 Unreasonable", { 3, 3, SYMM_ROT2, DIFF_RECURSIVE } },
{ "3x4 Basic", { 3, 4, SYMM_ROT2, DIFF_SIMPLE } },
{ "4x4 Basic", { 4, 4, SYMM_ROT2, DIFF_SIMPLE } },
};
if (i < 0 || i >= lenof(presets))
return FALSE;
*name = dupstr(presets[i].title);
*params = dup_params(&presets[i].params);
return TRUE;
}
static void decode_params(game_params *ret, char const *string)
{
ret->c = ret->r = atoi(string);
while (*string && isdigit((unsigned char)*string)) string++;
if (*string == 'x') {
string++;
ret->r = atoi(string);
while (*string && isdigit((unsigned char)*string)) string++;
}
while (*string) {
if (*string == 'r' || *string == 'm' || *string == 'a') {
int sn, sc;
sc = *string++;
sn = atoi(string);
while (*string && isdigit((unsigned char)*string)) string++;
if (sc == 'm' && sn == 4)
ret->symm = SYMM_REF4;
if (sc == 'r' && sn == 4)
ret->symm = SYMM_ROT4;
if (sc == 'r' && sn == 2)
ret->symm = SYMM_ROT2;
if (sc == 'a')
ret->symm = SYMM_NONE;
} else if (*string == 'd') {
string++;
if (*string == 't') /* trivial */
string++, ret->diff = DIFF_BLOCK;
else if (*string == 'b') /* basic */
string++, ret->diff = DIFF_SIMPLE;
else if (*string == 'i') /* intermediate */
string++, ret->diff = DIFF_INTERSECT;
else if (*string == 'a') /* advanced */
string++, ret->diff = DIFF_SET;
else if (*string == 'u') /* unreasonable */
string++, ret->diff = DIFF_RECURSIVE;
} else
string++; /* eat unknown character */
}
}
static char *encode_params(game_params *params, int full)
{
char str[80];
sprintf(str, "%dx%d", params->c, params->r);
if (full) {
switch (params->symm) {
case SYMM_REF4: strcat(str, "m4"); break;
case SYMM_ROT4: strcat(str, "r4"); break;
/* case SYMM_ROT2: strcat(str, "r2"); break; [default] */
case SYMM_NONE: strcat(str, "a"); break;
}
switch (params->diff) {
/* case DIFF_BLOCK: strcat(str, "dt"); break; [default] */
case DIFF_SIMPLE: strcat(str, "db"); break;
case DIFF_INTERSECT: strcat(str, "di"); break;
case DIFF_SET: strcat(str, "da"); break;
case DIFF_RECURSIVE: strcat(str, "du"); break;
}
}
return dupstr(str);
}
static config_item *game_configure(game_params *params)
{
config_item *ret;
char buf[80];
ret = snewn(5, config_item);
ret[0].name = "Columns of sub-blocks";
ret[0].type = C_STRING;
sprintf(buf, "%d", params->c);
ret[0].sval = dupstr(buf);
ret[0].ival = 0;
ret[1].name = "Rows of sub-blocks";
ret[1].type = C_STRING;
sprintf(buf, "%d", params->r);
ret[1].sval = dupstr(buf);
ret[1].ival = 0;
ret[2].name = "Symmetry";
ret[2].type = C_CHOICES;
ret[2].sval = ":None:2-way rotation:4-way rotation:4-way mirror";
ret[2].ival = params->symm;
ret[3].name = "Difficulty";
ret[3].type = C_CHOICES;
ret[3].sval = ":Trivial:Basic:Intermediate:Advanced:Unreasonable";
ret[3].ival = params->diff;
ret[4].name = NULL;
ret[4].type = C_END;
ret[4].sval = NULL;
ret[4].ival = 0;
return ret;
}
static game_params *custom_params(config_item *cfg)
{
game_params *ret = snew(game_params);
ret->c = atoi(cfg[0].sval);
ret->r = atoi(cfg[1].sval);
ret->symm = cfg[2].ival;
ret->diff = cfg[3].ival;
return ret;
}
static char *validate_params(game_params *params)
{
if (params->c < 2 || params->r < 2)
return "Both dimensions must be at least 2";
if (params->c > ORDER_MAX || params->r > ORDER_MAX)
return "Dimensions greater than "STR(ORDER_MAX)" are not supported";
return NULL;
}
/* ----------------------------------------------------------------------
* Full recursive Solo solver.
*
* The algorithm for this solver is shamelessly copied from a
* Python solver written by Andrew Wilkinson (which is GPLed, but
* I've reused only ideas and no code). It mostly just does the
* obvious recursive thing: pick an empty square, put one of the
* possible digits in it, recurse until all squares are filled,
* backtrack and change some choices if necessary.
*
* The clever bit is that every time it chooses which square to
* fill in next, it does so by counting the number of _possible_
* numbers that can go in each square, and it prioritises so that
* it picks a square with the _lowest_ number of possibilities. The
* idea is that filling in lots of the obvious bits (particularly
* any squares with only one possibility) will cut down on the list
* of possibilities for other squares and hence reduce the enormous
* search space as much as possible as early as possible.
*
* In practice the algorithm appeared to work very well; run on
* sample problems from the Times it completed in well under a
* second on my G5 even when written in Python, and given an empty
* grid (so that in principle it would enumerate _all_ solved
* grids!) it found the first valid solution just as quickly. So
* with a bit more randomisation I see no reason not to use this as
* my grid generator.
*/
/*
* Internal data structure used in solver to keep track of
* progress.
*/
struct rsolve_coord { int x, y, r; };
struct rsolve_usage {
int c, r, cr; /* cr == c*r */
/* grid is a copy of the input grid, modified as we go along */
digit *grid;
/* row[y*cr+n-1] TRUE if digit n has been placed in row y */
unsigned char *row;
/* col[x*cr+n-1] TRUE if digit n has been placed in row x */
unsigned char *col;
/* blk[(y*c+x)*cr+n-1] TRUE if digit n has been placed in block (x,y) */
unsigned char *blk;
/* This lists all the empty spaces remaining in the grid. */
struct rsolve_coord *spaces;
int nspaces;
/* If we need randomisation in the solve, this is our random state. */
random_state *rs;
/* Number of solutions so far found, and maximum number we care about. */
int solns, maxsolns;
};
/*
* The real recursive step in the solving function.
*/
static void rsolve_real(struct rsolve_usage *usage, digit *grid)
{
int c = usage->c, r = usage->r, cr = usage->cr;
int i, j, n, sx, sy, bestm, bestr;
int *digits;
/*
* Firstly, check for completion! If there are no spaces left
* in the grid, we have a solution.
*/
if (usage->nspaces == 0) {
if (!usage->solns) {
/*
* This is our first solution, so fill in the output grid.
*/
memcpy(grid, usage->grid, cr * cr);
}
usage->solns++;
return;
}
/*
* Otherwise, there must be at least one space. Find the most
* constrained space, using the `r' field as a tie-breaker.
*/
bestm = cr+1; /* so that any space will beat it */
bestr = 0;
i = sx = sy = -1;
for (j = 0; j < usage->nspaces; j++) {
int x = usage->spaces[j].x, y = usage->spaces[j].y;
int m;
/*
* Find the number of digits that could go in this space.
*/
m = 0;
for (n = 0; n < cr; n++)
if (!usage->row[y*cr+n] && !usage->col[x*cr+n] &&
!usage->blk[((y/c)*c+(x/r))*cr+n])
m++;
if (m < bestm || (m == bestm && usage->spaces[j].r < bestr)) {
bestm = m;
bestr = usage->spaces[j].r;
sx = x;
sy = y;
i = j;
}
}
/*
* Swap that square into the final place in the spaces array,
* so that decrementing nspaces will remove it from the list.
*/
if (i != usage->nspaces-1) {
struct rsolve_coord t;
t = usage->spaces[usage->nspaces-1];
usage->spaces[usage->nspaces-1] = usage->spaces[i];
usage->spaces[i] = t;
}
/*
* Now we've decided which square to start our recursion at,
* simply go through all possible values, shuffling them
* randomly first if necessary.
*/
digits = snewn(bestm, int);
j = 0;
for (n = 0; n < cr; n++)
if (!usage->row[sy*cr+n] && !usage->col[sx*cr+n] &&
!usage->blk[((sy/c)*c+(sx/r))*cr+n]) {
digits[j++] = n+1;
}
if (usage->rs) {
/* shuffle */
for (i = j; i > 1; i--) {
int p = random_upto(usage->rs, i);
if (p != i-1) {
int t = digits[p];
digits[p] = digits[i-1];
digits[i-1] = t;
}
}
}
/* And finally, go through the digit list and actually recurse. */
for (i = 0; i < j; i++) {
n = digits[i];
/* Update the usage structure to reflect the placing of this digit. */
usage->row[sy*cr+n-1] = usage->col[sx*cr+n-1] =
usage->blk[((sy/c)*c+(sx/r))*cr+n-1] = TRUE;
usage->grid[sy*cr+sx] = n;
usage->nspaces--;
/* Call the solver recursively. */
rsolve_real(usage, grid);
/*
* If we have seen as many solutions as we need, terminate
* all processing immediately.
*/
if (usage->solns >= usage->maxsolns)
break;
/* Revert the usage structure. */
usage->row[sy*cr+n-1] = usage->col[sx*cr+n-1] =
usage->blk[((sy/c)*c+(sx/r))*cr+n-1] = FALSE;
usage->grid[sy*cr+sx] = 0;
usage->nspaces++;
}
sfree(digits);
}
/*
* Entry point to solver. You give it dimensions and a starting
* grid, which is simply an array of N^4 digits. In that array, 0
* means an empty square, and 1..N mean a clue square.
*
* Return value is the number of solutions found; searching will
* stop after the provided `max'. (Thus, you can pass max==1 to
* indicate that you only care about finding _one_ solution, or
* max==2 to indicate that you want to know the difference between
* a unique and non-unique solution.) The input parameter `grid' is
* also filled in with the _first_ (or only) solution found by the
* solver.
*/
static int rsolve(int c, int r, digit *grid, random_state *rs, int max)
{
struct rsolve_usage *usage;
int x, y, cr = c*r;
int ret;
/*
* Create an rsolve_usage structure.
*/
usage = snew(struct rsolve_usage);
usage->c = c;
usage->r = r;
usage->cr = cr;
usage->grid = snewn(cr * cr, digit);
memcpy(usage->grid, grid, cr * cr);
usage->row = snewn(cr * cr, unsigned char);
usage->col = snewn(cr * cr, unsigned char);
usage->blk = snewn(cr * cr, unsigned char);
memset(usage->row, FALSE, cr * cr);
memset(usage->col, FALSE, cr * cr);
memset(usage->blk, FALSE, cr * cr);
usage->spaces = snewn(cr * cr, struct rsolve_coord);
usage->nspaces = 0;
usage->solns = 0;
usage->maxsolns = max;
usage->rs = rs;
/*
* Now fill it in with data from the input grid.
*/
for (y = 0; y < cr; y++) {
for (x = 0; x < cr; x++) {
int v = grid[y*cr+x];
if (v == 0) {
usage->spaces[usage->nspaces].x = x;
usage->spaces[usage->nspaces].y = y;
if (rs)
usage->spaces[usage->nspaces].r = random_bits(rs, 31);
else
usage->spaces[usage->nspaces].r = usage->nspaces;
usage->nspaces++;
} else {
usage->row[y*cr+v-1] = TRUE;
usage->col[x*cr+v-1] = TRUE;
usage->blk[((y/c)*c+(x/r))*cr+v-1] = TRUE;
}
}
}
/*
* Run the real recursive solving function.
*/
rsolve_real(usage, grid);
ret = usage->solns;
/*
* Clean up the usage structure now we have our answer.
*/
sfree(usage->spaces);
sfree(usage->blk);
sfree(usage->col);
sfree(usage->row);
sfree(usage->grid);
sfree(usage);
/*
* And return.
*/
return ret;
}
/* ----------------------------------------------------------------------
* End of recursive solver code.
*/
/* ----------------------------------------------------------------------
* Less capable non-recursive solver. This one is used to check
* solubility of a grid as we gradually remove numbers from it: by
* verifying a grid using this solver we can ensure it isn't _too_
* hard (e.g. does not actually require guessing and backtracking).
*
* It supports a variety of specific modes of reasoning. By
* enabling or disabling subsets of these modes we can arrange a
* range of difficulty levels.
*/
/*
* Modes of reasoning currently supported:
*
* - Positional elimination: a number must go in a particular
* square because all the other empty squares in a given
* row/col/blk are ruled out.
*
* - Numeric elimination: a square must have a particular number
* in because all the other numbers that could go in it are
* ruled out.
*
* - Intersectional analysis: given two domains which overlap
* (hence one must be a block, and the other can be a row or
* col), if the possible locations for a particular number in
* one of the domains can be narrowed down to the overlap, then
* that number can be ruled out everywhere but the overlap in
* the other domain too.
*
* - Set elimination: if there is a subset of the empty squares
* within a domain such that the union of the possible numbers
* in that subset has the same size as the subset itself, then
* those numbers can be ruled out everywhere else in the domain.
* (For example, if there are five empty squares and the
* possible numbers in each are 12, 23, 13, 134 and 1345, then
* the first three empty squares form such a subset: the numbers
* 1, 2 and 3 _must_ be in those three squares in some
* permutation, and hence we can deduce none of them can be in
* the fourth or fifth squares.)
* + You can also see this the other way round, concentrating
* on numbers rather than squares: if there is a subset of
* the unplaced numbers within a domain such that the union
* of all their possible positions has the same size as the
* subset itself, then all other numbers can be ruled out for
* those positions. However, it turns out that this is
* exactly equivalent to the first formulation at all times:
* there is a 1-1 correspondence between suitable subsets of
* the unplaced numbers and suitable subsets of the unfilled
* places, found by taking the _complement_ of the union of
* the numbers' possible positions (or the spaces' possible
* contents).
*/
/*
* Within this solver, I'm going to transform all y-coordinates by
* inverting the significance of the block number and the position
* within the block. That is, we will start with the top row of
* each block in order, then the second row of each block in order,
* etc.
*
* This transformation has the enormous advantage that it means
* every row, column _and_ block is described by an arithmetic
* progression of coordinates within the cubic array, so that I can
* use the same very simple function to do blockwise, row-wise and
* column-wise elimination.
*/
#define YTRANS(y) (((y)%c)*r+(y)/c)
#define YUNTRANS(y) (((y)%r)*c+(y)/r)
struct nsolve_usage {
int c, r, cr;
/*
* We set up a cubic array, indexed by x, y and digit; each
* element of this array is TRUE or FALSE according to whether
* or not that digit _could_ in principle go in that position.
*
* The way to index this array is cube[(x*cr+y)*cr+n-1].
* y-coordinates in here are transformed.
*/
unsigned char *cube;
/*
* This is the grid in which we write down our final
* deductions. y-coordinates in here are _not_ transformed.
*/
digit *grid;
/*
* Now we keep track, at a slightly higher level, of what we
* have yet to work out, to prevent doing the same deduction
* many times.
*/
/* row[y*cr+n-1] TRUE if digit n has been placed in row y */
unsigned char *row;
/* col[x*cr+n-1] TRUE if digit n has been placed in row x */
unsigned char *col;
/* blk[(y*c+x)*cr+n-1] TRUE if digit n has been placed in block (x,y) */
unsigned char *blk;
};
#define cubepos(x,y,n) (((x)*usage->cr+(y))*usage->cr+(n)-1)
#define cube(x,y,n) (usage->cube[cubepos(x,y,n)])
/*
* Function called when we are certain that a particular square has
* a particular number in it. The y-coordinate passed in here is
* transformed.
*/
static void nsolve_place(struct nsolve_usage *usage, int x, int y, int n)
{
int c = usage->c, r = usage->r, cr = usage->cr;
int i, j, bx, by;
assert(cube(x,y,n));
/*
* Rule out all other numbers in this square.
*/
for (i = 1; i <= cr; i++)
if (i != n)
cube(x,y,i) = FALSE;
/*
* Rule out this number in all other positions in the row.
*/
for (i = 0; i < cr; i++)
if (i != y)
cube(x,i,n) = FALSE;
/*
* Rule out this number in all other positions in the column.
*/
for (i = 0; i < cr; i++)
if (i != x)
cube(i,y,n) = FALSE;
/*
* Rule out this number in all other positions in the block.
*/
bx = (x/r)*r;
by = y % r;
for (i = 0; i < r; i++)
for (j = 0; j < c; j++)
if (bx+i != x || by+j*r != y)
cube(bx+i,by+j*r,n) = FALSE;
/*
* Enter the number in the result grid.
*/
usage->grid[YUNTRANS(y)*cr+x] = n;
/*
* Cross out this number from the list of numbers left to place
* in its row, its column and its block.
*/
usage->row[y*cr+n-1] = usage->col[x*cr+n-1] =
usage->blk[((y%r)*c+(x/r))*cr+n-1] = TRUE;
}
static int nsolve_elim(struct nsolve_usage *usage, int start, int step
#ifdef STANDALONE_SOLVER
, char *fmt, ...
#endif
)
{
int c = usage->c, r = usage->r, cr = c*r;
int fpos, m, i;
/*
* Count the number of set bits within this section of the
* cube.
*/
m = 0;
fpos = -1;
for (i = 0; i < cr; i++)
if (usage->cube[start+i*step]) {
fpos = start+i*step;
m++;
}
if (m == 1) {
int x, y, n;
assert(fpos >= 0);
n = 1 + fpos % cr;
y = fpos / cr;
x = y / cr;
y %= cr;
if (!usage->grid[YUNTRANS(y)*cr+x]) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf(":\n placing %d at (%d,%d)\n",
n, 1+x, 1+YUNTRANS(y));
}
#endif
nsolve_place(usage, x, y, n);
return TRUE;
}
}
return FALSE;
}
static int nsolve_intersect(struct nsolve_usage *usage,
int start1, int step1, int start2, int step2
#ifdef STANDALONE_SOLVER
, char *fmt, ...
#endif
)
{
int c = usage->c, r = usage->r, cr = c*r;
int ret, i;
/*
* Loop over the first domain and see if there's any set bit
* not also in the second.
*/
for (i = 0; i < cr; i++) {
int p = start1+i*step1;
if (usage->cube[p] &&
!(p >= start2 && p < start2+cr*step2 &&
(p - start2) % step2 == 0))
return FALSE; /* there is, so we can't deduce */
}
/*
* We have determined that all set bits in the first domain are
* within its overlap with the second. So loop over the second
* domain and remove all set bits that aren't also in that
* overlap; return TRUE iff we actually _did_ anything.
*/
ret = FALSE;
for (i = 0; i < cr; i++) {
int p = start2+i*step2;
if (usage->cube[p] &&
!(p >= start1 && p < start1+cr*step1 && (p - start1) % step1 == 0))
{
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
int px, py, pn;
if (!ret) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf(":\n");
}
pn = 1 + p % cr;
py = p / cr;
px = py / cr;
py %= cr;
printf(" ruling out %d at (%d,%d)\n",
pn, 1+px, 1+YUNTRANS(py));
}
#endif
ret = TRUE; /* we did something */
usage->cube[p] = 0;
}
}
return ret;
}
static int nsolve_set(struct nsolve_usage *usage,
int start, int step1, int step2
#ifdef STANDALONE_SOLVER
, char *fmt, ...
#endif
)
{
int c = usage->c, r = usage->r, cr = c*r;
int i, j, n, count;
unsigned char *grid = snewn(cr*cr, unsigned char);
unsigned char *rowidx = snewn(cr, unsigned char);
unsigned char *colidx = snewn(cr, unsigned char);
unsigned char *set = snewn(cr, unsigned char);
/*
* We are passed a cr-by-cr matrix of booleans. Our first job
* is to winnow it by finding any definite placements - i.e.
* any row with a solitary 1 - and discarding that row and the
* column containing the 1.
*/
memset(rowidx, TRUE, cr);
memset(colidx, TRUE, cr);
for (i = 0; i < cr; i++) {
int count = 0, first = -1;
for (j = 0; j < cr; j++)
if (usage->cube[start+i*step1+j*step2])
first = j, count++;
if (count == 0) {
/*
* This condition actually marks a completely insoluble
* (i.e. internally inconsistent) puzzle. We return and
* report no progress made.
*/
return FALSE;
}
if (count == 1)
rowidx[i] = colidx[first] = FALSE;
}
/*
* Convert each of rowidx/colidx from a list of 0s and 1s to a
* list of the indices of the 1s.
*/
for (i = j = 0; i < cr; i++)
if (rowidx[i])
rowidx[j++] = i;
n = j;
for (i = j = 0; i < cr; i++)
if (colidx[i])
colidx[j++] = i;
assert(n == j);
/*
* And create the smaller matrix.
*/
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
grid[i*cr+j] = usage->cube[start+rowidx[i]*step1+colidx[j]*step2];
/*
* Having done that, we now have a matrix in which every row
* has at least two 1s in. Now we search to see if we can find
* a rectangle of zeroes (in the set-theoretic sense of
* `rectangle', i.e. a subset of rows crossed with a subset of
* columns) whose width and height add up to n.
*/
memset(set, 0, n);
count = 0;
while (1) {
/*
* We have a candidate set. If its size is <=1 or >=n-1
* then we move on immediately.
*/
if (count > 1 && count < n-1) {
/*
* The number of rows we need is n-count. See if we can
* find that many rows which each have a zero in all
* the positions listed in `set'.
*/
int rows = 0;
for (i = 0; i < n; i++) {
int ok = TRUE;
for (j = 0; j < n; j++)
if (set[j] && grid[i*cr+j]) {
ok = FALSE;
break;
}
if (ok)
rows++;
}
/*
* We expect never to be able to get _more_ than
* n-count suitable rows: this would imply that (for
* example) there are four numbers which between them
* have at most three possible positions, and hence it
* indicates a faulty deduction before this point or
* even a bogus clue.
*/
assert(rows <= n - count);
if (rows >= n - count) {
int progress = FALSE;
/*
* We've got one! Now, for each row which _doesn't_
* satisfy the criterion, eliminate all its set
* bits in the positions _not_ listed in `set'.
* Return TRUE (meaning progress has been made) if
* we successfully eliminated anything at all.
*
* This involves referring back through
* rowidx/colidx in order to work out which actual
* positions in the cube to meddle with.
*/
for (i = 0; i < n; i++) {
int ok = TRUE;
for (j = 0; j < n; j++)
if (set[j] && grid[i*cr+j]) {
ok = FALSE;
break;
}
if (!ok) {
for (j = 0; j < n; j++)
if (!set[j] && grid[i*cr+j]) {
int fpos = (start+rowidx[i]*step1+
colidx[j]*step2);
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
int px, py, pn;
if (!progress) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf(":\n");
}
pn = 1 + fpos % cr;
py = fpos / cr;
px = py / cr;
py %= cr;
printf(" ruling out %d at (%d,%d)\n",
pn, 1+px, 1+YUNTRANS(py));
}
#endif
progress = TRUE;
usage->cube[fpos] = FALSE;
}
}
}
if (progress) {
sfree(set);
sfree(colidx);
sfree(rowidx);
sfree(grid);
return TRUE;
}
}
}
/*
* Binary increment: change the rightmost 0 to a 1, and
* change all 1s to the right of it to 0s.
*/
i = n;
while (i > 0 && set[i-1])
set[--i] = 0, count--;
if (i > 0)
set[--i] = 1, count++;
else
break; /* done */
}
sfree(set);
sfree(colidx);
sfree(rowidx);
sfree(grid);
return FALSE;
}
static int nsolve(int c, int r, digit *grid)
{
struct nsolve_usage *usage;
int cr = c*r;
int x, y, n;
int diff = DIFF_BLOCK;
/*
* Set up a usage structure as a clean slate (everything
* possible).
*/
usage = snew(struct nsolve_usage);
usage->c = c;
usage->r = r;
usage->cr = cr;
usage->cube = snewn(cr*cr*cr, unsigned char);
usage->grid = grid; /* write straight back to the input */
memset(usage->cube, TRUE, cr*cr*cr);
usage->row = snewn(cr * cr, unsigned char);
usage->col = snewn(cr * cr, unsigned char);
usage->blk = snewn(cr * cr, unsigned char);
memset(usage->row, FALSE, cr * cr);
memset(usage->col, FALSE, cr * cr);
memset(usage->blk, FALSE, cr * cr);
/*
* Place all the clue numbers we are given.
*/
for (x = 0; x < cr; x++)
for (y = 0; y < cr; y++)
if (grid[y*cr+x])
nsolve_place(usage, x, YTRANS(y), grid[y*cr+x]);
/*
* Now loop over the grid repeatedly trying all permitted modes
* of reasoning. The loop terminates if we complete an
* iteration without making any progress; we then return
* failure or success depending on whether the grid is full or
* not.
*/
while (1) {
/*
* I'd like to write `continue;' inside each of the
* following loops, so that the solver returns here after
* making some progress. However, I can't specify that I
* want to continue an outer loop rather than the innermost
* one, so I'm apologetically resorting to a goto.
*/
cont:
/*
* Blockwise positional elimination.
*/
for (x = 0; x < cr; x += r)
for (y = 0; y < r; y++)
for (n = 1; n <= cr; n++)
if (!usage->blk[(y*c+(x/r))*cr+n-1] &&
nsolve_elim(usage, cubepos(x,y,n), r*cr
#ifdef STANDALONE_SOLVER
, "positional elimination,"
" block (%d,%d)", 1+x/r, 1+y
#endif
)) {
diff = max(diff, DIFF_BLOCK);
goto cont;
}
/*
* Row-wise positional elimination.
*/
for (y = 0; y < cr; y++)
for (n = 1; n <= cr; n++)
if (!usage->row[y*cr+n-1] &&
nsolve_elim(usage, cubepos(0,y,n), cr*cr
#ifdef STANDALONE_SOLVER
, "positional elimination,"
" row %d", 1+YUNTRANS(y)
#endif
)) {
diff = max(diff, DIFF_SIMPLE);
goto cont;
}
/*
* Column-wise positional elimination.
*/
for (x = 0; x < cr; x++)
for (n = 1; n <= cr; n++)
if (!usage->col[x*cr+n-1] &&
nsolve_elim(usage, cubepos(x,0,n), cr
#ifdef STANDALONE_SOLVER
, "positional elimination," " column %d", 1+x
#endif
)) {
diff = max(diff, DIFF_SIMPLE);
goto cont;
}
/*
* Numeric elimination.
*/
for (x = 0; x < cr; x++)
for (y = 0; y < cr; y++)
if (!usage->grid[YUNTRANS(y)*cr+x] &&
nsolve_elim(usage, cubepos(x,y,1), 1
#ifdef STANDALONE_SOLVER
, "numeric elimination at (%d,%d)", 1+x,
1+YUNTRANS(y)
#endif
)) {
diff = max(diff, DIFF_SIMPLE);
goto cont;
}
/*
* Intersectional analysis, rows vs blocks.
*/
for (y = 0; y < cr; y++)
for (x = 0; x < cr; x += r)
for (n = 1; n <= cr; n++)
if (!usage->row[y*cr+n-1] &&
!usage->blk[((y%r)*c+(x/r))*cr+n-1] &&
(nsolve_intersect(usage, cubepos(0,y,n), cr*cr,
cubepos(x,y%r,n), r*cr
#ifdef STANDALONE_SOLVER
, "intersectional analysis,"
" row %d vs block (%d,%d)",
1+YUNTRANS(y), 1+x/r, 1+y%r
#endif
) ||
nsolve_intersect(usage, cubepos(x,y%r,n), r*cr,
cubepos(0,y,n), cr*cr
#ifdef STANDALONE_SOLVER
, "intersectional analysis,"
" block (%d,%d) vs row %d",
1+x/r, 1+y%r, 1+YUNTRANS(y)
#endif
))) {
diff = max(diff, DIFF_INTERSECT);
goto cont;
}
/*
* Intersectional analysis, columns vs blocks.
*/
for (x = 0; x < cr; x++)
for (y = 0; y < r; y++)
for (n = 1; n <= cr; n++)
if (!usage->col[x*cr+n-1] &&
!usage->blk[(y*c+(x/r))*cr+n-1] &&
(nsolve_intersect(usage, cubepos(x,0,n), cr,
cubepos((x/r)*r,y,n), r*cr
#ifdef STANDALONE_SOLVER
, "intersectional analysis,"
" column %d vs block (%d,%d)",
1+x, 1+x/r, 1+y
#endif
) ||
nsolve_intersect(usage, cubepos((x/r)*r,y,n), r*cr,
cubepos(x,0,n), cr
#ifdef STANDALONE_SOLVER
, "intersectional analysis,"
" block (%d,%d) vs column %d",
1+x/r, 1+y, 1+x
#endif
))) {
diff = max(diff, DIFF_INTERSECT);
goto cont;
}
/*
* Blockwise set elimination.
*/
for (x = 0; x < cr; x += r)
for (y = 0; y < r; y++)
if (nsolve_set(usage, cubepos(x,y,1), r*cr, 1
#ifdef STANDALONE_SOLVER
, "set elimination, block (%d,%d)", 1+x/r, 1+y
#endif
)) {
diff = max(diff, DIFF_SET);
goto cont;
}
/*
* Row-wise set elimination.
*/
for (y = 0; y < cr; y++)
if (nsolve_set(usage, cubepos(0,y,1), cr*cr, 1
#ifdef STANDALONE_SOLVER
, "set elimination, row %d", 1+YUNTRANS(y)
#endif
)) {
diff = max(diff, DIFF_SET);
goto cont;
}
/*
* Column-wise set elimination.
*/
for (x = 0; x < cr; x++)
if (nsolve_set(usage, cubepos(x,0,1), cr, 1
#ifdef STANDALONE_SOLVER
, "set elimination, column %d", 1+x
#endif
)) {
diff = max(diff, DIFF_SET);
goto cont;
}
/*
* If we reach here, we have made no deductions in this
* iteration, so the algorithm terminates.
*/
break;
}
sfree(usage->cube);
sfree(usage->row);
sfree(usage->col);
sfree(usage->blk);
sfree(usage);
for (x = 0; x < cr; x++)
for (y = 0; y < cr; y++)
if (!grid[y*cr+x])
return DIFF_IMPOSSIBLE;
return diff;
}
/* ----------------------------------------------------------------------
* End of non-recursive solver code.
*/
/*
* Check whether a grid contains a valid complete puzzle.
*/
static int check_valid(int c, int r, digit *grid)
{
int cr = c*r;
unsigned char *used;
int x, y, n;
used = snewn(cr, unsigned char);
/*
* Check that each row contains precisely one of everything.
*/
for (y = 0; y < cr; y++) {
memset(used, FALSE, cr);
for (x = 0; x < cr; x++)
if (grid[y*cr+x] > 0 && grid[y*cr+x] <= cr)
used[grid[y*cr+x]-1] = TRUE;
for (n = 0; n < cr; n++)
if (!used[n]) {
sfree(used);
return FALSE;
}
}
/*
* Check that each column contains precisely one of everything.
*/
for (x = 0; x < cr; x++) {
memset(used, FALSE, cr);
for (y = 0; y < cr; y++)
if (grid[y*cr+x] > 0 && grid[y*cr+x] <= cr)
used[grid[y*cr+x]-1] = TRUE;
for (n = 0; n < cr; n++)
if (!used[n]) {
sfree(used);
return FALSE;
}
}
/*
* Check that each block contains precisely one of everything.
*/
for (x = 0; x < cr; x += r) {
for (y = 0; y < cr; y += c) {
int xx, yy;
memset(used, FALSE, cr);
for (xx = x; xx < x+r; xx++)
for (yy = 0; yy < y+c; yy++)
if (grid[yy*cr+xx] > 0 && grid[yy*cr+xx] <= cr)
used[grid[yy*cr+xx]-1] = TRUE;
for (n = 0; n < cr; n++)
if (!used[n]) {
sfree(used);
return FALSE;
}
}
}
sfree(used);
return TRUE;
}
static void symmetry_limit(game_params *params, int *xlim, int *ylim, int s)
{
int c = params->c, r = params->r, cr = c*r;
switch (s) {
case SYMM_NONE:
*xlim = *ylim = cr;
break;
case SYMM_ROT2:
*xlim = (cr+1) / 2;
*ylim = cr;
break;
case SYMM_REF4:
case SYMM_ROT4:
*xlim = *ylim = (cr+1) / 2;
break;
}
}
static int symmetries(game_params *params, int x, int y, int *output, int s)
{
int c = params->c, r = params->r, cr = c*r;
int i = 0;
*output++ = x;
*output++ = y;
i++;
switch (s) {
case SYMM_NONE:
break; /* just x,y is all we need */
case SYMM_REF4:
case SYMM_ROT4:
switch (s) {
case SYMM_REF4:
*output++ = cr - 1 - x;
*output++ = y;
i++;
*output++ = x;
*output++ = cr - 1 - y;
i++;
break;
case SYMM_ROT4:
*output++ = cr - 1 - y;
*output++ = x;
i++;
*output++ = y;
*output++ = cr - 1 - x;
i++;
break;
}
/* fall through */
case SYMM_ROT2:
*output++ = cr - 1 - x;
*output++ = cr - 1 - y;
i++;
break;
}
return i;
}
struct game_aux_info {
int c, r;
digit *grid;
};
static char *new_game_desc(game_params *params, random_state *rs,
game_aux_info **aux)
{
int c = params->c, r = params->r, cr = c*r;
int area = cr*cr;
digit *grid, *grid2;
struct xy { int x, y; } *locs;
int nlocs;
int ret;
char *desc;
int coords[16], ncoords;
int xlim, ylim;
int maxdiff, recursing;
/*
* Adjust the maximum difficulty level to be consistent with
* the puzzle size: all 2x2 puzzles appear to be Trivial
* (DIFF_BLOCK) so we cannot hold out for even a Basic
* (DIFF_SIMPLE) one.
*/
maxdiff = params->diff;
if (c == 2 && r == 2)
maxdiff = DIFF_BLOCK;
grid = snewn(area, digit);
locs = snewn(area, struct xy);
grid2 = snewn(area, digit);
/*
* Loop until we get a grid of the required difficulty. This is
* nasty, but it seems to be unpleasantly hard to generate
* difficult grids otherwise.
*/
do {
/*
* Start the recursive solver with an empty grid to generate a
* random solved state.
*/
memset(grid, 0, area);
ret = rsolve(c, r, grid, rs, 1);
assert(ret == 1);
assert(check_valid(c, r, grid));
/*
* Save the solved grid in the aux_info.
*/
{
game_aux_info *ai = snew(game_aux_info);
ai->c = c;
ai->r = r;
ai->grid = snewn(cr * cr, digit);
memcpy(ai->grid, grid, cr * cr * sizeof(digit));
*aux = ai;
}
/*
* Now we have a solved grid, start removing things from it
* while preserving solubility.
*/
symmetry_limit(params, &xlim, &ylim, params->symm);
recursing = FALSE;
while (1) {
int x, y, i, j;
/*
* Iterate over the grid and enumerate all the filled
* squares we could empty.
*/
nlocs = 0;
for (x = 0; x < xlim; x++)
for (y = 0; y < ylim; y++)
if (grid[y*cr+x]) {
locs[nlocs].x = x;
locs[nlocs].y = y;
nlocs++;
}
/*
* Now shuffle that list.
*/
for (i = nlocs; i > 1; i--) {
int p = random_upto(rs, i);
if (p != i-1) {
struct xy t = locs[p];
locs[p] = locs[i-1];
locs[i-1] = t;
}
}
/*
* Now loop over the shuffled list and, for each element,
* see whether removing that element (and its reflections)
* from the grid will still leave the grid soluble by
* nsolve.
*/
for (i = 0; i < nlocs; i++) {
int ret;
x = locs[i].x;
y = locs[i].y;
memcpy(grid2, grid, area);
ncoords = symmetries(params, x, y, coords, params->symm);
for (j = 0; j < ncoords; j++)
grid2[coords[2*j+1]*cr+coords[2*j]] = 0;
if (recursing)
ret = (rsolve(c, r, grid2, NULL, 2) == 1);
else
ret = (nsolve(c, r, grid2) <= maxdiff);
if (ret) {
for (j = 0; j < ncoords; j++)
grid[coords[2*j+1]*cr+coords[2*j]] = 0;
break;
}
}
if (i == nlocs) {
/*
* There was nothing we could remove without
* destroying solvability. If we're trying to
* generate a recursion-only grid and haven't
* switched over to rsolve yet, we now do;
* otherwise we give up.
*/
if (maxdiff == DIFF_RECURSIVE && !recursing) {
recursing = TRUE;
} else {
break;
}
}
}
memcpy(grid2, grid, area);
} while (nsolve(c, r, grid2) < maxdiff);
sfree(grid2);
sfree(locs);
/*
* Now we have the grid as it will be presented to the user.
* Encode it in a game desc.
*/
{
char *p;
int run, i;
desc = snewn(5 * area, char);
p = desc;
run = 0;
for (i = 0; i <= area; i++) {
int n = (i < area ? grid[i] : -1);
if (!n)
run++;
else {
if (run) {
while (run > 0) {
int c = 'a' - 1 + run;
if (run > 26)
c = 'z';
*p++ = c;
run -= c - ('a' - 1);
}
} else {
/*
* If there's a number in the very top left or
* bottom right, there's no point putting an
* unnecessary _ before or after it.
*/
if (p > desc && n > 0)
*p++ = '_';
}
if (n > 0)
p += sprintf(p, "%d", n);
run = 0;
}
}
assert(p - desc < 5 * area);
*p++ = '\0';
desc = sresize(desc, p - desc, char);
}
sfree(grid);
return desc;
}
static void game_free_aux_info(game_aux_info *aux)
{
sfree(aux->grid);
sfree(aux);
}
static char *validate_desc(game_params *params, char *desc)
{
int area = params->r * params->r * params->c * params->c;
int squares = 0;
while (*desc) {
int n = *desc++;
if (n >= 'a' && n <= 'z') {
squares += n - 'a' + 1;
} else if (n == '_') {
/* do nothing */;
} else if (n > '0' && n <= '9') {
squares++;
while (*desc >= '0' && *desc <= '9')
desc++;
} else
return "Invalid character in game description";
}
if (squares < area)
return "Not enough data to fill grid";
if (squares > area)
return "Too much data to fit in grid";
return NULL;
}
static game_state *new_game(midend_data *me, game_params *params, char *desc)
{
game_state *state = snew(game_state);
int c = params->c, r = params->r, cr = c*r, area = cr * cr;
int i;
state->c = params->c;
state->r = params->r;
state->grid = snewn(area, digit);
state->pencil = snewn(area * cr, unsigned char);
memset(state->pencil, 0, area * cr);
state->immutable = snewn(area, unsigned char);
memset(state->immutable, FALSE, area);
state->completed = state->cheated = FALSE;
i = 0;
while (*desc) {
int n = *desc++;
if (n >= 'a' && n <= 'z') {
int run = n - 'a' + 1;
assert(i + run <= area);
while (run-- > 0)
state->grid[i++] = 0;
} else if (n == '_') {
/* do nothing */;
} else if (n > '0' && n <= '9') {
assert(i < area);
state->immutable[i] = TRUE;
state->grid[i++] = atoi(desc-1);
while (*desc >= '0' && *desc <= '9')
desc++;
} else {
assert(!"We can't get here");
}
}
assert(i == area);
return state;
}
static game_state *dup_game(game_state *state)
{
game_state *ret = snew(game_state);
int c = state->c, r = state->r, cr = c*r, area = cr * cr;
ret->c = state->c;
ret->r = state->r;
ret->grid = snewn(area, digit);
memcpy(ret->grid, state->grid, area);
ret->pencil = snewn(area * cr, unsigned char);
memcpy(ret->pencil, state->pencil, area * cr);
ret->immutable = snewn(area, unsigned char);
memcpy(ret->immutable, state->immutable, area);
ret->completed = state->completed;
ret->cheated = state->cheated;
return ret;
}
static void free_game(game_state *state)
{
sfree(state->immutable);
sfree(state->pencil);
sfree(state->grid);
sfree(state);
}
static game_state *solve_game(game_state *state, game_aux_info *ai,
char **error)
{
game_state *ret;
int c = state->c, r = state->r, cr = c*r;
int rsolve_ret;
ret = dup_game(state);
ret->completed = ret->cheated = TRUE;
/*
* If we already have the solution in the aux_info, save
* ourselves some time.
*/
if (ai) {
assert(c == ai->c);
assert(r == ai->r);
memcpy(ret->grid, ai->grid, cr * cr * sizeof(digit));
} else {
rsolve_ret = rsolve(c, r, ret->grid, NULL, 2);
if (rsolve_ret != 1) {
free_game(ret);
if (rsolve_ret == 0)
*error = "No solution exists for this puzzle";
else
*error = "Multiple solutions exist for this puzzle";
return NULL;
}
}
return ret;
}
static char *grid_text_format(int c, int r, digit *grid)
{
int cr = c*r;
int x, y;
int maxlen;
char *ret, *p;
/*
* There are cr lines of digits, plus r-1 lines of block
* separators. Each line contains cr digits, cr-1 separating
* spaces, and c-1 two-character block separators. Thus, the
* total length of a line is 2*cr+2*c-3 (not counting the
* newline), and there are cr+r-1 of them.
*/
maxlen = (cr+r-1) * (2*cr+2*c-2);
ret = snewn(maxlen+1, char);
p = ret;
for (y = 0; y < cr; y++) {
for (x = 0; x < cr; x++) {
int ch = grid[y * cr + x];
if (ch == 0)
ch = ' ';
else if (ch <= 9)
ch = '0' + ch;
else
ch = 'a' + ch-10;
*p++ = ch;
if (x+1 < cr) {
*p++ = ' ';
if ((x+1) % r == 0) {
*p++ = '|';
*p++ = ' ';
}
}
}
*p++ = '\n';
if (y+1 < cr && (y+1) % c == 0) {
for (x = 0; x < cr; x++) {
*p++ = '-';
if (x+1 < cr) {
*p++ = '-';
if ((x+1) % r == 0) {
*p++ = '+';
*p++ = '-';
}
}
}
*p++ = '\n';
}
}
assert(p - ret == maxlen);
*p = '\0';
return ret;
}
static char *game_text_format(game_state *state)
{
return grid_text_format(state->c, state->r, state->grid);
}
struct game_ui {
/*
* These are the coordinates of the currently highlighted
* square on the grid, or -1,-1 if there isn't one. When there
* is, pressing a valid number or letter key or Space will
* enter that number or letter in the grid.
*/
int hx, hy;
/*
* This indicates whether the current highlight is a
* pencil-mark one or a real one.
*/
int hpencil;
};
static game_ui *new_ui(game_state *state)
{
game_ui *ui = snew(game_ui);
ui->hx = ui->hy = -1;
ui->hpencil = 0;
return ui;
}
static void free_ui(game_ui *ui)
{
sfree(ui);
}
static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
int button)
{
int c = from->c, r = from->r, cr = c*r;
int tx, ty;
game_state *ret;
button &= ~MOD_MASK;
tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1;
ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1;
if (tx >= 0 && tx < cr && ty >= 0 && ty < cr &&
(button == LEFT_BUTTON || button == RIGHT_BUTTON)) {
/*
* Prevent pencil-mode highlighting of a filled square.
*/
if (button == RIGHT_BUTTON && from->grid[ty*cr+tx])
return NULL;
if (tx == ui->hx && ty == ui->hy) {
ui->hx = ui->hy = -1;
} else {
ui->hx = tx;
ui->hy = ty;
}
ui->hpencil = (button == RIGHT_BUTTON);
return from; /* UI activity occurred */
}
if (ui->hx != -1 && ui->hy != -1 &&
((button >= '1' && button <= '9' && button - '0' <= cr) ||
(button >= 'a' && button <= 'z' && button - 'a' + 10 <= cr) ||
(button >= 'A' && button <= 'Z' && button - 'A' + 10 <= cr) ||
button == ' ')) {
int n = button - '0';
if (button >= 'A' && button <= 'Z')
n = button - 'A' + 10;
if (button >= 'a' && button <= 'z')
n = button - 'a' + 10;
if (button == ' ')
n = 0;
if (from->immutable[ui->hy*cr+ui->hx])
return NULL; /* can't overwrite this square */
/*
* Can't make pencil marks in a filled square. In principle
* this shouldn't happen anyway because we should never
* have even been able to pencil-highlight the square, but
* it never hurts to be careful.
*/
if (ui->hpencil && from->grid[ui->hy*cr+ui->hx])
return NULL;
ret = dup_game(from);
if (ui->hpencil && n > 0) {
int index = (ui->hy*cr+ui->hx) * cr + (n-1);
ret->pencil[index] = !ret->pencil[index];
} else {
ret->grid[ui->hy*cr+ui->hx] = n;
memset(ret->pencil + (ui->hy*cr+ui->hx)*cr, 0, cr);
/*
* We've made a real change to the grid. Check to see
* if the game has been completed.
*/
if (!ret->completed && check_valid(c, r, ret->grid)) {
ret->completed = TRUE;
}
}
ui->hx = ui->hy = -1;
return ret; /* made a valid move */
}
return NULL;
}
/* ----------------------------------------------------------------------
* Drawing routines.
*/
struct game_drawstate {
int started;
int c, r, cr;
digit *grid;
unsigned char *pencil;
unsigned char *hl;
};
#define XSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
#define YSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
static void game_size(game_params *params, int *x, int *y)
{
int c = params->c, r = params->r, cr = c*r;
*x = XSIZE(cr);
*y = YSIZE(cr);
}
static float *game_colours(frontend *fe, game_state *state, int *ncolours)
{
float *ret = snewn(3 * NCOLOURS, float);
frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
ret[COL_GRID * 3 + 0] = 0.0F;
ret[COL_GRID * 3 + 1] = 0.0F;
ret[COL_GRID * 3 + 2] = 0.0F;
ret[COL_CLUE * 3 + 0] = 0.0F;
ret[COL_CLUE * 3 + 1] = 0.0F;
ret[COL_CLUE * 3 + 2] = 0.0F;
ret[COL_USER * 3 + 0] = 0.0F;
ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1];
ret[COL_USER * 3 + 2] = 0.0F;
ret[COL_HIGHLIGHT * 3 + 0] = 0.85F * ret[COL_BACKGROUND * 3 + 0];
ret[COL_HIGHLIGHT * 3 + 1] = 0.85F * ret[COL_BACKGROUND * 3 + 1];
ret[COL_HIGHLIGHT * 3 + 2] = 0.85F * ret[COL_BACKGROUND * 3 + 2];
ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
*ncolours = NCOLOURS;
return ret;
}
static game_drawstate *game_new_drawstate(game_state *state)
{
struct game_drawstate *ds = snew(struct game_drawstate);
int c = state->c, r = state->r, cr = c*r;
ds->started = FALSE;
ds->c = c;
ds->r = r;
ds->cr = cr;
ds->grid = snewn(cr*cr, digit);
memset(ds->grid, 0, cr*cr);
ds->pencil = snewn(cr*cr*cr, digit);
memset(ds->pencil, 0, cr*cr*cr);
ds->hl = snewn(cr*cr, unsigned char);
memset(ds->hl, 0, cr*cr);
return ds;
}
static void game_free_drawstate(game_drawstate *ds)
{
sfree(ds->hl);
sfree(ds->pencil);
sfree(ds->grid);
sfree(ds);
}
static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
int x, int y, int hl)
{
int c = state->c, r = state->r, cr = c*r;
int tx, ty;
int cx, cy, cw, ch;
char str[2];
if (ds->grid[y*cr+x] == state->grid[y*cr+x] &&
ds->hl[y*cr+x] == hl &&
!memcmp(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr))
return; /* no change required */
tx = BORDER + x * TILE_SIZE + 2;
ty = BORDER + y * TILE_SIZE + 2;
cx = tx;
cy = ty;
cw = TILE_SIZE-3;
ch = TILE_SIZE-3;
if (x % r)
cx--, cw++;
if ((x+1) % r)
cw++;
if (y % c)
cy--, ch++;
if ((y+1) % c)
ch++;
clip(fe, cx, cy, cw, ch);
/* background needs erasing */
draw_rect(fe, cx, cy, cw, ch, hl == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
/* pencil-mode highlight */
if (hl == 2) {
int coords[6];
coords[0] = cx;
coords[1] = cy;
coords[2] = cx+cw/2;
coords[3] = cy;
coords[4] = cx;
coords[5] = cy+ch/2;
draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
}
/* new number needs drawing? */
if (state->grid[y*cr+x]) {
str[1] = '\0';
str[0] = state->grid[y*cr+x] + '0';
if (str[0] > '9')
str[0] += 'a' - ('9'+1);
draw_text(fe, tx + TILE_SIZE/2, ty + TILE_SIZE/2,
FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
state->immutable[y*cr+x] ? COL_CLUE : COL_USER, str);
} else {
/* pencil marks required? */
int i, j;
for (i = j = 0; i < cr; i++)
if (state->pencil[(y*cr+x)*cr+i]) {
int dx = j % r, dy = j / r, crm = max(c, r);
str[1] = '\0';
str[0] = i + '1';
if (str[0] > '9')
str[0] += 'a' - ('9'+1);
draw_text(fe, tx + (4*dx+3) * TILE_SIZE / (4*r+2),
ty + (4*dy+3) * TILE_SIZE / (4*c+2),
FONT_VARIABLE, TILE_SIZE/(crm*5/4),
ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
j++;
}
}
unclip(fe);
draw_update(fe, cx, cy, cw, ch);
ds->grid[y*cr+x] = state->grid[y*cr+x];
memcpy(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr);
ds->hl[y*cr+x] = hl;
}
static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
game_state *state, int dir, game_ui *ui,
float animtime, float flashtime)
{
int c = state->c, r = state->r, cr = c*r;
int x, y;
if (!ds->started) {
/*
* The initial contents of the window are not guaranteed
* and can vary with front ends. To be on the safe side,
* all games should start by drawing a big
* background-colour rectangle covering the whole window.
*/
draw_rect(fe, 0, 0, XSIZE(cr), YSIZE(cr), COL_BACKGROUND);
/*
* Draw the grid.
*/
for (x = 0; x <= cr; x++) {
int thick = (x % r ? 0 : 1);
draw_rect(fe, BORDER + x*TILE_SIZE - thick, BORDER-1,
1+2*thick, cr*TILE_SIZE+3, COL_GRID);
}
for (y = 0; y <= cr; y++) {
int thick = (y % c ? 0 : 1);
draw_rect(fe, BORDER-1, BORDER + y*TILE_SIZE - thick,
cr*TILE_SIZE+3, 1+2*thick, COL_GRID);
}
}
/*
* Draw any numbers which need redrawing.
*/
for (x = 0; x < cr; x++) {
for (y = 0; y < cr; y++) {
int highlight = 0;
if (flashtime > 0 &&
(flashtime <= FLASH_TIME/3 ||
flashtime >= FLASH_TIME*2/3))
highlight = 1;
if (x == ui->hx && y == ui->hy)
highlight = ui->hpencil ? 2 : 1;
draw_number(fe, ds, state, x, y, highlight);
}
}
/*
* Update the _entire_ grid if necessary.
*/
if (!ds->started) {
draw_update(fe, 0, 0, XSIZE(cr), YSIZE(cr));
ds->started = TRUE;
}
}
static float game_anim_length(game_state *oldstate, game_state *newstate,
int dir, game_ui *ui)
{
return 0.0F;
}
static float game_flash_length(game_state *oldstate, game_state *newstate,
int dir, game_ui *ui)
{
if (!oldstate->completed && newstate->completed &&
!oldstate->cheated && !newstate->cheated)
return FLASH_TIME;
return 0.0F;
}
static int game_wants_statusbar(void)
{
return FALSE;
}
#ifdef COMBINED
#define thegame solo
#endif
const struct game thegame = {
"Solo", "games.solo",
default_params,
game_fetch_preset,
decode_params,
encode_params,
free_params,
dup_params,
TRUE, game_configure, custom_params,
validate_params,
new_game_desc,
game_free_aux_info,
validate_desc,
new_game,
dup_game,
free_game,
TRUE, solve_game,
TRUE, game_text_format,
new_ui,
free_ui,
make_move,
game_size,
game_colours,
game_new_drawstate,
game_free_drawstate,
game_redraw,
game_anim_length,
game_flash_length,
game_wants_statusbar,
};
#ifdef STANDALONE_SOLVER
/*
* gcc -DSTANDALONE_SOLVER -o solosolver solo.c malloc.c
*/
void frontend_default_colour(frontend *fe, float *output) {}
void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
int align, int colour, char *text) {}
void draw_rect(frontend *fe, int x, int y, int w, int h, int colour) {}
void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour) {}
void draw_polygon(frontend *fe, int *coords, int npoints,
int fill, int colour) {}
void clip(frontend *fe, int x, int y, int w, int h) {}
void unclip(frontend *fe) {}
void start_draw(frontend *fe) {}
void draw_update(frontend *fe, int x, int y, int w, int h) {}
void end_draw(frontend *fe) {}
unsigned long random_bits(random_state *state, int bits)
{ assert(!"Shouldn't get randomness"); return 0; }
unsigned long random_upto(random_state *state, unsigned long limit)
{ assert(!"Shouldn't get randomness"); return 0; }
void fatal(char *fmt, ...)
{
va_list ap;
fprintf(stderr, "fatal error: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(1);
}
int main(int argc, char **argv)
{
game_params *p;
game_state *s;
int recurse = TRUE;
char *id = NULL, *desc, *err;
int y, x;
int grade = FALSE;
while (--argc > 0) {
char *p = *++argv;
if (!strcmp(p, "-r")) {
recurse = TRUE;
} else if (!strcmp(p, "-n")) {
recurse = FALSE;
} else if (!strcmp(p, "-v")) {
solver_show_working = TRUE;
recurse = FALSE;
} else if (!strcmp(p, "-g")) {
grade = TRUE;
recurse = FALSE;
} else if (*p == '-') {
fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0]);
return 1;
} else {
id = p;
}
}
if (!id) {
fprintf(stderr, "usage: %s [-n | -r | -g | -v] <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(p, desc);
if (recurse) {
int ret = rsolve(p->c, p->r, s->grid, NULL, 2);
if (ret > 1) {
fprintf(stderr, "%s: rsolve: multiple solutions detected\n",
argv[0]);
}
} else {
int ret = nsolve(p->c, p->r, s->grid);
if (grade) {
if (ret == DIFF_IMPOSSIBLE) {
/*
* Now resort to rsolve to determine whether it's
* really soluble.
*/
ret = rsolve(p->c, p->r, s->grid, NULL, 2);
if (ret == 0)
ret = DIFF_IMPOSSIBLE;
else if (ret == 1)
ret = DIFF_RECURSIVE;
else
ret = DIFF_AMBIGUOUS;
}
printf("Difficulty rating: %s\n",
ret==DIFF_BLOCK ? "Trivial (blockwise positional elimination only)":
ret==DIFF_SIMPLE ? "Basic (row/column/number elimination required)":
ret==DIFF_INTERSECT ? "Intermediate (intersectional analysis required)":
ret==DIFF_SET ? "Advanced (set elimination required)":
ret==DIFF_RECURSIVE ? "Unreasonable (guesswork and backtracking required)":
ret==DIFF_AMBIGUOUS ? "Ambiguous (multiple solutions exist)":
ret==DIFF_IMPOSSIBLE ? "Impossible (no solution exists)":
"INTERNAL ERROR: unrecognised difficulty code");
}
}
printf("%s\n", grid_text_format(p->c, p->r, s->grid));
return 0;
}
#endif