* 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.
*
- * - 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>
#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,
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:
*
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)
{
* 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)
{
* 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]);
*
* 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 */
* 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++) {
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);
/*
* 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];
*/
struct minectx {
- char *grid;
+ signed char *grid;
int w, h;
int sx, sy;
+ int allow_big_perturbs;
random_state *rs;
};
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
* 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;
* 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'
* 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])
}
/*
- * 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
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);
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
{
char *ret = snewn(w*h, char);
int success;
+ int ntries = 0;
do {
success = FALSE;
+ ntries++;
memset(ret, 0, w*h);
* 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;
ctx->sx = x;
ctx->sy = y;
ctx->rs = rs;
+ ctx->allow_big_perturbs = (ntries > 100);
while (1) {
memset(solvegrid, -2, w*h);
SHA_Final(&final, digest);
digestpos = 0;
}
- steps[i].targetstart[j] ^= digest[digestpos]++;
+ steps[i].targetstart[j] ^= digest[digestpos++];
}
/*
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) {
*/
int x = random_upto(rs, params->w);
int y = random_upto(rs, params->h);
- char *grid, *desc;
+ signed char *grid;
+ char *desc;
grid = new_mine_layout(params->w, params->h, params->n,
x, y, params->unique, rs, &desc);
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->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->layout->mines[yy*w+xx] &&
- state->grid[yy*w+xx] == -1) {
- state->grid[yy*w+xx] = 66;
- }
- }
state->grid[y*w+x] = 65;
return -1;
}
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->layout->me = me;
} else {
+ state->layout->rs = NULL;
+ state->layout->me = NULL;
state->layout->mines = snewn(wh, char);
x = atoi(desc);
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);
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)
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)
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;
}
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;
return NULL;
ret = dup_game(from);
+ ret->just_used_solve = FALSE;
ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
return ret;
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);
+ if (ret->dead)
+ ui->deaths++;
return ret;
}
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 &&
(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;
}
}
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:
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;
/*
* 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 {
* 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);
{
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);
}
}
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;
new_game,
dup_game,
free_game,
- FALSE, solve_game,
+ TRUE, solve_game,
TRUE, game_text_format,
new_ui,
free_ui,