* 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.
*
* - think about configurably supporting question marks. Once,
* that is, we've thought about configurability in general!
return ret;
}
+static const struct game_params mines_presets[] = {
+ {9, 9, 10, TRUE},
+ {16, 16, 40, TRUE},
+ {30, 16, 99, TRUE},
+};
+
static int game_fetch_preset(int i, char **name, game_params **params)
{
game_params *ret;
char str[80];
- static const struct { int w, h, n; } values[] = {
- {9, 9, 10},
- {16, 16, 40},
- {30, 16, 99},
- };
- if (i < 0 || i >= lenof(values))
+ if (i < 0 || i >= lenof(mines_presets))
return FALSE;
ret = snew(game_params);
- ret->w = values[i].w;
- ret->h = values[i].h;
- ret->n = values[i].n;
- ret->unique = TRUE;
+ *ret = mines_presets[i];
sprintf(str, "%dx%d, %d mines", ret->w, ret->h, ret->n);
static char *validate_params(game_params *params)
{
- if (params->w <= 0 && params->h <= 0)
- return "Width and height must both be greater than zero";
- if (params->w <= 0)
- return "Width must be greater than zero";
- if (params->h <= 0)
- return "Height must be greater than zero";
+ /*
+ * Lower limit on grid size: each dimension must be at least 3.
+ * 1 is theoretically workable if rather boring, but 2 is a
+ * real problem: there is often _no_ way to generate a uniquely
+ * solvable 2xn Mines grid. You either run into two mines
+ * blocking the way and no idea what's behind them, or one mine
+ * and no way to know which of the two rows it's in. If the
+ * mine count is even you can create a soluble grid by packing
+ * all the mines at one end (so what when you hit a two-mine
+ * wall there are only as many covered squares left as there
+ * are mines); but if it's odd, you are doomed, because you
+ * _have_ to have a gap somewhere which you can't determine the
+ * position of.
+ */
+ if (params->w <= 2 || params->h <= 2)
+ return "Width and height must both be greater than two";
if (params->n > params->w * params->h - 9)
return "Too many mines for grid size";
std->next[i] = -1;
}
+typedef int (*open_cb)(void *, int, int);
+
static void known_squares(int w, int h, struct squaretodo *std,
- signed char *grid,
- int (*open)(void *ctx, int x, int y), void *openctx,
+ signed char *grid,
+ open_cb open, void *openctx,
int x, int y, int mask, int mine)
{
int xx, yy, bit;
* steps were required; the exact return value is the number of
* perturb calls.
*/
+
+typedef struct perturbations *(*perturb_cb) (void *, signed char *, int, int, int);
+
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,
- signed char *grid,
- int x, int y, int mask),
+ open_cb open,
+ perturb_cb perturb,
void *ctx, random_state *rs)
{
struct setstore *ss = ss_new();
static char *new_game_desc(game_params *params, random_state *rs,
game_aux_info **aux, int interactive)
{
+ /*
+ * We generate the coordinates of an initial click even if they
+ * aren't actually used. This has the effect of harmonising the
+ * random number usage between interactive and batch use: if
+ * you use `mines --generate' with an explicit random seed, you
+ * should get exactly the same results as if you type the same
+ * random seed into the interactive game and click in the same
+ * initial location. (Of course you won't get the same grid if
+ * you click in a _different_ initial location, but there's
+ * nothing to be done about that.)
+ */
+ int x = random_upto(rs, params->w);
+ int y = random_upto(rs, params->h);
+
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;
rsdesc = random_state_encode(rs);
desc = snewn(strlen(rsdesc) + 100, char);
- sprintf(desc, "r%d,%c,%s", params->n, params->unique ? 'u' : 'a', rsdesc);
+ sprintf(desc, "r%d,%c,%s", params->n, (char)(params->unique ? 'u' : 'a'), rsdesc);
sfree(rsdesc);
return 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;
}
wh = state->w * state->h;
state->layout = snew(struct mine_layout);
+ memset(state->layout, 0, sizeof(struct mine_layout));
state->layout->refcount = 1;
state->grid = snewn(wh, char);
}
ret = open_square(state, x, y);
+ sfree(bmp);
}
return 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;
}
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.
return ret;
}
- if (button == LEFT_RELEASE) {
+ if (button == LEFT_RELEASE || button == MIDDLE_RELEASE) {
ui->hx = ui->hy = -1;
ui->hradius = 0;
* 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;
(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;
}
}
bg = COL_BACKGROUND;
if (!ds->started) {
- int coords[6];
+ int coords[10];
draw_rect(fe, 0, 0,
TILE_SIZE * state->w + 2 * BORDER,
coords[1] = COORD(state->h) + OUTER_HIGHLIGHT_WIDTH - 1;
coords[2] = COORD(state->w) + OUTER_HIGHLIGHT_WIDTH - 1;
coords[3] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
- coords[4] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
- coords[5] = COORD(state->h) + OUTER_HIGHLIGHT_WIDTH - 1;
- draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
- draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT);
+ coords[4] = coords[2] - TILE_SIZE;
+ coords[5] = coords[3] + TILE_SIZE;
+ coords[8] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
+ coords[9] = COORD(state->h) + OUTER_HIGHLIGHT_WIDTH - 1;
+ coords[6] = coords[8] + TILE_SIZE;
+ coords[7] = coords[9] - TILE_SIZE;
+ draw_polygon(fe, coords, 5, TRUE, COL_HIGHLIGHT);
+ draw_polygon(fe, coords, 5, FALSE, COL_HIGHLIGHT);
coords[1] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
coords[0] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
- draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT);
- draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT);
+ draw_polygon(fe, coords, 5, TRUE, COL_LOWLIGHT);
+ draw_polygon(fe, coords, 5, FALSE, COL_LOWLIGHT);
ds->started = TRUE;
}
{
char statusbar[512];
if (state->dead) {
- sprintf(statusbar, "GAME OVER!");
+ sprintf(statusbar, "DEAD!");
} else if (state->won) {
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);
}
}
game_flash_length,
game_wants_statusbar,
TRUE, game_timing_state,
+ BUTTON_BEATS(LEFT_BUTTON, RIGHT_BUTTON),
};