silver-tongue 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/courses/zh-china-en/course.json +1 -1
- package/dist/silver-tongue.mjs +121 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> You arrive speaking pidgin; you leave with a silver tongue.
|
|
4
4
|
|
|
5
|
-
A language-learning life game.
|
|
5
|
+
A language-learning life game. Your phone is dead, your wallet is gone, and you're standing on a street in a Chinese city with ¥20 in your pocket. An old man on a bench teaches you your first words; after that you earn your living by understanding people. Every job, purchase and conversation happens in the language you're learning.
|
|
6
6
|
|
|
7
7
|
The first course is Mandarin (HSK 1), in a Chinese city, explained in English.
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ npx silver-tongue
|
|
|
15
15
|
| Key | Does |
|
|
16
16
|
|---|---|
|
|
17
17
|
| `1`–`9` | choose from the menu, or pick a reply |
|
|
18
|
-
| `w` | word help: look up a word from the last line |
|
|
18
|
+
| `w` | word help: look up a word from the last line or the replies; `s` explains the whole sentence |
|
|
19
19
|
| `enter` / `⌫` | say / undo, when building a reply from tiles |
|
|
20
20
|
| `esc` | back |
|
|
21
21
|
| `q` | save and quit (from the menu) |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"zh-china-en","typing":false,"words":{"w0003":{"id":"w0003","w":"三","lv":"1","gloss":"three; 3","pron":"sān"},"w0009":{"id":"w0009","w":"不","lv":"1","gloss":"no; not","pron":"bù"},"w0020":{"id":"w0020","w":"五","lv":"1","gloss":"five; 5","pron":"wǔ"},"w0028":{"id":"w0028","w":"你","lv":"1","gloss":"you (informal)","pron":"nǐ"},"w0037":{"id":"w0037","w":"几","lv":"1","gloss":"how much; how many","pron":"jǐ"},"w0051":{"id":"w0051","w":"吗","lv":"1","gloss":"(question particle for \"yes-no\" questions)","pron":"ma"},"w0060":{"id":"w0060","w":"四","lv":"1","gloss":"four; 4","pron":"sì"},"w0072":{"id":"w0072","w":"好","lv":"1","gloss":"good; appropriate; proper","pron":"hǎo"},"w0084":{"id":"w0084","w":"工作","lv":"1","gloss":"to work; (of a machine) to operate","pron":"gōngzuò"},"w0099":{"id":"w0099","w":"是","lv":"1","gloss":"to be (followed by substantives only); correct; right; true","pron":"shì"},"w0108":{"id":"w0108","w":"水","lv":"1","gloss":"water; river","pron":"shuǐ"},"w0133":{"id":"w0133","w":"茶","lv":"1","gloss":"tea; tea plant","pron":"chá"},"x0001":{"id":"x0001","w":"杯","lv":"1","gloss":"cup; glass (measure word for drinks)","pron":"bēi","bonus":true}},"concepts":{"tea":["w0133"],"water":["w0108"],"three":["w0003"],"four":["w0060"],"five":["w0020"]},"groups":{"drinks":["tea","water"],"numbers_3_5":["three","four","five"]},"world":{"start":"street","currency":"¥","slotsPerDay":4,"startWallet":20,"foodPerDay":5,"rentPerWeek":50,"places":{"street":{"links":["noodle_shop"]},"noodle_shop":{"links":["street"]}},"npcs":{"cook":{"place":"noodle_shop"}}},"scenes":[{"id":"noodle-intro","place":"noodle_shop","npc":"cook","stage":1,"after":[],"requires":{},"repeatable":false,"trustGain":1,"exchanges":[{"id":"greet","slots":{},"expect":{"action":"greet"},"hinges":[],"pay":0,"missCost":0,"variants":{"":{"npc":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}]},"reply":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}]}}}},{"id":"job","slots":{},"expect":{"action":"accept"},"hinges":[],"pay":0,"missCost":0,"variants":{"":{"npc":{"text":"工作,好吗?","tokens":[{"start":0,"end":2,"word":"w0084"},{"start":3,"end":4,"word":"w0072"},{"start":4,"end":5,"word":"w0051"}]},"reply":{"text":"好。","tokens":[{"start":0,"end":1,"word":"w0072"}]}}}}]},{"id":"noodle-shift","place":"noodle_shop","npc":"cook","stage":1,"after":["noodle-intro"],"requires":{"trust":{"cook":1}},"repeatable":true,"trustGain":1,"exchanges":[{"id":"drink","slots":{"item":"drinks"},"expect":{"action":"fetch","item":"$item"},"hinges":["$item"],"pay":1,"missCost":1,"variants":{"item=tea":{"npc":{"text":"茶。","tokens":[{"start":0,"end":1,"word":"w0133"}]},"reply":{"text":"好,茶。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0133"}]}},"item=water":{"npc":{"text":"水。","tokens":[{"start":0,"end":1,"word":"w0108"}]},"reply":{"text":"好,水。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0108"}]}}}},{"id":"order","slots":{"item":"drinks","count":"numbers_3_5"},"expect":{"action":"serve","item":"$item","count":"$count"},"hinges":["$item","$count"],"pay":3,"missCost":2,"variants":{"count=three|item=tea":{"npc":{"text":"三杯茶。","tokens":[{"start":0,"end":1,"word":"w0003"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0133"}]},"reply":{"text":"好,三杯茶。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0003"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0133"}]},"rephrase":{"text":"茶。三杯。","tokens":[{"start":0,"end":1,"word":"w0133"},{"start":2,"end":3,"word":"w0003"},{"start":3,"end":4,"word":"x0001"}]}},"count=three|item=water":{"npc":{"text":"三杯水。","tokens":[{"start":0,"end":1,"word":"w0003"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0108"}]},"reply":{"text":"好,三杯水。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0003"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0108"}]},"rephrase":{"text":"水。三杯。","tokens":[{"start":0,"end":1,"word":"w0108"},{"start":2,"end":3,"word":"w0003"},{"start":3,"end":4,"word":"x0001"}]}},"count=four|item=tea":{"npc":{"text":"四杯茶。","tokens":[{"start":0,"end":1,"word":"w0060"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0133"}]},"reply":{"text":"好,四杯茶。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0060"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0133"}]},"rephrase":{"text":"茶。四杯。","tokens":[{"start":0,"end":1,"word":"w0133"},{"start":2,"end":3,"word":"w0060"},{"start":3,"end":4,"word":"x0001"}]}},"count=four|item=water":{"npc":{"text":"四杯水。","tokens":[{"start":0,"end":1,"word":"w0060"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0108"}]},"reply":{"text":"好,四杯水。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0060"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0108"}]},"rephrase":{"text":"水。四杯。","tokens":[{"start":0,"end":1,"word":"w0108"},{"start":2,"end":3,"word":"w0060"},{"start":3,"end":4,"word":"x0001"}]}},"count=five|item=tea":{"npc":{"text":"五杯茶。","tokens":[{"start":0,"end":1,"word":"w0020"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0133"}]},"reply":{"text":"好,五杯茶。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0020"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0133"}]},"rephrase":{"text":"茶。五杯。","tokens":[{"start":0,"end":1,"word":"w0133"},{"start":2,"end":3,"word":"w0020"},{"start":3,"end":4,"word":"x0001"}]}},"count=five|item=water":{"npc":{"text":"五杯水。","tokens":[{"start":0,"end":1,"word":"w0020"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0108"}]},"reply":{"text":"好,五杯水。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0020"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0108"}]},"rephrase":{"text":"水。五杯。","tokens":[{"start":0,"end":1,"word":"w0108"},{"start":2,"end":3,"word":"w0020"},{"start":3,"end":4,"word":"x0001"}]}}}}]}],"reactions":{"wrong-generic":{"text":"不是。","tokens":[{"start":0,"end":1,"word":"w0009"},{"start":1,"end":2,"word":"w0099"}]},"wrong-count":{"text":"几杯?","tokens":[{"start":0,"end":1,"word":"w0037"},{"start":1,"end":2,"word":"x0001"}]}},"learnerFtl":"# Text-game UI. Every id here is required by packages/tui (UI_KEYS).\n\nhud = Day { $day } · slot { $slot }/{ $slots } · { $currency }{ $wallet } · { $rank }\n\nrank-0 = Pidgin\nrank-1 = Getting By\nrank-2 = Conversational\nrank-3 = Fluent\nrank-4 = Silver Tongue\n\nmenu-title = What now?\nmenu-talk = Talk to { $npc }: { $scene }\nmenu-go = Go to { $place }\nmenu-sleep = Sleep (end the day)\nmenu-quit = Save and quit\n\nkeys-explore = [1-9] choose · [q] quit\nkeys-pick = [1-4] reply · [w] word help\nkeys-tiles = [1-9] add tile · [⌫] undo · [enter] say it · [w] word help\nkeys-help = [1-9] look up · [esc] back\n\nhelp-title = Which word?\ntiles-answer = You say:\n\nmismatch = That's not what they asked for.\nrephrased = (slower)\nwallet-change = { $sign }{ $currency }{ $amount } ({ $reason })\nreason-wages = wages\nreason-mixup = mix-up\nreason-food = food\nreason-rent = rent\ntrust-up = { $npc } trusts you a little more ({ $trust }).\nscene-done = Done. You earned { $currency }{ $earned }.\nunlocked = New: { $scene }\nrank-up = You're now: { $rank }\nday-ended = Day { $day } is over. You sleep.\nreject-unknown-scene = There's nobody here for that.\nreject-in-scene = Finish the conversation first.\nreject-wrong-place = They're not here.\nreject-locked = They're not ready to talk about that yet.\nreject-no-slots = You're out of time today. Sleep first.\nreject-stale-run = That conversation can't continue. Start it again.\nreject-no-pick = Choose a reply with the number keys.\nreject-bad-choice = There's no reply with that number.\nreject-no-tiles = Build your reply from the tiles.\nreject-bad-tile = There's no tile with that number.\nreject-not-linked = You can't get there from here.\nreject-unknown-word = That word isn't in the dictionary.\nnotice-read-only = Your progress can't be saved on this computer, so this session won't be kept.\nnotice-bad-save = Your save couldn't be read. It was kept as a backup and a new game started.\n\nplace-street = The street\nplace-street-desc = Bikes, steam and shouting. A noodle shop glows across the road.\nplace-noodle_shop = Noodle shop\nplace-noodle_shop-desc = Steam everywhere. The cook waves you over.\n\nnpc-cook = Cook\n\nscene-noodle-intro = Get a job\nscene-noodle-shift = Work a shift\n"}
|
|
1
|
+
{"id":"zh-china-en","typing":false,"words":{"w0003":{"id":"w0003","w":"三","lv":"1","gloss":"three; 3","pron":"sān"},"w0009":{"id":"w0009","w":"不","lv":"1","gloss":"no; not","pron":"bù"},"w0010":{"id":"w0010","w":"不客气","lv":"1","gloss":"you're welcome; don't mention it","pron":"bùkèqi"},"w0020":{"id":"w0020","w":"五","lv":"1","gloss":"five; 5","pron":"wǔ"},"w0028":{"id":"w0028","w":"你","lv":"1","gloss":"you (informal)","pron":"nǐ"},"w0034":{"id":"w0034","w":"再见","lv":"1","gloss":"goodbye","pron":"zàijiàn"},"w0037":{"id":"w0037","w":"几","lv":"1","gloss":"how much; how many","pron":"jǐ"},"w0051":{"id":"w0051","w":"吗","lv":"1","gloss":"(question particle for \"yes-no\" questions)","pron":"ma"},"w0060":{"id":"w0060","w":"四","lv":"1","gloss":"four; 4","pron":"sì"},"w0072":{"id":"w0072","w":"好","lv":"1","gloss":"good; appropriate; proper","pron":"hǎo"},"w0079":{"id":"w0079","w":"对不起","lv":"1","gloss":"I'm sorry; excuse me; I beg your pardon","pron":"duìbuqǐ"},"w0084":{"id":"w0084","w":"工作","lv":"1","gloss":"to work; (of a machine) to operate","pron":"gōngzuò"},"w0099":{"id":"w0099","w":"是","lv":"1","gloss":"to be (followed by substantives only); correct; right; true","pron":"shì"},"w0108":{"id":"w0108","w":"水","lv":"1","gloss":"water; river","pron":"shuǐ"},"w0112":{"id":"w0112","w":"没关系","lv":"1","gloss":"it doesn't matter","pron":"méiguānxi"},"w0133":{"id":"w0133","w":"茶","lv":"1","gloss":"tea; tea plant","pron":"chá"},"w0141":{"id":"w0141","w":"谢谢","lv":"1","gloss":"to thank; thanks","pron":"xièxie"},"x0001":{"id":"x0001","w":"杯","lv":"1","gloss":"cup; glass (measure word for drinks)","pron":"bēi","bonus":true}},"concepts":{"tea":["w0133"],"water":["w0108"],"three":["w0003"],"four":["w0060"],"five":["w0020"],"hello":["w0028","w0072"],"thanks":["w0141"],"sorry":["w0079"],"bye":["w0034"]},"groups":{"courtesies":["hello","thanks","sorry","bye"],"drinks":["tea","water"],"numbers_3_5":["three","four","five"]},"world":{"start":"street","currency":"¥","slotsPerDay":4,"startWallet":20,"foodPerDay":5,"rentPerWeek":50,"places":{"street":{"links":["noodle_shop"]},"noodle_shop":{"links":["street"]}},"npcs":{"wang":{"place":"street"},"cook":{"place":"noodle_shop"}}},"scenes":[{"id":"noodle-intro","place":"noodle_shop","npc":"cook","stage":1,"after":["street-hello"],"requires":{},"repeatable":false,"trustGain":1,"exchanges":[{"id":"greet","slots":{},"expect":{"action":"greet"},"hinges":[],"pay":0,"missCost":0,"variants":{"":{"npc":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"},"reply":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"}}}},{"id":"job","slots":{},"expect":{"action":"accept"},"hinges":[],"pay":0,"missCost":0,"variants":{"":{"npc":{"text":"工作,好吗?","tokens":[{"start":0,"end":2,"word":"w0084"},{"start":3,"end":4,"word":"w0072"},{"start":4,"end":5,"word":"w0051"}],"meaning":"Work here — OK?"},"reply":{"text":"好。","tokens":[{"start":0,"end":1,"word":"w0072"}],"meaning":"OK."}}}}]},{"id":"noodle-shift","place":"noodle_shop","npc":"cook","stage":1,"after":["noodle-intro"],"requires":{"trust":{"cook":1}},"repeatable":true,"trustGain":1,"exchanges":[{"id":"drink","slots":{"item":"drinks"},"expect":{"action":"fetch","item":"$item"},"hinges":["$item"],"pay":1,"missCost":1,"variants":{"item=tea":{"npc":{"text":"茶。","tokens":[{"start":0,"end":1,"word":"w0133"}],"meaning":"Tea."},"reply":{"text":"好,茶。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0133"}],"meaning":"OK, tea."}},"item=water":{"npc":{"text":"水。","tokens":[{"start":0,"end":1,"word":"w0108"}],"meaning":"Water."},"reply":{"text":"好,水。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0108"}],"meaning":"OK, water."}}}},{"id":"order","slots":{"item":"drinks","count":"numbers_3_5"},"expect":{"action":"serve","item":"$item","count":"$count"},"hinges":["$item","$count"],"pay":3,"missCost":2,"variants":{"count=three|item=tea":{"npc":{"text":"三杯茶。","tokens":[{"start":0,"end":1,"word":"w0003"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0133"}],"meaning":"Three cups of tea."},"reply":{"text":"好,三杯茶。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0003"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0133"}],"meaning":"OK, three cups of tea."},"rephrase":{"text":"茶。三杯。","tokens":[{"start":0,"end":1,"word":"w0133"},{"start":2,"end":3,"word":"w0003"},{"start":3,"end":4,"word":"x0001"}],"meaning":"Tea. Three cups."}},"count=three|item=water":{"npc":{"text":"三杯水。","tokens":[{"start":0,"end":1,"word":"w0003"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0108"}],"meaning":"Three cups of water."},"reply":{"text":"好,三杯水。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0003"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0108"}],"meaning":"OK, three cups of water."},"rephrase":{"text":"水。三杯。","tokens":[{"start":0,"end":1,"word":"w0108"},{"start":2,"end":3,"word":"w0003"},{"start":3,"end":4,"word":"x0001"}],"meaning":"Water. Three cups."}},"count=four|item=tea":{"npc":{"text":"四杯茶。","tokens":[{"start":0,"end":1,"word":"w0060"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0133"}],"meaning":"Four cups of tea."},"reply":{"text":"好,四杯茶。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0060"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0133"}],"meaning":"OK, four cups of tea."},"rephrase":{"text":"茶。四杯。","tokens":[{"start":0,"end":1,"word":"w0133"},{"start":2,"end":3,"word":"w0060"},{"start":3,"end":4,"word":"x0001"}],"meaning":"Tea. Four cups."}},"count=four|item=water":{"npc":{"text":"四杯水。","tokens":[{"start":0,"end":1,"word":"w0060"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0108"}],"meaning":"Four cups of water."},"reply":{"text":"好,四杯水。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0060"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0108"}],"meaning":"OK, four cups of water."},"rephrase":{"text":"水。四杯。","tokens":[{"start":0,"end":1,"word":"w0108"},{"start":2,"end":3,"word":"w0060"},{"start":3,"end":4,"word":"x0001"}],"meaning":"Water. Four cups."}},"count=five|item=tea":{"npc":{"text":"五杯茶。","tokens":[{"start":0,"end":1,"word":"w0020"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0133"}],"meaning":"Five cups of tea."},"reply":{"text":"好,五杯茶。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0020"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0133"}],"meaning":"OK, five cups of tea."},"rephrase":{"text":"茶。五杯。","tokens":[{"start":0,"end":1,"word":"w0133"},{"start":2,"end":3,"word":"w0020"},{"start":3,"end":4,"word":"x0001"}],"meaning":"Tea. Five cups."}},"count=five|item=water":{"npc":{"text":"五杯水。","tokens":[{"start":0,"end":1,"word":"w0020"},{"start":1,"end":2,"word":"x0001"},{"start":2,"end":3,"word":"w0108"}],"meaning":"Five cups of water."},"reply":{"text":"好,五杯水。","tokens":[{"start":0,"end":1,"word":"w0072"},{"start":2,"end":3,"word":"w0020"},{"start":3,"end":4,"word":"x0001"},{"start":4,"end":5,"word":"w0108"}],"meaning":"OK, five cups of water."},"rephrase":{"text":"水。五杯。","tokens":[{"start":0,"end":1,"word":"w0108"},{"start":2,"end":3,"word":"w0020"},{"start":3,"end":4,"word":"x0001"}],"meaning":"Water. Five cups."}}}}]},{"id":"street-hello","place":"street","npc":"wang","stage":1,"after":[],"requires":{},"repeatable":false,"trustGain":1,"exchanges":[{"id":"hello","slots":{},"expect":{"action":"greet"},"hinges":["hello"],"pay":0,"missCost":0,"variants":{"":{"npc":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"},"reply":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"}}}},{"id":"polite","slots":{"said":"courtesies"},"expect":{"action":"answer","said":"$said"},"hinges":["$said"],"pay":0,"missCost":0,"variants":{"said=hello":{"npc":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"},"reply":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"}},"said=thanks":{"npc":{"text":"谢谢!","tokens":[{"start":0,"end":2,"word":"w0141"}],"meaning":"Thank you!"},"reply":{"text":"不客气!","tokens":[{"start":0,"end":3,"word":"w0010"}],"meaning":"You're welcome!"}},"said=sorry":{"npc":{"text":"对不起!","tokens":[{"start":0,"end":3,"word":"w0079"}],"meaning":"Sorry!"},"reply":{"text":"没关系!","tokens":[{"start":0,"end":3,"word":"w0112"}],"meaning":"That's all right!"}},"said=bye":{"npc":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"},"reply":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"}}}},{"id":"polite-again","slots":{"said":"courtesies"},"expect":{"action":"answer","said":"$said"},"hinges":["$said"],"pay":0,"missCost":0,"variants":{"said=hello":{"npc":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"},"reply":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"}},"said=thanks":{"npc":{"text":"谢谢!","tokens":[{"start":0,"end":2,"word":"w0141"}],"meaning":"Thank you!"},"reply":{"text":"不客气!","tokens":[{"start":0,"end":3,"word":"w0010"}],"meaning":"You're welcome!"}},"said=sorry":{"npc":{"text":"对不起!","tokens":[{"start":0,"end":3,"word":"w0079"}],"meaning":"Sorry!"},"reply":{"text":"没关系!","tokens":[{"start":0,"end":3,"word":"w0112"}],"meaning":"That's all right!"}},"said=bye":{"npc":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"},"reply":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"}}}},{"id":"bye","slots":{},"expect":{"action":"farewell"},"hinges":["bye"],"pay":0,"missCost":0,"variants":{"":{"npc":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"},"reply":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"}}}}]},{"id":"street-practice","place":"street","npc":"wang","stage":1,"after":["street-hello"],"requires":{},"repeatable":true,"trustGain":1,"exchanges":[{"id":"polite-1","slots":{"said":"courtesies"},"expect":{"action":"answer","said":"$said"},"hinges":["$said"],"pay":0,"missCost":0,"variants":{"said=hello":{"npc":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"},"reply":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"}},"said=thanks":{"npc":{"text":"谢谢!","tokens":[{"start":0,"end":2,"word":"w0141"}],"meaning":"Thank you!"},"reply":{"text":"不客气!","tokens":[{"start":0,"end":3,"word":"w0010"}],"meaning":"You're welcome!"}},"said=sorry":{"npc":{"text":"对不起!","tokens":[{"start":0,"end":3,"word":"w0079"}],"meaning":"Sorry!"},"reply":{"text":"没关系!","tokens":[{"start":0,"end":3,"word":"w0112"}],"meaning":"That's all right!"}},"said=bye":{"npc":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"},"reply":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"}}}},{"id":"polite-2","slots":{"said":"courtesies"},"expect":{"action":"answer","said":"$said"},"hinges":["$said"],"pay":0,"missCost":0,"variants":{"said=hello":{"npc":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"},"reply":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"}},"said=thanks":{"npc":{"text":"谢谢!","tokens":[{"start":0,"end":2,"word":"w0141"}],"meaning":"Thank you!"},"reply":{"text":"不客气!","tokens":[{"start":0,"end":3,"word":"w0010"}],"meaning":"You're welcome!"}},"said=sorry":{"npc":{"text":"对不起!","tokens":[{"start":0,"end":3,"word":"w0079"}],"meaning":"Sorry!"},"reply":{"text":"没关系!","tokens":[{"start":0,"end":3,"word":"w0112"}],"meaning":"That's all right!"}},"said=bye":{"npc":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"},"reply":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"}}}},{"id":"polite-3","slots":{"said":"courtesies"},"expect":{"action":"answer","said":"$said"},"hinges":["$said"],"pay":0,"missCost":0,"variants":{"said=hello":{"npc":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"},"reply":{"text":"你好!","tokens":[{"start":0,"end":1,"word":"w0028"},{"start":1,"end":2,"word":"w0072"}],"meaning":"Hello!"}},"said=thanks":{"npc":{"text":"谢谢!","tokens":[{"start":0,"end":2,"word":"w0141"}],"meaning":"Thank you!"},"reply":{"text":"不客气!","tokens":[{"start":0,"end":3,"word":"w0010"}],"meaning":"You're welcome!"}},"said=sorry":{"npc":{"text":"对不起!","tokens":[{"start":0,"end":3,"word":"w0079"}],"meaning":"Sorry!"},"reply":{"text":"没关系!","tokens":[{"start":0,"end":3,"word":"w0112"}],"meaning":"That's all right!"}},"said=bye":{"npc":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"},"reply":{"text":"再见!","tokens":[{"start":0,"end":2,"word":"w0034"}],"meaning":"Goodbye!"}}}}]}],"reactions":{"wrong-generic":{"text":"不是。","tokens":[{"start":0,"end":1,"word":"w0009"},{"start":1,"end":2,"word":"w0099"}],"meaning":"No."},"wrong-count":{"text":"几杯?","tokens":[{"start":0,"end":1,"word":"w0037"},{"start":1,"end":2,"word":"x0001"}],"meaning":"How many cups?"}},"learnerFtl":"# Text-game UI. Every id here is required by packages/tui (UI_KEYS).\n\nhud = Day { $day } · slot { $slot }/{ $slots } · { $currency }{ $wallet } · { $rank }\n\nrank-0 = Pidgin\nrank-1 = Getting By\nrank-2 = Conversational\nrank-3 = Fluent\nrank-4 = Silver Tongue\n\nmenu-title = What now?\nmenu-talk = Talk to { $npc }: { $scene }\nmenu-go = Go to { $place }\nmenu-sleep = Sleep (end the day)\nmenu-quit = Save and quit\n\nkeys-explore = [{ $keys }] choose · [q] quit\nkeys-pick = [{ $keys }] reply · [w] word help\nkeys-tiles = [{ $keys }] add tile · [⌫] undo · [enter] say it · [w] word help\nkeys-help = [{ $keys }] look up · [esc] back\nkeys-help-sentence = [{ $keys }] look up · [s] whole sentence · [esc] back\n\nhelp-title = Which word?\nhelp-sentence = The whole sentence\nhelp-in-replies = In the replies:\nreply-title = Your reply:\ntiles-answer = You say:\nyou = You\n\nmismatch = That's not what they asked for.\nrephrased = (slower)\nwallet-change = { $sign }{ $currency }{ $amount } ({ $reason })\nreason-wages = wages\nreason-mixup = mix-up\nreason-food = food\nreason-rent = rent\ntrust-up = { $npc } trusts you a little more ({ $trust }).\nscene-done = Done. You earned { $currency }{ $earned }.\nunlocked = New: { $scene }\nrank-up = You're now: { $rank }\nday-ended = Day { $day } is over. You sleep.\nreject-unknown-scene = There's nobody here for that.\nreject-in-scene = Finish the conversation first.\nreject-wrong-place = They're not here.\nreject-locked = They're not ready to talk about that yet.\nreject-no-slots = You're out of time today. Sleep first.\nreject-stale-run = That conversation can't continue. Start it again.\nreject-no-pick = Choose a reply with the number keys.\nreject-bad-choice = There's no reply with that number.\nreject-no-tiles = Build your reply from the tiles.\nreject-bad-tile = There's no tile with that number.\nreject-not-linked = You can't get there from here.\nreject-unknown-word = That word isn't in the dictionary.\nnotice-read-only = Your progress can't be saved on this computer, so this session won't be kept.\nnotice-bad-save = Your save couldn't be read. It was kept as a backup and a new game started.\n\n# The story. intro-1, intro-2, … open a new game ($currency, $wallet, $rent are available).\nintro-1 = Your phone is dead, your wallet is gone, and the bus that brought you here left an hour ago.\nintro-2 = You're standing on a street in a Chinese city with { $currency }{ $wallet } in your pocket. You know nobody, and nobody here speaks your language.\nintro-3 = Food costs money. Rent is { $currency }{ $rent }, due at the end of the week. To earn anything you'll need work, and for work you'll need words.\nintro-4 = On a bench by the road, an old man is watching you with open curiosity. Maybe start there.\n\nplace-street = The street\nplace-street-desc = Bikes, steam and shouting. An old man sits on a bench by the road. Across it, a noodle shop glows.\nplace-noodle_shop = Noodle shop\nplace-noodle_shop-desc = Steam everywhere. The cook is shouting orders at nobody in particular.\n\nnpc-wang = Old Wang\nnpc-cook = Cook\n\n# Scene names for the menu; scene-<id>-start and scene-<id>-end are optional narration.\nscene-street-hello = Say hello\nscene-street-hello-start = The old man pats the bench beside him. \"Wang,\" he says, pointing at himself. He seems to have decided you need lessons: he says something, you answer. Use [w] when you're stuck.\nscene-street-hello-end = Old Wang beams. He points across the road at the noodle shop, mimes slurping noodles, then rubs his fingers together. Work. Money.\n\nscene-street-practice = Practise greetings\nscene-street-practice-start = Old Wang shuffles over to make room. Another lesson, then.\nscene-street-practice-end = Old Wang nods, satisfied, and goes back to watching the street.\n\nscene-noodle-intro = Ask about work\nscene-noodle-intro-start = The cook looks you up and down, wiping her hands on her apron.\nscene-noodle-intro-end = She throws you an apron. You have a job, sort of.\n\nscene-noodle-shift = Work a shift\nscene-noodle-shift-start = Orders fly across the counter. Keep up.\nscene-noodle-shift-end = The rush dies down. The cook counts coins into your hand.\n"}
|
package/dist/silver-tongue.mjs
CHANGED
|
@@ -1167,14 +1167,14 @@ function pickOptions(ctx, ex, combo) {
|
|
|
1167
1167
|
}
|
|
1168
1168
|
function buildTiles(ctx, ex, combo) {
|
|
1169
1169
|
const rightKey = comboKey(combo);
|
|
1170
|
-
const
|
|
1170
|
+
const pieces2 = tilePieces(ex.variants[rightKey].reply);
|
|
1171
1171
|
const extra = /* @__PURE__ */ new Set();
|
|
1172
1172
|
for (const key of shuffle(Object.keys(ex.variants), ctx.rng)) {
|
|
1173
1173
|
if (key === rightKey) continue;
|
|
1174
|
-
for (const p of tilePieces(ex.variants[key].reply)) if (!
|
|
1174
|
+
for (const p of tilePieces(ex.variants[key].reply)) if (!pieces2.includes(p)) extra.add(p);
|
|
1175
1175
|
if (extra.size >= 2) break;
|
|
1176
1176
|
}
|
|
1177
|
-
return shuffle([...
|
|
1177
|
+
return shuffle([...pieces2, ...[...extra].slice(0, 2)], ctx.rng);
|
|
1178
1178
|
}
|
|
1179
1179
|
function optionsEvent(ex, run) {
|
|
1180
1180
|
return run.mode === "pick" ? { type: "replyOptions", mode: "pick", options: run.options.map((k) => ex.variants[k].reply) } : { type: "replyOptions", mode: "tiles", tiles: run.tiles };
|
|
@@ -1318,9 +1318,9 @@ function isRunShape(x) {
|
|
|
1318
1318
|
if (!["pick", "tiles", "type"].includes(x.mode) || !isStrings(x.options) || !isStrings(x.tiles)) return false;
|
|
1319
1319
|
return ["exchange", "misses", "earned", "mixups"].every((k) => isCount(x[k]));
|
|
1320
1320
|
}
|
|
1321
|
-
function tilesCover(tiles,
|
|
1321
|
+
function tilesCover(tiles, pieces2) {
|
|
1322
1322
|
const left = [...tiles];
|
|
1323
|
-
return
|
|
1323
|
+
return pieces2.every((p) => {
|
|
1324
1324
|
const i = left.indexOf(p);
|
|
1325
1325
|
return i >= 0 && left.splice(i, 1).length === 1;
|
|
1326
1326
|
});
|
|
@@ -1439,16 +1439,67 @@ function fitLine(line, cols) {
|
|
|
1439
1439
|
if (used < cols) out.push({ text: " ".repeat(cols - used) });
|
|
1440
1440
|
return out;
|
|
1441
1441
|
}
|
|
1442
|
+
function pieces(line) {
|
|
1443
|
+
const out = [];
|
|
1444
|
+
for (const span of line) {
|
|
1445
|
+
let cur = "";
|
|
1446
|
+
const flush = () => {
|
|
1447
|
+
if (cur) out.push({ text: cur, span });
|
|
1448
|
+
cur = "";
|
|
1449
|
+
};
|
|
1450
|
+
for (const ch of span.text) {
|
|
1451
|
+
const wide = charWidth(ch.codePointAt(0)) === 2;
|
|
1452
|
+
if (ch === " ") cur += ch;
|
|
1453
|
+
else if (wide || cur.endsWith(" ")) {
|
|
1454
|
+
flush();
|
|
1455
|
+
cur = ch;
|
|
1456
|
+
if (wide) flush();
|
|
1457
|
+
} else cur += ch;
|
|
1458
|
+
}
|
|
1459
|
+
flush();
|
|
1460
|
+
}
|
|
1461
|
+
return out;
|
|
1462
|
+
}
|
|
1463
|
+
function wrapLine(line, cols) {
|
|
1464
|
+
const lines = [];
|
|
1465
|
+
let cur = [];
|
|
1466
|
+
let curFrom;
|
|
1467
|
+
let used = 0;
|
|
1468
|
+
const finish = () => {
|
|
1469
|
+
const last = cur.at(-1);
|
|
1470
|
+
if (last) last.text = last.text.replace(/ +$/, "");
|
|
1471
|
+
lines.push(cur.filter((s) => s.text));
|
|
1472
|
+
cur = [];
|
|
1473
|
+
curFrom = void 0;
|
|
1474
|
+
used = 0;
|
|
1475
|
+
};
|
|
1476
|
+
const add = ({ text, span }) => {
|
|
1477
|
+
if (used > 0 && used + strWidth(text.replace(/ +$/, "")) > cols) finish();
|
|
1478
|
+
if (used === 0 && lines.length > 0) text = text.replace(/^ +/, "");
|
|
1479
|
+
if (!text) return;
|
|
1480
|
+
if (curFrom === span) cur[cur.length - 1].text += text;
|
|
1481
|
+
else cur.push({ ...span, text });
|
|
1482
|
+
curFrom = span;
|
|
1483
|
+
used += strWidth(text);
|
|
1484
|
+
};
|
|
1485
|
+
for (const p of pieces(line)) {
|
|
1486
|
+
if (strWidth(p.text.replace(/ +$/, "")) > cols) for (const ch of p.text) add({ text: ch, span: p.span });
|
|
1487
|
+
else add(p);
|
|
1488
|
+
}
|
|
1489
|
+
finish();
|
|
1490
|
+
return lines;
|
|
1491
|
+
}
|
|
1442
1492
|
|
|
1443
1493
|
// ../tui/src/text.ts
|
|
1444
1494
|
var import_bundle = __toESM(require_bundle(), 1);
|
|
1445
1495
|
function makeText(ftl, locale = "en") {
|
|
1446
1496
|
const bundle = new import_bundle.FluentBundle(locale, { useIsolating: false });
|
|
1447
1497
|
bundle.addResource(new import_bundle.FluentResource(ftl));
|
|
1448
|
-
|
|
1498
|
+
const t = (id, args) => {
|
|
1449
1499
|
const msg = bundle.getMessage(id);
|
|
1450
1500
|
return msg?.value ? bundle.formatPattern(msg.value, args ?? {}, []) : id;
|
|
1451
1501
|
};
|
|
1502
|
+
return Object.assign(t, { has: (id) => !!bundle.getMessage(id)?.value });
|
|
1452
1503
|
}
|
|
1453
1504
|
var UI_KEYS = {
|
|
1454
1505
|
hud: ["day", "slot", "slots", "currency", "wallet", "rank"],
|
|
@@ -1462,12 +1513,17 @@ var UI_KEYS = {
|
|
|
1462
1513
|
"menu-go": ["place"],
|
|
1463
1514
|
"menu-sleep": [],
|
|
1464
1515
|
"menu-quit": [],
|
|
1465
|
-
"keys-explore": [],
|
|
1466
|
-
"keys-pick": [],
|
|
1467
|
-
"keys-tiles": [],
|
|
1468
|
-
"keys-help": [],
|
|
1516
|
+
"keys-explore": ["keys"],
|
|
1517
|
+
"keys-pick": ["keys"],
|
|
1518
|
+
"keys-tiles": ["keys"],
|
|
1519
|
+
"keys-help": ["keys"],
|
|
1520
|
+
"keys-help-sentence": ["keys"],
|
|
1521
|
+
"help-sentence": [],
|
|
1522
|
+
"help-in-replies": [],
|
|
1469
1523
|
"help-title": [],
|
|
1524
|
+
"reply-title": [],
|
|
1470
1525
|
"tiles-answer": [],
|
|
1526
|
+
you: [],
|
|
1471
1527
|
mismatch: [],
|
|
1472
1528
|
rephrased: [],
|
|
1473
1529
|
"wallet-change": ["sign", "currency", "amount", "reason"],
|
|
@@ -1522,9 +1578,10 @@ function border(left, label, right, fill, cols, rightLabel = "") {
|
|
|
1522
1578
|
function renderScreen(m, cols, rows) {
|
|
1523
1579
|
const inner = Math.max(1, cols - 4);
|
|
1524
1580
|
const bodyRows = Math.max(1, rows - 2);
|
|
1525
|
-
const
|
|
1581
|
+
const wrap = (lines) => lines.flatMap((l) => wrapLine(l, inner));
|
|
1582
|
+
const prompt = wrap(m.prompt).slice(-bodyRows);
|
|
1526
1583
|
const logRows = Math.max(0, bodyRows - prompt.length - (prompt.length ? 1 : 0));
|
|
1527
|
-
const log = logRows > 0 ? m.log.slice(-logRows) : [];
|
|
1584
|
+
const log = logRows > 0 ? wrap(m.log).slice(-logRows) : [];
|
|
1528
1585
|
const body = [...Array(logRows - log.length).fill([]), ...log];
|
|
1529
1586
|
if (prompt.length) body.push([]);
|
|
1530
1587
|
body.push(...prompt);
|
|
@@ -1548,6 +1605,7 @@ function startApp(opts) {
|
|
|
1548
1605
|
let replyMode = "pick";
|
|
1549
1606
|
let tileInput = [];
|
|
1550
1607
|
let lastLine = null;
|
|
1608
|
+
let resuming = false;
|
|
1551
1609
|
const push = (...lines) => {
|
|
1552
1610
|
log = [...log, ...lines].slice(-LOG_LIMIT);
|
|
1553
1611
|
};
|
|
@@ -1556,6 +1614,18 @@ function startApp(opts) {
|
|
|
1556
1614
|
{ text: `${npcName(npc)}${suffix}: `, color: "cyan", bold: true },
|
|
1557
1615
|
...lineSpans(line, fresh)
|
|
1558
1616
|
];
|
|
1617
|
+
const keyRange = (n) => n <= 1 ? "1" : `1-${Math.min(n, 9)}`;
|
|
1618
|
+
const echo = (text) => push([{ text: `${t("you")}: `, color: "green", bold: true }, { text }]);
|
|
1619
|
+
const narrate = (id, args) => {
|
|
1620
|
+
if (t.has(id)) push([{ text: t(id, args), dim: true }]);
|
|
1621
|
+
};
|
|
1622
|
+
function tellIntro() {
|
|
1623
|
+
const s = core2.state;
|
|
1624
|
+
const fresh = s.day === 1 && s.slot === 0 && !s.run && s.place === course2.world.start && !Object.keys(s.scenesDone).length && !Object.keys(s.words).length;
|
|
1625
|
+
if (!fresh) return;
|
|
1626
|
+
const args = { currency: course2.world.currency, wallet: s.wallet, rent: course2.world.rentPerWeek };
|
|
1627
|
+
for (let i = 1; t.has(`intro-${i}`); i++) push([{ text: t(`intro-${i}`, args) }], []);
|
|
1628
|
+
}
|
|
1559
1629
|
function enterPlace(place) {
|
|
1560
1630
|
push([], [{ text: t(`place-${place}`), bold: true }], [{ text: t(`place-${place}-desc`), dim: true }]);
|
|
1561
1631
|
}
|
|
@@ -1571,6 +1641,7 @@ function startApp(opts) {
|
|
|
1571
1641
|
case "sceneStarted":
|
|
1572
1642
|
mode = "scene";
|
|
1573
1643
|
push([]);
|
|
1644
|
+
if (!resuming) narrate(`scene-${e.scene}-start`);
|
|
1574
1645
|
break;
|
|
1575
1646
|
case "lineSpoken":
|
|
1576
1647
|
lastLine = e.line;
|
|
@@ -1610,6 +1681,7 @@ function startApp(opts) {
|
|
|
1610
1681
|
break;
|
|
1611
1682
|
case "sceneEnded":
|
|
1612
1683
|
mode = "explore";
|
|
1684
|
+
narrate(`scene-${e.scene}-end`);
|
|
1613
1685
|
push([{ text: t("scene-done", { currency: course2.world.currency, earned: e.earned }), bold: true }]);
|
|
1614
1686
|
break;
|
|
1615
1687
|
case "unlocked":
|
|
@@ -1656,19 +1728,33 @@ function startApp(opts) {
|
|
|
1656
1728
|
}
|
|
1657
1729
|
function helpWords() {
|
|
1658
1730
|
if (!lastLine) return [];
|
|
1659
|
-
const
|
|
1660
|
-
|
|
1731
|
+
const pieces2 = (l) => l.tokens.map((tk) => ({ text: l.text.slice(tk.start, tk.end), word: tk.word }));
|
|
1732
|
+
const said = pieces2(lastLine).map((p) => ({ ...p, inReplies: false }));
|
|
1733
|
+
const seen = new Set(said.map((p) => p.word));
|
|
1734
|
+
const replies = replyMode === "pick" ? pickOptions2.flatMap(pieces2) : [];
|
|
1735
|
+
const extra = replies.filter((p) => !seen.has(p.word) && seen.add(p.word)).map((p) => ({ ...p, inReplies: true }));
|
|
1736
|
+
return [...said, ...extra].slice(0, 9);
|
|
1661
1737
|
}
|
|
1662
1738
|
function prompt(width) {
|
|
1663
1739
|
if (mode === "explore") {
|
|
1664
1740
|
return [[{ text: t("menu-title"), dim: true }], ...menu().map((m, i) => [{ text: `${i + 1}) ${m.label}` }])];
|
|
1665
1741
|
}
|
|
1666
1742
|
if (mode === "help") {
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1743
|
+
const items = helpWords().map((w, i) => ({ ...w, label: `${i + 1}) ${w.text}` }));
|
|
1744
|
+
const said = items.filter((w) => !w.inReplies).map((w) => w.label);
|
|
1745
|
+
const inReplies = items.filter((w) => w.inReplies).map((w) => w.label);
|
|
1746
|
+
const sentence = lastLine?.meaning ? [[{ text: `s) ${t("help-sentence")}` }]] : [];
|
|
1747
|
+
return [
|
|
1748
|
+
[{ text: t("help-title"), dim: true }],
|
|
1749
|
+
...wrapItems(said, width),
|
|
1750
|
+
...sentence,
|
|
1751
|
+
...inReplies.length ? [[{ text: t("help-in-replies"), dim: true }], ...wrapItems(inReplies, width)] : []
|
|
1752
|
+
];
|
|
1669
1753
|
}
|
|
1670
|
-
|
|
1754
|
+
const title = [{ text: t("reply-title"), dim: true }];
|
|
1755
|
+
if (replyMode === "pick") return [title, ...pickOptions2.map((o, i) => [{ text: `${i + 1}) ` }, ...lineSpans(o, /* @__PURE__ */ new Set())])];
|
|
1671
1756
|
return [
|
|
1757
|
+
title,
|
|
1672
1758
|
...wrapItems(
|
|
1673
1759
|
tiles.map((x, i) => `[${i + 1}]${x}`),
|
|
1674
1760
|
width,
|
|
@@ -1688,7 +1774,8 @@ function startApp(opts) {
|
|
|
1688
1774
|
wallet: s.wallet,
|
|
1689
1775
|
rank: t(`rank-${rankFor(s.words, wordIds, opts.now())}`)
|
|
1690
1776
|
});
|
|
1691
|
-
const
|
|
1777
|
+
const [footerId, count] = mode === "explore" ? ["keys-explore", menu().length] : mode === "help" ? [lastLine?.meaning ? "keys-help-sentence" : "keys-help", helpWords().length] : replyMode === "pick" ? ["keys-pick", pickOptions2.length] : ["keys-tiles", tiles.length];
|
|
1778
|
+
const footer = t(footerId, { keys: keyRange(count) });
|
|
1692
1779
|
term2.write(renderScreen({ title: t(`place-${s.place}`), hud, log, prompt: prompt(cols - 4), footer }, cols, rows));
|
|
1693
1780
|
}
|
|
1694
1781
|
function press(key) {
|
|
@@ -1709,23 +1796,38 @@ function startApp(opts) {
|
|
|
1709
1796
|
{ text: ` \u2014 ${w.gloss}` }
|
|
1710
1797
|
]);
|
|
1711
1798
|
}
|
|
1799
|
+
if (key.name === "s" && lastLine?.meaning) {
|
|
1800
|
+
const pron = lastLine.tokens.flatMap((tk) => course2.words[tk.word]?.pron ?? []).join(" ");
|
|
1801
|
+
push([
|
|
1802
|
+
{ text: lastLine.text, bold: true },
|
|
1803
|
+
...pron ? [{ text: ` ${pron}`, color: "yellow" }] : [],
|
|
1804
|
+
{ text: ` \u2014 ${lastLine.meaning}` }
|
|
1805
|
+
]);
|
|
1806
|
+
}
|
|
1712
1807
|
if (key.name === "escape" || key.name === "w") mode = "scene";
|
|
1713
1808
|
} else if (key.name === "w") {
|
|
1714
1809
|
mode = "help";
|
|
1715
1810
|
} else if (replyMode === "pick") {
|
|
1716
|
-
if (n >= 0 && n < pickOptions2.length)
|
|
1811
|
+
if (n >= 0 && n < pickOptions2.length) {
|
|
1812
|
+
echo(pickOptions2[n].text);
|
|
1813
|
+
send({ type: "reply", choice: n });
|
|
1814
|
+
}
|
|
1717
1815
|
} else if (n >= 0 && n < tiles.length && !tileInput.includes(n)) {
|
|
1718
1816
|
tileInput = [...tileInput, n];
|
|
1719
1817
|
} else if (key.name === "backspace") {
|
|
1720
1818
|
tileInput = tileInput.slice(0, -1);
|
|
1721
1819
|
} else if (key.name === "return" && tileInput.length) {
|
|
1820
|
+
echo(tileInput.map((i) => tiles[i]).join(""));
|
|
1722
1821
|
send({ type: "replyTiles", tiles: tileInput });
|
|
1723
1822
|
}
|
|
1724
1823
|
render();
|
|
1725
1824
|
}
|
|
1726
1825
|
if (opts.notice) push([{ text: t(opts.notice), color: "yellow" }]);
|
|
1826
|
+
tellIntro();
|
|
1727
1827
|
enterPlace(core2.state.place);
|
|
1828
|
+
resuming = true;
|
|
1728
1829
|
apply(describeRun(course2, core2.state));
|
|
1830
|
+
resuming = false;
|
|
1729
1831
|
term2.onKey(press);
|
|
1730
1832
|
term2.onResize(render);
|
|
1731
1833
|
render();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silver-tongue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Learn a language by living in it: a text game for the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": { "type": "git", "url": "git+https://github.com/jamil314/silver-tongue.git", "directory": "packages/tui-node" },
|