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}