@johnmorrisdotca/kumimoji 1.0.1 → 1.1.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.
@@ -26,6 +26,7 @@ import type { TileWords } from "./tileWords.ts";
26
26
  */
27
27
  /** The most things a computer does in one turn before pressing Done: at the page's pause, a turn of about three seconds. */
28
28
  export declare const COMPUTER_STEPS_MOST = 8;
29
+ /** A computer seat's whole turn as its steps, in order, for a page to show one at a time. None when it is not a computer's turn or the game is over. */
29
30
  export declare function planComputerTurn(game: PartyGame, words: TileWords): ComputerStep[];
30
31
  /** The game a computer's turn passes on: its plan's last step, or the game as it was where there is nothing to play. */
31
32
  export declare function afterComputerTurn(game: PartyGame, words: TileWords): PartyGame;
@@ -29,6 +29,7 @@ import { liftAll, mayTrade, trade } from "./play.js";
29
29
  export const COMPUTER_STEPS_MOST = 8;
30
30
  /** How many settling steps may be needed at the end to be allowed Done at all; one trade a turn and a hand of tiles, so it is bounded anyway. */
31
31
  const SETTLING_MOST = 200;
32
+ /** A computer seat's whole turn as its steps, in order, for a page to show one at a time. None when it is not a computer's turn or the game is over. */
32
33
  export function planComputerTurn(game, words) {
33
34
  const steps = [];
34
35
  if (game.ending !== null || !isComputer(game, game.turn))
@@ -0,0 +1,87 @@
1
+ import { type Tiles } from "./grid.ts";
2
+ import type { KumimojiDeal, KumimojiLanguage, KumimojiLength, KumimojiLevel } from "./kumimoji.types.ts";
3
+ import { type TilePlay } from "./play.ts";
4
+ import { type KumimojiStrings } from "./strings.ts";
5
+ /**
6
+ * A GAME WRITTEN OUT for other programs and for people: JSON that reads back
7
+ * in, and plain text for a chat or a note. Both pure: a game in, a string
8
+ * out. What is done with the string (a file, a clipboard, a server) is the
9
+ * caller's.
10
+ *
11
+ * What is written is how the game was set up, its seed, and where the player
12
+ * has got to; never the bag, which the seed deals again. So `kumimojiFromJSON`
13
+ * trusts nothing in a file: it deals the bag from the seed itself, and opens
14
+ * the game only if every tile in the hand and on the table is one that came
15
+ * out of that bag and was not given back.
16
+ */
17
+ /** The shape of the JSON this package writes. It goes up only when a reader of the old shape would be wrong about the new one. */
18
+ export declare const KUMIMOJI_EXPORT_FORMAT = 1;
19
+ /** A game alone as it is kept: the deal, where the player has got to, and the time spent so far. */
20
+ export type KumimojiSaved = {
21
+ /** The game as dealt: `generateKumimoji`'s. */
22
+ deal: KumimojiDeal;
23
+ /** Where the player has got to. */
24
+ play: TilePlay;
25
+ /** Milliseconds spent on it so far. */
26
+ elapsedMs: number;
27
+ };
28
+ /** A whole JSON export of one game: what `kumimojiToJSON` writes and `kumimojiFromJSON` reads. */
29
+ export type KumimojiExported = {
30
+ /** The shape's number: `KUMIMOJI_EXPORT_FORMAT`. */
31
+ format: typeof KUMIMOJI_EXPORT_FORMAT;
32
+ /** Always `"kumimoji"`, so a file of this shape is not mistaken for another game's. */
33
+ game: "kumimoji";
34
+ /** The package and version that wrote it, such as `"kumimoji 1.1.0"`. For people; never read back. */
35
+ generator: string;
36
+ /** The opening hand: 3, 7 or 11. */
37
+ size: number;
38
+ /** How many of the tiles are wild. */
39
+ level: KumimojiLevel;
40
+ /** The seed the bag is dealt from. */
41
+ seed: number;
42
+ /** The language of the tiles. */
43
+ language: KumimojiLanguage;
44
+ /** How much of the set the bag holds. */
45
+ gameLength: KumimojiLength;
46
+ /** Two English sets at once. */
47
+ doubleSet: boolean;
48
+ /** Whether diagonal runs of three or more must be words too. */
49
+ diagonals: boolean;
50
+ /** Where the player has got to, as `encodeTileProgress` writes it: tiles taken, tiles traded back, the hand, the grid. */
51
+ progress: string;
52
+ /** Milliseconds spent so far. */
53
+ elapsedMs: number;
54
+ };
55
+ /** A game as the JSON export's object. */
56
+ export declare function kumimojiExported(saved: KumimojiSaved): KumimojiExported;
57
+ /** A game as JSON, two spaces deep, with the format's number first. `kumimojiFromJSON` reads it back. */
58
+ export declare function kumimojiToJSON(saved: KumimojiSaved): string;
59
+ /**
60
+ * A game from JSON that `kumimojiToJSON` wrote. Nothing in it is trusted: the
61
+ * bag is dealt again from the seed, and the game opens only if every tile held
62
+ * is one that came out of that bag. Resolves to null when the text is not
63
+ * JSON, is of a later format than this version reads, is another game's, or
64
+ * holds a hand or a grid that this bag never dealt.
65
+ *
66
+ * The word list of the game's language is loaded first, since dealing needs
67
+ * it; where there is no browser, import `@johnmorrisdotca/kumimoji/words`
68
+ * once, or the promise is rejected as `loadTileWords` rejects it.
69
+ */
70
+ export declare function kumimojiFromJSON(text: string): Promise<KumimojiSaved | null>;
71
+ /** A time as minutes and seconds: `1:05`. */
72
+ export declare function kumimojiClock(ms: number): string;
73
+ /**
74
+ * A grid as lines of text, the way it lies on the table: English letters in
75
+ * capitals with a space between squares and a dot for an empty one, kana side
76
+ * by side with a full-width dot for an empty one, so that either lines up in a
77
+ * fixed-width face, every row as wide as the crossword. A wild that has been given nothing is `*`. No tiles is "".
78
+ */
79
+ export declare function gridToText(tiles: Tiles, language?: KumimojiLanguage): string;
80
+ /**
81
+ * A game as plain text, for a chat or a note: how it was set up, the
82
+ * crossword as it lies, the hand, how many tiles are left, and the time. In
83
+ * English unless given another table of strings (`KUMIMOJI_STRINGS.ja`). The
84
+ * word list of the game's language must have been loaded, as for any judging.
85
+ * Lines end with a line feed.
86
+ */
87
+ export declare function kumimojiToText(saved: KumimojiSaved, strings?: KumimojiStrings): string;
package/dist/export.js ADDED
@@ -0,0 +1,158 @@
1
+ import { boundsOf, squareAt } from "./grid.js";
2
+ import { generateKumimoji } from "./generate.js";
3
+ import { judgeWithWords } from "./judge.js";
4
+ import { decodeTileProgress, encodeTileProgress, isFinished, tilesLeft } from "./play.js";
5
+ import { KUMIMOJI_STRINGS, kumimojiSay } from "./strings.js";
6
+ import { tileFace } from "./tileFace.js";
7
+ import { KUMIMOJI_BAG } from "./tiles.constants.js";
8
+ import { loadTileWords, tileWords } from "./tileWords.js";
9
+ import { KUMIMOJI_VERSION } from "./version.js";
10
+ /**
11
+ * A GAME WRITTEN OUT for other programs and for people: JSON that reads back
12
+ * in, and plain text for a chat or a note. Both pure: a game in, a string
13
+ * out. What is done with the string (a file, a clipboard, a server) is the
14
+ * caller's.
15
+ *
16
+ * What is written is how the game was set up, its seed, and where the player
17
+ * has got to; never the bag, which the seed deals again. So `kumimojiFromJSON`
18
+ * trusts nothing in a file: it deals the bag from the seed itself, and opens
19
+ * the game only if every tile in the hand and on the table is one that came
20
+ * out of that bag and was not given back.
21
+ */
22
+ /** The shape of the JSON this package writes. It goes up only when a reader of the old shape would be wrong about the new one. */
23
+ export const KUMIMOJI_EXPORT_FORMAT = 1;
24
+ /** A game as the JSON export's object. */
25
+ export function kumimojiExported(saved) {
26
+ const { deal, play } = saved;
27
+ return {
28
+ format: KUMIMOJI_EXPORT_FORMAT,
29
+ game: "kumimoji",
30
+ generator: `kumimoji ${KUMIMOJI_VERSION}`,
31
+ size: deal.size,
32
+ level: deal.level,
33
+ seed: deal.seed,
34
+ language: deal.language,
35
+ gameLength: deal.gameLength,
36
+ doubleSet: deal.doubleSet,
37
+ diagonals: deal.diagonals === true,
38
+ progress: encodeTileProgress(play),
39
+ elapsedMs: Math.max(0, Math.round(saved.elapsedMs)),
40
+ };
41
+ }
42
+ /** A game as JSON, two spaces deep, with the format's number first. `kumimojiFromJSON` reads it back. */
43
+ export function kumimojiToJSON(saved) {
44
+ return `${JSON.stringify(kumimojiExported(saved), null, 2)}\n`;
45
+ }
46
+ const LEVELS = ["easy", "medium", "hard"];
47
+ const LENGTHS_KEPT = ["short", "medium", "full"];
48
+ /** The longest progress a game of two full sets could write, with room to spare: anything longer is not read. */
49
+ const PROGRESS_MOST = 4000;
50
+ /**
51
+ * A game from JSON that `kumimojiToJSON` wrote. Nothing in it is trusted: the
52
+ * bag is dealt again from the seed, and the game opens only if every tile held
53
+ * is one that came out of that bag. Resolves to null when the text is not
54
+ * JSON, is of a later format than this version reads, is another game's, or
55
+ * holds a hand or a grid that this bag never dealt.
56
+ *
57
+ * The word list of the game's language is loaded first, since dealing needs
58
+ * it; where there is no browser, import `@johnmorrisdotca/kumimoji/words`
59
+ * once, or the promise is rejected as `loadTileWords` rejects it.
60
+ */
61
+ export async function kumimojiFromJSON(text) {
62
+ let data;
63
+ try {
64
+ data = JSON.parse(text);
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ if (typeof data !== "object" || data === null || Array.isArray(data))
70
+ return null;
71
+ const { format, game, size, level, seed, language, gameLength, doubleSet, diagonals, progress, elapsedMs } = data;
72
+ if (typeof format !== "number" || !Number.isInteger(format) || format < 1 || format > KUMIMOJI_EXPORT_FORMAT)
73
+ return null;
74
+ if (game !== "kumimoji")
75
+ return null;
76
+ if (typeof size !== "number" || KUMIMOJI_BAG[size] === undefined)
77
+ return null;
78
+ if (typeof level !== "string" || !LEVELS.includes(level))
79
+ return null;
80
+ if (typeof seed !== "number" || !Number.isInteger(seed) || seed < 0 || seed > 0xffffffff)
81
+ return null;
82
+ if (language !== "english" && language !== "japanese")
83
+ return null;
84
+ if (typeof gameLength !== "string" || !LENGTHS_KEPT.includes(gameLength))
85
+ return null;
86
+ if (typeof doubleSet !== "boolean" || typeof diagonals !== "boolean")
87
+ return null;
88
+ // Two sets are English's alone: the Japanese set is dealt once.
89
+ if (doubleSet && language !== "english")
90
+ return null;
91
+ if (typeof progress !== "string" || progress.length > PROGRESS_MOST)
92
+ return null;
93
+ const spent = typeof elapsedMs === "number" && Number.isFinite(elapsedMs) && elapsedMs >= 0 ? elapsedMs : 0;
94
+ await loadTileWords(language);
95
+ let deal;
96
+ try {
97
+ deal = generateKumimoji(size, level, seed, { language, gameLength: gameLength, doubleSet, diagonals });
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ const play = decodeTileProgress(progress, deal.givens, language);
103
+ return play === null ? null : { deal, play, elapsedMs: spent };
104
+ }
105
+ /** A time as minutes and seconds: `1:05`. */
106
+ export function kumimojiClock(ms) {
107
+ const seconds = Math.max(0, Math.floor(ms / 1000));
108
+ return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
109
+ }
110
+ /**
111
+ * A grid as lines of text, the way it lies on the table: English letters in
112
+ * capitals with a space between squares and a dot for an empty one, kana side
113
+ * by side with a full-width dot for an empty one, so that either lines up in a
114
+ * fixed-width face, every row as wide as the crossword. A wild that has been given nothing is `*`. No tiles is "".
115
+ */
116
+ export function gridToText(tiles, language = "english") {
117
+ const bounds = boundsOf(tiles);
118
+ if (bounds === null)
119
+ return "";
120
+ const kana = language === "japanese";
121
+ const rows = [];
122
+ for (let row = bounds.top; row <= bounds.bottom; row += 1) {
123
+ const cells = [];
124
+ for (let col = bounds.left; col <= bounds.right; col += 1) {
125
+ const tile = tiles.get(squareAt(row, col));
126
+ if (tile === undefined)
127
+ cells.push(kana ? "・" : ".");
128
+ else {
129
+ const face = tileFace(tile);
130
+ cells.push(face.blank ? (kana ? "*" : "*") : kana ? face.glyph : face.glyph.toUpperCase());
131
+ }
132
+ }
133
+ rows.push(cells.join(kana ? "" : " "));
134
+ }
135
+ return rows.join("\n");
136
+ }
137
+ /**
138
+ * A game as plain text, for a chat or a note: how it was set up, the
139
+ * crossword as it lies, the hand, how many tiles are left, and the time. In
140
+ * English unless given another table of strings (`KUMIMOJI_STRINGS.ja`). The
141
+ * word list of the game's language must have been loaded, as for any judging.
142
+ * Lines end with a line feed.
143
+ */
144
+ export function kumimojiToText(saved, strings = KUMIMOJI_STRINGS.en) {
145
+ const { deal, play } = saved;
146
+ const words = tileWords(deal.language);
147
+ const face = (tile) => (tileFace(tile).blank ? "*" : deal.language === "japanese" ? words.glyphOf(tile) : words.glyphOf(tile).toUpperCase());
148
+ const lines = [kumimojiSay(strings.textTitle, { language: deal.language === "japanese" ? strings.japanese : strings.english, hand: deal.size, seed: deal.seed })];
149
+ lines.push(play.tiles.size === 0 ? strings.textEmpty : gridToText(play.tiles, deal.language));
150
+ if (isFinished(play, judgeWithWords(play.tiles, words, { diagonals: deal.diagonals === true })))
151
+ lines.push(strings.textFinished);
152
+ else {
153
+ lines.push(play.hand.length === 0 ? strings.textHandEmpty : kumimojiSay(strings.textHand, { tiles: play.hand.map(face).join(" ") }));
154
+ lines.push(kumimojiSay(strings.textLeft, { n: tilesLeft(play) }));
155
+ }
156
+ lines.push(kumimojiSay(strings.textTime, { time: kumimojiClock(saved.elapsedMs) }));
157
+ return `${lines.join("\n")}\n`;
158
+ }
package/dist/grid.d.ts CHANGED
@@ -20,14 +20,16 @@ export declare function placeOf(square: string): {
20
20
  row: number;
21
21
  col: number;
22
22
  };
23
- /** The rows and columns the tiles stand within, or null for no tiles. */
23
+ /** The rows and columns a grid's tiles stand within. */
24
24
  export type Bounds = {
25
25
  top: number;
26
26
  left: number;
27
27
  bottom: number;
28
28
  right: number;
29
29
  };
30
+ /** The rows and columns the tiles stand within, or null for no tiles. */
30
31
  export declare function boundsOf(tiles: Tiles): Bounds | null;
32
+ /** A grid as a string, drawn from its own top-left tile: `cat/2o/2w`. `decodeGrid` reads it back. */
31
33
  export declare function encodeGrid(tiles: Tiles): string;
32
34
  /**
33
35
  * A grid read back, its first row at row 0, or null for anything that is not
@@ -94,4 +96,5 @@ export type GridVerdict = {
94
96
  /** The runs that are not words, as spelled, for the line under the table. */
95
97
  notWords: readonly string[];
96
98
  };
99
+ /** A grid judged against any test of a word: `isWord` answers for a run as spelt, and `readable` says how to name one that is not. `judgeWithWords` is this with a language's list. */
97
100
  export declare function judgeGrid(tiles: Tiles, isWord: (word: string) => boolean, readable?: (word: string) => string, rules?: GridRules): GridVerdict;
package/dist/grid.js CHANGED
@@ -8,6 +8,7 @@ export function placeOf(square) {
8
8
  const [row, col] = square.split(",").map(Number);
9
9
  return { row: row, col: col };
10
10
  }
11
+ /** The rows and columns the tiles stand within, or null for no tiles. */
11
12
  export function boundsOf(tiles) {
12
13
  let bounds = null;
13
14
  for (const square of tiles.keys()) {
@@ -19,6 +20,7 @@ export function boundsOf(tiles) {
19
20
  }
20
21
  return bounds;
21
22
  }
23
+ /** A grid as a string, drawn from its own top-left tile: `cat/2o/2w`. `decodeGrid` reads it back. */
22
24
  export function encodeGrid(tiles) {
23
25
  const bounds = boundsOf(tiles);
24
26
  if (bounds === null)
@@ -191,6 +193,7 @@ export function groupsOf(tiles, links = []) {
191
193
  }
192
194
  return groups.sort((a, b) => b.length - a.length);
193
195
  }
196
+ /** A grid judged against any test of a word: `isWord` answers for a run as spelt, and `readable` says how to name one that is not. `judgeWithWords` is this with a language's list. */
194
197
  export function judgeGrid(tiles, isWord, readable = (word) => word, rules = {}) {
195
198
  const misspelt = new Set();
196
199
  const notWords = [];
package/dist/index.d.ts CHANGED
@@ -42,3 +42,6 @@ export * from "./computer.types.ts";
42
42
  export * from "./computerPlay.ts";
43
43
  export * from "./computerTurn.ts";
44
44
  export * from "./random.ts";
45
+ export * from "./export.ts";
46
+ export * from "./strings.ts";
47
+ export { KUMIMOJI_VERSION } from "./version.ts";
package/dist/index.js CHANGED
@@ -42,3 +42,6 @@ export * from "./computer.types.js";
42
42
  export * from "./computerPlay.js";
43
43
  export * from "./computerTurn.js";
44
44
  export * from "./random.js";
45
+ export * from "./export.js";
46
+ export * from "./strings.js";
47
+ export { KUMIMOJI_VERSION } from "./version.js";
@@ -1,6 +1,8 @@
1
+ /** How much of the set a game's bag holds: a short game, half the set, or all of it. */
1
2
  export type KumimojiLength = "short" | "medium" | "full";
2
3
  /** How many of the bag's tiles are wild: easy the most, hard none (`KUMIMOJI_WILDS`). */
3
4
  export type KumimojiLevel = "easy" | "medium" | "hard";
5
+ /** The language of the tiles and of the word list they are judged by. */
4
6
  export type KumimojiLanguage = "english" | "japanese";
5
7
  /** How far the player has turned the table to look at it: quarter turns clockwise, 0 to 3. The view alone; the grid never turns. */
6
8
  export type Turn = 0 | 1 | 2 | 3;
@@ -1,4 +1,5 @@
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
3
  export declare function encodeParty(game: PartyGame): string;
3
4
  /** A game's settings as something outside this browser sent them — a table on several devices, a kept game — checked, or null. */
4
5
  export declare function readSettings(value: unknown): PartySettings | null;
package/dist/partyKept.js CHANGED
@@ -18,6 +18,7 @@ import { KUMIMOJI_PARTY, KUMIMOJI_HANDS } from "./tiles.constants.js";
18
18
  * nothing to check, and the names in it are the players' own business.
19
19
  */
20
20
  const VERSION = 1;
21
+ /** A table of players as text to keep. `decodeParty` reads it back, and given the word list's `familyKey` checks every tile held against the bag. */
21
22
  export function encodeParty(game) {
22
23
  const kept = {
23
24
  v: VERSION,
@@ -26,11 +26,13 @@ import type { PartyGame, PartySeat } from "./party.types.ts";
26
26
  */
27
27
  /** Why nobody can join now, or null when a hand can be dealt. */
28
28
  export type JoinRefusal = "over" | "full" | "lastRound" | "bag";
29
+ /** Why nobody can join the table now, or null when a hand can be dealt. */
29
30
  export declare function joinRefused(game: PartyGame): JoinRefusal | null;
30
31
  /** A player, or a computer, sat down after the last seat with the next hand out of the bag. */
31
32
  export declare function joinParty(game: PartyGame, seat: PartySeat): PartyGame;
32
33
  /** Why this player cannot leave now, or null when they can. */
33
34
  export type LeaveRefusal = "over" | "out" | "lastPerson";
35
+ /** Why the player at seat `at` cannot leave now, or null when they can. */
34
36
  export declare function leaveRefused(game: PartyGame, at: number): LeaveRefusal | null;
35
37
  /** How many tiles a player would put back in the bag by leaving: their hand and their table. */
36
38
  export declare function tilesHeldBy(game: PartyGame, at: number): number;
@@ -2,6 +2,7 @@ import { seededRandom, shuffled } from "./random.js";
2
2
  import { computerName, isComputer, nextIn, partyTilesLeft, stillIn, tableTally, tidyName } from "./party.js";
3
3
  import { tileFace } from "./tileFace.js";
4
4
  import { KUMIMOJI_PARTY } from "./tiles.constants.js";
5
+ /** Why nobody can join the table now, or null when a hand can be dealt. */
5
6
  export function joinRefused(game) {
6
7
  if (game.ending !== null)
7
8
  return "over";
@@ -32,6 +33,7 @@ function freePlayerName(players) {
32
33
  if (!taken.has(`Player ${number}`))
33
34
  return `Player ${number}`;
34
35
  }
36
+ /** Why the player at seat `at` cannot leave now, or null when they can. */
35
37
  export function leaveRefused(game, at) {
36
38
  if (game.ending !== null || game.players[at] === undefined)
37
39
  return "over";
@@ -1,3 +1,4 @@
1
+ /** A square by its row and column. */
1
2
  export type Square = {
2
3
  row: number;
3
4
  col: number;
package/dist/play.d.ts CHANGED
@@ -59,6 +59,13 @@ export declare function mayTrade(play: TilePlay): boolean;
59
59
  export declare function trade(play: TilePlay, handAt: number): TilePlay;
60
60
  /** Whether the game is over: the bag empty, the hand used, and the grid sound. */
61
61
  export declare function isFinished(play: TilePlay, verdict: GridVerdict): boolean;
62
+ /**
63
+ * A GAME KEPT half way, as one string (`PuzzleRun.progress`): how many tiles
64
+ * were taken, the tiles traded back, the hand in its order and the grid
65
+ * (`encodeGrid`), as `12:q:ae:cat/2o/2w`. The bag is the puzzle's own, from
66
+ * its seed, so it is not written again, and where on the table the grid stood
67
+ * is the view's and not kept.
68
+ */
62
69
  export declare function encodeTileProgress(play: TilePlay): string;
63
70
  /** The parts of a kept game, or null for a string that is not one: its shape only, with no bag to check it against. */
64
71
  export declare function readTileProgress(code: string): {
package/dist/play.js CHANGED
@@ -111,7 +111,7 @@ export function trade(play, handAt) {
111
111
  export function isFinished(play, verdict) {
112
112
  return play.hand.length === 0 && tilesLeft(play) === 0 && verdict.sound;
113
113
  }
114
- /*
114
+ /**
115
115
  * A GAME KEPT half way, as one string (`PuzzleRun.progress`): how many tiles
116
116
  * were taken, the tiles traded back, the hand in its order and the grid
117
117
  * (`encodeGrid`), as `12:q:ae:cat/2o/2w`. The bag is the puzzle's own, from
package/dist/random.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  /** A number in [0, 1), like `Math.random`, from a stream a seed fixes. */
8
8
  export type Random = () => number;
9
+ /** A stream of numbers in [0, 1) that the seed fixes: mulberry32. */
9
10
  export declare function seededRandom(seed: number): Random;
10
11
  /** A copy of the list in a random order (Fisher–Yates). */
11
12
  export declare function shuffled<T>(items: readonly T[], random: Random): T[];
package/dist/random.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * is small, fast and good enough for shuffling tiles; nothing here is a
5
5
  * credential.
6
6
  */
7
+ /** A stream of numbers in [0, 1) that the seed fixes: mulberry32. */
7
8
  export function seededRandom(seed) {
8
9
  let state = seed >>> 0;
9
10
  return () => {
package/dist/react.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { type HTMLAttributes } from "react";
2
2
  import type { GridVerdict, Tiles } from "./grid.ts";
3
3
  import { type KumimojiTableOptions } from "./ui/mount.ts";
4
+ /** What `KumimojiBoard` takes: a grid to draw, and any attribute of its `<div>`. */
4
5
  export type KumimojiBoardProps = {
6
+ /** The tiles on the table, by square: `play.tiles`. */
5
7
  tiles: Tiles;
6
8
  /** From `judgeWithWords`: marks the runs that are not words and the tiles apart. Without it, no marks. */
7
9
  verdict?: GridVerdict | null;
@@ -22,6 +24,7 @@ export type KumimojiBoardProps = {
22
24
  * with `placeFromHand`, `moveOnTable` and the rest.
23
25
  */
24
26
  export declare function KumimojiBoard({ tiles, verdict, glyphOf, onSquare, held, square, style, ...element }: KumimojiBoardProps): import("react").JSX.Element;
27
+ /** What `KumimojiTable` takes: the options of `mountKumimoji`, and any attribute of its `<div>`. */
25
28
  export type KumimojiTableProps = KumimojiTableOptions & Omit<HTMLAttributes<HTMLDivElement>, keyof KumimojiTableOptions>;
26
29
  /**
27
30
  * A whole game alone, as a React component: the plain-DOM table
@@ -29,4 +32,4 @@ export type KumimojiTableProps = KumimojiTableOptions & Omit<HTMLAttributes<HTML
29
32
  * has it. Options are read when it mounts; give it a new `key` to start over
30
33
  * with different ones.
31
34
  */
32
- export declare function KumimojiTable({ language, hand, level, gameLength, diagonals, seed, onFinish, ...element }: KumimojiTableProps): import("react").JSX.Element;
35
+ export declare function KumimojiTable({ language, hand, level, gameLength, diagonals, seed, onFinish, onChange, locale, strings, theme, keep, ...element }: KumimojiTableProps): import("react").JSX.Element;
package/dist/react.js CHANGED
@@ -34,17 +34,17 @@ export function KumimojiBoard({ tiles, verdict = null, glyphOf, onSquare, held =
34
34
  * has it. Options are read when it mounts; give it a new `key` to start over
35
35
  * with different ones.
36
36
  */
37
- export function KumimojiTable({ language, hand, level, gameLength, diagonals, seed, onFinish, ...element }) {
37
+ export function KumimojiTable({ language, hand, level, gameLength, diagonals, seed, onFinish, onChange, locale, strings, theme, keep, ...element }) {
38
38
  const host = useRef(null);
39
- const latest = useRef(onFinish);
39
+ const latest = useRef({ onFinish, onChange });
40
40
  useEffect(() => {
41
- latest.current = onFinish;
41
+ latest.current = { onFinish, onChange };
42
42
  });
43
43
  useEffect(() => {
44
44
  const target = host.current;
45
45
  if (target === null)
46
46
  return;
47
- const table = mountKumimoji(target, { language, hand, level, gameLength, diagonals, seed, onFinish: (result) => latest.current?.(result) });
47
+ const table = mountKumimoji(target, { language, hand, level, gameLength, diagonals, seed, locale, strings, theme, keep, onFinish: (result) => latest.current.onFinish?.(result), onChange: (saved) => latest.current.onChange?.(saved) });
48
48
  return () => table.destroy();
49
49
  // Mounted once per key, as documented above.
50
50
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -25,7 +25,9 @@ export type MixShown = {
25
25
  /** The tiles with the most, the ones a hand is full of. */
26
26
  commonest: string[];
27
27
  };
28
+ /** A language's set as a page shows it: every tile with its count, the total, and which are the rarest and the commonest. */
28
29
  export declare function mixShown(language: KumimojiLanguage): MixShown;
30
+ /** Every hiragana a tile plays as, itself first: は is は, ば and ぱ. None for a kana that is not a tile's own. */
29
31
  export declare function formsOfTile(kana: string): string[];
30
32
  /** One length of game at one opening hand: how many tiles it deals from one set and from two, and how many of them are wild at each level. */
31
33
  export type LengthRow = {
@@ -35,7 +37,9 @@ export type LengthRow = {
35
37
  doubleTiles: number;
36
38
  wilds: Record<"easy" | "medium" | "hard", number>;
37
39
  };
40
+ /** The lengths of game, shortest first. */
38
41
  export declare const LENGTHS: readonly KumimojiLength[];
42
+ /** For one opening hand, each length of game: how many tiles it deals, from one set and from two, and how many are wild at each level. */
39
43
  export declare function lengthRows(hand?: number): LengthRow[];
40
44
  /**
41
45
  * THE TRY-IT ON THE FRONT DOOR: a Kumimoji of ten tiles, played in the
package/dist/showcase.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { BASE_KANA, CORNER_FORMS, tileKana } from "./kana.js";
2
2
  import { kanaTileCode } from "./tileFace.js";
3
3
  import { JAPANESE_TILE_MIX, KUMIMOJI_HANDS, TILE_MIX, kumimojiTileCount, kumimojiWildCount } from "./tiles.constants.js";
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. */
4
5
  export function mixShown(language) {
5
6
  const tiles = language === "english"
6
7
  ? Object.entries(TILE_MIX).map(([letter, count]) => ({ code: letter, glyph: letter, forms: "", count }))
@@ -17,7 +18,7 @@ export function mixShown(language) {
17
18
  commonest: tiles.filter((tile) => tile.count === most).map((tile) => tile.glyph),
18
19
  };
19
20
  }
20
- /**
21
+ /*
21
22
  * EVERY HIRAGANA A TILE PLAYS AS, itself first: は is は, ば and ぱ; つ is つ,
22
23
  * っ and づ; お is お, ぉ and を. Read off the rule the word check uses
23
24
  * (`tileKana`) over the whole hiragana block, so the list is what the game
@@ -25,6 +26,7 @@ export function mixShown(language) {
25
26
  */
26
27
  const HIRAGANA_FIRST = 0x3041;
27
28
  const HIRAGANA_LAST = 0x3096;
29
+ /** Every hiragana a tile plays as, itself first: は is は, ば and ぱ. None for a kana that is not a tile's own. */
28
30
  export function formsOfTile(kana) {
29
31
  if (tileKana(kana) !== kana)
30
32
  return [];
@@ -36,7 +38,9 @@ export function formsOfTile(kana) {
36
38
  }
37
39
  return forms;
38
40
  }
41
+ /** The lengths of game, shortest first. */
39
42
  export const LENGTHS = ["short", "medium", "full"];
43
+ /** For one opening hand, each length of game: how many tiles it deals, from one set and from two, and how many are wild at each level. */
40
44
  export function lengthRows(hand = KUMIMOJI_HANDS.classic) {
41
45
  return LENGTHS.map((length) => {
42
46
  const tiles = kumimojiTileCount(hand, length);
@@ -0,0 +1,62 @@
1
+ /**
2
+ * EVERY WORD KUMIMOJI SHOWS A PERSON, in English and Japanese: the table's
3
+ * buttons and status lines, and the lines of a game written out as text
4
+ * (`kumimojiToText`). This is the language of the page, which is not the
5
+ * language of the tiles: an English page can play kana, and a Japanese page
6
+ * English letters.
7
+ *
8
+ * A page in another language passes its own table, whole or in part:
9
+ * `{ ...KUMIMOJI_STRINGS.en, draw: "Robar" }`. `{n}`, `{time}` and the other
10
+ * braces are filled in when a line is shown; a table must keep them.
11
+ * `docs/strings-ja.md` lists the two languages side by side and is made from
12
+ * this file.
13
+ */
14
+ /** The languages the table speaks: English and Japanese. */
15
+ export type KumimojiLocale = "en" | "ja";
16
+ declare const EN: {
17
+ dealing: string;
18
+ leftOne: string;
19
+ left: string;
20
+ finished: string;
21
+ tableLabel: string;
22
+ handLabel: string;
23
+ emptySquare: string;
24
+ wildTile: string;
25
+ wildBlank: string;
26
+ notWords: string;
27
+ apart: string;
28
+ noTiles: string;
29
+ giveWild: string;
30
+ changeWild: string;
31
+ draw: string;
32
+ trade: string;
33
+ sort: string;
34
+ liftAll: string;
35
+ newGame: string;
36
+ keep: string;
37
+ saveJson: string;
38
+ saveText: string;
39
+ load: string;
40
+ loaded: string;
41
+ loadBad: string;
42
+ textTitle: string;
43
+ textEmpty: string;
44
+ textHand: string;
45
+ textHandEmpty: string;
46
+ textLeft: string;
47
+ textTime: string;
48
+ textFinished: string;
49
+ english: string;
50
+ japanese: string;
51
+ };
52
+ /** Every word the table shows, by name: one table of these for each language. */
53
+ export type KumimojiStrings = {
54
+ [Name in keyof typeof EN]: string;
55
+ };
56
+ /** The table's words in each language it speaks: `KUMIMOJI_STRINGS.en`, `KUMIMOJI_STRINGS.ja`. */
57
+ export declare const KUMIMOJI_STRINGS: Readonly<Record<KumimojiLocale, KumimojiStrings>>;
58
+ /** A line from a table of strings with its braces filled in: `kumimojiSay(strings.left, { n: 12, time: "1:05" })`. A brace with no value is left as it is. */
59
+ export declare function kumimojiSay(line: string, values?: Readonly<Record<string, string | number>>): string;
60
+ /** The table of strings for a locale with a page's own words laid over it. An unknown locale is English. */
61
+ export declare function kumimojiStrings(locale: KumimojiLocale | string | undefined, own?: Partial<KumimojiStrings>): KumimojiStrings;
62
+ export {};