@johnmorrisdotca/kumimoji 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/CONTRIBUTING.md +81 -44
  3. package/README.md +478 -327
  4. package/dist/check.d.ts +26 -1
  5. package/dist/check.js +26 -1
  6. package/dist/computer.types.d.ts +24 -2
  7. package/dist/computerPlay.d.ts +45 -2
  8. package/dist/computerPlay.js +45 -2
  9. package/dist/computerTurn.d.ts +42 -3
  10. package/dist/computerTurn.js +42 -3
  11. package/dist/daily.d.ts +22 -2
  12. package/dist/daily.js +22 -2
  13. package/dist/element-define.d.ts +1 -1
  14. package/dist/element-define.js +3 -13
  15. package/dist/element.d.ts +85 -4
  16. package/dist/element.js +176 -45
  17. package/dist/embed-day.d.ts +4 -0
  18. package/dist/embed-day.js +23 -0
  19. package/dist/export.d.ts +123 -6
  20. package/dist/export.js +91 -4
  21. package/dist/generate.d.ts +14 -0
  22. package/dist/generate.js +14 -0
  23. package/dist/grid.d.ts +181 -10
  24. package/dist/grid.js +122 -7
  25. package/dist/help.d.ts +36 -3
  26. package/dist/help.js +36 -3
  27. package/dist/judge.d.ts +14 -0
  28. package/dist/judge.js +14 -0
  29. package/dist/kana.d.ts +23 -1
  30. package/dist/kana.js +23 -1
  31. package/dist/kumimoji.types.d.ts +70 -5
  32. package/dist/party.d.ts +268 -14
  33. package/dist/party.js +268 -14
  34. package/dist/party.types.d.ts +50 -2
  35. package/dist/partyKept.d.ts +74 -4
  36. package/dist/partyKept.js +74 -4
  37. package/dist/partySeats.d.ts +92 -6
  38. package/dist/partySeats.js +72 -4
  39. package/dist/partyTurns.d.ts +171 -5
  40. package/dist/partyTurns.js +171 -5
  41. package/dist/placement.d.ts +50 -2
  42. package/dist/placement.js +30 -0
  43. package/dist/play.d.ts +247 -15
  44. package/dist/play.js +237 -15
  45. package/dist/random.d.ts +34 -3
  46. package/dist/random.js +23 -2
  47. package/dist/react.d.ts +42 -4
  48. package/dist/react.js +22 -5
  49. package/dist/showcase.d.ts +83 -7
  50. package/dist/showcase.js +49 -4
  51. package/dist/strings.d.ts +54 -5
  52. package/dist/strings.js +34 -3
  53. package/dist/tableView.d.ts +151 -8
  54. package/dist/tableView.js +129 -6
  55. package/dist/tileFace.d.ts +59 -5
  56. package/dist/tileFace.js +48 -4
  57. package/dist/tileFamily.d.ts +9 -0
  58. package/dist/tileFamily.js +9 -0
  59. package/dist/tileWords.d.ts +93 -6
  60. package/dist/tileWords.js +83 -6
  61. package/dist/tileWordsModule.d.ts +11 -1
  62. package/dist/tileWordsModule.js +11 -1
  63. package/dist/tiles.constants.d.ts +99 -5
  64. package/dist/tiles.constants.js +99 -5
  65. package/dist/turn.d.ts +82 -4
  66. package/dist/turn.js +82 -4
  67. package/dist/ui/board.d.ts +60 -5
  68. package/dist/ui/board.js +28 -2
  69. package/dist/ui/mount.d.ts +41 -2
  70. package/dist/ui/mount.js +38 -1
  71. package/dist/ui/style.d.ts +6 -1
  72. package/dist/ui/style.js +6 -0
  73. package/dist/version.d.ts +9 -2
  74. package/dist/version.js +9 -2
  75. package/dist/wilds.d.ts +22 -1
  76. package/dist/wilds.js +22 -1
  77. package/package.json +66 -57
@@ -8,7 +8,20 @@ import { KUMIMOJI_TRADE } from "./tiles.constants.js";
8
8
  * solo game's (`play.ts`), and the deal and the draw are `party.ts`'s. Pure:
9
9
  * each returns a new game, or the one it was given where the move is refused.
10
10
  */
11
- /** Whether the game is over. */
11
+ /** Whether the game is over.
12
+ *
13
+ * @param game - The current immutable shared-bag party game.
14
+ * @returns Whether the game is over.
15
+ * @example
16
+ * ```ts
17
+ * import { isOver, startParty } from "@johnmorrisdotca/kumimoji";
18
+ *
19
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
20
+ * const bag = "catdogsun";
21
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
22
+ * console.log(isOver(game));
23
+ * ```
24
+ */
12
25
  export function isOver(game) {
13
26
  return game.ending !== null;
14
27
  }
@@ -16,15 +29,57 @@ export function isOver(game) {
16
29
  * Whether the player whose turn it is would go out with Done: their hand
17
30
  * used, their grid sound, and too few tiles in the bag for everybody still in
18
31
  * to draw.
32
+ *
33
+ * @param game - The current immutable shared-bag party game.
34
+ * @param verdict - The native word and connectivity verdict for this grid.
35
+ * @returns Whether the player whose turn it is would go out with Done: their hand used, their grid sound, and too few tiles in the bag for everybody still in to draw.
36
+ * @example
37
+ * ```ts
38
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
39
+ * import { goesOut, judgeWithWords, startParty } from "@johnmorrisdotca/kumimoji";
40
+ *
41
+ * const words = await loadTileWordsFromModule("english");
42
+ * const verdict = judgeWithWords(new Map([["0,0","c"],["0,1","a"],["0,2","t"]]),words);
43
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
44
+ * const bag = "catdogsun";
45
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
46
+ * console.log(goesOut(game,verdict));
47
+ * ```
19
48
  */
20
49
  export function goesOut(game, verdict) {
21
50
  return !isOver(game) && game.players[game.turn].hand.length === 0 && verdict.sound && partyTilesLeft(game) < stillIn(game).length;
22
51
  }
23
- /** Whether the player whose turn it is has laid a tile on their table this turn: more tiles there than when it began. */
52
+ /** Whether the player whose turn it is has laid a tile on their table this turn: more tiles there than when it began.
53
+ *
54
+ * @param game - The current immutable shared-bag party game.
55
+ * @returns Whether the player whose turn it is has laid a tile on their table this turn: more tiles there than when it began.
56
+ * @example
57
+ * ```ts
58
+ * import { laidThisTurn, startParty } from "@johnmorrisdotca/kumimoji";
59
+ *
60
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
61
+ * const bag = "catdogsun";
62
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
63
+ * console.log(laidThisTurn(game));
64
+ * ```
65
+ */
24
66
  export function laidThisTurn(game) {
25
67
  return game.players[game.turn].tiles.size > [...game.startTable].length;
26
68
  }
27
- /** Whether the player whose turn it is is the only one still in: everybody else has resigned. */
69
+ /** Whether the player whose turn it is is the only one still in: everybody else has resigned.
70
+ *
71
+ * @param game - The current immutable shared-bag party game.
72
+ * @returns Whether the player whose turn it is is the only one still in: everybody else has resigned.
73
+ * @example
74
+ * ```ts
75
+ * import { lastStanding, startParty } from "@johnmorrisdotca/kumimoji";
76
+ *
77
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
78
+ * const bag = "catdogsun";
79
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
80
+ * console.log(lastStanding(game));
81
+ * ```
82
+ */
28
83
  export function lastStanding(game) {
29
84
  return !isOver(game) && game.lastTurns === null && stillIn(game).length === 1;
30
85
  }
@@ -35,6 +90,21 @@ export function lastStanding(game) {
35
90
  * spell no word, has laid nothing on (or lifted nothing from) their table this
36
91
  * turn, and has not traded — unless the bag cannot give three, when there is
37
92
  * nothing to trade for. `handSpells` answers for a hand (`handCanSpell`).
93
+ *
94
+ * @param game - The current immutable shared-bag party game.
95
+ * @param handSpells - Whether a hand can form any accepted word.
96
+ * @returns A HAND THAT SPELLS NOTHING MUST BE TRADED. John, 2026-09-28: "If no words can be formed which is possible, they must dump a tile to get 3 more. Easy rule." So Done waits while the player whose turn it is holds tiles that spell no word, has laid nothing on (or lifted nothing from) their table this turn, and has not traded — unless the bag cannot give three, when there is nothing to trade for. `handSpells` answers for a hand (`handCanSpell`).
97
+ * @example
98
+ * ```ts
99
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
100
+ * import { handCanSpell, mustTradeFirst, startParty } from "@johnmorrisdotca/kumimoji";
101
+ *
102
+ * const words = await loadTileWordsFromModule("english");
103
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
104
+ * const bag = "catdogsun";
105
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
106
+ * console.log(mustTradeFirst(game,hand=>handCanSpell(hand,words)));
107
+ * ```
38
108
  */
39
109
  export function mustTradeFirst(game, handSpells) {
40
110
  if (isOver(game) || game.traded)
@@ -44,7 +114,20 @@ export function mustTradeFirst(game, handSpells) {
44
114
  return false;
45
115
  return !handSpells(play.hand);
46
116
  }
47
- /** Whether a hand can spell a word from the list: any word of two tiles or more, a wild counting as able to be anything. */
117
+ /** Whether a hand can spell a word from the list: any word of two tiles or more, a wild counting as able to be anything.
118
+ *
119
+ * @param hand - The hand size or tile codes, as declared by the function signature.
120
+ * @param words - The loaded native language word list and tile mappings.
121
+ * @returns Whether a hand can spell a word from the list: any word of two tiles or more, a wild counting as able to be anything.
122
+ * @example
123
+ * ```ts
124
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
125
+ * import { handCanSpell } from "@johnmorrisdotca/kumimoji";
126
+ *
127
+ * const words = await loadTileWordsFromModule("english");
128
+ * console.log(handCanSpell(["c","a","t"],words));
129
+ * ```
130
+ */
48
131
  export function handCanSpell(hand, words) {
49
132
  return hand.some(words.isWild) || wordsInHand(hand, words).length > 0;
50
133
  }
@@ -52,6 +135,23 @@ export function handCanSpell(hand, words) {
52
135
  * Why Done cannot be pressed now, or null when it can: a hand to trade first,
53
136
  * or, for the last one standing, no tile laid on a sound grid yet (a hand
54
137
  * already used on a sound grid has nothing left to lay, and wins as it is).
138
+ *
139
+ * @param game - The current immutable shared-bag party game.
140
+ * @param verdict - The native word and connectivity verdict for this grid.
141
+ * @param handSpells - Whether a hand can form any accepted word.
142
+ * @returns Why Done cannot be pressed now, or null when it can: a hand to trade first, or, for the last one standing, no tile laid on a sound grid yet (a hand already used on a sound grid has nothing left to lay, and wins as it is).
143
+ * @example
144
+ * ```ts
145
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
146
+ * import { doneRefused, handCanSpell, judgeWithWords, seatPlay, startParty } from "@johnmorrisdotca/kumimoji";
147
+ *
148
+ * const words = await loadTileWordsFromModule("english");
149
+ * const tiles = new Map([["0,0","c"],["0,1","a"],["0,2","t"]]);
150
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
151
+ * const bag = "catdogsun";
152
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
153
+ * console.log(doneRefused(game,judgeWithWords(seatPlay(game).tiles,words),hand=>handCanSpell(hand,words)));
154
+ * ```
55
155
  */
56
156
  export function doneRefused(game, verdict, handSpells) {
57
157
  if (mustTradeFirst(game, handSpells))
@@ -75,6 +175,23 @@ function passTo(game, turn, rest) {
75
175
  * The last one standing, everybody else resigned, wins by laying a tile on a
76
176
  * sound grid and pressing Done. `verdict` is the grid of the player whose turn
77
177
  * it is. Refused, the game given back as it was, while `doneRefused` says so.
178
+ *
179
+ * @param game - The current immutable shared-bag party game.
180
+ * @param verdict - The native word and connectivity verdict for this grid.
181
+ * @param handSpells - Whether a hand can form any accepted word.
182
+ * @returns DONE: the end of a turn, and the device passed on. Before anybody is out, to the next player still in. A player who goes out with it starts the last round: everybody else still in, in order from the next, has one last turn, and any of them who goes out on it shares the win (John, 2026-09-28: "Sharing the win is correct"). After the last of those, the game is over.
183
+ * @example
184
+ * ```ts
185
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
186
+ * import { endTurn, handCanSpell, judgeWithWords, seatPlay, startParty } from "@johnmorrisdotca/kumimoji";
187
+ *
188
+ * const words = await loadTileWordsFromModule("english");
189
+ * const tiles = new Map([["0,0","c"],["0,1","a"],["0,2","t"]]);
190
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
191
+ * const bag = "catdogsun";
192
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
193
+ * console.log(endTurn(game,judgeWithWords(seatPlay(game).tiles,words),hand=>handCanSpell(hand,words)).turn);
194
+ * ```
78
195
  */
79
196
  export function endTurn(game, verdict, handSpells) {
80
197
  if (isOver(game) || doneRefused(game, verdict, handSpells) !== null)
@@ -108,6 +225,18 @@ function nextLastTurn(game, out, rest = {}) {
108
225
  * give a trade, so nothing more can be got from it. John, 2026-09-28:
109
226
  * "Perhaps we offer a resign button only when there are no remaining tiles
110
227
  * they can get from the bag."
228
+ *
229
+ * @param game - The current immutable shared-bag party game.
230
+ * @returns Whether the player whose turn it is may resign: only once the bag cannot give a trade, so nothing more can be got from it. John, 2026-09-28: "Perhaps we offer a resign button only when there are no remaining tiles they can get from the bag."
231
+ * @example
232
+ * ```ts
233
+ * import { mayResign, startParty } from "@johnmorrisdotca/kumimoji";
234
+ *
235
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
236
+ * const bag = "catdogsun";
237
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
238
+ * console.log(mayResign(game));
239
+ * ```
111
240
  */
112
241
  export function mayResign(game) {
113
242
  return !isOver(game) && partyTilesLeft(game) < KUMIMOJI_TRADE.take;
@@ -120,6 +249,18 @@ export function mayResign(game) {
120
249
  * resigned too — it ends tied between everybody who resigned since a player
121
250
  * still in last pressed Done. John: "if you resign, as the turns pass, if
122
251
  * someone else can't go and has to resign then they are tied."
252
+ *
253
+ * @param game - The current immutable shared-bag party game.
254
+ * @returns RESIGN: the player whose turn it is is out of the game, their table kept for the finish, and their turn passed over from then on. In the last round it is their last turn ended without going out. Otherwise the game goes on with whoever is still in; and when nobody is — the last one standing resigned too — it ends tied between everybody who resigned since a player still in last pressed Done. John: "if you resign, as the turns pass, if someone else can't go and has to resign then they are tied."
255
+ * @example
256
+ * ```ts
257
+ * import { resign, startParty } from "@johnmorrisdotca/kumimoji";
258
+ *
259
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
260
+ * const bag = "catdogsun";
261
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
262
+ * console.log(resign(game));
263
+ * ```
123
264
  */
124
265
  export function resign(game) {
125
266
  if (!mayResign(game))
@@ -137,6 +278,18 @@ export function resign(game) {
137
278
  * The winners of a finished game, by place: everybody who went out; the last
138
279
  * one standing; or, tied, the final run of resignations. Empty while it is
139
280
  * played.
281
+ *
282
+ * @param game - The current immutable shared-bag party game.
283
+ * @returns The winners of a finished game, by place: everybody who went out; the last one standing; or, tied, the final run of resignations. Empty while it is played.
284
+ * @example
285
+ * ```ts
286
+ * import { startParty, winnersOf } from "@johnmorrisdotca/kumimoji";
287
+ *
288
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
289
+ * const bag = "catdogsun";
290
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
291
+ * console.log(winnersOf(game));
292
+ * ```
140
293
  */
141
294
  export function winnersOf(game) {
142
295
  if (game.ending === "out")
@@ -147,7 +300,20 @@ export function winnersOf(game) {
147
300
  return game.resignRun;
148
301
  return [];
149
302
  }
150
- /** Whether the turn now being played is a player's last. */
303
+ /** Whether the turn now being played is a player's last.
304
+ *
305
+ * @param game - The current immutable shared-bag party game.
306
+ * @returns Whether the turn now being played is a player's last.
307
+ * @example
308
+ * ```ts
309
+ * import { isLastTurn, startParty } from "@johnmorrisdotca/kumimoji";
310
+ *
311
+ * const settings = {size:3,level:"hard",seed:44,gameLength:"short",language:"english",doubleSet:false,diagonals:false,hints:false} as const;
312
+ * const bag = "catdogsun";
313
+ * const game = startParty(settings,bag,["Seat 1","Seat 2"]);
314
+ * console.log(isLastTurn(game));
315
+ * ```
316
+ */
151
317
  export function isLastTurn(game) {
152
318
  return !isOver(game) && game.lastTurns !== null;
153
319
  }
@@ -1,9 +1,27 @@
1
- /** A square by its row and column. */
1
+ /** A square by its row and column.
2
+ *
3
+ * @example
4
+ * ```ts
5
+ * import type { Square } from "@johnmorrisdotca/kumimoji";
6
+ *
7
+ * const value: Square = {row:0,col:0};
8
+ * console.log(value);
9
+ * ```
10
+ */
2
11
  export type Square = {
3
12
  row: number;
4
13
  col: number;
5
14
  };
6
- /** A word's place: its first square, and the squares it lays a new tile on, in the word's order. */
15
+ /** A word's place: its first square, and the squares it lays a new tile on, in the word's order.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import type { CrossingFit } from "@johnmorrisdotca/kumimoji";
20
+ *
21
+ * const value: CrossingFit = {first:{row:0,col:0},fresh:[{row:0,col:1},{row:0,col:2}]};
22
+ * console.log(value);
23
+ * ```
24
+ */
7
25
  export type CrossingFit = {
8
26
  first: Square;
9
27
  fresh: Square[];
@@ -15,6 +33,21 @@ export type CrossingFit = {
15
33
  * neighbour at its side, or no new tile at all — and, given `diagonalWord`
16
34
  * (a game with Diagonals), a new tile standing in a diagonal run of three or
17
35
  * more that `diagonalWord` does not take (`diagonalsRead`).
36
+ *
37
+ * @param letterAt - The tile at a coordinate, or an empty string for no tile.
38
+ * @param inside - Whether a coordinate is within the caller’s working grid.
39
+ * @param word - The encoded word to lay or spell.
40
+ * @param anchor - The existing tile that the word crosses.
41
+ * @param at - The word offset, seat index or date specified by this signature.
42
+ * @param across - Whether to lay or type horizontally rather than vertically.
43
+ * @param diagonalWord - Optional diagonal-word predicate; null disables diagonal checking.
44
+ * @returns Where `word` stands with its letter `at` on the tile at `anchor`, running across or down, or null where it cannot: a square outside what may be used, a tile at either end, a different letter in its way, a new tile with a neighbour at its side, or no new tile at all — and, given `diagonalWord` (a game with Diagonals), a new tile standing in a diagonal run of three or more that `diagonalWord` does not take (`diagonalsRead`).
45
+ * @example
46
+ * ```ts
47
+ * import { crossingFit } from "@johnmorrisdotca/kumimoji";
48
+ *
49
+ * console.log(crossingFit((r,c)=>r===0&&c===0?"c":"",()=>true,"cat",{row:0,col:0},0,true));
50
+ * ```
18
51
  */
19
52
  export declare function crossingFit(letterAt: (row: number, col: number) => string, inside: (row: number, col: number) => boolean, word: string, anchor: Square, at: number, across: boolean, diagonalWord?: ((run: string) => boolean) | null): CrossingFit | null;
20
53
  /**
@@ -23,5 +56,20 @@ export declare function crossingFit(letterAt: (row: number, col: number) => stri
23
56
  * its new squares. Only a run through a new tile can change, and two new
24
57
  * tiles of one word never share a diagonal (they share a row or a column), so
25
58
  * each is read on its own. A run of two, a corner touch, is never read.
59
+ *
60
+ * @param letterAt - The tile at a coordinate, or an empty string for no tile.
61
+ * @param inside - Whether a coordinate is within the caller’s working grid.
62
+ * @param word - The encoded word to lay or spell.
63
+ * @param fit - The first coordinate and fresh tile coordinates of the proposed laying.
64
+ * @param isWord - The predicate that accepts a readable word.
65
+ * @returns Whether every diagonal run a laying's new tiles would stand in is taken by `isWord`: each walked from its top end down, with the word's own letters on its new squares. Only a run through a new tile can change, and two new tiles of one word never share a diagonal (they share a row or a column), so each is read on its own. A run of two, a corner touch, is never read.
66
+ * @example
67
+ * ```ts
68
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
69
+ * import { diagonalsRead } from "@johnmorrisdotca/kumimoji";
70
+ *
71
+ * const words = await loadTileWordsFromModule("english");
72
+ * console.log(diagonalsRead(()=>"",()=>true,"cat",{first:{row:0,col:0},fresh:[{row:0,col:0},{row:0,col:1},{row:0,col:2}]},word=>words.allowed.has(word)));
73
+ * ```
26
74
  */
27
75
  export declare function diagonalsRead(letterAt: (row: number, col: number) => string, inside: (row: number, col: number) => boolean, word: string, fit: CrossingFit, isWord: (run: string) => boolean): boolean;
package/dist/placement.js CHANGED
@@ -28,6 +28,21 @@ import { DIAGONAL_RUN_LEAST } from "./grid.js";
28
28
  * neighbour at its side, or no new tile at all — and, given `diagonalWord`
29
29
  * (a game with Diagonals), a new tile standing in a diagonal run of three or
30
30
  * more that `diagonalWord` does not take (`diagonalsRead`).
31
+ *
32
+ * @param letterAt - The tile at a coordinate, or an empty string for no tile.
33
+ * @param inside - Whether a coordinate is within the caller’s working grid.
34
+ * @param word - The encoded word to lay or spell.
35
+ * @param anchor - The existing tile that the word crosses.
36
+ * @param at - The word offset, seat index or date specified by this signature.
37
+ * @param across - Whether to lay or type horizontally rather than vertically.
38
+ * @param diagonalWord - Optional diagonal-word predicate; null disables diagonal checking.
39
+ * @returns Where `word` stands with its letter `at` on the tile at `anchor`, running across or down, or null where it cannot: a square outside what may be used, a tile at either end, a different letter in its way, a new tile with a neighbour at its side, or no new tile at all — and, given `diagonalWord` (a game with Diagonals), a new tile standing in a diagonal run of three or more that `diagonalWord` does not take (`diagonalsRead`).
40
+ * @example
41
+ * ```ts
42
+ * import { crossingFit } from "@johnmorrisdotca/kumimoji";
43
+ *
44
+ * console.log(crossingFit((r,c)=>r===0&&c===0?"c":"",()=>true,"cat",{row:0,col:0},0,true));
45
+ * ```
31
46
  */
32
47
  export function crossingFit(letterAt, inside, word, anchor, at, across, diagonalWord = null) {
33
48
  const dr = across ? 0 : 1;
@@ -67,6 +82,21 @@ export function crossingFit(letterAt, inside, word, anchor, at, across, diagonal
67
82
  * its new squares. Only a run through a new tile can change, and two new
68
83
  * tiles of one word never share a diagonal (they share a row or a column), so
69
84
  * each is read on its own. A run of two, a corner touch, is never read.
85
+ *
86
+ * @param letterAt - The tile at a coordinate, or an empty string for no tile.
87
+ * @param inside - Whether a coordinate is within the caller’s working grid.
88
+ * @param word - The encoded word to lay or spell.
89
+ * @param fit - The first coordinate and fresh tile coordinates of the proposed laying.
90
+ * @param isWord - The predicate that accepts a readable word.
91
+ * @returns Whether every diagonal run a laying's new tiles would stand in is taken by `isWord`: each walked from its top end down, with the word's own letters on its new squares. Only a run through a new tile can change, and two new tiles of one word never share a diagonal (they share a row or a column), so each is read on its own. A run of two, a corner touch, is never read.
92
+ * @example
93
+ * ```ts
94
+ * import { loadTileWordsFromModule } from "@johnmorrisdotca/kumimoji/words";
95
+ * import { diagonalsRead } from "@johnmorrisdotca/kumimoji";
96
+ *
97
+ * const words = await loadTileWordsFromModule("english");
98
+ * console.log(diagonalsRead(()=>"",()=>true,"cat",{first:{row:0,col:0},fresh:[{row:0,col:0},{row:0,col:1},{row:0,col:2}]},word=>words.allowed.has(word)));
99
+ * ```
70
100
  */
71
101
  export function diagonalsRead(letterAt, inside, word, fit, isWord) {
72
102
  const freshAt = new Map(fit.fresh.map((square) => [`${square.row},${square.col}`, word[(square.row - fit.first.row) + (square.col - fit.first.col)]]));