@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/party.js
CHANGED
|
@@ -15,15 +15,48 @@ import { KUMIMOJI_PARTY, kumimojiTileCount } from "./tiles.constants.js";
|
|
|
15
15
|
* everybody — and in `partyTurns.ts`: Done, going out, the last round,
|
|
16
16
|
* resigning and who won. Pure, like every rule here: each returns a new game.
|
|
17
17
|
*/
|
|
18
|
-
/** The tiles a bag must hold for this many players: a hand each and one round of draws.
|
|
18
|
+
/** The tiles a bag must hold for this many players: a hand each and one round of draws.
|
|
19
|
+
*
|
|
20
|
+
* @param players - The requested player count or current player rows.
|
|
21
|
+
* @param hand - The hand size or tile codes, as declared by the function signature.
|
|
22
|
+
* @returns The tiles a bag must hold for this many players: a hand each and one round of draws.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { partyTilesNeeded } from "@johnmorrisdotca/kumimoji";
|
|
26
|
+
*
|
|
27
|
+
* console.log(partyTilesNeeded(2,3));
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
19
30
|
export function partyTilesNeeded(players, hand) {
|
|
20
31
|
return players * hand + players;
|
|
21
32
|
}
|
|
22
|
-
/** Whether a bag of this many tiles can be played by this many: one player can play any bag.
|
|
33
|
+
/** Whether a bag of this many tiles can be played by this many: one player can play any bag.
|
|
34
|
+
*
|
|
35
|
+
* @param players - The requested player count or current player rows.
|
|
36
|
+
* @param hand - The hand size or tile codes, as declared by the function signature.
|
|
37
|
+
* @param tiles - The map from row,column square names to internal tile codes.
|
|
38
|
+
* @returns Whether a bag of this many tiles can be played by this many: one player can play any bag.
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { partyFits } from "@johnmorrisdotca/kumimoji";
|
|
42
|
+
*
|
|
43
|
+
* console.log(partyFits(2,3,12));
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
23
46
|
export function partyFits(players, hand, tiles) {
|
|
24
47
|
return players < KUMIMOJI_PARTY.least || tiles >= partyTilesNeeded(players, hand);
|
|
25
48
|
}
|
|
26
|
-
/** A players count from the address, or 1 (the solo game) for anything that is not two to eight.
|
|
49
|
+
/** A players count from the address, or 1 (the solo game) for anything that is not two to eight.
|
|
50
|
+
*
|
|
51
|
+
* @param value - The unknown setup value to interpret.
|
|
52
|
+
* @returns A players count from the address, or 1 (the solo game) for anything that is not two to eight.
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* import { partyPlayersAsked } from "@johnmorrisdotca/kumimoji";
|
|
56
|
+
*
|
|
57
|
+
* console.log(partyPlayersAsked("3"));
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
27
60
|
export function partyPlayersAsked(value) {
|
|
28
61
|
const count = Number(value);
|
|
29
62
|
return Number.isInteger(count) && count >= KUMIMOJI_PARTY.least && count <= KUMIMOJI_PARTY.most ? count : 1;
|
|
@@ -35,6 +68,18 @@ const LENGTHS = ["short", "medium", "full"];
|
|
|
35
68
|
* players comes back when there are fewer again. A Full bag of one set is 144
|
|
36
69
|
* tiles, and eight Classic hands with a round of draws are 96, so there is
|
|
37
70
|
* always one.
|
|
71
|
+
*
|
|
72
|
+
* @param players - The requested player count or current player rows.
|
|
73
|
+
* @param hand - The hand size or tile codes, as declared by the function signature.
|
|
74
|
+
* @param chosen - The selected game length.
|
|
75
|
+
* @param doubleSet - Whether to use two English tile sets.
|
|
76
|
+
* @returns The game length a set-up plays at: the one chosen where the bag holds this many players, else the shortest that does, so a choice made for fewer players comes back when there are fewer again. A Full bag of one set is 144 tiles, and eight Classic hands with a round of draws are 96, so there is always one.
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { partyLength } from "@johnmorrisdotca/kumimoji";
|
|
80
|
+
*
|
|
81
|
+
* console.log(partyLength(8,11,"short",false));
|
|
82
|
+
* ```
|
|
38
83
|
*/
|
|
39
84
|
export function partyLength(players, hand, chosen, doubleSet) {
|
|
40
85
|
// One player plays any length, and asks nothing of the size: the set-up screen asks this of every puzzle's.
|
|
@@ -49,6 +94,19 @@ export function partyLength(players, hand, chosen, doubleSet) {
|
|
|
49
94
|
* The deal: each player the next hand from the bag in turn, player one first,
|
|
50
95
|
* and every table empty. A seat is a name, or a name and whether a computer
|
|
51
96
|
* plays it; a computer is named "Computer 1", "Computer 2"… in seat order.
|
|
97
|
+
*
|
|
98
|
+
* @param settings - The native party setup settings.
|
|
99
|
+
* @param bag - The original bag in draw order.
|
|
100
|
+
* @param seats - Local player names and optional computer flags.
|
|
101
|
+
* @returns The deal: each player the next hand from the bag in turn, player one first, and every table empty. A seat is a name, or a name and whether a computer plays it; a computer is named "Computer 1", "Computer 2"… in seat order.
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { startParty } from "@johnmorrisdotca/kumimoji";
|
|
105
|
+
*
|
|
106
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
107
|
+
* const bag = "catdogsun";
|
|
108
|
+
* console.log(startParty(settings,bag,["Seat 1","Seat 2"]).players.length);
|
|
109
|
+
* ```
|
|
52
110
|
*/
|
|
53
111
|
export function startParty(settings, bag, seats) {
|
|
54
112
|
const count = Math.min(Math.max(seats.length, KUMIMOJI_PARTY.least), KUMIMOJI_PARTY.most);
|
|
@@ -60,35 +118,129 @@ export function startParty(settings, bag, seats) {
|
|
|
60
118
|
const seat = seats[at] ?? "";
|
|
61
119
|
const asked = typeof seat === "string" ? { name: seat } : seat;
|
|
62
120
|
const dealt = { hand: [...bag.slice(at * hand, (at + 1) * hand)], tiles: new Map() };
|
|
63
|
-
players.push(asked.computer === true
|
|
121
|
+
players.push(asked.computer === true
|
|
122
|
+
? { name: computerName(players), computer: true, ...dealt }
|
|
123
|
+
: { name: tidyName(asked.name), ...dealt });
|
|
64
124
|
}
|
|
65
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
settings,
|
|
127
|
+
bag,
|
|
128
|
+
returned: "",
|
|
129
|
+
taken: count * hand,
|
|
130
|
+
players,
|
|
131
|
+
dealt: count,
|
|
132
|
+
turn: 0,
|
|
133
|
+
turns: 0,
|
|
134
|
+
out: [],
|
|
135
|
+
lastTurns: null,
|
|
136
|
+
resigned: [],
|
|
137
|
+
resignRun: [],
|
|
138
|
+
ending: null,
|
|
139
|
+
startTable: "",
|
|
140
|
+
traded: false,
|
|
141
|
+
passedBy: null,
|
|
142
|
+
};
|
|
66
143
|
}
|
|
67
|
-
/** The name a new computer takes: "Computer" and the lowest number no player at the table already goes by.
|
|
144
|
+
/** The name a new computer takes: "Computer" and the lowest number no player at the table already goes by.
|
|
145
|
+
*
|
|
146
|
+
* @param players - The requested player count or current player rows.
|
|
147
|
+
* @returns The name a new computer takes: "Computer" and the lowest number no player at the table already goes by.
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* import { computerName, startParty } from "@johnmorrisdotca/kumimoji";
|
|
151
|
+
*
|
|
152
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
153
|
+
* const bag = "catdogsun";
|
|
154
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
155
|
+
* console.log(computerName(game.players));
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
68
158
|
export function computerName(players) {
|
|
69
159
|
const taken = new Set(players.map((player) => player.name));
|
|
70
160
|
for (let number = 1;; number += 1)
|
|
71
161
|
if (!taken.has(`Computer ${number}`))
|
|
72
162
|
return `Computer ${number}`;
|
|
73
163
|
}
|
|
74
|
-
/** Whether a computer plays this seat.
|
|
164
|
+
/** Whether a computer plays this seat.
|
|
165
|
+
*
|
|
166
|
+
* @param game - The current immutable shared-bag party game.
|
|
167
|
+
* @param at - The word offset, seat index or date specified by this signature.
|
|
168
|
+
* @returns Whether a computer plays this seat.
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* import { isComputer, startParty } from "@johnmorrisdotca/kumimoji";
|
|
172
|
+
*
|
|
173
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
174
|
+
* const bag = "catdogsun";
|
|
175
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
176
|
+
* console.log(isComputer(game,0));
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
75
179
|
export function isComputer(game, at) {
|
|
76
180
|
return game.players[at]?.computer === true;
|
|
77
181
|
}
|
|
78
|
-
/** A table's tiles as one string in order, wherever they stand: what a turn compares to see whether anything was laid or lifted.
|
|
182
|
+
/** A table's tiles as one string in order, wherever they stand: what a turn compares to see whether anything was laid or lifted.
|
|
183
|
+
*
|
|
184
|
+
* @param tiles - The map from row,column square names to internal tile codes.
|
|
185
|
+
* @returns A table's tiles as one string in order, wherever they stand: what a turn compares to see whether anything was laid or lifted.
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* import { tableTally } from "@johnmorrisdotca/kumimoji";
|
|
189
|
+
*
|
|
190
|
+
* const tiles = new Map([["0,0","c"],["0,1","a"],["0,2","t"]]);
|
|
191
|
+
* console.log(tableTally(tiles));
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
79
194
|
export function tableTally(tiles) {
|
|
80
195
|
return [...tiles.values()].sort().join("");
|
|
81
196
|
}
|
|
82
|
-
/** A name as kept: trimmed, one line, and no longer than a line holds.
|
|
197
|
+
/** A name as kept: trimmed, one line, and no longer than a line holds.
|
|
198
|
+
*
|
|
199
|
+
* @param name - The local player name to tidy.
|
|
200
|
+
* @returns A name as kept: trimmed, one line, and no longer than a line holds.
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* import { tidyName } from "@johnmorrisdotca/kumimoji";
|
|
204
|
+
*
|
|
205
|
+
* console.log(tidyName(" Seat 1 "));
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
83
208
|
export function tidyName(name) {
|
|
84
209
|
return name.replace(/\s+/g, " ").trim().slice(0, KUMIMOJI_PARTY.nameMost);
|
|
85
210
|
}
|
|
86
|
-
/** What a player is called: their name, or "Player 2".
|
|
211
|
+
/** What a player is called: their name, or "Player 2".
|
|
212
|
+
*
|
|
213
|
+
* @param game - The current immutable shared-bag party game.
|
|
214
|
+
* @param at - The word offset, seat index or date specified by this signature.
|
|
215
|
+
* @returns What a player is called: their name, or "Player 2".
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* import { nameOf, startParty } from "@johnmorrisdotca/kumimoji";
|
|
219
|
+
*
|
|
220
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
221
|
+
* const bag = "catdogsun";
|
|
222
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
223
|
+
* console.log(nameOf(game,0));
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
87
226
|
export function nameOf(game, at) {
|
|
88
227
|
const name = game.players[at]?.name ?? "";
|
|
89
228
|
return name === "" ? `Player ${at + 1}` : name;
|
|
90
229
|
}
|
|
91
|
-
/** The player whose turn it is, as the solo game's moves take a game: the shared bag, and their own hand and table.
|
|
230
|
+
/** The player whose turn it is, as the solo game's moves take a game: the shared bag, and their own hand and table.
|
|
231
|
+
*
|
|
232
|
+
* @param game - The current immutable shared-bag party game.
|
|
233
|
+
* @returns The player whose turn it is, as the solo game's moves take a game: the shared bag, and their own hand and table.
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* import { seatPlay, startParty } from "@johnmorrisdotca/kumimoji";
|
|
237
|
+
*
|
|
238
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
239
|
+
* const bag = "catdogsun";
|
|
240
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
241
|
+
* console.log(seatPlay(game));
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
92
244
|
export function seatPlay(game) {
|
|
93
245
|
const player = game.players[game.turn];
|
|
94
246
|
return { bag: game.bag, returned: game.returned, taken: game.taken, tiles: player.tiles, hand: player.hand };
|
|
@@ -100,29 +252,95 @@ export function seatPlay(game) {
|
|
|
100
252
|
* takes three, so trading without end lets one player take the bag from the
|
|
101
253
|
* others. After a trade the player may still lay, lift and draw, and press
|
|
102
254
|
* Done; the next trade is on their next turn.
|
|
255
|
+
*
|
|
256
|
+
* @param game - The current immutable shared-bag party game.
|
|
257
|
+
* @returns ONE TRADE A TURN. John, 2026-09-29, after a player traded over and over and built up a hand: "if they swap tiles … give them a chance to lay some tiles but cannot ask for another swap until next turn." A trade gives one tile and takes three, so trading without end lets one player take the bag from the others. After a trade the player may still lay, lift and draw, and press Done; the next trade is on their next turn.
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* import { mayTradeThisTurn, startParty } from "@johnmorrisdotca/kumimoji";
|
|
261
|
+
*
|
|
262
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
263
|
+
* const bag = "catdogsun";
|
|
264
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
265
|
+
* console.log(mayTradeThisTurn(game));
|
|
266
|
+
* ```
|
|
103
267
|
*/
|
|
104
268
|
export function mayTradeThisTurn(game) {
|
|
105
269
|
return game.ending === null && !game.traded && mayTrade(seatPlay(game));
|
|
106
270
|
}
|
|
107
|
-
/** A move made on `seatPlay` put back: the bag as it left it, and the hand and table to the player whose turn it is. A second trade in one turn is refused, the game given back as it was.
|
|
271
|
+
/** A move made on `seatPlay` put back: the bag as it left it, and the hand and table to the player whose turn it is. A second trade in one turn is refused, the game given back as it was.
|
|
272
|
+
*
|
|
273
|
+
* @param game - The current immutable shared-bag party game.
|
|
274
|
+
* @param play - The current immutable solo-game progress.
|
|
275
|
+
* @returns A move made on `seatPlay` put back: the bag as it left it, and the hand and table to the player whose turn it is. A second trade in one turn is refused, the game given back as it was.
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* import { seatPlay, startParty, withSeatPlay } from "@johnmorrisdotca/kumimoji";
|
|
279
|
+
*
|
|
280
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
281
|
+
* const bag = "catdogsun";
|
|
282
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
283
|
+
* console.log(withSeatPlay(game,seatPlay(game)).taken);
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
108
286
|
export function withSeatPlay(game, play) {
|
|
109
287
|
if (game.ending !== null)
|
|
110
288
|
return game;
|
|
111
289
|
if (game.traded && play.returned.length > game.returned.length)
|
|
112
290
|
return game;
|
|
113
|
-
const players = game.players.map((player, at) =>
|
|
291
|
+
const players = game.players.map((player, at) => at === game.turn ? { ...player, hand: play.hand, tiles: play.tiles } : player);
|
|
114
292
|
const traded = game.traded || play.returned.length > game.returned.length;
|
|
115
293
|
return { ...game, returned: play.returned, taken: play.taken, players, traded };
|
|
116
294
|
}
|
|
117
|
-
/** Tiles still in the shared bag.
|
|
295
|
+
/** Tiles still in the shared bag.
|
|
296
|
+
*
|
|
297
|
+
* @param game - The current immutable shared-bag party game.
|
|
298
|
+
* @returns Tiles still in the shared bag.
|
|
299
|
+
* @example
|
|
300
|
+
* ```ts
|
|
301
|
+
* import { partyTilesLeft, startParty } from "@johnmorrisdotca/kumimoji";
|
|
302
|
+
*
|
|
303
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
304
|
+
* const bag = "catdogsun";
|
|
305
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
306
|
+
* console.log(partyTilesLeft(game));
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
118
309
|
export function partyTilesLeft(game) {
|
|
119
310
|
return tilesLeft(seatPlay(game));
|
|
120
311
|
}
|
|
121
|
-
/** The players still in — not resigned — by place, in turn order. Somebody who went out is still in until the game ends.
|
|
312
|
+
/** The players still in — not resigned — by place, in turn order. Somebody who went out is still in until the game ends.
|
|
313
|
+
*
|
|
314
|
+
* @param game - The current immutable shared-bag party game.
|
|
315
|
+
* @returns The players still in — not resigned — by place, in turn order. Somebody who went out is still in until the game ends.
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* import { startParty, stillIn } from "@johnmorrisdotca/kumimoji";
|
|
319
|
+
*
|
|
320
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
321
|
+
* const bag = "catdogsun";
|
|
322
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
323
|
+
* console.log(stillIn(game));
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
122
326
|
export function stillIn(game) {
|
|
123
327
|
return game.players.flatMap((_, at) => (game.resigned.includes(at) ? [] : [at]));
|
|
124
328
|
}
|
|
125
|
-
/** The next player still in after `from`, round the table; `from` itself when nobody else is.
|
|
329
|
+
/** The next player still in after `from`, round the table; `from` itself when nobody else is.
|
|
330
|
+
*
|
|
331
|
+
* @param game - The current immutable shared-bag party game.
|
|
332
|
+
* @param from - The source square or seat index.
|
|
333
|
+
* @returns The next player still in after `from`, round the table; `from` itself when nobody else is.
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* import { nextIn, startParty } from "@johnmorrisdotca/kumimoji";
|
|
337
|
+
*
|
|
338
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
339
|
+
* const bag = "catdogsun";
|
|
340
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
341
|
+
* console.log(nextIn(game,0));
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
126
344
|
export function nextIn(game, from) {
|
|
127
345
|
const count = game.players.length;
|
|
128
346
|
for (let step = 1; step <= count; step += 1) {
|
|
@@ -136,14 +354,46 @@ export function nextIn(game, from) {
|
|
|
136
354
|
* Whether Draw may be pressed: the hand of the player whose turn it is used,
|
|
137
355
|
* their grid sound, and a tile in the bag for every player still in. Never in
|
|
138
356
|
* the last round, when the bag already holds fewer than that.
|
|
357
|
+
*
|
|
358
|
+
* @param game - The current immutable shared-bag party game.
|
|
359
|
+
* @param verdict - The native word and connectivity verdict for this grid.
|
|
360
|
+
* @returns Whether Draw may be pressed: the hand of the player whose turn it is used, their grid sound, and a tile in the bag for every player still in. Never in the last round, when the bag already holds fewer than that.
|
|
361
|
+
* @example
|
|
362
|
+
* ```ts
|
|
363
|
+
* import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
|
|
364
|
+
* import { judgeWithWords, mayDrawAll, startParty } from "@johnmorrisdotca/kumimoji";
|
|
365
|
+
*
|
|
366
|
+
* const words = await loadTileWordsFromModule("english");
|
|
367
|
+
* const verdict = judgeWithWords(new Map([["0,0","c"],["0,1","a"],["0,2","t"]]),words);
|
|
368
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
369
|
+
* const bag = "catdogsun";
|
|
370
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
371
|
+
* console.log(mayDrawAll(game,verdict));
|
|
372
|
+
* ```
|
|
139
373
|
*/
|
|
140
374
|
export function mayDrawAll(game, verdict) {
|
|
141
|
-
return game.ending === null &&
|
|
375
|
+
return (game.ending === null &&
|
|
376
|
+
game.lastTurns === null &&
|
|
377
|
+
game.players[game.turn].hand.length === 0 &&
|
|
378
|
+
verdict.sound &&
|
|
379
|
+
partyTilesLeft(game) >= stillIn(game).length);
|
|
142
380
|
}
|
|
143
381
|
/**
|
|
144
382
|
* DRAW: every player still in takes one tile, in turn order from the one who
|
|
145
383
|
* pressed it — the race game's rule that when one player uses their hand,
|
|
146
384
|
* everybody draws.
|
|
385
|
+
*
|
|
386
|
+
* @param game - The current immutable shared-bag party game.
|
|
387
|
+
* @returns DRAW: every player still in takes one tile, in turn order from the one who pressed it — the race game's rule that when one player uses their hand, everybody draws.
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* import { drawAll, startParty } from "@johnmorrisdotca/kumimoji";
|
|
391
|
+
*
|
|
392
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
393
|
+
* const bag = "catdogsun";
|
|
394
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
395
|
+
* console.log(drawAll(game));
|
|
396
|
+
* ```
|
|
147
397
|
*/
|
|
148
398
|
export function drawAll(game) {
|
|
149
399
|
const count = game.players.length;
|
|
@@ -151,7 +401,7 @@ export function drawAll(game) {
|
|
|
151
401
|
if (partyTilesLeft(game) < order.length)
|
|
152
402
|
return game;
|
|
153
403
|
const coming = (game.bag + game.returned).slice(game.taken, game.taken + order.length);
|
|
154
|
-
const players = game.players.map((player, at) =>
|
|
404
|
+
const players = game.players.map((player, at) => order.includes(at) ? { ...player, hand: [...player.hand, coming[order.indexOf(at)]] } : player);
|
|
155
405
|
return { ...game, taken: game.taken + order.length, players };
|
|
156
406
|
}
|
|
157
407
|
/**
|
|
@@ -161,11 +411,38 @@ export function drawAll(game) {
|
|
|
161
411
|
* screen opens on the table of whoever has just played (the first player's,
|
|
162
412
|
* before anybody has), and a swipe steps round every player in seat order,
|
|
163
413
|
* resigned ones included.
|
|
414
|
+
*
|
|
415
|
+
* @param game - The current immutable shared-bag party game.
|
|
416
|
+
* @returns WHOSE TABLE IS SHOWN. Nothing in a pass-and-play game is secret — John, 2026-09-28: "there are no secrets because they are face up" — so every table and every hand can be looked at by anybody, read-only. The pass screen opens on the table of whoever has just played (the first player's, before anybody has), and a swipe steps round every player in seat order, resigned ones included.
|
|
417
|
+
* @example
|
|
418
|
+
* ```ts
|
|
419
|
+
* import { passViewStart, startParty } from "@johnmorrisdotca/kumimoji";
|
|
420
|
+
*
|
|
421
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
422
|
+
* const bag = "catdogsun";
|
|
423
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
424
|
+
* console.log(passViewStart(game));
|
|
425
|
+
* ```
|
|
164
426
|
*/
|
|
165
427
|
export function passViewStart(game) {
|
|
166
428
|
return game.passedBy ?? game.turn;
|
|
167
429
|
}
|
|
168
|
-
/** The player a swipe or an arrow shows next: `by` seats on, round the table.
|
|
430
|
+
/** The player a swipe or an arrow shows next: `by` seats on, round the table.
|
|
431
|
+
*
|
|
432
|
+
* @param game - The current immutable shared-bag party game.
|
|
433
|
+
* @param from - The source square or seat index.
|
|
434
|
+
* @param by - The direction or distance to step between seats.
|
|
435
|
+
* @returns The player a swipe or an arrow shows next: `by` seats on, round the table.
|
|
436
|
+
* @example
|
|
437
|
+
* ```ts
|
|
438
|
+
* import { startParty, stepView } from "@johnmorrisdotca/kumimoji";
|
|
439
|
+
*
|
|
440
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
441
|
+
* const bag = "catdogsun";
|
|
442
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
443
|
+
* console.log(stepView(game,0,1));
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
169
446
|
export function stepView(game, from, by) {
|
|
170
447
|
const count = game.players.length;
|
|
171
448
|
return (((from + by) % count) + count) % count;
|
package/dist/party.types.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { Tiles } from "./grid.ts";
|
|
2
2
|
import type { KumimojiLanguage, KumimojiLength, KumimojiLevel } from "./kumimoji.types.ts";
|
|
3
|
-
/** What a pass-and-play game was set up as: the address's own choices, which a kept game is matched to.
|
|
3
|
+
/** What a pass-and-play game was set up as: the address's own choices, which a kept game is matched to.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import type { PartySettings } from "@johnmorrisdotca/kumimoji";
|
|
8
|
+
*
|
|
9
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
10
|
+
* const value: PartySettings = settings;
|
|
11
|
+
* console.log(value);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
4
14
|
export type PartySettings = {
|
|
5
15
|
/** The hand each player is dealt: 7 Quick or 11 Classic (3 in the browser tests alone). */
|
|
6
16
|
size: number;
|
|
@@ -20,6 +30,15 @@ export type PartySettings = {
|
|
|
20
30
|
* their own table, and whether the seat is a computer's (`computerTurn.ts`),
|
|
21
31
|
* whose turns play themselves in the browser. A computer's name is always
|
|
22
32
|
* written out ("Computer 1"), never left for its number.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import type { PartyPlayer } from "@johnmorrisdotca/kumimoji";
|
|
37
|
+
*
|
|
38
|
+
* const tiles = new Map([["0,0","c"],["0,1","a"],["0,2","t"]]);
|
|
39
|
+
* const value: PartyPlayer = {name:"Seat 1",hand:["c","a","t"],tiles:new Map()};
|
|
40
|
+
* console.log(value);
|
|
41
|
+
* ```
|
|
23
42
|
*/
|
|
24
43
|
export type PartyPlayer = {
|
|
25
44
|
name: string;
|
|
@@ -27,7 +46,16 @@ export type PartyPlayer = {
|
|
|
27
46
|
tiles: Tiles;
|
|
28
47
|
computer?: boolean;
|
|
29
48
|
};
|
|
30
|
-
/** A seat asked for before the deal or at a join: a name (empty for its number) and whether a computer plays it.
|
|
49
|
+
/** A seat asked for before the deal or at a join: a name (empty for its number) and whether a computer plays it.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import type { PartySeat } from "@johnmorrisdotca/kumimoji";
|
|
54
|
+
*
|
|
55
|
+
* const value: PartySeat = {name:"Computer",computer:true};
|
|
56
|
+
* console.log(value);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
31
59
|
export type PartySeat = {
|
|
32
60
|
name: string;
|
|
33
61
|
computer?: boolean;
|
|
@@ -35,6 +63,18 @@ export type PartySeat = {
|
|
|
35
63
|
/**
|
|
36
64
|
* A PASS-AND-PLAY KUMIMOJI: one bag, read as a line the way the solo game
|
|
37
65
|
* reads it (`TilePlay`), and a hand and a table for each player.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { startParty } from "@johnmorrisdotca/kumimoji";
|
|
70
|
+
* import type { PartyGame } 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
|
+
* const bag = "catdogsun";
|
|
74
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
75
|
+
* const value: PartyGame = game;
|
|
76
|
+
* console.log(value);
|
|
77
|
+
* ```
|
|
38
78
|
*/
|
|
39
79
|
export type PartyGame = {
|
|
40
80
|
settings: PartySettings;
|
|
@@ -82,5 +122,13 @@ export type PartyGame = {
|
|
|
82
122
|
* had a last turn; the winners are everybody who went out. `standing`: every
|
|
83
123
|
* other player resigned and the last one in laid a tile. `tied`: nobody was
|
|
84
124
|
* left standing, and the players who resigned in that final run are tied.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* import type { PartyEnding } from "@johnmorrisdotca/kumimoji";
|
|
129
|
+
*
|
|
130
|
+
* const value: PartyEnding = "out";
|
|
131
|
+
* console.log(value);
|
|
132
|
+
* ```
|
|
85
133
|
*/
|
|
86
134
|
export type PartyEnding = "out" | "standing" | "tied";
|
package/dist/partyKept.d.ts
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import type { PartyGame, PartySettings } from "./party.types.ts";
|
|
2
|
-
/** 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.
|
|
2
|
+
/** 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.
|
|
3
|
+
*
|
|
4
|
+
* @param game - The current immutable shared-bag party game.
|
|
5
|
+
* @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.
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { encodeParty, startParty } from "@johnmorrisdotca/kumimoji";
|
|
9
|
+
*
|
|
10
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
11
|
+
* const bag = "catdogsun";
|
|
12
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
13
|
+
* console.log(encodeParty(game));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
3
16
|
export declare function encodeParty(game: PartyGame): string;
|
|
4
|
-
/** A game's settings as something outside this browser sent them — a table on several devices, a kept game — checked, or null.
|
|
17
|
+
/** A game's settings as something outside this browser sent them — a table on several devices, a kept game — checked, or null.
|
|
18
|
+
*
|
|
19
|
+
* @param value - The unknown setup value to interpret.
|
|
20
|
+
* @returns A game's settings as something outside this browser sent them — a table on several devices, a kept game — checked, or null.
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { readSettings } from "@johnmorrisdotca/kumimoji";
|
|
24
|
+
*
|
|
25
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
26
|
+
* console.log(readSettings(settings));
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
5
29
|
export declare function readSettings(value: unknown): PartySettings | null;
|
|
6
30
|
/**
|
|
7
31
|
* A kept game read back, or null for anything that is not one. With
|
|
@@ -10,9 +34,55 @@ export declare function readSettings(value: unknown): PartySettings | null;
|
|
|
10
34
|
* it and not given back, as the solo game checks a kept run
|
|
11
35
|
* (`decodeTileProgress`). Without, its shape alone, which is enough to offer
|
|
12
36
|
* it on the set-up screen.
|
|
37
|
+
*
|
|
38
|
+
* @param code - The encoded grid or progress text.
|
|
39
|
+
* @param familyKey - The function identifying physical tile families, or null for invalid codes.
|
|
40
|
+
* @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.
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
|
|
44
|
+
* import { decodeParty, encodeParty, startParty } from "@johnmorrisdotca/kumimoji";
|
|
45
|
+
*
|
|
46
|
+
* const words = await loadTileWordsFromModule("english");
|
|
47
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
48
|
+
* const bag = "catdogsun";
|
|
49
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
50
|
+
* console.log(decodeParty(encodeParty(game),words.familyKey)?.players.length);
|
|
51
|
+
* ```
|
|
13
52
|
*/
|
|
14
53
|
export declare function decodeParty(code: string | null, familyKey?: (tile: string) => string | null): PartyGame | null;
|
|
15
|
-
/** Whether what everybody holds is exactly what was taken from the bag, less what was given back.
|
|
54
|
+
/** Whether what everybody holds is exactly what was taken from the bag, less what was given back.
|
|
55
|
+
*
|
|
56
|
+
* @param game - The current immutable shared-bag party game.
|
|
57
|
+
* @param familyKey - The function identifying physical tile families, or null for invalid codes.
|
|
58
|
+
* @returns Whether what everybody holds is exactly what was taken from the bag, less what was given back.
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
|
|
62
|
+
* import { holdsItsBag, startParty } from "@johnmorrisdotca/kumimoji";
|
|
63
|
+
*
|
|
64
|
+
* const words = await loadTileWordsFromModule("english");
|
|
65
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
66
|
+
* const bag = "catdogsun";
|
|
67
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
68
|
+
* console.log(holdsItsBag(game,words.familyKey));
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
16
71
|
export declare function holdsItsBag(game: PartyGame, familyKey: (tile: string) => string | null): boolean;
|
|
17
|
-
/** 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.
|
|
72
|
+
/** 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.
|
|
73
|
+
*
|
|
74
|
+
* @param game - The current immutable shared-bag party game.
|
|
75
|
+
* @param settings - The native party setup settings.
|
|
76
|
+
* @param players - The requested player count or current player rows.
|
|
77
|
+
* @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.
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* import { isPartyFor, startParty } from "@johnmorrisdotca/kumimoji";
|
|
81
|
+
*
|
|
82
|
+
* const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
|
|
83
|
+
* const bag = "catdogsun";
|
|
84
|
+
* const game = startParty(settings,bag,["Seat 1","Seat 2"]);
|
|
85
|
+
* console.log(isPartyFor(game,settings,2));
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
18
88
|
export declare function isPartyFor(game: PartyGame, settings: PartySettings, players: number): boolean;
|