Oops! A trivial typo in obfuscate_bitmap() made the obfuscation
[sgt/puzzles] / mines.c
diff --git a/mines.c b/mines.c
index 4006294..5023faa 100644 (file)
--- a/mines.c
+++ b/mines.c
@@ -70,6 +70,7 @@ 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 */
     signed char *grid;                        /* player knowledge */
     /*
@@ -1791,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,6 +1810,69 @@ static char *new_mine_layout(int w, int h, int n, int x, int y, int unique,
     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) {
@@ -2062,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;
 
@@ -2156,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);
@@ -2179,7 +2246,43 @@ 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)
@@ -2271,6 +2374,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
            return NULL;
 
        ret = dup_game(from);
+        ret->just_used_solve = FALSE;
        ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
 
        return ret;
@@ -2293,6 +2397,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
        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;
        }
@@ -2318,6 +2423,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
 
            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 &&
@@ -2704,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);
        }
@@ -2721,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;
@@ -2766,7 +2878,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
-    FALSE, solve_game,
+    TRUE, solve_game,
     TRUE, game_text_format,
     new_ui,
     free_ui,