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.
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 16field.eml- the field: laying the mines, counting around a cell, opening a cell and everything it spreads to, and drawing the fieldrng.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,Fa flag,-an open cell with no mine around it and1to8the mines around. When the game is over the mines show as*, and a flag on a cell without a mine asx. - 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