Project 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.

3 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-10

Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.

About

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).

Recorded sessions

What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.

bad-input

interpreter: byte-equal
== 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.
What was typed (47 lines)
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

basic

interpreter: byte-equal
== 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.
What was typed (37 lines)
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

Modules

The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.

main.eml(entry)

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 (main.py)
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 (field.py)
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 (rng.py)
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

Built on these corpus cases