@johnmorrisdotca/domino 1.0.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 +21 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/mexicanTrain/dominoes.d.ts +27 -0
- package/dist/mexicanTrain/dominoes.js +58 -0
- package/dist/mexicanTrain/mexicanTrain.constants.d.ts +42 -0
- package/dist/mexicanTrain/mexicanTrain.constants.js +40 -0
- package/dist/mexicanTrain/mexicanTrain.d.ts +92 -0
- package/dist/mexicanTrain/mexicanTrain.js +339 -0
- package/dist/mexicanTrain/mexicanTrain.types.d.ts +154 -0
- package/dist/mexicanTrain/mexicanTrain.types.js +17 -0
- package/dist/mexicanTrain/trainCodec.d.ts +4 -0
- package/dist/mexicanTrain/trainCodec.js +90 -0
- package/dist/mexicanTrain/trainComputer.d.ts +10 -0
- package/dist/mexicanTrain/trainComputer.js +113 -0
- package/dist/random.d.ts +17 -0
- package/dist/random.js +31 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +85 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are written here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.0.0] - 2026-09-30
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Double-nine, double-twelve and double-fifteen sets of dominoes, a tile as a
|
|
14
|
+
number.
|
|
15
|
+
- Mexican Train for two to eight players: the hub, a train for every player and
|
|
16
|
+
the Mexican Train, open trains, doubles to cover, blocked rounds and the score
|
|
17
|
+
over every round, with house rules as options.
|
|
18
|
+
- A computer player, seeded deals that replay exactly, and a saved-game format
|
|
19
|
+
read back through the rules.
|
|
20
|
+
- Brought from itsutsu.com, and held by its tests to sixty-four games dealt and
|
|
21
|
+
played out there before the move.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 John Morris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<h1 align="center">Domino <sub>ドミノ</sub></h1>
|
|
2
|
+
|
|
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>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
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>
|
|
8
|
+
<a href="https://www.npmjs.com/package/@johnmorrisdotca/domino"><img alt="npm" src="https://img.shields.io/npm/v/@johnmorrisdotca/domino?color=2f5d4a"></a>
|
|
9
|
+
<a href="./LICENSE"><img alt="MIT licence" src="https://img.shields.io/badge/licence-MIT-2f5d4a"></a>
|
|
10
|
+
<img alt="No dependencies" src="https://img.shields.io/badge/dependencies-0-2f5d4a">
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
## In 30 seconds
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @johnmorrisdotca/domino
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { computerMove, encodeTrain, legalPlays, playTrain, startTrain } from "@johnmorrisdotca/domino";
|
|
21
|
+
|
|
22
|
+
// A double-twelve table for four, a computer in every seat but the first, dealt from seed 2026.
|
|
23
|
+
let game = startTrain(12, ["You", "", "", ""], 2026, undefined, [false, true, true, true])!;
|
|
24
|
+
|
|
25
|
+
legalPlays(game); // every tile you may lay, and on which train
|
|
26
|
+
game = playTrain(game, computerMove(game))!; // a move: a new game, or null for one the rules refuse
|
|
27
|
+
encodeTrain(game); // the whole game as a short text, to keep and read back
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Who it is for
|
|
31
|
+
|
|
32
|
+
- **Game sites and apps** that want a domino table with the rules already right,
|
|
33
|
+
a computer for any empty seat, and games that can be saved and resumed.
|
|
34
|
+
- **Anyone writing a domino game of their own**, who wants the set, the tiles
|
|
35
|
+
and their ends as plain numbers and pure functions to build on.
|
|
36
|
+
|
|
37
|
+
## Mexican Train
|
|
38
|
+
|
|
39
|
+
A hub double in the middle, a train out of it for every player, and one more,
|
|
40
|
+
the Mexican Train, that anybody may add to. Rounds count down from the set's
|
|
41
|
+
highest double, the hub, to the blank; whoever goes out first ends the round, and
|
|
42
|
+
the fewest pips over the whole game wins.
|
|
43
|
+
|
|
44
|
+
- **Sets**: double-nine (55 tiles), double-twelve (91, the usual one) and
|
|
45
|
+
double-fifteen (136), with the hand size for each set and number of players.
|
|
46
|
+
- **House rules**, as options: `length` (`full`, every round, or `short`),
|
|
47
|
+
`doubles` (`one`: a double laid must be covered next, or `chain`), and
|
|
48
|
+
`mexican` (`any`: anyone may start the Mexican Train, or `ownFirst`).
|
|
49
|
+
- **Trains open and close**: a player who cannot lay draws, and if still stuck
|
|
50
|
+
marks their train open for everyone until they next lay on it.
|
|
51
|
+
- **A blocked round** ends when nobody can lay and nothing is left to draw.
|
|
52
|
+
- **A computer player** (`computerMove`) that lays its longest run on its own
|
|
53
|
+
train and plays doubles well, in a few milliseconds.
|
|
54
|
+
- **Saved games**: `encodeTrain` and `decodeTrain` write and read a game as
|
|
55
|
+
text, and reading trusts nothing: every move is played again through the
|
|
56
|
+
rules, and a changed save is refused.
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
| Export | What it does |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `startTrain(set, players, seed?, options?, computers?)` | A new game, or null for a table the rules do not allow |
|
|
63
|
+
| `playTrain(game, move)` | The game after a move, or null for a move the rules refuse |
|
|
64
|
+
| `trainMoves`, `legalPlays`, `mayLay`, `openEnd`, `mexicanOf` | What may be played, and where |
|
|
65
|
+
| `computerMove(game)`, `longestRun(hand, end)` | The computer's move, and the run it plans |
|
|
66
|
+
| `encodeTrain`, `decodeTrain`, `replayTrain`, `movesOf`, `moveCount` | Saving a game, reading it back, and replaying its moves |
|
|
67
|
+
| `trainTotals`, `handPips`, `trainAgain`, `trainPlayerName`, `peopleAt`, `cleanTrainName` | Scores, a fresh deal at the same table, and the seats |
|
|
68
|
+
| `tileOf`, `endsOf`, `isDouble`, `pipsOf`, `fits`, `laidAgainst`, `laidEnds`, `tileOfLaid`, `everyTile`, `tileWords` | Tiles as numbers: making, reading and laying them |
|
|
69
|
+
| `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
|
+
| `seededRandom(seed)`, `shuffled(items, random)` | The seeded stream every deal is made from |
|
|
71
|
+
| `TrainGame`, `TrainMove`, `TrainOptions`, `Domino`, … | The types |
|
|
72
|
+
| `VERSION` | This package's version |
|
|
73
|
+
|
|
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.
|
|
76
|
+
|
|
77
|
+
## The name
|
|
78
|
+
|
|
79
|
+
*Domino* is ドミノ (domino), the word Japanese uses for dominoes, borrowed from
|
|
80
|
+
the European game as English borrowed it.
|
|
81
|
+
|
|
82
|
+
## Where it comes from, and where it is used
|
|
83
|
+
|
|
84
|
+
Domino was written for [itsutsu.com](https://itsutsu.com), a site of games
|
|
85
|
+
played with friends and family, where Mexican Train is played at one device or
|
|
86
|
+
several. Every deal and every computer game there before the move is held by
|
|
87
|
+
this package's tests, so a game kept on the site replays exactly.
|
|
88
|
+
|
|
89
|
+
Using it somewhere? [Tell us](https://github.com/johnmorrisdotca/domino/issues/new?title=Add+my+project).
|
|
90
|
+
|
|
91
|
+
### The family
|
|
92
|
+
|
|
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
|
|
101
|
+
|
|
102
|
+
## Roadmap
|
|
103
|
+
|
|
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
|
+
- More domino games: Block, Draw, All Fives and Chicken Foot
|
|
107
|
+
|
|
108
|
+
Left out on purpose: anything played for stakes, and play over a network, which
|
|
109
|
+
needs a server. A game here is plain data, so your own server can carry it.
|
|
110
|
+
|
|
111
|
+
## Contributing
|
|
112
|
+
|
|
113
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
114
|
+
|
|
115
|
+
## Licence
|
|
116
|
+
|
|
117
|
+
MIT © John Morris
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Domino: double-nine, double-twelve and double-fifteen sets of dominoes, and Mexican Train with a computer player and a saved-game format. */
|
|
2
|
+
export * from "./mexicanTrain/dominoes.ts";
|
|
3
|
+
export * from "./mexicanTrain/mexicanTrain.constants.ts";
|
|
4
|
+
export * from "./mexicanTrain/mexicanTrain.ts";
|
|
5
|
+
export type * from "./mexicanTrain/mexicanTrain.types.ts";
|
|
6
|
+
export * from "./mexicanTrain/trainComputer.ts";
|
|
7
|
+
export * from "./mexicanTrain/trainCodec.ts";
|
|
8
|
+
export { seededRandom, shuffled } from "./random.ts";
|
|
9
|
+
export type { Random } from "./random.ts";
|
|
10
|
+
export { VERSION } from "./version.ts";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Domino: double-nine, double-twelve and double-fifteen sets of dominoes, and Mexican Train with a computer player and a saved-game format. */
|
|
2
|
+
export * from "./mexicanTrain/dominoes.js";
|
|
3
|
+
export * from "./mexicanTrain/mexicanTrain.constants.js";
|
|
4
|
+
export * from "./mexicanTrain/mexicanTrain.js";
|
|
5
|
+
export * from "./mexicanTrain/trainComputer.js";
|
|
6
|
+
export * from "./mexicanTrain/trainCodec.js";
|
|
7
|
+
export { seededRandom, shuffled } from "./random.js";
|
|
8
|
+
export { VERSION } from "./version.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Domino, LaidDomino } from "./mexicanTrain.types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* DOMINOES AS NUMBERS: a tile is `low * 16 + high`, a tile laid in a train
|
|
4
|
+
* `from * 16 + to` (see `mexicanTrain.types.ts`). Small, pure helpers the
|
|
5
|
+
* rules, the computer player and the drawing all read, so that none of them
|
|
6
|
+
* pulls a number apart its own way.
|
|
7
|
+
*/
|
|
8
|
+
/** The tile with these two ends, whichever order they are given in. */
|
|
9
|
+
export declare function tileOf(a: number, b: number): Domino;
|
|
10
|
+
/** A tile's two ends, the smaller first. */
|
|
11
|
+
export declare function endsOf(tile: Domino): [number, number];
|
|
12
|
+
/** Both ends the same: a double, which is laid across a train and must be covered. */
|
|
13
|
+
export declare function isDouble(tile: Domino): boolean;
|
|
14
|
+
/** Every pip on a tile: what it counts against its holder when a round ends. */
|
|
15
|
+
export declare function pipsOf(tile: Domino): number;
|
|
16
|
+
/** Whether a tile has this number at either end, so it can be laid against it. */
|
|
17
|
+
export declare function fits(tile: Domino, end: number): boolean;
|
|
18
|
+
/** The tile laid against `end`: turned so that end touches, the other one left open. */
|
|
19
|
+
export declare function laidAgainst(tile: Domino, end: number): LaidDomino;
|
|
20
|
+
/** A laid tile's two ends in the order it lies: the one touching the train, then the open one. */
|
|
21
|
+
export declare function laidEnds(laid: LaidDomino): [number, number];
|
|
22
|
+
/** The tile a laid tile is, whichever way round it lies. */
|
|
23
|
+
export declare function tileOfLaid(laid: LaidDomino): Domino;
|
|
24
|
+
/** Every tile of a set, double-blank to its highest double, in order. */
|
|
25
|
+
export declare function everyTile(set: number): Domino[];
|
|
26
|
+
/** A tile as a person reads it: "6–4", the larger end first, as a tile is named at the table. */
|
|
27
|
+
export declare function tileWords(tile: Domino): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Relative, like the rest of lib/party: the browser specs import this, and Playwright resolves no alias.
|
|
2
|
+
import { TRAIN_PIP_BASE } from "./mexicanTrain.constants.js";
|
|
3
|
+
/**
|
|
4
|
+
* DOMINOES AS NUMBERS: a tile is `low * 16 + high`, a tile laid in a train
|
|
5
|
+
* `from * 16 + to` (see `mexicanTrain.types.ts`). Small, pure helpers the
|
|
6
|
+
* rules, the computer player and the drawing all read, so that none of them
|
|
7
|
+
* pulls a number apart its own way.
|
|
8
|
+
*/
|
|
9
|
+
/** The tile with these two ends, whichever order they are given in. */
|
|
10
|
+
export function tileOf(a, b) {
|
|
11
|
+
return a <= b ? a * TRAIN_PIP_BASE + b : b * TRAIN_PIP_BASE + a;
|
|
12
|
+
}
|
|
13
|
+
/** A tile's two ends, the smaller first. */
|
|
14
|
+
export function endsOf(tile) {
|
|
15
|
+
return [Math.floor(tile / TRAIN_PIP_BASE), tile % TRAIN_PIP_BASE];
|
|
16
|
+
}
|
|
17
|
+
/** Both ends the same: a double, which is laid across a train and must be covered. */
|
|
18
|
+
export function isDouble(tile) {
|
|
19
|
+
const [low, high] = endsOf(tile);
|
|
20
|
+
return low === high;
|
|
21
|
+
}
|
|
22
|
+
/** Every pip on a tile: what it counts against its holder when a round ends. */
|
|
23
|
+
export function pipsOf(tile) {
|
|
24
|
+
const [low, high] = endsOf(tile);
|
|
25
|
+
return low + high;
|
|
26
|
+
}
|
|
27
|
+
/** Whether a tile has this number at either end, so it can be laid against it. */
|
|
28
|
+
export function fits(tile, end) {
|
|
29
|
+
const [low, high] = endsOf(tile);
|
|
30
|
+
return low === end || high === end;
|
|
31
|
+
}
|
|
32
|
+
/** The tile laid against `end`: turned so that end touches, the other one left open. */
|
|
33
|
+
export function laidAgainst(tile, end) {
|
|
34
|
+
const [low, high] = endsOf(tile);
|
|
35
|
+
return low === end ? low * TRAIN_PIP_BASE + high : high * TRAIN_PIP_BASE + low;
|
|
36
|
+
}
|
|
37
|
+
/** A laid tile's two ends in the order it lies: the one touching the train, then the open one. */
|
|
38
|
+
export function laidEnds(laid) {
|
|
39
|
+
return [Math.floor(laid / TRAIN_PIP_BASE), laid % TRAIN_PIP_BASE];
|
|
40
|
+
}
|
|
41
|
+
/** The tile a laid tile is, whichever way round it lies. */
|
|
42
|
+
export function tileOfLaid(laid) {
|
|
43
|
+
const [from, to] = laidEnds(laid);
|
|
44
|
+
return tileOf(from, to);
|
|
45
|
+
}
|
|
46
|
+
/** Every tile of a set, double-blank to its highest double, in order. */
|
|
47
|
+
export function everyTile(set) {
|
|
48
|
+
const tiles = [];
|
|
49
|
+
for (let low = 0; low <= set; low += 1)
|
|
50
|
+
for (let high = low; high <= set; high += 1)
|
|
51
|
+
tiles.push(tileOf(low, high));
|
|
52
|
+
return tiles;
|
|
53
|
+
}
|
|
54
|
+
/** A tile as a person reads it: "6–4", the larger end first, as a tile is named at the table. */
|
|
55
|
+
export function tileWords(tile) {
|
|
56
|
+
const [low, high] = endsOf(tile);
|
|
57
|
+
return `${high}–${low}`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TrainLength, TrainOptions } from "./mexicanTrain.types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* THE SETS OFFERED, by their highest double. Double-twelve is the set Mexican
|
|
4
|
+
* Train is sold with and the one most published rules are written for, so it
|
|
5
|
+
* is the default; double-nine for a quicker game with fewer, larger pips, and
|
|
6
|
+
* double-fifteen for a long evening. These are the party game's "sizes"
|
|
7
|
+
* (`PARTY_SPECS`), since the set is what the table is played on.
|
|
8
|
+
*/
|
|
9
|
+
export declare const TRAIN_SETS: {
|
|
10
|
+
readonly nine: 9;
|
|
11
|
+
readonly twelve: 12;
|
|
12
|
+
readonly fifteen: 15;
|
|
13
|
+
};
|
|
14
|
+
/** The largest set's highest double, and so the base a tile's two ends are written in (`tileOf`). */
|
|
15
|
+
export declare const TRAIN_PIP_BASE = 16;
|
|
16
|
+
/**
|
|
17
|
+
* HOW MANY TILES EACH PLAYER IS DEALT, by the set and the number at the
|
|
18
|
+
* table. Double-twelve's are the figures most published rules give (fifteen
|
|
19
|
+
* each for two to four, twelve for five or six, ten for seven or eight); the
|
|
20
|
+
* other sets are scaled so that at every table some tiles are left to draw.
|
|
21
|
+
*/
|
|
22
|
+
export declare function handSizeFor(set: number, players: number): number;
|
|
23
|
+
/** The options a table opens on: every round, one double at a time, and the Mexican Train open from the start. */
|
|
24
|
+
export declare const TRAIN_DEFAULT_OPTIONS: TrainOptions;
|
|
25
|
+
export declare const TRAIN_LENGTHS: {
|
|
26
|
+
readonly full: "full";
|
|
27
|
+
readonly short: "short";
|
|
28
|
+
};
|
|
29
|
+
export declare const TRAIN_DOUBLES: {
|
|
30
|
+
readonly one: "one";
|
|
31
|
+
readonly chain: "chain";
|
|
32
|
+
};
|
|
33
|
+
export declare const TRAIN_MEXICAN: {
|
|
34
|
+
readonly any: "any";
|
|
35
|
+
readonly ownFirst: "ownFirst";
|
|
36
|
+
};
|
|
37
|
+
/** How many rounds a game of this length plays with this set: one per double, top to blank, or the first half of them. */
|
|
38
|
+
export declare function roundsFor(set: number, length: TrainLength): number;
|
|
39
|
+
/** Each set by name, for the set-up's tiles and the card on My games. */
|
|
40
|
+
export declare const TRAIN_SET_NAMES: Record<number, string>;
|
|
41
|
+
/** The set's name, or null for a number that is no set offered. */
|
|
42
|
+
export declare function trainSetName(set: number): string | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE SETS OFFERED, by their highest double. Double-twelve is the set Mexican
|
|
3
|
+
* Train is sold with and the one most published rules are written for, so it
|
|
4
|
+
* is the default; double-nine for a quicker game with fewer, larger pips, and
|
|
5
|
+
* double-fifteen for a long evening. These are the party game's "sizes"
|
|
6
|
+
* (`PARTY_SPECS`), since the set is what the table is played on.
|
|
7
|
+
*/
|
|
8
|
+
export const TRAIN_SETS = { nine: 9, twelve: 12, fifteen: 15 };
|
|
9
|
+
/** The largest set's highest double, and so the base a tile's two ends are written in (`tileOf`). */
|
|
10
|
+
export const TRAIN_PIP_BASE = 16;
|
|
11
|
+
/**
|
|
12
|
+
* HOW MANY TILES EACH PLAYER IS DEALT, by the set and the number at the
|
|
13
|
+
* table. Double-twelve's are the figures most published rules give (fifteen
|
|
14
|
+
* each for two to four, twelve for five or six, ten for seven or eight); the
|
|
15
|
+
* other sets are scaled so that at every table some tiles are left to draw.
|
|
16
|
+
*/
|
|
17
|
+
export function handSizeFor(set, players) {
|
|
18
|
+
const few = players <= 4;
|
|
19
|
+
const some = players <= 6;
|
|
20
|
+
if (set === TRAIN_SETS.nine)
|
|
21
|
+
return few ? 10 : some ? 8 : 6;
|
|
22
|
+
if (set === TRAIN_SETS.fifteen)
|
|
23
|
+
return few ? 15 : some ? 13 : 12;
|
|
24
|
+
return few ? 15 : some ? 12 : 10;
|
|
25
|
+
}
|
|
26
|
+
/** The options a table opens on: every round, one double at a time, and the Mexican Train open from the start. */
|
|
27
|
+
export const TRAIN_DEFAULT_OPTIONS = { length: "full", doubles: "one", mexican: "any" };
|
|
28
|
+
export const TRAIN_LENGTHS = { full: "full", short: "short" };
|
|
29
|
+
export const TRAIN_DOUBLES = { one: "one", chain: "chain" };
|
|
30
|
+
export const TRAIN_MEXICAN = { any: "any", ownFirst: "ownFirst" };
|
|
31
|
+
/** How many rounds a game of this length plays with this set: one per double, top to blank, or the first half of them. */
|
|
32
|
+
export function roundsFor(set, length) {
|
|
33
|
+
return length === TRAIN_LENGTHS.full ? set + 1 : Math.ceil((set + 1) / 2);
|
|
34
|
+
}
|
|
35
|
+
/** Each set by name, for the set-up's tiles and the card on My games. */
|
|
36
|
+
export const TRAIN_SET_NAMES = { 9: "Double-nine", 12: "Double-twelve", 15: "Double-fifteen" };
|
|
37
|
+
/** The set's name, or null for a number that is no set offered. */
|
|
38
|
+
export function trainSetName(set) {
|
|
39
|
+
return TRAIN_SET_NAMES[set] ?? null;
|
|
40
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Domino, TrainGame, TrainMove, TrainOptions, TrainSeat } from "./mexicanTrain.types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* MEXICAN TRAIN, THE DOMINO GAME: the rules, and nothing else.
|
|
4
|
+
*
|
|
5
|
+
* Pure, as the engine is: every function returns a new game and leaves the
|
|
6
|
+
* one it was given untouched. A game is its table (the set, the options, the
|
|
7
|
+
* seed, the seats) and its moves, in order; hands, trains, the boneyard and
|
|
8
|
+
* whose turn it is are always read again from those (`replayTrain`), so a game
|
|
9
|
+
* read back out of a browser's storage is exactly the game its moves make,
|
|
10
|
+
* or none. Every shuffle is drawn from the game's seed and the round's
|
|
11
|
+
* number, so a reload deals exactly what it dealt before.
|
|
12
|
+
*
|
|
13
|
+
* The rules as the site plays them, most published rules' own:
|
|
14
|
+
*
|
|
15
|
+
* - a round is dealt round the engine double, the set's highest in the
|
|
16
|
+
* first round and one fewer each round after, which sits in the hub;
|
|
17
|
+
* - every player has a train of their own out of the hub, and there is one
|
|
18
|
+
* more, the Mexican Train, anybody may play on;
|
|
19
|
+
* - on your turn lay one tile against the open end of your own train, the
|
|
20
|
+
* Mexican Train, or any player's train whose marker is out;
|
|
21
|
+
* - nothing to lay: draw one tile; lay it if it goes, or put your marker out
|
|
22
|
+
* and pass (with nothing to draw, just put it out); lay on your own train
|
|
23
|
+
* and your marker comes in;
|
|
24
|
+
* - a double must be covered before anything else is played anywhere, and
|
|
25
|
+
* whoever lays one lays again to cover it (`DoublesRule` for the house
|
|
26
|
+
* rule that lets doubles be chained);
|
|
27
|
+
* - a round ends when somebody lays their last tile, or when nobody can lay
|
|
28
|
+
* and there is nothing left to draw; every player scores the pips left in
|
|
29
|
+
* their hand, and after the last round the lowest total wins.
|
|
30
|
+
*/
|
|
31
|
+
export declare const TRAIN_PHASES: {
|
|
32
|
+
readonly playing: "playing";
|
|
33
|
+
readonly roundOver: "roundOver";
|
|
34
|
+
readonly finished: "finished";
|
|
35
|
+
};
|
|
36
|
+
/** The longest name a seat keeps. */
|
|
37
|
+
export declare const TRAIN_NAME_MOST = 20;
|
|
38
|
+
/** A seat's name as given, its spaces tidied and cut to `TRAIN_NAME_MOST` characters. */
|
|
39
|
+
export declare function cleanTrainName(name: string): string;
|
|
40
|
+
/** The Mexican Train's number among a game's trains: after every seat's own. */
|
|
41
|
+
export declare function mexicanOf(game: Pick<TrainGame, "players">): number;
|
|
42
|
+
/** The number a train's next tile must match: its last tile's open end, or the engine double's when nothing is laid on it yet. */
|
|
43
|
+
export declare function openEnd(game: TrainGame, train: number): number;
|
|
44
|
+
/**
|
|
45
|
+
* A new game: the set (its highest double), the names at the table (one a
|
|
46
|
+
* seat), which seats a computer plays, the options, and the seed every
|
|
47
|
+
* shuffle is drawn from. Null for a table the game is not offered for — a set
|
|
48
|
+
* not in `PARTY_SPECS`, or too few or too many players — rather than a game
|
|
49
|
+
* nobody chose.
|
|
50
|
+
*/
|
|
51
|
+
export declare function startTrain(set: number, players: readonly string[], seed?: number, options?: TrainOptions, computers?: readonly boolean[]): TrainGame | null;
|
|
52
|
+
/**
|
|
53
|
+
* Every tile the player to move may lay, and where: what `moves` offers, and
|
|
54
|
+
* what the table lights up. While a double is uncovered anywhere the only
|
|
55
|
+
* lay is to cover the last of them, on whoever's train it is — or, under the
|
|
56
|
+
* chained-doubles rule, another double laid by the player still laying them.
|
|
57
|
+
*/
|
|
58
|
+
export declare function legalPlays(game: TrainGame): {
|
|
59
|
+
tile: Domino;
|
|
60
|
+
train: number;
|
|
61
|
+
}[];
|
|
62
|
+
/**
|
|
63
|
+
* Whether the player to move may lay this tile on this train now: the same
|
|
64
|
+
* answer `legalPlays` gives, for one tile, without listing every other.
|
|
65
|
+
*/
|
|
66
|
+
export declare function mayLay(game: TrainGame, tile: Domino, train: number): boolean;
|
|
67
|
+
/** Every move the player to move may make now; none once the game is over. */
|
|
68
|
+
export declare function trainMoves(game: TrainGame): TrainMove[];
|
|
69
|
+
/** Every move a game has made, first to last. */
|
|
70
|
+
export declare function movesOf(game: Pick<TrainGame, "history">): TrainMove[];
|
|
71
|
+
/** How many moves a game has made. */
|
|
72
|
+
export declare function moveCount(game: Pick<TrainGame, "history">): number;
|
|
73
|
+
/** Each seat's total over every round played: the lowest wins. */
|
|
74
|
+
export declare function trainTotals(game: Pick<TrainGame, "players" | "results">): number[];
|
|
75
|
+
/** The pips in a hand, which count against its holder when the round ends. */
|
|
76
|
+
export declare function handPips(hand: readonly Domino[]): number;
|
|
77
|
+
/**
|
|
78
|
+
* The game after that move, or null for a move that may not be made now.
|
|
79
|
+
* The game given is left untouched, and the move is added to its record.
|
|
80
|
+
*/
|
|
81
|
+
export declare function playTrain(game: TrainGame, move: TrainMove): TrainGame | null;
|
|
82
|
+
/**
|
|
83
|
+
* A game played again from its table and its moves: what reading a kept game
|
|
84
|
+
* back does. Null if any move is one the rules would not have taken.
|
|
85
|
+
*/
|
|
86
|
+
export declare function replayTrain(set: number, players: readonly string[], seed: number, options: TrainOptions, computers: readonly boolean[], moves: readonly TrainMove[]): TrainGame | null;
|
|
87
|
+
/** The same table again, the same seats and options, with a fresh shuffle. */
|
|
88
|
+
export declare function trainAgain(game: TrainGame, seed: number): TrainGame;
|
|
89
|
+
/** A seat's name as the table reads it: the one given, or "Computer 3" for a computer's seat left blank, "Player 3" for a person's. */
|
|
90
|
+
export declare function trainPlayerName(game: Pick<TrainGame, "players" | "computers">, seat: TrainSeat): string;
|
|
91
|
+
/** The seats a person plays: a table with two or more of them passes the device, and covers each hand between turns. */
|
|
92
|
+
export declare function peopleAt(game: Pick<TrainGame, "computers">): TrainSeat[];
|