<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P047-minesweeper/ | updated: 2026-10-10 -->

# P047 Minesweeper

Open every cell without a mine on a field of up to 12 x 16: numbers count the mines around a cell, cells with none around open their neighbours by themselves, and flags mark the mines you are sure of. The mines are laid after the first move - never on that cell or next to it - from a seed, so the same seed lays the same field.

EML-P project `projects/minesweeper` in the EML language repo: 3 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/minesweeper` runs it and `eml project verify projects/minesweeper` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: flood-fill (https://efficientnewlanguage.org/cases/099-flood-fill/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
2
5 5
1
5 5
2
5 5
1

1
0 0
10 1
a b
3
5,5
1
5 5
2
5 5
2
1 2
2
3 5
1
1 1
1
2
3
4
4
13
x
5
17
5
0
17
16
-1
7
1
3 3
4

3
5
```

Screen:

```text
== Minesweeper ==
Open every cell without a mine. A number counts the mines in the eight cells around it.
A field of 9 x 9 with 10 mines, seed 2026.
     1 2 3 4 5 6 7 8 9
  1  . . . . . . . . .
  2  . . . . . . . . .
  3  . . . . . . . . .
  4  . . . . . . . . .
  5  . . . . . . . . .
  6  . . . . . . . . .
  7  . . . . . . . . .
  8  . . . . . . . . .
  9  . . . . . . . . .
Mines 10, flags 0.

1) open  2) flag  3) show  4) new game  5) quit
choice> 0
Pick a number from 1 to 5.

1) open  2) flag  3) show  4) new game  5) quit
choice> x
Pick a number from 1 to 5.

1) open  2) flag  3) show  4) new game  5) quit
choice> 2
flag (row column)> 5 5
     1 2 3 4 5 6 7 8 9
  1  . . . . . . . . .
  2  . . . . . . . . .
  3  . . . . . . . . .
  4  . . . . . . . . .
  5  . . . . F . . . .
  6  . . . . . . . . .
  7  . . . . . . . . .
  8  . . . . . . . . .
  9  . . . . . . . . .
Mines 10, flags 1.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 5 5
That cell is flagged - take the flag off first.

1) open  2) flag  3) show  4) new game  5) quit
choice> 2
flag (row column)> 5 5
     1 2 3 4 5 6 7 8 9
  1  . . . . . . . . .
  2  . . . . . . . . .
  3  . . . . . . . . .
  4  . . . . . . . . .
  5  . . . . . . . . .
  6  . . . . . . . . .
  7  . . . . . . . . .
  8  . . . . . . . . .
  9  . . . . . . . . .
Mines 10, flags 0.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 
Cancelled.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 0 0
Type a row from 1 to 9 and a column from 1 to 9, like 3 4.
open (row column)> 10 1
Type a row from 1 to 9 and a column from 1 to 9, like 3 4.
open (row column)> a b
Type a row from 1 to 9 and a column from 1 to 9, like 3 4.
open (row column)> 3
Type a row from 1 to 9 and a column from 1 to 9, like 3 4.
open (row column)> 5,5
     1 2 3 4 5 6 7 8 9
  1  . . . . . . . . .
  2  . . . . . . . . .
  3  . . . . . . . . .
  4  . . . 2 1 1 1 . .
  5  . . 2 1 - - 1 . .
  6  . . 1 - - 1 1 . .
  7  . . 1 - - 1 . . .
  8  . . 1 1 - 2 . . .
  9  . . . 1 - 1 . . .
Mines 10, flags 0.
25 cells opened.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 5 5
That cell is open already.

1) open  2) flag  3) show  4) new game  5) quit
choice> 2
flag (row column)> 5 5
That cell is open - there is nothing to flag.

1) open  2) flag  3) show  4) new game  5) quit
choice> 2
flag (row column)> 1 2
     1 2 3 4 5 6 7 8 9
  1  . F . . . . . . .
  2  . . . . . . . . .
  3  . . . . . . . . .
  4  . . . 2 1 1 1 . .
  5  . . 2 1 - - 1 . .
  6  . . 1 - - 1 1 . .
  7  . . 1 - - 1 . . .
  8  . . 1 1 - 2 . . .
  9  . . . 1 - 1 . . .
Mines 10, flags 1.

1) open  2) flag  3) show  4) new game  5) quit
choice> 2
flag (row column)> 3 5
     1 2 3 4 5 6 7 8 9
  1  . F . . . . . . .
  2  . . . . . . . . .
  3  . . . . F . . . .
  4  . . . 2 1 1 1 . .
  5  . . 2 1 - - 1 . .
  6  . . 1 - - 1 1 . .
  7  . . 1 - - 1 . . .
  8  . . 1 1 - 2 . . .
  9  . . . 1 - 1 . . .
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 1 1
     1 2 3 4 5 6 7 8 9
  1  * x . . . . . . .
  2  . . . * . . . . .
  3  . . . . F . . . .
  4  . . * 2 1 1 1 * .
  5  . . 2 1 - - 1 . *
  6  . * 1 - - 1 1 . .
  7  . . 1 - - 1 * . .
  8  . . 1 1 - 2 . . .
  9  . . * 1 - 1 * . .
Mines 10, flags 2.
Boom - row 1, column 1 was a mine. Game over after 2 openings.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
This game is over - start a new one.

1) open  2) flag  3) show  4) new game  5) quit
choice> 2
This game is over - start a new one.

1) open  2) flag  3) show  4) new game  5) quit
choice> 3
     1 2 3 4 5 6 7 8 9
  1  * x . . . . . . .
  2  . . . * . . . . .
  3  . . . . F . . . .
  4  . . * 2 1 1 1 * .
  5  . . 2 1 - - 1 . *
  6  . * 1 - - 1 1 . .
  7  . . 1 - - 1 * . .
  8  . . 1 1 - 2 . . .
  9  . . * 1 - 1 * . .
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 4
rows (5 to 12)> 4
Type a number from 5 to 12.
rows (5 to 12)> 13
Type a number from 5 to 12.
rows (5 to 12)> x
Type a number from 5 to 12.
rows (5 to 12)> 5
columns (5 to 16)> 17
Type a number from 5 to 16.
columns (5 to 16)> 5
mines (1 to 16)> 0
Type a number from 1 to 16.
mines (1 to 16)> 17
Type a number from 1 to 16.
mines (1 to 16)> 16
seed (0 to 999999999)> -1
Type a number from 0 to 999999999.
seed (0 to 999999999)> 7
A field of 5 x 5 with 16 mines, seed 7.
     1 2 3 4 5
  1  . . . . .
  2  . . . . .
  3  . . . . .
  4  . . . . .
  5  . . . . .
Mines 16, flags 0.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 3 3
     1 2 3 4 5
  1  . . . . .
  2  . 5 3 5 .
  3  . 3 - 3 .
  4  . 5 3 5 .
  5  . . . . .
Mines 16, flags 0.
Cleared - every safe cell is open, after 1 opening.

1) open  2) flag  3) show  4) new game  5) quit
choice> 4
rows (5 to 12)> 
Cancelled.

1) open  2) flag  3) show  4) new game  5) quit
choice> 3
     1 2 3 4 5
  1  * * * * *
  2  * 5 3 5 *
  3  * 3 - 3 *
  4  * 5 3 5 *
  5  * * * * *
Mines 16, flags 0.

1) open  2) flag  3) show  4) new game  5) quit
choice> 5
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
1
5 5
2
1 1
1
1 6
1
3 1
1
7 9
1
8 1
2
6 2
3
1
1 2
1
1 3
1
1 4
1
2 3
1
3 3
1
3 4
1
4 9
1
5 8
1
6 1
1
8 7
3
5
```

Screen:

```text
== Minesweeper ==
Open every cell without a mine. A number counts the mines in the eight cells around it.
A field of 9 x 9 with 10 mines, seed 2026.
     1 2 3 4 5 6 7 8 9
  1  . . . . . . . . .
  2  . . . . . . . . .
  3  . . . . . . . . .
  4  . . . . . . . . .
  5  . . . . . . . . .
  6  . . . . . . . . .
  7  . . . . . . . . .
  8  . . . . . . . . .
  9  . . . . . . . . .
Mines 10, flags 0.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 5 5
     1 2 3 4 5 6 7 8 9
  1  . . . . . . . . .
  2  . . . . . . . . .
  3  . . . . . . . . .
  4  . . . 2 1 1 1 . .
  5  . . 2 1 - - 1 . .
  6  . . 1 - - 1 1 . .
  7  . . 1 - - 1 . . .
  8  . . 1 1 - 2 . . .
  9  . . . 1 - 1 . . .
Mines 10, flags 0.
25 cells opened.

1) open  2) flag  3) show  4) new game  5) quit
choice> 2
flag (row column)> 1 1
     1 2 3 4 5 6 7 8 9
  1  F . . . . . . . .
  2  . . . . . . . . .
  3  . . . . . . . . .
  4  . . . 2 1 1 1 . .
  5  . . 2 1 - - 1 . .
  6  . . 1 - - 1 1 . .
  7  . . 1 - - 1 . . .
  8  . . 1 1 - 2 . . .
  9  . . . 1 - 1 . . .
Mines 10, flags 1.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 1 6
     1 2 3 4 5 6 7 8 9
  1  F . . . 1 - - - -
  2  . . . . 2 1 - - -
  3  . . . . . 1 1 1 1
  4  . . . 2 1 1 1 . .
  5  . . 2 1 - - 1 . .
  6  . . 1 - - 1 1 . .
  7  . . 1 - - 1 . . .
  8  . . 1 1 - 2 . . .
  9  . . . 1 - 1 . . .
Mines 10, flags 1.
14 cells opened.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 3 1
     1 2 3 4 5 6 7 8 9
  1  F . . . 1 - - - -
  2  1 1 . . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . . 1 - - 1 1 . .
  7  . . 1 - - 1 . . .
  8  . . 1 1 - 2 . . .
  9  . . . 1 - 1 . . .
Mines 10, flags 1.
8 cells opened.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 7 9
     1 2 3 4 5 6 7 8 9
  1  F . . . 1 - - - -
  2  1 1 . . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . . 1 - - 1 1 2 1
  7  . . 1 - - 1 . 1 -
  8  . . 1 1 - 2 . 2 -
  9  . . . 1 - 1 . 1 -
Mines 10, flags 1.
8 cells opened.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 8 1
     1 2 3 4 5 6 7 8 9
  1  F . . . 1 - - - -
  2  1 1 . . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . . 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 1.
6 cells opened.

1) open  2) flag  3) show  4) new game  5) quit
choice> 2
flag (row column)> 6 2
     1 2 3 4 5 6 7 8 9
  1  F . . . 1 - - - -
  2  1 1 . . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 3
     1 2 3 4 5 6 7 8 9
  1  F . . . 1 - - - -
  2  1 1 . . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 1 2
     1 2 3 4 5 6 7 8 9
  1  F 1 . . 1 - - - -
  2  1 1 . . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 1 3
     1 2 3 4 5 6 7 8 9
  1  F 1 1 . 1 - - - -
  2  1 1 . . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 1 4
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 . . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 2 3
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 1 . 2 1 - - -
  3  - 1 . . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 3 3
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 1 . 2 1 - - -
  3  - 1 2 . . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 3 4
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 1 . 2 1 - - -
  3  - 1 2 3 . 1 1 1 1
  4  - 1 . 2 1 1 1 . .
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 4 9
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 1 . 2 1 - - -
  3  - 1 2 3 . 1 1 1 1
  4  - 1 . 2 1 1 1 . 2
  5  1 2 2 1 - - 1 . .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 5 8
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 1 . 2 1 - - -
  3  - 1 2 3 . 1 1 1 1
  4  - 1 . 2 1 1 1 . 2
  5  1 2 2 1 - - 1 2 .
  6  . F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 6 1
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 1 . 2 1 - - -
  3  - 1 2 3 . 1 1 1 1
  4  - 1 . 2 1 1 1 . 2
  5  1 2 2 1 - - 1 2 .
  6  1 F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 . 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 1
open (row column)> 8 7
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 1 . 2 1 - - -
  3  - 1 2 3 . 1 1 1 1
  4  - 1 . 2 1 1 1 . 2
  5  1 2 2 1 - - 1 2 .
  6  1 F 1 - - 1 1 2 1
  7  1 1 1 - - 1 . 1 -
  8  - 1 1 1 - 2 2 2 -
  9  - 1 . 1 - 1 . 1 -
Mines 10, flags 2.
Cleared - every safe cell is open, after 15 openings.

1) open  2) flag  3) show  4) new game  5) quit
choice> 3
     1 2 3 4 5 6 7 8 9
  1  F 1 1 1 1 - - - -
  2  1 1 1 * 2 1 - - -
  3  - 1 2 3 * 1 1 1 1
  4  - 1 * 2 1 1 1 * 2
  5  1 2 2 1 - - 1 2 *
  6  1 F 1 - - 1 1 2 1
  7  1 1 1 - - 1 * 1 -
  8  - 1 1 1 - 2 2 2 -
  9  - 1 * 1 - 1 * 1 -
Mines 10, flags 2.

1) open  2) flag  3) show  4) new game  5) quit
choice> 5
Bye.
```

## Modules

### main.eml

```eml
# P047 minesweeper: open every cell without a mine. A number tells how many
# of the eight cells around hold one; flag the cells you are sure of. The
# mines are laid after the first cell is opened, never on it or next to it,
# and the same seed always lays the same mines.
import field
import rng

def trim(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def words(s):
    [] => out
    "" => word
    for c in s + " ":
        if c == " " or c == ",":
            if word != "":
                out + [word] => out
            "" => word
        else:
            word + c => word
    return out

def number(s):
    if s == "" or len(s) > 9:
        return -1
    0 => n
    for c in s:
        if not (c in "0123456789"):
            return -1
        n * 10 + int(c) => n
    return n

def counted(n, word):
    if n == 1:
        return "1 " + word
    return str(n) + " " + word + "s"

def show(game, reveal):
    for line in field.drawn(game[0], reveal):
        line ^0
    game[0] => f
    ("Mines " + str(game[1]) + ", flags " + str(field.flag_count(f)) + ".") ^0

def start(rows, cols, mines, seed):
    ("A field of " + str(rows) + " x " + str(cols) + " with " + str(mines) + " mines, seed " + str(seed) + ".") ^0
    # [field, mines, the generator, over, openings - the cells the player
    # opened; flags are not counted]
    return [field.new_field(rows, cols), mines, rng.Rng(seed), False, 0]

def ask_cell(game, prompt):
    # [row, column] counting from 0, or [] when the answer is empty.
    game[0] => f
    while True:
        trim(input(prompt)) => answer
        if answer == "":
            return []
        words(answer) => ws
        if len(ws) == 2:
            number(ws[0]) => r
            number(ws[1]) => c
            if r >= 1 and r <= f[0] and c >= 1 and c <= f[1]:
                return [r - 1, c - 1]
        ("Type a row from 1 to " + str(f[0]) + " and a column from 1 to " + str(f[1]) + ", like 3 4.") ^0

def reveal(game):
    if game[3]:
        "This game is over - start a new one." ^0
        return game
    ask_cell(game, "open (row column)> ") => cell
    if len(cell) == 0:
        "Cancelled." ^0
        return game
    game[0] => f
    cell[0] => r
    cell[1] => c
    if f[3][r][c]:
        "That cell is open already." ^0
        return game
    if f[4][r][c]:
        "That cell is flagged - take the flag off first." ^0
        return game
    if len(f[2]) == 0:
        field.lay_mines(f, game[1], r, c, game[2])
    field.open_cell(f, r, c) => opened
    game[4] + 1 => game[4]
    if opened == -1:
        True => game[3]
        show(game, True)
        ("Boom - row " + str(r + 1) + ", column " + str(c + 1) + " was a mine. Game over after " + counted(game[4], "opening") + ".") ^0
        return game
    show(game, False)
    if field.closed_safe(f) == 0:
        True => game[3]
        ("Cleared - every safe cell is open, after " + counted(game[4], "opening") + ".") ^0
    elif opened > 1:
        (str(opened) + " cells opened.") ^0
    return game

def flag(game):
    if game[3]:
        "This game is over - start a new one." ^0
        return game
    ask_cell(game, "flag (row column)> ") => cell
    if len(cell) == 0:
        "Cancelled." ^0
        return game
    game[0] => f
    if f[3][cell[0]][cell[1]]:
        "That cell is open - there is nothing to flag." ^0
        return game
    not f[4][cell[0]][cell[1]] => f[4][cell[0]][cell[1]]
    show(game, False)
    return game

def ask_size(prompt, low, high):
    while True:
        trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> ")) => answer
        if answer == "":
            return -1
        number(answer) => n
        if n >= low and n <= high:
            return n
        ("Type a number from " + str(low) + " to " + str(high) + ".") ^0

def new_game(game):
    ask_size("rows", 5, 12) => rows
    if rows == -1:
        "Cancelled." ^0
        return game
    ask_size("columns", 5, 16) => cols
    if cols == -1:
        "Cancelled." ^0
        return game
    ask_size("mines", 1, rows * cols - 9) => mines
    if mines == -1:
        "Cancelled." ^0
        return game
    ask_size("seed", 0, 999999999) => seed
    if seed == -1:
        "Cancelled." ^0
        return game
    start(rows, cols, mines, seed) => game
    show(game, False)
    return game

"== Minesweeper ==" ^0
"Open every cell without a mine. A number counts the mines in the eight cells around it." ^0
start(9, 9, 10, 2026) => game
show(game, False)
True => running
while running:
    "" ^0
    "1) open  2) flag  3) show  4) new game  5) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        reveal(game) => game
    elif choice == "2":
        flag(game) => game
    elif choice == "3":
        show(game, game[3])
    elif choice == "4":
        new_game(game) => game
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import field
import rng

def trim(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def words(s):
    out = []
    word = ""
    for c in s + " ":
        if c == " " or c == ",":
            if word != "":
                out = out + [word]
            word = ""
        else:
            word = word + c
    return out

def number(s):
    if s == "" or len(s) > 9:
        return -1
    n = 0
    for c in s:
        if not c in "0123456789":
            return -1
        n = n * 10 + int(c)
    return n

def counted(n, word):
    if n == 1:
        return "1 " + word
    return str(n) + " " + word + "s"

def show(game, reveal):
    for line in field.drawn(game[0], reveal):
        print(line)
    f = game[0]
    print("Mines " + str(game[1]) + ", flags " + str(field.flag_count(f)) + ".")

def start(rows, cols, mines, seed):
    print("A field of " + str(rows) + " x " + str(cols) + " with " + str(mines) + " mines, seed " + str(seed) + ".")
    return [field.new_field(rows, cols), mines, rng.Rng(seed), False, 0]

def ask_cell(game, prompt):
    f = game[0]
    while True:
        answer = trim(input(prompt))
        if answer == "":
            return []
        ws = words(answer)
        if len(ws) == 2:
            r = number(ws[0])
            c = number(ws[1])
            if r >= 1 and r <= f[0] and c >= 1 and c <= f[1]:
                return [r - 1, c - 1]
        print("Type a row from 1 to " + str(f[0]) + " and a column from 1 to " + str(f[1]) + ", like 3 4.")

def reveal(game):
    if game[3]:
        print("This game is over - start a new one.")
        return game
    cell = ask_cell(game, "open (row column)> ")
    if len(cell) == 0:
        print("Cancelled.")
        return game
    f = game[0]
    r = cell[0]
    c = cell[1]
    if f[3][r][c]:
        print("That cell is open already.")
        return game
    if f[4][r][c]:
        print("That cell is flagged - take the flag off first.")
        return game
    if len(f[2]) == 0:
        field.lay_mines(f, game[1], r, c, game[2])
    opened = field.open_cell(f, r, c)
    game[4] = game[4] + 1
    if opened == -1:
        game[3] = True
        show(game, True)
        print("Boom - row " + str(r + 1) + ", column " + str(c + 1) + " was a mine. Game over after " + counted(game[4], "opening") + ".")
        return game
    show(game, False)
    if field.closed_safe(f) == 0:
        game[3] = True
        print("Cleared - every safe cell is open, after " + counted(game[4], "opening") + ".")
    elif opened > 1:
        print(str(opened) + " cells opened.")
    return game

def flag(game):
    if game[3]:
        print("This game is over - start a new one.")
        return game
    cell = ask_cell(game, "flag (row column)> ")
    if len(cell) == 0:
        print("Cancelled.")
        return game
    f = game[0]
    if f[3][cell[0]][cell[1]]:
        print("That cell is open - there is nothing to flag.")
        return game
    f[4][cell[0]][cell[1]] = not f[4][cell[0]][cell[1]]
    show(game, False)
    return game

def ask_size(prompt, low, high):
    while True:
        answer = trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> "))
        if answer == "":
            return -1
        n = number(answer)
        if n >= low and n <= high:
            return n
        print("Type a number from " + str(low) + " to " + str(high) + ".")

def new_game(game):
    rows = ask_size("rows", 5, 12)
    if rows == -1:
        print("Cancelled.")
        return game
    cols = ask_size("columns", 5, 16)
    if cols == -1:
        print("Cancelled.")
        return game
    mines = ask_size("mines", 1, rows * cols - 9)
    if mines == -1:
        print("Cancelled.")
        return game
    seed = ask_size("seed", 0, 999999999)
    if seed == -1:
        print("Cancelled.")
        return game
    game = start(rows, cols, mines, seed)
    show(game, False)
    return game

print("== Minesweeper ==")
print("Open every cell without a mine. A number counts the mines in the eight cells around it.")
game = start(9, 9, 10, 2026)
show(game, False)
running = True
while running:
    print("")
    print("1) open  2) flag  3) show  4) new game  5) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        game = reveal(game)
    elif choice == "2":
        game = flag(game)
    elif choice == "3":
        show(game, game[3])
    elif choice == "4":
        game = new_game(game)
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")
```

### field.eml

```eml
# P047 minesweeper - the field. A field is [rows, cols, mines, open, flags]:
# mines[r][c] is True where a mine lies (an empty list until the first cell
# is opened), open and flags are grids of True/False.

def grid(rows, cols, value):
    [] => g
    for r in [1:rows]:
        g + [[value] * cols] => g
    return g

def new_field(rows, cols):
    return [rows, cols, [], grid(rows, cols, False), grid(rows, cols, False)]

def around(f, r, c):
    # The cells next to (r, c), across corners too.
    [] => out
    for dr in [0:2]:
        for dc in [0:2]:
            r + dr - 1 => y
            c + dc - 1 => x
            if (dr != 1 or dc != 1) and y >= 0 and y < f[0] and x >= 0 and x < f[1]:
                out + [[y, x]] => out
    return out

def lay_mines(f, count, r0, c0, rng):
    # Mines go anywhere but the first cell opened and the cells around it, so
    # the first move always opens an area. A partial Fisher-Yates shuffle
    # picks `count` of the allowed cells, each set of cells equally likely.
    [] => allowed
    for r in [0:f[0] - 1]:
        for c in [0:f[1] - 1]:
            if abs(r - r0) > 1 or abs(c - c0) > 1:
                allowed + [[r, c]] => allowed
    grid(f[0], f[1], False) => mines
    for k in [0:count - 1]:
        k + rng.below(len(allowed) - k) => j
        allowed[j] => pick
        allowed[k] => allowed[j]
        pick => allowed[k]
        True => mines[pick[0]][pick[1]]
    mines => f[2]

def count_around(f, r, c):
    0 => n
    for cell in around(f, r, c):
        if f[2][cell[0]][cell[1]]:
            n + 1 => n
    return n

def open_cell(f, r, c):
    # Opens (r, c). Returns the number of cells opened, or -1 for a mine. A
    # cell with no mine around it opens its neighbours too, and so on - the
    # spreading of the corpus case flood-fill: a cell is marked before it is
    # visited, so none is counted twice. Here it spreads to all eight
    # neighbours, never into a flagged cell, and keeps a list of cells still
    # to visit instead of calling itself.
    if f[2][r][c]:
        True => f[3][r][c]
        return -1
    0 => opened
    [[r, c]] => todo
    True => f[3][r][c]
    while len(todo) > 0:
        todo[len(todo) - 1] => cell
        todo[0:len(todo) - 1] => todo
        opened + 1 => opened
        if count_around(f, cell[0], cell[1]) == 0:
            for nb in around(f, cell[0], cell[1]):
                if not f[3][nb[0]][nb[1]] and not f[4][nb[0]][nb[1]]:
                    True => f[3][nb[0]][nb[1]]
                    todo + [nb] => todo
    return opened

def closed_safe(f):
    # How many cells without a mine are still closed.
    0 => n
    for r in [0:f[0] - 1]:
        for c in [0:f[1] - 1]:
            if not f[3][r][c] and not f[2][r][c]:
                n + 1 => n
    return n

def flag_count(f):
    0 => n
    for row in f[4]:
        for x in row:
            if x:
                n + 1 => n
    return n

def drawn(f, reveal):
    # The field as lines: . closed, F flag, - an open cell with no mine
    # around, 1-8 the mines around. With reveal (the game is over) the mines
    # show as *, and a flag on a cell without a mine as x.
    "    " => header
    for c in [1:f[1]]:
        header + " " + str(c % 10) => header
    [header] => lines
    for r in [0:f[0] - 1]:
        str(r + 1) => label
        while len(label) < 3:
            " " + label => label
        label + " " => line
        for c in [0:f[1] - 1]:
            "." => ch
            if f[3][r][c]:
                if f[2][r][c]:
                    "*" => ch
                else:
                    count_around(f, r, c) => n
                    "-" => ch
                    if n > 0:
                        str(n) => ch
            elif f[4][r][c]:
                "F" => ch
                if reveal and len(f[2]) > 0 and not f[2][r][c]:
                    "x" => ch
            elif reveal and len(f[2]) > 0 and f[2][r][c]:
                "*" => ch
            line + " " + ch => line
        lines + [line] => lines
    return lines
```

Python projection of field.eml:

```python
def grid(rows, cols, value):
    g = []
    for r in range(1, rows+1):
        g = g + [[value] * cols]
    return g

def new_field(rows, cols):
    return [rows, cols, [], grid(rows, cols, False), grid(rows, cols, False)]

def around(f, r, c):
    out = []
    for dr in range(0, 3):
        for dc in range(0, 3):
            y = r + dr - 1
            x = c + dc - 1
            if (dr != 1 or dc != 1) and y >= 0 and y < f[0] and x >= 0 and x < f[1]:
                out = out + [[y, x]]
    return out

def lay_mines(f, count, r0, c0, rng):
    allowed = []
    for r in range(0, f[0]):
        for c in range(0, f[1]):
            if abs(r - r0) > 1 or abs(c - c0) > 1:
                allowed = allowed + [[r, c]]
    mines = grid(f[0], f[1], False)
    for k in range(0, count):
        j = k + rng.below(len(allowed) - k)
        pick = allowed[j]
        allowed[j] = allowed[k]
        allowed[k] = pick
        mines[pick[0]][pick[1]] = True
    f[2] = mines

def count_around(f, r, c):
    n = 0
    for cell in around(f, r, c):
        if f[2][cell[0]][cell[1]]:
            n = n + 1
    return n

def open_cell(f, r, c):
    if f[2][r][c]:
        f[3][r][c] = True
        return -1
    opened = 0
    todo = [[r, c]]
    f[3][r][c] = True
    while len(todo) > 0:
        cell = todo[len(todo) - 1]
        todo = todo[0:len(todo) - 1]
        opened = opened + 1
        if count_around(f, cell[0], cell[1]) == 0:
            for nb in around(f, cell[0], cell[1]):
                if not f[3][nb[0]][nb[1]] and not f[4][nb[0]][nb[1]]:
                    f[3][nb[0]][nb[1]] = True
                    todo = todo + [nb]
    return opened

def closed_safe(f):
    n = 0
    for r in range(0, f[0]):
        for c in range(0, f[1]):
            if not f[3][r][c] and not f[2][r][c]:
                n = n + 1
    return n

def flag_count(f):
    n = 0
    for row in f[4]:
        for x in row:
            if x:
                n = n + 1
    return n

def drawn(f, reveal):
    header = "    "
    for c in range(1, f[1]+1):
        header = header + " " + str(c % 10)
    lines = [header]
    for r in range(0, f[0]):
        label = str(r + 1)
        while len(label) < 3:
            label = " " + label
        line = label + " "
        for c in range(0, f[1]):
            ch = "."
            if f[3][r][c]:
                if f[2][r][c]:
                    ch = "*"
                else:
                    n = count_around(f, r, c)
                    ch = "-"
                    if n > 0:
                        ch = str(n)
            elif f[4][r][c]:
                ch = "F"
                if reveal and len(f[2]) > 0 and not f[2][r][c]:
                    ch = "x"
            elif reveal and len(f[2]) > 0 and f[2][r][c]:
                ch = "*"
            line = line + " " + ch
        lines = lines + [line]
    return lines
```

### rng.eml

```eml
# P047 minesweeper - random numbers written in EML: the linear congruential
# generator of P008 (number guessing), with the constants of the C
# standard's example rand(). Python's random module is not used, so a seed
# gives the same mines on every machine, and the interpreter can check a
# whole session byte for byte.

class Rng:
    def __init__(self, seed):
        seed % 2147483648 => self.state

    def step(self):
        (1103515245 * self.state + 12345) % 2147483648 => self.state
        return self.state

    def below(self, n):
        # A number from 0 to n - 1, taken from the high bits of the state: the
        # low bits of this generator repeat with short periods. Dividing by
        # 65536 is exact in a float for a state below 2^31.
        return int(self.step() / 65536) % n
```

Python projection of rng.eml:

```python
class Rng:
    def __init__(self, seed):
        self.state = seed % 2147483648
    def step(self):
        self.state = (1103515245 * self.state + 12345) % 2147483648
        return self.state
    def below(self, n):
        return int(self.step() / 65536) % n
```

## README

# P047 - Minesweeper

Open every cell of the field that holds no mine. An open cell shows how many
mines lie in the eight cells around it; a cell with none around it opens its
neighbours by itself. Flag the cells you are sure hold a mine - a flagged
cell cannot be opened by mistake. Open a mine and the game is over.

- `main.eml` - the menu, reading a cell as a row and a column, the game's
  messages, and a new game of any size from 5 x 5 to 12 x 16
- `field.eml` - the field: laying the mines, counting around a cell,
  opening a cell and everything it spreads to, and drawing the field
- `rng.eml` - random numbers written in EML, the generator of P008

How each part works:

- The mines are laid when the first cell is opened, anywhere but on that
  cell and the cells around it, so the first move always opens an area. They
  are picked by a partial Fisher-Yates shuffle of the allowed cells, so
  every set of cells is equally likely. The random numbers come from EML
  code, not Python's random module, so a seed lays the same mines on every
  machine.
- Opening a cell with no mine around it spreads, as in the corpus case
  `flood-fill`: each cell is marked open before it is visited, so none is
  counted twice. Here it spreads to all eight neighbours, stops at cells
  that have mines around them (they open, showing their number), and never
  opens a flagged cell.
- On the field `.` is a closed cell, `F` a flag, `-` an open cell with no
  mine around it and `1` to `8` the mines around. When the game is over the
  mines show as `*`, and a flag on a cell without a mine as `x`.
- The game is won when every cell without a mine is open; flags are not
  needed for that. The game counts your openings; flags do not count.

What is checked: menu choices 1 to 5; a cell as a row and a column inside
the field, separated by a space or a comma; a cell that is already open or
flagged; a game that is over. A new game takes 5 to 12 rows, 5 to 16
columns, 1 mine up to the cells minus the nine kept clear, and a seed from
0 to 999999999. An empty answer cancels.

Sessions: `sessions/basic.in` plays the default field (seed 2026) to the
end. The first opening, in the middle, spreads over 25 cells, and four more
spread over 14, 8, 8 and 6. Two flags go on mines, there is a look at the
field, and then ten single cells are opened. The field is cleared after 15
openings and shown once more with its mines.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- a flag before the first opening, which stops that cell from being opened
  until the flag is taken off;
- an empty cell, then 0 0, 10 1, a b and 3, then 5,5 with a comma;
- opening and flagging a cell that is already open;
- a wrong flag and a right one, then a mine: the field shows every mine,
  and the wrong flag as x;
- opening and flagging after the game is over;
- a new game that first gets rows 4, 13 and x, columns 17, mines 0 and 17
  and seed -1, and then a 5 x 5 field with 16 mines. That is the most there
  can be, so the first opening, in the middle, clears the field at once;
- a new game that is cancelled.

Built on the verified corpus case `flood-fill` (filling a region of a grid
from one cell, marking each cell before spreading from it).
