@johnmorrisdotca/domino 1.0.0 → 1.1.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/CHANGELOG.md CHANGED
@@ -6,6 +6,25 @@ All notable changes to this project are written here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.1.0] - 2026-10-01
10
+
11
+ ### Added
12
+
13
+ - **A command line**, `domino`: `deal` makes the deal a seed makes, every hand and the hub double; `play` plays a game out by computers, with its scores, every move in words (`--moves`) and the saved game (`--save`); `check` reads a saved game back through the rules and says where it stands; `replay` tells every move of one in words. All take `--json`, and `--lang en|ja`. The whole of it is `runCli`, a pure function, and it is tested as plain data and as a child process on Linux, macOS and Windows.
14
+ - **The words of a table in English and Japanese**: `DOMINO_STRINGS`, with `dominoSay` and `dominoLanguage`, and `trainStatus`, `trainNews`, `trainSeatName`, `trainName` and `trainSetLabel`, which say what the rules have already decided: whose turn it is, what was laid, how a round ended. The Japanese has not yet been read by a native reader; every string is listed in `docs/strings-ja.md`.
15
+ - **Tile sounds**, optional: `createTileSounds` (`@johnmorrisdotca/domino/tile-sounds`) plays tiles shuffled, drawn and laid, and a knock for a pass, from recordings fetched by the first sound (`@johnmorrisdotca/domino/sounds`). They are poker chips from Kenney's Casino Audio (CC0), the nearest free recording of a hard tile on a table, and `docs/credits.md` says so.
16
+ - **A README** with the sections a package of the family has: Use it in your project, Features, Saved games, The command line, Languages, Sounds, Theming (none, on purpose), Limits, Browser and runtime support, The family and Changes; `src/docs.test.js` holds its examples and tables to the code. Issue templates, a security policy and more keywords.
17
+ - **The demo** has a *Using it* panel (the code, the command and the saved text of the game on the table, each copyable), a link that names the deal (the set, the players, the rules and the seed are in the address), and a Sound switch.
18
+
19
+ - **A Help switch in the demo.** Beside the language chooser in the family header, shared by every demo. Off (the default) the page is as it was; on, each option row (the set, the players, the rounds, the doubles and the Mexican Train) says in one plain line what it does, in English or Japanese, and every button in it has the same words as its hover text. Kept on the device.
20
+ - **A playable demo on GitHub Pages** (published before this release), in the family's look and in English and Japanese: Mexican Train for one against one to seven computers, with the set, the number of players and every house rule chosen with a press, the family's cloth patches, and an API reference page in the same frame. It is tested in a real browser on a phone and a desk (`pnpm test:demo`), including a whole game played to its end.
21
+
22
+ ### Changed
23
+
24
+ - **Node 22 or later** is what the package declares (`engines`) and is tested on; Node 20 reached its end of life.
25
+ - The demo's table says its words through the package, so a switch of language renames the seats at once; the first computer is "Computer 2", the seat it sits in, as `trainPlayerName` has always named it.
26
+ - Changing the table in the demo keeps the seed in use; Deal again takes a new one.
27
+
9
28
  ## [1.0.0] - 2026-09-30
10
29
 
11
30
  ### Added
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <h1 align="center">Domino <sub>ドミノ</sub></h1>
2
2
 
3
3
  <p align="center"><strong>Dominoes and Mexican Train for JavaScript and TypeScript.</strong><br>
4
- Double-nine, double-twelve and double-fifteen sets; the full rules of Mexican Train for two to eight players; a computer player; seeded deals that replay exactly; and a saved game small enough to keep in a column. No dependencies.</p>
4
+ Double-nine, double-twelve and double-fifteen sets; the full rules of Mexican Train for two to eight players; a computer player; seeded deals that replay exactly; and a saved game small enough to keep in a column; the words of the table in English and Japanese; tile sounds; and a command line. No dependencies.</p>
5
5
 
6
6
  <p align="center">
7
7
  <a href="https://github.com/johnmorrisdotca/domino/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/johnmorrisdotca/domino/actions/workflows/ci.yml/badge.svg"></a>
@@ -10,6 +10,13 @@ Double-nine, double-twelve and double-fifteen sets; the full rules of Mexican Tr
10
10
  <img alt="No dependencies" src="https://img.shields.io/badge/dependencies-0-2f5d4a">
11
11
  </p>
12
12
 
13
+ <p align="center"><a href="https://johnmorrisdotca.github.io/domino/"><strong>Play Mexican Train →</strong></a> · <a href="https://johnmorrisdotca.github.io/domino/api.html">API reference</a></p>
14
+
15
+ <p align="center">
16
+ <img src="docs/desktop.jpg" alt="A game of Mexican Train for three in the demo, under its header with the language chooser, five cloth patches and the Help switch: the set, players and rules to choose, a train for every seat and the Mexican Train, and your hand with the tiles you may lay lifted" width="620">
17
+ <img src="docs/phone.jpg" alt="The same game on a phone in dark mode, in Japanese: every train down the table and your hand of ten, four tiles lifted" width="200">
18
+ </p>
19
+
13
20
  ## In 30 seconds
14
21
 
15
22
  ```sh
@@ -27,12 +34,94 @@ game = playTrain(game, computerMove(game))!; // a move: a new game, or null fo
27
34
  encodeTrain(game); // the whole game as a short text, to keep and read back
28
35
  ```
29
36
 
37
+ Or from a terminal, with nothing to install:
38
+
39
+ ```sh
40
+ npx @johnmorrisdotca/domino play --seed 2026 --players 3 --set 9 --length short
41
+ ```
42
+
30
43
  ## Who it is for
31
44
 
32
45
  - **Game sites and apps** that want a domino table with the rules already right,
33
46
  a computer for any empty seat, and games that can be saved and resumed.
34
47
  - **Anyone writing a domino game of their own**, who wants the set, the tiles
35
48
  and their ends as plain numbers and pure functions to build on.
49
+ - **People who want to look at a game**: the command line deals a seed, plays
50
+ one out and reads a kept game back, in English or Japanese.
51
+
52
+ ## Use it in your project
53
+
54
+ Domino has no screen of its own: it is plain functions over plain data, so
55
+ every framework uses it the same way. Keep the game in your state, show it
56
+ however you like, and pass each move through `playTrain`. The table in the
57
+ [demo](https://johnmorrisdotca.github.io/domino/) is
58
+ [`demo/page.js`](./demo/page.js), a page of plain DOM that does exactly that,
59
+ and the demo's *Using it* panel shows the code for the game on the table.
60
+
61
+ ### 1. The API alone
62
+
63
+ ```ts
64
+ import { computerMove, decodeTrain, encodeTrain, legalPlays, mexicanOf, playTrain, startTrain, trainStatus } from "@johnmorrisdotca/domino";
65
+
66
+ let game = startTrain(9, ["Ann", "Ben", ""], 7, { length: "short", doubles: "chain", mexican: "ownFirst" }, [false, false, true])!;
67
+
68
+ legalPlays(game); // [{ tile, train }, …]: every lay now, a tile being a number
69
+ playTrain(game, { kind: "draw" }); // null: the rules refuse a draw while a tile fits
70
+ trainStatus(game, "en", 0); // "Your turn. Tap a tile to lay it." (seat 0 is you)
71
+
72
+ const kept = encodeTrain(game); // text: the table, the seed and the moves
73
+ decodeTrain(kept); // the same game, played again through the rules, or null
74
+ mexicanOf(game); // 3: the Mexican Train is numbered after the seats
75
+ ```
76
+
77
+ ### 2. A plain page
78
+
79
+ ```html
80
+ <script type="module">
81
+ import { startTrain, tileWords } from "./node_modules/@johnmorrisdotca/domino/dist/index.js";
82
+
83
+ const game = startTrain(12, ["You", "", ""], 2026, undefined, [false, true, true]);
84
+ document.body.textContent = game.hands[0].map(tileWords).join(" ");
85
+ </script>
86
+ ```
87
+
88
+ No bundler is needed: `dist/index.js` is an ES module that imports nothing
89
+ outside the package, so a `<script type="module">` reads it as it is, from
90
+ wherever you serve it.
91
+
92
+ ### 3. On a server
93
+
94
+ A game is its table, its seed and its moves, so a server keeps one short text
95
+ per game and plays it again through the rules whenever it needs the state.
96
+ `decodeTrain` trusts nothing it reads: a changed or invented save comes back
97
+ `null`, so a client can send moves and the server can check every one.
98
+
99
+ ### 4. From a terminal
100
+
101
+ See [The command line](#the-command-line).
102
+
103
+ There is no React, Vue, Svelte or Angular component, on purpose: a table is a
104
+ screen, and every site draws its own. Domino gives it the rules, the words and
105
+ the sounds.
106
+
107
+ ## Features
108
+
109
+ - **The full rules of Mexican Train**, for two to eight players on a
110
+ double-nine, double-twelve or double-fifteen set, with the house rules as
111
+ options.
112
+ - **A computer player** that plans its longest run, plays doubles well and
113
+ answers in a few milliseconds.
114
+ - **Seeded deals that replay exactly.** The same seed and table deal the same
115
+ hands on every machine, and a game is only its table, its seed and its moves.
116
+ - **Saved games as text**, read back through the rules, so a changed save is
117
+ refused.
118
+ - **The words of a table** in English and Japanese: whose turn it is, what was
119
+ laid, how a round ended. See [Languages](#languages).
120
+ - **Tile sounds**, optional: tiles laid, drawn and shuffled, and a knock for a
121
+ pass. See [Sounds](#sounds).
122
+ - **A command line**: deal a seed, play a game out, read a kept game back. See
123
+ [The command line](#the-command-line).
124
+ - **No dependencies, and no drawing.** Plain functions over plain data.
36
125
 
37
126
  ## Mexican Train
38
127
 
@@ -55,8 +144,166 @@ the fewest pips over the whole game wins.
55
144
  text, and reading trusts nothing: every move is played again through the
56
145
  rules, and a changed save is refused.
57
146
 
147
+ A tile is a number, `a × 16 + b` with `a ≤ b` (`TRAIN_PIP_BASE`), so a hand is
148
+ an array of numbers and a game is plain data that can be stored or sent as it is.
149
+
150
+ ## Saved games
151
+
152
+ A kept game is JSON: the version, the set, the options, the seed, the names,
153
+ which seats are computers, and the moves. A move is a few characters:
154
+ `p<tile>.<train>` for a tile laid (the tile as its number, the train by seat,
155
+ the Mexican Train last), `d` for a draw, `x` for a pass and `n` for the next
156
+ round dealt. The game in *In 30 seconds*, after its one move, is:
157
+
158
+ ```json
159
+ {"v":1,"set":12,"options":{"length":"full","doubles":"one","mexican":"any"},"seed":2026,"players":["You","","",""],"computers":[false,true,true,true],"moves":"p156.0"}
160
+ ```
161
+
162
+ Nothing else is kept: hands, trains and the boneyard are made again from the
163
+ seed (`replayTrain`), so what is read back is exactly the game those moves make,
164
+ or nothing at all. Every game in `src/mexicanTrain/train.fixture.json` was kept by
165
+ people on itsutsu.com before the move to this package, and is dealt and played
166
+ again exactly by the tests.
167
+
168
+ ## The command line
169
+
170
+ ```sh
171
+ npm install -g @johnmorrisdotca/domino # then `domino`, or use npx with nothing installed
172
+ ```
173
+
174
+ ```
175
+ Usage: domino <command> [options]
176
+
177
+ Dominoes and Mexican Train: the same deal for the same seed, on every machine.
178
+
179
+ domino deal --seed 2026 --players 4 the deal a seed makes: every hand, and the hub double
180
+ domino play --seed 2026 --players 4 a game played out by computers, with its scores
181
+ domino play --seed 2026 --moves ...and every move, in words
182
+ domino play --seed 2026 --save ...and the saved game, as text
183
+ domino check "<saved game>" read a saved game back through the rules, and say where it stands
184
+ domino replay "<saved game>" every move of a saved game, in words
185
+
186
+ Options:
187
+ --set <9|12|15> the set, by its highest double (12 unless said)
188
+ --players <2..8> how many sit at the table (4 unless said)
189
+ -s, --seed <n> a whole number (one is drawn, and named on standard error, unless given)
190
+ --length <full|short> every round, or half of them (full unless said)
191
+ --doubles <one|chain> cover each double at once, or chain doubles (one unless said)
192
+ --mexican <any|own-first>
193
+ the Mexican Train open to all, or only once you have laid on your own
194
+ --moves play: print every move
195
+ --save play: print the saved game on the last line
196
+ --stdin check, replay: read the saved game from standard input
197
+ -j, --json print JSON (format 1) for deal, play and check
198
+ --lang <en|ja> English or Japanese (default: your system's)
199
+ -h, --help this help
200
+ -v, --version the version
201
+
202
+ Exit codes: 0 done, 1 what was asked for could not be done (a saved game the
203
+ rules refuse), 2 the command was wrong.
204
+ ```
205
+
206
+ ```sh
207
+ $ domino deal --seed 2026 --players 3 --set 9
208
+ Double-nine, 3 players, seed 2026
209
+ Round 1 of 10, hub double 9
210
+ Computer 1: 3-0 4-2 4-3 6-3 6-6 7-7 8-4 8-7 9-4 9-5
211
+ Computer 2: 1-1 2-0 2-1 5-0 5-1 7-3 7-4 8-0 8-2 8-6
212
+ Computer 3: 0-0 2-2 3-3 5-4 7-1 7-2 7-5 9-1 9-2 9-3
213
+ Boneyard: 24 tiles
214
+ $ domino play --seed 2026 --players 3 --set 9 --length short
215
+ Double-nine, 3 players, seed 2026, 5 rounds; moves made: 277
216
+ Hub Computer 1 Computer 2 Computer 3
217
+ 9 23 51 0*
218
+ 8 11 0* 16
219
+ 7 0* 58 27
220
+ 6 54 0* 0
221
+ 5 0* 8 14
222
+ Total 88 117 57
223
+
224
+ Winner: Computer 3 with 57 pips.
225
+ ```
226
+
227
+ In the table of scores a `*` marks the seat that played out. The language
228
+ follows `--lang`, then `LC_ALL`, `LC_MESSAGES` and `LANG`, then the system's.
229
+ A seed that is not given is drawn and named on standard error, so the run can
230
+ be repeated. The command line is run as a child process on Linux, macOS and
231
+ Windows in CI.
232
+
233
+ From code, the whole command line is one pure function:
234
+
235
+ ```ts
236
+ import { runCli } from "@johnmorrisdotca/domino";
237
+
238
+ runCli(["deal", "--seed", "2026", "--players", "3", "--set", "9"]); // { code: 0, out: "Double-nine, 3 players, …", err: "" }
239
+ ```
240
+
241
+ ## Languages
242
+
243
+ The words of a table are English and Japanese: `DOMINO_STRINGS.en` and
244
+ `DOMINO_STRINGS.ja`, one table, so the two are kept side by side. Five functions
245
+ say what the rules have already decided, in either language:
246
+
247
+ ```ts
248
+ import { trainNews, trainSeatName, trainStatus } from "@johnmorrisdotca/domino";
249
+
250
+ trainStatus(game, "ja", 0); // "あなたの番です。牌をタップして置きます。"
251
+ trainNews(game, "en", 0); // "You laid 12–9 on Your train." (after the first move)
252
+ trainSeatName(game, 1, "en", 0); // "Computer 2"
253
+ ```
254
+
255
+ `dominoSay` fills in the braces (`{who}`, `{tile}`) of any string, and
256
+ `dominoLanguage` reads a tag such as `ja_JP.UTF-8`. **Japanese: included; not yet
257
+ reviewed by a native reader. Corrections welcome.** Every Japanese string is
258
+ listed beside its English in [docs/strings-ja.md](./docs/strings-ja.md), and there
259
+ is an [issue template](https://github.com/johnmorrisdotca/domino/issues/new?template=fix-a-translation.md)
260
+ for fixing one. Any other language is a table of your own with the same names.
261
+
262
+ ## Sounds
263
+
264
+ Recordings for a table to play: the tiles shuffled, one drawn, one laid, and a
265
+ knock on the table for a pass. Nothing sounds unless a table asks, and nothing is
266
+ fetched until the first sound.
267
+
268
+ ```ts
269
+ import { createTileSounds } from "@johnmorrisdotca/domino/tile-sounds";
270
+
271
+ const sounds = createTileSounds(); // silent until asked: nothing is fetched yet
272
+ sounds.play("shuffle");
273
+ sounds.play("draw", { count: 7, gap: 120 }); // seven tiles drawn, one after another
274
+ sounds.play("lay"); // a tile laid on a train
275
+ sounds.play("knock"); // a pass
276
+ muteButton.onclick = () => sounds.setMuted(!sounds.muted);
277
+ ```
278
+
279
+ | Kind | What it is |
280
+ | --- | --- |
281
+ | `shuffle` | the tiles stirred face down |
282
+ | `draw` | a tile taken from the boneyard; `{ count }` takes several, one every `gap` milliseconds |
283
+ | `lay` | a tile set down on a train |
284
+ | `knock` | a rap on the table: a pass |
285
+
286
+ - **What they really are.** The recordings are poker chips, from Kenney's
287
+ [Casino Audio](https://kenney.nl/assets/casino-audio) (CC0), because that is the
288
+ nearest recorded click of a hard tile on a table that is free to use. They are
289
+ not dominoes, and [docs/credits.md](./docs/credits.md) says so, names each file
290
+ and what was done to it.
291
+ - **What it costs.** The player is small, and the recordings are a few tens of
292
+ kilobytes of AAC in a module of their own (`@johnmorrisdotca/domino/sounds`),
293
+ fetched by the first sound and never before: a muted table never downloads them.
294
+ - **A browser only lets a page make sound after somebody has touched it**, so a
295
+ sound asked for by code before any tap is silent.
296
+ - If the recordings cannot be fetched or decoded, a short sound made in the
297
+ browser stands in. Nothing throws where there is no audio, as on a server.
298
+ - Options of `createTileSounds`: `muted` (start muted), `volume` (0 to 1; 0.6
299
+ unless said), `load` (where the recordings come from) and `window` (the window
300
+ to make sound in, or `null` for silence). The demo's table has a **Sound**
301
+ switch, off until pressed.
302
+
58
303
  ## API
59
304
 
305
+ The [API reference](https://johnmorrisdotca.github.io/domino/api.html) (also kept in the repository as [`docs/api.md`](https://github.com/johnmorrisdotca/domino/blob/main/docs/api.md)) lists every export of every entry point with its signature and its doc comment. It is made from the source by `pnpm docs:api`, and a test fails when it falls behind the code.
306
+
60
307
  | Export | What it does |
61
308
  | --- | --- |
62
309
  | `startTrain(set, players, seed?, options?, computers?)` | A new game, or null for a table the rules do not allow |
@@ -68,11 +315,78 @@ the fewest pips over the whole game wins.
68
315
  | `tileOf`, `endsOf`, `isDouble`, `pipsOf`, `fits`, `laidAgainst`, `laidEnds`, `tileOfLaid`, `everyTile`, `tileWords` | Tiles as numbers: making, reading and laying them |
69
316
  | `TRAIN_SETS`, `TRAIN_SET_NAMES`, `trainSetName`, `handSizeFor`, `roundsFor`, `TRAIN_DEFAULT_OPTIONS`, `TRAIN_LENGTHS`, `TRAIN_DOUBLES`, `TRAIN_MEXICAN`, `TRAIN_PHASES`, `TRAIN_PIP_BASE`, `TRAIN_NAME_MOST` | The vocabulary and the limits |
70
317
  | `seededRandom(seed)`, `shuffled(items, random)` | The seeded stream every deal is made from |
71
- | `TrainGame`, `TrainMove`, `TrainOptions`, `Domino`, … | The types |
318
+ | `DOMINO_STRINGS`, `dominoSay`, `dominoLanguage` | The words of a table, English and Japanese |
319
+ | `trainStatus`, `trainNews`, `trainSeatName`, `trainName`, `trainSetLabel` | What is going on, what just happened, and who is who, in words |
320
+ | `runCli`, `cliLanguage`, `CLI_MOVES_MOST` | The command line as a pure function |
321
+ | `createTileSounds`, `TILE_SOUND_KINDS`, `soundTimes`, `MOST_SOUNDS_AT_ONCE` (`/tile-sounds`) | The tile sounds |
322
+ | `TILE_SOUND_DATA` (`/sounds`) | The recordings, as base64 AAC |
323
+ | `TrainGame`, `TrainMove`, `TrainOptions`, `Domino`, `Language`, … | The types |
72
324
  | `VERSION` | This package's version |
73
325
 
74
- A tile is a number, `a × 16 + b` with `a ≤ b` (`TRAIN_PIP_BASE`), so a hand is
75
- an array of numbers and a game is plain data that can be stored or sent as it is.
326
+ ## Theming
327
+
328
+ None, on purpose: Domino draws nothing, so there is nothing of its own to
329
+ theme, and a table built on it looks however your page looks. The demo is the
330
+ worked example: its dominoes and table are drawn by [`demo/page.js`](./demo/page.js)
331
+ and [`demo/site.css`](./demo/site.css), over the family's shared stylesheet.
332
+
333
+ ## Limits
334
+
335
+ | Limit | Value | Constant |
336
+ | --- | --- | --- |
337
+ | Players at a table | 2 to 8 | |
338
+ | Sets | double-nine (55 tiles), double-twelve (91), double-fifteen (136) | `TRAIN_SETS` |
339
+ | Tiles in a hand | 6 to 15, by the set and the players | `handSizeFor` |
340
+ | Rounds | one for every double (10, 13 or 16), or about half of them (5, 7 or 8) | `roundsFor` |
341
+ | A seat's name | 20 characters | `TRAIN_NAME_MOST` |
342
+ | A seed | a whole number, read modulo 4,294,967,296; the command line takes 0 to 4,294,967,295 | |
343
+ | A tile | `low * 16 + high`, each end 0 to 15 | `TRAIN_PIP_BASE` |
344
+ | Moves a played game may take on the command line | 100,000 | `CLI_MOVES_MOST` |
345
+
346
+ ## Browser and runtime support
347
+
348
+ The rules, the computer player, the words and the command line run anywhere
349
+ JavaScript does: every current browser, Node, Deno and Bun. It needs ES2020. The
350
+ package declares Node 22 and later (`engines`), and CI runs it on Node 22 and 24
351
+ and, for the packed package and the command line, on Linux, macOS and Windows.
352
+ The sounds need the Web Audio API, which every current browser has; elsewhere
353
+ they are silent and nothing throws. The demo is tested in Chromium and in
354
+ WebKit, Safari's engine, at phone size with touch.
355
+
356
+ ## Architecture
357
+
358
+ The dominoes and the rules of Mexican Train are plain functions over plain
359
+ data with no DOM and no dependency: a game is a value, every move returns the
360
+ next one, and a seed replays a deal exactly. The computer player and the
361
+ saved-game format are separate modules over the same rules, and every game so
362
+ far lives in a folder of its own, so the next one is a sibling of
363
+ `mexicanTrain/`.
364
+
365
+ ```text
366
+ src/
367
+ ├── index.ts the main entry: the dominoes, Mexican Train's rules, its computer player, its saved-game format, its words and its command line
368
+ ├── cli.ts the command line, as a pure function
369
+ ├── random.ts seeded randomness, the one thing every deal is made from
370
+ ├── sounds.ts the recordings of the tile sounds, as base64 (written by scripts/sounds.mjs)
371
+ ├── strings.ts every word Domino says, in English and Japanese
372
+ ├── tile-sounds.ts the tile sounds: a player that fetches the recordings on the first sound
373
+ ├── version.ts the package's version
374
+ ├── words.ts what a table says: who is who, what just happened, whose turn it is
375
+ └── mexicanTrain/ Mexican Train, the one game so far
376
+ ├── dominoes.ts the dominoes themselves: the double-nine, double-twelve and double-fifteen sets, their ends and pips, and which fit
377
+ ├── mexicanTrain.constants.ts the numbers the game is played by: set sizes, train lengths and the doubles rules
378
+ ├── mexicanTrain.ts the rules of Mexican Train: deal, trains, doubles, drawing and scoring
379
+ ├── mexicanTrain.types.ts the game, its moves and its options, as the rules speak of them
380
+ ├── trainCodec.ts a game as text and back: its options, its seed and every move
381
+ └── trainComputer.ts the computer player
382
+ ```
383
+
384
+ Tests sit beside the code they test (`*.test.ts`), and `src/docs.test.js` holds
385
+ this README's examples and tables to the code. `bin/` is the command line's few
386
+ lines. `scripts/` checks the package as npm packs it (`pnpm test:package`), runs
387
+ the command line as a child process (`pnpm test:cli`), makes the API reference,
388
+ `docs/api.md`, cuts the sounds, and builds the demo (`pnpm site`) and tests it in
389
+ a real browser (`pnpm test:demo`); `demo/` is the page published on GitHub Pages.
76
390
 
77
391
  ## The name
78
392
 
@@ -86,32 +400,57 @@ played with friends and family, where Mexican Train is played at one device or
86
400
  several. Every deal and every computer game there before the move is held by
87
401
  this package's tests, so a game kept on the site replays exactly.
88
402
 
89
- Using it somewhere? [Tell us](https://github.com/johnmorrisdotca/domino/issues/new?title=Add+my+project).
403
+ Using it somewhere? [Tell us](https://github.com/johnmorrisdotca/domino/issues/new?template=add-my-project.md).
90
404
 
91
405
  ### The family
92
406
 
93
- - [Korokoro](https://github.com/johnmorrisdotca/korokoro): dice, with exact odds and real sounds
94
- - [Kyuubu](https://github.com/johnmorrisdotca/kyuubu): a turning cube, with a solve you can follow
95
- - [Toranpu](https://github.com/johnmorrisdotca/toranpu): playing cards and ten card games, and three solitaires
96
- - [Hitotsu](https://github.com/johnmorrisdotca/hitotsu): a colour-card game in the manner of UNO
97
- - [Tane](https://github.com/johnmorrisdotca/tane): seeded random numbers and daily seeds
98
- - [Narabe](https://github.com/johnmorrisdotca/narabe): a rules engine for board games of the five-in-a-row family and more
99
- - [Tenka](https://github.com/johnmorrisdotca/tenka): a game of world conquest
100
- - [Kumimoji](https://github.com/johnmorrisdotca/kumimoji): a crossword tile race
407
+ The code of every package is MIT, and all of them were written for the same site. Each is at
408
+ [github.com/johnmorrisdotca](https://github.com/johnmorrisdotca):
409
+
410
+ - [Korokoro](https://github.com/johnmorrisdotca/korokoro) (コロコロ): dice, with exact odds, the dice of 44 games and real sounds.
411
+ - [Kyuubu](https://github.com/johnmorrisdotca/kyuubu) (キューブ): a turning cube for the browser, 2×2 to 7×7, with record solves to replay.
412
+ - [Hitotsu](https://github.com/johnmorrisdotca/hitotsu) (一つ): a colour-card shedding game for two to eight, with the house rules people play.
413
+ - [Toranpu](https://github.com/johnmorrisdotca/toranpu) (トランプ): a deck of playing cards, ten card games with computer players, and three solitaires.
414
+ - [Tane](https://github.com/johnmorrisdotca/tane) (種): seeded random numbers and daily seeds, the same in every browser and on every server.
415
+ - [Narabe](https://github.com/johnmorrisdotca/narabe) (並べ): one rules engine for abstract board games: gomoku, renju, Reversi, Hex, Go, checkers and more.
416
+ - [Tenka](https://github.com/johnmorrisdotca/tenka) (天下): world conquest for two to six, on a map of the real world.
417
+ - [Kumimoji](https://github.com/johnmorrisdotca/kumimoji) (組み文字): the crossword tile race, in English and Japanese kana.
418
+ - [Tsunagi](https://github.com/johnmorrisdotca/tsunagi) (繋ぎ): a line-joining logic puzzle with 1,792 levels, each with exactly one answer.
419
+ - [Jarajara](https://github.com/johnmorrisdotca/jarajara) (ジャラジャラ): mahjong tiles drawn as SVG, stacked layouts, and the matching solitaire Awase.
420
+ - [Suido](https://github.com/johnmorrisdotca/suido) (水道): a pipe puzzle: turn the pieces until the water reaches every drain.
421
+ - [Kotoba](https://github.com/johnmorrisdotca/kotoba) (言葉): word lists and word-game rules in English, French, German and Japanese.
422
+ - [Sugoroku](https://github.com/johnmorrisdotca/sugoroku) (双六): backgammon and its variants, with the doubling cube and match play.
423
+ - [Kazu](https://github.com/johnmorrisdotca/kazu) (数): grid number puzzles: Sudoku and its variants, Futoshiki and Skyscrapers.
424
+ - [Meikyuu](https://github.com/johnmorrisdotca/meikyuu) (迷宮): a maze game of a thousand levels.
101
425
 
102
426
  ## Roadmap
103
427
 
104
- - A demo site, playable in the browser, in the family's look, with English and Japanese
105
- - The words for the table in English and Japanese, and a React hook
106
428
  - More domino games: Block, Draw, All Fives and Chicken Foot
429
+ - A React hook, for a table kept in component state
107
430
 
108
431
  Left out on purpose: anything played for stakes, and play over a network, which
109
432
  needs a server. A game here is plain data, so your own server can carry it.
110
433
 
111
434
  ## Contributing
112
435
 
113
- See [CONTRIBUTING.md](./CONTRIBUTING.md).
436
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). In short:
437
+
438
+ ```sh
439
+ pnpm install
440
+ pnpm check # lint, types and tests
441
+ pnpm test:cli # the command line, run as a child process
442
+ pnpm test:package # pack it as npm does, install it, import every entry and run the command
443
+ pnpm test:demo # the demo in real browsers, by taps
444
+ ```
445
+
446
+ A change to the rules must leave every game in the fixture dealing and playing
447
+ exactly as it did. Please follow the [code of conduct](./CODE_OF_CONDUCT.md).
448
+
449
+ ## Changes
450
+
451
+ See [CHANGELOG.md](./CHANGELOG.md).
114
452
 
115
453
  ## Licence
116
454
 
117
- MIT © John Morris
455
+ [MIT](./LICENSE) © John Morris. The tile sounds are Kenney's Casino Audio, CC0:
456
+ see [docs/credits.md](./docs/credits.md).
package/bin/domino.mjs ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ // The command line: `domino`. All of it is `runCli`, a pure function in the
3
+ // package; these lines hand it the real process.
4
+ import { readFileSync } from "node:fs";
5
+ import process from "node:process";
6
+
7
+ import { runCli } from "../dist/cli.js";
8
+
9
+ const args = process.argv.slice(2);
10
+ let stdin;
11
+ if (args.includes("--stdin")) {
12
+ try {
13
+ stdin = readFileSync(0, "utf8");
14
+ } catch {
15
+ // Nothing was piped in: no saved game to read.
16
+ stdin = "";
17
+ }
18
+ }
19
+ const result = runCli(args, { env: process.env, stdin, locale: Intl.DateTimeFormat().resolvedOptions().locale });
20
+ if (result.out !== "") process.stdout.write(result.out);
21
+ if (result.err !== "") process.stderr.write(result.err);
22
+ process.exitCode = result.code;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { type Language } from "./strings.ts";
2
+ /**
3
+ * The command line, as a pure function: arguments and surroundings in, what
4
+ * to print and the exit code out. `bin/domino.mjs` is the few lines that hand
5
+ * it the real process. Nothing here touches a file, a terminal or the
6
+ * network, so every line of it is tested as plain data.
7
+ */
8
+ /** What the command line is run in. All of it is optional. */
9
+ export type CliSurroundings = {
10
+ /** The environment, for the language: `LC_ALL`, `LC_MESSAGES` and `LANG`. */
11
+ env?: Record<string, string | undefined>;
12
+ /** Standard input, when `--stdin` asks for it: the saved game. */
13
+ stdin?: string;
14
+ /** The system's language where the environment names none: what `Intl` says, on Windows. */
15
+ locale?: string;
16
+ /** Where a seed comes from when none is given: a function like `Math.random`, which it is unless given. */
17
+ random?: () => number;
18
+ };
19
+ /** What the command line came to. */
20
+ export type CliResult = {
21
+ /** 0 when all went well, 1 when what was asked for could not be done, 2 when the command itself was wrong. */
22
+ code: 0 | 1 | 2;
23
+ /** For standard output. */
24
+ out: string;
25
+ /** For standard error. */
26
+ err: string;
27
+ };
28
+ /** The most moves a played game may take before the command line gives up, far above any game the rules allow. */
29
+ export declare const CLI_MOVES_MOST = 100000;
30
+ /** The language the command line speaks: `--lang`, or the environment's, or the system's; Japanese for `ja…`, English for anything else. */
31
+ export declare function cliLanguage(flag: string | undefined, env?: Record<string, string | undefined>, locale?: string): Language;
32
+ /** Run the command line. See `domino --help` for what it takes. One seed serves the whole run, so the same command prints the same lines on every machine. */
33
+ export declare function runCli(args: readonly string[], around?: CliSurroundings): CliResult;