Oops! A trivial typo in obfuscate_bitmap() made the obfuscation
[sgt/puzzles] / mines.c
diff --git a/mines.c b/mines.c
index 4016b44..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,
@@ -78,8 +70,9 @@ struct mine_layout {
 
 struct game_state {
     int w, h, n, dead, won;
+    int used_solve, just_used_solve;
     struct mine_layout *layout;               /* real mine positions */
-    char *grid;                               /* player knowledge */
+    signed char *grid;                        /* player knowledge */
     /*
      * Each item in the `grid' array is one of the following values:
      * 
@@ -263,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
@@ -566,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)
 {
@@ -633,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)
 {
@@ -1284,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;
@@ -1343,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;
@@ -1663,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;
 
@@ -1795,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++];
        }
 
        /*
@@ -1809,10 +1806,73 @@ static void obfuscate_bitmap(unsigned char *bmp, int bits, int decode)
 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 i, area;
 
+#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(w, h, n, x, y, unique, rs);
 
     if (game_desc) {
@@ -1853,24 +1913,30 @@ static char *new_mine_layout(int w, int h, int n, int x, int y, int unique,
 }
 
 static char *new_game_desc(game_params *params, random_state *rs,
-                          game_aux_info **aux)
+                          game_aux_info **aux, int interactive)
 {
-#ifdef PREOPENED
-    int x = random_upto(rs, params->w);
-    int y = random_upto(rs, params->h);
-    char *grid, *desc;
+    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);
-#else
-    char *rsdesc, *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;
-#endif
+       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)
@@ -2060,6 +2126,7 @@ static game_state *new_game(midend_data *me, 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;
 
@@ -2087,6 +2154,8 @@ static game_state *new_game(midend_data *me, game_params *params, char *desc)
        state->layout->me = me;
 
     } else {
+       state->layout->rs = NULL;
+       state->layout->me = NULL;
 
        state->layout->mines = snewn(wh, char);
        x = atoi(desc);
@@ -2152,6 +2221,8 @@ static game_state *dup_game(game_state *state)
     ret->n = state->n;
     ret->dead = state->dead;
     ret->won = state->won;
+    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);
@@ -2175,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 {
@@ -2202,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;
@@ -2244,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;
@@ -2266,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;
        }
@@ -2291,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 &&
@@ -2314,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:
@@ -2339,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;
@@ -2439,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 {
@@ -2507,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);
 
@@ -2671,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);
        }
@@ -2688,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;
@@ -2706,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
@@ -2726,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,
@@ -2739,4 +2891,5 @@ const struct game thegame = {
     game_anim_length,
     game_flash_length,
     game_wants_statusbar,
+    TRUE, game_timing_state,
 };