@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.
- package/CHANGELOG.md +63 -1
- package/CONTRIBUTING.md +65 -20
- package/README.md +869 -146
- package/dist/computerTurn.d.ts +1 -0
- package/dist/computerTurn.js +1 -0
- package/dist/export.d.ts +87 -0
- package/dist/export.js +158 -0
- package/dist/grid.d.ts +4 -1
- package/dist/grid.js +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/kumimoji.types.d.ts +2 -0
- package/dist/partyKept.d.ts +1 -0
- package/dist/partyKept.js +1 -0
- package/dist/partySeats.d.ts +2 -0
- package/dist/partySeats.js +2 -0
- package/dist/placement.d.ts +1 -0
- package/dist/play.d.ts +7 -0
- package/dist/play.js +1 -1
- package/dist/random.d.ts +1 -0
- package/dist/random.js +1 -0
- package/dist/react.d.ts +4 -1
- package/dist/react.js +4 -4
- package/dist/showcase.d.ts +4 -0
- package/dist/showcase.js +5 -1
- package/dist/strings.d.ts +62 -0
- package/dist/strings.js +96 -0
- package/dist/tableView.d.ts +1 -0
- package/dist/tableView.js +1 -0
- package/dist/tileFace.d.ts +4 -0
- package/dist/tileFace.js +2 -0
- package/dist/tileWords.d.ts +2 -0
- package/dist/tileWords.js +2 -0
- package/dist/ui/board.d.ts +6 -0
- package/dist/ui/mount.d.ts +32 -2
- package/dist/ui/mount.js +157 -26
- package/dist/ui/style.d.ts +5 -4
- package/dist/ui/style.js +23 -15
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +102 -32
package/dist/computerTurn.d.ts
CHANGED
|
@@ -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;
|
package/dist/computerTurn.js
CHANGED
|
@@ -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))
|
package/dist/export.d.ts
ADDED
|
@@ -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
|
|
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
package/dist/index.js
CHANGED
package/dist/kumimoji.types.d.ts
CHANGED
|
@@ -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;
|
package/dist/partyKept.d.ts
CHANGED
|
@@ -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,
|
package/dist/partySeats.d.ts
CHANGED
|
@@ -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;
|
package/dist/partySeats.js
CHANGED
|
@@ -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";
|
package/dist/placement.d.ts
CHANGED
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
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
|
package/dist/showcase.d.ts
CHANGED
|
@@ -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 {};
|