sourcegames/capitals/Atlas.xtl

1⍝# The capitals game's rules and its map, a library: capitals.xtl 2⍝# (scripted), play.xtl (the game: a map you click) and the web page all 3⍝# use these. (Named Atlas because a disk that ignores case cannot hold 4⍝# Capitals.xtl beside capitals.xtl.) The data is in two TOML files, read 5⍝# with []L_IST and []T_ABLE: assets/cache/world.toml (Natural Earth's 6⍝# country outlines, national capitals and cities, fetched by 7⍝# assets/fetch.sh, not tracked) and places.toml (the regions, and 8⍝# places of your own). The map is SVG written here. 9⍝# 10⍝# Map units are hundredths of a degree: x = 100 (lon + 180) east from 11⍝# 180 W, y = 100 (90 - lat) south from the pole (the plate carree). A 12⍝# game's state: the region (0 the whole world), the dot whose menu is 13⍝# open (0 none), the score, the number of dots n, then the n places, 14⍝# the n results (0 not yet, 1 right, 2 wrong), then the last dot 15⍝# answered and the city chosen for it. 16 17ᵗ⁼u̲se< "Text" 18ˢᵛ⁼u̲se< "Svg" 19 20⍝## The data 21 22⍝# Every place: the capitals, then places of your own; a row each of 23⍝# name, latitude, longitude and region, as texts. 24all ← (("assets/cache/world.toml" "place") ⎕T̲ABLE ("places" "fields")) c̲at ("places.toml" "place") ⎕T̲ABLE ("places" "fields") 25⍝# The cities the wrong choices come from: Natural Earth's populated 26⍝# places, then places of your own; name, latitude, longitude. 27towns ← (("assets/cache/world.toml" "city") ⎕T̲ABLE ("cities" "cityfields")) c̲at 1 2 3 s̲elect₂ ("places.toml" "place") ⎕T̲ABLE ("places" "fields") 28⍝# A name up to its comma: "Paris, France" is Paris (h:, private to this file). 29ʰc̲ity ← { x → 30 t ← d̲isclose x 31 (n̲ot '∨ s̲\ t = f̲irst ",") r̲eplicate t 32} 33names ← 1 s̲elect₂ all 34lat ← 'ˢᵛn̲um e̲ach 2 s̲elect₂ all 35lon ← 'ˢᵛn̲um e̲ach 3 s̲elect₂ all 36regions ← "places.toml" ⎕L̲IST "regions" 37region ← regions i̲ndexOf 4 s̲elect₂ all 38tlat ← 'ˢᵛn̲um e̲ach 2 s̲elect₂ towns 39tlon ← 'ˢᵛn̲um e̲ach 3 s̲elect₂ towns 40⍝# The countries, unlabeled: each outline (SVG path data in degrees) 41⍝# wrapped as a path, joined once. 42land ← ᵗj̲oin '{ d → e̲nclose "<path d=\"" c̲at (d̲isclose d) c̲at "\"/>" } e̲ach "assets/cache/world.toml" ⎕L̲IST "countries" 43 44⍝## Where things are 45 46⍝ :: Num a => a -> a 47⍝# Map x of longitudes, and map y of latitudes (map units). 48ˡm̲x ← { lo → 100 × 180 + lo } 49⍝ :: Num a => a -> a 50⍝# Map y of latitudes (map units): hundredths of a degree south of the north pole. 51ˡm̲y ← { la → 100 × 90 − la } 52⍝ :: Int -> Int 53⍝# The places of region r (0 the whole world), by number. 54ˡp̲ool ← { r → w̲here (r = 0) ∨ region = r } 55⍝ :: Unit -> Box Char 56⍝# The regions, in the menu's order (from places.toml). 57ˡr̲egions ← { @ → regions } 58⍝ :: Int -> Float 59⍝# Place i's point on the map: x and y (map units). 60ˡa̲t ← { i → (ˡm̲x i s̲elect lon) c̲at ˡm̲y i s̲elect lat } 61⍝ :: Int -> Char 62⍝# Place i's name ("Paris, France"). 63ˡn̲ame ← { i → d̲isclose i s̲elect names } 64⍝ :: Int -> Float 65⍝# What the map shows for region r: x, y, width and height (map units), 66⍝# the region's places with a margin, at least 30 degrees wide and 67⍝# widened or heightened to the shape of the whole map (5 by 2), with 68⍝# room at the top for the buttons. 69ˡv̲iew ← { r → 70 0 = r ? 0.0 600.0 36000.0 14400.0 71 p ← ˡp̲ool r 72 x ← ˡm̲x p s̲elect lon 73 y ← ˡm̲y p s̲elect lat 74 w ← 3000.0 m̲ax 1.3 × ('m̲ax r̲/ x) − 'm̲in r̲/ x 75 h ← 1.3 × ('m̲ax r̲/ y) − 'm̲in r̲/ y 76 w ← w m̲ax 2.5 × h 77 h ← w ÷ 2.5 78 cx ← ('m̲ax r̲/ x) + 'm̲in r̲/ x 79 cy ← ('m̲ax r̲/ y) + 'm̲in r̲/ y 80 ((cx − 1.2 × w) ÷ 2) c̲at (((cy − h) ÷ 2) − 0.2 × h) c̲at (1.2 × w) c̲at 1.2 × h 81} 82⍝ :: Int -> Float 83⍝# The cosine of the angle at the Earth's center between place i and 84⍝# every city at once (the spherical law of cosines): larger is nearer. 85ˡc̲lose ← { i → 86 r ← (p̲i @) ÷ 180 87 a ← r × i s̲elect lat 88 b ← r × i s̲elect lon 89 ((s̲in a) × s̲in r × tlat) + (c̲os a) × (c̲os r × tlat) × c̲os b − r × tlon 90} 91⍝ :: Int -> Char 92⍝# City k's name, up to any comma. 93ˡt̲own ← { k → ʰc̲ity k s̲elect 1 s̲elect₂ towns } 94⍝ :: Int -> Int 95⍝# Place i's choices, as city numbers: the nearest cities with 96⍝# different names, four of them, west to east. The nearest is the 97⍝# place itself (every capital is among the cities), so it is the answer. 98ˡc̲hoices ← { i → 99 o ← 12 t̲ake g̲rade n̲eg ˡc̲lose i 100 nm ← '{ k → e̲nclose ˡt̲own k } e̲ach o 101 c ← 4 t̲ake ((nm i̲ndexOf nm) = r̲ange t̲ally nm) r̲eplicate o 102 (g̲rade c s̲elect tlon) s̲elect c 103} 104⍝ :: Int -> Int 105⍝# Place i's answer among the cities. 106ˡa̲nswer ← { i → f̲irst g̲rade n̲eg ˡc̲lose i } 107 108⍝## The game 109 110⍝ :: Int -> Int 111⍝# A new round in region r (0 the whole world): five of its places drawn 112⍝# at random (all of them when it has fewer), no menu open, no score. 113ˡn̲ew ← { r → 114 p ← ˡp̲ool r 115 n ← 5 m̲in t̲ally p 116 r c̲at 0 0 c̲at n c̲at (n t̲ake (g̲rade r̲oll! (t̲ally p) r̲eshape 1000000) s̲elect p) c̲at (n r̲eshape 0) c̲at 0 0 117} 118⍝ :: a -> a 119⍝# The round's score. 120ˡs̲core ← { s → 3 s̲elect s } 121⍝ :: Int -> Int 122⍝# The round's places, and each one's result (0 not yet, 1 right, 2 wrong). 123ˡd̲ots ← { s → (4 s̲elect s) t̲ake 4 d̲rop s } 124⍝ :: Int -> Int 125⍝# Each dot's result in the round: 0 not yet, 1 right, 2 wrong. 126ˡr̲esults ← { s → (4 s̲elect s) t̲ake (4 + 4 s̲elect s) d̲rop s } 127⍝ :: (Num a, Truthy a) => Int -> a 128⍝# 0 playing, 1 when every dot is answered. 129ˡs̲tatus ← { s → 0 + 0 = '+ r̲/ 0 + 0 = ˡr̲esults s } 130 131⍝ :: Int -> Float 132⍝# The open dot's menu: a row each of x, y, width and height for its 133⍝# four choices, beside the dot (Svg's menu), kept inside the view. 134ˡm̲enu ← { s → (ˡv̲iew f̲irst s) ˢᵛm̲enu ˡa̲t (2 s̲elect s) s̲elect ˡd̲ots s } 135⍝# Which unanswered dot is at xy: the nearest within reach (dots of close 136⍝# capitals overlap), 1 to n, 0 for none. 137ʰd̲ot ← { s xy → 138 u ← ˢᵛu̲nit ˡv̲iew f̲irst s 139 p ← ˡd̲ots s 140 dx ← (ˡm̲x p s̲elect lon) − f̲irst xy 141 dy ← (ˡm̲y p s̲elect lat) − 2 s̲elect xy 142 d2 ← (dx × dx) + dy × dy 143 ok ← (d2 ≤ (u × 14) × u × 14) ∧ 0 = ˡr̲esults s 144 0 = '+ r̲/ 0 + ok ? 0 145 f̲irst g̲rade d2 + 1e12 × f̲loat n̲ot ok 146} 147⍝ :: Int -> Float -> Int 148⍝# A click at xy (map units): a region button starts a round there; in 149⍝# an open menu a choice answers its dot; a dot not yet answered opens 150⍝# its menu; anywhere else closes the menu. 151ˡc̲lick ← { s xy → 152 b ← ((ˡv̲iew f̲irst s) ˢᵛb̲uttons 1 + t̲ally regions) ˢᵛh̲it xy 153 b > 0 ? ˡn̲ew b − 1 154 (2 s̲elect s) > 0 ? s ʰo̲pen xy 155 (2 c̲at s ʰd̲ot xy) ʰs̲et s 156} 157⍝# A click while a menu is open: a choice answers; elsewhere, another 158⍝# dot's menu opens or the menu closes. 159ʰo̲pen ← { s xy → 160 k ← (ˡm̲enu s) ˢᵛh̲it xy 161 k > 0 ? s ʰa̲nswer k 162 (2 c̲at s ʰd̲ot xy) ʰs̲et s 163} 164⍝# The state with item k (from 1) replaced by v: kv is k v. 165ʰs̲et ← { kv s → s + ((2 s̲elect kv) − (f̲irst kv) s̲elect s) × (f̲irst kv) = r̲ange t̲ally s } 166⍝# Choice k of the open dot: right or wrong, the score, the menu closed. 167ʰa̲nswer ← { s k → 168 d ← 2 s̲elect s 169 i ← d s̲elect ˡd̲ots s 170 pick ← k s̲elect ˡc̲hoices i 171 right ← pick = ˡa̲nswer i 172 n ← 4 s̲elect s 173 t ← (4 + n + d) c̲at 2 − right 174 s ← (2 c̲at 0) ʰs̲et (3 c̲at (3 s̲elect s) + right) ʰs̲et t ʰs̲et s 175 (-2 d̲rop s) c̲at d c̲at pick 176} 177 178⍝## What the game says and draws 179 180⍝# The end of a round, said after its last answer (else nothing). 181ʰr̲oundOver ← { s → 182 0 = ˡs̲tatus s ? "" 183 @ f̲ormat< " Round over: {l:s_core s} of {4 s_elect s}. Choose a region for another round."
f̲ormat< expands to
" Round over: " c̲at (f̲ormat (ˡs̲core s)) c̲at " of " c̲at (f̲ormat (4 s̲elect s)) c̲at ". Choose a region for another round."
184} 185⍝ :: Int -> Char 186⍝# What has happened, in a line: the open menu, the last answer, the 187⍝# round's end. 188ˡs̲ay ← { s → 189 (2 s̲elect s) > 0 ? "Which city is this?" 190 d ← ((t̲ally s) − 1) s̲elect s 191 last ← (1 + d) s̲elect 0 c̲at ˡr̲esults s 192 note ← ʰr̲oundOver s 193 0 = d ? "Click a red dot: which city is it?" 194 i ← d s̲elect ˡd̲ots s 195 1 = last ? @ f̲ormat< "Right: {l:n_ame i}.{note}"
f̲ormat< expands to
("Right: " c̲at (f̲ormat (ˡn̲ame i)) c̲at "." c̲at (f̲ormat (note)))
196 @ f̲ormat< "No: that is {l:n_ame i}, not {l:t_own (t_ally s) s_elect s}.{note}"
f̲ormat< expands to
"No: that is " c̲at (f̲ormat (ˡn̲ame i)) c̲at ", not " c̲at (f̲ormat (ˡt̲own (t̲ally s) s̲elect s)) c̲at "." c̲at (f̲ormat (note))
197} 198⍝# A dot's color by its result (0 not yet, red; 1 right, green; 2 199⍝# wrong, orange). 200ʰd̲otColor ← { r → d̲isclose (1 + r) s̲elect "#d62828" "#2a9d3a" "#f08c00" } 201⍝# The round's dots: red not yet answered, green right, orange wrong; 202⍝# each answered one named. 203ʰs̲pots ← { s u → 204 p ← ˡd̲ots s 205 r ← ˡr̲esults s 206 x ← ˡm̲x p s̲elect lon 207 y ← ˡm̲y p s̲elect lat 208 dots ← '{ k → e̲nclose @ f̲ormat< "<circle class=\"dot\" cx=\"{sv:w_hole k s_elect x}\" cy=\"{sv:w_hole k s_elect y}\" r=\"{sv:w_hole u * 9}\" fill=\"{h:d_otColor k s_elect r}\" stroke=\"#ffffff\" stroke-width=\"{sv:w_hole u * 2}\"/>" } e̲ach r̲ange t̲ally p
f̲ormat< expands to
("<circle class=\"dot\" cx=\"" c̲at (f̲ormat (ˢᵛw̲hole k s̲elect x)) c̲at "\" cy=\"" c̲at (f̲ormat (ˢᵛw̲hole k s̲elect y)) c̲at "\" r=\"" c̲at (f̲ormat (ˢᵛw̲hole u × 9)) c̲at "\" fill=\"" c̲at (f̲ormat (ʰd̲otColor k s̲elect r)) c̲at "\" stroke=\"#ffffff\" stroke-width=\"" c̲at (f̲ormat (ˢᵛw̲hole u × 2)) c̲at "\"/>")
209 named ← w̲here r > 0 210 dots c̲at '{ k → (((k s̲elect x) + u × 13) c̲at ((k s̲elect y) + u × 6) c̲at u × 18) ˢᵛw̲ords ʰc̲ity (k s̲elect p) s̲elect names } e̲ach named 211} 212⍝# The region buttons (the region shown filled), and the score. 213ʰb̲ar ← { s v → 214 u ← ˢᵛu̲nit v 215 score ← ((f̲irst v) + (3 s̲elect v) − u × 190) c̲at ((2 s̲elect v) + u × 34) c̲at u × 22 216 ((v c̲at f̲loat 1 + f̲irst s) ˢᵛl̲abeled (e̲nclose "World") c̲at regions) c̲at score ˢᵛw̲ords @ f̲ormat< "Score {l:s_core s} of {4 s_elect s}"
f̲ormat< expands to
("Score " c̲at (f̲ormat (ˡs̲core s)) c̲at " of " c̲at (f̲ormat (4 s̲elect s)))
217} 218⍝# What happened, along the bottom. 219ʰs̲aid ← { s v → 220 u ← ˢᵛu̲nit v 221 (((f̲irst v) + u × 12) c̲at ((2 s̲elect v) + (4 s̲elect v) − u × 16) c̲at u × 22) ˢᵛw̲ords ˡs̲ay s 222} 223⍝# The open menu: the four choices, each a button. 224ʰc̲hoose ← { s → 225 0 = 2 s̲elect s ? 0 r̲eshape e̲nclose "" 226 (ˡm̲enu s) ˢᵛc̲hoices '{ k → e̲nclose ˡt̲own k } e̲ach ˡc̲hoices (2 s̲elect s) s̲elect ˡd̲ots s 227} 228⍝ :: Int -> Char 229⍝# The picture, as SVG text: the sea, the countries (unlabeled, outlines 230⍝# in degrees under the projection's transform), the dots, the region 231⍝# buttons and the score, what happened, and the open menu. Every 232⍝# coordinate is computed here; the page only shows the picture. 233ˡd̲raw ← { s → 234 v ← ˡv̲iew f̲irst s 235 u ← ˢᵛu̲nit v 236 sea ← v ˢᵛr̲ect "fill=\"#bfdcef\"" 237 open ← e̲nclose @ f̲ormat< "<g transform=\"matrix(100 0 0 -100 18000 9000)\" fill=\"#f3eee0\" stroke=\"#7a7a7a\" stroke-width=\"{u / 100}\">"
f̲ormat< expands to
("<g transform=\"matrix(100 0 0 -100 18000 9000)\" fill=\"#f3eee0\" stroke=\"#7a7a7a\" stroke-width=\"" c̲at (f̲ormat (u ÷ 100)) c̲at "\">")
238 v ˢᵛp̲icture sea c̲at open c̲at (e̲nclose land) c̲at (e̲nclose "</g>") c̲at (s ʰs̲pots u) c̲at (s ʰb̲ar v) c̲at (s ʰs̲aid v) c̲at ʰc̲hoose s 239}