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,
+ COL_WRONGNUMBER,
+ COL_CURSOR,
NCOLOURS
};
-#define TILE_SIZE 20
+#define PREFERRED_TILE_SIZE 20
+#define TILE_SIZE (ds->tilesize)
+#ifdef SMALL_SCREEN
+#define BORDER 8
+#else
#define BORDER (TILE_SIZE * 3 / 2)
-#define HIGHLIGHT_WIDTH 2
-#define OUTER_HIGHLIGHT_WIDTH 3
+#endif
+#define HIGHLIGHT_WIDTH (TILE_SIZE / 10)
+#define OUTER_HIGHLIGHT_WIDTH (BORDER / 10)
#define COORD(x) ( (x) * TILE_SIZE + BORDER )
#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
*/
int n, unique;
random_state *rs;
- midend_data *me; /* to give back the new game desc */
+ midend *me; /* to give back the new game desc */
};
struct game_state {
int w, h, n, dead, won;
- int used_solve, just_used_solve;
+ int used_solve;
struct mine_layout *layout; /* real mine positions */
signed char *grid; /* player knowledge */
/*
return ret;
}
+static const struct game_params mines_presets[] = {
+ {9, 9, 10, TRUE},
+ {9, 9, 35, TRUE},
+ {16, 16, 40, TRUE},
+ {16, 16, 99, TRUE},
+#ifndef SMALL_SCREEN
+ {30, 16, 99, TRUE},
+ {30, 16, 170, TRUE},
+#endif
+};
+
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);
sfree(params);
}
-static game_params *dup_params(game_params *params)
+static game_params *dup_params(const game_params *params)
{
game_params *ret = snew(game_params);
*ret = *params; /* structure copy */
}
}
-static char *encode_params(game_params *params, int full)
+static char *encode_params(const game_params *params, int full)
{
char ret[400];
int len;
return dupstr(ret);
}
-static config_item *game_configure(game_params *params)
+static config_item *game_configure(const game_params *params)
{
config_item *ret;
char buf[80];
return ret;
}
-static game_params *custom_params(config_item *cfg)
+static game_params *custom_params(const config_item *cfg)
{
game_params *ret = snew(game_params);
return ret;
}
-static char *validate_params(game_params *params)
+static char *validate_params(const game_params *params, int full)
{
- 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 (full && params->unique && (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";
/*
* Count the bits in a word. Only needs to cope with 16 bits.
*/
-static int bitcount16(int word)
+static int bitcount16(int inword)
{
+ unsigned int word = inword;
+
word = ((word & 0xAAAA) >> 1) + (word & 0x5555);
word = ((word & 0xCCCC) >> 2) + (word & 0x3333);
word = ((word & 0xF0F0) >> 4) + (word & 0x0F0F);
word = ((word & 0xFF00) >> 8) + (word & 0x00FF);
- return word;
+ return (int)word;
}
/*
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();
*/
struct minectx {
- signed char *grid;
+ char *grid;
int w, h;
int sx, sy;
int allow_big_perturbs;
* We bypass this bit if we're not after a unique grid.
*/
if (unique) {
- signed char *solvegrid = snewn(w*h, char);
+ signed char *solvegrid = snewn(w*h, signed char);
struct minectx actx, *ctx = &actx;
int solveret, prevret = -2;
return ret;
}
-/*
- * The Mines game descriptions contain the location of every mine,
- * and can therefore be used to cheat.
- *
- * It would be pointless to attempt to _prevent_ this form of
- * cheating by encrypting the description, since Mines is
- * open-source so anyone can find out the encryption key. However,
- * I think it is worth doing a bit of gentle obfuscation to prevent
- * _accidental_ spoilers: if you happened to note that the game ID
- * starts with an F, for example, you might be unable to put the
- * knowledge of those mines out of your mind while playing. So,
- * just as discussions of film endings are rot13ed to avoid
- * spoiling it for people who don't want to be told, we apply a
- * keyless, reversible, but visually completely obfuscatory masking
- * function to the mine bitmap.
- */
-static void obfuscate_bitmap(unsigned char *bmp, int bits, int decode)
+static char *describe_layout(char *grid, int area, int x, int y,
+ int obfuscate)
{
- int bytes, firsthalf, secondhalf;
- struct step {
- unsigned char *seedstart;
- int seedlen;
- unsigned char *targetstart;
- int targetlen;
- } steps[2];
- int i, j;
+ char *ret, *p;
+ unsigned char *bmp;
+ int i;
/*
- * My obfuscation algorithm is similar in concept to the OAEP
- * encoding used in some forms of RSA. Here's a specification
- * of it:
- *
- * + We have a `masking function' which constructs a stream of
- * pseudorandom bytes from a seed of some number of input
- * bytes.
- *
- * + We pad out our input bit stream to a whole number of
- * bytes by adding up to 7 zero bits on the end. (In fact
- * the bitmap passed as input to this function will already
- * have had this done in practice.)
- *
- * + We divide the _byte_ stream exactly in half, rounding the
- * half-way position _down_. So an 81-bit input string, for
- * example, rounds up to 88 bits or 11 bytes, and then
- * dividing by two gives 5 bytes in the first half and 6 in
- * the second half.
- *
- * + We generate a mask from the second half of the bytes, and
- * XOR it over the first half.
- *
- * + We generate a mask from the (encoded) first half of the
- * bytes, and XOR it over the second half. Any null bits at
- * the end which were added as padding are cleared back to
- * zero even if this operation would have made them nonzero.
- *
- * To de-obfuscate, the steps are precisely the same except
- * that the final two are reversed.
- *
- * Finally, our masking function. Given an input seed string of
- * bytes, the output mask consists of concatenating the SHA-1
- * hashes of the seed string and successive decimal integers,
- * starting from 0.
+ * Set up the mine bitmap and obfuscate it.
*/
+ 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);
+ }
+ if (obfuscate)
+ obfuscate_bitmap(bmp, area, FALSE);
- bytes = (bits + 7) / 8;
- firsthalf = bytes / 2;
- secondhalf = bytes - firsthalf;
-
- steps[decode ? 1 : 0].seedstart = bmp + firsthalf;
- steps[decode ? 1 : 0].seedlen = secondhalf;
- steps[decode ? 1 : 0].targetstart = bmp;
- steps[decode ? 1 : 0].targetlen = firsthalf;
-
- steps[decode ? 0 : 1].seedstart = bmp;
- steps[decode ? 0 : 1].seedlen = firsthalf;
- steps[decode ? 0 : 1].targetstart = bmp + firsthalf;
- steps[decode ? 0 : 1].targetlen = secondhalf;
-
- for (i = 0; i < 2; i++) {
- SHA_State base, final;
- unsigned char digest[20];
- char numberbuf[80];
- int digestpos = 20, counter = 0;
-
- SHA_Init(&base);
- SHA_Bytes(&base, steps[i].seedstart, steps[i].seedlen);
-
- for (j = 0; j < steps[i].targetlen; j++) {
- if (digestpos >= 20) {
- sprintf(numberbuf, "%d", counter++);
- final = base;
- SHA_Bytes(&final, numberbuf, strlen(numberbuf));
- SHA_Final(&final, digest);
- digestpos = 0;
- }
- steps[i].targetstart[j] ^= digest[digestpos++];
- }
-
- /*
- * Mask off the pad bits in the final byte after both steps.
- */
- if (bits % 8)
- bmp[bits / 8] &= 0xFF & (0xFF00 >> (bits % 8));
+ /*
+ * 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,%s", x, y,
+ obfuscate ? "m" : "u"); /* '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);
+
+ return ret;
}
static char *new_mine_layout(int w, int h, int n, int x, int y, int unique,
random_state *rs, char **game_desc)
{
- signed char *grid, *ret, *p;
- unsigned char *bmp;
- int i, area;
+ char *grid;
#ifdef TEST_OBFUSCATION
static int tested_obfuscation = FALSE;
grid = minegen(w, h, n, x, y, unique, rs);
- 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';
-
- sfree(bmp);
-
- *game_desc = ret;
- }
+ if (game_desc)
+ *game_desc = describe_layout(grid, w * h, x, y, TRUE);
return grid;
}
-static char *new_game_desc(game_params *params, random_state *rs,
- game_aux_info **aux, int interactive)
+static char *new_game_desc(const game_params *params, random_state *rs,
+ char **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 *grid;
char *desc;
grid = new_mine_layout(params->w, params->h, params->n,
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;
}
}
-static void game_free_aux_info(game_aux_info *aux)
-{
- assert(!"Shouldn't happen");
-}
-
-static char *validate_desc(game_params *params, char *desc)
+static char *validate_desc(const game_params *params, const char *desc)
{
int wh = params->w * params->h;
int x, y;
if (*desc == 'r') {
+ desc++;
if (!*desc || !isdigit((unsigned char)*desc))
return "No initial mine count in game description";
while (*desc && isdigit((unsigned char)*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')
+ if (*desc && isdigit((unsigned char)*desc)) {
+ 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' for `masked' or `u' for `unmasked', if present */
+ if (*desc == 'm' || *desc == 'u')
desc++;
/* now just check length of remainder */
if (strlen(desc) != (wh+3)/4)
* hasn't been generated yet. Generate it based on the
* initial click location.
*/
- char *desc;
+ char *desc, *privdesc;
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);
+ /*
+ * Find the trailing substring of the game description
+ * corresponding to just the mine layout; we will use this
+ * as our second `private' game ID for serialisation.
+ */
+ privdesc = desc;
+ while (*privdesc && isdigit((unsigned char)*privdesc)) privdesc++;
+ if (*privdesc == ',') privdesc++;
+ while (*privdesc && isdigit((unsigned char)*privdesc)) privdesc++;
+ if (*privdesc == ',') privdesc++;
+ assert(*privdesc == 'm');
+ midend_supersede_game_desc(state->layout->me, desc, privdesc);
sfree(desc);
random_free(state->layout->rs);
state->layout->rs = NULL;
return 0;
}
-static game_state *new_game(midend_data *me, game_params *params, char *desc)
+static game_state *new_game(midend *me, const game_params *params,
+ const char *desc)
{
game_state *state = snew(game_state);
- int i, wh, x, y, ret, masked;
+ int i, wh, x, y, masked;
unsigned char *bmp;
state->w = params->w;
state->h = params->h;
state->n = params->n;
state->dead = state->won = FALSE;
- state->used_solve = state->just_used_solve = FALSE;
+ state->used_solve = FALSE;
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);
+ state->grid = snewn(wh, signed char);
memset(state->grid, -2, wh);
if (*desc == 'r') {
} 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 && isdigit((unsigned char)*desc)) {
+ 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 */
+ } else {
+ x = y = -1;
+ }
if (*desc == 'm') {
masked = TRUE;
desc++;
} else {
+ if (*desc == 'u')
+ desc++;
/*
* We permit game IDs to be entered by hand without the
* masking transformation.
state->layout->mines[i] = 1;
}
- ret = open_square(state, x, y);
+ if (x >= 0 && y >= 0)
+ open_square(state, x, y);
+ sfree(bmp);
}
return state;
}
-static game_state *dup_game(game_state *state)
+static game_state *dup_game(const game_state *state)
{
game_state *ret = snew(game_state);
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);
+ ret->grid = snewn(ret->w * ret->h, signed char);
memcpy(ret->grid, state->grid, ret->w * ret->h);
return ret;
sfree(state);
}
-static game_state *solve_game(game_state *state, game_aux_info *aux,
- char **error)
+static char *solve_game(const game_state *state, const game_state *currstate,
+ const char *aux, char **error)
{
- /*
- * 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;
+ *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 dupstr("S");
+}
- return ret;
+static int game_can_format_as_text_now(const game_params *params)
+{
+ return TRUE;
}
-static char *game_text_format(game_state *state)
+static char *game_text_format(const game_state *state)
{
char *ret;
int x, y;
struct game_ui {
int hx, hy, hradius; /* for mouse-down highlights */
+ int validradius;
int flash_is_death;
- int deaths;
+ int deaths, completed;
+ int cur_x, cur_y, cur_visible;
};
-static game_ui *new_ui(game_state *state)
+static game_ui *new_ui(const game_state *state)
{
game_ui *ui = snew(game_ui);
ui->hx = ui->hy = -1;
- ui->hradius = 0;
+ ui->hradius = ui->validradius = 0;
ui->deaths = 0;
+ ui->completed = FALSE;
ui->flash_is_death = FALSE; /* *shrug* */
+ ui->cur_x = ui->cur_y = ui->cur_visible = 0;
return ui;
}
sfree(ui);
}
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
- int x, int y, int button)
+static char *encode_ui(const game_ui *ui)
+{
+ char buf[80];
+ /*
+ * The deaths counter and completion status need preserving
+ * across a serialisation.
+ */
+ sprintf(buf, "D%d", ui->deaths);
+ if (ui->completed)
+ strcat(buf, "C");
+ return dupstr(buf);
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+ int p= 0;
+ sscanf(encoding, "D%d%n", &ui->deaths, &p);
+ if (encoding[p] == 'C')
+ ui->completed = TRUE;
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+ const game_state *newstate)
+{
+ if (newstate->won)
+ ui->completed = TRUE;
+}
+
+struct game_drawstate {
+ int w, h, started, tilesize, bg;
+ signed char *grid;
+ /*
+ * Items in this `grid' array have all the same values as in
+ * the game_state grid, and in addition:
+ *
+ * - -10 means the tile was drawn `specially' as a result of a
+ * flash, so it will always need redrawing.
+ *
+ * - -22 and -23 mean the tile is highlighted for a possible
+ * click.
+ */
+ int cur_x, cur_y; /* -1, -1 for no cursor displayed. */
+};
+
+static char *interpret_move(const game_state *from, game_ui *ui,
+ const game_drawstate *ds,
+ int x, int y, int button)
{
- game_state *ret;
int cx, cy;
+ char buf[256];
if (from->dead || from->won)
return NULL; /* no further moves permitted */
- if (!IS_MOUSE_DOWN(button) && !IS_MOUSE_DRAG(button) &&
- !IS_MOUSE_RELEASE(button))
- return NULL;
-
cx = FROMCOORD(x);
cy = FROMCOORD(y);
- if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
- return NULL;
+
+ if (IS_CURSOR_MOVE(button)) {
+ move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0);
+ ui->cur_visible = 1;
+ return "";
+ }
+ if (IS_CURSOR_SELECT(button)) {
+ int v = from->grid[ui->cur_y * from->w + ui->cur_x];
+
+ if (!ui->cur_visible) {
+ ui->cur_visible = 1;
+ return "";
+ }
+ if (button == CURSOR_SELECT2) {
+ /* As for RIGHT_BUTTON; only works on covered square. */
+ if (v != -2 && v != -1)
+ return NULL;
+ sprintf(buf, "F%d,%d", ui->cur_x, ui->cur_y);
+ return dupstr(buf);
+ }
+ /* Otherwise, treat as LEFT_BUTTON, for a single square. */
+ if (v == -2 || v == -3) {
+ if (from->layout->mines &&
+ from->layout->mines[ui->cur_y * from->w + ui->cur_x])
+ ui->deaths++;
+
+ sprintf(buf, "O%d,%d", ui->cur_x, ui->cur_y);
+ return dupstr(buf);
+ }
+ cx = ui->cur_x; cy = ui->cur_y;
+ ui->validradius = 1;
+ goto uncover;
+ }
if (button == LEFT_BUTTON || button == LEFT_DRAG ||
button == MIDDLE_BUTTON || button == MIDDLE_DRAG) {
+ if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
+ return NULL;
+
/*
* Mouse-downs and mouse-drags just cause highlighting
* updates.
ui->hx = cx;
ui->hy = cy;
ui->hradius = (from->grid[cy*from->w+cx] >= 0 ? 1 : 0);
- return from;
+ if (button == LEFT_BUTTON)
+ ui->validradius = ui->hradius;
+ else if (button == MIDDLE_BUTTON)
+ ui->validradius = 1;
+ ui->cur_visible = 0;
+ return "";
}
if (button == RIGHT_BUTTON) {
+ if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
+ return NULL;
+
/*
* Right-clicking only works on a covered square, and it
* toggles between -1 (marked as mine) and -2 (not marked
from->grid[cy * from->w + cx] != -1)
return NULL;
- ret = dup_game(from);
- ret->just_used_solve = FALSE;
- ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
-
- return ret;
+ sprintf(buf, "F%d,%d", cx, cy);
+ return dupstr(buf);
}
if (button == LEFT_RELEASE || button == MIDDLE_RELEASE) {
/*
* At this stage we must never return NULL: we have adjusted
- * the ui, so at worst we return `from'.
+ * the ui, so at worst we return "".
*/
+ if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
+ return "";
/*
* Left-clicking on a covered square opens a tile. Not
*/
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;
+ from->grid[cy * from->w + cx] == -3) &&
+ ui->validradius == 0) {
+ /* Check if you've killed yourself. */
+ if (from->layout->mines && from->layout->mines[cy * from->w + cx])
+ ui->deaths++;
+
+ sprintf(buf, "O%d,%d", cx, cy);
+ return dupstr(buf);
}
+ goto uncover;
+ }
+ return NULL;
+uncover:
+ {
/*
* 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) {
+ if (from->grid[cy * from->w + cx] > 0 && ui->validradius == 1) {
int dy, dx, n;
/* Count mine markers. */
}
if (n == from->grid[cy * from->w + cx]) {
- ret = dup_game(from);
- ret->just_used_solve = FALSE;
+
+ /*
+ * Now see if any of the squares we're clearing
+ * contains a mine (which will happen iff you've
+ * incorrectly marked the mines around the clicked
+ * square). If so, we open _just_ those squares, to
+ * reveal as little additional information as we
+ * can.
+ */
+ char *p = buf;
+ char *sep = "";
+
+ for (dy = -1; dy <= +1; dy++)
+ for (dx = -1; dx <= +1; dx++)
+ if (cx+dx >= 0 && cx+dx < from->w &&
+ cy+dy >= 0 && cy+dy < from->h) {
+ if (from->grid[(cy+dy)*from->w+(cx+dx)] != -1 &&
+ from->layout->mines &&
+ from->layout->mines[(cy+dy)*from->w+(cx+dx)]) {
+ p += sprintf(p, "%sO%d,%d", sep, cx+dx, cy+dy);
+ sep = ";";
+ }
+ }
+
+ if (p > buf) {
+ ui->deaths++;
+ } else {
+ sprintf(buf, "C%d,%d", cx, cy);
+ }
+
+ return dupstr(buf);
+ }
+ }
+
+ return "";
+ }
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+ int cy, cx;
+ game_state *ret;
+
+ if (!strcmp(move, "S")) {
+ int yy, xx;
+
+ ret = dup_game(from);
+ if (!ret->dead) {
+ /*
+ * If the player is still alive at the moment of pressing
+ * Solve, expose the entire grid as if it were a completed
+ * solution.
+ */
+ 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;
+ }
+ }
+ } else {
+ /*
+ * If the player pressed Solve _after dying_, show a full
+ * corrections grid in the style of standard Minesweeper.
+ * Players who don't like Mines's behaviour on death of
+ * only showing the mine that killed you (so that in case
+ * of a typo you can undo and carry on without the rest of
+ * the grid being spoiled) can use this to get the display
+ * that ordinary Minesweeper would have given them.
+ */
+ for (yy = 0; yy < ret->h; yy++)
+ for (xx = 0; xx < ret->w; xx++) {
+ int pos = yy*ret->w+xx;
+ if ((ret->grid[pos] == -2 || ret->grid[pos] == -3) &&
+ ret->layout->mines[pos]) {
+ ret->grid[pos] = 64;
+ } else if (ret->grid[pos] == -1 &&
+ !ret->layout->mines[pos]) {
+ ret->grid[pos] = 66;
+ }
+ }
+ }
+ ret->used_solve = TRUE;
+
+ return ret;
+ } else {
+ ret = dup_game(from);
+
+ while (*move) {
+ if (move[0] == 'F' &&
+ sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+ cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+ ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
+ } else if (move[0] == 'O' &&
+ sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+ cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+ open_square(ret, cx, cy);
+ } else if (move[0] == 'C' &&
+ sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+ cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+ int dx, dy;
+
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;
+ } else {
+ free_game(ret);
+ return NULL;
}
+
+ while (*move && *move != ';') move++;
+ if (*move) move++;
}
- return from;
+ return ret;
}
-
- return NULL;
}
/* ----------------------------------------------------------------------
* Drawing routines.
*/
-struct game_drawstate {
- int w, h, started;
- signed char *grid;
- /*
- * Items in this `grid' array have all the same values as in
- * the game_state grid, and in addition:
- *
- * - -10 means the tile was drawn `specially' as a result of a
- * flash, so it will always need redrawing.
- *
- * - -22 and -23 mean the tile is highlighted for a possible
- * click.
- */
-};
-
-static void game_size(game_params *params, int *x, int *y)
+static void game_compute_size(const game_params *params, int tilesize,
+ int *x, int *y)
{
+ /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+ struct { int tilesize; } ads, *ds = &ads;
+ ads.tilesize = tilesize;
+
*x = BORDER * 2 + TILE_SIZE * params->w;
*y = BORDER * 2 + TILE_SIZE * params->h;
}
-static float *game_colours(frontend *fe, game_state *state, int *ncolours)
+static void game_set_size(drawing *dr, game_drawstate *ds,
+ const game_params *params, int tilesize)
+{
+ ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
{
float *ret = snewn(3 * NCOLOURS, float);
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_BACKGROUND2 * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 19.0F / 20.0F;
+ ret[COL_BACKGROUND2 * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 19.0F / 20.0F;
+ ret[COL_BACKGROUND2 * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 19.0F / 20.0F;
ret[COL_1 * 3 + 0] = 0.0F;
ret[COL_1 * 3 + 1] = 0.0F;
ret[COL_HIGHLIGHT * 3 + 1] = 1.0F;
ret[COL_HIGHLIGHT * 3 + 2] = 1.0F;
- ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2.0 / 3.0;
- ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2.0 / 3.0;
- ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2.0 / 3.0;
+ ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2.0F / 3.0F;
+ ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2.0F / 3.0F;
+ ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2.0F / 3.0F;
+
+ ret[COL_WRONGNUMBER * 3 + 0] = 1.0F;
+ ret[COL_WRONGNUMBER * 3 + 1] = 0.6F;
+ ret[COL_WRONGNUMBER * 3 + 2] = 0.6F;
+
+ /* Red tinge to a light colour, for the cursor. */
+ ret[COL_CURSOR * 3 + 0] = ret[COL_HIGHLIGHT * 3 + 0];
+ ret[COL_CURSOR * 3 + 1] = ret[COL_HIGHLIGHT * 3 + 0] / 2.0F;
+ ret[COL_CURSOR * 3 + 2] = ret[COL_HIGHLIGHT * 3 + 0] / 2.0F;
*ncolours = NCOLOURS;
return ret;
}
-static game_drawstate *game_new_drawstate(game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
{
struct game_drawstate *ds = snew(struct game_drawstate);
ds->w = state->w;
ds->h = state->h;
ds->started = FALSE;
- ds->grid = snewn(ds->w * ds->h, char);
+ ds->tilesize = 0; /* not decided yet */
+ ds->grid = snewn(ds->w * ds->h, signed char);
+ ds->bg = -1;
+ ds->cur_x = ds->cur_y = -1;
memset(ds->grid, -99, ds->w * ds->h);
return ds;
}
-static void game_free_drawstate(game_drawstate *ds)
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
{
sfree(ds->grid);
sfree(ds);
}
-static void draw_tile(frontend *fe, int x, int y, int v, int bg)
+static void draw_tile(drawing *dr, game_drawstate *ds,
+ int x, int y, int v, int bg)
{
if (v < 0) {
int coords[12];
/*
* Omit the highlights in this case.
*/
- draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+ draw_rect(dr, 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);
+ draw_line(dr, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
+ draw_line(dr, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
} else {
/*
* Draw highlights to indicate the square is covered.
coords[3] = y;
coords[4] = x;
coords[5] = y + TILE_SIZE - 1;
- draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT ^ hl);
- draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT ^ hl);
+ draw_polygon(dr, coords, 3, COL_LOWLIGHT ^ hl, COL_LOWLIGHT ^ hl);
coords[0] = x;
coords[1] = y;
- draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT ^ hl);
- draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT ^ hl);
+ draw_polygon(dr, coords, 3, COL_HIGHLIGHT ^ hl,
+ COL_HIGHLIGHT ^ hl);
- draw_rect(fe, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+ draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
bg);
}
* Draw a flag.
*/
#define SETCOORD(n, dx, dy) do { \
- coords[(n)*2+0] = x + TILE_SIZE * (dx); \
- coords[(n)*2+1] = y + TILE_SIZE * (dy); \
+ coords[(n)*2+0] = x + (int)(TILE_SIZE * (dx)); \
+ coords[(n)*2+1] = y + (int)(TILE_SIZE * (dy)); \
} while (0)
- SETCOORD(0, 0.6, 0.35);
- SETCOORD(1, 0.6, 0.7);
- SETCOORD(2, 0.8, 0.8);
- SETCOORD(3, 0.25, 0.8);
- SETCOORD(4, 0.55, 0.7);
- SETCOORD(5, 0.55, 0.35);
- draw_polygon(fe, coords, 6, TRUE, COL_FLAGBASE);
- draw_polygon(fe, coords, 6, FALSE, COL_FLAGBASE);
-
- SETCOORD(0, 0.6, 0.2);
- SETCOORD(1, 0.6, 0.5);
- SETCOORD(2, 0.2, 0.35);
- draw_polygon(fe, coords, 3, TRUE, COL_FLAG);
- draw_polygon(fe, coords, 3, FALSE, COL_FLAG);
+ SETCOORD(0, 0.6F, 0.35F);
+ SETCOORD(1, 0.6F, 0.7F);
+ SETCOORD(2, 0.8F, 0.8F);
+ SETCOORD(3, 0.25F, 0.8F);
+ SETCOORD(4, 0.55F, 0.7F);
+ SETCOORD(5, 0.55F, 0.35F);
+ draw_polygon(dr, coords, 6, COL_FLAGBASE, COL_FLAGBASE);
+
+ SETCOORD(0, 0.6F, 0.2F);
+ SETCOORD(1, 0.6F, 0.5F);
+ SETCOORD(2, 0.2F, 0.35F);
+ draw_polygon(dr, coords, 3, COL_FLAG, COL_FLAG);
#undef SETCOORD
} else if (v == -3) {
/*
* Draw a question mark.
*/
- draw_text(fe, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
+ draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
FONT_VARIABLE, TILE_SIZE * 6 / 8,
ALIGN_VCENTRE | ALIGN_HCENTRE,
COL_QUERY, "?");
* Exception is that for value 65 (mine we've just trodden
* on), we clear the square to COL_BANG.
*/
- draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+ if (v & 32) {
+ bg = COL_WRONGNUMBER;
+ v &= ~32;
+ }
+ draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
(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);
+ draw_line(dr, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
+ draw_line(dr, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
if (v > 0 && v <= 8) {
/*
char str[2];
str[0] = v + '0';
str[1] = '\0';
- draw_text(fe, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
+ draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
FONT_VARIABLE, TILE_SIZE * 7 / 8,
ALIGN_VCENTRE | ALIGN_HCENTRE,
(COL_1 - 1) + v, str);
} else if (v >= 64) {
/*
* Mark a mine.
- *
- * FIXME: this could be done better!
*/
-#if 0
- draw_text(fe, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
- FONT_VARIABLE, TILE_SIZE * 7 / 8,
- ALIGN_VCENTRE | ALIGN_HCENTRE,
- COL_MINE, "*");
-#else
{
int cx = x + TILE_SIZE / 2;
int cy = y + TILE_SIZE / 2;
int r = TILE_SIZE / 2 - 3;
- int coords[4*5*2];
- int xdx = 1, xdy = 0, ydx = 0, ydy = 1;
- int tdx, tdy, i;
-
- for (i = 0; i < 4*5*2; i += 5*2) {
- coords[i+2*0+0] = cx - r/6*xdx + r*4/5*ydx;
- coords[i+2*0+1] = cy - r/6*xdy + r*4/5*ydy;
- coords[i+2*1+0] = cx - r/6*xdx + r*ydx;
- coords[i+2*1+1] = cy - r/6*xdy + r*ydy;
- coords[i+2*2+0] = cx + r/6*xdx + r*ydx;
- coords[i+2*2+1] = cy + r/6*xdy + r*ydy;
- coords[i+2*3+0] = cx + r/6*xdx + r*4/5*ydx;
- coords[i+2*3+1] = cy + r/6*xdy + r*4/5*ydy;
- coords[i+2*4+0] = cx + r*3/5*xdx + r*3/5*ydx;
- coords[i+2*4+1] = cy + r*3/5*xdy + r*3/5*ydy;
-
- tdx = ydx;
- tdy = ydy;
- ydx = xdx;
- ydy = xdy;
- xdx = -tdx;
- xdy = -tdy;
- }
-
- draw_polygon(fe, coords, 5*4, TRUE, COL_MINE);
- draw_polygon(fe, coords, 5*4, FALSE, COL_MINE);
- draw_rect(fe, cx-r/3, cy-r/3, r/3, r/4, COL_HIGHLIGHT);
+ draw_circle(dr, cx, cy, 5*r/6, COL_MINE, COL_MINE);
+ draw_rect(dr, cx - r/6, cy - r, 2*(r/6)+1, 2*r+1, COL_MINE);
+ draw_rect(dr, cx - r, cy - r/6, 2*r+1, 2*(r/6)+1, COL_MINE);
+ draw_rect(dr, cx-r/3, cy-r/3, r/3, r/4, COL_HIGHLIGHT);
}
-#endif
if (v == 66) {
/*
*/
int dx;
for (dx = -1; dx <= +1; dx++) {
- draw_line(fe, x + 3 + dx, y + 2,
+ draw_line(dr, x + 3 + dx, y + 2,
x + TILE_SIZE - 3 + dx,
y + TILE_SIZE - 2, COL_CROSS);
- draw_line(fe, x + TILE_SIZE - 3 + dx, y + 2,
+ draw_line(dr, x + TILE_SIZE - 3 + dx, y + 2,
x + 3 + dx, y + TILE_SIZE - 2,
COL_CROSS);
}
}
}
- draw_update(fe, x, y, TILE_SIZE, TILE_SIZE);
+ draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
}
-static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
- game_state *state, int dir, game_ui *ui,
- float animtime, float flashtime)
+static void game_redraw(drawing *dr, game_drawstate *ds,
+ const game_state *oldstate, const game_state *state,
+ int dir, const game_ui *ui,
+ float animtime, float flashtime)
{
int x, y;
int mines, markers, bg;
+ int cx = -1, cy = -1, cmoved;
if (flashtime) {
- int frame = (flashtime / FLASH_FRAME);
+ int frame = (int)(flashtime / FLASH_FRAME);
if (frame % 2)
bg = (ui->flash_is_death ? COL_BACKGROUND : COL_LOWLIGHT);
else
if (!ds->started) {
int coords[10];
- draw_rect(fe, 0, 0,
+ draw_rect(dr, 0, 0,
TILE_SIZE * state->w + 2 * BORDER,
TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
- draw_update(fe, 0, 0,
+ draw_update(dr, 0, 0,
TILE_SIZE * state->w + 2 * BORDER,
TILE_SIZE * state->h + 2 * BORDER);
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);
+ draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
coords[1] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
coords[0] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
- draw_polygon(fe, coords, 5, TRUE, COL_LOWLIGHT);
- draw_polygon(fe, coords, 5, FALSE, COL_LOWLIGHT);
+ draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
ds->started = TRUE;
}
+ if (ui->cur_visible) cx = ui->cur_x;
+ if (ui->cur_visible) cy = ui->cur_y;
+ cmoved = (cx != ds->cur_x || cy != ds->cur_y);
+
/*
* Now draw the tiles. Also in this loop, count up the number
* of mines and mine markers.
mines = markers = 0;
for (y = 0; y < ds->h; y++)
for (x = 0; x < ds->w; x++) {
- int v = state->grid[y*ds->w+x];
+ int v = state->grid[y*ds->w+x], cc = 0;
if (v == -1)
markers++;
if (state->layout->mines && state->layout->mines[y*ds->w+x])
mines++;
+ if (v >= 0 && v <= 8) {
+ /*
+ * Count up the flags around this tile, and if
+ * there are too _many_, highlight the tile.
+ */
+ int dx, dy, flags = 0;
+
+ for (dy = -1; dy <= +1; dy++)
+ for (dx = -1; dx <= +1; dx++) {
+ int nx = x+dx, ny = y+dy;
+ if (nx >= 0 && nx < ds->w &&
+ ny >= 0 && ny < ds->h &&
+ state->grid[ny*ds->w+nx] == -1)
+ flags++;
+ }
+
+ if (flags > v)
+ v |= 32;
+ }
+
if ((v == -2 || v == -3) &&
(abs(x-ui->hx) <= ui->hradius && abs(y-ui->hy) <= ui->hradius))
v -= 20;
- if (ds->grid[y*ds->w+x] != v || bg != COL_BACKGROUND) {
- draw_tile(fe, COORD(x), COORD(y), v, bg);
- ds->grid[y*ds->w+x] = (bg == COL_BACKGROUND ? v : -10);
+ if (cmoved && /* if cursor has moved, force redraw of curr and prev pos */
+ ((x == cx && y == cy) || (x == ds->cur_x && y == ds->cur_y)))
+ cc = 1;
+
+ if (ds->grid[y*ds->w+x] != v || bg != ds->bg || cc) {
+ draw_tile(dr, ds, COORD(x), COORD(y), v,
+ (x == cx && y == cy) ? COL_CURSOR : bg);
+ ds->grid[y*ds->w+x] = v;
}
}
+ ds->bg = bg;
+ ds->cur_x = cx; ds->cur_y = cy;
if (!state->layout->mines)
mines = state->layout->n;
if (ui->deaths)
sprintf(statusbar + strlen(statusbar),
" Deaths: %d", ui->deaths);
- status_bar(fe, statusbar);
+ status_bar(dr, statusbar);
}
}
-static float game_anim_length(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui)
+static float game_anim_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
{
return 0.0F;
}
-static float game_flash_length(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui)
+static float game_flash_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
{
if (oldstate->used_solve || newstate->used_solve)
return 0.0F;
return 0.0F;
}
-static int game_wants_statusbar(void)
+static int game_status(const game_state *state)
{
- return TRUE;
+ /*
+ * We report the game as lost only if the player has used the
+ * Solve function to reveal all the mines. Otherwise, we assume
+ * they'll undo and continue play.
+ */
+ return state->won ? (state->used_solve ? -1 : +1) : 0;
}
-static int game_timing_state(game_state *state)
+static int game_timing_state(const game_state *state, game_ui *ui)
{
- if (state->dead || state->won || !state->layout->mines)
+ if (state->dead || state->won || ui->completed || !state->layout->mines)
return FALSE;
return TRUE;
}
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
#ifdef COMBINED
#define thegame mines
#endif
const struct game thegame = {
- "Mines", "games.mines",
+ "Mines", "games.mines", "mines",
default_params,
game_fetch_preset,
decode_params,
TRUE, game_configure, custom_params,
validate_params,
new_game_desc,
- game_free_aux_info,
validate_desc,
new_game,
dup_game,
free_game,
TRUE, solve_game,
- TRUE, game_text_format,
+ TRUE, game_can_format_as_text_now, game_text_format,
new_ui,
free_ui,
- make_move,
- game_size,
+ encode_ui,
+ decode_ui,
+ game_changed_state,
+ interpret_move,
+ execute_move,
+ PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
game_colours,
game_new_drawstate,
game_free_drawstate,
game_redraw,
game_anim_length,
game_flash_length,
- game_wants_statusbar,
+ game_status,
+ FALSE, FALSE, game_print_size, game_print,
+ TRUE, /* wants_statusbar */
TRUE, game_timing_state,
- BUTTON_BEATS(LEFT_BUTTON, RIGHT_BUTTON),
+ BUTTON_BEATS(LEFT_BUTTON, RIGHT_BUTTON) | REQUIRE_RBUTTON,
};
+
+#ifdef STANDALONE_OBFUSCATOR
+
+/*
+ * Vaguely useful stand-alone program which translates between
+ * obfuscated and clear Mines game descriptions. Pass in a game
+ * description on the command line, and if it's clear it will be
+ * obfuscated and vice versa. The output text should also be a
+ * valid game ID describing the same game. Like this:
+ *
+ * $ ./mineobfusc 9x9:4,4,mb071b49fbd1cb6a0d5868
+ * 9x9:4,4,004000007c00010022080
+ * $ ./mineobfusc 9x9:4,4,004000007c00010022080
+ * 9x9:4,4,mb071b49fbd1cb6a0d5868
+ */
+
+int main(int argc, char **argv)
+{
+ game_params *p;
+ game_state *s;
+ char *id = NULL, *desc, *err;
+ int y, x;
+
+ while (--argc > 0) {
+ char *p = *++argv;
+ if (*p == '-') {
+ fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+ return 1;
+ } else {
+ id = p;
+ }
+ }
+
+ if (!id) {
+ fprintf(stderr, "usage: %s <game_id>\n", argv[0]);
+ return 1;
+ }
+
+ desc = strchr(id, ':');
+ if (!desc) {
+ fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+ return 1;
+ }
+ *desc++ = '\0';
+
+ p = default_params();
+ decode_params(p, id);
+ err = validate_desc(p, desc);
+ if (err) {
+ fprintf(stderr, "%s: %s\n", argv[0], err);
+ return 1;
+ }
+ s = new_game(NULL, p, desc);
+
+ x = atoi(desc);
+ while (*desc && *desc != ',') desc++;
+ if (*desc) desc++;
+ y = atoi(desc);
+ while (*desc && *desc != ',') desc++;
+ if (*desc) desc++;
+
+ printf("%s:%s\n", id, describe_layout(s->layout->mines,
+ p->w * p->h,
+ x, y,
+ (*desc != 'm')));
+
+ return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */