librarygames/capitals/Atlas.xtl

The capitals game's rules and its map, a library: capitals.xtl (scripted), play.xtl (the game: a map you click) and the web page all use these. (Named Atlas because a disk that ignores case cannot hold Capitals.xtl beside capitals.xtl.) The data is in two TOML files, read with []L_IST and []T_ABLE: assets/cache/world.toml (Natural Earth's country outlines, national capitals and cities, fetched by assets/fetch.sh, not tracked) and places.toml (the regions, and places of your own). The map is SVG written here.

Map units are hundredths of a degree: x = 100 (lon + 180) east from 180 W, y = 100 (90 - lat) south from the pole (the plate carree). A game's state: the region (0 the whole world), the dot whose menu is open (0 none), the score, the number of dots n, then the n places, the n results (0 not yet, 1 right, 2 wrong), then the last dot answered and the city chosen for it.

source · imports t: lib/Text.xtl; sv: lib/Svg.xtl

The data

all : Box Char

value (private) · line 24

Every place: the capitals, then places of your own; a row each of name, latitude, longitude and region, as texts.

all ← (("assets/cache/world.toml" "place") ⎕T̲ABLE ("places" "fields")) c̲at ("places.toml" "place") ⎕T̲ABLE ("places" "fields")
Used in: names, lat, lon, region

towns : Box Char

value (private) · line 27

The cities the wrong choices come from: Natural Earth's populated places, then places of your own; name, latitude, longitude.

towns ← (("assets/cache/world.toml" "city") ⎕T̲ABLE ("cities" "cityfields")) c̲at 1 2 3 s̲elect₂ ("places.toml" "place") ⎕T̲ABLE ("places" "fields")
Used in: tlat, tlon, ˡt̲own

ʰc̲ity : Box Char -> Char

function (private) · line 29

A name up to its comma: "Paris, France" is Paris (h:, private to this file).

ʰc̲ity ← { x →
  t ← d̲isclose x
  (n̲ot '∨ s̲\ t = f̲irst ",") r̲eplicate t
}

names : Box Char

value (private) · line 33
names ← 1 s̲elect₂ all

lat : Float

value (private) · line 34
lat ← 'ˢᵛn̲um e̲ach 2 s̲elect₂ all

lon : Float

value (private) · line 35
lon ← 'ˢᵛn̲um e̲ach 3 s̲elect₂ all

regions : Box Char

value (private) · line 36
regions ← "places.toml" ⎕L̲IST "regions"

region : Int

value (private) · line 37
region ← regions i̲ndexOf 4 s̲elect₂ all
Used in: ˡp̲ool

tlat : Float

value (private) · line 38
tlat ← 'ˢᵛn̲um e̲ach 2 s̲elect₂ towns
Used in: ˡc̲lose

tlon : Float

value (private) · line 39
tlon ← 'ˢᵛn̲um e̲ach 3 s̲elect₂ towns

land : Char

value (private) · line 42

The countries, unlabeled: each outline (SVG path data in degrees) wrapped as a path, joined once.

land ← ᵗj̲oin '{ d → e̲nclose "<path d=\"" c̲at (d̲isclose d) c̲at "\"/>" } e̲ach "assets/cache/world.toml" ⎕L̲IST "countries"
Used in: ˡd̲raw

Where things are

ˡm̲x : Num a => a -> a

function · line 48

Map x of longitudes, and map y of latitudes (map units).

ˡm̲x ← { lo → 100 × 180 + lo }

ˡm̲y : Num a => a -> a

function · line 51

Map y of latitudes (map units): hundredths of a degree south of the north pole.

ˡm̲y ← { la → 100 × 90 − la }

ˡp̲ool : Int -> Int

function · line 54

The places of region r (0 the whole world), by number.

ˡp̲ool ← { r → w̲here (r = 0) ∨ region = r }

ˡr̲egions : Unit -> Box Char

function · line 57

The regions, in the menu's order (from places.toml).

ˡr̲egions ← { @ → regions }

ˡa̲t : Int -> Float

function · line 60

Place i's point on the map: x and y (map units).

ˡa̲t ← { i → (ˡm̲x i s̲elect lon) c̲at ˡm̲y i s̲elect lat }

ˡn̲ame : Int -> Char

function · line 63

Place i's name ("Paris, France").

ˡn̲ame ← { i → d̲isclose i s̲elect names }

ˡv̲iew : Int -> Float

function · line 69

What the map shows for region r: x, y, width and height (map units), the region's places with a margin, at least 30 degrees wide and widened or heightened to the shape of the whole map (5 by 2), with room at the top for the buttons.

ˡv̲iew ← { r →
  0 = r ? 0.0 600.0 36000.0 14400.0
  p ← ˡp̲ool r
  x ← ˡm̲x p s̲elect lon
  y ← ˡm̲y p s̲elect lat
  w ← 3000.0 m̲ax 1.3 × ('m̲ax r̲/ x) − 'm̲in r̲/ x
  h ← 1.3 × ('m̲ax r̲/ y) − 'm̲in r̲/ y
  w ← w m̲ax 2.5 × h
  h ← w ÷ 2.5
  cx ← ('m̲ax r̲/ x) + 'm̲in r̲/ x
  cy ← ('m̲ax r̲/ y) + 'm̲in r̲/ y
  ((cx − 1.2 × w) ÷ 2) c̲at (((cy − h) ÷ 2) − 0.2 × h) c̲at (1.2 × w) c̲at 1.2 × h
}

ˡc̲lose : Int -> Float

function · line 85

The cosine of the angle at the Earth's center between place i and every city at once (the spherical law of cosines): larger is nearer.

ˡc̲lose ← { i →
  r ← (p̲i @) ÷ 180
  a ← r × i s̲elect lat
  b ← r × i s̲elect lon
  ((s̲in a) × s̲in r × tlat) + (c̲os a) × (c̲os r × tlat) × c̲os b − r × tlon
}

ˡt̲own : Int -> Char

function · line 93

City k's name, up to any comma.

ˡt̲own ← { k → ʰc̲ity k s̲elect 1 s̲elect₂ towns }

ˡc̲hoices : Int -> Int

function · line 98

Place i's choices, as city numbers: the nearest cities with different names, four of them, west to east. The nearest is the place itself (every capital is among the cities), so it is the answer.

ˡc̲hoices ← { i →
  o ← 12 t̲ake g̲rade n̲eg ˡc̲lose i
  nm ← '{ k → e̲nclose ˡt̲own k } e̲ach o
  c ← 4 t̲ake ((nm i̲ndexOf nm) = r̲ange t̲ally nm) r̲eplicate o
  (g̲rade c s̲elect tlon) s̲elect c
}

ˡa̲nswer : Int -> Int

function · line 106

Place i's answer among the cities.

ˡa̲nswer ← { i → f̲irst g̲rade n̲eg ˡc̲lose i }
Used in: ʰa̲nswer, k, ok

The game

ˡn̲ew : Int -> Int

function · line 113

A new round in region r (0 the whole world): five of its places drawn at random (all of them when it has fewer), no menu open, no score.

ˡn̲ew ← { r →
  p ← ˡp̲ool r
  n ← 5 m̲in t̲ally p
  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
}

ˡs̲core : a -> a

function · line 120

The round's score.

ˡs̲core ← { s → 3 s̲elect s }

ˡd̲ots : Int -> Int

function · line 123

The round's places, and each one's result (0 not yet, 1 right, 2 wrong).

ˡd̲ots ← { s → (4 s̲elect s) t̲ake 4 d̲rop s }

ˡr̲esults : Int -> Int

function · line 126

Each dot's result in the round: 0 not yet, 1 right, 2 wrong.

ˡr̲esults ← { s → (4 s̲elect s) t̲ake (4 + 4 s̲elect s) d̲rop s }

ˡs̲tatus : (Num a, Truthy a) => Int -> a

function · line 129

0 playing, 1 when every dot is answered.

ˡs̲tatus ← { s → 0 + 0 = '+ r̲/ 0 + 0 = ˡr̲esults s }
Used in: ʰr̲oundOver

ˡm̲enu : Int -> Float

function · line 134

The open dot's menu: a row each of x, y, width and height for its four choices, beside the dot (Svg's menu), kept inside the view.

ˡm̲enu ← { s → (ˡv̲iew f̲irst s) ˢᵛm̲enu ˡa̲t (2 s̲elect s) s̲elect ˡd̲ots s }
Used in: ʰo̲pen, ʰc̲hoose, b

ʰd̲ot : Int -> Float -> Int

function (private) · line 137

Which unanswered dot is at xy: the nearest within reach (dots of close capitals overlap), 1 to n, 0 for none.

ʰd̲ot ← { s xy →
  u ← ˢᵛu̲nit ˡv̲iew f̲irst s
  p ← ˡd̲ots s
  dx ← (ˡm̲x p s̲elect lon) − f̲irst xy
  dy ← (ˡm̲y p s̲elect lat) − 2 s̲elect xy
  d2 ← (dx × dx) + dy × dy
  ok ← (d2 ≤ (u × 14) × u × 14) ∧ 0 = ˡr̲esults s
  0 = '+ r̲/ 0 + ok ? 0
  f̲irst g̲rade d2 + 1e12 × f̲loat n̲ot ok
}

ˡc̲lick : Int -> Float -> Int

function · line 151

A click at xy (map units): a region button starts a round there; in an open menu a choice answers its dot; a dot not yet answered opens its menu; anywhere else closes the menu.

ˡc̲lick ← { s xy →
  b ← ((ˡv̲iew f̲irst s) ˢᵛb̲uttons 1 + t̲ally regions) ˢᵛh̲it xy
  b > 0 ? ˡn̲ew b − 1
  (2 s̲elect s) > 0 ? s ʰo̲pen xy
  (2 c̲at s ʰd̲ot xy) ʰs̲et s
}
Used in: t, ᵘl̲oop

ʰo̲pen : Int -> Float -> Int

function (private) · line 159

A click while a menu is open: a choice answers; elsewhere, another dot's menu opens or the menu closes.

ʰo̲pen ← { s xy →
  k ← (ˡm̲enu s) ˢᵛh̲it xy
  k > 0 ? s ʰa̲nswer k
  (2 c̲at s ʰd̲ot xy) ʰs̲et s
}
Used in: ˡc̲lick

ʰs̲et : Int -> Int -> Int

function (private) · line 165

The state with item k (from 1) replaced by v: kv is k v.

ʰs̲et ← { kv s → s + ((2 s̲elect kv) − (f̲irst kv) s̲elect s) × (f̲irst kv) = r̲ange t̲ally s }

ʰa̲nswer : Int -> Int -> Int

function (private) · line 167

Choice k of the open dot: right or wrong, the score, the menu closed.

ʰa̲nswer ← { s k →
  d ← 2 s̲elect s
  i ← d s̲elect ˡd̲ots s
  pick ← k s̲elect ˡc̲hoices i
  right ← pick = ˡa̲nswer i
  n ← 4 s̲elect s
  t ← (4 + n + d) c̲at 2 − right
  s ← (2 c̲at 0) ʰs̲et (3 c̲at (3 s̲elect s) + right) ʰs̲et t ʰs̲et s
  (-2 d̲rop s) c̲at d c̲at pick
}
Used in: ʰo̲pen

What the game says and draws

ʰr̲oundOver : Int -> Char

function (private) · line 181

The end of a round, said after its last answer (else nothing).

ʰr̲oundOver ← { s →
  0 = ˡs̲tatus s ? ""
  @ 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."
Used in: ˡs̲ay

ˡs̲ay : Int -> Char

function · line 188

What has happened, in a line: the open menu, the last answer, the round's end.

ˡs̲ay ← { s →
  (2 s̲elect s) > 0 ? "Which city is this?"
  d ← ((t̲ally s) − 1) s̲elect s
  last ← (1 + d) s̲elect 0 c̲at ˡr̲esults s
  note ← ʰr̲oundOver s
  0 = d ? "Click a red dot: which city is it?"
  i ← d s̲elect ˡd̲ots s
  1 = last ? @ f̲ormat< "Right: {l:n_ame i}.{note}"
  @ f̲ormat< "No: that is {l:n_ame i}, not {l:t_own (t_ally s) s_elect s}.{note}"
}
f̲ormat< expands to
("Right: " c̲at (f̲ormat (ˡn̲ame i)) c̲at "." c̲at (f̲ormat (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))

ʰd̲otColor : Int -> Char

function (private) · line 200

A dot's color by its result (0 not yet, red; 1 right, green; 2 wrong, orange).

ʰd̲otColor ← { r → d̲isclose (1 + r) s̲elect "#d62828" "#2a9d3a" "#f08c00" }
Used in: ʰs̲pots

ʰs̲pots : Int -> Float -> Box Char

function (private) · line 203

The round's dots: red not yet answered, green right, orange wrong; each answered one named.

ʰs̲pots ← { s u →
  p ← ˡd̲ots s
  r ← ˡr̲esults s
  x ← ˡm̲x p s̲elect lon
  y ← ˡm̲y p s̲elect lat
  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
  named ← w̲here r > 0
  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
}
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 "\"/>")
Used in: ˡd̲raw

ʰb̲ar : Num a => a -> Float -> Box Char

function (private) · line 213

The region buttons (the region shown filled), and the score.

ʰb̲ar ← { s v →
  u ← ˢᵛu̲nit v
  score ← ((f̲irst v) + (3 s̲elect v) − u × 190) c̲at ((2 s̲elect v) + u × 34) c̲at u × 22
  ((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)))
Used in: ˡd̲raw

ʰs̲aid : Int -> Float -> Box Char

function (private) · line 219

What happened, along the bottom.

ʰs̲aid ← { s v →
  u ← ˢᵛu̲nit v
  (((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
}
Used in: ˡd̲raw

ʰc̲hoose : Int -> Box Char

function (private) · line 224

The open menu: the four choices, each a button.

ʰc̲hoose ← { s →
  0 = 2 s̲elect s ? 0 r̲eshape e̲nclose ""
  (ˡm̲enu s) ˢᵛc̲hoices '{ k → e̲nclose ˡt̲own k } e̲ach ˡc̲hoices (2 s̲elect s) s̲elect ˡd̲ots s
}
Used in: ˡd̲raw

ˡd̲raw : Int -> Char

function · line 233

The picture, as SVG text: the sea, the countries (unlabeled, outlines in degrees under the projection's transform), the dots, the region buttons and the score, what happened, and the open menu. Every coordinate is computed here; the page only shows the picture.

ˡd̲raw ← { s →
  v ← ˡv̲iew f̲irst s
  u ← ˢᵛu̲nit v
  sea ← v ˢᵛr̲ect "fill=\"#bfdcef\""
  open ← e̲nclose @ f̲ormat< "<g transform=\"matrix(100 0 0 -100 18000 9000)\" fill=\"#f3eee0\" stroke=\"#7a7a7a\" stroke-width=\"{u / 100}\">"
  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
}
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 "\">")
Used in: pic, ᵘl̲oop