sourcegames/2048/Twenty48.xtl

1⍝# 2048's rules, a library: 2048.xtl (scripted), play.xtl (at the 2⍝# terminal) and the web page all use these. Slide the tiles of a 4 by 4 3⍝# board left, right, up or down; two equal tiles that meet merge into 4⍝# their sum (once a move), and a 2 or a 4 appears on an empty square. 5⍝# Make a 2048 tile; the game ends when no move changes the board. 6⍝# 7⍝# The state is one vector: the score, then the 16 squares row by row 8⍝# (0 empty). 9 10⍝ :: a -> a 11⍝# The score so far. 12ˡs̲core ← { s → f̲irst s } 13⍝ :: a -> a 14⍝# The board, 4 by 4 (0 an empty square). 15ˡb̲oard ← { s → 4 4 r̲eshape 1 d̲rop s } 16 17⍝ :: (Num a, Truthy a) => a -> a 18⍝# Every row at once, its tiles slid left: each tile's new column is the 19⍝# count of tiles up to it in its row (a running sum); a 4 by 4 by 4 20⍝# table of each square against each column (row, old column, new 21⍝# column) puts every value in its place, summed over the old columns. 22ˡc̲ompress ← { b → 23 c ← (b ≠ 0) × '+ s̲\₂ b ≠ 0 24 '+ r̲/₂ (b 'l̲eft t̲able r̲ange 4) × c '= t̲able r̲ange 4 25} 26⍝ :: Num a => a -> Int 27⍝# Every row at once, which tiles double when equal neighbors merge, 28⍝# left to right: a run of equal tiles starts where a tile differs from 29⍝# the one before; each tile's place in its run is its column less the 30⍝# run's start (the latest start so far, by a max-scan). A tile in an 31⍝# odd place doubles when the next tile is equal; a tile in an even 32⍝# place is absorbed (the one before it doubled). 33ˡp̲lace ← { b → 34 j ← 4 4 r̲eshape r̲ange 4 35 prev ← 0 c̲at₂ -1 d̲rop₂ b 36 1 + j − 'm̲ax s̲\₂ j × b ≠ prev 37} 38⍝ :: (Num a, Truthy b) => a -> b 39⍝# Where a tile meets an equal one to its right in a row, the first of each pair (counted from the left). 40ˡd̲oubles ← { b → 41 next ← (1 d̲rop₂ b) c̲at₂ 0 42 (b ≠ 0) ∧ (1 = (ˡp̲lace b) m̲od 2) ∧ b = next 43} 44⍝ :: (Num a, Truthy a) => a -> a 45⍝# A row after merging: each first of a pair doubled, its partner emptied. 46ˡm̲erge ← { b → (b × 1 = (ˡp̲lace b) m̲od 2) + b × ˡd̲oubles b } 47⍝ :: (Num a, Truthy a) => a -> a 48⍝# A move left: compress, merge, compress, composed as a train of atops 49⍝# (each applied to the result of the one on its right). 50ˡl̲eft ← [ˡc̲ompress [ˡm̲erge ˡc̲ompress]] 51 52⍝ :: Num b => a -> b -> a 53⍝# The other moves turn the board so that the move is left, and turn it 54⍝# back: d is 1 left, 2 right, 3 up, 4 down. Up and down transpose the 55⍝# board (o_\) once, right and down reverse its rows once: each a power 56⍝# of 0 or 1. Turning back undoes them in the other order. 57ˡt̲urn ← { b d → (1 × d m̲ember? 2 4) 'r̲ev₂ p̲ower (1 × d m̲ember? 3 4) 'o̲\ p̲ower b } 58⍝ :: Num b => a -> b -> a 59⍝# The board turned back from the left-moving view of move d (undoing l:t_urn). 60ˡb̲ack ← { b d → (1 × d m̲ember? 3 4) 'o̲\ p̲ower (1 × d m̲ember? 2 4) 'r̲ev₂ p̲ower b } 61⍝ :: (Num a, Truthy a, Num b) => a -> b -> a 62⍝# Every tile of board b moved in direction d (1 left, 2 right, 3 up, 4 down): turned so the move is left, moved left, turned back. 63ˡs̲lide ← { b d → (ˡl̲eft b ˡt̲urn d) ˡb̲ack d } 64⍝ :: (Num a, Truthy a, Num b) => a -> b -> a 65⍝# What a move scores: the value of every tile it makes by merging. 66ˡg̲ain ← { b d → 67 c ← ˡc̲ompress b ˡt̲urn d 68 '+ r̲/ r̲avel 2 × c × ˡd̲oubles c 69} 70 71⍝ :: (Num a, Truthy a) => a -> a 72⍝# A 2 (or, one time in ten, a 4) on a random empty square. 73ˡs̲pawn ← { b → 74 e ← w̲here 0 = r̲avel b 75 0 = t̲ally e ? b 76 k ← (r̲oll! t̲ally e) s̲elect e 77 v ← 2 + 2 × 10 = r̲oll! 10 78 4 4 r̲eshape (r̲avel b) + v × k = r̲ange 16 79} 80⍝ :: (Any a, Num b, Truthy b) => a -> b 81⍝# A new game: no score, two tiles spawned on an empty board. 82ˡn̲ew ← { x → 0 c̲at r̲avel ˡs̲pawn ˡs̲pawn 4 4 r̲eshape 0 } 83 84⍝ :: (Num a, Truthy a, Num b) => a -> b -> a 85⍝# A move d: the board slides; if anything moved, the score gains the 86⍝# merged tiles and a new tile appears; if not, nothing happens. 87ˡm̲ove ← { s d → 88 b ← ˡb̲oard s 89 n ← b ˡs̲lide d 90 n m̲atch b ? s 91 ((ˡs̲core s) + b ˡg̲ain d) c̲at r̲avel ˡs̲pawn n 92} 93⍝ :: (Num a, Truthy a, Num b) => a -> b 94⍝# 0 playing, 1 a 2048 tile made, 2 no move changes the board. 95ˡs̲tatus ← { s → 96 b ← ˡb̲oard s 97 stuck ← '∧ r̲/ '{ (b ˡs̲lide ⍵) m̲atch b } e̲ach 1 2 3 4 98 f̲irst (((2048 m̲ember? r̲avel b) c̲at stuck) r̲eplicate 1 2) c̲at 0 99}