@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.
- package/CHANGELOG.md +33 -1
- package/CONTRIBUTING.md +11 -2
- package/README.md +80 -39
- package/dist/check.d.ts +26 -1
- package/dist/check.js +30 -3
- package/dist/computer.types.d.ts +24 -2
- package/dist/computerPlay.d.ts +45 -2
- package/dist/computerPlay.js +56 -6
- package/dist/computerTurn.d.ts +42 -3
- package/dist/computerTurn.js +47 -5
- package/dist/daily.d.ts +22 -2
- package/dist/daily.js +23 -3
- package/dist/element-define.d.ts +1 -1
- package/dist/element-define.js +3 -13
- package/dist/element.d.ts +85 -4
- package/dist/element.js +359 -77
- package/dist/embed-day.d.ts +4 -0
- package/dist/embed-day.js +23 -0
- package/dist/export.d.ts +123 -6
- package/dist/export.js +109 -9
- package/dist/generate.d.ts +14 -0
- package/dist/generate.js +32 -5
- package/dist/grid.d.ts +181 -10
- package/dist/grid.js +148 -11
- package/dist/help.d.ts +36 -3
- package/dist/help.js +36 -3
- package/dist/index.d.ts +22 -22
- package/dist/index.js +22 -22
- package/dist/judge.d.ts +14 -0
- package/dist/judge.js +14 -0
- package/dist/kana.d.ts +23 -1
- package/dist/kana.js +62 -7
- package/dist/kumimoji.types.d.ts +70 -5
- package/dist/party.d.ts +268 -14
- package/dist/party.js +296 -19
- package/dist/party.types.d.ts +50 -2
- package/dist/partyKept.d.ts +74 -4
- package/dist/partyKept.js +127 -12
- package/dist/partySeats.d.ts +92 -6
- package/dist/partySeats.js +83 -8
- package/dist/partyTurns.d.ts +171 -5
- package/dist/partyTurns.js +187 -8
- package/dist/placement.d.ts +50 -2
- package/dist/placement.js +35 -2
- package/dist/play.d.ts +247 -15
- package/dist/play.js +251 -18
- package/dist/random.d.ts +34 -3
- package/dist/random.js +23 -2
- package/dist/react.d.ts +42 -4
- package/dist/react.js +54 -7
- package/dist/showcase.d.ts +83 -7
- package/dist/showcase.js +61 -7
- package/dist/strings.d.ts +54 -5
- package/dist/strings.js +34 -3
- package/dist/tableView.d.ts +151 -8
- package/dist/tableView.js +129 -6
- package/dist/tileFace.d.ts +59 -5
- package/dist/tileFace.js +48 -4
- package/dist/tileFamily.d.ts +9 -0
- package/dist/tileFamily.js +9 -0
- package/dist/tileWords.d.ts +93 -6
- package/dist/tileWords.js +94 -12
- package/dist/tileWordsModule.d.ts +12 -2
- package/dist/tileWordsModule.js +14 -2
- package/dist/tiles.constants.d.ts +99 -5
- package/dist/tiles.constants.js +171 -13
- package/dist/turn.d.ts +82 -4
- package/dist/turn.js +88 -5
- package/dist/ui/board.d.ts +60 -5
- package/dist/ui/board.js +44 -4
- package/dist/ui/draw.d.ts +62 -0
- package/dist/ui/draw.js +229 -0
- package/dist/ui/mount.d.ts +41 -2
- package/dist/ui/mount.js +190 -265
- package/dist/ui/style.d.ts +6 -1
- package/dist/ui/style.js +6 -0
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +2 -2
- package/dist/version.d.ts +9 -2
- package/dist/version.js +9 -2
- package/dist/wilds.d.ts +22 -1
- package/dist/wilds.js +25 -3
- 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 {
|
|
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) => ({
|
|
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 {
|
|
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) ||
|
|
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) ||
|
|
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" ||
|
|
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
|
|
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 = {
|
|
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 &&
|
package/dist/partySeats.d.ts
CHANGED
|
@@ -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;
|
package/dist/partySeats.js
CHANGED
|
@@ -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
|
|
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) =>
|
|
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 {
|
|
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
|
}
|