Implement the remaining modes of reasoning in nsolve, and thus
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 26 Apr 2005 11:19:00 +0000 (11:19 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 26 Apr 2005 11:19:00 +0000 (11:19 +0000)
enable configurable puzzle difficulty. I'm only generating grids up
to Times level (complicated non-recursive analysis but guessing
never required); I wouldn't object to providing a Telegraph
difficulty level (guessing required) but it turns out to be very
hard indeed to generate at random. I might still add it later
(probably under the name `Unreasonable' :-) if I can think of an
efficient way to find them.

git-svn-id: svn://svn.tartarus.org/sgt/puzzles@5682 cda61777-01e9-0310-a592-d414129be87e

puzzles.but
solo.c

index 8e99a31..8d592b5 100644 (file)
@@ -589,19 +589,39 @@ make them easier, since the symmetry constraints can force more
 clues than necessary to be present. Completely asymmetric puzzles
 have the freedom to contain as few clues as possible.
 
 clues than necessary to be present. Completely asymmetric puzzles
 have the freedom to contain as few clues as possible.
 
+Finally, you can configure the difficulty of the generated puzzles.
+Difficulty levels are judged by the complexity of the techniques of
+deduction required to solve the puzzle: each level requires a mode
+of reasoning which was not necessary in the previous one. In
+particular, on difficulty levels \q{Trivial} and \q{Basic} there
+will be a square you can fill in with a single number at all times,
+whereas at \q{Intermediate} level and beyond you will have to make
+partial deductions about the \e{set} of squares a number could be in
+(or the set of numbers that could be in a square). None of the
+difficulty levels generated by this program ever requires making a
+guess and backtracking if it turns out to be wrong.
+
+Generating difficult puzzles is itself difficult: if you select
+\q{Intermediate} or \q{Advanced} difficulty, Solo may have to make
+many attempts at generating a puzzle before it finds one hard enough
+for you. Be prepared to wait, especially if you have also configured
+a large puzzle size.
+
 \H{solo-cmdline} \I{command line, for Solo}Additional command-line
 configuration
 
 \H{solo-cmdline} \I{command line, for Solo}Additional command-line
 configuration
 
-The symmetry parameter, described in \k{solo-parameters}, is not
-mentioned by default in the game ID (see \k{common-id}). So if you
-set your symmetry to (say) 4-way rotational, and then you generate a
-3\by\.4 grid, then the game ID will simply say \c{3x4:}\e{numbers}.
-This means that if you send the game ID to another player and they
-paste it into their copy of Solo, their game will not be
-automatically configured to use the same symmetry in any subsequent
-grids it generates. (I don't think the average person examining a
-single grid sent to them by another player would want their
-configuration modified to that extent.)
+The symmetry and difficulty parameters (described in
+\k{solo-parameters}) are not mentioned by default in the game ID
+(see \k{common-id}). So if (for example) you set your symmetry to
+4-way rotational and your difficulty to \q{Advanced}, and then you
+generate a 3\by\.4 grid, then the game ID will simply say
+\c{3x4:}\e{numbers}. This means that if you send the game ID to
+another player and they paste it into their copy of Solo, their game
+will not be automatically configured to use the same symmetry and
+difficulty settings in any subsequent grids it generates. (I don't
+think the average person examining a single grid sent to them by
+another player would want their configuration modified to that
+extent.)
 
 If you are specifying a game ID or game parameters on the command
 line (see \k{common-cmdline}) and you do want to configure the
 
 If you are specifying a game ID or game parameters on the command
 line (see \k{common-cmdline}) and you do want to configure the
@@ -616,9 +636,18 @@ parameters:
 
 \b \cq{a} for no symmetry at all (stands for \q{asymmetric})
 
 
 \b \cq{a} for no symmetry at all (stands for \q{asymmetric})
 
+\b \cq{dt} for Trivial difficulty level
+
+\b \cq{db} for Basic difficulty level
+
+\b \cq{di} for Intermediate difficulty level
+
+\b \cq{da} for Advanced difficulty level
+
 So, for example, you can make Solo generate asymmetric 3x4 grids by
 running \cq{solo 3x4a}, or 4-way rotationally symmetric 2x3 grids by
 So, for example, you can make Solo generate asymmetric 3x4 grids by
 running \cq{solo 3x4a}, or 4-way rotationally symmetric 2x3 grids by
-running \cq{solo 2x3r4}.
+running \cq{solo 2x3r4}, or \q{Advanced}-level 2x3 grids by running
+\cq{solo 2x3da}.
 
 
 \A{licence} \I{MIT licence}\ii{Licence}
 
 
 \A{licence} \I{MIT licence}\ii{Licence}
diff --git a/solo.c b/solo.c
index 8ef1cad..d991536 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -7,11 +7,6 @@
  *    seems to be taking ascenders/descenders into account when
  *    centring. Ick.
  *
  *    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
- *
  *  - 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
  *  - 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
@@ -34,8 +29,7 @@
  *      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
  *      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.
+ *       deduction, now we can create puzzles that require them.
  */
 
 /*
  */
 
 /*
 #include <ctype.h>
 #include <math.h>
 
 #include <ctype.h>
 #include <math.h>
 
+#ifdef STANDALONE_SOLVER
+#include <stdarg.h>
+int solver_show_working;
+#endif
+
 #include "puzzles.h"
 
 #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
 /*
  * To save space, I store digits internally as unsigned char. This
  * imposes a hard limit of 255 on the order of the puzzle. Since
@@ -94,6 +95,9 @@ typedef unsigned char digit;
 
 enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF4 };
 
 
 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,
 enum {
     COL_BACKGROUND,
     COL_GRID,
@@ -104,7 +108,7 @@ enum {
 };
 
 struct game_params {
 };
 
 struct game_params {
-    int c, r, symm;
+    int c, r, symm, diff;
 };
 
 struct game_state {
 };
 
 struct game_state {
@@ -120,35 +124,11 @@ static game_params *default_params(void)
 
     ret->c = ret->r = 3;
     ret->symm = SYMM_ROT2;            /* a plausible default */
 
     ret->c = ret->r = 3;
     ret->symm = SYMM_ROT2;            /* a plausible default */
+    ret->diff = DIFF_SIMPLE;           /* so is this */
 
     return ret;
 }
 
 
     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);
 static void free_params(game_params *params)
 {
     sfree(params);
@@ -161,6 +141,30 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
     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 Basic", { 3, 3, SYMM_ROT2, DIFF_SIMPLE } },
+        { "3x3 Intermediate", { 3, 3, SYMM_ROT2, DIFF_INTERSECT } },
+        { "3x3 Advanced", { 3, 3, SYMM_ROT2, DIFF_SET } },
+        { "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 game_params *decode_params(char const *string)
 {
     game_params *ret = default_params();
 static game_params *decode_params(char const *string)
 {
     game_params *ret = default_params();
@@ -173,21 +177,33 @@ static game_params *decode_params(char const *string)
         ret->r = atoi(string);
        while (*string && isdigit((unsigned char)*string)) 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;
+            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
+            string++;                  /* eat unknown character */
     }
     }
-    /* FIXME: difficulty levels */
 
     return ret;
 }
 
     return ret;
 }
@@ -229,14 +245,15 @@ static config_item *game_configure(game_params *params)
     ret[2].sval = ":None:2-way rotation:4-way rotation:4-way mirror";
     ret[2].ival = params->symm;
 
     ret[2].sval = ":None:2-way rotation:4-way rotation:4-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";
+    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;
 }
 
     return ret;
 }
@@ -248,6 +265,7 @@ static game_params *custom_params(config_item *cfg)
     ret->c = atoi(cfg[0].sval);
     ret->r = atoi(cfg[1].sval);
     ret->symm = cfg[2].ival;
     ret->c = atoi(cfg[0].sval);
     ret->r = atoi(cfg[1].sval);
     ret->symm = cfg[2].ival;
+    ret->diff = cfg[3].ival;
 
     return ret;
 }
 
     return ret;
 }
@@ -548,31 +566,35 @@ static int rsolve(int c, int r, digit *grid, random_state *rs, int max)
  *    in because all the other numbers that could go in it are
  *    ruled out.
  *
  *    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.
  *
  *    (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).
  */
 
 /*
  */
 
 /*
@@ -675,10 +697,14 @@ static void nsolve_place(struct nsolve_usage *usage, int x, int y, int n)
      * in its row, its column and its block.
      */
     usage->row[y*cr+n-1] = usage->col[x*cr+n-1] =
      * 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;
 {
     int c = usage->c, r = usage->r, cr = c*r;
     int fpos, m, i;
@@ -705,6 +731,16 @@ static int nsolve_elim(struct nsolve_usage *usage, int start, int step)
        y %= cr;
 
         if (!usage->grid[YUNTRANS(y)*cr+x]) {
        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;
         }
             nsolve_place(usage, x, y, n);
             return TRUE;
         }
@@ -713,11 +749,260 @@ static int nsolve_elim(struct nsolve_usage *usage, int start, int step)
     return FALSE;
 }
 
     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;
 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
 
     /*
      * Set up a usage structure as a clean slate (everything
@@ -754,6 +1039,13 @@ static int nsolve(int c, int r, digit *grid)
      * not.
      */
     while (1) {
      * 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:
 
        /*
         cont:
 
        /*
@@ -763,8 +1055,15 @@ static int nsolve(int c, int r, digit *grid)
            for (y = 0; y < r; y++)
                for (n = 1; n <= cr; n++)
                    if (!usage->blk[(y*c+(x/r))*cr+n-1] &&
            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;
                         goto cont;
+                    }
 
        /*
         * Row-wise positional elimination.
 
        /*
         * Row-wise positional elimination.
@@ -772,16 +1071,29 @@ static int nsolve(int c, int r, digit *grid)
        for (y = 0; y < cr; y++)
            for (n = 1; n <= cr; n++)
                if (!usage->row[y*cr+n-1] &&
        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;
                     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] &&
        /*
         * 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;
                     goto cont;
+                }
 
        /*
         * Numeric elimination.
 
        /*
         * Numeric elimination.
@@ -789,8 +1101,111 @@ static int nsolve(int c, int r, digit *grid)
        for (x = 0; x < cr; x++)
            for (y = 0; y < cr; y++)
                if (!usage->grid[YUNTRANS(y)*cr+x] &&
        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, 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, 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
 
        /*
         * If we reach here, we have made no deductions in this
@@ -808,8 +1223,8 @@ static int nsolve(int c, int r, digit *grid)
     for (x = 0; x < cr; x++)
        for (y = 0; y < cr; y++)
            if (!grid[y*cr+x])
     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;
 }
 
 /* ----------------------------------------------------------------------
 }
 
 /* ----------------------------------------------------------------------
@@ -955,83 +1370,105 @@ static char *new_game_seed(game_params *params, random_state *rs)
     char *seed;
     int coords[16], ncoords;
     int xlim, ylim;
     char *seed;
     int coords[16], ncoords;
     int xlim, ylim;
+    int maxdiff;
 
     /*
 
     /*
-     * 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);
     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++;
-               }
-
-       /*
-        * 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 we have a solved grid, start removing things from it
+         * while preserving solubility.
+         */
+        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++;
+                    }
+
+            /*
+             * 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++) {
+                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) <= maxdiff) {
+                    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.
+                 */
+                break;
+            }
+        }
 
 
-       /*
-        * 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++) {
-           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;
-           }
-       }
+        memcpy(grid2, grid, area);
+    } while (nsolve(c, r, grid2) != maxdiff);
 
 
-       if (i == nlocs) {
-           /*
-            * There was nothing we could remove without destroying
-            * solvability.
-            */
-           break;
-       }
-    }
     sfree(grid2);
     sfree(locs);
 
     sfree(grid2);
     sfree(locs);
 
@@ -1488,6 +1925,10 @@ const struct game thegame = {
 
 #ifdef STANDALONE_SOLVER
 
 
 #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 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) {}
@@ -1500,8 +1941,10 @@ 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) {}
 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, ...)
 {
 
 void fatal(char *fmt, ...)
 {
@@ -1521,9 +1964,10 @@ int main(int argc, char **argv)
 {
     game_params *p;
     game_state *s;
 {
     game_params *p;
     game_state *s;
-    int recurse = FALSE;
+    int recurse = TRUE;
     char *id = NULL, *seed, *err;
     int y, x;
     char *id = NULL, *seed, *err;
     int y, x;
+    int grade = FALSE;
 
     while (--argc > 0) {
         char *p = *++argv;
 
     while (--argc > 0) {
         char *p = *++argv;
@@ -1531,6 +1975,12 @@ int main(int argc, char **argv)
             recurse = TRUE;
         } else if (!strcmp(p, "-n")) {
             recurse = FALSE;
             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 if (*p == '-') {
             fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0]);
             return 1;
@@ -1540,7 +1990,7 @@ int main(int argc, char **argv)
     }
 
     if (!id) {
     }
 
     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;
     }
 
         return 1;
     }
 
@@ -1562,17 +2012,67 @@ int main(int argc, char **argv)
     if (recurse) {
         int ret = rsolve(p->c, p->r, s->grid, NULL, 2);
         if (ret > 1) {
     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 {
         }
     } else {
-        nsolve(p->c, p->r, s->grid);
+        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 ? "blockwise positional elimination only":
+                   ret==DIFF_SIMPLE ? "row/column/number elimination required":
+                   ret==DIFF_INTERSECT ? "intersectional analysis required":
+                   ret==DIFF_SET ? "set elimination required":
+                   ret==DIFF_RECURSIVE ? "guesswork and backtracking required":
+                   ret==DIFF_AMBIGUOUS ? "multiple solutions exist":
+                   ret==DIFF_IMPOSSIBLE ? "no solution exists":
+                   "INTERNAL ERROR: unrecognised difficulty code");
+        }
     }
 
     for (y = 0; y < p->c * p->r; y++) {
         for (x = 0; x < p->c * p->r; x++) {
     }
 
     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 c = s->grid[y * p->c * p->r + x];
+            if (c == 0)
+                c = ' ';
+            else if (c <= 9)
+                c = '0' + c;
+            else
+                c = 'a' + c-10;
+            printf("%c", c);
+            if (x+1 < p->c * p->r) {
+                if ((x+1) % p->c)
+                    printf(" ");
+                else
+                    printf(" | ");
+            }
         }
         printf("\n");
         }
         printf("\n");
+        if (y+1 < p->c * p->r && (y+1) % p->r == 0) {
+            for (x = 0; x < p->c * p->r; x++) {
+                printf("-");
+                if (x+1 < p->c * p->r) {
+                    if ((x+1) % p->c)
+                        printf("-");
+                    else
+                        printf("-+-");
+                }
+            }
+            printf("\n");
+        }
     }
     printf("\n");
 
     }
     printf("\n");