*
* TODO:
*
- * - can we do anything about nasty centring of text in GTK? It
- * seems to be taking ascenders/descenders into account when
- * centring. Ick.
- *
- * - implement stronger modes of reasoning in nsolve, thus
- * enabling harder puzzles
- * + and having done that, supply configurable difficulty
- * levels
- *
+ * - 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.
- * + be careful of being too clever here, though, until after
- * I've tried implementing difficulty levels. It's not
- * impossible that those might impose much more important
- * constraints on this process.
*
* - alternative interface modes
* + sudoku.com's Windows program has a palette of possible
* 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.
- * + `pencil marks' might be useful for more subtle forms of
- * deduction, once we implement creation of puzzles that
- * require it.
+ * + 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.
*/
/*
#include <ctype.h>
#include <math.h>
+#ifdef STANDALONE_SOLVER
+#include <stdarg.h>
+int solver_show_working;
+#endif
+
#include "puzzles.h"
/*
typedef unsigned char digit;
#define ORDER_MAX 255
-#define TILE_SIZE 32
-#define BORDER 18
+#define PREFERRED_TILE_SIZE 32
+#define TILE_SIZE (ds->tilesize)
+#define BORDER (TILE_SIZE / 2)
#define FLASH_TIME 0.4F
-enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF4 };
+enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF2, SYMM_REF2D, SYMM_REF4,
+ SYMM_REF4D, SYMM_REF8 };
+
+enum { DIFF_BLOCK, DIFF_SIMPLE, DIFF_INTERSECT,
+ DIFF_SET, DIFF_RECURSIVE, DIFF_AMBIGUOUS, DIFF_IMPOSSIBLE };
enum {
COL_BACKGROUND,
COL_CLUE,
COL_USER,
COL_HIGHLIGHT,
+ COL_ERROR,
+ COL_PENCIL,
NCOLOURS
};
struct game_params {
- int c, r, symm;
+ 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;
+ int completed, cheated;
};
static game_params *default_params(void)
ret->c = ret->r = 3;
ret->symm = SYMM_ROT2; /* a plausible default */
+ ret->diff = DIFF_BLOCK; /* so is this */
return ret;
}
-static int game_fetch_preset(int i, char **name, game_params **params)
-{
- game_params *ret;
- int c, r;
- char buf[80];
-
- switch (i) {
- case 0: c = 2, r = 2; break;
- case 1: c = 2, r = 3; break;
- case 2: c = 3, r = 3; break;
- case 3: c = 3, r = 4; break;
- case 4: c = 4, r = 4; break;
- default: return FALSE;
- }
-
- sprintf(buf, "%dx%d", c, r);
- *name = dupstr(buf);
- *params = ret = snew(game_params);
- ret->c = c;
- ret->r = r;
- ret->symm = SYMM_ROT2;
- /* FIXME: difficulty presets? */
- return TRUE;
-}
-
static void free_params(game_params *params)
{
sfree(params);
return ret;
}
-static game_params *decode_params(char const *string)
+static int game_fetch_preset(int i, char **name, game_params **params)
{
- game_params *ret = default_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 } },
+#ifndef SLOW_SYSTEM
+ { "3x4 Basic", { 3, 4, SYMM_ROT2, DIFF_SIMPLE } },
+ { "4x4 Basic", { 4, 4, SYMM_ROT2, DIFF_SIMPLE } },
+#endif
+ };
+ 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);
- ret->symm = SYMM_ROT2;
while (*string && isdigit((unsigned char)*string)) string++;
if (*string == 'x') {
string++;
ret->r = atoi(string);
while (*string && isdigit((unsigned char)*string)) 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;
+ while (*string) {
+ if (*string == 'r' || *string == 'm' || *string == 'a') {
+ int sn, sc, sd;
+ sc = *string++;
+ if (*string == 'd') {
+ sd = TRUE;
+ string++;
+ } else {
+ sd = FALSE;
+ }
+ sn = atoi(string);
+ while (*string && isdigit((unsigned char)*string)) string++;
+ if (sc == 'm' && sn == 8)
+ ret->symm = SYMM_REF8;
+ if (sc == 'm' && sn == 4)
+ ret->symm = sd ? SYMM_REF4D : SYMM_REF4;
+ if (sc == 'm' && sn == 2)
+ ret->symm = sd ? SYMM_REF2D : SYMM_REF2;
+ 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 */
}
- /* FIXME: difficulty levels */
-
- return ret;
}
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
{
char str[80];
- /*
- * Symmetry is a game generation preference and hence is left
- * out of the encoding. Users can add it back in as they see
- * fit.
- */
sprintf(str, "%dx%d", params->c, params->r);
+ if (full) {
+ switch (params->symm) {
+ case SYMM_REF8: strcat(str, "m8"); break;
+ case SYMM_REF4: strcat(str, "m4"); break;
+ case SYMM_REF4D: strcat(str, "md4"); break;
+ case SYMM_REF2: strcat(str, "m2"); break;
+ case SYMM_REF2D: strcat(str, "md2"); 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);
}
ret[2].name = "Symmetry";
ret[2].type = C_CHOICES;
- ret[2].sval = ":None:2-way rotation:4-way rotation:4-way mirror";
+ ret[2].sval = ":None:2-way rotation:4-way rotation:2-way mirror:"
+ "2-way diagonal mirror:4-way mirror:4-way diagonal mirror:"
+ "8-way mirror";
ret[2].ival = params->symm;
- /*
- * FIXME: difficulty level.
- */
+ ret[3].name = "Difficulty";
+ ret[3].type = C_CHOICES;
+ ret[3].sval = ":Trivial:Basic:Intermediate:Advanced:Unreasonable";
+ ret[3].ival = params->diff;
- ret[3].name = NULL;
- ret[3].type = C_END;
- ret[3].sval = NULL;
- ret[3].ival = 0;
+ ret[4].name = NULL;
+ ret[4].type = C_END;
+ ret[4].sval = NULL;
+ ret[4].ival = 0;
return ret;
}
ret->c = atoi(cfg[0].sval);
ret->r = atoi(cfg[1].sval);
ret->symm = cfg[2].ival;
+ ret->diff = cfg[3].ival;
return ret;
}
* in because all the other numbers that could go in it are
* ruled out.
*
- * More advanced modes of reasoning I'd like to support in future:
- *
- * - Intersectional elimination: given two domains which overlap
+ * - 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.
*
- * - Setwise numeric 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.)
- *
- * - Setwise positional elimination: 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.
+ * - 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).
*/
/*
* in its row, its column and its block.
*/
usage->row[y*cr+n-1] = usage->col[x*cr+n-1] =
- usage->blk[((y/c)*c+(x/r))*cr+n-1] = TRUE;
+ usage->blk[((y%r)*c+(x/r))*cr+n-1] = TRUE;
}
-static int nsolve_elim(struct nsolve_usage *usage, int start, int step)
+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;
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;
+}
+
+struct nsolve_scratch {
+ unsigned char *grid, *rowidx, *colidx, *set;
+};
+
+static int nsolve_set(struct nsolve_usage *usage,
+ struct nsolve_scratch *scratch,
+ 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 = scratch->grid;
+ unsigned char *rowidx = scratch->rowidx;
+ unsigned char *colidx = scratch->colidx;
+ unsigned char *set = scratch->set;
+
+ /*
+ * 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) {
+ 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 */
+ }
+
+ return FALSE;
+}
+
+static struct nsolve_scratch *nsolve_new_scratch(struct nsolve_usage *usage)
+{
+ struct nsolve_scratch *scratch = snew(struct nsolve_scratch);
+ int cr = usage->cr;
+ scratch->grid = snewn(cr*cr, unsigned char);
+ scratch->rowidx = snewn(cr, unsigned char);
+ scratch->colidx = snewn(cr, unsigned char);
+ scratch->set = snewn(cr, unsigned char);
+ return scratch;
+}
+
+static void nsolve_free_scratch(struct nsolve_scratch *scratch)
+{
+ sfree(scratch->set);
+ sfree(scratch->colidx);
+ sfree(scratch->rowidx);
+ sfree(scratch->grid);
+ sfree(scratch);
+}
+
static int nsolve(int c, int r, digit *grid)
{
struct nsolve_usage *usage;
+ struct nsolve_scratch *scratch;
int cr = c*r;
int x, y, n;
+ int diff = DIFF_BLOCK;
/*
* Set up a usage structure as a clean slate (everything
memset(usage->col, FALSE, cr * cr);
memset(usage->blk, FALSE, cr * cr);
+ scratch = nsolve_new_scratch(usage);
+
/*
* Place all the clue numbers we are given.
*/
* 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:
/*
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))
+ 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))
+ 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))
+ 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))
- goto cont;
+ 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, scratch, 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, scratch, 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, scratch, 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
break;
}
+ nsolve_free_scratch(scratch);
+
sfree(usage->cube);
sfree(usage->row);
sfree(usage->col);
for (x = 0; x < cr; x++)
for (y = 0; y < cr; y++)
if (!grid[y*cr+x])
- return FALSE;
- return TRUE;
+ return DIFF_IMPOSSIBLE;
+ return diff;
}
/* ----------------------------------------------------------------------
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++;
+#define ADD(x,y) (*output++ = (x), *output++ = (y), i++)
+
+ ADD(x, y);
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;
+ ADD(cr - 1 - x, cr - 1 - y);
+ break;
+ case SYMM_ROT4:
+ ADD(cr - 1 - y, x);
+ ADD(y, cr - 1 - x);
+ ADD(cr - 1 - x, cr - 1 - y);
+ break;
+ case SYMM_REF2:
+ ADD(cr - 1 - x, y);
+ break;
+ case SYMM_REF2D:
+ ADD(y, x);
+ break;
+ case SYMM_REF4:
+ ADD(cr - 1 - x, y);
+ ADD(x, cr - 1 - y);
+ ADD(cr - 1 - x, cr - 1 - y);
+ break;
+ case SYMM_REF4D:
+ ADD(y, x);
+ ADD(cr - 1 - x, cr - 1 - y);
+ ADD(cr - 1 - y, cr - 1 - x);
+ break;
+ case SYMM_REF8:
+ ADD(cr - 1 - x, y);
+ ADD(x, cr - 1 - y);
+ ADD(cr - 1 - x, cr - 1 - y);
+ ADD(y, x);
+ ADD(y, cr - 1 - x);
+ ADD(cr - 1 - y, x);
+ ADD(cr - 1 - y, cr - 1 - x);
+ break;
}
+#undef ADD
+
return i;
}
-static char *new_game_seed(game_params *params, random_state *rs)
+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 interactive)
{
int c = params->c, r = params->r, cr = c*r;
int area = cr*cr;
struct xy { int x, y; } *locs;
int nlocs;
int ret;
- char *seed;
+ char *desc;
int coords[16], ncoords;
- int xlim, ylim;
+ int *symmclasses, nsymmclasses;
+ int maxdiff, recursing;
/*
- * Start the recursive solver with an empty grid to generate a
- * random solved state.
+ * 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.
*/
- grid = snewn(area, digit);
- memset(grid, 0, area);
- ret = rsolve(c, r, grid, rs, 1);
- assert(ret == 1);
- assert(check_valid(c, r, grid));
+ maxdiff = params->diff;
+ if (c == 2 && r == 2)
+ maxdiff = DIFF_BLOCK;
- /*
- * Now we have a solved grid, start removing things from it
- * while preserving solubility.
- */
+ grid = snewn(area, digit);
locs = snewn(area, struct xy);
grid2 = snewn(area, digit);
- symmetry_limit(params, &xlim, &ylim, params->symm);
- 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++;
- }
+ /*
+ * Find the set of equivalence classes of squares permitted
+ * by the selected symmetry. We do this by enumerating all
+ * the grid squares which have no symmetric companion
+ * sorting lower than themselves.
+ */
+ nsymmclasses = 0;
+ symmclasses = snewn(cr * cr, int);
+ {
+ int x, y;
+
+ for (y = 0; y < cr; y++)
+ for (x = 0; x < cr; x++) {
+ int i = y*cr+x;
+ int j;
+
+ ncoords = symmetries(params, x, y, coords, params->symm);
+ for (j = 0; j < ncoords; j++)
+ if (coords[2*j+1]*cr+coords[2*j] < i)
+ break;
+ if (j == ncoords)
+ symmclasses[nsymmclasses++] = i;
+ }
+ }
- /*
- * 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;
- }
- }
+ /*
+ * 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));
/*
- * 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.
+ * Save the solved grid in the aux_info.
*/
- for (i = 0; i < nlocs; i++) {
- 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 (nsolve(c, r, grid2)) {
- for (j = 0; j < ncoords; j++)
- grid[coords[2*j+1]*cr+coords[2*j]] = 0;
- break;
- }
- }
-
- if (i == nlocs) {
+ {
+ 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));
/*
- * There was nothing we could remove without destroying
- * solvability.
+ * We might already have written *aux the last time we
+ * went round this loop, in which case we should free
+ * the old aux_info before overwriting it with the new
+ * one.
*/
- break;
+ if (*aux) {
+ sfree((*aux)->grid);
+ sfree(*aux);
+ }
+ *aux = ai;
}
- }
+
+ /*
+ * Now we have a solved grid, start removing things from it
+ * while preserving solubility.
+ */
+ 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 (i = 0; i < nsymmclasses; i++) {
+ x = symmclasses[i] % cr;
+ y = symmclasses[i] / cr;
+ 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);
+ sfree(symmclasses);
+
/*
* Now we have the grid as it will be presented to the user.
- * Encode it in a game seed.
+ * Encode it in a game desc.
*/
{
char *p;
int run, i;
- seed = snewn(5 * area, char);
- p = seed;
+ desc = snewn(5 * area, char);
+ p = desc;
run = 0;
for (i = 0; i <= area; i++) {
int n = (i < area ? grid[i] : -1);
* bottom right, there's no point putting an
* unnecessary _ before or after it.
*/
- if (p > seed && n > 0)
+ if (p > desc && n > 0)
*p++ = '_';
}
if (n > 0)
run = 0;
}
}
- assert(p - seed < 5 * area);
+ assert(p - desc < 5 * area);
*p++ = '\0';
- seed = sresize(seed, p - seed, char);
+ desc = sresize(desc, p - desc, char);
}
sfree(grid);
- return seed;
+ return desc;
}
-static char *validate_seed(game_params *params, char *seed)
+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 (*seed) {
- int n = *seed++;
+ 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 (*seed >= '0' && *seed <= '9')
- seed++;
+ while (*desc >= '0' && *desc <= '9')
+ desc++;
} else
- return "Invalid character in game specification";
+ return "Invalid character in game description";
}
if (squares < area)
return NULL;
}
-static game_state *new_game(game_params *params, char *seed)
+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;
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 = FALSE;
+ state->completed = state->cheated = FALSE;
i = 0;
- while (*seed) {
- int n = *seed++;
+ while (*desc) {
+ int n = *desc++;
if (n >= 'a' && n <= 'z') {
int run = n - 'a' + 1;
assert(i + run <= area);
} else if (n > '0' && n <= '9') {
assert(i < area);
state->immutable[i] = TRUE;
- state->grid[i++] = atoi(seed-1);
- while (*seed >= '0' && *seed <= '9')
- seed++;
+ state->grid[i++] = atoi(desc-1);
+ while (*desc >= '0' && *desc <= '9')
+ desc++;
} else {
assert(!"We can't get here");
}
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_state *currstate,
+ 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
* 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;
}
sfree(ui);
}
-static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
- int button)
+static void game_changed_state(game_ui *ui, game_state *oldstate,
+ game_state *newstate)
+{
+ int c = newstate->c, r = newstate->r, cr = c*r;
+ /*
+ * We prevent pencil-mode highlighting of a filled square. So
+ * if the user has just filled in a square which we had a
+ * pencil-mode highlight in (by Undo, or by Redo, or by Solve),
+ * then we cancel the highlight.
+ */
+ if (ui->hx >= 0 && ui->hy >= 0 && ui->hpencil &&
+ newstate->grid[ui->hy * cr + ui->hx] != 0) {
+ ui->hx = ui->hy = -1;
+ }
+}
+
+struct game_drawstate {
+ int started;
+ int c, r, cr;
+ int tilesize;
+ digit *grid;
+ unsigned char *pencil;
+ unsigned char *hl;
+ /* This is scratch space used within a single call to game_redraw. */
+ int *entered_items;
+};
+
+static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
+ int x, int y, int button)
{
int c = from->c, r = from->r, cr = c*r;
int tx, ty;
game_state *ret;
- tx = (x - BORDER) / TILE_SIZE;
- ty = (y - BORDER) / TILE_SIZE;
-
- if (tx >= 0 && tx < cr && ty >= 0 && ty < cr && button == LEFT_BUTTON) {
- if (tx == ui->hx && ty == ui->hy) {
- ui->hx = ui->hy = -1;
- } else {
- ui->hx = tx;
- ui->hy = ty;
- }
- return from; /* UI activity occurred */
+ 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) {
+ if (button == LEFT_BUTTON) {
+ if (from->immutable[ty*cr+tx]) {
+ ui->hx = ui->hy = -1;
+ } else if (tx == ui->hx && ty == ui->hy && ui->hpencil == 0) {
+ ui->hx = ui->hy = -1;
+ } else {
+ ui->hx = tx;
+ ui->hy = ty;
+ ui->hpencil = 0;
+ }
+ return from; /* UI activity occurred */
+ }
+ if (button == RIGHT_BUTTON) {
+ /*
+ * Pencil-mode highlighting for non filled squares.
+ */
+ if (from->grid[ty*cr+tx] == 0) {
+ if (tx == ui->hx && ty == ui->hy && ui->hpencil) {
+ ui->hx = ui->hy = -1;
+ } else {
+ ui->hpencil = 1;
+ ui->hx = tx;
+ ui->hy = ty;
+ }
+ } else {
+ ui->hx = ui->hy = -1;
+ }
+ return from; /* UI activity occurred */
+ }
}
if (ui->hx != -1 && ui->hy != -1 &&
if (button == ' ')
n = 0;
+ /*
+ * Can't overwrite this square. In principle this shouldn't
+ * happen anyway because we should never have even been
+ * able to highlight the square, but it never hurts to be
+ * careful.
+ */
if (from->immutable[ui->hy*cr+ui->hx])
- return NULL; /* can't overwrite this square */
+ return NULL;
+
+ /*
+ * 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);
- ret->grid[ui->hy*cr+ui->hx] = n;
+ 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;
- /*
- * 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;
- }
-
return ret; /* made a valid move */
}
* Drawing routines.
*/
-struct game_drawstate {
- int started;
- int c, r, cr;
- digit *grid;
- unsigned char *hl;
-};
-
-#define XSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
-#define YSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
+#define SIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
+#define GETTILESIZE(cr, w) ( (w-1) / (cr+1) )
-static void game_size(game_params *params, int *x, int *y)
+static void game_size(game_params *params, game_drawstate *ds,
+ int *x, int *y, int expand)
{
int c = params->c, r = params->r, cr = c*r;
+ int ts;
+
+ ts = min(GETTILESIZE(cr, *x), GETTILESIZE(cr, *y));
+ if (expand)
+ ds->tilesize = ts;
+ else
+ ds->tilesize = min(ts, PREFERRED_TILE_SIZE);
- *x = XSIZE(cr);
- *y = YSIZE(cr);
+ *x = SIZE(cr);
+ *y = SIZE(cr);
}
static float *game_colours(frontend *fe, game_state *state, int *ncolours)
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_ERROR * 3 + 0] = 1.0F;
+ ret[COL_ERROR * 3 + 1] = 0.0F;
+ ret[COL_ERROR * 3 + 2] = 0.0F;
+
+ 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;
}
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);
-
+ ds->entered_items = snewn(cr*cr, int);
+ ds->tilesize = 0; /* not decided yet */
return ds;
}
static void game_free_drawstate(game_drawstate *ds)
{
sfree(ds->hl);
+ sfree(ds->pencil);
sfree(ds->grid);
+ sfree(ds->entered_items);
sfree(ds);
}
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)
+ 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;
clip(fe, cx, cy, cw, ch);
- /* background needs erasing? */
- if (ds->grid[y*cr+x] || ds->hl[y*cr+x] != hl)
- draw_rect(fe, cx, cy, cw, ch, hl ? COL_HIGHLIGHT : COL_BACKGROUND);
+ /* background needs erasing */
+ draw_rect(fe, cx, cy, cw, ch, (hl & 15) == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
+
+ /* pencil-mode highlight */
+ if ((hl & 15) == 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[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);
+ state->immutable[y*cr+x] ? COL_CLUE : (hl & 16) ? COL_ERROR : COL_USER, str);
+ } else {
+ int i, j, npencil;
+ int pw, ph, pmax, fontsize;
+
+ /* count the pencil marks required */
+ for (i = npencil = 0; i < cr; i++)
+ if (state->pencil[(y*cr+x)*cr+i])
+ npencil++;
+
+ /*
+ * It's not sensible to arrange pencil marks in the same
+ * layout as the squares within a block, because this leads
+ * to the font being too small. Instead, we arrange pencil
+ * marks in the nearest thing we can to a square layout,
+ * and we adjust the square layout depending on the number
+ * of pencil marks in the square.
+ */
+ for (pw = 1; pw * pw < npencil; pw++);
+ if (pw < 3) pw = 3; /* otherwise it just looks _silly_ */
+ ph = (npencil + pw - 1) / pw;
+ if (ph < 2) ph = 2; /* likewise */
+ pmax = max(pw, ph);
+ fontsize = TILE_SIZE/(pmax*(11-pmax)/8);
+
+ for (i = j = 0; i < cr; i++)
+ if (state->pencil[(y*cr+x)*cr+i]) {
+ int dx = j % pw, dy = j / pw;
+
+ 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*pw+2),
+ ty + (4*dy+3) * TILE_SIZE / (4*ph+2),
+ FONT_VARIABLE, fontsize,
+ 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;
}
* 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_rect(fe, 0, 0, SIZE(cr), SIZE(cr), COL_BACKGROUND);
/*
* Draw the grid.
}
}
+ /*
+ * This array is used to keep track of rows, columns and boxes
+ * which contain a number more than once.
+ */
+ for (x = 0; x < cr * cr; x++)
+ ds->entered_items[x] = 0;
+ for (x = 0; x < cr; x++)
+ for (y = 0; y < cr; y++) {
+ digit d = state->grid[y*cr+x];
+ if (d) {
+ int box = (x/r)+(y/c)*c;
+ ds->entered_items[x*cr+d-1] |= ((ds->entered_items[x*cr+d-1] & 1) << 1) | 1;
+ ds->entered_items[y*cr+d-1] |= ((ds->entered_items[y*cr+d-1] & 4) << 1) | 4;
+ ds->entered_items[box*cr+d-1] |= ((ds->entered_items[box*cr+d-1] & 16) << 1) | 16;
+ }
+ }
+
/*
* Draw any numbers which need redrawing.
*/
for (x = 0; x < cr; x++) {
for (y = 0; y < cr; y++) {
- draw_number(fe, ds, state, x, y,
- (x == ui->hx && y == ui->hy) ||
- (flashtime > 0 &&
- (flashtime <= FLASH_TIME/3 ||
- flashtime >= FLASH_TIME*2/3)));
+ int highlight = 0;
+ digit d = state->grid[y*cr+x];
+
+ if (flashtime > 0 &&
+ (flashtime <= FLASH_TIME/3 ||
+ flashtime >= FLASH_TIME*2/3))
+ highlight = 1;
+
+ /* Highlight active input areas. */
+ if (x == ui->hx && y == ui->hy)
+ highlight = ui->hpencil ? 2 : 1;
+
+ /* Mark obvious errors (ie, numbers which occur more than once
+ * in a single row, column, or box). */
+ if (d && ((ds->entered_items[x*cr+d-1] & 2) ||
+ (ds->entered_items[y*cr+d-1] & 8) ||
+ (ds->entered_items[((x/r)+(y/c)*c)*cr+d-1] & 32)))
+ highlight |= 16;
+
+ 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));
+ draw_update(fe, 0, 0, SIZE(cr), SIZE(cr));
ds->started = TRUE;
}
}
static float game_anim_length(game_state *oldstate, game_state *newstate,
- int dir)
+ int dir, game_ui *ui)
{
return 0.0F;
}
static float game_flash_length(game_state *oldstate, game_state *newstate,
- int dir)
+ int dir, game_ui *ui)
{
- if (!oldstate->completed && newstate->completed)
+ if (!oldstate->completed && newstate->completed &&
+ !oldstate->cheated && !newstate->cheated)
return FLASH_TIME;
return 0.0F;
}
return FALSE;
}
+static int game_timing_state(game_state *state)
+{
+ return TRUE;
+}
+
#ifdef COMBINED
#define thegame solo
#endif
const struct game thegame = {
- "Solo", "games.solo", TRUE,
+ "Solo", "games.solo",
default_params,
game_fetch_preset,
decode_params,
encode_params,
free_params,
dup_params,
- game_configure,
- custom_params,
+ TRUE, game_configure, custom_params,
validate_params,
- new_game_seed,
- validate_seed,
+ 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,
+ game_changed_state,
make_move,
game_size,
game_colours,
game_anim_length,
game_flash_length,
game_wants_statusbar,
+ FALSE, game_timing_state,
+ 0, /* mouse_priorities */
};
#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 start_draw(frontend *fe) {}
void draw_update(frontend *fe, int x, int y, int w, int h) {}
void end_draw(frontend *fe) {}
-
-#include <stdarg.h>
+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, ...)
{
{
game_params *p;
game_state *s;
- int recurse = FALSE;
- char *id = NULL, *seed, *err;
+ int recurse = TRUE;
+ char *id = NULL, *desc, *err;
int y, x;
+ int grade = FALSE;
while (--argc > 0) {
char *p = *++argv;
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;
}
if (!id) {
- fprintf(stderr, "usage: %s [-n | -r] <game_id>\n", argv[0]);
+ fprintf(stderr, "usage: %s [-n | -r | -g | -v] <game_id>\n", argv[0]);
return 1;
}
- seed = strchr(id, ':');
- if (!seed) {
+ desc = strchr(id, ':');
+ if (!desc) {
fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
return 1;
}
- *seed++ = '\0';
+ *desc++ = '\0';
- p = decode_params(id);
- err = validate_seed(p, seed);
+ 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, seed);
+ s = new_game(NULL, p, desc);
if (recurse) {
int ret = rsolve(p->c, p->r, s->grid, NULL, 2);
if (ret > 1) {
- printf("multiple solutions detected; only first one output\n");
+ fprintf(stderr, "%s: rsolve: multiple solutions detected\n",
+ argv[0]);
}
} else {
- nsolve(p->c, p->r, s->grid);
- }
-
- for (y = 0; y < p->c * p->r; y++) {
- for (x = 0; x < p->c * p->r; x++) {
- printf("%2.0d", s->grid[y * p->c * p->r + x]);
+ 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("\n");
}
- printf("\n");
+
+ printf("%s\n", grid_text_format(p->c, p->r, s->grid));
return 0;
}