Patches from Richard B for Solo:
[sgt/puzzles] / mines.c
diff --git a/mines.c b/mines.c
index 3c06b53..6a3d7aa 100644 (file)
--- a/mines.c
+++ b/mines.c
@@ -2,30 +2,9 @@
  * mines.c: Minesweeper clone with sophisticated grid generation.
  * 
  * Still TODO:
- * 
- *  - possibly disable undo? Or alternatively mark game states as
- *    `cheated', although that's horrid.
- *     + OK. Rather than _disabling_ undo, we have a hook callable
- *       in the game backend which is called before we do an undo.
- *       That hook can talk to the game_ui and set the cheated flag,
- *       and then make_move can avoid setting the `won' flag after that.
  *
- *  - delay game description generation until first click
- *     + do we actually _need_ to do this? Hmm.
- *     + it's a perfectly good puzzle game without
- *     + but it might be useful when we start timing, since it
- *      ensures the user is really paying attention.
- * 
- *  - timer
- * 
- *  - question marks (arrgh, preferences?)
- * 
- *  - sensible parameter constraints
- *     + 30x16: 191 mines just about works if rather slowly, 192 is
- *      just about doom (the latter corresponding to a density of
- *      exactly 1 in 2.5)
- *     + 9x9: 45 mines works - over 1 in 2! 50 seems a bit slow.
- *     + it might not be feasible to work out the exact limit
+ *  - think about configurably supporting question marks. Once,
+ *    that is, we've thought about configurability in general!
  */
 
 #include <stdio.h>
@@ -39,7 +18,7 @@
 #include "puzzles.h"
 
 enum {
-    COL_BACKGROUND,
+    COL_BACKGROUND, COL_BACKGROUND2,
     COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8,
     COL_MINE, COL_BANG, COL_CROSS, COL_FLAG, COL_FLAGBASE, COL_QUERY,
     COL_HIGHLIGHT, COL_LOWLIGHT,
@@ -60,10 +39,27 @@ struct game_params {
     int unique;
 };
 
+struct mine_layout {
+    /*
+     * This structure is shared between all the game_states for a
+     * given instance of the puzzle, so we reference-count it.
+     */
+    int refcount;
+    char *mines;
+    /*
+     * If we haven't yet actually generated the mine layout, here's
+     * all the data we will need to do so.
+     */
+    int n, unique;
+    random_state *rs;
+    midend_data *me;                  /* to give back the new game desc */
+};
+
 struct game_state {
     int w, h, n, dead, won;
-    char *mines;                      /* real mine positions */
-    char *grid;                               /* player knowledge */
+    int used_solve, just_used_solve;
+    struct mine_layout *layout;               /* real mine positions */
+    signed char *grid;                        /* player knowledge */
     /*
      * Each item in the `grid' array is one of the following values:
      * 
@@ -232,6 +228,8 @@ static game_params *custom_params(config_item *cfg)
     ret->w = atoi(cfg[0].sval);
     ret->h = atoi(cfg[1].sval);
     ret->n = atoi(cfg[2].sval);
+    if (strchr(cfg[2].sval, '%'))
+       ret->n = ret->n * (ret->w * ret->h) / 100;
     ret->unique = cfg[3].ival;
 
     return ret;
@@ -245,6 +243,8 @@ static char *validate_params(game_params *params)
        return "Width must be greater than zero";
     if (params->h <= 0)
        return "Height must be greater than zero";
+    if (params->n > params->w * params->h - 9)
+       return "Too many mines for grid size";
 
     /*
      * FIXME: Need more constraints here. Not sure what the
@@ -548,7 +548,8 @@ static void std_add(struct squaretodo *std, int i)
     std->next[i] = -1;
 }
 
-static void known_squares(int w, int h, struct squaretodo *std, char *grid,
+static void known_squares(int w, int h, struct squaretodo *std,
+                         signed char *grid,
                          int (*open)(void *ctx, int x, int y), void *openctx,
                          int x, int y, int mask, int mine)
 {
@@ -615,9 +616,10 @@ struct perturbations {
  *    steps were required; the exact return value is the number of
  *    perturb calls.
  */
-static int minesolve(int w, int h, int n, char *grid,
+static int minesolve(int w, int h, int n, signed char *grid,
                     int (*open)(void *ctx, int x, int y),
-                    struct perturbations *(*perturb)(void *ctx, char *grid,
+                    struct perturbations *(*perturb)(void *ctx,
+                                                     signed char *grid,
                                                      int x, int y, int mask),
                     void *ctx, random_state *rs)
 {
@@ -1071,7 +1073,7 @@ static int minesolve(int w, int h, int n, char *grid,
                         * next. Backtrack cursor to the nearest 1,
                         * change it to a 0 and continue.
                         */
-                       while (cursor-- >= 0 && !setused[cursor]);
+                       while (--cursor >= 0 && !setused[cursor]);
                        if (cursor >= 0) {
                            assert(setused[cursor]);
 
@@ -1151,13 +1153,18 @@ static int minesolve(int w, int h, int n, char *grid,
             * 
             * If we have no sets at all, we must give up.
             */
-           if (count234(ss->sets) == 0)
-               break;
-           s = index234(ss->sets, random_upto(rs, count234(ss->sets)));
+           if (count234(ss->sets) == 0) {
+#ifdef SOLVER_DIAGNOSTICS
+               printf("perturbing on entire unknown set\n");
+#endif
+               ret = perturb(ctx, grid, 0, 0, 0);
+           } else {
+               s = index234(ss->sets, random_upto(rs, count234(ss->sets)));
 #ifdef SOLVER_DIAGNOSTICS
-           printf("perturbing on set %d,%d %03x\n", s->x, s->y, s->mask);
+               printf("perturbing on set %d,%d %03x\n", s->x, s->y, s->mask);
 #endif
-           ret = perturb(ctx, grid, s->x, s->y, s->mask);
+               ret = perturb(ctx, grid, s->x, s->y, s->mask);
+           }
 
            if (ret) {
                assert(ret->n > 0);    /* otherwise should have been NULL */
@@ -1167,6 +1174,8 @@ static int minesolve(int w, int h, int n, char *grid,
                 * the returned structure tells us which. Adjust
                 * the mine count in any set which overlaps one of
                 * those squares, and put them back on the to-do
+                * list. Also, if the square itself is marked as a
+                * known non-mine, put it back on the squares-to-do
                 * list.
                 */
                for (i = 0; i < ret->n; i++) {
@@ -1176,6 +1185,11 @@ static int minesolve(int w, int h, int n, char *grid,
                           ret->changes[i].x, ret->changes[i].y);
 #endif
 
+                   if (ret->changes[i].delta < 0 &&
+                       grid[ret->changes[i].y*w+ret->changes[i].x] != -2) {
+                       std_add(std, ret->changes[i].y*w+ret->changes[i].x);
+                   }
+
                    list = ss_overlap(ss,
                                      ret->changes[i].x, ret->changes[i].y, 1);
 
@@ -1197,7 +1211,7 @@ static int minesolve(int w, int h, int n, char *grid,
                /*
                 * Dump the current known state of the grid.
                 */
-               printf("state after perturbation:\n", nperturbs);
+               printf("state after perturbation:\n");
                for (y = 0; y < h; y++) {
                    for (x = 0; x < w; x++) {
                        int v = grid[y*w+x];
@@ -1266,9 +1280,10 @@ static int minesolve(int w, int h, int n, char *grid,
  */
 
 struct minectx {
-    char *grid;
+    signed char *grid;
     int w, h;
     int sx, sy;
+    int allow_big_perturbs;
     random_state *rs;
 };
 
@@ -1325,15 +1340,35 @@ static int squarecmp(const void *av, const void *bv)
     return 0;
 }
 
-static struct perturbations *mineperturb(void *vctx, char *grid,
+/*
+ * Normally this function is passed an (x,y,mask) set description.
+ * On occasions, though, there is no _localised_ set being used,
+ * and the set being perturbed is supposed to be the entirety of
+ * the unreachable area. This is signified by the special case
+ * mask==0: in this case, anything labelled -2 in the grid is part
+ * of the set.
+ * 
+ * Allowing perturbation in this special case appears to make it
+ * guaranteeably possible to generate a workable grid for any mine
+ * density, but they tend to be a bit boring, with mines packed
+ * densely into far corners of the grid and the remainder being
+ * less dense than one might like. Therefore, to improve overall
+ * grid quality I disable this feature for the first few attempts,
+ * and fall back to it after no useful grid has been generated.
+ */
+static struct perturbations *mineperturb(void *vctx, signed char *grid,
                                         int setx, int sety, int mask)
 {
     struct minectx *ctx = (struct minectx *)vctx;
     struct square *sqlist;
     int x, y, dx, dy, i, n, nfull, nempty;
-    struct square *tofill[9], *toempty[9], **todo;
+    struct square **tofill, **toempty, **todo;
     int ntofill, ntoempty, ntodo, dtodo, dset;
     struct perturbations *ret;
+    int *setlist;
+
+    if (!mask && !ctx->allow_big_perturbs)
+       return NULL;
 
     /*
      * Make a list of all the squares in the grid which we can
@@ -1364,9 +1399,10 @@ static struct perturbations *mineperturb(void *vctx, char *grid,
             * If this square is in the input set, also don't put
             * it on the list!
             */
-           if (x >= setx && x < setx + 3 &&
-               y >= sety && y < sety + 3 &&
-               mask & (1 << ((y-sety)*3+(x-setx))))
+           if ((mask == 0 && grid[y*ctx->w+x] == -2) ||
+               (x >= setx && x < setx + 3 &&
+                y >= sety && y < sety + 3 &&
+                mask & (1 << ((y-sety)*3+(x-setx)))))
                continue;
 
            sqlist[n].x = x;
@@ -1409,16 +1445,27 @@ static struct perturbations *mineperturb(void *vctx, char *grid,
      * we've been provided.
      */
     nfull = nempty = 0;
-    for (dy = 0; dy < 3; dy++)
-       for (dx = 0; dx < 3; dx++)
-           if (mask & (1 << (dy*3+dx))) {
-               assert(setx+dx <= ctx->w);
-               assert(sety+dy <= ctx->h);
-               if (ctx->grid[(sety+dy)*ctx->w+(setx+dx)])
-                   nfull++;
-               else
-                   nempty++;
-           }
+    if (mask) {
+       for (dy = 0; dy < 3; dy++)
+           for (dx = 0; dx < 3; dx++)
+               if (mask & (1 << (dy*3+dx))) {
+                   assert(setx+dx <= ctx->w);
+                   assert(sety+dy <= ctx->h);
+                   if (ctx->grid[(sety+dy)*ctx->w+(setx+dx)])
+                       nfull++;
+                   else
+                       nempty++;
+               }
+    } else {
+       for (y = 0; y < ctx->h; y++)
+           for (x = 0; x < ctx->w; x++)
+               if (grid[y*ctx->w+x] == -2) {
+                   if (ctx->grid[y*ctx->w+x])
+                       nfull++;
+                   else
+                       nempty++;
+               }
+    }
 
     /*
      * Now go through our sorted list until we find either `nfull'
@@ -1428,6 +1475,13 @@ static struct perturbations *mineperturb(void *vctx, char *grid,
      * overall.
      */
     ntofill = ntoempty = 0;
+    if (mask) {
+       tofill = snewn(9, struct square *);
+       toempty = snewn(9, struct square *);
+    } else {
+       tofill = snewn(ctx->w * ctx->h, struct square *);
+       toempty = snewn(ctx->w * ctx->h, struct square *);
+    }
     for (i = 0; i < n; i++) {
        struct square *sq = &sqlist[i];
        if (ctx->grid[sq->y * ctx->w + sq->x])
@@ -1439,12 +1493,55 @@ static struct perturbations *mineperturb(void *vctx, char *grid,
     }
 
     /*
-     * If this didn't work at all, I think we just give up.
+     * If we haven't found enough empty squares outside the set to
+     * empty it into _or_ enough full squares outside it to fill it
+     * up with, we'll have to settle for doing only a partial job.
+     * In this case we choose to always _fill_ the set (because
+     * this case will tend to crop up when we're working with very
+     * high mine densities and the only way to get a solvable grid
+     * is going to be to pack most of the mines solidly around the
+     * edges). So now our job is to make a list of the empty
+     * squares in the set, and shuffle that list so that we fill a
+     * random selection of them.
      */
     if (ntofill != nfull && ntoempty != nempty) {
-       sfree(sqlist);
-       return NULL;
-    }
+       int k;
+
+       assert(ntoempty != 0);
+
+       setlist = snewn(ctx->w * ctx->h, int);
+       i = 0;
+       if (mask) {
+           for (dy = 0; dy < 3; dy++)
+               for (dx = 0; dx < 3; dx++)
+                   if (mask & (1 << (dy*3+dx))) {
+                       assert(setx+dx <= ctx->w);
+                       assert(sety+dy <= ctx->h);
+                       if (!ctx->grid[(sety+dy)*ctx->w+(setx+dx)])
+                           setlist[i++] = (sety+dy)*ctx->w+(setx+dx);
+                   }
+       } else {
+           for (y = 0; y < ctx->h; y++)
+               for (x = 0; x < ctx->w; x++)
+                   if (grid[y*ctx->w+x] == -2) {
+                       if (!ctx->grid[y*ctx->w+x])
+                           setlist[i++] = y*ctx->w+x;
+                   }
+       }
+       assert(i > ntoempty);
+       /*
+        * Now pick `ntoempty' items at random from the list.
+        */
+       for (k = 0; k < ntoempty; k++) {
+           int index = k + random_upto(ctx->rs, i - k);
+           int tmp;
+
+           tmp = setlist[k];
+           setlist[k] = setlist[index];
+           setlist[index] = tmp;
+       }
+    } else
+       setlist = NULL;
 
     /*
      * Now we're pretty much there. We need to either
@@ -1464,11 +1561,17 @@ static struct perturbations *mineperturb(void *vctx, char *grid,
        ntodo = ntofill;
        dtodo = +1;
        dset = -1;
+       sfree(toempty);
     } else {
+       /*
+        * (We also fall into this case if we've constructed a
+        * setlist.)
+        */
        todo = toempty;
        ntodo = ntoempty;
        dtodo = -1;
        dset = +1;
+       sfree(tofill);
     }
     ret->n = 2 * ntodo;
     ret->changes = snewn(ret->n, struct perturbation);
@@ -1478,20 +1581,45 @@ static struct perturbations *mineperturb(void *vctx, char *grid,
        ret->changes[i].delta = dtodo;
     }
     /* now i == ntodo */
-    for (dy = 0; dy < 3; dy++)
-       for (dx = 0; dx < 3; dx++)
-           if (mask & (1 << (dy*3+dx))) {
-               int currval = (ctx->grid[(sety+dy)*ctx->w+(setx+dx)] ? +1 : -1);
-               if (dset == -currval) {
-                   ret->changes[i].x = setx + dx;
-                   ret->changes[i].y = sety + dy;
-                   ret->changes[i].delta = dset;
-                   i++;
+    if (setlist) {
+       int j;
+       assert(todo == toempty);
+       for (j = 0; j < ntoempty; j++) {
+           ret->changes[i].x = setlist[j] % ctx->w;
+           ret->changes[i].y = setlist[j] / ctx->w;
+           ret->changes[i].delta = dset;
+           i++;
+       }
+       sfree(setlist);
+    } else if (mask) {
+       for (dy = 0; dy < 3; dy++)
+           for (dx = 0; dx < 3; dx++)
+               if (mask & (1 << (dy*3+dx))) {
+                   int currval = (ctx->grid[(sety+dy)*ctx->w+(setx+dx)] ? +1 : -1);
+                   if (dset == -currval) {
+                       ret->changes[i].x = setx + dx;
+                       ret->changes[i].y = sety + dy;
+                       ret->changes[i].delta = dset;
+                       i++;
+                   }
                }
-           }
+    } else {
+       for (y = 0; y < ctx->h; y++)
+           for (x = 0; x < ctx->w; x++)
+               if (grid[y*ctx->w+x] == -2) {
+                   int currval = (ctx->grid[y*ctx->w+x] ? +1 : -1);
+                   if (dset == -currval) {
+                       ret->changes[i].x = x;
+                       ret->changes[i].y = y;
+                       ret->changes[i].delta = dset;
+                       i++;
+                   }
+               }
+    }
     assert(i == ret->n);
 
     sfree(sqlist);
+    sfree(todo);
 
     /*
      * Having set up the precise list of changes we're going to
@@ -1578,9 +1706,11 @@ static char *minegen(int w, int h, int n, int x, int y, int unique,
 {
     char *ret = snewn(w*h, char);
     int success;
+    int ntries = 0;
 
     do {
        success = FALSE;
+       ntries++;
 
        memset(ret, 0, w*h);
 
@@ -1645,7 +1775,7 @@ static char *minegen(int w, int h, int n, int x, int y, int unique,
         * We bypass this bit if we're not after a unique grid.
          */
        if (unique) {
-           char *solvegrid = snewn(w*h, char);
+           signed char *solvegrid = snewn(w*h, char);
            struct minectx actx, *ctx = &actx;
            int solveret, prevret = -2;
 
@@ -1655,6 +1785,7 @@ static char *minegen(int w, int h, int n, int x, int y, int unique,
            ctx->sx = x;
            ctx->sy = y;
            ctx->rs = rs;
+           ctx->allow_big_perturbs = (ntries > 100);
 
            while (1) {
                memset(solvegrid, -2, w*h);
@@ -1777,7 +1908,7 @@ static void obfuscate_bitmap(unsigned char *bmp, int bits, int decode)
                SHA_Final(&final, digest);
                digestpos = 0;
            }
-           steps[i].targetstart[j] ^= digest[digestpos]++;
+           steps[i].targetstart[j] ^= digest[digestpos++];
        }
 
        /*
@@ -1788,52 +1919,140 @@ static void obfuscate_bitmap(unsigned char *bmp, int bits, int decode)
     }
 }
 
-static char *new_game_desc(game_params *params, random_state *rs,
-                          game_aux_info **aux)
+static char *new_mine_layout(int w, int h, int n, int x, int y, int unique,
+                            random_state *rs, char **game_desc)
 {
-    char *grid, *ret, *p;
+    signed char *grid, *ret, *p;
     unsigned char *bmp;
-    int x, y, i, area;
+    int i, area;
 
-    /*
-     * FIXME: allow user to specify initial open square.
-     */
-    x = random_upto(rs, params->w);
-    y = random_upto(rs, params->h);
+#ifdef TEST_OBFUSCATION
+    static int tested_obfuscation = FALSE;
+    if (!tested_obfuscation) {
+       /*
+        * A few simple test vectors for the obfuscator.
+        * 
+        * First test: the 28-bit stream 1234567. This divides up
+        * into 1234 and 567[0]. The SHA of 56 70 30 (appending
+        * "0") is 15ce8ab946640340bbb99f3f48fd2c45d1a31d30. Thus,
+        * we XOR the 16-bit string 15CE into the input 1234 to get
+        * 07FA. Next, we SHA that with "0": the SHA of 07 FA 30 is
+        * 3370135c5e3da4fed937adc004a79533962b6391. So we XOR the
+        * 12-bit string 337 into the input 567 to get 650. Thus
+        * our output is 07FA650.
+        */
+       {
+           unsigned char bmp1[] = "\x12\x34\x56\x70";
+           obfuscate_bitmap(bmp1, 28, FALSE);
+           printf("test 1 encode: %s\n",
+                  memcmp(bmp1, "\x07\xfa\x65\x00", 4) ? "failed" : "passed");
+           obfuscate_bitmap(bmp1, 28, TRUE);
+           printf("test 1 decode: %s\n",
+                  memcmp(bmp1, "\x12\x34\x56\x70", 4) ? "failed" : "passed");
+       }
+       /*
+        * Second test: a long string to make sure we switch from
+        * one SHA to the next correctly. My input string this time
+        * is simply fifty bytes of zeroes.
+        */
+       {
+           unsigned char bmp2[50];
+           unsigned char bmp2a[50];
+           memset(bmp2, 0, 50);
+           memset(bmp2a, 0, 50);
+           obfuscate_bitmap(bmp2, 50 * 8, FALSE);
+           /*
+            * SHA of twenty-five zero bytes plus "0" is
+            * b202c07b990c01f6ff2d544707f60e506019b671. SHA of
+            * twenty-five zero bytes plus "1" is
+            * fcb1d8b5a2f6b592fe6780b36aa9d65dd7aa6db9. Thus our
+            * first half becomes
+            * b202c07b990c01f6ff2d544707f60e506019b671fcb1d8b5a2.
+            * 
+            * SHA of that lot plus "0" is
+            * 10b0af913db85d37ca27f52a9f78bba3a80030db. SHA of the
+            * same string plus "1" is
+            * 3d01d8df78e76d382b8106f480135a1bc751d725. So the
+            * second half becomes
+            * 10b0af913db85d37ca27f52a9f78bba3a80030db3d01d8df78.
+            */
+           printf("test 2 encode: %s\n",
+                  memcmp(bmp2, "\xb2\x02\xc0\x7b\x99\x0c\x01\xf6\xff\x2d\x54"
+                         "\x47\x07\xf6\x0e\x50\x60\x19\xb6\x71\xfc\xb1\xd8"
+                         "\xb5\xa2\x10\xb0\xaf\x91\x3d\xb8\x5d\x37\xca\x27"
+                         "\xf5\x2a\x9f\x78\xbb\xa3\xa8\x00\x30\xdb\x3d\x01"
+                         "\xd8\xdf\x78", 50) ? "failed" : "passed");
+           obfuscate_bitmap(bmp2, 50 * 8, TRUE);
+           printf("test 2 decode: %s\n",
+                  memcmp(bmp2, bmp2a, 50) ? "failed" : "passed");
+       }
+    }
+#endif
 
-    grid = minegen(params->w, params->h, params->n, x, y, params->unique, rs);
+    grid = minegen(w, h, n, x, y, unique, rs);
 
-    /*
-     * Set up the mine bitmap and obfuscate it.
-     */
-    area = params->w * params->h;
-    bmp = snewn((area + 7) / 8, unsigned char);
-    memset(bmp, 0, (area + 7) / 8);
-    for (i = 0; i < area; i++) {
-       if (grid[i])
-           bmp[i / 8] |= 0x80 >> (i % 8);
-    }
-    obfuscate_bitmap(bmp, area, FALSE);
+    if (game_desc) {
+       /*
+        * Set up the mine bitmap and obfuscate it.
+        */
+       area = w * h;
+       bmp = snewn((area + 7) / 8, unsigned char);
+       memset(bmp, 0, (area + 7) / 8);
+       for (i = 0; i < area; i++) {
+           if (grid[i])
+               bmp[i / 8] |= 0x80 >> (i % 8);
+       }
+       obfuscate_bitmap(bmp, area, FALSE);
 
-    /*
-     * Now encode the resulting bitmap in hex. We can work to
-     * nibble rather than byte granularity, since the obfuscation
-     * function guarantees to return a bit string of the same
-     * length as its input.
-     */
-    ret = snewn((area+3)/4 + 100, char);
-    p = ret + sprintf(ret, "%d,%d,m", x, y);   /* 'm' == masked */
-    for (i = 0; i < (area+3)/4; i++) {
-       int v = bmp[i/2];
-       if (i % 2 == 0)
-           v >>= 4;
-       *p++ = "0123456789abcdef"[v & 0xF];
-    }
-    *p = '\0';
+       /*
+        * Now encode the resulting bitmap in hex. We can work to
+        * nibble rather than byte granularity, since the obfuscation
+        * function guarantees to return a bit string of the same
+        * length as its input.
+        */
+       ret = snewn((area+3)/4 + 100, char);
+       p = ret + sprintf(ret, "%d,%d,m", x, y);   /* 'm' == masked */
+       for (i = 0; i < (area+3)/4; i++) {
+           int v = bmp[i/2];
+           if (i % 2 == 0)
+               v >>= 4;
+           *p++ = "0123456789abcdef"[v & 0xF];
+       }
+       *p = '\0';
 
-    sfree(bmp);
+       sfree(bmp);
 
-    return ret;
+       *game_desc = ret;
+    }  
+
+    return grid;
+}
+
+static char *new_game_desc(game_params *params, random_state *rs,
+                          game_aux_info **aux, int interactive)
+{
+    if (!interactive) {
+       /*
+        * For batch-generated grids, pre-open one square.
+        */
+       int x = random_upto(rs, params->w);
+       int y = random_upto(rs, params->h);
+       signed char *grid;
+       char *desc;
+
+       grid = new_mine_layout(params->w, params->h, params->n,
+                              x, y, params->unique, rs, &desc);
+       sfree(grid);
+       return desc;
+    } else {
+       char *rsdesc, *desc;
+
+       rsdesc = random_state_encode(rs);
+       desc = snewn(strlen(rsdesc) + 100, char);
+       sprintf(desc, "r%d,%c,%s", params->n, params->unique ? 'u' : 'a', rsdesc);
+       sfree(rsdesc);
+       return desc;
+    }
 }
 
 static void game_free_aux_info(game_aux_info *aux)
@@ -1846,32 +2065,48 @@ static char *validate_desc(game_params *params, char *desc)
     int wh = params->w * params->h;
     int x, y;
 
-    if (!*desc || !isdigit((unsigned char)*desc))
-       return "No initial x-coordinate in game description";
-    x = atoi(desc);
-    if (x < 0 || x >= params->w)
-       return "Initial x-coordinate was out of range";
-    while (*desc && isdigit((unsigned char)*desc))
-       desc++;                        /* skip over x coordinate */
-    if (*desc != ',')
-       return "No ',' after initial x-coordinate in game description";
-    desc++;                           /* eat comma */
-    if (!*desc || !isdigit((unsigned char)*desc))
-       return "No initial y-coordinate in game description";
-    y = atoi(desc);
-    if (y < 0 || y >= params->h)
-       return "Initial y-coordinate was out of range";
-    while (*desc && isdigit((unsigned char)*desc))
-       desc++;                        /* skip over y coordinate */
-    if (*desc != ',')
-       return "No ',' after initial y-coordinate in game description";
-    desc++;                           /* eat comma */
-    /* eat `m', meaning `masked', if present */
-    if (*desc == 'm')
+    if (*desc == 'r') {
+       if (!*desc || !isdigit((unsigned char)*desc))
+           return "No initial mine count in game description";
+       while (*desc && isdigit((unsigned char)*desc))
+           desc++;                    /* skip over mine count */
+       if (*desc != ',')
+           return "No ',' after initial x-coordinate in game description";
        desc++;
-    /* now just check length of remainder */
-    if (strlen(desc) != (wh+3)/4)
-       return "Game description is wrong length";
+       if (*desc != 'u' && *desc != 'a')
+           return "No uniqueness specifier in game description";
+       desc++;
+       if (*desc != ',')
+           return "No ',' after uniqueness specifier in game description";
+       /* now ignore the rest */
+    } else {
+       if (!*desc || !isdigit((unsigned char)*desc))
+           return "No initial x-coordinate in game description";
+       x = atoi(desc);
+       if (x < 0 || x >= params->w)
+           return "Initial x-coordinate was out of range";
+       while (*desc && isdigit((unsigned char)*desc))
+           desc++;                    /* skip over x coordinate */
+       if (*desc != ',')
+           return "No ',' after initial x-coordinate in game description";
+       desc++;                        /* eat comma */
+       if (!*desc || !isdigit((unsigned char)*desc))
+           return "No initial y-coordinate in game description";
+       y = atoi(desc);
+       if (y < 0 || y >= params->h)
+           return "Initial y-coordinate was out of range";
+       while (*desc && isdigit((unsigned char)*desc))
+           desc++;                    /* skip over y coordinate */
+       if (*desc != ',')
+           return "No ',' after initial y-coordinate in game description";
+       desc++;                        /* eat comma */
+       /* eat `m', meaning `masked', if present */
+       if (*desc == 'm')
+           desc++;
+       /* now just check length of remainder */
+       if (strlen(desc) != (wh+3)/4)
+           return "Game description is wrong length";
+    }
 
     return NULL;
 }
@@ -1881,24 +2116,30 @@ static int open_square(game_state *state, int x, int y)
     int w = state->w, h = state->h;
     int xx, yy, nmines, ncovered;
 
-    if (state->mines[y*w+x]) {
+    if (!state->layout->mines) {
+       /*
+        * We have a preliminary game in which the mine layout
+        * hasn't been generated yet. Generate it based on the
+        * initial click location.
+        */
+       char *desc;
+       state->layout->mines = new_mine_layout(w, h, state->layout->n,
+                                              x, y, state->layout->unique,
+                                              state->layout->rs,
+                                              &desc);
+       midend_supersede_game_desc(state->layout->me, desc);
+       sfree(desc);
+       random_free(state->layout->rs);
+       state->layout->rs = NULL;
+    }
+
+    if (state->layout->mines[y*w+x]) {
        /*
-        * The player has landed on a mine. Bad luck. Expose all
-        * the mines.
+        * The player has landed on a mine. Bad luck. Expose the
+        * mine that killed them, but not the rest (in case they
+        * want to Undo and carry on playing).
         */
        state->dead = TRUE;
-       for (yy = 0; yy < h; yy++)
-           for (xx = 0; xx < w; xx++) {
-               if (state->mines[yy*w+xx] &&
-                   (state->grid[yy*w+xx] == -2 ||
-                    state->grid[yy*w+xx] == -3)) {
-                   state->grid[yy*w+xx] = 64;
-               }
-               if (!state->mines[yy*w+xx] &&
-                   state->grid[yy*w+xx] == -1) {
-                   state->grid[yy*w+xx] = 66;
-               }
-           }
        state->grid[y*w+x] = 65;
        return -1;
     }
@@ -1925,7 +2166,7 @@ static int open_square(game_state *state, int x, int y)
                if (state->grid[yy*w+xx] == -10) {
                    int dx, dy, v;
 
-                   assert(!state->mines[yy*w+xx]);
+                   assert(!state->layout->mines[yy*w+xx]);
 
                    v = 0;
 
@@ -1933,7 +2174,7 @@ static int open_square(game_state *state, int x, int y)
                        for (dy = -1; dy <= +1; dy++)
                            if (xx+dx >= 0 && xx+dx < state->w &&
                                yy+dy >= 0 && yy+dy < state->h &&
-                               state->mines[(yy+dy)*w+(xx+dx)])
+                               state->layout->mines[(yy+dy)*w+(xx+dx)])
                                v++;
 
                    state->grid[yy*w+xx] = v;
@@ -1964,7 +2205,7 @@ static int open_square(game_state *state, int x, int y)
        for (xx = 0; xx < w; xx++) {
            if (state->grid[yy*w+xx] < 0)
                ncovered++;
-           if (state->mines[yy*w+xx])
+           if (state->layout->mines[yy*w+xx])
                nmines++;
        }
     assert(ncovered >= nmines);
@@ -1980,7 +2221,7 @@ static int open_square(game_state *state, int x, int y)
     return 0;
 }
 
-static game_state *new_game(game_params *params, char *desc)
+static game_state *new_game(midend_data *me, game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int i, wh, x, y, ret, masked;
@@ -1990,69 +2231,88 @@ static game_state *new_game(game_params *params, char *desc)
     state->h = params->h;
     state->n = params->n;
     state->dead = state->won = FALSE;
+    state->used_solve = state->just_used_solve = FALSE;
 
     wh = state->w * state->h;
-    state->mines = snewn(wh, char);
-
-    x = atoi(desc);
-    while (*desc && isdigit((unsigned char)*desc))
-       desc++;                        /* skip over x coordinate */
-    if (*desc) desc++;                /* eat comma */
-    y = atoi(desc);
-    while (*desc && isdigit((unsigned char)*desc))
-       desc++;                        /* skip over y coordinate */
-    if (*desc) desc++;                /* eat comma */
-
-    if (*desc == 'm') {
-       masked = TRUE;
-       desc++;
-    } else {
-       /*
-        * We permit game IDs to be entered by hand without the
-        * masking transformation.
-        */
-       masked = FALSE;
-    }
 
-    bmp = snewn((wh + 7) / 8, unsigned char);
-    memset(bmp, 0, (wh + 7) / 8);
-    for (i = 0; i < (wh+3)/4; i++) {
-       int c = desc[i];
-       int v;
-
-       assert(c != 0);                /* validate_desc should have caught */
-       if (c >= '0' && c <= '9')
-           v = c - '0';
-       else if (c >= 'a' && c <= 'f')
-           v = c - 'a' + 10;
-       else if (c >= 'A' && c <= 'F')
-           v = c - 'A' + 10;
+    state->layout = snew(struct mine_layout);
+    state->layout->refcount = 1;
+
+    state->grid = snewn(wh, char);
+    memset(state->grid, -2, wh);
+
+    if (*desc == 'r') {
+       desc++;
+       state->layout->n = atoi(desc);
+       while (*desc && isdigit((unsigned char)*desc))
+           desc++;                    /* skip over mine count */
+       if (*desc) desc++;             /* eat comma */
+       if (*desc == 'a')
+           state->layout->unique = FALSE;
        else
-           v = 0;
+           state->layout->unique = TRUE;
+       desc++;
+       if (*desc) desc++;             /* eat comma */
 
-       bmp[i / 2] |= v << (4 * (1 - (i % 2)));
-    }
+       state->layout->mines = NULL;
+       state->layout->rs = random_state_decode(desc);
+       state->layout->me = me;
 
-    if (masked)
-       obfuscate_bitmap(bmp, wh, TRUE);
+    } else {
+       state->layout->rs = NULL;
+       state->layout->me = NULL;
+
+       state->layout->mines = snewn(wh, char);
+       x = atoi(desc);
+       while (*desc && isdigit((unsigned char)*desc))
+           desc++;                    /* skip over x coordinate */
+       if (*desc) desc++;             /* eat comma */
+       y = atoi(desc);
+       while (*desc && isdigit((unsigned char)*desc))
+           desc++;                    /* skip over y coordinate */
+       if (*desc) desc++;             /* eat comma */
+
+       if (*desc == 'm') {
+           masked = TRUE;
+           desc++;
+       } else {
+           /*
+            * We permit game IDs to be entered by hand without the
+            * masking transformation.
+            */
+           masked = FALSE;
+       }
 
-    memset(state->mines, 0, wh);
-    for (i = 0; i < wh; i++) {
-       if (bmp[i / 8] & (0x80 >> (i % 8)))
-           state->mines[i] = 1;
-    }
+       bmp = snewn((wh + 7) / 8, unsigned char);
+       memset(bmp, 0, (wh + 7) / 8);
+       for (i = 0; i < (wh+3)/4; i++) {
+           int c = desc[i];
+           int v;
+
+           assert(c != 0);            /* validate_desc should have caught */
+           if (c >= '0' && c <= '9')
+               v = c - '0';
+           else if (c >= 'a' && c <= 'f')
+               v = c - 'a' + 10;
+           else if (c >= 'A' && c <= 'F')
+               v = c - 'A' + 10;
+           else
+               v = 0;
+
+           bmp[i / 2] |= v << (4 * (1 - (i % 2)));
+       }
 
-    state->grid = snewn(wh, char);
-    memset(state->grid, -2, wh);
+       if (masked)
+           obfuscate_bitmap(bmp, wh, TRUE);
 
-    ret = open_square(state, x, y);
-    /*
-     * FIXME: This shouldn't be an assert. Perhaps we actually
-     * ought to check it in validate_params! Alternatively, we can
-     * remove the assert completely and actually permit a game
-     * description to start you off dead.
-     */
-    assert(ret != -1);
+       memset(state->layout->mines, 0, wh);
+       for (i = 0; i < wh; i++) {
+           if (bmp[i / 8] & (0x80 >> (i % 8)))
+               state->layout->mines[i] = 1;
+       }
+
+       ret = open_square(state, x, y);
+    }
 
     return state;
 }
@@ -2066,8 +2326,10 @@ static game_state *dup_game(game_state *state)
     ret->n = state->n;
     ret->dead = state->dead;
     ret->won = state->won;
-    ret->mines = snewn(ret->w * ret->h, char);
-    memcpy(ret->mines, state->mines, ret->w * ret->h);
+    ret->used_solve = state->used_solve;
+    ret->just_used_solve = state->just_used_solve;
+    ret->layout = state->layout;
+    ret->layout->refcount++;
     ret->grid = snewn(ret->w * ret->h, char);
     memcpy(ret->grid, state->grid, ret->w * ret->h);
 
@@ -2076,7 +2338,12 @@ static game_state *dup_game(game_state *state)
 
 static void free_game(game_state *state)
 {
-    sfree(state->mines);
+    if (--state->layout->refcount <= 0) {
+       sfree(state->layout->mines);
+       if (state->layout->rs)
+           random_free(state->layout->rs);
+       sfree(state->layout);
+    }
     sfree(state->grid);
     sfree(state);
 }
@@ -2084,17 +2351,77 @@ static void free_game(game_state *state)
 static game_state *solve_game(game_state *state, game_aux_info *aux,
                              char **error)
 {
-    return NULL;
+    /*
+     * Simply expose the entire grid as if it were a completed
+     * solution.
+     */
+    game_state *ret;
+    int yy, xx;
+
+    if (!state->layout->mines) {
+        *error = "Game has not been started yet";
+        return NULL;
+    }
+
+    ret = dup_game(state);
+    for (yy = 0; yy < ret->h; yy++)
+        for (xx = 0; xx < ret->w; xx++) {
+
+            if (ret->layout->mines[yy*ret->w+xx]) {
+                ret->grid[yy*ret->w+xx] = -1;
+            } else {
+                int dx, dy, v;
+
+                v = 0;
+
+                for (dx = -1; dx <= +1; dx++)
+                    for (dy = -1; dy <= +1; dy++)
+                        if (xx+dx >= 0 && xx+dx < ret->w &&
+                            yy+dy >= 0 && yy+dy < ret->h &&
+                            ret->layout->mines[(yy+dy)*ret->w+(xx+dx)])
+                            v++;
+
+                ret->grid[yy*ret->w+xx] = v;
+            }
+        }
+    ret->used_solve = ret->just_used_solve = TRUE;
+    ret->won = TRUE;
+
+    return ret;
 }
 
 static char *game_text_format(game_state *state)
 {
-    return NULL;
+    char *ret;
+    int x, y;
+
+    ret = snewn((state->w + 1) * state->h + 1, char);
+    for (y = 0; y < state->h; y++) {
+       for (x = 0; x < state->w; x++) {
+           int v = state->grid[y*state->w+x];
+           if (v == 0)
+               v = '-';
+           else if (v >= 1 && v <= 8)
+               v = '0' + v;
+           else if (v == -1)
+               v = '*';
+           else if (v == -2 || v == -3)
+               v = '?';
+           else if (v >= 64)
+               v = '!';
+           ret[y * (state->w+1) + x] = v;
+       }
+       ret[y * (state->w+1) + state->w] = '\n';
+    }
+    ret[(state->w + 1) * state->h] = '\0';
+
+    return ret;
 }
 
 struct game_ui {
     int hx, hy, hradius;              /* for mouse-down highlights */
     int flash_is_death;
+    int deaths;
 };
 
 static game_ui *new_ui(game_state *state)
@@ -2102,6 +2429,7 @@ static game_ui *new_ui(game_state *state)
     game_ui *ui = snew(game_ui);
     ui->hx = ui->hy = -1;
     ui->hradius = 0;
+    ui->deaths = 0;
     ui->flash_is_death = FALSE;               /* *shrug* */
     return ui;
 }
@@ -2111,8 +2439,8 @@ static void free_ui(game_ui *ui)
     sfree(ui);
 }
 
-static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
-                            int button)
+static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
+                             int x, int y, int button)
 {
     game_state *ret;
     int cx, cy;
@@ -2126,10 +2454,11 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
 
     cx = FROMCOORD(x);
     cy = FROMCOORD(y);
-    if (cx < 0 || cx >= from->w || cy < 0 || cy > from->h)
+    if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
        return NULL;
 
-    if (button == LEFT_BUTTON || button == LEFT_DRAG) {
+    if (button == LEFT_BUTTON || button == LEFT_DRAG ||
+       button == MIDDLE_BUTTON || button == MIDDLE_DRAG) {
        /*
         * Mouse-downs and mouse-drags just cause highlighting
         * updates.
@@ -2153,12 +2482,13 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
            return NULL;
 
        ret = dup_game(from);
+        ret->just_used_solve = FALSE;
        ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
 
        return ret;
     }
 
-    if (button == LEFT_RELEASE) {
+    if (button == LEFT_RELEASE || button == MIDDLE_RELEASE) {
        ui->hx = ui->hy = -1;
        ui->hradius = 0;
 
@@ -2172,18 +2502,22 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
         * permitted if the tile is marked as a mine, for safety.
         * (Unmark it and _then_ open it.)
         */
-       if (from->grid[cy * from->w + cx] == -2 ||
-           from->grid[cy * from->w + cx] == -3) {
+       if (button == LEFT_RELEASE &&
+           (from->grid[cy * from->w + cx] == -2 ||
+            from->grid[cy * from->w + cx] == -3)) {
            ret = dup_game(from);
+            ret->just_used_solve = FALSE;
            open_square(ret, cx, cy);
+            if (ret->dead)
+                ui->deaths++;
            return ret;
        }
 
        /*
-        * Left-clicking on an uncovered tile: first we check to see if
-        * the number of mine markers surrounding the tile is equal to
-        * its mine count, and if so then we open all other surrounding
-        * squares.
+        * Left-clicking or middle-clicking on an uncovered tile:
+        * first we check to see if the number of mine markers
+        * surrounding the tile is equal to its mine count, and if
+        * so then we open all other surrounding squares.
         */
        if (from->grid[cy * from->w + cx] > 0) {
            int dy, dx, n;
@@ -2200,6 +2534,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
 
            if (n == from->grid[cy * from->w + cx]) {
                ret = dup_game(from);
+                ret->just_used_solve = FALSE;
                for (dy = -1; dy <= +1; dy++)
                    for (dx = -1; dx <= +1; dx++)
                        if (cx+dx >= 0 && cx+dx < ret->w &&
@@ -2207,6 +2542,8 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
                            (ret->grid[(cy+dy)*ret->w+(cx+dx)] == -2 ||
                             ret->grid[(cy+dy)*ret->w+(cx+dx)] == -3))
                            open_square(ret, cx+dx, cy+dy);
+                if (ret->dead)
+                    ui->deaths++;
                return ret;
            }
        }
@@ -2223,7 +2560,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
 
 struct game_drawstate {
     int w, h, started;
-    char *grid;
+    signed char *grid;
     /*
      * Items in this `grid' array have all the same values as in
      * the game_state grid, and in addition:
@@ -2248,6 +2585,10 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
 
     frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
 
+    ret[COL_BACKGROUND2 * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 19.0 / 20.0;
+    ret[COL_BACKGROUND2 * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 19.0 / 20.0;
+    ret[COL_BACKGROUND2 * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 19.0 / 20.0;
+
     ret[COL_1 * 3 + 0] = 0.0F;
     ret[COL_1 * 3 + 1] = 0.0F;
     ret[COL_1 * 3 + 2] = 1.0F;
@@ -2348,7 +2689,8 @@ static void draw_tile(frontend *fe, int x, int y, int v, int bg)
            /*
             * Omit the highlights in this case.
             */
-           draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE, bg);
+           draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+                      bg == COL_BACKGROUND ? COL_BACKGROUND2 : bg);
            draw_line(fe, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
            draw_line(fe, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
        } else {
@@ -2416,7 +2758,8 @@ static void draw_tile(frontend *fe, int x, int y, int v, int bg)
         * on), we clear the square to COL_BANG.
         */
         draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
-                 (v == 65 ? COL_BANG : bg));
+                 (v == 65 ? COL_BANG :
+                   bg == COL_BACKGROUND ? COL_BACKGROUND2 : bg));
        draw_line(fe, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
        draw_line(fe, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
 
@@ -2556,7 +2899,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
            if (v == -1)
                markers++;
-           if (state->mines[y*ds->w+x])
+           if (state->layout->mines && state->layout->mines[y*ds->w+x])
                mines++;
 
            if ((v == -2 || v == -3) &&
@@ -2569,18 +2912,27 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            }
        }
 
+    if (!state->layout->mines)
+       mines = state->layout->n;
+
     /*
      * Update the status bar.
      */
     {
        char statusbar[512];
        if (state->dead) {
-           sprintf(statusbar, "GAME OVER!");
+           sprintf(statusbar, "DEAD!");
        } else if (state->won) {
-           sprintf(statusbar, "COMPLETED!");
+            if (state->used_solve)
+                sprintf(statusbar, "Auto-solved.");
+            else
+                sprintf(statusbar, "COMPLETED!");
        } else {
-           sprintf(statusbar, "Mines marked: %d / %d", markers, mines);
+           sprintf(statusbar, "Marked: %d / %d", markers, mines);
        }
+        if (ui->deaths)
+            sprintf(statusbar + strlen(statusbar),
+                    "  Deaths: %d", ui->deaths);
        status_bar(fe, statusbar);
     }
 }
@@ -2594,6 +2946,9 @@ static float game_anim_length(game_state *oldstate, game_state *newstate,
 static float game_flash_length(game_state *oldstate, game_state *newstate,
                               int dir, game_ui *ui)
 {
+    if (oldstate->used_solve || newstate->used_solve)
+        return 0.0F;
+
     if (dir > 0 && !oldstate->dead && !oldstate->won) {
        if (newstate->dead) {
            ui->flash_is_death = TRUE;
@@ -2612,6 +2967,13 @@ static int game_wants_statusbar(void)
     return TRUE;
 }
 
+static int game_timing_state(game_state *state)
+{
+    if (state->dead || state->won || !state->layout->mines)
+       return FALSE;
+    return TRUE;
+}
+
 #ifdef COMBINED
 #define thegame mines
 #endif
@@ -2632,8 +2994,8 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
-    FALSE, solve_game,
-    FALSE, game_text_format,
+    TRUE, solve_game,
+    TRUE, game_text_format,
     new_ui,
     free_ui,
     make_move,
@@ -2645,4 +3007,6 @@ const struct game thegame = {
     game_anim_length,
     game_flash_length,
     game_wants_statusbar,
+    TRUE, game_timing_state,
+    BUTTON_BEATS(LEFT_BUTTON, RIGHT_BUTTON),
 };