@johnmorrisdotca/kumimoji 1.3.0 → 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/dist/generate.js CHANGED
@@ -1,6 +1,6 @@
1
- import { seededRandom, shuffled } from "./random.js";
2
1
  import { encodeGrid, squareAt } from "./grid.js";
3
2
  import { crossingFit } from "./placement.js";
3
+ import { seededRandom, shuffled } from "./random.js";
4
4
  import { kumimojiTileCount, kumimojiWildCount, TILE_MIX_TOTAL } from "./tiles.constants.js";
5
5
  import { tileWords } from "./tileWords.js";
6
6
  /**
@@ -58,10 +58,12 @@ export function generateKumimoji(size, level, seed, options = {}) {
58
58
  const side = layingSideFor(tiles);
59
59
  let bestProgress = 0;
60
60
  for (let attempt = 0; attempt < 50; attempt += 1) {
61
- const squares = layCrossword(tiles, random, words, side, multiplier, words.mix, (laid) => { bestProgress = Math.max(bestProgress, laid); }, diagonals);
61
+ const squares = layCrossword(tiles, random, words, side, multiplier, words.mix, (laid) => {
62
+ bestProgress = Math.max(bestProgress, laid);
63
+ }, diagonals);
62
64
  if (squares === null)
63
65
  continue;
64
- const positions = shuffled(squares.flatMap((tile, at) => tile === "" ? [] : [at]), random);
66
+ const positions = shuffled(squares.flatMap((tile, at) => (tile === "" ? [] : [at])), random);
65
67
  const wildAt = new Set(positions.slice(0, wilds));
66
68
  const bag = squares.flatMap((tile, at) => {
67
69
  if (tile === "")
@@ -74,7 +76,18 @@ export function generateKumimoji(size, level, seed, options = {}) {
74
76
  squares[at] = assigned;
75
77
  return ["*"];
76
78
  });
77
- return { kind: "kumimoji", size, level, seed, givens: shuffled(bag, random).join(""), solution: encodeGrid(tilesOf(squares, side)), gameLength, doubleSet, language, ...(diagonals ? { diagonals } : {}) };
79
+ return {
80
+ kind: "kumimoji",
81
+ size,
82
+ level,
83
+ seed,
84
+ givens: shuffled(bag, random).join(""),
85
+ solution: encodeGrid(tilesOf(squares, side)),
86
+ gameLength,
87
+ doubleSet,
88
+ language,
89
+ ...(diagonals ? { diagonals } : {}),
90
+ };
78
91
  }
79
92
  throw new Error(`Could not lay a Kumimoji of ${tiles} tiles from seed ${seed}; furthest attempt laid ${bestProgress}.`);
80
93
  }
@@ -102,7 +115,7 @@ const LONGEST_LAID = 7;
102
115
  const ANCHORS_TRIED = 8;
103
116
  const WORDS_READ = 60;
104
117
  const FINISH_BRANCHES = 40;
105
- const FINISH_NODES = 2000;
118
+ const FINISH_NODES = 2_000;
106
119
  /**
107
120
  * With Diagonals the last tiles are placed from every tile but a sample of
108
121
  * words, and a try that cannot finish is given up sooner: a crossword read
package/dist/grid.js CHANGED
@@ -48,7 +48,12 @@ export function boundsOf(tiles) {
48
48
  bounds =
49
49
  bounds === null
50
50
  ? { top: row, left: col, bottom: row, right: col }
51
- : { top: Math.min(bounds.top, row), left: Math.min(bounds.left, col), bottom: Math.max(bounds.bottom, row), right: Math.max(bounds.right, col) };
51
+ : {
52
+ top: Math.min(bounds.top, row),
53
+ left: Math.min(bounds.left, col),
54
+ bottom: Math.max(bounds.bottom, row),
55
+ right: Math.max(bounds.right, col),
56
+ };
52
57
  }
53
58
  return bounds;
54
59
  }
@@ -188,7 +193,12 @@ export function sameLetters(a, b) {
188
193
  return true;
189
194
  }
190
195
  /** One step along each line, as rows and columns. */
191
- const STEP = { across: [0, 1], down: [1, 0], downRight: [1, 1], downLeft: [1, -1] };
196
+ const STEP = {
197
+ across: [0, 1],
198
+ down: [1, 0],
199
+ downRight: [1, 1],
200
+ downLeft: [1, -1],
201
+ };
192
202
  /**
193
203
  * The fewest tiles a diagonal run needs before it is read. Two tiles touching
194
204
  * at a corner are what every crossword is full of — the letter above a word
@@ -283,7 +293,13 @@ export function groupsOf(tiles, links = []) {
283
293
  const at = queue.pop();
284
294
  group.push(at);
285
295
  const { row, col } = placeOf(at);
286
- for (const near of [squareAt(row - 1, col), squareAt(row + 1, col), squareAt(row, col - 1), squareAt(row, col + 1), ...(joined.get(at) ?? [])]) {
296
+ for (const near of [
297
+ squareAt(row - 1, col),
298
+ squareAt(row + 1, col),
299
+ squareAt(row, col - 1),
300
+ squareAt(row, col + 1),
301
+ ...(joined.get(at) ?? []),
302
+ ]) {
287
303
  if (tiles.has(near) && !seen.has(near)) {
288
304
  seen.add(near);
289
305
  queue.push(near);
@@ -322,5 +338,11 @@ export function judgeGrid(tiles, isWord, readable = (word) => word, rules = {})
322
338
  }
323
339
  const groups = groupsOf(tiles, runs.filter((run) => run.line === "downRight" || run.line === "downLeft"));
324
340
  const apart = new Set(groups.slice(1).flat());
325
- return { sound: tiles.size >= 2 && groups.length === 1 && notWords.length === 0, tiles: tiles.size, misspelt, apart, notWords };
341
+ return {
342
+ sound: tiles.size >= 2 && groups.length === 1 && notWords.length === 0,
343
+ tiles: tiles.size,
344
+ misspelt,
345
+ apart,
346
+ notWords,
347
+ };
326
348
  }
package/dist/index.d.ts CHANGED
@@ -16,33 +16,33 @@
16
16
  * a test through `@johnmorrisdotca/kumimoji/words`, which reads them from
17
17
  * disk.
18
18
  */
19
- export * from "./kumimoji.types.ts";
20
- export * from "./tiles.constants.ts";
21
- export * from "./kana.ts";
22
- export * from "./tileFace.ts";
23
- export * from "./tileFamily.ts";
24
- export * from "./tileWords.ts";
25
- export * from "./grid.ts";
26
- export * from "./placement.ts";
27
- export * from "./wilds.ts";
28
- export * from "./judge.ts";
29
19
  export * from "./check.ts";
30
- export * from "./generate.ts";
31
- export * from "./play.ts";
32
- export * from "./help.ts";
33
- export * from "./turn.ts";
34
- export * from "./tableView.ts";
35
- export * from "./showcase.ts";
36
- export * from "./party.types.ts";
37
- export * from "./party.ts";
38
- export * from "./partyTurns.ts";
39
- export * from "./partySeats.ts";
40
- export * from "./partyKept.ts";
41
20
  export * from "./computer.types.ts";
42
21
  export * from "./computerPlay.ts";
43
22
  export * from "./computerTurn.ts";
44
- export * from "./random.ts";
45
23
  export * from "./daily.ts";
46
24
  export * from "./export.ts";
25
+ export * from "./generate.ts";
26
+ export * from "./grid.ts";
27
+ export * from "./help.ts";
28
+ export * from "./judge.ts";
29
+ export * from "./kana.ts";
30
+ export * from "./kumimoji.types.ts";
31
+ export * from "./party.ts";
32
+ export * from "./party.types.ts";
33
+ export * from "./partyKept.ts";
34
+ export * from "./partySeats.ts";
35
+ export * from "./partyTurns.ts";
36
+ export * from "./placement.ts";
37
+ export * from "./play.ts";
38
+ export * from "./random.ts";
39
+ export * from "./showcase.ts";
47
40
  export * from "./strings.ts";
41
+ export * from "./tableView.ts";
42
+ export * from "./tileFace.ts";
43
+ export * from "./tileFamily.ts";
44
+ export * from "./tiles.constants.ts";
45
+ export * from "./tileWords.ts";
46
+ export * from "./turn.ts";
48
47
  export { KUMIMOJI_VERSION } from "./version.ts";
48
+ export * from "./wilds.ts";
package/dist/index.js CHANGED
@@ -16,33 +16,33 @@
16
16
  * a test through `@johnmorrisdotca/kumimoji/words`, which reads them from
17
17
  * disk.
18
18
  */
19
- export * from "./kumimoji.types.js";
20
- export * from "./tiles.constants.js";
21
- export * from "./kana.js";
22
- export * from "./tileFace.js";
23
- export * from "./tileFamily.js";
24
- export * from "./tileWords.js";
25
- export * from "./grid.js";
26
- export * from "./placement.js";
27
- export * from "./wilds.js";
28
- export * from "./judge.js";
29
19
  export * from "./check.js";
30
- export * from "./generate.js";
31
- export * from "./play.js";
32
- export * from "./help.js";
33
- export * from "./turn.js";
34
- export * from "./tableView.js";
35
- export * from "./showcase.js";
36
- export * from "./party.types.js";
37
- export * from "./party.js";
38
- export * from "./partyTurns.js";
39
- export * from "./partySeats.js";
40
- export * from "./partyKept.js";
41
20
  export * from "./computer.types.js";
42
21
  export * from "./computerPlay.js";
43
22
  export * from "./computerTurn.js";
44
- export * from "./random.js";
45
23
  export * from "./daily.js";
46
24
  export * from "./export.js";
25
+ export * from "./generate.js";
26
+ export * from "./grid.js";
27
+ export * from "./help.js";
28
+ export * from "./judge.js";
29
+ export * from "./kana.js";
30
+ export * from "./kumimoji.types.js";
31
+ export * from "./party.js";
32
+ export * from "./party.types.js";
33
+ export * from "./partyKept.js";
34
+ export * from "./partySeats.js";
35
+ export * from "./partyTurns.js";
36
+ export * from "./placement.js";
37
+ export * from "./play.js";
38
+ export * from "./random.js";
39
+ export * from "./showcase.js";
47
40
  export * from "./strings.js";
41
+ export * from "./tableView.js";
42
+ export * from "./tileFace.js";
43
+ export * from "./tileFamily.js";
44
+ export * from "./tiles.constants.js";
45
+ export * from "./tileWords.js";
46
+ export * from "./turn.js";
48
47
  export { KUMIMOJI_VERSION } from "./version.js";
48
+ export * from "./wilds.js";
package/dist/kana.js CHANGED
@@ -21,7 +21,21 @@
21
21
  */
22
22
  export const BASE_KANA = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわん";
23
23
  const SMALL_AND_OLD = {
24
- ぁ: "あ", ぃ: "い", ぅ: "う", ぇ: "え", ぉ: "お", っ: "つ", ゃ: "や", ゅ: "ゆ", ょ: "よ", ゎ: "わ", ゕ: "か", ゖ: "け", を: "お", ゐ: "い", ゑ: "え",
24
+ ぁ: "あ",
25
+ ぃ: "い",
26
+ ぅ: "う",
27
+ ぇ: "え",
28
+ ぉ: "お",
29
+ っ: "つ",
30
+ ゃ: "や",
31
+ ゅ: "ゆ",
32
+ ょ: "よ",
33
+ ゎ: "わ",
34
+ ゕ: "か",
35
+ ゖ: "け",
36
+ を: "お",
37
+ ゐ: "い",
38
+ ゑ: "え",
25
39
  };
26
40
  /** The tile a kana is played with, or null for anything that is not hiragana (ー, katakana, a letter).
27
41
  *
@@ -52,9 +66,28 @@ export function tileKana(kana) {
52
66
  * ```
53
67
  */
54
68
  export const CORNER_FORMS = {
55
- か: "が", き: "ぎ", く: "ぐ", け: "げ", こ: "ご",
56
- さ: "ざ", し: "じ", す: "ず", せ: "ぜ", そ: "ぞ",
57
- た: "だ", ち: "ぢ", つ: "っづ", て: "で", と: "ど",
58
- は: "ばぱ", ひ: "びぴ", ふ: "ぶぷ", へ: "べぺ", ほ: "ぼぽ",
59
- や: "ゃ", ゆ: "ゅ", よ: "ょ", お: "を",
69
+ か: "が",
70
+ き: "ぎ",
71
+ く: "ぐ",
72
+ け: "げ",
73
+ こ: "ご",
74
+ さ: "ざ",
75
+ し: "じ",
76
+ す: "ず",
77
+ せ: "ぜ",
78
+ そ: "ぞ",
79
+ た: "だ",
80
+ ち: "ぢ",
81
+ つ: "っづ",
82
+ て: "で",
83
+ と: "ど",
84
+ は: "ばぱ",
85
+ ひ: "びぴ",
86
+ ふ: "ぶぷ",
87
+ へ: "べぺ",
88
+ ほ: "ぼぽ",
89
+ や: "ゃ",
90
+ ゆ: "ゅ",
91
+ よ: "ょ",
92
+ お: "を",
60
93
  };
package/dist/party.js CHANGED
@@ -118,9 +118,28 @@ export function startParty(settings, bag, seats) {
118
118
  const seat = seats[at] ?? "";
119
119
  const asked = typeof seat === "string" ? { name: seat } : seat;
120
120
  const dealt = { hand: [...bag.slice(at * hand, (at + 1) * hand)], tiles: new Map() };
121
- players.push(asked.computer === true ? { name: computerName(players), computer: true, ...dealt } : { name: tidyName(asked.name), ...dealt });
121
+ players.push(asked.computer === true
122
+ ? { name: computerName(players), computer: true, ...dealt }
123
+ : { name: tidyName(asked.name), ...dealt });
122
124
  }
123
- return { settings, bag, returned: "", taken: count * hand, players, dealt: count, turn: 0, turns: 0, out: [], lastTurns: null, resigned: [], resignRun: [], ending: null, startTable: "", traded: false, passedBy: null };
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
+ };
124
143
  }
125
144
  /** The name a new computer takes: "Computer" and the lowest number no player at the table already goes by.
126
145
  *
@@ -269,7 +288,7 @@ export function withSeatPlay(game, play) {
269
288
  return game;
270
289
  if (game.traded && play.returned.length > game.returned.length)
271
290
  return game;
272
- const players = game.players.map((player, at) => (at === game.turn ? { ...player, hand: play.hand, tiles: play.tiles } : player));
291
+ const players = game.players.map((player, at) => at === game.turn ? { ...player, hand: play.hand, tiles: play.tiles } : player);
273
292
  const traded = game.traded || play.returned.length > game.returned.length;
274
293
  return { ...game, returned: play.returned, taken: play.taken, players, traded };
275
294
  }
@@ -353,7 +372,11 @@ export function nextIn(game, from) {
353
372
  * ```
354
373
  */
355
374
  export function mayDrawAll(game, verdict) {
356
- return game.ending === null && game.lastTurns === null && game.players[game.turn].hand.length === 0 && verdict.sound && partyTilesLeft(game) >= stillIn(game).length;
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);
357
380
  }
358
381
  /**
359
382
  * DRAW: every player still in takes one tile, in turn order from the one who
@@ -378,7 +401,7 @@ export function drawAll(game) {
378
401
  if (partyTilesLeft(game) < order.length)
379
402
  return game;
380
403
  const coming = (game.bag + game.returned).slice(game.taken, game.taken + order.length);
381
- const players = game.players.map((player, at) => (order.includes(at) ? { ...player, hand: [...player.hand, coming[order.indexOf(at)]] } : player));
404
+ const players = game.players.map((player, at) => order.includes(at) ? { ...player, hand: [...player.hand, coming[order.indexOf(at)]] } : player);
382
405
  return { ...game, taken: game.taken + order.length, players };
383
406
  }
384
407
  /**
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
@@ -50,7 +50,12 @@ export function encodeParty(game) {
50
50
  traded: game.traded,
51
51
  passedBy: game.passedBy,
52
52
  ...(game.dealt === game.players.length ? {} : { dealt: game.dealt }),
53
- 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
+ })),
54
59
  };
55
60
  return JSON.stringify(kept);
56
61
  }
@@ -88,7 +93,16 @@ export function readSettings(value) {
88
93
  // A game kept before Diagonals existed has no word for it, and was played without.
89
94
  if (s.diagonals !== undefined && typeof s.diagonals !== "boolean")
90
95
  return null;
91
- 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
+ };
92
106
  }
93
107
  /**
94
108
  * A kept game read back, or null for anything that is not one. With
@@ -138,23 +152,54 @@ export function decodeParty(code, familyKey) {
138
152
  const dealt = kept.dealt ?? count;
139
153
  if (!Number.isInteger(dealt) || dealt < KUMIMOJI_PARTY.least || dealt > KUMIMOJI_PARTY.most)
140
154
  return null;
141
- 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)))
142
161
  return null;
143
162
  const seats = (list) => Array.isArray(list) && list.every((at) => isCount(at, count - 1)) && new Set(list).size === list.length;
144
- 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))
145
167
  return null;
146
168
  const players = [];
147
169
  for (const player of kept.players) {
148
- 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))
149
175
  return null;
150
176
  if (player.computer !== undefined && player.computer !== true)
151
177
  return null;
152
178
  const tiles = decodeGrid(player.grid);
153
179
  if (tiles === null)
154
180
  return null;
155
- 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 });
156
184
  }
157
- 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
+ };
158
203
  if (familyKey !== undefined && !holdsItsBag(game, familyKey))
159
204
  return null;
160
205
  return game;
@@ -1,5 +1,5 @@
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
5
  /** Why nobody can join the table now, or null when a hand can be dealt.
@@ -47,7 +47,9 @@ export function joinParty(game, seat) {
47
47
  return game;
48
48
  const hand = [...(game.bag + game.returned).slice(game.taken, game.taken + game.settings.size)];
49
49
  const tiles = new Map();
50
- 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 };
51
53
  return { ...game, players: [...game.players, player], taken: game.taken + hand.length };
52
54
  }
53
55
  /** "Player" and the next seat's number, or the lowest number nobody at the table goes by. */
@@ -137,7 +139,7 @@ export function leaveParty(game, at) {
137
139
  return game;
138
140
  const leaver = game.players[at];
139
141
  const back = [...leaver.hand, ...leaver.tiles.values()].map((tile) => (tileFace(tile).wild ? "*" : tile));
140
- 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}` }]);
141
143
  const seatOf = (seat) => (seat > at ? seat - 1 : seat);
142
144
  const without = (seats) => seats.filter((seat) => seat !== at).map(seatOf);
143
145
  const held = players.flatMap((player) => [...player.hand, ...player.tiles.values()]);
@@ -179,7 +181,12 @@ export function leaveParty(game, at) {
179
181
  const moved = at === game.turn ? { startTable: tableTally(players[turn].tiles), traded: false } : {};
180
182
  if (ending === null && lastTurns === null && stillIn(after).length === 0) {
181
183
  // Only players who resigned are left: tied, the last run of them, or all of them where that run has left.
182
- 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
+ };
183
190
  }
184
191
  return { ...after, ...moved };
185
192
  }
@@ -47,7 +47,10 @@ export function isOver(game) {
47
47
  * ```
48
48
  */
49
49
  export function goesOut(game, verdict) {
50
- return !isOver(game) && game.players[game.turn].hand.length === 0 && verdict.sound && partyTilesLeft(game) < stillIn(game).length;
50
+ return (!isOver(game) &&
51
+ game.players[game.turn].hand.length === 0 &&
52
+ verdict.sound &&
53
+ partyTilesLeft(game) < stillIn(game).length);
51
54
  }
52
55
  /** Whether the player whose turn it is has laid a tile on their table this turn: more tiles there than when it began.
53
56
  *
@@ -163,7 +166,15 @@ export function doneRefused(game, verdict, handSpells) {
163
166
  }
164
167
  /** The game passed to `turn`, a new turn with nothing traded and its table as it stands. */
165
168
  function passTo(game, turn, rest) {
166
- return { ...game, ...rest, turns: game.turns + 1, turn, traded: false, startTable: tableTally(game.players[turn].tiles), passedBy: game.turn };
169
+ return {
170
+ ...game,
171
+ ...rest,
172
+ turns: game.turns + 1,
173
+ turn,
174
+ traded: false,
175
+ startTable: tableTally(game.players[turn].tiles),
176
+ passedBy: game.turn,
177
+ };
167
178
  }
168
179
  /**
169
180
  * DONE: the end of a turn, and the device passed on. Before anybody is out,
@@ -202,7 +213,9 @@ export function endTurn(game, verdict, handSpells) {
202
213
  if (game.lastTurns === null) {
203
214
  if (out.length === 0)
204
215
  return passTo(game, nextIn(game, game.turn), { resignRun: [] });
205
- const lastTurns = stillIn(game).filter((at) => at !== game.turn).sort((a, b) => fromTurn(game, a) - fromTurn(game, b));
216
+ const lastTurns = stillIn(game)
217
+ .filter((at) => at !== game.turn)
218
+ .sort((a, b) => fromTurn(game, a) - fromTurn(game, b));
206
219
  if (lastTurns.length === 0)
207
220
  return passTo(game, game.turn, { out, lastTurns, ending: "out" });
208
221
  return passTo(game, lastTurns[0], { out, lastTurns, resignRun: [] });
package/dist/placement.js CHANGED
@@ -99,8 +99,11 @@ export function crossingFit(letterAt, inside, word, anchor, at, across, diagonal
99
99
  * ```
100
100
  */
101
101
  export function diagonalsRead(letterAt, inside, word, fit, isWord) {
102
- const freshAt = new Map(fit.fresh.map((square) => [`${square.row},${square.col}`, word[(square.row - fit.first.row) + (square.col - fit.first.col)]]));
103
- const at = (row, col) => (inside(row, col) ? letterAt(row, col) || (freshAt.get(`${row},${col}`) ?? "") : "");
102
+ const freshAt = new Map(fit.fresh.map((square) => [
103
+ `${square.row},${square.col}`,
104
+ word[square.row - fit.first.row + (square.col - fit.first.col)],
105
+ ]));
106
+ const at = (row, col) => inside(row, col) ? letterAt(row, col) || (freshAt.get(`${row},${col}`) ?? "") : "";
104
107
  for (const { row, col } of fit.fresh) {
105
108
  for (const lean of [1, -1]) {
106
109
  let top = 0;
package/dist/play.js CHANGED
@@ -57,7 +57,11 @@ export function placeFromHand(play, handAt, square) {
57
57
  const letter = play.hand[handAt];
58
58
  if (letter === undefined || play.tiles.has(square))
59
59
  return play;
60
- return { ...play, tiles: withTiles(play, (tiles) => tiles.set(square, letter)), hand: play.hand.filter((_, at) => at !== handAt) };
60
+ return {
61
+ ...play,
62
+ tiles: withTiles(play, (tiles) => tiles.set(square, letter)),
63
+ hand: play.hand.filter((_, at) => at !== handAt),
64
+ };
61
65
  }
62
66
  /** Give a hand tile its chosen reading, without changing which physical tile it is.
63
67
  *
@@ -276,7 +280,12 @@ export function trade(play, handAt) {
276
280
  return play;
277
281
  const returned = play.returned + letter;
278
282
  const coming = (play.bag + returned).slice(play.taken, play.taken + KUMIMOJI_TRADE.take);
279
- return { ...play, returned, taken: play.taken + coming.length, hand: [...play.hand.filter((_, at) => at !== handAt), ...coming] };
283
+ return {
284
+ ...play,
285
+ returned,
286
+ taken: play.taken + coming.length,
287
+ hand: [...play.hand.filter((_, at) => at !== handAt), ...coming],
288
+ };
280
289
  }
281
290
  /** Whether the game is over: the bag empty, the hand used, and the grid sound.
282
291
  *
@@ -334,7 +343,9 @@ export function readTileProgress(code) {
334
343
  if (parts.length !== 4)
335
344
  return null;
336
345
  const [takenText, returned, hand, grid] = parts;
337
- if (!/^\d{1,3}$/.test(takenText) || !/^[a-zA-Z*\uE000-\uF8FF]*$/.test(returned) || !/^[a-zA-Z*\uE000-\uF8FF]*$/.test(hand))
346
+ if (!/^\d{1,3}$/.test(takenText) ||
347
+ !/^[a-zA-Z*\uE000-\uF8FF]*$/.test(returned) ||
348
+ !/^[a-zA-Z*\uE000-\uF8FF]*$/.test(hand))
338
349
  return null;
339
350
  const tiles = decodeGrid(grid);
340
351
  if (tiles === null)
package/dist/react.js CHANGED
@@ -29,11 +29,25 @@ const TILE = {
29
29
  */
30
30
  export function KumimojiBoard({ tiles, verdict = null, glyphOf, onSquare, held = null, square = 36, style, ...element }) {
31
31
  const model = boardModel(tiles, verdict, glyphOf);
32
- return (_jsx("div", { role: "grid", style: { display: "grid", gap: 2, gridTemplateColumns: `repeat(${model.cols}, ${square}px)`, width: "max-content", ...style }, ...element, children: model.squares.map((cell) => {
32
+ return (_jsx("div", { role: "grid", style: {
33
+ display: "grid",
34
+ gap: 2,
35
+ gridTemplateColumns: `repeat(${model.cols}, ${square}px)`,
36
+ width: "max-content",
37
+ ...style,
38
+ }, ...element, children: model.squares.map((cell) => {
33
39
  const ring = cell.square === held ? "0 0 0 3px #ffcf3f" : cell.mark === "misspelt" ? "0 0 0 3px #d9534f inset" : undefined;
34
40
  const look = cell.tile === null
35
41
  ? { width: square, height: square, borderRadius: 5, background: "rgba(127,127,127,.18)", boxShadow: ring }
36
- : { ...TILE, width: square, height: square, fontSize: square * 0.55, background: cell.wild ? "#f6d27a" : TILE.background, opacity: cell.mark === "apart" ? 0.6 : 1, boxShadow: ring ?? TILE.boxShadow };
42
+ : {
43
+ ...TILE,
44
+ width: square,
45
+ height: square,
46
+ fontSize: square * 0.55,
47
+ background: cell.wild ? "#f6d27a" : TILE.background,
48
+ opacity: cell.mark === "apart" ? 0.6 : 1,
49
+ boxShadow: ring ?? TILE.boxShadow,
50
+ };
37
51
  return (_jsx("button", { type: "button", "data-square": cell.square, "aria-label": cell.tile === null ? "Empty square" : cell.glyph, onClick: onSquare === undefined ? undefined : () => onSquare(cell.square), style: { border: 0, padding: 0, cursor: onSquare === undefined ? "default" : "pointer", ...look }, children: cell.glyph.toUpperCase() }, cell.square));
38
52
  }) }));
39
53
  }
@@ -61,7 +75,23 @@ export function KumimojiTable(props) {
61
75
  const target = host.current;
62
76
  if (target === null)
63
77
  return;
64
- const table = mountKumimoji(target, { language, hand, level, gameLength, diagonals, seed, locale, strings, theme, keep, controls, saved, onError: error => latest.current.onError?.(error), onFinish: (result) => latest.current.onFinish?.(result), onChange: (saved) => latest.current.onChange?.(saved) });
78
+ const table = mountKumimoji(target, {
79
+ language,
80
+ hand,
81
+ level,
82
+ gameLength,
83
+ diagonals,
84
+ seed,
85
+ locale,
86
+ strings,
87
+ theme,
88
+ keep,
89
+ controls,
90
+ saved,
91
+ onError: (error) => latest.current.onError?.(error),
92
+ onFinish: (result) => latest.current.onFinish?.(result),
93
+ onChange: (saved) => latest.current.onChange?.(saved),
94
+ });
65
95
  return () => table.destroy();
66
96
  // Mounted once per key, as documented above.
67
97
  // eslint-disable-next-line react-hooks/exhaustive-deps
package/dist/showcase.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { BASE_KANA, CORNER_FORMS, tileKana } from "./kana.js";
2
2
  import { kanaTileCode } from "./tileFace.js";
3
- import { JAPANESE_TILE_MIX, KUMIMOJI_HANDS, TILE_MIX, kumimojiTileCount, kumimojiWildCount } from "./tiles.constants.js";
3
+ import { JAPANESE_TILE_MIX, KUMIMOJI_HANDS, kumimojiTileCount, kumimojiWildCount, TILE_MIX, } from "./tiles.constants.js";
4
4
  /** A language's set as a page shows it: every tile with its count, the total, and which are the rarest and the commonest.
5
5
  *
6
6
  * @param language - The English or Japanese tile language.
@@ -17,7 +17,12 @@ import { JAPANESE_TILE_MIX, KUMIMOJI_HANDS, TILE_MIX, kumimojiTileCount, kumimoj
17
17
  export function mixShown(language) {
18
18
  const tiles = language === "english"
19
19
  ? Object.entries(TILE_MIX).map(([letter, count]) => ({ code: letter, glyph: letter, forms: "", count }))
20
- : [...BASE_KANA].map((kana) => ({ code: kanaTileCode(kana), glyph: kana, forms: CORNER_FORMS[kana] ?? "", count: JAPANESE_TILE_MIX[kana] ?? 0 }));
20
+ : [...BASE_KANA].map((kana) => ({
21
+ code: kanaTileCode(kana),
22
+ glyph: kana,
23
+ forms: CORNER_FORMS[kana] ?? "",
24
+ count: JAPANESE_TILE_MIX[kana] ?? 0,
25
+ }));
21
26
  const counts = tiles.map((tile) => tile.count);
22
27
  const most = Math.max(...counts);
23
28
  const fewest = Math.min(...counts);
@@ -87,7 +92,11 @@ export function lengthRows(hand = KUMIMOJI_HANDS.classic) {
87
92
  length,
88
93
  tiles,
89
94
  doubleTiles: kumimojiTileCount(hand, length, undefined, true),
90
- wilds: { easy: kumimojiWildCount(hand, "easy", tiles), medium: kumimojiWildCount(hand, "medium", tiles), hard: kumimojiWildCount(hand, "hard", tiles) },
95
+ wilds: {
96
+ easy: kumimojiWildCount(hand, "easy", tiles),
97
+ medium: kumimojiWildCount(hand, "medium", tiles),
98
+ hard: kumimojiWildCount(hand, "hard", tiles),
99
+ },
91
100
  };
92
101
  });
93
102
  }