Skip to content

Minesweeper: act on what is proved, price every guess

In Minesweeper you never see the mines, only numbers that say how many mines touch a cell. Sometimes the numbers prove a cell is safe; sometimes nothing on the board is safe for certain and any click is a gamble. This page shows how the library's grounded pattern (propose, check, then act, degrade or abstain) handles that hidden information: the gated player clicks a cell only when a check proves it safe, falls back to a proved cell when the proposal was a gamble, and when no proof exists it takes the least risky guess and writes down exactly what that guess risked.

This page is for anyone who wants to see act, abstain and degrade on a game with hidden information, and for engineers who want a template for gates whose checks are proofs about what a player cannot see. You need no Minesweeper experience; the next section explains the game.

Try it in your browser

The board below plays this game's recorded example with no download; Play live runs this package itself in your browser, in Pyodide (about 7 MB, downloaded once), and the play page has every setting.

Minesweeper runs here when JavaScript is on; the play page has it too, and the commands below run it in a terminal.

Run it

uv run grounded-decision examples games_minesweeper   # the report below
make watch-minesweeper                                # the gated player, live
make play-minesweeper                                 # you propose, the gate checks
make audit-minesweeper                                # every move's exact risk; regime table

Source: src/grounded_decision/examples/games/minesweeper_solver.py (exact reasoning: proofs and probabilities), minesweeper_rules.py (the game, its worlds, the board for the viewers) and minesweeper.py (the players, the gate, the report). Tests: tests/test_games_minesweeper.py.

The game in three paragraphs

A grid of closed cells hides a known number of mines: beginner is 9 x 9 with 10 mines, intermediate 16 x 16 with 40, expert 30 x 16 with 99 (--size beginner, --size intermediate, --size expert; any other --size WxH gets about one mine per 6.4 cells, and --mines N sets the count). A move reveals a closed cell or flags it (a note to yourself that it is a mine). Revealing a mine loses. Revealing a safe cell shows the number of mines among its up to eight neighbours; a 0 opens its neighbours too. You win when every safe cell is open.

The first click is always safe: the mines are placed after it, uniformly at random among the other cells (the rule of the classic Windows game, and of the published results compared below). With --first-click opening its neighbours are mine-free too, so the first click always opens an area (it shows a 0). Cells are named like a spreadsheet: column letter, then row number from the top (C5).

In the terminal a closed cell is ■ (# in plain text), a flag ⚑ (F), an opened cell its number in colour (· or . for a 0); when the game is lost the mines appear as ✱ (*) and the one revealed as ✖ (X).

What the player knows, exactly

Everything a player knows is the numbers and the mine count. Call a placement of the mines on the closed cells consistent if it has the right number of mines and agrees with every number shown. Then:

  • A cell is proved safe if no consistent layout puts a mine on it, and proved a mine if every consistent layout does. Nothing more can be known: every other cell can hold a mine.
  • If the mines were placed at random (the standard game), every consistent layout is equally likely given what the player sees, so the chance that a cell holds a mine is the share of consistent layouts with a mine there. This is exact, and it assumes the random world.

The solver computes both exactly. It counts consistent layouts over the closed cells next to numbers (splitting them into independent groups and branching only where it must), multiplies by the number of ways to place the remaining mines on the other closed cells, and reports every probability as a fraction. On beginner boards a game takes about two milliseconds; on expert boards about forty.

The gate: act, degrade, abstain

The gated player's proposer is the pair of rules a person learns first: a number that already touches as many proved mines as it shows makes its other closed neighbours safe; a number with exactly as many closed neighbours left as mines missing makes them all mines. When no rule applies, the proposer guesses at random.

Every proposal faces one check, proved_safe (for a flag, proved_mine). It passes only on a proof: a single-cell rule naming the number that forces it, or the exact count. Then:

Situation Verdict What is played What the record says
The proposal is proved safe act the proposal which number proved it, or how many layouts were counted
The proposal is not proved, but another cell is degrade a proved-safe cell (or a flag on a proved mine) the proposal's exact risk, and the proof for the cell played
Nothing on the board is proved safe degrade the least risky cell its exact mine probability, over how many layouts, and that this assumes the random world
Nothing is proved safe, and a person proposed (--you --guarded) abstain the person's own cell the person's risk and the least risky alternative

Abstain here means what it means everywhere in this library: this gate will not decide. A person proposing gets the decision back when no proof is possible; the gated player, which has no person behind it, falls back to the least risky guess, a fallback named in advance.

A game, move by move

From uv run grounded-decision examples games_minesweeper (seed 26, beginner). Every move that was not a plain act:

    1. ACT      played reveal A1
       the first click is always safe (mines are placed after it)
    2. DEGRADE  proposed reveal I3, played reveal I1
       proposal: not proved safe: P(mine at I3) = 9/77 ≈ 0.117: 56,567,650,725 of 483,967,678,425 consistent layouts; assumes the random world (every consistent layout equally likely)
       GUESS: no closed cell is proved safe in any of the 483,967,678,425 consistent layouts; least risky: P(mine at I1) = 9/77 ≈ 0.117: 56,567,650,725 of 483,967,678,425 consistent layouts; assumes the random world (every consistent layout equally likely)
    3. DEGRADE  proposed reveal A5, played reveal G4
       proposal: not proved safe: P(mine at A5) = 351/3599 ≈ 0.098: 34,231,977 of 350,999,673 consistent layouts; assumes the random world (every consistent layout equally likely)
       proved safe: no mine there in any of the 350,999,673 layouts consistent with the board and the 10-mine count (exact count); 1 closed cell(s) proved safe now
    4. DEGRADE  proposed reveal D8, played flag F3
       proposal: not proved safe: P(mine at D8) = 219/2233 ≈ 0.098: 17,301,438 of 176,411,466 consistent layouts; assumes the random world (every consistent layout equally likely)
       proved mine: a mine in every one of the 176,411,466 consistent layouts (exact count)
    5. DEGRADE  proposed reveal C2, played reveal F4
       proposal: not proved safe: P(mine at C2) = 219/2233 ≈ 0.098: 17,301,438 of 176,411,466 consistent layouts; assumes the random world (every consistent layout equally likely)
       GUESS: no closed cell is proved safe in any of the 176,411,466 consistent layouts; least risky: P(mine at F4) = 6/77 ≈ 0.078: 13,746,348 of 176,411,466 consistent layouts; assumes the random world (every consistent layout equally likely)
   ...
   11. DEGRADE  proposed reveal H4, played reveal F2
       proposal: H4 is a proved MINE: a mine in every one of the 66,045 consistent layouts (exact count)
       proved safe: no mine there in any of the 66,045 layouts consistent with the board and the 10-mine count (exact count); 8 closed cell(s) proved safe now
  (15 moves acted on a proposal proved safe or proved a mine.)

Read it as a record of what was known. Move 2 is a forced guess: the first click showed a 1, nothing is proved, so the gate takes the least risky cell (a corner, about 1 in 8.6) and says so. At move 3 the rules see nothing, but the exact count proves G4 safe, so the random guess at A5 (about a 1-in-10 risk) is replaced by a certain move. Move 11 is the gate earning its keep: the proposer's random guess H4 was a proved mine; the gate played a proved-safe cell instead. The game is won after 22 moves with two guesses, each with its risk on the record.

The live view shows the same record beside the board (make watch-minesweeper): a green ACT, an amber OVERRIDE with the rejected and chosen cells flashed, the check with its explanation, and risk= and proof= for every move.

The players

Player Regime How it moves
random baseline a uniformly random closed cell
rules baseline the two single-cell rules; otherwise a random guess; opens in the corner
solver probabilistic a proved-safe cell if any; flags proved mines; otherwise the least risky cell (ties: fewest closed neighbours)
gated worst-case rules proposes; proved_safe checks; the fallback is solver's move
clairvoyant benchmark sees the mines; reveals only safe cells

grounded-decision play minesweeper --explain AGENT prints the full description of each.

Worlds: who places the mines

The world decides where the mines are (--world MODE). It is a separate question from what a player assumes (its regime):

World What it does
random the standard game: a uniform layout after the first click
no_guess a board that can be cleared from the first click without any guess; the generator keeps the first uniform layout that the complete deduction clears (the first click opens an area), and that deduction is the board's proof
adversarial mines are decided lazily: a revealed cell that is not proved safe is a mine whenever some consistent layout allows it, so every guess loses
kind the opposite: a revealed cell is safe whenever some consistent layout allows it

The lazy worlds keep the board honest: they only switch to another layout consistent with every number already shown. MODE:p applies a mode to each reveal with probability p (no_guess:p: to the whole board).

What is proved

Proofs are in the docstring of minesweeper_rules.py; each statement is also checked on many seeds in tests/test_games_minesweeper.py.

  1. Probabilities are exact for the random world. Counts and probabilities match a brute-force enumeration on hundreds of small boards; the single-cell rules never contradict them.
  2. Revealing proved-safe cells first never hurts. Knowing more can only shrink the set of consistent layouts, so a proved-safe cell stays proved safe. A player that first opens every proved-safe cell and then does what any other strategy would do reveals a mine exactly when that strategy does. So the worst-case gate gives up nothing.
  3. solver and gated win exactly the same games in the random world: same first click, the same set of cells opened before each guess (the closure of proved-safe reveals does not depend on order), the same guess. Checked: identical on every seed.
  4. Adversarial world: gated wins if and only if the board can be cleared without guessing, and no player does better. Any reveal of a cell that is not proved safe loses there, and gated reveals only proved-safe cells while any exist. Checked: on 200 seeds gated won exactly the 118 boards whose first-click layout clears without a guess.
  5. no_guess and kind worlds: gated never loses.

What is measured

Comparisons over the same 200 seeds, with exact 95% Clopper–Pearson intervals for the win rate. grounded-decision compare minesweeper --agents random,rules,solver,gated,clairvoyant --seeds 200:

  agent                    games  won drawn lost  cut  points    moves    score overrides  win rate (95% interval)
  clairvoyant                200  200     0    0    0   200.0     15.6    100.0         0  1.00 [0.98, 1.00]
  solver                     200  186     0   14    0   186.0     20.2     95.5         0  0.93 [0.89, 0.96]
  gated                      200  186     0   14    0   186.0     21.6     95.5       342  0.93 [0.89, 0.96]
  rules                      200  148     0   52    0   148.0     18.0     84.4         0  0.74 [0.67, 0.80]
  random                     200    0     0  200    0     0.0      4.8     53.1         0  0.00 [0.00, 0.02]

score is the share of safe cells opened. overrides counts the gated player's degrades: proposals replaced by a proved cell, by a flag on a proved mine, or by the least risky guess.

The same comparison in every world (grounded-decision compare minesweeper --agents random,solver,gated --world random,no_guess,adversarial,kind --seeds 200), win rates of solver and gated (identical in every world): random 0.93 [0.89, 0.96], no_guess 1.00 [0.98, 1.00], adversarial 0.59 [0.52, 0.66], kind 1.00 [0.98, 1.00]. random wins none in any world (even the kind world explodes it on a proved mine sooner or later). Outside the random world the command adds: "assumption not met: probabilistic agents model the random world".

Against published solvers

Tu, Li, Chen, Zu and Gu, "Exploring Efficient Strategies for Minesweeper" (AAAI-17 Workshop on What's Next for AI in Games), use the same rules (first click safe, mines uniform over the other cells) and report success rates for their best heuristic combination, PSEQ, over many simulated games: 81.6% on their beginner level (8 x 8, 10 mines), 78.1% on intermediate and 39.6% on expert. Our solver is simpler (the least risky cell, one step at a time), measured on 200 seeds each:

Board Command solver wins Published (PSEQ)
8 x 8, 10 mines compare minesweeper --agents solver,gated --size 8x8 --mines 10 --seeds 200 0.80 [0.73, 0.85] 0.816
16 x 16, 40 mines compare minesweeper --agents rules,solver,gated --size 16x16 --seeds 200 0.73 [0.66, 0.79] 0.781
30 x 16, 99 mines compare minesweeper --agents rules,solver,gated --size 30x16 --seeds 200 0.38 [0.31, 0.45] 0.396

Every published rate lies inside our interval; 200 games cannot tell whether PSEQ's look-ahead tie-breaking beats ours by a few points (it probably does on intermediate). On intermediate and expert the rules player collapses (0.40 and 0.01): its random guesses ignore the exact risk and even land on proved mines.

The regime table

make audit-minesweeper ends with the information-regime table (beginner, 200 seeds; real output):

  agent [regime]                           opponent         games       W/D/L  rate (95% interval)        guesses/game
  clairvoyant [clairvoyant (benchmark)]    random world       200     200/0/0  win 1.000 [0.982, 1.000]              0
  solver [probabilistic]                   random world       200    186/0/14  win 0.930 [0.885, 0.961]           0.59
  gated [worst-case]                       random world       200    186/0/14  win 0.930 [0.885, 0.961]           0.59
  solver [probabilistic]                   adversarial world   200    118/0/82  win 0.590 [0.518, 0.659]           0.41
  gated [worst-case]                       adversarial world   200    118/0/82  win 0.590 [0.518, 0.659]           0.41

The cost of not seeing the future (difference in rate, simultaneous 95% interval):
  vs random world: clairvoyant - solver = +0.070 [+0.014, +0.121]
The cost of insuring against the worst case (difference in rate, simultaneous 95% interval):
  vs random world: solver - gated = +0.000 [-0.086, +0.086]
The price of an adversarial world (difference in rate, simultaneous 95% interval):
  vs random world: gated - gated = +0.340 [+0.210, +0.456]

Hidden information costs about 7 points of win rate on beginner (the games lost to forced guesses). Insuring against the worst case costs exactly nothing (theorem 2). An adversary that places mines where you guess takes the 34% of games that needed a guess.

What the gate does not do

  • It does not make guesses safe. When nothing is proved, the record says so and states the risk; the game can still be lost (14 of 200 beginner games).
  • Its probabilities are statements about the random world. In the adversarial world the same number is not a risk: there every guess loses, and the record says "assumption not met".
  • The least risky cell is chosen one move at a time. It is not the strategy that maximises the chance of winning the whole game (which can prefer a slightly riskier cell that reveals more); the published look-ahead heuristics above gain a little that way.
  • The exact count has a node budget. On the standard boards it has never come near it; if it were exceeded the solver raises an error rather than guess silently.

Try it

uv run grounded-decision play minesweeper                                   # gated, beginner, live
uv run grounded-decision play minesweeper --size 30x16 --agents gated       # expert
uv run grounded-decision play minesweeper --agents you --guarded            # c5 reveals, f c5 flags, q quits
uv run grounded-decision play minesweeper --world adversarial --agents gated
uv run grounded-decision compare minesweeper --agents solver,gated --world random,adversarial,no_guess --seeds 50
uv run grounded-decision audit minesweeper --agent rules --seed 6           # each move's exact risk

As a person you type a cell (c5) to reveal it, f c5 to flag it and u c5 to remove a flag. With --guarded, the gate replaces a gamble by a proved-safe cell whenever one exists, and abstains, leaving the choice to you, when none does.