Oops! A trivial typo in obfuscate_bitmap() made the obfuscation
[sgt/puzzles] / mines.c
diff --git a/mines.c b/mines.c
index 3c06b53..5023faa 100644 (file)
--- a/mines.c
+++ b/mines.c
  *       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
@@ -39,7 +31,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 +52,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 +241,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 +256,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 +561,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 +629,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)
 {
@@ -1266,7 +1281,7 @@ static int minesolve(int w, int h, int n, char *grid,
  */
 
 struct minectx {
-    char *grid;
+    signed char *grid;
     int w, h;
     int sx, sy;
     random_state *rs;
@@ -1325,7 +1340,7 @@ static int squarecmp(const void *av, const void *bv)
     return 0;
 }
 
-static struct perturbations *mineperturb(void *vctx, char *grid,
+static struct perturbations *mineperturb(void *vctx, signed char *grid,
                                         int setx, int sety, int mask)
 {
     struct minectx *ctx = (struct minectx *)vctx;
@@ -1645,7 +1660,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;
 
@@ -1777,7 +1792,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 +1803,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 +1949,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,7 +2000,24 @@ 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.
@@ -1889,12 +2025,12 @@ static int open_square(game_state *state, int x, int y)
        state->dead = TRUE;
        for (yy = 0; yy < h; yy++)
            for (xx = 0; xx < w; xx++) {
-               if (state->mines[yy*w+xx] &&
+               if (state->layout->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] &&
+               if (!state->layout->mines[yy*w+xx] &&
                    state->grid[yy*w+xx] == -1) {
                    state->grid[yy*w+xx] = 66;
                }
@@ -1925,7 +2061,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 +2069,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 +2100,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 +2116,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 +2126,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;
+
+    } 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;
+       }
 
-    if (masked)
-       obfuscate_bitmap(bmp, wh, TRUE);
+       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)));
+       }
 
-    memset(state->mines, 0, wh);
-    for (i = 0; i < wh; i++) {
-       if (bmp[i / 8] & (0x80 >> (i % 8)))
-           state->mines[i] = 1;
-    }
+       if (masked)
+           obfuscate_bitmap(bmp, wh, TRUE);
 
-    state->grid = snewn(wh, char);
-    memset(state->grid, -2, wh);
+       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);
-    /*
-     * 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);
+       ret = open_square(state, x, y);
+    }
 
     return state;
 }
@@ -2066,8 +2221,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 +2233,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,12 +2246,71 @@ 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 {
@@ -2111,8 +2332,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;
@@ -2153,6 +2374,7 @@ 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;
@@ -2175,6 +2397,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
        if (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);
            return ret;
        }
@@ -2200,6 +2423,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 &&
@@ -2223,7 +2447,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 +2472,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 +2576,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 +2645,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 +2786,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,6 +2799,9 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            }
        }
 
+    if (!state->layout->mines)
+       mines = state->layout->n;
+
     /*
      * Update the status bar.
      */
@@ -2577,7 +2810,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        if (state->dead) {
            sprintf(statusbar, "GAME OVER!");
        } 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);
        }
@@ -2594,6 +2830,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 +2851,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 +2878,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 +2891,5 @@ const struct game thegame = {
     game_anim_length,
     game_flash_length,
     game_wants_statusbar,
+    TRUE, game_timing_state,
 };