sourcegames/minesweeper/Mines.xtl
1⍝# Minesweeper's rules, a library: minesweeper.xtl (scripted), play.xtl
2⍝# (at the terminal) and the web page all use these. A 9 by 9 field
3⍝# hides 10 mines; open a square to see how many mines touch it, flag
4⍝# the ones you think are mines, and open every safe square to win.
5⍝#
6⍝# The state is one vector: the status (0 playing, 1 won, 2 lost), then
7⍝# the mines, the opened squares and the flags, three 9 by 9 0/1 boards.
8
9ᵇ⁼u̲se< "Board"
10
11⍝ :: Int
12⍝# The field's width and height.
13ˡn ← 9
14⍝ :: a -> a
15⍝# Where the mines are, n by n (1 a mine).
16ˡm̲ines ← { s → (ˡn c̲at ˡn) r̲eshape 81 t̲ake 1 d̲rop s }
17⍝ :: a -> a
18⍝# Which squares are open, n by n.
19ˡo̲pen ← { s → (ˡn c̲at ˡn) r̲eshape 81 t̲ake 82 d̲rop s }
20⍝ :: a -> a
21⍝# Which squares are flagged, n by n.
22ˡf̲lags ← { s → (ˡn c̲at ˡn) r̲eshape -81 t̲ake s }
23⍝ :: a -> a
24⍝# 0 playing, 1 won, 2 lost.
25ˡs̲tatus ← { s → f̲irst s }
26⍝ :: a -> a -> a
27⍝# The state again from a status and the three boards.
28ˡs̲tate ← { st m → st c̲at r̲avel m }
29
30⍝ :: (Any a, Num b, Truthy b) => a -> b
31⍝# A new field: 10 mines on the first 10 squares of a random order.
32ˡn̲ew ← { x →
33 m ← (r̲ange 81) m̲ember? 10 t̲ake g̲rade r̲oll! 81 r̲eshape 1000000
34 0 c̲at m c̲at 162 r̲eshape 0
35}
36
37⍝ :: Num a => a -> a
38⍝# The eight neighbors of every square at once, as Life counts them
39⍝# (lib/Board: the board in a border of zeros rotated by every offset and
40⍝# summed, less itself).
41ˡa̲round ← { b → ᵇa̲round b }
42⍝ :: Num a => a -> a
43⍝# How many mines touch each square.
44ˡc̲ounts ← { s → ˡa̲round ˡm̲ines s }
45
46⍝ :: a -> a -> a
47⍝# The state again from a status and the three boards' parts.
48ˡw̲ith ← { s so → (f̲irst so) c̲at (r̲avel ˡm̲ines s) c̲at 1 d̲rop so }
49
50⍝ :: (Num a, Truthy a) => a -> Int -> a
51⍝# Opening square rc (row, column from 1): a mine loses; otherwise the
52⍝# opened region grows by flood fill: every opened square with no mine
53⍝# around it opens its neighbors, again and again until nothing
54⍝# changes. Squares next to a mine have a count, so the fill never
55⍝# reaches a mine. Opened squares lose their flags.
56ˡr̲eveal ← { s rc →
57 k ← ˡn ᵇc̲ell rc − 1
58 1 = k s̲elect r̲avel ˡm̲ines s ? s ˡw̲ith 2 c̲at (r̲avel (ˡo̲pen s) ∨ ˡm̲ines s) c̲at r̲avel ˡf̲lags s
59 start ← (ˡo̲pen s) ∨ (ˡn c̲at ˡn) r̲eshape k = r̲ange 81
60 o ← s ˡg̲row start
61 won ← 0 = '+ r̲/ r̲avel (n̲ot o) ∧ n̲ot ˡm̲ines s
62 s ˡw̲ith won c̲at (r̲avel o) c̲at r̲avel (ˡf̲lags s) ∧ n̲ot o
63}
64⍝ :: (Num a, Truthy a) => a -> a -> a
65⍝# The opened region grown by flood fill: every opened square with no mine around it opens its neighbors, until nothing changes.
66ˡg̲row ← { s o →
67 zero ← o ∧ 0 = ˡc̲ounts s
68 more ← (o ∨ 0 < ˡa̲round zero) ∧ n̲ot ˡm̲ines s
69 more m̲atch o ? o
70 s ˡg̲row more
71}
72⍝ :: (Num a, Truthy a) => a -> Int -> a
73⍝# Flag (or unflag) square rc, if it is not open.
74ˡf̲lag ← { s rc →
75 k ← ˡn ᵇc̲ell rc − 1
76 1 = k s̲elect r̲avel ˡo̲pen s ? s
77 f ← (r̲avel ˡf̲lags s) ≠ k = r̲ange 81
78 s ˡw̲ith (ˡs̲tatus s) c̲at (r̲avel ˡo̲pen s) c̲at f
79}
80
81⍝ :: (Num a, Truthy a) => a -> a
82⍝# The field as text: # unopened, F flagged, . an opened square with no
83⍝# mine around, 1 to 8 its count, * a mine (shown once the game is
84⍝# lost); row and column numbers around it, spaced (lib/Board). Every
85⍝# square's code at once.
86ˡc̲odes ← { s →
87 o ← ˡo̲pen s
88 shown ← o ∧ n̲ot ˡm̲ines s
89 boom ← o ∧ ˡm̲ines s
90 1 + ((n̲ot o) ∧ ˡf̲lags s) + (2 × boom) + shown × 3 + ˡc̲ounts s
91}
92⍝ :: Int -> Char
93⍝# The field as text: # unopened, F flagged, * a mine (when lost), . an opened square with no mine around, else its count.
94ˡv̲iew ← { s →
95 lab ← (ˡn c̲at 2) r̲eshape "1 2 3 4 5 6 7 8 9 "
96 (1 20 r̲eshape " 1 2 3 4 5 6 7 8 9 ") c̲at lab c̲at₂ ᵇs̲paced (ˡc̲odes s) s̲elect "#F*.12345678"
97}
98⍝ :: Int -> Char
99⍝# The field as a picture X_eTaL draws: the same characters in a grid.
100ˡp̲icture ← { s → ⎕G̲RID (ˡc̲odes s) s̲elect "#F*.12345678" }