@johnmorrisdotca/kumimoji 1.2.2 → 1.3.1

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/CONTRIBUTING.md +11 -2
  3. package/README.md +80 -39
  4. package/dist/check.d.ts +26 -1
  5. package/dist/check.js +30 -3
  6. package/dist/computer.types.d.ts +24 -2
  7. package/dist/computerPlay.d.ts +45 -2
  8. package/dist/computerPlay.js +56 -6
  9. package/dist/computerTurn.d.ts +42 -3
  10. package/dist/computerTurn.js +47 -5
  11. package/dist/daily.d.ts +22 -2
  12. package/dist/daily.js +23 -3
  13. package/dist/element-define.d.ts +1 -1
  14. package/dist/element-define.js +3 -13
  15. package/dist/element.d.ts +85 -4
  16. package/dist/element.js +359 -77
  17. package/dist/embed-day.d.ts +4 -0
  18. package/dist/embed-day.js +23 -0
  19. package/dist/export.d.ts +123 -6
  20. package/dist/export.js +109 -9
  21. package/dist/generate.d.ts +14 -0
  22. package/dist/generate.js +32 -5
  23. package/dist/grid.d.ts +181 -10
  24. package/dist/grid.js +148 -11
  25. package/dist/help.d.ts +36 -3
  26. package/dist/help.js +36 -3
  27. package/dist/index.d.ts +22 -22
  28. package/dist/index.js +22 -22
  29. package/dist/judge.d.ts +14 -0
  30. package/dist/judge.js +14 -0
  31. package/dist/kana.d.ts +23 -1
  32. package/dist/kana.js +62 -7
  33. package/dist/kumimoji.types.d.ts +70 -5
  34. package/dist/party.d.ts +268 -14
  35. package/dist/party.js +296 -19
  36. package/dist/party.types.d.ts +50 -2
  37. package/dist/partyKept.d.ts +74 -4
  38. package/dist/partyKept.js +127 -12
  39. package/dist/partySeats.d.ts +92 -6
  40. package/dist/partySeats.js +83 -8
  41. package/dist/partyTurns.d.ts +171 -5
  42. package/dist/partyTurns.js +187 -8
  43. package/dist/placement.d.ts +50 -2
  44. package/dist/placement.js +35 -2
  45. package/dist/play.d.ts +247 -15
  46. package/dist/play.js +251 -18
  47. package/dist/random.d.ts +34 -3
  48. package/dist/random.js +23 -2
  49. package/dist/react.d.ts +42 -4
  50. package/dist/react.js +54 -7
  51. package/dist/showcase.d.ts +83 -7
  52. package/dist/showcase.js +61 -7
  53. package/dist/strings.d.ts +54 -5
  54. package/dist/strings.js +34 -3
  55. package/dist/tableView.d.ts +151 -8
  56. package/dist/tableView.js +129 -6
  57. package/dist/tileFace.d.ts +59 -5
  58. package/dist/tileFace.js +48 -4
  59. package/dist/tileFamily.d.ts +9 -0
  60. package/dist/tileFamily.js +9 -0
  61. package/dist/tileWords.d.ts +93 -6
  62. package/dist/tileWords.js +94 -12
  63. package/dist/tileWordsModule.d.ts +12 -2
  64. package/dist/tileWordsModule.js +14 -2
  65. package/dist/tiles.constants.d.ts +99 -5
  66. package/dist/tiles.constants.js +171 -13
  67. package/dist/turn.d.ts +82 -4
  68. package/dist/turn.js +88 -5
  69. package/dist/ui/board.d.ts +60 -5
  70. package/dist/ui/board.js +44 -4
  71. package/dist/ui/draw.d.ts +62 -0
  72. package/dist/ui/draw.js +229 -0
  73. package/dist/ui/mount.d.ts +41 -2
  74. package/dist/ui/mount.js +190 -265
  75. package/dist/ui/style.d.ts +6 -1
  76. package/dist/ui/style.js +6 -0
  77. package/dist/ui.d.ts +3 -3
  78. package/dist/ui.js +2 -2
  79. package/dist/version.d.ts +9 -2
  80. package/dist/version.js +9 -2
  81. package/dist/wilds.d.ts +22 -1
  82. package/dist/wilds.js +25 -3
  83. package/package.json +68 -61
package/dist/partyKept.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { decodeGrid, encodeGrid, lettersOf, sameLetters } from "./grid.js";
2
- import { KUMIMOJI_PARTY, KUMIMOJI_HANDS } from "./tiles.constants.js";
2
+ import { KUMIMOJI_HANDS, KUMIMOJI_PARTY } from "./tiles.constants.js";
3
3
  /**
4
4
  * A PASS-AND-PLAY GAME KEPT IN THE BROWSER, as one string of JSON: the
5
5
  * settings, the whole bag in its order, what was traded back and taken, whose
@@ -18,7 +18,20 @@ import { KUMIMOJI_PARTY, KUMIMOJI_HANDS } from "./tiles.constants.js";
18
18
  * nothing to check, and the names in it are the players' own business.
19
19
  */
20
20
  const VERSION = 1;
21
- /** A table of players as text to keep. `decodeParty` reads it back, and given the word list's `familyKey` checks every tile held against the bag. */
21
+ /** A table of players as text to keep. `decodeParty` reads it back, and given the word list's `familyKey` checks every tile held against the bag.
22
+ *
23
+ * @param game - The current immutable shared-bag party game.
24
+ * @returns A table of players as text to keep. `decodeParty` reads it back, and given the word list's `familyKey` checks every tile held against the bag.
25
+ * @example
26
+ * ```ts
27
+ * import { encodeParty, startParty } from "@johnmorrisdotca/kumimoji";
28
+ *
29
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
30
+ * const bag = "catdogsun";
31
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
32
+ * console.log(encodeParty(game));
33
+ * ```
34
+ */
22
35
  export function encodeParty(game) {
23
36
  const kept = {
24
37
  v: VERSION,
@@ -37,13 +50,29 @@ export function encodeParty(game) {
37
50
  traded: game.traded,
38
51
  passedBy: game.passedBy,
39
52
  ...(game.dealt === game.players.length ? {} : { dealt: game.dealt }),
40
- players: game.players.map((player) => ({ name: player.name, hand: player.hand.join(""), grid: encodeGrid(player.tiles), ...(player.computer === true ? { computer: true } : {}) })),
53
+ players: game.players.map((player) => ({
54
+ name: player.name,
55
+ hand: player.hand.join(""),
56
+ grid: encodeGrid(player.tiles),
57
+ ...(player.computer === true ? { computer: true } : {}),
58
+ })),
41
59
  };
42
60
  return JSON.stringify(kept);
43
61
  }
44
62
  const isCount = (value, most) => Number.isInteger(value) && value >= 0 && value <= most;
45
63
  const isTiles = (value) => typeof value === "string" && /^[a-zA-Z*-]*$/.test(value);
46
- /** A game's settings as something outside this browser sent them — a table on several devices, a kept game — checked, or null. */
64
+ /** A game's settings as something outside this browser sent them — a table on several devices, a kept game — checked, or null.
65
+ *
66
+ * @param value - The unknown setup value to interpret.
67
+ * @returns A game's settings as something outside this browser sent them — a table on several devices, a kept game — checked, or null.
68
+ * @example
69
+ * ```ts
70
+ * import { readSettings } from "@johnmorrisdotca/kumimoji";
71
+ *
72
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
73
+ * console.log(readSettings(settings));
74
+ * ```
75
+ */
47
76
  export function readSettings(value) {
48
77
  if (typeof value !== "object" || value === null)
49
78
  return null;
@@ -64,7 +93,16 @@ export function readSettings(value) {
64
93
  // A game kept before Diagonals existed has no word for it, and was played without.
65
94
  if (s.diagonals !== undefined && typeof s.diagonals !== "boolean")
66
95
  return null;
67
- return { size: s.size, level: s.level, seed: s.seed, gameLength: s.gameLength, language: s.language, doubleSet: s.doubleSet, diagonals: s.diagonals === true, hints: s.hints };
96
+ return {
97
+ size: s.size,
98
+ level: s.level,
99
+ seed: s.seed,
100
+ gameLength: s.gameLength,
101
+ language: s.language,
102
+ doubleSet: s.doubleSet,
103
+ diagonals: s.diagonals === true,
104
+ hints: s.hints,
105
+ };
68
106
  }
69
107
  /**
70
108
  * A kept game read back, or null for anything that is not one. With
@@ -73,6 +111,21 @@ export function readSettings(value) {
73
111
  * it and not given back, as the solo game checks a kept run
74
112
  * (`decodeTileProgress`). Without, its shape alone, which is enough to offer
75
113
  * it on the set-up screen.
114
+ *
115
+ * @param code - The encoded grid or progress text.
116
+ * @param familyKey - The function identifying physical tile families, or null for invalid codes.
117
+ * @returns A kept game read back, or null for anything that is not one. With `familyKey` (the loaded word list's), it is also checked against its own bag: every tile held by anybody — hand and table — must be one taken out of it and not given back, as the solo game checks a kept run (`decodeTileProgress`). Without, its shape alone, which is enough to offer it on the set-up screen.
118
+ * @example
119
+ * ```ts
120
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
121
+ * import { decodeParty, encodeParty, startParty } from "@johnmorrisdotca/kumimoji";
122
+ *
123
+ * const words = await loadTileWordsFromModule("english");
124
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
125
+ * const bag = "catdogsun";
126
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
127
+ * console.log(decodeParty(encodeParty(game),words.familyKey)?.players.length);
128
+ * ```
76
129
  */
77
130
  export function decodeParty(code, familyKey) {
78
131
  if (code === null)
@@ -99,28 +152,75 @@ export function decodeParty(code, familyKey) {
99
152
  const dealt = kept.dealt ?? count;
100
153
  if (!Number.isInteger(dealt) || dealt < KUMIMOJI_PARTY.least || dealt > KUMIMOJI_PARTY.most)
101
154
  return null;
102
- if (!isCount(kept.turn, count - 1) || !isCount(kept.turns, 1000000) || (kept.ending !== null && kept.ending !== "out" && kept.ending !== "standing" && kept.ending !== "tied") || typeof kept.traded !== "boolean" || !isTiles(kept.startTable) || (kept.passedBy !== null && !isCount(kept.passedBy, count - 1)))
155
+ if (!isCount(kept.turn, count - 1) ||
156
+ !isCount(kept.turns, 1_000_000) ||
157
+ (kept.ending !== null && kept.ending !== "out" && kept.ending !== "standing" && kept.ending !== "tied") ||
158
+ typeof kept.traded !== "boolean" ||
159
+ !isTiles(kept.startTable) ||
160
+ (kept.passedBy !== null && !isCount(kept.passedBy, count - 1)))
103
161
  return null;
104
162
  const seats = (list) => Array.isArray(list) && list.every((at) => isCount(at, count - 1)) && new Set(list).size === list.length;
105
- if (!seats(kept.out) || (kept.lastTurns !== null && !seats(kept.lastTurns)) || !seats(kept.resigned) || !seats(kept.resignRun))
163
+ if (!seats(kept.out) ||
164
+ (kept.lastTurns !== null && !seats(kept.lastTurns)) ||
165
+ !seats(kept.resigned) ||
166
+ !seats(kept.resignRun))
106
167
  return null;
107
168
  const players = [];
108
169
  for (const player of kept.players) {
109
- if (typeof player !== "object" || player === null || typeof player.name !== "string" || player.name.length > KUMIMOJI_PARTY.nameMost || !isTiles(player.hand))
170
+ if (typeof player !== "object" ||
171
+ player === null ||
172
+ typeof player.name !== "string" ||
173
+ player.name.length > KUMIMOJI_PARTY.nameMost ||
174
+ !isTiles(player.hand))
110
175
  return null;
111
176
  if (player.computer !== undefined && player.computer !== true)
112
177
  return null;
113
178
  const tiles = decodeGrid(player.grid);
114
179
  if (tiles === null)
115
180
  return null;
116
- players.push(player.computer === true ? { name: player.name, hand: [...player.hand], tiles, computer: true } : { name: player.name, hand: [...player.hand], tiles });
181
+ players.push(player.computer === true
182
+ ? { name: player.name, hand: [...player.hand], tiles, computer: true }
183
+ : { name: player.name, hand: [...player.hand], tiles });
117
184
  }
118
- const game = { settings, bag: kept.bag, returned: kept.returned, taken: kept.taken, players, dealt, turn: kept.turn, turns: kept.turns, out: kept.out, lastTurns: kept.lastTurns, resigned: kept.resigned, resignRun: kept.resignRun, ending: kept.ending, startTable: kept.startTable, traded: kept.traded, passedBy: kept.passedBy };
185
+ const game = {
186
+ settings,
187
+ bag: kept.bag,
188
+ returned: kept.returned,
189
+ taken: kept.taken,
190
+ players,
191
+ dealt,
192
+ turn: kept.turn,
193
+ turns: kept.turns,
194
+ out: kept.out,
195
+ lastTurns: kept.lastTurns,
196
+ resigned: kept.resigned,
197
+ resignRun: kept.resignRun,
198
+ ending: kept.ending,
199
+ startTable: kept.startTable,
200
+ traded: kept.traded,
201
+ passedBy: kept.passedBy,
202
+ };
119
203
  if (familyKey !== undefined && !holdsItsBag(game, familyKey))
120
204
  return null;
121
205
  return game;
122
206
  }
123
- /** Whether what everybody holds is exactly what was taken from the bag, less what was given back. */
207
+ /** Whether what everybody holds is exactly what was taken from the bag, less what was given back.
208
+ *
209
+ * @param game - The current immutable shared-bag party game.
210
+ * @param familyKey - The function identifying physical tile families, or null for invalid codes.
211
+ * @returns Whether what everybody holds is exactly what was taken from the bag, less what was given back.
212
+ * @example
213
+ * ```ts
214
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
215
+ * import { holdsItsBag, startParty } from "@johnmorrisdotca/kumimoji";
216
+ *
217
+ * const words = await loadTileWordsFromModule("english");
218
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
219
+ * const bag = "catdogsun";
220
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
221
+ * console.log(holdsItsBag(game,words.familyKey));
222
+ * ```
223
+ */
124
224
  export function holdsItsBag(game, familyKey) {
125
225
  const taken = [...(game.bag + game.returned).slice(0, game.taken)].map(familyKey);
126
226
  const back = [...game.returned].map(familyKey);
@@ -135,7 +235,22 @@ export function holdsItsBag(game, familyKey) {
135
235
  tally.delete(tile);
136
236
  return sameLetters(tally, lettersOf(held));
137
237
  }
138
- /** Whether a kept game is the one an address asks for: the same settings, and dealt to as many players, whoever has joined or left since. */
238
+ /** Whether a kept game is the one an address asks for: the same settings, and dealt to as many players, whoever has joined or left since.
239
+ *
240
+ * @param game - The current immutable shared-bag party game.
241
+ * @param settings - The native party setup settings.
242
+ * @param players - The requested player count or current player rows.
243
+ * @returns Whether a kept game is the one an address asks for: the same settings, and dealt to as many players, whoever has joined or left since.
244
+ * @example
245
+ * ```ts
246
+ * import { isPartyFor, startParty } from "@johnmorrisdotca/kumimoji";
247
+ *
248
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
249
+ * const bag = "catdogsun";
250
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
251
+ * console.log(isPartyFor(game,settings,2));
252
+ * ```
253
+ */
139
254
  export function isPartyFor(game, settings, players) {
140
255
  const a = game.settings;
141
256
  return (game.dealt === players &&
@@ -24,17 +24,90 @@ import type { PartyGame, PartySeat } from "./party.types.ts";
24
24
  * standing (`lastStanding`), and wins by laying a tile on a sound grid, as
25
25
  * when everybody else resigns — unless somebody joins first.
26
26
  */
27
- /** Why nobody can join now, or null when a hand can be dealt. */
27
+ /** Why nobody can join now, or null when a hand can be dealt.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import type { JoinRefusal } from "@johnmorrisdotca/kumimoji";
32
+ *
33
+ * const value: JoinRefusal = "full";
34
+ * console.log(value);
35
+ * ```
36
+ */
28
37
  export type JoinRefusal = "over" | "full" | "lastRound" | "bag";
29
- /** Why nobody can join the table now, or null when a hand can be dealt. */
38
+ /** Why nobody can join the table now, or null when a hand can be dealt.
39
+ *
40
+ * @param game - The current immutable shared-bag party game.
41
+ * @returns Why nobody can join the table now, or null when a hand can be dealt.
42
+ * @example
43
+ * ```ts
44
+ * import { joinRefused, startParty } from "@johnmorrisdotca/kumimoji";
45
+ *
46
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
47
+ * const bag = "catdogsun";
48
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
49
+ * console.log(joinRefused(game));
50
+ * ```
51
+ */
30
52
  export declare function joinRefused(game: PartyGame): JoinRefusal | null;
31
- /** A player, or a computer, sat down after the last seat with the next hand out of the bag. */
53
+ /** A player, or a computer, sat down after the last seat with the next hand out of the bag.
54
+ *
55
+ * @param game - The current immutable shared-bag party game.
56
+ * @param seat - The local name and computer flag for the joining player.
57
+ * @returns A player, or a computer, sat down after the last seat with the next hand out of the bag.
58
+ * @example
59
+ * ```ts
60
+ * import { joinParty, startParty } from "@johnmorrisdotca/kumimoji";
61
+ *
62
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
63
+ * const bag = "catdogsun";
64
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
65
+ * console.log(joinParty(game,{name:"Seat 3"}).players.length);
66
+ * ```
67
+ */
32
68
  export declare function joinParty(game: PartyGame, seat: PartySeat): PartyGame;
33
- /** Why this player cannot leave now, or null when they can. */
69
+ /** Why this player cannot leave now, or null when they can.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import type { LeaveRefusal } from "@johnmorrisdotca/kumimoji";
74
+ *
75
+ * const value: LeaveRefusal = "lastPerson";
76
+ * console.log(value);
77
+ * ```
78
+ */
34
79
  export type LeaveRefusal = "over" | "out" | "lastPerson";
35
- /** Why the player at seat `at` cannot leave now, or null when they can. */
80
+ /** Why the player at seat `at` cannot leave now, or null when they can.
81
+ *
82
+ * @param game - The current immutable shared-bag party game.
83
+ * @param at - The word offset, seat index or date specified by this signature.
84
+ * @returns Why the player at seat `at` cannot leave now, or null when they can.
85
+ * @example
86
+ * ```ts
87
+ * import { leaveRefused, startParty } from "@johnmorrisdotca/kumimoji";
88
+ *
89
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
90
+ * const bag = "catdogsun";
91
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
92
+ * console.log(leaveRefused(game,0));
93
+ * ```
94
+ */
36
95
  export declare function leaveRefused(game: PartyGame, at: number): LeaveRefusal | null;
37
- /** How many tiles a player would put back in the bag by leaving: their hand and their table. */
96
+ /** How many tiles a player would put back in the bag by leaving: their hand and their table.
97
+ *
98
+ * @param game - The current immutable shared-bag party game.
99
+ * @param at - The word offset, seat index or date specified by this signature.
100
+ * @returns How many tiles a player would put back in the bag by leaving: their hand and their table.
101
+ * @example
102
+ * ```ts
103
+ * import { startParty, tilesHeldBy } from "@johnmorrisdotca/kumimoji";
104
+ *
105
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
106
+ * const bag = "catdogsun";
107
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
108
+ * console.log(tilesHeldBy(game,0));
109
+ * ```
110
+ */
38
111
  export declare function tilesHeldBy(game: PartyGame, at: number): number;
39
112
  /**
40
113
  * A PLAYER LEAVES. Their tiles go back into the bag and the bag is shuffled
@@ -51,5 +124,18 @@ export declare function tilesHeldBy(game: PartyGame, at: number): number;
51
124
  * round to the next still to have their last turn; a last round with nobody
52
125
  * left to play ends. With nobody still in — everybody left has resigned —
53
126
  * the game ends tied between them.
127
+ *
128
+ * @param game - The current immutable shared-bag party game.
129
+ * @param at - The word offset, seat index or date specified by this signature.
130
+ * @returns A PLAYER LEAVES. Their tiles go back into the bag and the bag is shuffled — with the game's own seed and how far it has gone, so the same game left the same way deals the same tiles after a reload. A wild goes back blank.
131
+ * @example
132
+ * ```ts
133
+ * import { leaveParty, startParty } from "@johnmorrisdotca/kumimoji";
134
+ *
135
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
136
+ * const bag = "catdogsun";
137
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
138
+ * console.log(leaveParty(game,0).players.length);
139
+ * ```
54
140
  */
55
141
  export declare function leaveParty(game: PartyGame, at: number): PartyGame;
@@ -1,8 +1,21 @@
1
- import { seededRandom, shuffled } from "./random.js";
2
1
  import { computerName, isComputer, nextIn, partyTilesLeft, stillIn, tableTally, tidyName } from "./party.js";
2
+ import { seededRandom, shuffled } from "./random.js";
3
3
  import { tileFace } from "./tileFace.js";
4
4
  import { KUMIMOJI_PARTY } from "./tiles.constants.js";
5
- /** Why nobody can join the table now, or null when a hand can be dealt. */
5
+ /** Why nobody can join the table now, or null when a hand can be dealt.
6
+ *
7
+ * @param game - The current immutable shared-bag party game.
8
+ * @returns Why nobody can join the table now, or null when a hand can be dealt.
9
+ * @example
10
+ * ```ts
11
+ * import { joinRefused, startParty } from "@johnmorrisdotca/kumimoji";
12
+ *
13
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
14
+ * const bag = "catdogsun";
15
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
16
+ * console.log(joinRefused(game));
17
+ * ```
18
+ */
6
19
  export function joinRefused(game) {
7
20
  if (game.ending !== null)
8
21
  return "over";
@@ -14,13 +27,29 @@ export function joinRefused(game) {
14
27
  return "bag";
15
28
  return null;
16
29
  }
17
- /** A player, or a computer, sat down after the last seat with the next hand out of the bag. */
30
+ /** A player, or a computer, sat down after the last seat with the next hand out of the bag.
31
+ *
32
+ * @param game - The current immutable shared-bag party game.
33
+ * @param seat - The local name and computer flag for the joining player.
34
+ * @returns A player, or a computer, sat down after the last seat with the next hand out of the bag.
35
+ * @example
36
+ * ```ts
37
+ * import { joinParty, startParty } from "@johnmorrisdotca/kumimoji";
38
+ *
39
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
40
+ * const bag = "catdogsun";
41
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
42
+ * console.log(joinParty(game,{name:"Seat 3"}).players.length);
43
+ * ```
44
+ */
18
45
  export function joinParty(game, seat) {
19
46
  if (joinRefused(game) !== null)
20
47
  return game;
21
48
  const hand = [...(game.bag + game.returned).slice(game.taken, game.taken + game.settings.size)];
22
49
  const tiles = new Map();
23
- const player = seat.computer === true ? { name: computerName(game.players), computer: true, hand, tiles } : { name: tidyName(seat.name) || freePlayerName(game.players), hand, tiles };
50
+ const player = seat.computer === true
51
+ ? { name: computerName(game.players), computer: true, hand, tiles }
52
+ : { name: tidyName(seat.name) || freePlayerName(game.players), hand, tiles };
24
53
  return { ...game, players: [...game.players, player], taken: game.taken + hand.length };
25
54
  }
26
55
  /** "Player" and the next seat's number, or the lowest number nobody at the table goes by. */
@@ -33,7 +62,21 @@ function freePlayerName(players) {
33
62
  if (!taken.has(`Player ${number}`))
34
63
  return `Player ${number}`;
35
64
  }
36
- /** Why the player at seat `at` cannot leave now, or null when they can. */
65
+ /** Why the player at seat `at` cannot leave now, or null when they can.
66
+ *
67
+ * @param game - The current immutable shared-bag party game.
68
+ * @param at - The word offset, seat index or date specified by this signature.
69
+ * @returns Why the player at seat `at` cannot leave now, or null when they can.
70
+ * @example
71
+ * ```ts
72
+ * import { leaveRefused, startParty } from "@johnmorrisdotca/kumimoji";
73
+ *
74
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
75
+ * const bag = "catdogsun";
76
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
77
+ * console.log(leaveRefused(game,0));
78
+ * ```
79
+ */
37
80
  export function leaveRefused(game, at) {
38
81
  if (game.ending !== null || game.players[at] === undefined)
39
82
  return "over";
@@ -43,7 +86,21 @@ export function leaveRefused(game, at) {
43
86
  return "lastPerson";
44
87
  return null;
45
88
  }
46
- /** How many tiles a player would put back in the bag by leaving: their hand and their table. */
89
+ /** How many tiles a player would put back in the bag by leaving: their hand and their table.
90
+ *
91
+ * @param game - The current immutable shared-bag party game.
92
+ * @param at - The word offset, seat index or date specified by this signature.
93
+ * @returns How many tiles a player would put back in the bag by leaving: their hand and their table.
94
+ * @example
95
+ * ```ts
96
+ * import { startParty, tilesHeldBy } from "@johnmorrisdotca/kumimoji";
97
+ *
98
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
99
+ * const bag = "catdogsun";
100
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
101
+ * console.log(tilesHeldBy(game,0));
102
+ * ```
103
+ */
47
104
  export function tilesHeldBy(game, at) {
48
105
  const player = game.players[at];
49
106
  return player === undefined ? 0 : player.hand.length + player.tiles.size;
@@ -63,13 +120,26 @@ export function tilesHeldBy(game, at) {
63
120
  * round to the next still to have their last turn; a last round with nobody
64
121
  * left to play ends. With nobody still in — everybody left has resigned —
65
122
  * the game ends tied between them.
123
+ *
124
+ * @param game - The current immutable shared-bag party game.
125
+ * @param at - The word offset, seat index or date specified by this signature.
126
+ * @returns A PLAYER LEAVES. Their tiles go back into the bag and the bag is shuffled — with the game's own seed and how far it has gone, so the same game left the same way deals the same tiles after a reload. A wild goes back blank.
127
+ * @example
128
+ * ```ts
129
+ * import { leaveParty, startParty } from "@johnmorrisdotca/kumimoji";
130
+ *
131
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
132
+ * const bag = "catdogsun";
133
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
134
+ * console.log(leaveParty(game,0).players.length);
135
+ * ```
66
136
  */
67
137
  export function leaveParty(game, at) {
68
138
  if (leaveRefused(game, at) !== null)
69
139
  return game;
70
140
  const leaver = game.players[at];
71
141
  const back = [...leaver.hand, ...leaver.tiles.values()].map((tile) => (tileFace(tile).wild ? "*" : tile));
72
- const players = game.players.flatMap((player, seat) => (seat === at ? [] : [{ ...player, name: player.name || `Player ${seat + 1}` }]));
142
+ const players = game.players.flatMap((player, seat) => seat === at ? [] : [{ ...player, name: player.name || `Player ${seat + 1}` }]);
73
143
  const seatOf = (seat) => (seat > at ? seat - 1 : seat);
74
144
  const without = (seats) => seats.filter((seat) => seat !== at).map(seatOf);
75
145
  const held = players.flatMap((player) => [...player.hand, ...player.tiles.values()]);
@@ -111,7 +181,12 @@ export function leaveParty(game, at) {
111
181
  const moved = at === game.turn ? { startTable: tableTally(players[turn].tiles), traded: false } : {};
112
182
  if (ending === null && lastTurns === null && stillIn(after).length === 0) {
113
183
  // Only players who resigned are left: tied, the last run of them, or all of them where that run has left.
114
- return { ...after, ...moved, ending: "tied", resignRun: after.resignRun.length > 0 ? after.resignRun : after.resigned };
184
+ return {
185
+ ...after,
186
+ ...moved,
187
+ ending: "tied",
188
+ resignRun: after.resignRun.length > 0 ? after.resignRun : after.resigned,
189
+ };
115
190
  }
116
191
  return { ...after, ...moved };
117
192
  }