chesspal 0.0.1 → 0.2.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.
- package/README.md +99 -2
- package/dist/cli/chat-hook.d.ts +2 -0
- package/dist/cli/chat-hook.js +33 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +28 -0
- package/dist/cli/command-line.d.ts +7 -0
- package/dist/cli/command-line.js +177 -0
- package/dist/commands/agents.d.ts +26 -0
- package/dist/commands/agents.js +72 -0
- package/dist/commands/ai-move.d.ts +28 -0
- package/dist/commands/ai-move.js +85 -0
- package/dist/commands/ai-say.d.ts +22 -0
- package/dist/commands/ai-say.js +58 -0
- package/dist/commands/analyse-game.d.ts +65 -0
- package/dist/commands/analyse-game.js +265 -0
- package/dist/commands/annotate-game.d.ts +39 -0
- package/dist/commands/annotate-game.js +131 -0
- package/dist/commands/answer-lines.d.ts +28 -0
- package/dist/commands/answer-lines.js +144 -0
- package/dist/commands/clock.d.ts +15 -0
- package/dist/commands/clock.js +45 -0
- package/dist/commands/coach.d.ts +45 -0
- package/dist/commands/coach.js +344 -0
- package/dist/commands/context.d.ts +67 -0
- package/dist/commands/context.js +37 -0
- package/dist/commands/delete-game.d.ts +7 -0
- package/dist/commands/delete-game.js +30 -0
- package/dist/commands/delete-review.d.ts +8 -0
- package/dist/commands/delete-review.js +34 -0
- package/dist/commands/draw-offer.d.ts +10 -0
- package/dist/commands/draw-offer.js +43 -0
- package/dist/commands/drawings.d.ts +13 -0
- package/dist/commands/drawings.js +25 -0
- package/dist/commands/end-game.d.ts +23 -0
- package/dist/commands/end-game.js +67 -0
- package/dist/commands/export-game.d.ts +14 -0
- package/dist/commands/export-game.js +27 -0
- package/dist/commands/game-summary.d.ts +78 -0
- package/dist/commands/game-summary.js +102 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.js +64 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +73 -0
- package/dist/commands/lead-input.d.ts +8 -0
- package/dist/commands/lead-input.js +21 -0
- package/dist/commands/levels.d.ts +9 -0
- package/dist/commands/levels.js +25 -0
- package/dist/commands/look.d.ts +31 -0
- package/dist/commands/look.js +64 -0
- package/dist/commands/new-game.d.ts +68 -0
- package/dist/commands/new-game.js +218 -0
- package/dist/commands/opening-book.d.ts +13 -0
- package/dist/commands/opening-book.js +84 -0
- package/dist/commands/page-link.d.ts +11 -0
- package/dist/commands/page-link.js +28 -0
- package/dist/commands/past-games.d.ts +11 -0
- package/dist/commands/past-games.js +47 -0
- package/dist/commands/play-move.d.ts +37 -0
- package/dist/commands/play-move.js +94 -0
- package/dist/commands/player-move.d.ts +13 -0
- package/dist/commands/player-move.js +51 -0
- package/dist/commands/player-say.d.ts +5 -0
- package/dist/commands/player-say.js +16 -0
- package/dist/commands/ponder.d.ts +52 -0
- package/dist/commands/ponder.js +179 -0
- package/dist/commands/position.d.ts +14 -0
- package/dist/commands/position.js +130 -0
- package/dist/commands/profiles.d.ts +69 -0
- package/dist/commands/profiles.js +163 -0
- package/dist/commands/registry.d.ts +59 -0
- package/dist/commands/registry.js +62 -0
- package/dist/commands/say.d.ts +16 -0
- package/dist/commands/say.js +84 -0
- package/dist/commands/speech.d.ts +8 -0
- package/dist/commands/speech.js +20 -0
- package/dist/commands/status.d.ts +35 -0
- package/dist/commands/status.js +128 -0
- package/dist/commands/study-games.d.ts +5 -0
- package/dist/commands/study-games.js +40 -0
- package/dist/commands/study.d.ts +38 -0
- package/dist/commands/study.js +106 -0
- package/dist/commands/view.d.ts +14 -0
- package/dist/commands/view.js +125 -0
- package/dist/core/board-text.d.ts +7 -0
- package/dist/core/board-text.js +18 -0
- package/dist/core/book.json +1 -0
- package/dist/core/clock.d.ts +45 -0
- package/dist/core/clock.js +109 -0
- package/dist/core/facts.d.ts +55 -0
- package/dist/core/facts.js +175 -0
- package/dist/core/game.d.ts +95 -0
- package/dist/core/game.js +352 -0
- package/dist/core/illegal.d.ts +19 -0
- package/dist/core/illegal.js +141 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +12 -0
- package/dist/core/lead.d.ts +15 -0
- package/dist/core/lead.js +40 -0
- package/dist/core/levels.d.ts +20 -0
- package/dist/core/levels.js +33 -0
- package/dist/core/material.d.ts +9 -0
- package/dist/core/material.js +12 -0
- package/dist/core/moods.d.ts +21 -0
- package/dist/core/moods.js +45 -0
- package/dist/core/openings.d.ts +35 -0
- package/dist/core/openings.js +98 -0
- package/dist/core/openings.json +1 -0
- package/dist/core/pgn.d.ts +10 -0
- package/dist/core/pgn.js +213 -0
- package/dist/core/profile.d.ts +117 -0
- package/dist/core/profile.js +232 -0
- package/dist/core/record.d.ts +21 -0
- package/dist/core/record.js +25 -0
- package/dist/core/review.d.ts +80 -0
- package/dist/core/review.js +146 -0
- package/dist/games/ai-model.d.ts +5 -0
- package/dist/games/ai-model.js +15 -0
- package/dist/games/analysis.d.ts +12 -0
- package/dist/games/analysis.js +26 -0
- package/dist/games/archive.d.ts +63 -0
- package/dist/games/archive.js +242 -0
- package/dist/games/chat-start.d.ts +16 -0
- package/dist/games/chat-start.js +26 -0
- package/dist/games/coach.d.ts +57 -0
- package/dist/games/coach.js +144 -0
- package/dist/games/current-game.d.ts +56 -0
- package/dist/games/current-game.js +134 -0
- package/dist/games/files.d.ts +9 -0
- package/dist/games/files.js +53 -0
- package/dist/games/index.d.ts +11 -0
- package/dist/games/index.js +12 -0
- package/dist/games/plans.d.ts +9 -0
- package/dist/games/plans.js +23 -0
- package/dist/games/table-talk.d.ts +12 -0
- package/dist/games/table-talk.js +25 -0
- package/dist/games/talk.d.ts +36 -0
- package/dist/games/talk.js +131 -0
- package/dist/games/thoughts.d.ts +15 -0
- package/dist/games/thoughts.js +60 -0
- package/dist/home/data-folder.d.ts +12 -0
- package/dist/home/data-folder.js +55 -0
- package/dist/home/index.d.ts +2 -0
- package/dist/home/index.js +3 -0
- package/dist/home/open-folder.d.ts +2 -0
- package/dist/home/open-folder.js +14 -0
- package/dist/host-files/agents.d.ts +20 -0
- package/dist/host-files/agents.js +53 -0
- package/dist/host-files/host-files.d.ts +15 -0
- package/dist/host-files/host-files.js +182 -0
- package/dist/host-files/index.d.ts +2 -0
- package/dist/host-files/index.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/page/app/anchor.js +88 -0
- package/dist/page/app/app.js +458 -0
- package/dist/page/app/keys.js +94 -0
- package/dist/page/app/phase.js +30 -0
- package/dist/page/app/served.js +73 -0
- package/dist/page/avatars/avatars.js +19 -0
- package/dist/page/avatars/beanie.webp +0 -0
- package/dist/page/avatars/beard.webp +0 -0
- package/dist/page/avatars/bun.webp +0 -0
- package/dist/page/avatars/cat.webp +0 -0
- package/dist/page/avatars/curls.webp +0 -0
- package/dist/page/avatars/glasses.webp +0 -0
- package/dist/page/avatars/moustache.webp +0 -0
- package/dist/page/avatars/owl.webp +0 -0
- package/dist/page/avatars/short-hair.webp +0 -0
- package/dist/page/avatars/silver-bob.webp +0 -0
- package/dist/page/avatars/thought.js +10 -0
- package/dist/page/avatars/torretta.js +38 -0
- package/dist/page/base.css +160 -0
- package/dist/page/board/board.css +959 -0
- package/dist/page/board/board.js +125 -0
- package/dist/page/board/explain.js +58 -0
- package/dist/page/board/marks.js +160 -0
- package/dist/page/board/motion.js +71 -0
- package/dist/page/board/pointer.js +239 -0
- package/dist/page/board/squares.js +26 -0
- package/dist/page/dialogs/analyses.js +195 -0
- package/dist/page/dialogs/dialog.js +26 -0
- package/dist/page/dialogs/dialogs.css +1228 -0
- package/dist/page/dialogs/new-game.js +42 -0
- package/dist/page/dialogs/profiles.js +152 -0
- package/dist/page/dialogs/search.js +31 -0
- package/dist/page/dialogs/settings.js +42 -0
- package/dist/page/dialogs/studied.js +39 -0
- package/dist/page/dialogs/study.js +51 -0
- package/dist/page/game/badges.js +50 -0
- package/dist/page/game/browse.js +17 -0
- package/dist/page/game/card-fold.js +40 -0
- package/dist/page/game/column.js +51 -0
- package/dist/page/game/ending.js +73 -0
- package/dist/page/game/game.css +577 -0
- package/dist/page/game/move-list.js +72 -0
- package/dist/page/game/opening-book.js +14 -0
- package/dist/page/game/plies.js +82 -0
- package/dist/page/game/result-tile.js +14 -0
- package/dist/page/helps/helps.css +689 -0
- package/dist/page/helps/helps.js +220 -0
- package/dist/page/helps/view.js +127 -0
- package/dist/page/layout/fold-tab.js +11 -0
- package/dist/page/layout/folds.js +43 -0
- package/dist/page/layout/header.js +28 -0
- package/dist/page/layout/layout.css +834 -0
- package/dist/page/lead/lead.js +82 -0
- package/dist/page/lead/view.js +38 -0
- package/dist/page/main.js +8 -0
- package/dist/page/pieces/CREDITS.md +19 -0
- package/dist/page/pieces/capablanca-small.svg +20 -0
- package/dist/page/pieces/capablanca.svg +22 -0
- package/dist/page/pieces/nitida-small.svg +20 -0
- package/dist/page/pieces/nitida.svg +21 -0
- package/dist/page/pieces/pieces.js +28 -0
- package/dist/page/pieces/torretta-small.svg +20 -0
- package/dist/page/pieces/torretta.svg +21 -0
- package/dist/page/players/card.js +56 -0
- package/dist/page/players/clock-time.js +12 -0
- package/dist/page/players/model-name.js +20 -0
- package/dist/page/profile/profile.js +99 -0
- package/dist/page/profile/settings.js +77 -0
- package/dist/page/sounds/capture.ogg +0 -0
- package/dist/page/sounds/check.ogg +0 -0
- package/dist/page/sounds/draw.ogg +0 -0
- package/dist/page/sounds/flag.ogg +0 -0
- package/dist/page/sounds/mate.ogg +0 -0
- package/dist/page/sounds/move.ogg +0 -0
- package/dist/page/sounds/offer.ogg +0 -0
- package/dist/page/sounds/resign.ogg +0 -0
- package/dist/page/sounds/sounds.js +105 -0
- package/dist/page/style.css +9 -0
- package/dist/page/talk/talk.css +958 -0
- package/dist/page/talk/talk.js +165 -0
- package/dist/page/talk/voice.js +38 -0
- package/dist/page/text.js +711 -0
- package/dist/page/theme.css +292 -0
- package/dist/profiles/avatar.d.ts +23 -0
- package/dist/profiles/avatar.js +84 -0
- package/dist/profiles/front-matter.d.ts +9 -0
- package/dist/profiles/front-matter.js +33 -0
- package/dist/profiles/index.d.ts +3 -0
- package/dist/profiles/index.js +5 -0
- package/dist/profiles/profiles.d.ts +69 -0
- package/dist/profiles/profiles.js +254 -0
- package/dist/profiles/settings.d.ts +12 -0
- package/dist/profiles/settings.js +46 -0
- package/dist/server/bell.d.ts +68 -0
- package/dist/server/bell.js +225 -0
- package/dist/server/caller.d.ts +21 -0
- package/dist/server/caller.js +132 -0
- package/dist/server/mcp.d.ts +13 -0
- package/dist/server/mcp.js +207 -0
- package/dist/server/page/analyses.d.ts +23 -0
- package/dist/server/page/analyses.js +65 -0
- package/dist/server/page/files.d.ts +5 -0
- package/dist/server/page/files.js +92 -0
- package/dist/server/page/game.d.ts +6 -0
- package/dist/server/page/game.js +381 -0
- package/dist/server/page/http.d.ts +29 -0
- package/dist/server/page/http.js +68 -0
- package/dist/server/page/page.d.ts +41 -0
- package/dist/server/page/page.js +229 -0
- package/dist/server/page/profiles.d.ts +19 -0
- package/dist/server/page/profiles.js +215 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/instructions/analysis.md +4 -0
- package/instructions/analyst-agent.md +8 -0
- package/instructions/analyst.md +4 -0
- package/instructions/coach.md +11 -0
- package/instructions/opponent.md +15 -0
- package/instructions/ponder.md +9 -0
- package/package.json +66 -5
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
|
+
// The talk on the board page (D-51, D-53): the AI's words in bubbles that point at the Torretta, the player's above the box they write in.
|
|
3
|
+
import { createContext } from "preact";
|
|
4
|
+
import { useContext, useEffect, useLayoutEffect, useRef } from "preact/hooks";
|
|
5
|
+
import { Torretta } from "../avatars/torretta.js";
|
|
6
|
+
import { BadgeIcon, NoteIcon } from "../game/badges.js";
|
|
7
|
+
import { Figurines } from "../game/move-list.js";
|
|
8
|
+
import { talkLimits } from "../game/plies.js";
|
|
9
|
+
import { signature } from "../players/model-name.js";
|
|
10
|
+
import { hush, speak } from "./voice.js";
|
|
11
|
+
/** The AI's words of the game, a bubble each, the newest by the Torretta: below it, or above it on a board turned round; the first points at it. */
|
|
12
|
+
export function AiWords({ words, newestFirst, voice }) {
|
|
13
|
+
const list = useRef(null);
|
|
14
|
+
useOnlyWhole(list, words, newestFirst);
|
|
15
|
+
useSpokenNews(words, voice);
|
|
16
|
+
return (_jsx("ol", { ref: list, class: "says", "aria-live": "polite", children: inOrder(words.map((word, index) => (_jsx("li", { class: "say", style: aged(words.length - 1 - index), children: _jsx(Marked, { text: word }) }, index))), newestFirst) }));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The AI's word on the move shown of a game in analysis (D-69), in a bubble that points down at the Torretta beside the AI's name: the move, its badge by name,
|
|
20
|
+
* and its note when it wrote one; read out as the player steps through the game. The Torretta and the name stay when the move has no word.
|
|
21
|
+
*/
|
|
22
|
+
export function AiNote({ ply, opening, closing, summary, reviewing, name, by, mood, voice, text }) {
|
|
23
|
+
// On a move of the book without the AI's note, the AI reports what the book says: the opening in the title, the variation under it.
|
|
24
|
+
const book = ply?.review?.note === undefined ? opening : undefined;
|
|
25
|
+
const shown = ply?.review ?? (book === undefined ? undefined : { badge: "book" });
|
|
26
|
+
const title = [shown?.badge === undefined || (book !== undefined && shown.badge === "book") ? undefined : text.badges[shown.badge], book?.family].filter((part) => part !== undefined).join(" · ");
|
|
27
|
+
const note = shown?.note ?? book?.variation;
|
|
28
|
+
const spoken = [ply === undefined || shown === undefined ? undefined : title, ply === undefined ? undefined : note, summary?.replace(/\s*•\s+/g, ". "), closing].filter((part) => part !== undefined && part !== "").join(". ");
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (voice !== undefined) {
|
|
31
|
+
speak(spoken, voice);
|
|
32
|
+
}
|
|
33
|
+
return hush;
|
|
34
|
+
}, [spoken, voice]);
|
|
35
|
+
// The Torretta stays in sight through the whole analysis, under its bubbles, which show only where it wrote something.
|
|
36
|
+
return (_jsxs("div", { class: "ai-note", "aria-live": "polite", children: [ply === undefined || shown === undefined ? null : (_jsxs("p", { class: `say move-note ${shown.badge ?? ""}`, children: [_jsxs("span", { class: "head", children: [shown.badge === undefined ? _jsx(NoteIcon, {}) : _jsx(BadgeIcon, { badge: shown.badge }), _jsxs("b", { children: [_jsxs("span", { class: "visually-hidden", children: [ply.number, ply.side === "white" ? "." : "...", ply.san] }), _jsxs("span", { "aria-hidden": "true", children: [_jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] })] }), " ", title] }), note === undefined ? null : (_jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })), shown.arrows === undefined && shown.squares === undefined ? null : _jsx(Legend, { review: shown, words: text.drawn })] })), summary === undefined ? null : _jsx(GameReview, { by: name, summary: summary, text: text }), closing === undefined ? null : (_jsx("p", { class: "say closing", children: _jsx(Marked, { text: closing }) })), reviewing === true
|
|
37
|
+
? (_jsxs("p", { class: "say reviewing", children: [_jsx("span", { class: "spinner", "aria-hidden": "true" }), text.reviewing(name)] }))
|
|
38
|
+
: null, _jsxs("p", { class: "who", children: [_jsx(Torretta, { size: 36, mood: mood }), _jsxs("span", { class: "signed", children: [name, by === undefined ? null : _jsx("small", { children: signature(by.annotator, by.effort) })] })] })] }));
|
|
39
|
+
}
|
|
40
|
+
/** The squares and moves in the AI's words, such as e3, Cxe4, 7.Nd4 or O-O: in bold, as the player looks for them on the board. */
|
|
41
|
+
export function Marked({ text }) {
|
|
42
|
+
// The words the AI wrote between two asterisks first, in bold as it chose; the squares and moves in the rest, and in them, in the accent.
|
|
43
|
+
return (_jsx(_Fragment, { children: text.split(boldWords).map((part, index) => index % 2 === 1
|
|
44
|
+
? (_jsx("b", { children: _jsx(OnBoard, { text: part }) }, index))
|
|
45
|
+
: _jsx(OnBoard, { text: part }, index)) }));
|
|
46
|
+
}
|
|
47
|
+
/** Where the pointer is among the squares and moves in the AI's words: the board lights that square up. */
|
|
48
|
+
export const PointSquare = createContext(() => undefined);
|
|
49
|
+
// **a few words** the AI put in bold, as Markdown writes them.
|
|
50
|
+
const boldWords = /\*\*(.+?)\*\*/;
|
|
51
|
+
// A square or a move in SAN or with the pieces' figurines (D-86, D-87), with its number if it has one; the capture group keeps it in split's parts.
|
|
52
|
+
const boardWords = /(?<![\p{L}\d])((?:\d+\.(?:\.\.)?\s?)?(?:O-O(?:-O)?|[KQRBN♔-♟]?[a-h]?[1-8]?x?[a-h][1-8](?:=[QRBN♔-♟])?)[+#]?[!?]{0,2})(?![\p{L}\d])/u;
|
|
53
|
+
/**
|
|
54
|
+
* What the colours drawn with a note or the coach's hint mean, the ones it uses, in the words of who drew them: the legend, for the player (D-69, Zoe).
|
|
55
|
+
*/
|
|
56
|
+
export function Legend({ review, words }) {
|
|
57
|
+
const used = [...new Set([...(review.arrows ?? []), ...(review.squares ?? [])].map((drawn) => drawn.colour))];
|
|
58
|
+
return (_jsx("span", { class: "legend", children: used.map((colour) => (_jsxs("span", { class: colour, children: [_jsx("i", { "aria-hidden": "true" }), words[colour]] }, colour))) }));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The player's words of the game, the newest by the box they write in with how far it got, and the box: Enter or the button sends the words alone, the next
|
|
62
|
+
* move takes them with it. 200 characters from one move of the AI's to the next: once they are written, the box goes until its move (D-54).
|
|
63
|
+
*/
|
|
64
|
+
export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, canMove, name, problem, text }) {
|
|
65
|
+
const list = useRef(null);
|
|
66
|
+
useOnlyWhole(list, lines.map((line) => line.text));
|
|
67
|
+
const left = talkLimits.line - spent;
|
|
68
|
+
const blank = draft.trim() === "";
|
|
69
|
+
const submit = () => {
|
|
70
|
+
if (!blank) {
|
|
71
|
+
onSend();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return (_jsxs(_Fragment, { children: [_jsx("ol", { ref: list, class: "sent", "aria-live": "polite", children: inOrder(lines.map((line, index) => (_jsxs("li", { style: aged(lines.length - 1 - index), children: [_jsx("p", { class: "said", children: line.text }), index === lines.length - 1 ? _jsx(Status, { heard: line.heard, name: name, text: text }) : null] }, index))), newestFirst) }), left <= 0 ? null : (_jsxs("form", { class: "compose", onSubmit: (event) => {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
submit();
|
|
77
|
+
}, children: [_jsx("p", { class: "limit", role: "status", children: problem ?? "" }), _jsx("textarea", { rows: 2, maxLength: left, placeholder: text.writeTo(name), "aria-label": text.writeTo(name), value: draft, onInput: (event) => onDraft(event.currentTarget.value), onKeyDown: (event) => {
|
|
78
|
+
if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
submit();
|
|
81
|
+
}
|
|
82
|
+
} }), _jsxs("div", { class: "foot", children: [spent + draft.length >= talkLimits.line - 50
|
|
83
|
+
? _jsxs("span", { class: "count", children: [spent + draft.length, "/", talkLimits.line] })
|
|
84
|
+
: _jsx("span", { class: "hint", children: !blank && canMove ? text.withMove : "" }), _jsxs("button", { class: "send", type: "submit", disabled: blank, children: [text.send, _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 12H18M13 7L18 12L13 17" }) })] })] })] }))] }));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The talk narrowed to a card beside the board: the Torretta above, the player below, each with their name and side written down the card, a ring on
|
|
88
|
+
* whoever moves and a dot on the Torretta when the AI spoke meanwhile.
|
|
89
|
+
*/
|
|
90
|
+
export function TalkFaces({ name, aiPicture, you, picture, ai, player, aiMoves, playerMoves, unread, text }) {
|
|
91
|
+
return (_jsxs("div", { class: "faces", children: [_jsx(RailPlayer, { place: "ai", picture: aiPicture ?? _jsx(Torretta, { size: 40 }), name: name, ...(ai === undefined ? {} : { side: text.sides[ai] }), moves: aiMoves, unread: unread }), _jsx(RailPlayer, { place: "you", picture: picture, name: you, ...(player === undefined ? {} : { side: text.sides[player] }), moves: playerMoves })] }));
|
|
92
|
+
}
|
|
93
|
+
function OnBoard({ text }) {
|
|
94
|
+
const point = useContext(PointSquare);
|
|
95
|
+
return (_jsx(_Fragment, { children: text.split(boardWords).map((part, index) => index % 2 === 1
|
|
96
|
+
? (_jsx("b", { class: "on-board", onPointerEnter: () => point(squareIn(part)), onPointerLeave: (event) => event.pointerType === "mouse" && point(undefined), children: _jsx(Takes, { move: part }) }, index))
|
|
97
|
+
: part) }));
|
|
98
|
+
}
|
|
99
|
+
/** A move with its capture's x light, as the move list writes it: the piece and the squares stand out (Stefano, for readers with ADHD). */
|
|
100
|
+
function Takes({ move }) {
|
|
101
|
+
return _jsx(_Fragment, { children: move.split("x").map((part, index) => index === 0 ? part : [_jsx("span", { class: "takes", children: "x" }, index), part]) });
|
|
102
|
+
}
|
|
103
|
+
/** The square a word names: a square, or where a move goes, the last square in it; none for a castling. */
|
|
104
|
+
function squareIn(word) {
|
|
105
|
+
return /[a-h][1-8](?!.*[a-h][1-8])/.exec(word)?.[0];
|
|
106
|
+
}
|
|
107
|
+
/** The AI's word on the whole game (D-69), in the Torretta's bubble at the start of the game: a paragraph, and its points as a list. */
|
|
108
|
+
function GameReview({ by, summary, text }) {
|
|
109
|
+
const [lead = "", ...points] = summary.split(/\s*•\s+/);
|
|
110
|
+
return (_jsxs("section", { class: "say review", "aria-label": text.reviewBy(by), children: [_jsxs("p", { class: "head", children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 4V20M8 20H16M5 7H19M5 7L2.5 13A2.5 2 0 0 0 7.5 13ZM19 7L16.5 13A2.5 2 0 0 0 21.5 13Z" }) }), text.reviewBy(by)] }), lead === "" ? null : (_jsx("p", { children: _jsx(Marked, { text: lead }) })), points.length === 0 ? null : (_jsx("ul", { children: points.map((point, index) => (_jsx("li", { children: _jsx(Marked, { text: point }) }, index))) }))] }));
|
|
111
|
+
}
|
|
112
|
+
function RailPlayer({ place, picture, name, side, moves, unread = false }) {
|
|
113
|
+
return (_jsxs("div", { class: `rail-player ${place}${moves ? " moving" : ""}`, children: [_jsx("span", { class: `face${unread ? " unread" : ""}`, children: picture }), _jsxs("span", { class: "rail-name", children: [name, " ", side === undefined ? null : _jsxs("span", { class: "note", children: ["\u00B7 ", side] })] })] }));
|
|
114
|
+
}
|
|
115
|
+
/** Reads aloud each word the AI adds while the page is open, not those already there. */
|
|
116
|
+
function useSpokenNews(words, voice) {
|
|
117
|
+
const heard = useRef(words.length);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (voice !== undefined && words.length > heard.current) {
|
|
120
|
+
speak(words.slice(heard.current).join(" "), voice);
|
|
121
|
+
}
|
|
122
|
+
heard.current = words.length;
|
|
123
|
+
}, [words.length, voice]);
|
|
124
|
+
}
|
|
125
|
+
/** A list's items, the newest first when it asks. */
|
|
126
|
+
function inOrder(items, newestFirst) {
|
|
127
|
+
return newestFirst ? items.reverse() : items;
|
|
128
|
+
}
|
|
129
|
+
/** A word fades with age: the newest whole, the older ones far back, so they never pass for the newest (Stefano), each fainter down to a floor. */
|
|
130
|
+
function aged(age) {
|
|
131
|
+
return { opacity: age === 0 ? 1 : Math.max(0.2, 0.4 - (age - 1) * 0.1) };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A list that never scrolls: a bubble that does not fit whole in it is hidden, the oldest first since the newest sit by the card, and shows again when the
|
|
135
|
+
* window grows.
|
|
136
|
+
*/
|
|
137
|
+
function useOnlyWhole(list, lines, newestFirst = false) {
|
|
138
|
+
useLayoutEffect(() => {
|
|
139
|
+
const element = list.current;
|
|
140
|
+
if (element === null) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const fit = () => {
|
|
144
|
+
const box = element.getBoundingClientRect();
|
|
145
|
+
const newest = newestFirst ? element.firstElementChild : element.lastElementChild;
|
|
146
|
+
for (const item of element.children) {
|
|
147
|
+
const place = item.getBoundingClientRect();
|
|
148
|
+
const style = item.style;
|
|
149
|
+
// The newest never hides: one longer than the column, as a note with the helps on (D-78), scrolls in itself.
|
|
150
|
+
style.maxHeight = item === newest ? box.height + "px" : "";
|
|
151
|
+
style.overflowY = item === newest && item.scrollHeight > box.height ? "auto" : "";
|
|
152
|
+
style.visibility = item === newest || (place.top >= box.top - 1 && place.bottom <= box.bottom + 1) ? "" : "hidden";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
fit();
|
|
156
|
+
const observer = new ResizeObserver(fit);
|
|
157
|
+
observer.observe(element);
|
|
158
|
+
return () => observer.disconnect();
|
|
159
|
+
}, [list, lines.length, lines.at(-1)]);
|
|
160
|
+
}
|
|
161
|
+
/** Under the player's last line: one check once written, two once the AI has read it. */
|
|
162
|
+
function Status({ heard, name, text }) {
|
|
163
|
+
const read = heard === "read" || heard === "answered";
|
|
164
|
+
return (_jsxs("span", { class: read ? "status read" : "status", children: [_jsx("svg", { class: "icon", viewBox: "0 0 21 24", "aria-hidden": "true", children: read ? _jsx("path", { d: "M2 12.5L6 16.5L13.5 8M10 15.5L11 16.5L18.5 8" }) : _jsx("path", { d: "M3.5 12.5L7.5 16.5L15 8" }) }), read ? text.readBy(name) : text.sent] }));
|
|
165
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The AI's words read aloud (Stefano's idea, IDEE.md): the browser's own voices, a local one first, so the words stay on the computer where it can.
|
|
2
|
+
/** Reads the words aloud in the language given, cutting short what was being read. */
|
|
3
|
+
export function speak(words, language) {
|
|
4
|
+
if (typeof speechSynthesis === "undefined") {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const said = words.replace(/\*\*/g, "").trim();
|
|
8
|
+
speechSynthesis.cancel();
|
|
9
|
+
clearTimeout(pending);
|
|
10
|
+
if (said === "") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// Chrome drops words spoken right after a cancel, and lists its voices late the first time: the words wait a moment.
|
|
14
|
+
pending = setTimeout(() => {
|
|
15
|
+
const utterance = new SpeechSynthesisUtterance(said);
|
|
16
|
+
const voice = voiceFor(language);
|
|
17
|
+
utterance.lang = voice?.lang ?? language;
|
|
18
|
+
if (voice !== undefined) {
|
|
19
|
+
utterance.voice = voice;
|
|
20
|
+
}
|
|
21
|
+
speechSynthesis.resume();
|
|
22
|
+
speechSynthesis.speak(utterance);
|
|
23
|
+
}, 150);
|
|
24
|
+
}
|
|
25
|
+
/** Stops reading. */
|
|
26
|
+
export function hush() {
|
|
27
|
+
if (typeof speechSynthesis !== "undefined") {
|
|
28
|
+
clearTimeout(pending);
|
|
29
|
+
speechSynthesis.cancel();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let pending;
|
|
33
|
+
/** A voice for the language: a local one first, the browser's default among them first. */
|
|
34
|
+
function voiceFor(language) {
|
|
35
|
+
const voices = speechSynthesis.getVoices().filter((voice) => voice.lang.toLowerCase().startsWith(language.toLowerCase()));
|
|
36
|
+
const local = voices.filter((voice) => voice.localService);
|
|
37
|
+
return local.find((voice) => voice.default) ?? local[0] ?? voices[0];
|
|
38
|
+
}
|