chesspal 0.1.0 → 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 +45 -82
- package/dist/cli/chat-hook.js +1 -1
- package/dist/cli/command-line.d.ts +1 -2
- package/dist/cli/command-line.js +2 -3
- package/dist/commands/agents.d.ts +26 -0
- package/dist/commands/agents.js +72 -0
- package/dist/commands/ai-move.d.ts +5 -14
- package/dist/commands/ai-move.js +28 -35
- package/dist/commands/ai-say.d.ts +3 -1
- package/dist/commands/ai-say.js +24 -6
- package/dist/commands/analyse-game.d.ts +4 -0
- package/dist/commands/analyse-game.js +43 -16
- package/dist/commands/annotate-game.d.ts +4 -0
- package/dist/commands/annotate-game.js +65 -12
- package/dist/commands/answer-lines.d.ts +28 -0
- package/dist/commands/answer-lines.js +144 -0
- package/dist/commands/coach.d.ts +45 -0
- package/dist/commands/coach.js +344 -0
- package/dist/commands/context.d.ts +16 -5
- package/dist/commands/context.js +1 -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/drawings.d.ts +0 -2
- package/dist/commands/drawings.js +5 -3
- package/dist/commands/end-game.js +5 -2
- package/dist/commands/export-game.d.ts +1 -1
- package/dist/commands/export-game.js +4 -3
- package/dist/commands/game-summary.d.ts +21 -38
- package/dist/commands/game-summary.js +30 -167
- package/dist/commands/index.d.ts +15 -0
- package/dist/commands/index.js +47 -2
- package/dist/commands/init.d.ts +3 -3
- package/dist/commands/init.js +4 -4
- package/dist/commands/lead-input.d.ts +8 -0
- package/dist/commands/lead-input.js +21 -0
- package/dist/commands/look.js +4 -1
- package/dist/commands/new-game.d.ts +2 -3
- package/dist/commands/new-game.js +7 -4
- package/dist/commands/play-move.d.ts +8 -15
- package/dist/commands/play-move.js +5 -13
- package/dist/commands/player-move.d.ts +2 -6
- package/dist/commands/player-move.js +13 -6
- package/dist/commands/player-say.d.ts +1 -1
- package/dist/commands/player-say.js +1 -1
- package/dist/commands/ponder.d.ts +10 -0
- package/dist/commands/ponder.js +33 -21
- package/dist/commands/position.d.ts +1 -1
- package/dist/commands/position.js +3 -1
- package/dist/commands/profiles.d.ts +13 -4
- package/dist/commands/profiles.js +49 -15
- package/dist/commands/registry.d.ts +8 -3
- package/dist/commands/registry.js +12 -11
- package/dist/commands/say.d.ts +1 -1
- package/dist/commands/say.js +16 -16
- package/dist/commands/speech.d.ts +8 -0
- package/dist/commands/speech.js +20 -0
- package/dist/commands/status.d.ts +9 -3
- package/dist/commands/status.js +40 -15
- package/dist/commands/study-games.js +1 -1
- package/dist/commands/study.js +3 -19
- package/dist/core/facts.js +15 -15
- package/dist/core/game.d.ts +3 -1
- package/dist/core/game.js +8 -4
- package/dist/core/illegal.js +1 -1
- package/dist/core/index.d.ts +5 -4
- package/dist/core/index.js +5 -4
- package/dist/core/lead.d.ts +15 -0
- package/dist/core/lead.js +40 -0
- package/dist/core/levels.d.ts +1 -1
- package/dist/core/levels.js +1 -1
- package/dist/core/moods.d.ts +0 -2
- package/dist/core/moods.js +2 -2
- package/dist/core/openings.d.ts +0 -2
- package/dist/core/openings.js +1 -1
- package/dist/core/profile.js +1 -1
- package/dist/core/record.d.ts +1 -1
- package/dist/core/record.js +1 -1
- package/dist/core/review.d.ts +9 -3
- package/dist/core/review.js +29 -9
- package/dist/games/analysis.d.ts +1 -1
- package/dist/games/archive.d.ts +24 -3
- package/dist/games/archive.js +96 -22
- package/dist/games/coach.d.ts +57 -0
- package/dist/games/coach.js +144 -0
- package/dist/games/current-game.d.ts +2 -0
- package/dist/games/current-game.js +7 -1
- package/dist/games/files.js +1 -0
- package/dist/games/index.d.ts +4 -3
- package/dist/games/index.js +4 -3
- package/dist/games/talk.d.ts +1 -5
- package/dist/games/talk.js +14 -14
- package/dist/home/open-folder.js +1 -1
- package/dist/host-files/{ponder-agents.d.ts → agents.d.ts} +10 -2
- package/dist/host-files/agents.js +53 -0
- package/dist/host-files/host-files.d.ts +7 -2
- package/dist/host-files/host-files.js +27 -12
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +3 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -0
- package/dist/page/app/anchor.js +88 -0
- package/dist/page/{app.js → app/app.js} +112 -230
- 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/thought.js +10 -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/{explain.js → board/explain.js} +1 -1
- 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/{dialog.js → dialogs/dialog.js} +1 -1
- package/dist/page/dialogs/dialogs.css +1228 -0
- package/dist/page/{new-game-dialog.js → dialogs/new-game.js} +3 -3
- package/dist/page/{profile-dialogs.js → dialogs/profiles.js} +40 -11
- 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/{badges.js → game/badges.js} +1 -1
- package/dist/page/{browse.js → game/browse.js} +1 -1
- package/dist/page/game/card-fold.js +40 -0
- package/dist/page/game/column.js +51 -0
- package/dist/page/{ending.js → game/ending.js} +2 -2
- package/dist/page/game/game.css +577 -0
- package/dist/page/{move-list.js → game/move-list.js} +10 -6
- package/dist/page/{plies.js → game/plies.js} +30 -10
- 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/{folds.js → layout/folds.js} +1 -1
- package/dist/page/{header.js → layout/header.js} +5 -4
- 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 +2 -2
- package/dist/page/pieces/CREDITS.md +12 -29
- package/dist/page/pieces/capablanca-small.svg +20 -0
- package/dist/page/pieces/capablanca.svg +22 -0
- package/dist/page/pieces/nitida-small.svg +3 -2
- package/dist/page/pieces/nitida.svg +3 -2
- package/dist/page/{pieces.js → pieces/pieces.js} +2 -2
- package/dist/page/pieces/torretta-small.svg +15 -14
- package/dist/page/pieces/torretta.svg +17 -15
- package/dist/page/{player-card.js → players/card.js} +2 -6
- package/dist/page/{clock-time.js → players/clock-time.js} +2 -2
- package/dist/page/{profile.js → profile/profile.js} +9 -5
- package/dist/page/{settings.js → profile/settings.js} +5 -4
- package/dist/page/style.css +9 -4398
- package/dist/page/talk/talk.css +958 -0
- package/dist/page/{talk.js → talk/talk.js} +39 -46
- package/dist/page/text.js +137 -19
- package/dist/page/theme.css +292 -0
- package/dist/profiles/index.d.ts +1 -1
- package/dist/profiles/profiles.d.ts +15 -4
- package/dist/profiles/profiles.js +24 -7
- package/dist/server/bell.d.ts +10 -2
- package/dist/server/bell.js +28 -12
- package/dist/server/caller.d.ts +14 -3
- package/dist/server/caller.js +44 -11
- package/dist/server/mcp.d.ts +1 -2
- package/dist/server/mcp.js +42 -14
- 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/{http.d.ts → page/http.d.ts} +13 -0
- package/dist/server/{http.js → page/http.js} +11 -0
- package/dist/server/{page.d.ts → page/page.d.ts} +8 -13
- package/dist/server/page/page.js +229 -0
- package/dist/server/{profile-routes.d.ts → page/profiles.d.ts} +2 -2
- package/dist/server/{profile-routes.js → page/profiles.js} +47 -18
- package/instructions/analysis.md +4 -0
- package/instructions/analyst-agent.md +8 -0
- package/instructions/analyst.md +3 -7
- package/instructions/coach.md +11 -0
- package/instructions/opponent.md +4 -4
- package/package.json +1 -1
- package/dist/host-files/ponder-agents.js +0 -40
- package/dist/page/avatars.js +0 -16
- package/dist/page/board.js +0 -566
- package/dist/page/fold-tab.js +0 -9
- package/dist/page/game-column.js +0 -44
- package/dist/page/helps-view.js +0 -51
- package/dist/page/helps.js +0 -90
- package/dist/page/pieces/cburnett-small.svg +0 -20
- package/dist/page/pieces/cburnett.svg +0 -20
- package/dist/page/settings-panel.js +0 -32
- package/dist/page/studied.js +0 -14
- package/dist/page/study-dialog.js +0 -58
- package/dist/server/analyses.d.ts +0 -17
- package/dist/server/analyses.js +0 -46
- package/dist/server/page.js +0 -657
- /package/dist/page/{torretta.js → avatars/torretta.js} +0 -0
- /package/dist/page/{opening-book.js → game/opening-book.js} +0 -0
- /package/dist/page/{model-name.js → players/model-name.js} +0 -0
- /package/dist/page/{sounds.js → sounds/sounds.js} +0 -0
- /package/dist/page/{voice.js → talk/voice.js} +0 -0
|
@@ -1,35 +1,42 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
3
|
-
import { aiModelIn, annotatorOf, closingOf, Game, gameReviewOf, materialOf, moodAt, movesOnly, openingOf, readPgn, suggestedLevel, timeControlText, writePgn, } from "
|
|
4
|
-
import { PlayerPicture } from "
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
3
|
+
import { aiModelIn, annotatorOf, closingOf, Game, gameReviewOf, materialOf, moodAt, movesOnly, openingOf, readPgn, suggestedLevel, timeControlText, writePgn, } from "../../core/index.js";
|
|
4
|
+
import { PlayerPicture } from "../avatars/avatars.js";
|
|
5
|
+
import { Thinker, Thought } from "../avatars/thought.js";
|
|
6
|
+
import { Torretta } from "../avatars/torretta.js";
|
|
7
|
+
import { Board } from "../board/board.js";
|
|
8
|
+
import { explain } from "../board/explain.js";
|
|
9
|
+
import { piecesOf } from "../board/squares.js";
|
|
10
|
+
import { NewGameDialog } from "../dialogs/new-game.js";
|
|
11
|
+
import { NameButton, PictureButton, PictureDialog, ProfilesDialog, Wizard } from "../dialogs/profiles.js";
|
|
12
|
+
import { SettingsPanel } from "../dialogs/settings.js";
|
|
13
|
+
import { playerIn, studiedSides } from "../dialogs/studied.js";
|
|
14
|
+
import { StudyDialog } from "../dialogs/study.js";
|
|
15
|
+
import { BadgeCounts } from "../game/badges.js";
|
|
16
|
+
import { stepped } from "../game/browse.js";
|
|
17
|
+
import { GameColumn } from "../game/column.js";
|
|
18
|
+
import { howEnded, useDeclined, useJustEnded, useSawEnd } from "../game/ending.js";
|
|
19
|
+
import { Steps } from "../game/move-list.js";
|
|
20
|
+
import { useOpeningBook } from "../game/opening-book.js";
|
|
21
|
+
import { aiSpoke, aiWords, judgmentsOf, playerSpent, playerWords, pliesOf, withBook, withJudgments, withoutReviews } from "../game/plies.js";
|
|
22
|
+
import { BareButton, HelpsCard, HintLine, MapButton } from "../helps/helps.js";
|
|
23
|
+
import { helpsOf } from "../helps/view.js";
|
|
24
|
+
import { FoldTab } from "../layout/fold-tab.js";
|
|
25
|
+
import { nextFold, rememberFold, startFolds } from "../layout/folds.js";
|
|
26
|
+
import { Header } from "../layout/header.js";
|
|
27
|
+
import { standingsOf } from "../lead/view.js";
|
|
28
|
+
import { ChosenSet } from "../pieces/pieces.js";
|
|
29
|
+
import { ClockFace, PlayerCard } from "../players/card.js";
|
|
30
|
+
import { signature } from "../players/model-name.js";
|
|
31
|
+
import { keepSettings } from "../profile/profile.js";
|
|
32
|
+
import { languagesOf, saveSettings, settingsFrom, showSettings } from "../profile/settings.js";
|
|
33
|
+
import { endSoundOf, playSound, soundOf, wakeSounds } from "../sounds/sounds.js";
|
|
34
|
+
import { AiNote, AiWords, Compose, PointSquare, TalkFaces } from "../talk/talk.js";
|
|
35
|
+
import { textFor } from "../text.js";
|
|
36
|
+
import { anchorName, useAnchor } from "./anchor.js";
|
|
37
|
+
import { useBrowseKeys, useFoldKeys, useMapKey, useWheelSteps } from "./keys.js";
|
|
38
|
+
import { phaseOf, verdictOf } from "./phase.js";
|
|
39
|
+
import { post, sendMessage, sendMove, useServed } from "./served.js";
|
|
33
40
|
/**
|
|
34
41
|
* The page: the board of the game in the player's folder, where the player moves on their turn, the talk with the AI beside it, and the game's column; in the
|
|
35
42
|
* settings the player chose, `start` as this browser remembered them (D-56).
|
|
@@ -52,51 +59,16 @@ export function App({ start }) {
|
|
|
52
59
|
keepSettings(served.profile.id, next);
|
|
53
60
|
}
|
|
54
61
|
};
|
|
55
|
-
const [
|
|
56
|
-
const
|
|
57
|
-
const [offline, setOffline] = useState(false);
|
|
62
|
+
const [dialog, setDialog] = useState(undefined);
|
|
63
|
+
const { data: served, problem, offline, reload } = useServed(() => setDialog("new-game"));
|
|
58
64
|
const [selected, setSelected] = useState(undefined);
|
|
59
65
|
const [refusal, setRefusal] = useState(undefined);
|
|
60
|
-
// A pawn taken to the last rank waits for the player to choose what it becomes
|
|
66
|
+
// A pawn taken to the last rank waits for the player to choose what it becomes.
|
|
61
67
|
const [promoting, setPromoting] = useState(undefined);
|
|
62
68
|
const [flipped, setFlipped] = useState(false);
|
|
63
69
|
const [folds, setFolds] = useState(startFolds);
|
|
64
70
|
const [draft, setDraft] = useState("");
|
|
65
71
|
const [unsent, setUnsent] = useState(undefined);
|
|
66
|
-
const [dialog, setDialog] = useState(undefined);
|
|
67
|
-
// Reads the game again: when the server says it changed, and when a clock runs out on the page (T-37).
|
|
68
|
-
const reload = useRef(() => { });
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
let latest = 0;
|
|
71
|
-
const load = () => {
|
|
72
|
-
// Two reads in flight may answer out of order: only the last one asked counts.
|
|
73
|
-
const asked = ++latest;
|
|
74
|
-
fetch("/api/game")
|
|
75
|
-
.then((response) => response.json())
|
|
76
|
-
.then((body) => {
|
|
77
|
-
if (asked !== latest) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
setProblem(body.error);
|
|
81
|
-
if (body.error === undefined) {
|
|
82
|
-
setServed(body);
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
.catch((error) => setProblem(String(error)));
|
|
86
|
-
};
|
|
87
|
-
// The server says when the game changes, whoever changed it: the page reads it again, and as it connects, the first time or to a new chat's server.
|
|
88
|
-
// The server lives in the chat's MCP server: when the chat ends, after /clear in Claude's app too, the page waits for the next one and says so.
|
|
89
|
-
reload.current = load;
|
|
90
|
-
const events = new EventSource("/api/events");
|
|
91
|
-
events.onopen = () => {
|
|
92
|
-
setOffline(false);
|
|
93
|
-
load();
|
|
94
|
-
};
|
|
95
|
-
// The chat asks for a new game: the player chooses it in the New game window, which opens here.
|
|
96
|
-
events.onmessage = (event) => event.data === "new-game" ? setDialog("new-game") : load();
|
|
97
|
-
events.onerror = () => setOffline(events.readyState !== EventSource.OPEN);
|
|
98
|
-
return () => events.close();
|
|
99
|
-
}, []);
|
|
100
72
|
// The profile in use brings its settings, as it comes and each time another comes in its place; one without any takes this browser's.
|
|
101
73
|
const profileId = served?.profile.id;
|
|
102
74
|
useEffect(() => {
|
|
@@ -122,44 +94,65 @@ export function App({ start }) {
|
|
|
122
94
|
// Between games the board shows the table set for the next (D-77): before the first, and on a game over the page did not see end; one it saw end keeps
|
|
123
95
|
// its final position until the next starts. The talk keeps the game's words all the same.
|
|
124
96
|
const over = isOver(resultOf(replayed));
|
|
125
|
-
const going = studied === undefined && game !== undefined && game !== null && !over;
|
|
126
|
-
const between = studied === undefined && game !== undefined && !going;
|
|
127
97
|
const sawEnd = useSawEnd(studied !== undefined || game === undefined || game === null ? undefined : over ? "over" : "going");
|
|
128
|
-
const
|
|
98
|
+
const phase = phaseOf(studied !== undefined, game, over, sawEnd);
|
|
99
|
+
const going = phase === "going";
|
|
100
|
+
const between = phase === "over" || phase === "idle";
|
|
101
|
+
const idle = phase === "idle";
|
|
129
102
|
const tableSet = useMemo(() => new Game(), []);
|
|
130
103
|
const live = idle ? tableSet : replayed;
|
|
131
104
|
const material = useMemo(() => materialOf(live.position.fen), [live]);
|
|
132
105
|
const pieces = useMemo(() => piecesOf(live.position.fen), [live]);
|
|
133
106
|
// While the game goes on, the book's first moves carry its badge, when the player wants it, as the AI's review gives it after (D-69).
|
|
134
107
|
const inBook = studied === undefined && settings.book ? game?.book ?? 0 : 0;
|
|
135
|
-
// The
|
|
108
|
+
// The judgments of the player's moves, and the AI's drawings, show during a game only with the helps on, always once it is over (D-78); during it the
|
|
109
|
+
// coach's come from its own file, never in the PGN the opponent may read (D-88).
|
|
136
110
|
const judged = !going || settings.helps;
|
|
137
|
-
const
|
|
111
|
+
const coached = going ? game?.coach : undefined;
|
|
112
|
+
const plies = useMemo(() => {
|
|
113
|
+
const read = pliesOf(replayed.record);
|
|
114
|
+
return withBook(!judged ? withoutReviews(read) : coached === undefined ? read : withJudgments(read, coached), inBook);
|
|
115
|
+
}, [replayed, inBook, judged, coached]);
|
|
138
116
|
const book = useOpeningBook(live.record.moves.length > 0);
|
|
139
117
|
const opening = useMemo(() => book === undefined ? undefined : openingOf(live.record, book), [live, book]);
|
|
140
|
-
// The player's move shows at once, before the server has it: the game it sends next takes its place
|
|
118
|
+
// The player's move shows at once, before the server has it: the game it sends next takes its place.
|
|
141
119
|
const [pending, setPending] = useState(undefined);
|
|
142
|
-
// The
|
|
120
|
+
// The coach's hint for the position on the board, none once the player has moved, folded until they open it; a new hint comes folded (Zoe).
|
|
121
|
+
const hint = pending === undefined && coached?.hint?.position === replayed.record.moves.length ? coached.hint : undefined;
|
|
122
|
+
const hintKey = hint === undefined ? undefined : `${hint.position} ${hint.text}`;
|
|
123
|
+
const [openHint, setOpenHint] = useState(undefined);
|
|
124
|
+
const hintOpen = hintKey !== undefined && openHint === hintKey;
|
|
125
|
+
const openTheHint = (open) => setOpenHint(open ? hintKey : undefined);
|
|
126
|
+
// The player may look back through the moves played: `viewing` how many are shown, undefined for the game as it is; a new move brings it back.
|
|
143
127
|
const [viewing, setViewing] = useState(undefined);
|
|
144
|
-
// The squares the player points at in the AI's words or the helps' card, lit on the board
|
|
128
|
+
// The squares the player points at in the AI's words or the helps' card, lit on the board; the control map while it is held (D-78).
|
|
145
129
|
const [pointed, setPointed] = useState(noSquares);
|
|
146
130
|
const pointSquare = useMemo(() => (square) => setPointed(square === undefined ? noSquares : [square]), []);
|
|
147
131
|
const [mapping, setMapping] = useState(false);
|
|
132
|
+
// The board without its drawings while their button is held (Stefano).
|
|
133
|
+
const [bare, setBare] = useState(false);
|
|
148
134
|
const past = useMemo(() => viewing === undefined ? undefined : new Game({ tags: live.record.tags, moves: live.record.moves.slice(0, viewing) }), [live, viewing]);
|
|
149
135
|
const next = useMemo(() => viewing === undefined ? undefined : new Game({ tags: live.record.tags, moves: live.record.moves.slice(0, viewing + 1) }).lastMove, [live, viewing]);
|
|
150
136
|
const shown = useMemo(() => past ?? (pending === undefined ? live : withMove(live, pending)), [live, pending, past]);
|
|
151
|
-
// A square lit from the AI's words goes off with the position: a touch keeps it lit until then, a mouse until it leaves the word
|
|
137
|
+
// A square lit from the AI's words goes off with the position: a touch keeps it lit until then, a mouse until it leaves the word.
|
|
152
138
|
useEffect(() => setPointed(noSquares), [shown]);
|
|
153
|
-
// With the helps on, the player sees all of Chesspal's facts, whatever the AI's level (D-79).
|
|
154
|
-
const
|
|
139
|
+
// With the helps on, the player sees all of Chesspal's facts, whatever the AI's level (D-79), in a game and in the analysis of one (Stefano).
|
|
140
|
+
const analysing = studied !== undefined;
|
|
141
|
+
// Seen from the player's side (D-91): theirs in a game, in analysis the side the game names theirs, none when it names neither.
|
|
142
|
+
const you = analysing ? playerIn(studiedSides(live.record.tags, served?.profile.name ?? "", name, text.sides, served?.profile.accounts)) : game?.ai === undefined ? undefined : game.ai === "white" ? "black" : "white";
|
|
143
|
+
const helps = useMemo(() => (going || analysing) && settings.helps ? helpsOf(shown, you) : undefined, [shown, going, analysing, settings.helps, you]);
|
|
144
|
+
// Who the AI saw ahead, the coach's with the helps on in a game and the analyst's in analysis, for the bar and the chart (D-90).
|
|
145
|
+
const drawnOnBoard = live.outcome.over && live.outcome.result === "1/2-1/2";
|
|
146
|
+
const standings = useMemo(() => (going && settings.helps) || analysing ? standingsOf(plies, drawnOnBoard) : undefined, [plies, drawnOnBoard, going, analysing, settings.helps]);
|
|
155
147
|
useMapKey(helps !== undefined, setMapping);
|
|
156
|
-
// A
|
|
157
|
-
//
|
|
148
|
+
// A new move or a new game ends what the player was doing on the one before: the move sent, the look back, the choice of a promotion. The same game
|
|
149
|
+
// sent again, for the talk, the coach's words or the settings, leaves them where they are (Zoe); so does a review written on the game in analysis.
|
|
150
|
+
const gameKey = studied !== undefined ? studied.name : game === undefined || game === null ? game : `${game.started} ${game.pgn}`;
|
|
158
151
|
useEffect(() => {
|
|
159
152
|
setPending(undefined);
|
|
160
153
|
setViewing(undefined);
|
|
161
154
|
setPromoting(undefined);
|
|
162
|
-
}, [
|
|
155
|
+
}, [gameKey]);
|
|
163
156
|
const played = studied === undefined ? game : null;
|
|
164
157
|
useGameSounds(played, replayed, sound);
|
|
165
158
|
// Before the first game the AI speaks in the talk alone (D-77).
|
|
@@ -171,6 +164,10 @@ export function App({ start }) {
|
|
|
171
164
|
if (next !== "open" && document.getElementById(column)?.contains(document.activeElement) === true) {
|
|
172
165
|
document.getElementById(`fold-${column}`)?.focus();
|
|
173
166
|
}
|
|
167
|
+
// The hint folds away with the game's column, so that its drawings never stay on the board without its words (Zoe).
|
|
168
|
+
if (column === "game" && next !== "open") {
|
|
169
|
+
setOpenHint(undefined);
|
|
170
|
+
}
|
|
174
171
|
setFolds({ ...folds, [column]: next });
|
|
175
172
|
rememberFold(column, next);
|
|
176
173
|
};
|
|
@@ -191,6 +188,10 @@ export function App({ start }) {
|
|
|
191
188
|
const step = (to) => goTo(stepped(to, at, total));
|
|
192
189
|
useBrowseKeys(step, () => setFlipped(!flipped));
|
|
193
190
|
const wheel = useWheelSteps(step);
|
|
191
|
+
// The move shown stays in the page's address, for a reload and a link to it; after the reset on a new game, which would undo the move it goes to. A
|
|
192
|
+
// game a link opens is there to look at: the analyst starts on the player's yes alone (D-89; Zoe and Federico).
|
|
193
|
+
const playedName = game === undefined || game === null ? undefined : anchorName(game.name);
|
|
194
|
+
useAnchor({ ready: served !== undefined, shown: studied !== undefined ? anchorName(studied.name) : idle ? undefined : playedName, played: playedName, going }, viewing, (plies) => goTo(plies ?? total), async (wanted) => await post("/api/study", { game: wanted, look: true }, text, name) === undefined);
|
|
194
195
|
// The header's theme switch picks the other of the two the page shows, leaving the system's.
|
|
195
196
|
const switchTheme = () => change({ ...settings, theme: document.documentElement.dataset.theme === "dark" ? "light" : "dark" });
|
|
196
197
|
// An action of the player's on the game: the game the server sends next shows it; a refusal shows on the board.
|
|
@@ -217,21 +218,10 @@ export function App({ start }) {
|
|
|
217
218
|
}
|
|
218
219
|
},
|
|
219
220
|
};
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
: idle
|
|
223
|
-
? text.noGameNow
|
|
224
|
-
: ended
|
|
225
|
-
? text.verdict(how, winner)
|
|
226
|
-
: offer === "ai"
|
|
227
|
-
? text.offers
|
|
228
|
-
: offer === "player"
|
|
229
|
-
? text.drawOffered
|
|
230
|
-
: declined
|
|
231
|
-
? text.declines
|
|
232
|
-
: undefined, ...(!ended && (offer === "ai" || declined) ? { who: name } : {}), actions: actions, onNewGame: served === undefined ? undefined : () => setDialog("new-game"),
|
|
221
|
+
const { kind, verdict } = verdictOf(phase, how, winner, offer, declined, text);
|
|
222
|
+
const header = (_jsx(Header, { text: text, kind: kind, verdict: verdict, ...(!ended && (offer === "ai" || declined) ? { who: name } : {}), actions: actions, onNewGame: served === undefined ? undefined : () => setDialog("new-game"),
|
|
233
223
|
// Between games the new one stands out in the player's card (D-77): in the header only when the talk, and the card with it, is folded away.
|
|
234
|
-
standOut: studied !== undefined || (between && folds.talk !== "open"), onStudy: served === undefined || going ? undefined : () => setDialog("study"), flipped: flipped, onFlip: () => setFlipped(!flipped), sound: sound, onSound: () => change({ ...settings, sound: !sound }), onTheme: switchTheme, onSettings: () => setSetting(true) }));
|
|
224
|
+
standOut: studied !== undefined || (between && folds.talk !== "open"), onStudy: served === undefined || going ? undefined : () => setDialog("study"), flipped: flipped, onFlip: () => setFlipped(!flipped), sound: sound, onSound: () => change({ ...settings, sound: !sound }), onTheme: switchTheme, onSettings: () => setSetting(true), ...(served === undefined ? {} : { profile: served.profile, onProfile: () => setDialog("profiles") }) }));
|
|
235
225
|
const panel = _jsx(SettingsPanel, { open: setting, settings: settings, onChange: change, onClose: () => setSetting(false), text: text });
|
|
236
226
|
if (problem !== undefined) {
|
|
237
227
|
return _jsx("p", { class: "problem", children: problem });
|
|
@@ -241,12 +231,12 @@ export function App({ start }) {
|
|
|
241
231
|
}
|
|
242
232
|
// At the first start the page asks who plays (D-23), to complete the profile Chesspal made (D-71): the game comes after.
|
|
243
233
|
if (served.profile.new === true) {
|
|
244
|
-
return _jsx(Wizard, { text: text, hasGame: served.game !== null, account: served.profile.name, dataFolder: served.dataFolder, onMade:
|
|
234
|
+
return _jsx(Wizard, { text: text, hasGame: served.game !== null, account: served.profile.name, dataFolder: served.dataFolder, onMade: reload });
|
|
245
235
|
}
|
|
246
236
|
const profile = served.profile;
|
|
247
237
|
const picture = _jsx(PlayerPicture, { profile: profile });
|
|
248
238
|
// In analysis the cards name the players of the game, the player's side at the bottom when they played it (D-69).
|
|
249
|
-
const sides = studied === undefined ? undefined : studiedSides(live.record.tags, profile.name, name, text.sides);
|
|
239
|
+
const sides = studied === undefined ? undefined : studiedSides(live.record.tags, profile.name, name, text.sides, profile.accounts);
|
|
250
240
|
const player = sides?.bottom ?? (game === null || game.ai === "black" ? "white" : "black");
|
|
251
241
|
const ai = player === "white" ? "black" : "white";
|
|
252
242
|
// The Torretta's face on the move shown: the mood the AI gave, while it lasts (D-70); in analysis, of the side the AI played.
|
|
@@ -254,9 +244,11 @@ export function App({ start }) {
|
|
|
254
244
|
const playerToMove = going && live.position.turn === player;
|
|
255
245
|
const ahead = (side) => side === "white" ? material.balance : -material.balance;
|
|
256
246
|
const reviewed = judged || at <= inBook ? plies[at - 1] : undefined;
|
|
257
|
-
// With the helps on, the
|
|
258
|
-
const
|
|
247
|
+
// With the helps on, the coach's judgments of the player's last move, once the AI has answered it, and of one a held note let go only now (D-78, D-88).
|
|
248
|
+
const judgments = going && settings.helps ? judgmentsOf(plies, player, coached ?? noCoach) : [];
|
|
259
249
|
const review = reviewed?.review;
|
|
250
|
+
// An open hint draws on the board as it is, in place of the last move's drawings.
|
|
251
|
+
const drawn = hintOpen && hint !== undefined && viewing === undefined && (hint.arrows !== undefined || hint.squares !== undefined) ? hint : review;
|
|
260
252
|
const summary = studied === undefined ? undefined : gameReviewOf(live.record);
|
|
261
253
|
const closing = studied === undefined ? undefined : closingOf(live.record);
|
|
262
254
|
const annotator = studied === undefined ? undefined : annotatorOf(live.record);
|
|
@@ -268,9 +260,12 @@ export function App({ start }) {
|
|
|
268
260
|
const clock = going ? game?.clock : undefined;
|
|
269
261
|
// No chat hears the player: the page says where to wake it, and the AI's clock waits, as the server holds it.
|
|
270
262
|
const asleep = served.listening === false && !offline;
|
|
263
|
+
// What the AI thinks on, a cloud at the Torretta's corner (Zoe): only in a game going on, and never while no chat hears the player, which the note says.
|
|
264
|
+
const thinking = going && !asleep && !offline && studied === undefined ? served.thinking : undefined;
|
|
265
|
+
const thinker = (size) => (_jsx(Thinker, { thinking: thinking, words: text.thinking, children: _jsx(Torretta, { size: size, mood: mood }) }));
|
|
271
266
|
// The clocks wait for the player from the game's start: when the AI moves first, a button gives it the start; else the player's first move does (Stefano).
|
|
272
267
|
const clockWaits = clock?.waiting === true && !ended && studied === undefined && viewing === undefined && live.position.turn === ai;
|
|
273
|
-
const clockOf = (side) => clock === undefined ? undefined : (_jsx(ClockFace, { clock: clock, side: side, toMove: !ended && live.position.turn === side, held: pending !== undefined || (asleep && side === ai), onFlag:
|
|
268
|
+
const clockOf = (side) => clock === undefined ? undefined : (_jsx(ClockFace, { clock: clock, side: side, toMove: !ended && live.position.turn === side, held: pending !== undefined || (asleep && side === ai), onFlag: reload, text: text }));
|
|
274
269
|
// Where to wake the chat, under the AI's card (over the board when the talk is folded); the button that starts the clocks, under the player's (Zoe).
|
|
275
270
|
const asleepNote = asleep ? _jsx("p", { class: "asleep", role: "status", children: text.asleep(name) }) : null;
|
|
276
271
|
const startClocks = clockWaits
|
|
@@ -337,7 +332,7 @@ export function App({ start }) {
|
|
|
337
332
|
clearSent(refused?.text, words);
|
|
338
333
|
});
|
|
339
334
|
};
|
|
340
|
-
// The new game starts from the choices of the game before; the first, from the level that suits the player
|
|
335
|
+
// The new game starts from the choices of the game before; the first, from the level that suits the player.
|
|
341
336
|
const level = suggestedLevel(played === null ? undefined : replayed.record, profile.level);
|
|
342
337
|
const lastControl = game?.clock?.control;
|
|
343
338
|
const lastClock = lastControl === undefined ? undefined : timeControlText(lastControl);
|
|
@@ -349,8 +344,10 @@ export function App({ start }) {
|
|
|
349
344
|
// In analysis the AI's note stays under the card on top and its word on the game over the one below, however the board is turned (D-69).
|
|
350
345
|
const top = flipped ? player : ai;
|
|
351
346
|
const bottom = flipped ? ai : player;
|
|
352
|
-
const studiedCard = (side, place) => sides === undefined ? null : (_jsx(PlayerCard, { place: place,
|
|
353
|
-
|
|
347
|
+
const studiedCard = (side, place) => sides === undefined ? null : (_jsx(PlayerCard, { place: place,
|
|
348
|
+
// The player changes their picture in analysis too, from their own side's card.
|
|
349
|
+
picture: sides[side].who === "player" ? _jsx(PictureButton, { profile: profile, onOpen: () => setDialog("picture"), text: text }) : pictureOf(sides[side], profile, 48, mood), name: sides[side].name, signed: sides[side].who === "ai" && playedBy !== undefined ? signature(playedBy.model, playedBy.effort) : undefined, side: side, taken: material.taken[side], ahead: ahead(side), badges: _jsx(BadgeCounts, { plies: plies, side: side, names: text.badges }), text: text }));
|
|
350
|
+
// A game brought to analyse: the page shows it once the server has it in analysis; a refusal stays in the dialog.
|
|
354
351
|
const studyGame = async (studied) => {
|
|
355
352
|
const refused = await post("/api/study", studied, text, name);
|
|
356
353
|
if (refused === undefined) {
|
|
@@ -358,11 +355,11 @@ export function App({ start }) {
|
|
|
358
355
|
}
|
|
359
356
|
return refused?.text;
|
|
360
357
|
};
|
|
361
|
-
return (_jsx(ChosenSet.Provider, { value: settings.set, children: _jsxs(PointSquare.Provider, { value: pointSquare, children: [panel, _jsx(PictureDialog, { open: dialog === "picture", profile: profile, onClose: () => setDialog(undefined), text: text }), _jsx(ProfilesDialog, { open: dialog === "profiles", profiles: served.profiles, active: profile,
|
|
362
|
-
? (_jsx(TalkFaces, { name: name, aiPicture:
|
|
363
|
-
: (_jsx(TalkFaces, { name: sides[ai].name, aiPicture: pictureOf(sides[ai], profile, 40, mood), you: sides[player].name, picture: pictureOf(sides[player], profile, 40, mood), ai: ai, player: player, aiMoves: false, playerMoves: false, unread: asleep, text: text })), _jsx(FoldTab, { column: "talk", folded: folds.talk === "folded", label: text.talk
|
|
364
|
-
? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[top].name, children: [studiedCard(top, "ai"), asleepNote, _jsx(AiNote, { ...(reviewed === undefined ? {} : { ply: reviewed }), ...(shownOpening === undefined ? {} : { opening: shownOpening }), ...(at === total && closing !== undefined ? { closing } : {}), ...(at === 0 && summary !== undefined ? { summary } : {}), name: name, by: annotator, mood: mood, ...(settings.voice ? { voice: text.language } : {}), text: text })] }), _jsx("section", { class: "you-block", "aria-label": sides[bottom].name, children: studiedCard(bottom, "you") })] }))
|
|
365
|
-
: (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": name, children: [_jsx(PlayerCard, { place: "ai", picture:
|
|
358
|
+
return (_jsx(ChosenSet.Provider, { value: settings.set, children: _jsxs(PointSquare.Provider, { value: pointSquare, children: [panel, _jsx(PictureDialog, { open: dialog === "picture", profile: profile, onClose: () => setDialog(undefined), text: text }), _jsx(ProfilesDialog, { open: dialog === "profiles", profiles: served.profiles, active: profile, locked: going ? text.profile.whilePlaying : undefined, dataFolder: served.dataFolder, onClose: () => setDialog(undefined), text: text }), _jsx(NewGameDialog, { open: dialog === "new-game", choice: choice, going: going, onStart: startNewGame, onClose: () => setDialog(undefined), text: text }), _jsx(StudyDialog, { open: dialog === "study", onStudy: studyGame, onClose: () => setDialog(undefined), you: profile.name, accounts: profile.accounts, ai: name, text: text }), offline ? _jsx("p", { class: "offline", role: "alert", children: text.offline(name) }) : null, served.outdated === true ? _jsx("p", { class: "outdated", role: "status", children: text.outdated(name) }) : null, _jsxs("main", { class: `app${flipped ? " flipped" : ""}${folds.talk === "open" ? "" : ` talk-${folds.talk}`}${folds.game === "folded" ? " folded" : ""}`, inert: offline, children: [header, sides === undefined
|
|
359
|
+
? (_jsx(TalkFaces, { name: name, aiPicture: thinker(40), you: profile.name, picture: picture, ...(idle ? {} : { ai, player }), aiMoves: going && !playerToMove, playerMoves: playerToMove, unread: unread || asleep, text: text }))
|
|
360
|
+
: (_jsx(TalkFaces, { name: sides[ai].name, aiPicture: pictureOf(sides[ai], profile, 40, mood), you: sides[player].name, picture: pictureOf(sides[player], profile, 40, mood), ai: ai, player: player, aiMoves: false, playerMoves: false, unread: asleep, text: text })), _jsx(FoldTab, { column: "talk", folded: folds.talk === "folded", label: thinking === undefined || folds.talk !== "folded" ? text.talk : `${text.talk}: ${text.thinking[thinking]}`, unread: (unread || asleep) && folds.talk === "folded", ...(thinking === undefined || folds.talk !== "folded" ? {} : { mark: _jsx(Thought, { thinking: thinking, words: text.thinking }) }), onFold: fold }), _jsx(FoldTab, { column: "game", folded: folds.game === "folded", label: text.game, onFold: fold }), _jsx("span", { class: "visually-hidden", "aria-live": "polite", children: unread ? text.wrote(name) : "" }), _jsx("div", { class: sides === undefined ? "talk" : "talk studied", id: "talk", children: sides !== undefined
|
|
361
|
+
? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[top].name, children: [studiedCard(top, "ai"), asleepNote, _jsx(AiNote, { ...(reviewed === undefined ? {} : { ply: reviewed }), ...(shownOpening === undefined ? {} : { opening: shownOpening }), ...(at === total && closing !== undefined ? { closing } : {}), ...(at === 0 && summary !== undefined ? { summary } : {}), ...(summary === undefined ? { reviewing: true } : {}), name: name, by: annotator, mood: mood, ...(settings.voice ? { voice: text.language } : {}), text: text })] }), _jsx("section", { class: "you-block", "aria-label": sides[bottom].name, children: studiedCard(bottom, "you") })] }))
|
|
362
|
+
: (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": name, children: [_jsx(PlayerCard, { place: "ai", picture: thinker(48), name: name, signed: signed, side: idle ? undefined : ai, ...(idle ? {} : { level: text.start.levels[level] }), taken: material.taken[ai], ahead: ahead(ai), clock: clockOf(ai), text: text }), asleepNote, _jsx(AiWords, { words: aiWords(plies, ai, talk), newestFirst: !flipped, ...(settings.voice ? { voice: text.language } : {}) })] }), _jsxs("section", { class: "you-block", "aria-label": profile.name, children: [_jsx(Compose, { lines: said, newestFirst: flipped,
|
|
366
363
|
// Between games, and before the first, the player writes freely, a line at a time, as the AI does (D-74, D-77).
|
|
367
364
|
spent: going ? playerSpent(plies, ai, talk) : 0, draft: draft, onDraft: (words) => {
|
|
368
365
|
setDraft(words);
|
|
@@ -371,9 +368,9 @@ export function App({ start }) {
|
|
|
371
368
|
// Between games the board goes off (D-77): a game the page saw end once its effects are over, its final position less off than the table set.
|
|
372
369
|
class: between && !justEnded ? "board-wrap off" : "board-wrap", onWheel: wheel,
|
|
373
370
|
// A press on the board while the player looks back brings the game as it is: the next one plays.
|
|
374
|
-
onPointerDownCapture: viewing === undefined || studied !== undefined ? undefined : (event) => event.button === 0 && goTo(total), children: [_jsx(Board, { fen: shown.position.fen, bottom: flipped ? ai : player, lastMove: shown.lastMove, pointed: pointed, ...(helps === undefined ? {} : { helps }), ...(mapping && helps !== undefined ? { control: helps.strategy.control } : {}), ...(review?.badge === undefined ? {} : { badge: { badge: review.badge, name: text.badges[review.badge] } }), ...(
|
|
371
|
+
onPointerDownCapture: viewing === undefined || studied !== undefined ? undefined : (event) => event.button === 0 && goTo(total), children: [_jsx(Board, { fen: shown.position.fen, bottom: flipped ? ai : player, lastMove: shown.lastMove, pointed: pointed, ...(helps === undefined ? {} : { helps }), ...(mapping && helps !== undefined ? { control: helps.strategy.control } : {}), bare: bare && helps !== undefined, ...(review?.badge === undefined ? {} : { badge: { badge: review.badge, name: text.badges[review.badge] } }), ...(drawn === undefined ? {} : { drawn }), nextMove: next, jumped: jumped, check: shown.position.check, end: ended && viewing === undefined && pending === undefined ? { how, winner, player, fresh: justEnded } : undefined, player: playerToMove && pending === undefined && viewing === undefined ? player : undefined, selected: selected, targets: selected === undefined || viewing !== undefined ? [] : live.legalMoves(selected), dots: settings.dots, names: settings.squareNames, notice: viewing === undefined ? refusal : undefined, promotion: promoting === undefined || viewing !== undefined || !playerToMove ? undefined : { ...promoting, side: player }, onPromote: (piece) => piece === undefined ? setPromoting(undefined) : play(promoting.from, promoting.to, piece), onSelect: select, onMove: play, onRefuse: refuse, text: text }), folds.talk !== "open" && (asleep || clockWaits)
|
|
375
372
|
? (_jsxs("div", { class: "over-board", children: [asleep ? _jsx("p", { class: "asleep", role: "status", children: text.asleep(name) }) : null, startClocks] }))
|
|
376
|
-
: null] }), studied === undefined ? null : (_jsx("div", { class: "board-steps", children: _jsx(Steps, { shown: at, total: total, onStep: step, text: text }) })), helps === undefined ? null : (
|
|
373
|
+
: null] }), studied === undefined ? null : (_jsx("div", { class: "board-steps", children: _jsx(Steps, { shown: at, total: total, onStep: step, text: text }) })), helps === undefined ? null : (_jsxs("div", { class: "board-map", children: [_jsx(BareButton, { bare: bare, onBare: setBare, text: text }), _jsx(MapButton, { mapping: mapping, onMap: setMapping, text: text }), hint === undefined || viewing !== undefined ? null : _jsx(HintLine, { hint: hint, open: hintOpen, onOpen: openTheHint, text: text })] })), _jsx(GameColumn, { plies: idle ? [] : plies, result: ended ? result : undefined, how: how, lit: justEnded, started: idle ? undefined : played?.started, move: live.position.moveNumber, toMove: live.position.turn, opening: opening, helps: helps === undefined ? undefined : (_jsx(HelpsCard, { helps: helps, judged: judgments, hint: viewing === undefined ? hint : undefined, hintOpen: hintOpen, onHint: openTheHint, onPoint: setPointed, onMap: setMapping, mapping: mapping, onBare: setBare, bare: bare, text: text })), ...(standings === undefined ? {} : { standings }), ...(studied === undefined ? {} : { analysis: summary === undefined ? { by: name } : { by: name, summary } }), shownPlies: at, onShow: goTo, onStep: step, ...(studied === undefined ? {} : { onDownload: () => download(studied.pgn, pgnName(live.record.tags, "analysis")) }), ...(total === 0 ? {} : { onSaveMoves: () => download(writePgn(movesOnly(live.record)), pgnName(live.record.tags)) }), ...(between && game !== null ? { onAnalyse: () => void studyGame({ last: true }) } : {}), text: text })] })] }) }));
|
|
377
374
|
}
|
|
378
375
|
/** The picture of a player of the game in analysis: the player's own, the AI's Torretta with its mood, or the plain face of someone else. */
|
|
379
376
|
function pictureOf({ who }, profile, size, mood) {
|
|
@@ -381,7 +378,7 @@ function pictureOf({ who }, profile, size, mood) {
|
|
|
381
378
|
}
|
|
382
379
|
/**
|
|
383
380
|
* Plays the sound of each move the game gains while the page watches it, the AI's and the player's, of its end without a move and of a draw offered:
|
|
384
|
-
* none for a game loaded or begun again
|
|
381
|
+
* none for a game loaded or begun again.
|
|
385
382
|
*/
|
|
386
383
|
function useGameSounds(game, live, on) {
|
|
387
384
|
const seen = useRef(undefined);
|
|
@@ -431,128 +428,13 @@ function useUnread(spoke, folded) {
|
|
|
431
428
|
}, [spoke, folded]);
|
|
432
429
|
return unread;
|
|
433
430
|
}
|
|
434
|
-
/**
|
|
435
|
-
* `[` and `]` fold the talk and the game, as in editors with two side bars; never while the player types or drags a piece, nor in one column, nor the game
|
|
436
|
-
* under the board.
|
|
437
|
-
*/
|
|
438
|
-
function useFoldKeys(fold) {
|
|
439
|
-
useEffect(() => {
|
|
440
|
-
const press = (event) => {
|
|
441
|
-
if (!forThePage(event) || oneColumn()) {
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
const column = Object.keys(foldKeys).find((key) => foldKeys[key] === event.key);
|
|
445
|
-
if (column !== undefined && !(column === "game" && gameUnderBoard())) {
|
|
446
|
-
event.preventDefault();
|
|
447
|
-
fold(column);
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
window.addEventListener("keydown", press);
|
|
451
|
-
return () => window.removeEventListener("keydown", press);
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
/** M held shows the control map while the helps are on (D-78); it goes off when M is let go or the page loses the focus. */
|
|
455
|
-
function useMapKey(helping, map) {
|
|
456
|
-
useEffect(() => {
|
|
457
|
-
if (!helping) {
|
|
458
|
-
map(false);
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
const down = (event) => {
|
|
462
|
-
if (forThePage(event) && event.key.toLowerCase() === "m") {
|
|
463
|
-
event.preventDefault();
|
|
464
|
-
map(true);
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
const up = (event) => {
|
|
468
|
-
if (event.key.toLowerCase() === "m") {
|
|
469
|
-
map(false);
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
const away = () => map(false);
|
|
473
|
-
window.addEventListener("keydown", down);
|
|
474
|
-
window.addEventListener("keyup", up);
|
|
475
|
-
window.addEventListener("blur", away);
|
|
476
|
-
return () => {
|
|
477
|
-
window.removeEventListener("keydown", down);
|
|
478
|
-
window.removeEventListener("keyup", up);
|
|
479
|
-
window.removeEventListener("blur", away);
|
|
480
|
-
};
|
|
481
|
-
}, [helping]);
|
|
482
|
-
}
|
|
483
|
-
/** The keys of Lichess and chess.com: the arrows, Home and End step through the moves (T-88), f and x turn the board round. */
|
|
484
|
-
function useBrowseKeys(step, flip) {
|
|
485
|
-
useEffect(() => {
|
|
486
|
-
const press = (event) => {
|
|
487
|
-
const to = stepKeys[event.key];
|
|
488
|
-
const flips = flipKeys.includes(event.key.toLowerCase());
|
|
489
|
-
if (!forThePage(event) || (to === undefined && !flips)) {
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
event.preventDefault();
|
|
493
|
-
if (to === undefined) {
|
|
494
|
-
flip();
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
step(to);
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
window.addEventListener("keydown", press);
|
|
501
|
-
return () => window.removeEventListener("keydown", press);
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
/** The mouse wheel over the board steps through the moves, as on Lichess: down forward, a step a notch, however finely a touchpad turns it. */
|
|
505
|
-
function useWheelSteps(step) {
|
|
506
|
-
const turned = useRef(0);
|
|
507
|
-
return (event) => {
|
|
508
|
-
event.preventDefault();
|
|
509
|
-
turned.current += event.deltaMode === WheelEvent.DOM_DELTA_PIXEL ? event.deltaY : event.deltaY * notch;
|
|
510
|
-
while (Math.abs(turned.current) >= notch) {
|
|
511
|
-
step(turned.current > 0 ? "forward" : "back");
|
|
512
|
-
turned.current -= Math.sign(turned.current) * notch;
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
// How far a wheel turns in one notch, in pixels.
|
|
517
|
-
const notch = 100;
|
|
518
431
|
const noSquares = [];
|
|
519
|
-
|
|
520
|
-
function forThePage(event) {
|
|
521
|
-
const typing = event.target.closest("input, textarea, select, [contenteditable], dialog") !== null;
|
|
522
|
-
return !(event.defaultPrevented || event.ctrlKey || event.altKey || event.metaKey || typing || document.documentElement.classList.contains("dragging"));
|
|
523
|
-
}
|
|
524
|
-
/** Sends the player's move to the server, which plays it as the chat would, with the words written for the AI: the reason it was refused, or undefined. */
|
|
525
|
-
function sendMove(move, words, text, name) {
|
|
526
|
-
const message = words.trim();
|
|
527
|
-
return post("/api/move", message === "" ? { move } : { move, message }, text, name);
|
|
528
|
-
}
|
|
529
|
-
/** Sends the player's words alone: the reason they were refused, or undefined. */
|
|
530
|
-
function sendMessage(words, text, name) {
|
|
531
|
-
return post("/api/message", { text: words.trim() }, text, name);
|
|
532
|
-
}
|
|
432
|
+
const noCoach = { judgments: [] };
|
|
533
433
|
/** Whether the words are a command for Claude's app, such as /clear, rather than words for the AI. */
|
|
534
434
|
function isCommand(words) {
|
|
535
435
|
return /^\s*\/[a-z]/i.test(words);
|
|
536
436
|
}
|
|
537
|
-
/**
|
|
538
|
-
* Sends a request to the server: the reason it was refused, in the player's language when the page knows the refusal (T-11, D-57), with the host's name where
|
|
539
|
-
* it sends the player, or undefined.
|
|
540
|
-
*/
|
|
541
|
-
async function post(path, body, text, name) {
|
|
542
|
-
try {
|
|
543
|
-
const response = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
544
|
-
if (response.ok) {
|
|
545
|
-
return undefined;
|
|
546
|
-
}
|
|
547
|
-
const refused = (await response.json());
|
|
548
|
-
const known = refused.refusal === undefined ? undefined : text.refused[refused.refusal];
|
|
549
|
-
return { text: known?.(refused.most ?? 0, name) ?? refused.error ?? response.statusText, words: refused.refusal?.startsWith("words-") === true };
|
|
550
|
-
}
|
|
551
|
-
catch (error) {
|
|
552
|
-
return { text: String(error), words: false };
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
/** Saves a PGN file in the player's downloads: a game in analysis with its review, which the board page loads back ready (T-106), or the moves alone (T-59). */
|
|
437
|
+
/** Saves a PGN file in the player's downloads: a game in analysis with its review, which the board page loads back ready, or the moves alone. */
|
|
556
438
|
function download(pgn, name) {
|
|
557
439
|
const link = document.createElement("a");
|
|
558
440
|
link.href = URL.createObjectURL(new Blob([pgn], { type: "application/x-chess-pgn" }));
|
|
@@ -570,7 +452,7 @@ function withMove(game, move) {
|
|
|
570
452
|
const copy = new Game(game.record);
|
|
571
453
|
return copy.play(move).played ? copy : game;
|
|
572
454
|
}
|
|
573
|
-
/** Whether a pawn's move takes it to the last rank, where the player chooses what it becomes
|
|
455
|
+
/** Whether a pawn's move takes it to the last rank, where the player chooses what it becomes. */
|
|
574
456
|
function promotes(piece, to) {
|
|
575
457
|
return (piece === "P" && to.endsWith("8")) || (piece === "p" && to.endsWith("1"));
|
|
576
458
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// The page's keys and wheel: [ and ] fold the side columns, M holds the helps' map (D-78), the arrows, Home and End step through the moves and f
|
|
2
|
+
// and x turn the board, and the wheel over the board steps too.
|
|
3
|
+
import { useEffect, useRef } from "preact/hooks";
|
|
4
|
+
import { flipKeys, stepKeys } from "../game/browse.js";
|
|
5
|
+
import { foldKeys, gameUnderBoard, oneColumn } from "../layout/folds.js";
|
|
6
|
+
/**
|
|
7
|
+
* `[` and `]` fold the talk and the game, as in editors with two side bars; never while the player types or drags a piece, nor in one column, nor the game
|
|
8
|
+
* under the board.
|
|
9
|
+
*/
|
|
10
|
+
export function useFoldKeys(fold) {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const press = (event) => {
|
|
13
|
+
if (!forThePage(event) || oneColumn()) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const column = Object.keys(foldKeys).find((key) => foldKeys[key] === event.key);
|
|
17
|
+
if (column !== undefined && !(column === "game" && gameUnderBoard())) {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
fold(column);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
window.addEventListener("keydown", press);
|
|
23
|
+
return () => window.removeEventListener("keydown", press);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** M held shows the control map while the helps are on (D-78); it goes off when M is let go or the page loses the focus. */
|
|
27
|
+
export function useMapKey(helping, map) {
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!helping) {
|
|
30
|
+
map(false);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const down = (event) => {
|
|
34
|
+
if (forThePage(event) && event.key.toLowerCase() === "m") {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
map(true);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const up = (event) => {
|
|
40
|
+
if (event.key.toLowerCase() === "m") {
|
|
41
|
+
map(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const away = () => map(false);
|
|
45
|
+
window.addEventListener("keydown", down);
|
|
46
|
+
window.addEventListener("keyup", up);
|
|
47
|
+
window.addEventListener("blur", away);
|
|
48
|
+
return () => {
|
|
49
|
+
window.removeEventListener("keydown", down);
|
|
50
|
+
window.removeEventListener("keyup", up);
|
|
51
|
+
window.removeEventListener("blur", away);
|
|
52
|
+
};
|
|
53
|
+
}, [helping]);
|
|
54
|
+
}
|
|
55
|
+
/** The keys of Lichess and chess.com: the arrows, Home and End step through the moves, f and x turn the board round. */
|
|
56
|
+
export function useBrowseKeys(step, flip) {
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const press = (event) => {
|
|
59
|
+
const to = stepKeys[event.key];
|
|
60
|
+
const flips = flipKeys.includes(event.key.toLowerCase());
|
|
61
|
+
if (!forThePage(event) || (to === undefined && !flips)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
if (to === undefined) {
|
|
66
|
+
flip();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
step(to);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
window.addEventListener("keydown", press);
|
|
73
|
+
return () => window.removeEventListener("keydown", press);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** The mouse wheel over the board steps through the moves, as on Lichess: down forward, a step a notch, however finely a touchpad turns it. */
|
|
77
|
+
export function useWheelSteps(step) {
|
|
78
|
+
const turned = useRef(0);
|
|
79
|
+
return (event) => {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
turned.current += event.deltaMode === WheelEvent.DOM_DELTA_PIXEL ? event.deltaY : event.deltaY * notch;
|
|
82
|
+
while (Math.abs(turned.current) >= notch) {
|
|
83
|
+
step(turned.current > 0 ? "forward" : "back");
|
|
84
|
+
turned.current -= Math.sign(turned.current) * notch;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// How far a wheel turns in one notch, in pixels.
|
|
89
|
+
const notch = 100;
|
|
90
|
+
/** Whether a key is the page's: not one the player types into a box or the settings, nor with a modifier, nor while a piece is in the air. */
|
|
91
|
+
function forThePage(event) {
|
|
92
|
+
const typing = event.target.closest("input, textarea, select, [contenteditable], dialog") !== null;
|
|
93
|
+
return !(event.defaultPrevented || event.ctrlKey || event.altKey || event.metaKey || typing || document.documentElement.classList.contains("dragging"));
|
|
94
|
+
}
|