Arrange that random seeds are as harmonised as they can reasonably
[sgt/puzzles] / mines.c
diff --git a/mines.c b/mines.c
index 42543cc..5afefe6 100644 (file)
--- a/mines.c
+++ b/mines.c
@@ -2,13 +2,6 @@
  * 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!
@@ -244,12 +237,22 @@ static game_params *custom_params(config_item *cfg)
 
 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";
 
@@ -2038,12 +2041,24 @@ 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, 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;
 
@@ -2142,22 +2157,11 @@ static int open_square(game_state *state, int x, int y)
 
     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;
     }
@@ -2439,6 +2443,7 @@ 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)
@@ -2446,6 +2451,7 @@ 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;
 }
@@ -2470,10 +2476,11 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
 
     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.
@@ -2503,7 +2510,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
        return ret;
     }
 
-    if (button == LEFT_RELEASE) {
+    if (button == LEFT_RELEASE || button == MIDDLE_RELEASE) {
        ui->hx = ui->hy = -1;
        ui->hradius = 0;
 
@@ -2517,19 +2524,22 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
         * 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;
@@ -2554,6 +2564,8 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                            (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;
            }
        }
@@ -2869,7 +2881,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        bg = COL_BACKGROUND;
 
     if (!ds->started) {
-        int coords[6];
+        int coords[10];
 
        draw_rect(fe, 0, 0,
                  TILE_SIZE * state->w + 2 * BORDER,
@@ -2885,15 +2897,19 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         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;
     }
@@ -2931,15 +2947,18 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     {
        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);
     }
 }
@@ -3015,4 +3034,5 @@ const struct game thegame = {
     game_flash_length,
     game_wants_statusbar,
     TRUE, game_timing_state,
+    BUTTON_BEATS(LEFT_BUTTON, RIGHT_BUTTON),
 };