sourcegames/robot-chase/RobotChase.xtl
1⍝# Robot chase's rules, a library: robot-chase.xtl (scripted), play.xtl
2⍝# (at the terminal) and the web page all use these. A 16 by 16 board,
3⍝# you and 12 robots. Every turn you step (or wait, or teleport), then
4⍝# every robot steps one square toward you, all at once. Robots that
5⍝# land on the same square crash and leave a wreck; a robot that walks
6⍝# into a wreck is destroyed. Clear the board to win; meet a robot or a
7⍝# wreck and you lose. Ported from the COR24 BASIC robot-chase.bas.
8⍝#
9⍝# The game is one flat vector of numbers (so a page can hand it back
10⍝# in): player row and column (0 to 15), teleports left, turn, the last
11⍝# event, the status (0 playing, 1 won, 2 lost), then the robots' rows,
12⍝# their columns, which are alive (12 each), and the wrecks (256 cells).
13
14ᵇ⁼u̲se< "Board"
15
16⍝ :: Int
17⍝# How many robots.
18ˡn ← 12
19⍝ :: Int
20⍝# How many numbers come before the robots in the state.
21ˡhead ← 6
22
23⍝## Reading the state
24⍝ :: a -> a
25⍝# The player's row and column.
26ˡp̲os ← { s → 2 t̲ake s }
27⍝ :: a -> a
28⍝# Teleports left.
29ˡt̲ele ← { s → 3 s̲elect s }
30⍝ :: a -> a
31⍝# The turn number.
32ˡt̲urn ← { s → 4 s̲elect s }
33⍝ :: a -> a
34⍝# What happened last turn (a code for the message).
35ˡe̲vent ← { s → 5 s̲elect s }
36⍝ :: a -> a
37⍝# 0 playing, 1 won, 2 caught.
38ˡs̲tatus ← { s → 6 s̲elect s }
39⍝ :: a -> a
40⍝# The robots as a 2 by 12 matrix: row 1 their rows, row 2 their columns.
41ˡr̲obots ← { s → (2 c̲at ˡn) r̲eshape (2 × ˡn) t̲ake ˡhead d̲rop s }
42⍝ :: a -> a
43⍝# Which robots still run (1) or are wrecked (0).
44ˡa̲live ← { s → ˡn t̲ake (ˡhead + 2 × ˡn) d̲rop s }
45⍝ :: a -> a
46⍝# The wreck piles on the 16 by 16 field (1 a pile).
47ˡw̲recks ← { s → 16 16 r̲eshape -256 t̲ake s }
48⍝ :: Num a => a -> a
49⍝# A square's number, 1 to 256, from row and column (or many, as a
50⍝# 2-row matrix).
51ˡc̲ell ← { rc → 16 ᵇc̲ell rc }
52⍝ :: (Num a, Truthy a) => Int -> a -> a
53⍝# How much of w lands on each of the 256 squares, when item i of w is
54⍝# on square k[i]: a 256 by 12 table of square against robot, times w
55⍝# (an inner product). With w the live robots, the robots per square.
56ˡo̲nSquares ← { k w → ((r̲ange 256) '= t̲able k) '+ '× i̲nner w }
57
58⍝## A new game
59⍝ :: Any a => a -> Int
60⍝# 13 different squares at random (the order of 256 random keys), as a 2
61⍝# by 13 matrix of rows and columns: you on the first, a robot on each of
62⍝# the others.
63ˡn̲ew ← { x →
64 rc ← 16 ᵇr̲c (ˡn + 1) t̲ake g̲rade r̲oll! 256 r̲eshape 1000000
65 (1 s̲elect₂ rc) c̲at 3 0 0 0 c̲at (r̲avel 1 d̲rop₂ rc) c̲at (ˡn r̲eshape 1) c̲at 256 r̲eshape 0
66}
67
68⍝## The robots' turn
69⍝ :: Int -> Int
70⍝# Every live robot steps toward you at once: the sign of the difference
71⍝# (a fork, d [> - <] 0), a 2 by 12 matrix of -1, 0 and 1, added to their
72⍝# positions. Then: a robot on your square catches you; robots on a
73⍝# wreck die; robots sharing a square (more than one live robot there)
74⍝# crash and leave a wreck.
75ˡc̲hase ← { s →
76 a ← ˡa̲live s
77 w ← r̲avel ˡw̲recks s
78 d ← ((ˡp̲os s) 'l̲eft t̲able r̲ange ˡn) − ˡr̲obots s
79 R ← (ˡr̲obots s) + ((2 c̲at ˡn) r̲eshape a) × d [> − <] 0
80 k ← ˡc̲ell R
81 caught ← '∨ r̲/ a × k = ˡc̲ell ˡp̲os s
82 a1 ← a × n̲ot k s̲elect w
83 crash ← a1 × 1 < k s̲elect k ˡo̲nSquares a1
84 a2 ← a1 × n̲ot crash
85 w2 ← 1 × w ∨ 0 < k ˡo̲nSquares crash
86 caught = 1 ? (4 t̲ake s) c̲at 8 2 c̲at (r̲avel R) c̲at a c̲at w
87 (4 t̲ake s) c̲at (ˡe̲vent s) c̲at (0 = '+ r̲/ a2) c̲at (r̲avel R) c̲at a2 c̲at w2
88}
89
90⍝## Your turn
91⍝ :: Num a => Unit -> a
92⍝# The numeric keypad: 7 8 9 up, 4 5 6 (5 waits), 1 2 3 down; the step
93⍝# for each key as a column of a 2 by 9 table (rows, columns).
94ˡk̲eys ← { @ → 2 9 r̲eshape 1 1 1 0 0 0 -1 -1 -1 -1 0 1 -1 0 1 -1 0 1 }
95
96⍝ :: Int -> Int -> Int
97⍝# What is on square q: 2 a wreck, 1 a live robot, 0 nothing.
98ˡo̲n ← { s q →
99 k ← ˡc̲ell q
100 w ← k s̲elect r̲avel ˡw̲recks s
101 r ← '∨ r̲/ (ˡa̲live s) × k = ˡc̲ell ˡr̲obots s
102 (2 × w) m̲ax r
103}
104
105⍝ :: Num a => a -> a -> a
106⍝# The state after your move: h is where you are (row, column), the
107⍝# teleports left, the event and the status; the turn counts up.
108ˡm̲oved ← { s h → (3 t̲ake h) c̲at (1 + ˡt̲urn s) c̲at (-2 t̲ake h) c̲at ˡhead d̲rop s }
109
110⍝ :: Int -> Int -> Int
111⍝# A turn: key 1 to 9 steps (5 waits), 0 teleports, 99 resigns; then,
112⍝# unless the game ended, the robots chase. The event says what happened:
113⍝# 0 nothing, 1 blocked by the wall, 2 stepped onto a wreck, 3 walked
114⍝# into a robot, 4 no teleports left, 5 teleported, 6 teleported into a
115⍝# wreck, 7 teleported into a robot, 8 caught, 9 resigned.
116ˡm̲ove ← { s c →
117 c = 99 ? s ˡm̲oved (ˡp̲os s) c̲at (ˡt̲ele s) c̲at 9 2
118 c = 0 ? ˡc̲hase ˡt̲eleport s
119 q ← (ˡp̲os s) + c s̲elect₂ ˡk̲eys @
120 wall ← '∨ r̲/ (q < 0) ∨ q > 15
121 wall = 1 ? ˡc̲hase s ˡm̲oved (ˡp̲os s) c̲at (ˡt̲ele s) c̲at 1 0
122 there ← s ˡo̲n q
123 there = 2 ? s ˡm̲oved q c̲at (ˡt̲ele s) c̲at 2 2
124 there = 1 ? s ˡm̲oved q c̲at (ˡt̲ele s) c̲at 3 2
125 ˡc̲hase s ˡm̲oved q c̲at (ˡt̲ele s) c̲at 0 0
126}
127⍝ :: Int -> Int
128⍝# Teleport: the player to a random square, then the robots chase.
129ˡt̲eleport ← { s →
130 t ← ˡt̲ele s
131 t ≤ 0 ? s ˡm̲oved (ˡp̲os s) c̲at 0 c̲at 4 0
132 q ← 16 ᵇr̲c r̲oll! 256
133 there ← s ˡo̲n q
134 there = 2 ? s ˡm̲oved q c̲at (t − 1) c̲at 6 2
135 there = 1 ? s ˡm̲oved q c̲at (t − 1) c̲at 7 2
136 s ˡm̲oved q c̲at (t − 1) c̲at 5 0
137}
138
139⍝ :: Int -> Char
140⍝# What the last event was, as the BASIC said it.
141ˡm̲essage ← { s →
142 e ← ˡe̲vent s
143 d̲isclose (1 + e) s̲elect "" "BLOCKED BY WALL." "YOU STEPPED ONTO WRECKAGE." "YOU WALKED INTO A ROBOT." "NO TELEPORTS LEFT." "*BEEP* TELEPORTED." "TELEPORTED INTO WRECKAGE!" "TELEPORTED INTO A ROBOT!" "A ROBOT CAUGHT YOU!" "*** COMMAND RESIGNED ***"
144}
145
146⍝## What you see
147⍝ :: Int -> Int
148⍝# Every square's code at once, from three 0/1 boards: 1 empty, 2 a
149⍝# robot, 3 a wreck, 4 you, 5 where something got you.
150ˡc̲odes ← { s →
151 code ← 1 + (0 < (ˡc̲ell ˡr̲obots s) ˡo̲nSquares ˡa̲live s) + 2 × r̲avel ˡw̲recks s
152 kp ← ˡc̲ell ˡp̲os s
153 me ← (r̲ange 256) = kp
154 16 16 r̲eshape (code × n̲ot me) + me × 4 + 1 < kp s̲elect code
155}
156⍝ :: Int -> Char
157⍝# The board as text: . empty, R robot, * wreck, P you (X where
158⍝# something got you), spaced, with the BASIC's labels.
159ˡv̲iew ← { s →
160 lab ← 16 3 r̲eshape " 0 1 2 3 4 5 6 7 8 9 A B C D E F "
161 top ← 1 35 r̲eshape " 0 1 2 3 4 5 6 7 8 9 A B C D E F "
162 top c̲at lab c̲at₂ ᵇs̲paced (ˡc̲odes s) s̲elect ".R*PX"
163}
164⍝ :: Int -> Char
165⍝# The board as a picture X_eTaL draws (SVG text): the same characters,
166⍝# one per square of a 16 by 16 grid; []S_HOW shows it.
167ˡp̲icture ← { s → ⎕G̲RID (ˡc̲odes s) s̲elect ".R*PX" }
168⍝ :: Int -> Int
169⍝# The long-range scan: the board in 4 by 4 regions, as a 4 by 4 by 4 by
170⍝# 4 array (region row, row, region column, column) summed over the rows
171⍝# and columns inside each region: robots, wrecks, and where you are.
172ˡl̲rs ← { s →
173 robots ← (ˡc̲ell ˡr̲obots s) ˡo̲nSquares ˡa̲live s
174 me ← (r̲ange 256) = ˡc̲ell ˡp̲os s
175 (ʰr̲egions robots) c̲at₂ (ʰr̲egions r̲avel ˡw̲recks s) c̲at₂ ʰr̲egions me
176}
177⍝# The 256 squares as 4 by 4 regions, each summed (private).
178ʰr̲egions ← { v → '+ r̲/₂₄ 4 4 4 4 r̲eshape v }