sourcegames/shut-the-box/ShutTheBox.xtl
1⍝# Shut the box's rules, a library: shut-the-box.xtl (scripted), play.xtl
2⍝# (at the terminal) and the web page all use these. Tiles 1 to 9 start
3⍝# open. Each turn roll two dice (one die once the open tiles add up to
4⍝# 6 or less) and shut open tiles adding up to the roll. When no set of
5⍝# open tiles makes the roll, the game is over and the open tiles are
6⍝# the score; shut them all to win.
7⍝#
8⍝# The state is the open tiles as a 0/1 vector of 9.
9
10ᵇ⁼u̲se< "Board"
11
12⍝ :: (Any a, Num b) => a -> b
13⍝# A new box: all nine tiles open (1).
14ˡn̲ew ← { x → 9 r̲eshape 1 }
15⍝ :: Int -> Int
16⍝# The open tiles' sum: the tiles times the 0/1 vector, an inner product.
17ˡs̲core ← { s → s '+ '× i̲nner r̲ange 9 }
18
19⍝ :: Unit -> Int
20⍝# Every set of tiles at once: the 512 subsets of 1 to 9 as the columns
21⍝# of a 9 by 512 matrix of bits (the binary digits of 0 to 511), and all
22⍝# their sums by one inner product.
23ˡs̲ubsets ← { @ → 2 2 2 2 2 2 2 2 2 e̲ncode o̲ffsets 512 }
24⍝ :: Unit -> Int
25⍝# Every subset's sum: the tiles' numbers against the 512 subsets, one inner product.
26ˡs̲ums ← { @ → (r̲ange 9) '+ '× i̲nner ˡs̲ubsets @ }
27
28⍝ :: Int -> Int
29⍝# The roll: two dice, or one when the open tiles add up to 6 or less.
30ˡr̲oll ← { s → r̲oll! (1 + 6 < ˡs̲core s) r̲eshape 6 }
31
32⍝ :: Int -> Int -> Int
33⍝# The moves for roll r: the subsets that use only open tiles (every bit
34⍝# at most the tile's 0 or 1) and add up to r, one per row.
35ˡm̲oves ← { s r →
36 M ← ˡs̲ubsets @
37 fits ← '∧ r̲/ M ≤ s 'l̲eft t̲able o̲ffsets 512
38 o̲\ (w̲here fits ∧ r = ˡs̲ums @) s̲elect₂ M
39}
40⍝ :: (Num a, Truthy a, Truthy b) => a -> b -> a
41⍝# Shut the tiles of a move (a 0/1 vector of 9).
42ˡs̲hut ← { s m → s × n̲ot m }
43⍝ :: Num a => Int -> Int -> a
44⍝# 0 playing, 1 the box shut, 2 stuck (the score stands).
45ˡs̲tatus ← { s r → f̲irst (((0 = ˡs̲core s) c̲at 0 = t̲ally s ˡm̲oves r) r̲eplicate 1 2) c̲at 0 }
46
47⍝ :: Int -> Char
48⍝# The tiles as the box shows them: open ones by number, shut ones as .,
49⍝# spaced (lib/Board).
50ˡv̲iew ← { s → r̲avel ᵇs̲paced (1 9 r̲eshape 1 + s × r̲ange 9) s̲elect ".123456789" }
51⍝ :: Truthy a => a -> Char
52⍝# A move as its tiles, "1 2 4".
53ˡt̲iles ← { m → f̲ormat w̲here m }
54⍝ :: Truthy a => a -> Box Char
55⍝# The moves as a strand of lines, " 1 2 4", one per move (a function
56⍝# mapped over the moves' row numbers).
57ˡl̲isted ← { m → '{ " " c̲at ˡt̲iles ⍵ s̲elect m } m̲ap r̲ange t̲ally m }
58
59⍝ :: Int -> Int -> Int
60⍝# A player that shuts the highest tiles it can: of the moves, the one
61⍝# whose tiles, read as a number from 9 down, is largest.
62ˡh̲igh ← { s r →
63 m ← s ˡm̲oves r
64 key ← m '+ '× i̲nner 2 ^ o̲ffsets 9
65 (f̲irst w̲here key = 'm̲ax r̲/ key) s̲elect m
66}