librarygames/trek/StarTrek.xtl
Star Trek's rules, a library: trek.xtl (scripted), play.xtl (at the terminal) and the web page all use these. The classic Star Trek game as the COR24 BASIC demo plays it: an 8 by 8 galaxy of quadrants, each an 8 by 8 grid of sectors; find and destroy every Klingon before the stardate runs out, with warp and impulse drive, phasers, photon torpedoes and shields.
The game is one vector: 1 energy, 2 torpedoes, 3 shields, 4 stardate, 5 deadline, 6 and 7 the quadrant (row, column, 0 to 7), 8 and 9 the sector, 10 the status (0 playing, 1 won, 2 out of time, 3 ship lost, 4 resigned); then the galaxy as three planes of 64 (Klingons, bases, stars per quadrant) and the current quadrant's 64 sectors (0 empty, 1 the Enterprise, 2 a star, 3 a base, 4 and more a Klingon: its hit points plus 4).
Reading the state
ˡs̲ector : a -> a
The sector within the quadrant: row and column, 0 to 7.
ˡs̲ector ← { s → 8 9 s̲elect s }
ˡs̲tatus : a -> a
0 playing, 1 won, 2 time ran out, 3 the Enterprise lost, 4 resigned.
ˡs̲tatus ← { s → 10 s̲elect s }
ˡg̲alaxy : a -> Int -> a
Plane p of the galaxy (1 Klingons, 2 bases, 3 stars), an 8 by 8 matrix.
ˡg̲alaxy ← { s p → 8 8 r̲eshape 64 t̲ake (10 + 64 × p − 1) d̲rop s }
ˡg̲rid : a -> a
The quadrant shown: 8 by 8 sectors (0 empty, then the Enterprise, a star, a base, a Klingon's hit points).
ˡg̲rid ← { s → 8 8 r̲eshape -64 t̲ake s }
ˡk̲lingons : Num a => a -> a
Klingons in the galaxy and in this quadrant.
ˡk̲lingons ← { s → '+ r̲/ r̲avel s ˡg̲alaxy 1 }
ˡh̲ere : (Num a, Num b, Truthy b) => a -> b
How many Klingons are in this quadrant.
ˡh̲ere ← { s → '+ r̲/ r̲avel 4 ≤ ˡg̲rid s }
Changing it
ˡu̲pdate : Int -> Int -> Int
Several items changed at once (lib/State): iv is index, value, ...
ˡu̲pdate ← { s iv → s ˢu̲pdate iv }
ˡp̲ut : Int -> Int -> Int
The items from position p on replaced by v (pv is p then v; lib/State).
ˡp̲ut ← { s pv → s ˢp̲ut pv }
ˡw̲ithGrid : Int -> Int -> Int
The state with a new quadrant grid.
ˡw̲ithGrid ← { s g → s ˡp̲ut ((t̲ally s) − 63) c̲at r̲avel g }
ˡw̲ithPlane : Int -> Int -> Int
The state with plane p of the galaxy replaced (pg is p then its 64 numbers).
ˡw̲ithPlane ← { s pg → s ˡp̲ut (11 + 64 × (f̲irst pg) − 1) c̲at 1 d̲rop pg }
ˡh̲ome : Num a => a -> a
The quadrant's square in a galaxy plane, 1 to 64.
ˡh̲ome ← { s → 1 + (8 × 6 s̲elect s) + 7 s̲elect s }
Where things are, said as the BASIC said it
ˡa̲t : Int -> Char
A row and column (0 to 7) as "C-5".
ˡa̲t ← { rc → ((1 + f̲irst rc) s̲elect "ABCDEFGH") c̲at "-" c̲at f̲ormat 1 + 2 s̲elect rc }
ˡc̲ourses : Num a => Unit -> a
The eight courses of the compass, 1 north, clockwise: the step in rows and columns for each, as a 2 by 8 table.
ˡc̲ourses ← { @ → 2 8 r̲eshape -1 -1 0 1 1 1 0 -1 0 1 1 1 0 -1 -1 -1 }
A new game and a new quadrant
ˡn̲ew : Any a => a -> Int
Every quadrant at once: Klingons in about 15 of each 100 (1 to 3 of them), a base in 2 of 10, 1 to 5 stars. A galaxy without Klingons is rolled again.
ˡn̲ew ← { x → kl ← (85 < r̲oll! 64 r̲eshape 100) × r̲oll! 64 r̲eshape 3 0 = '+ r̲/ kl ? ˡn̲ew x bases ← 2 ≥ r̲oll! 64 r̲eshape 10 stars ← r̲oll! 64 r̲eshape 5 q ← (r̲oll! 8 8) − 1 ˡe̲nter (3000 10 0 2300 2330 c̲at q c̲at 0 0 0) c̲at kl c̲at bases c̲at stars c̲at 64 r̲eshape 0 }
ˡe̲nter : Int -> Int
Entering a quadrant: the Enterprise, its Klingons (5 hit points each), base and stars on different sectors at random, all placed at once: the codes in a row, put into the sectors of a random order (the order's own order is its inverse).
ˡe̲nter ← { s → c ← ˡh̲ome s n ← c s̲elect r̲avel s ˡg̲alaxy 1 b ← c s̲elect r̲avel s ˡg̲alaxy 2 i ← c s̲elect r̲avel s ˡg̲alaxy 3 order ← g̲rade r̲oll! 64 r̲eshape 1000000 codes ← 1 c̲at (n r̲eshape 9) c̲at (b r̲eshape 3) c̲at (i r̲eshape 2) c̲at (63 − n + b + i) r̲eshape 0 e ← (f̲irst order) − 1 t ← (s ˡu̲pdate ˡsectRow c̲at (e d̲iv 8) c̲at ˡsectCol c̲at e m̲od 8) ˡw̲ithGrid (g̲rade order) s̲elect codes 0 = n ? t said ← p̲rint! "" said ← p̲rint! "*** RED ALERT *** RED ALERT *** RED ALERT ***" said ← p̲rint! @ f̲ormat< "*** {n} KLINGON(S) IN QUADRANT {l:a_t l:q_uad s} ***" said ← p̲rint! "*** RED ALERT *** RED ALERT *** RED ALERT ***" t }
Scans
ˡs̲rs : Int -> Int
The short-range scan: the sector grid as characters, every sector at once (a Klingon's code, 4 or more, is cut to 4 and read as K), spaced by lib/Board.
ˡs̲rs ← { s → said ← p̲rint! "" said ← p̲rint! @ f̲ormat< "QUADRANT {l:a_t l:q_uad s} SECTOR {l:a_t l:s_ector s}" said ← p̲rint! " 1 2 3 4 5 6 7 8" shown ← p̲rint! (8 2 r̲eshape "A B C D E F G H ") c̲at₂ ᵇs̲paced (1 + 4 m̲in ˡg̲rid s) s̲elect ".E*BK" said ← p̲rint! @ f̲ormat< "STARDATE {l:d_ate s} ENERGY {l:e_nergy s} SHIELDS {l:s_hields s} TORP {l:t_orps s}" said ← p̲rint! @ f̲ormat< "QUADRANT {l:a_t l:q_uad s} KLINGONS LEFT {l:k_lingons s}" s }
f̲ormat< expands to
("QUADRANT " c̲at (f̲ormat (ˡa̲t ˡq̲uad s)) c̲at " SECTOR " c̲at (f̲ormat (ˡa̲t ˡs̲ector s)))
f̲ormat< expands to
("STARDATE " c̲at (f̲ormat (ˡd̲ate s)) c̲at " ENERGY " c̲at (f̲ormat (ˡe̲nergy s)) c̲at " SHIELDS " c̲at (f̲ormat (ˡs̲hields s)) c̲at " TORP " c̲at (f̲ormat (ˡt̲orps s)))
ˡl̲rs : Int -> Int
The long-range scan: the 5 by 5 quadrants around this one, each as its Klingons, bases and stars in three digits, "***" off the galaxy. The window's rows and columns are a table; the three planes' values for the 25 quadrants are one selection from a 3 by 64 matrix, and every cell's four characters are picked at once from " *0123456789".
ˡl̲rs ← { s → said ← p̲rint! "" said ← p̲rint! @ f̲ormat< "LONG RANGE SCAN AT QUADRANT {l:a_t l:q_uad s}" said ← p̲rint! "LEGEND: K B S K=KLINGONS B=BASES S=STARS ***=VOID" r ← (f̲irst ˡq̲uad s) + -2 -1 0 1 2 c ← (2 s̲elect ˡq̲uad s) + -2 -1 0 1 2 in ← r̲avel ((r ≥ 0) ∧ r ≤ 7) '∧ t̲able (c ≥ 0) ∧ c ≤ 7 cell ← r̲avel 1 + (8 × 0 m̲ax 7 m̲in r) '+ t̲able 0 m̲ax 7 m̲in c planes ← cell s̲elect₂ 3 64 r̲eshape 192 t̲ake 10 d̲rop s digits ← ((3 + planes) × 3 25 r̲eshape in) + 2 × 3 25 r̲eshape n̲ot in shown ← p̲rint! (5 20 r̲eshape r̲avel₂ (25 r̲eshape 1) c̲at digits) s̲elect " *0123456789" s }
Small helpers
ˡi̲nsufficient : Any a => a -> a
Refuse a command for want of energy.
ˡi̲nsufficient ← { s → s ˡs̲ay "INSUFFICIENT ENERGY" }
ˡc̲ell : Num a => a -> a
A sector's number, 1 to 64, from its row and column.
ˡc̲ell ← { rc → 8 ᵇc̲ell rc }
ˡm̲oveTo : Int -> Int -> Int
The Enterprise moved to sector rc: its old sector emptied, the new one marked, both in one expression over the 64 sectors.
ˡm̲oveTo ← { s rc → g ← r̲avel ˡg̲rid s all ← r̲ange 64 g ← g × all ≠ ˡc̲ell ˡs̲ector s g ← (g × all ≠ ˡc̲ell rc) + all = ˡc̲ell rc (s ˡu̲pdate ˡsectRow c̲at (f̲irst rc) c̲at ˡsectCol c̲at 2 s̲elect rc) ˡw̲ithGrid g }
ˡp̲ath : Int -> Int -> Int
A path from the Enterprise along course c for k steps: a 2 by k matrix of rows and columns, every step at once; and which steps are inside the quadrant, and their sectors (clamped, for looking them up).
ˡp̲ath ← { s ck → d ← (f̲irst ck) s̲elect₂ ˡc̲ourses @ k ← r̲ange 2 s̲elect ck ((ˡs̲ector s) 'l̲eft t̲able k) + d '× t̲able k }
ˡi̲nside : (Num a, Truthy b) => a -> b
Whether every point of P (rows and columns) lies inside a quadrant.
ˡi̲nside ← { P → '∧ r̲/ (P ≥ 0) ∧ P ≤ 7 }
ˡc̲ells : Num a => a -> a
The sectors (1 to 64) of the points P, clamped to the quadrant.
ˡc̲ells ← { P → 1 + (8 × 0 m̲ax 7 m̲in 1 s̲elect P) + 0 m̲ax 7 m̲in 2 s̲elect P }
ˡt̲rack : Num a => Int -> a
Print each sector of a flat list of rows and columns, " C-5".
ˡt̲rack ← { v → 0 = t̲ally v ? 0 said ← p̲rint! @ f̲ormat< " {l:a_t 2 t_ake v}" 1 + ˡt̲rack 2 d̲rop v }
The Klingons fire back
ˡf̲ire : Int -> Int
30 to 60 units: the shields take them first, the rest comes off the energy.
ˡf̲ire ← { s → 0 = ˡh̲ere s ? s u ← 29 + r̲oll! 31 h ← ˡs̲hields s e ← (ˡe̲nergy s) − 0 m̲ax u − h h ← 0 m̲ax h − u said ← p̲rint! @ f̲ormat< "HIT - SHIELDS {h} ENERGY {e}" s ˡu̲pdate ˡenergy c̲at e c̲at ˡshieldsAt c̲at h }
Moving
ˡw̲arp : Int -> Int -> Int
Warp: course c, factor p (1 to 8) quadrants; 8 energy and one stardate a factor; clamped at the galaxy's edge; a new quadrant, then a scan.
ˡw̲arp ← { s cp → c ← f̲irst cp p ← 2 s̲elect cp (c < 1) ∨ (c > 8) ∨ (p < 1) ∨ p > 8 ? s (ˡe̲nergy s) < 8 × p ? ˡi̲nsufficient s q ← (ˡq̲uad s) + p × c s̲elect₂ ˡc̲ourses @ said ← (q < 0) ∨ q > 7 told ← ˡc̲lamped '+ r̲/ said q ← 0 m̲ax 7 m̲in q ˡs̲rs ˡe̲nter s ˡu̲pdate ˡenergy c̲at ((ˡe̲nergy s) − 8 × p) c̲at ˡstardate c̲at ((ˡd̲ate s) + p) c̲at ˡquadRow c̲at (f̲irst q) c̲at ˡquadCol c̲at 2 s̲elect q }
ˡc̲lamped : Num a => Int -> a
Say n times that a move was clamped at the galaxy's edge.
ˡc̲lamped ← { n → ˣl̲ines n r̲eshape e̲nclose "OUT OF GALAXY - CLAMPED" }
ˡi̲mpulse : Int -> Int -> Int
Impulse: course c, p (1 to 8) sectors, a unit of energy each. The whole path at once; the first step that leaves the quadrant or meets something decides: through to the end, into the next quadrant, or halted just before the obstacle.
ˡi̲mpulse ← { s cp → c ← f̲irst cp p ← 2 s̲elect cp (c < 1) ∨ (c > 8) ∨ (p < 1) ∨ p > 8 ? s (ˡe̲nergy s) < p ? ˡi̲nsufficient s t ← s ˡu̲pdate ˡenergy c̲at (ˡe̲nergy s) − p P ← s ˡp̲ath c c̲at p in ← ˡi̲nside P busy ← in ∧ 0 ≠ (ˡc̲ells P) s̲elect r̲avel ˡg̲rid s j ← f̲irst (w̲here (n̲ot in) ∨ busy) c̲at p + 1 j > p ? ˡs̲rs t ˡm̲oveTo p s̲elect₂ P 0 = j s̲elect in ? ˡs̲rs t ˡc̲ross c ˡs̲rs t ˡh̲alt j s̲elect₂ (ˡs̲ector s) c̲at₂ P }
ˡc̲ross : Int -> Int -> Int
Leave the quadrant along course c: into the neighbor, clamped at the galaxy's edge.
ˡc̲ross ← { s c → q ← 0 m̲ax 7 m̲in (ˡq̲uad s) + c s̲elect₂ ˡc̲ourses @ ˡe̲nter s ˡu̲pdate ˡquadRow c̲at (f̲irst q) c̲at ˡquadCol c̲at 2 s̲elect q }
ˡh̲alt : Int -> Int -> Int
Stop at sector rc in front of an obstacle.
ˡh̲alt ← { s rc → said ← p̲rint! @ f̲ormat< "OBSTRUCTED - HALTED AT SECTOR {l:a_t rc}" s ˡm̲oveTo rc }
Fighting
ˡp̲hasers : Int -> Int -> Int
Phasers: p energy split over the Klingons here; every Klingon is hit at once (its code less the share); those below 4 are destroyed. One line per Klingon, in sector order; then they fire back.
ˡp̲hasers ← { s p → n ← ˡh̲ere s 0 = n ? s ˡs̲ay "NO KLINGONS HERE" p ≤ 0 ? s p > ˡe̲nergy s ? ˡi̲nsufficient s g ← r̲avel ˡg̲rid s hit ← g ≥ 4 v ← g − hit × p d̲iv n dead ← hit ∧ v < 4 said ← ˡh̲its r̲avel₂ (2 c̲at '+ r̲/ hit) r̲eshape ((w̲here hit) c̲at hit r̲eplicate v) kl ← (r̲avel s ˡg̲alaxy 1) − ('+ r̲/ dead) × (r̲ange 64) = ˡh̲ome s ˡf̲ire ((s ˡu̲pdate ˡenergy c̲at (ˡe̲nergy s) − p) ˡw̲ithGrid v × n̲ot dead) ˡw̲ithPlane 1 c̲at kl }
ˡh̲its : Num a => Int -> a
The phaser report from pairs of a sector and what is left of its Klingon.
ˡh̲its ← { v → 0 = t̲ally v ? 0 k ← (f̲irst v) − 1 rc ← (k d̲iv 8) c̲at k m̲od 8 hp ← (2 s̲elect v) − 4 said ← p̲rint! @ f̲ormat< "KLINGON AT {l:a_t rc}{l:p_hp hp}" 1 + ˡh̲its 2 d̲rop v }
ˡp̲hp : Int -> Char
What a hit leaves of a Klingon: destroyed, or its hit points left.
ˡp̲hp ← { hp → hp < 0 ? " DESTROYED" @ f̲ormat< " HIT - HP LEFT {hp}" }
ˡt̲orpedo : Int -> Int -> Int
A photon torpedo along course c: the whole track at once, to the first thing it meets or the quadrant's edge; a Klingon or a base there is destroyed, a star absorbs it. Then the Klingons fire back.
ˡt̲orpedo ← { s c → 0 ≥ ˡt̲orps s ? s ˡs̲ay "NO TORPEDOES REMAINING" (c < 1) ∨ c > 8 ? s said ← p̲rint! "TORPEDO TRACK:" t ← s ˡu̲pdate ˡtorpedoes c̲at (ˡt̲orps s) − 1 P ← s ˡp̲ath c c̲at 7 in ← ˡi̲nside P what ← in × (ˡc̲ells P) s̲elect r̲avel ˡg̲rid s j ← f̲irst (w̲here (n̲ot in) ∨ what ≠ 0) c̲at 8 hits ← j s̲elect in c̲at 0 shown ← ˡt̲rack r̲avel₂ ((j − 1) + hits) t̲ake₂ P 0 = hits ? ˡf̲ire t ˡs̲ay "TORPEDO MISSED - LEFT QUADRANT" ˡf̲ire t ˡs̲trike (j s̲elect what) c̲at j s̲elect₂ P }
ˡs̲trike : Int -> Int -> Int
What a torpedo did on sector rc (the thing's code first).
ˡs̲trike ← { s wrc → w ← f̲irst wrc rc ← 1 d̲rop wrc at ← ˡa̲t rc k ← ˡc̲ell rc w = 2 ? s ˡs̲ay @ f̲ormat< "TORPEDO ABSORBED BY STAR AT {at}" g ← (r̲avel ˡg̲rid s) × k ≠ r̲ange 64 w = 3 ? (s ˡw̲ithGrid g) ˡw̲ithPlane 2 c̲at (r̲avel s ˡg̲alaxy 2) × (ˡh̲ome s) ≠ r̲ange 64 said ← p̲rint! @ f̲ormat< "*** KLINGON DESTROYED AT {at} ***" (s ˡw̲ithGrid g) ˡw̲ithPlane 1 c̲at (r̲avel s ˡg̲alaxy 1) − (ˡh̲ome s) = r̲ange 64 }
ˡs̲hieldsTo : Int -> Int -> Int
Shields: p energy into the shields (negative drains them back).
ˡs̲hieldsTo ← { s p → 0 > (ˡe̲nergy s) − p ? ˡi̲nsufficient s s ˡu̲pdate ˡenergy c̲at ((ˡe̲nergy s) − p) c̲at ˡshieldsAt c̲at (ˡs̲hields s) + p }
Reports
ˡr̲eport : Int -> Int
The status report: the stardate, energy, shields, torpedoes and Klingons left, the quadrant and the sector.
ˡr̲eport ← { s → said ← p̲rint! "" said ← p̲rint! @ f̲ormat< "STARDATE {l:d_ate s} / {5 s_elect s}" said ← p̲rint! @ f̲ormat< "ENERGY {l:e_nergy s}" said ← p̲rint! @ f̲ormat< "SHIELDS {l:s_hields s}" said ← p̲rint! @ f̲ormat< "TORP {l:t_orps s}" said ← p̲rint! @ f̲ormat< "KLINGONS {l:k_lingons s}" said ← p̲rint! @ f̲ormat< "QUADRANT {l:a_t l:q_uad s}" said ← p̲rint! @ f̲ormat< "SECTOR {l:a_t l:s_ector s}" s }
f̲ormat< expands to
("KLINGONS " c̲at (f̲ormat (ˡk̲lingons s)))
ˡh̲elp : Any a => a -> a
Print the commands.
ˡh̲elp ← { s → said ← p̲rint! "" shown ← ˡl̲ines ( "1 SRS SHORT RANGE SCAN" "2 LRS LONG RANGE SCAN" "3 WAR WARP DRIVE" "4 IMP IMPULSE DRIVE" "5 PHA PHASERS" "6 TOR TORPEDOES" "7 SHE SHIELD CONTROL" "8 STA STATUS" "9 HEL THIS HELP" "0 QUI RESIGN" "" "COURSE COMPASS (FOR WARP/IMPULSE/TORPEDO):" " 8 1 2 1=N 2=NE 3=E 4=SE" " 7 + 3 5=S 6=SW 7=W 8=NW" " 6 5 4" "COORDS: ROWS A-H DOWN, COLS 1-8 ACROSS (E.G. C-5)") s }
ˡl̲ines : Num a => Box Char -> a
Print each string of a list of strings (lib/Text).
ˡl̲ines ← { v → ˣl̲ines v }
The game's course
ˡc̲ommand : Int -> Int -> Int
A command and its arguments: 1 SRS, 2 LRS, 3 warp (course, factor), 4 impulse (course, sectors), 5 phasers (energy), 6 torpedo (course), 7 shields (energy), 8 status, 9 help, 0 resign; then the verdict.
ˡc̲ommand ← { s v → ˡj̲udge s ˡd̲o v }
ˡd̲o : Int -> Int -> Int
Carry out command v (its number, then its arguments).
ˡd̲o ← { s v → c ← f̲irst v a ← 1 d̲rop v c = 1 ? ˡs̲rs s c = 2 ? ˡl̲rs s c = 3 ? s ˡw̲arp a c = 4 ? s ˡi̲mpulse a c = 5 ? s ˡp̲hasers f̲irst a c = 6 ? s ˡt̲orpedo f̲irst a c = 7 ? s ˡs̲hieldsTo f̲irst a c = 8 ? ˡr̲eport s c = 9 ? ˡh̲elp s c = 0 ? s ˡu̲pdate ˡstatus c̲at 4 s ˡs̲ay "?WHAT" }
ˡj̲udge : Int -> Int
The status: out of time first, then the ship lost, then the mission complete (as the BASIC checks them); a resignation stands.
ˡj̲udge ← { s → 4 = ˡs̲tatus s ? s over ← ((ˡd̲ate s) > 5 s̲elect s) c̲at (0 ≥ ˡe̲nergy s) c̲at 0 = ˡk̲lingons s s ˡu̲pdate ˡstatus c̲at f̲irst (over r̲eplicate 2 3 1) c̲at 0 }
ˡe̲nd : Int -> Char
The game's ending, printed, and the Klingons left.
ˡe̲nd ← { s → st ← ˡs̲tatus s said ← p̲rint! "" st = 1 ? ˡm̲ission s said ← p̲rint! d̲isclose (st − 1) s̲elect "*** STARDATE EXPIRED ***" "*** ENTERPRISE LOST ***" "*** COMMAND RESIGNED ***" @ f̲ormat< "KLINGONS REMAINING {l:k_lingons s}" }
f̲ormat< expands to
"KLINGONS REMAINING " c̲at (f̲ormat (ˡk̲lingons s))