chesspal 0.3.6 → 0.4.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/dist/cli/cli.js +7 -2
- package/dist/cli/command-line.js +5 -3
- package/dist/cli/prompt-hook.d.ts +17 -0
- package/dist/cli/prompt-hook.js +100 -0
- package/dist/commands/analysis/analyse.js +8 -14
- package/dist/commands/analysis/annotate.d.ts +8 -0
- package/dist/commands/analysis/annotate.js +19 -3
- package/dist/commands/answers/agents.d.ts +7 -5
- package/dist/commands/answers/agents.js +16 -9
- package/dist/commands/answers/clock.js +2 -1
- package/dist/commands/answers/drawings.js +6 -2
- package/dist/commands/answers/index.d.ts +4 -3
- package/dist/commands/answers/index.js +4 -3
- package/dist/commands/answers/lines.d.ts +5 -3
- package/dist/commands/answers/lines.js +14 -7
- package/dist/commands/answers/opening-book.d.ts +13 -7
- package/dist/commands/answers/opening-book.js +34 -38
- package/dist/commands/answers/opening-tags.d.ts +4 -0
- package/dist/commands/answers/opening-tags.js +11 -0
- package/dist/commands/answers/play-move.d.ts +3 -1
- package/dist/commands/answers/play-move.js +9 -6
- package/dist/commands/answers/say.js +2 -1
- package/dist/commands/answers/summary.d.ts +5 -2
- package/dist/commands/answers/summary.js +16 -5
- package/dist/commands/game/ai-move.js +4 -4
- package/dist/commands/game/coach.js +7 -6
- package/dist/commands/game/end.js +2 -2
- package/dist/commands/game/export.d.ts +1 -1
- package/dist/commands/game/index.d.ts +1 -1
- package/dist/commands/game/index.js +1 -1
- package/dist/commands/game/new.js +41 -11
- package/dist/commands/game/player-move.d.ts +16 -0
- package/dist/commands/game/player-move.js +69 -19
- package/dist/commands/game/ponder.js +4 -1
- package/dist/commands/game/status.d.ts +1 -1
- package/dist/commands/game/status.js +6 -6
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +3 -3
- package/dist/commands/profiles/past-games.d.ts +1 -1
- package/dist/commands/profiles/past-games.js +21 -10
- package/dist/commands/registry/context.d.ts +2 -0
- package/dist/commands/registry/context.js +5 -0
- package/dist/commands/registry/index.d.ts +1 -1
- package/dist/commands/registry/index.js +1 -1
- package/dist/commands/talk/ai-say.d.ts +1 -1
- package/dist/commands/talk/ai-say.js +6 -5
- package/dist/core/game.d.ts +13 -0
- package/dist/core/game.js +29 -3
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/core/openings.d.ts +20 -6
- package/dist/core/openings.js +45 -22
- package/dist/core/pgn.d.ts +4 -0
- package/dist/core/pgn.js +35 -6
- package/dist/games/archive.d.ts +25 -5
- package/dist/games/archive.js +52 -19
- package/dist/games/chat-start.d.ts +2 -2
- package/dist/games/chat-start.js +6 -1
- package/dist/games/current-game.d.ts +2 -2
- package/dist/games/current-game.js +3 -2
- package/dist/games/index.d.ts +2 -1
- package/dist/games/index.js +2 -1
- package/dist/games/position-shown.d.ts +24 -0
- package/dist/games/position-shown.js +51 -0
- package/dist/home/index.d.ts +1 -1
- package/dist/home/index.js +1 -1
- package/dist/home/same-path.d.ts +6 -1
- package/dist/home/same-path.js +22 -3
- package/dist/host-files/host-files.d.ts +5 -0
- package/dist/host-files/host-files.js +13 -4
- package/dist/host-files/index.d.ts +1 -1
- package/dist/host-files/index.js +1 -1
- package/dist/page/app/app.js +30 -7
- package/dist/page/app/served.js +9 -4
- package/dist/page/avatars/torretta.css +4 -4
- package/dist/page/base.css +1 -1
- package/dist/page/board/board.css +4 -2
- package/dist/page/dialogs/dialogs.css +3 -1
- package/dist/page/game/column.js +10 -8
- package/dist/page/game/game.css +86 -6
- package/dist/page/game/plies.js +4 -0
- package/dist/page/game/review-progress.js +11 -0
- package/dist/page/guide/drawings/drawings.css +0 -1
- package/dist/page/guide/en.md +2 -2
- package/dist/page/guide/it.md +2 -2
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/layout.css +32 -15
- package/dist/page/layout/window.js +41 -2
- package/dist/page/legend/legend.css +4 -16
- package/dist/page/legend/legend.js +28 -15
- package/dist/page/profile/settings.js +1 -0
- package/dist/page/table/action.js +42 -0
- package/dist/page/table/table.css +57 -39
- package/dist/page/table/window.js +67 -67
- package/dist/page/talk/talk.css +52 -9
- package/dist/page/talk/talk.js +65 -20
- package/dist/page/talk/voice.js +92 -21
- package/dist/page/text.js +24 -8
- package/dist/server/bell/bell.d.ts +24 -27
- package/dist/server/bell/bell.js +79 -143
- package/dist/server/bell/index.d.ts +1 -1
- package/dist/server/bell/index.js +2 -2
- package/dist/server/mcp/mcp.js +49 -7
- package/dist/server/page/analyses.js +15 -4
- package/dist/server/page/files.js +3 -1
- package/dist/server/page/game.d.ts +5 -0
- package/dist/server/page/game.js +78 -13
- package/dist/server/page/http.d.ts +1 -1
- package/dist/server/page/page.d.ts +14 -9
- package/dist/server/page/page.js +111 -26
- package/dist/server/page/table.d.ts +38 -4
- package/dist/server/page/table.js +61 -23
- package/dist/server/page/windows.d.ts +22 -0
- package/dist/server/page/windows.js +45 -0
- package/dist/server/ports/index.d.ts +1 -0
- package/dist/server/ports/index.js +2 -0
- package/dist/server/ports/ports.d.ts +78 -0
- package/dist/server/ports/ports.js +222 -0
- package/dist/server/table/browser.d.ts +22 -6
- package/dist/server/table/browser.js +98 -20
- package/dist/server/table/hosts/hooks.d.ts +6 -0
- package/dist/server/table/hosts/hooks.js +38 -0
- package/dist/server/table/hosts/hosts.d.ts +13 -5
- package/dist/server/table/hosts/hosts.js +16 -10
- package/dist/server/table/hosts/index.d.ts +1 -0
- package/dist/server/table/hosts/index.js +1 -0
- package/dist/server/table/hosts/play.d.ts +6 -4
- package/dist/server/table/hosts/play.js +58 -16
- package/dist/server/table/index.d.ts +1 -1
- package/dist/server/table/launch.d.ts +5 -4
- package/dist/server/table/launch.js +18 -5
- package/dist/server/table/link.js +1 -0
- package/dist/server/table/table.d.ts +7 -0
- package/dist/server/table/table.js +39 -11
- package/instructions/opponent.md +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join, relative } from "node:path";
|
|
4
4
|
import { makeFolder, writeAtomically } from "../games/index.js";
|
|
5
|
-
import { agentFiles, instructionsFile } from "./agents.js";
|
|
5
|
+
import { agentFiles, agentHosts, instructionsFile } from "./agents.js";
|
|
6
6
|
/**
|
|
7
7
|
* Sets up the player's folder for Claude Code and Codex (D-31): .mcp.json with the chesspal server, AGENTS.md with the lines that start a chat between
|
|
8
8
|
* Chesspal's marks (D-102), CLAUDE.md importing it, the hosts' settings with the server approved, and their hooks. What the player wrote there stays; it returns
|
|
@@ -31,6 +31,13 @@ export function outdatedHostFiles(folder, platform) {
|
|
|
31
31
|
export function codexHooksPath(folder) {
|
|
32
32
|
return join(folder, ".codex", "hooks.json");
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether the host runs the doorbell's hook init writes, Claude Code and Codex by the names they give the server, so the player's act on the page wakes
|
|
36
|
+
* the chat; the command line, which names no host, is taken as one. Elsewhere the player tells the chat in its window.
|
|
37
|
+
*/
|
|
38
|
+
export function hasDoorbell(host) {
|
|
39
|
+
return host === undefined || agentHosts.includes(host);
|
|
40
|
+
}
|
|
34
41
|
/** The instructions of the AI opponent, read from the package wherever it is installed: the MCP server's `instructions` tool gives them (D-102). */
|
|
35
42
|
export function opponentInstructions() {
|
|
36
43
|
return instructionsFile("opponent");
|
|
@@ -88,8 +95,8 @@ function claudeFile(folder) {
|
|
|
88
95
|
return before.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md") ? undefined : { path, text: `@AGENTS.md\n${before}` };
|
|
89
96
|
}
|
|
90
97
|
/**
|
|
91
|
-
* Claude Code's settings: the chesspal server of .mcp.json approved and its tools allowed, so that the first chat asks for neither,
|
|
92
|
-
* moves on the page (D-47),
|
|
98
|
+
* Claude Code's settings: the chesspal server of .mcp.json approved and its tools allowed, so that the first chat asks for neither, the doorbell of the
|
|
99
|
+
* moves on the page (D-47), the note of how each chat starts (D-57), and the move the player writes alone in the chat (D-36); the player's own stay.
|
|
93
100
|
*/
|
|
94
101
|
function claudeSettingsFile(folder) {
|
|
95
102
|
const path = join(folder, ".claude", "settings.json");
|
|
@@ -100,7 +107,7 @@ function claudeSettingsFile(folder) {
|
|
|
100
107
|
...settings,
|
|
101
108
|
enabledMcpjsonServers: withItem(settings.enabledMcpjsonServers, "chesspal"),
|
|
102
109
|
permissions: { ...permissions, allow: withItem(permissions.allow, "mcp__chesspal") },
|
|
103
|
-
hooks: withHook(withHook(hooks, "Stop", bellHook), "SessionStart", chatHook),
|
|
110
|
+
hooks: withHook(withHook(withHook(hooks, "Stop", bellHook), "SessionStart", chatHook), "UserPromptSubmit", promptHook),
|
|
104
111
|
});
|
|
105
112
|
}
|
|
106
113
|
/** The hooks with one of Chesspal's put back beside the player's own whenever it is missing; an event of theirs that is no list stays theirs. */
|
|
@@ -118,6 +125,8 @@ function withHook(hooks, event, ours) {
|
|
|
118
125
|
const bellHook = { type: "command", command: "chesspal listen", asyncRewake: true, timeout: 86_400 };
|
|
119
126
|
// Claude Code's SessionStart hook (D-57): it notes how each chat starts, so that a new game waits for a clean one.
|
|
120
127
|
const chatHook = { type: "command", command: "chesspal chat" };
|
|
128
|
+
// Claude Code's UserPromptSubmit hook (D-36): a message that is one move and nothing else is played before the AI reads it, which then only replies.
|
|
129
|
+
const promptHook = { type: "command", command: "chesspal prompt", timeout: 10 };
|
|
121
130
|
/**
|
|
122
131
|
* Codex's settings, read once the player trusts the folder: the chesspal server with its tools approved, a table of Chesspal's rewritten but for the
|
|
123
132
|
* environment the player gave it; CHESSPAL_HOME and CHESSPAL_SEAT always passed on to the server, which Codex otherwise starts without them, so that a
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { agentHosts, agentTools, analystAgent, coachAgent, hasAgent, instructionsFile, isCodex, onQuickModel, ponderAgentFor, ponderAgents } from "./agents.ts";
|
|
2
|
-
export { codexHooksPath, opponentInstructions, outdatedHostFiles, setUpHosts, startInstructions } from "./host-files.ts";
|
|
2
|
+
export { codexHooksPath, hasDoorbell, opponentInstructions, outdatedHostFiles, setUpHosts, startInstructions } from "./host-files.ts";
|
package/dist/host-files/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// The files through which an AI host finds Chesspal in the player's folder: the server, the opponent's instructions, the hosts' settings.
|
|
2
2
|
export { agentHosts, agentTools, analystAgent, coachAgent, hasAgent, instructionsFile, isCodex, onQuickModel, ponderAgentFor, ponderAgents } from "./agents.js";
|
|
3
|
-
export { codexHooksPath, opponentInstructions, outdatedHostFiles, setUpHosts, startInstructions } from "./host-files.js";
|
|
3
|
+
export { codexHooksPath, hasDoorbell, opponentInstructions, outdatedHostFiles, setUpHosts, startInstructions } from "./host-files.js";
|
package/dist/page/app/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 { aiHostIn, aiModelIn, aiNameOf, annotatorOf, closingOf, Game, gameReviewOf, materialOf, moodAt, movesOnly, openingOf, readPgn, suggestedLevel, timeControlText, writePgn, } from "../../core/index.js";
|
|
3
|
+
import { aiHostIn, aiModelIn, aiNameOf, annotatorOf, closingOf, Game, gameReviewOf, materialOf, moodAt, movesOnly, openingOf, outOfBookMove, readPgn, suggestedLevel, timeControlText, writePgn, } from "../../core/index.js";
|
|
4
4
|
import { PlayerPicture } from "../avatars/avatars.js";
|
|
5
5
|
import { Thinker, Thought } from "../avatars/thought.js";
|
|
6
6
|
import { Torretta } from "../avatars/torretta.js";
|
|
@@ -19,7 +19,7 @@ import { GameColumn } from "../game/column.js";
|
|
|
19
19
|
import { howEnded, useDeclined, useJustEnded, useSawEnd } from "../game/ending.js";
|
|
20
20
|
import { Steps } from "../game/move-list.js";
|
|
21
21
|
import { useOpeningBook } from "../game/opening-book.js";
|
|
22
|
-
import { aiSpoke, aiWords, analysisTalk, judgmentsOf, playerSpent, playerWords, pliesOf, withBook, withJudgments, withoutReviews } from "../game/plies.js";
|
|
22
|
+
import { aiSpoke, aiWords, analysisTalk, judgmentsOf, playerSpent, playerWords, pliesOf, reviewCount, withBook, withJudgments, withoutReviews } from "../game/plies.js";
|
|
23
23
|
import { GuideDialog } from "../guide/guide.js";
|
|
24
24
|
import { BareButton, HelpsCard, HintLine, MapButton } from "../helps/helps.js";
|
|
25
25
|
import { helpsOf } from "../helps/view.js";
|
|
@@ -44,7 +44,7 @@ import { textFor } from "../text.js";
|
|
|
44
44
|
import { anchorName, useAnchor } from "./anchor.js";
|
|
45
45
|
import { useBrowseKeys, useFoldKeys, useLegendKey, useMapKey, useWheelSteps } from "./keys.js";
|
|
46
46
|
import { phaseOf, verdictOf } from "./phase.js";
|
|
47
|
-
import { ask, post, sendMessage, sendMove, useServed } from "./served.js";
|
|
47
|
+
import { ask, post, refusedText, sendMessage, sendMove, useServed } from "./served.js";
|
|
48
48
|
import { tabTitle, useTabTitle } from "./title.js";
|
|
49
49
|
/**
|
|
50
50
|
* 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
|
|
@@ -55,6 +55,8 @@ export function App({ start }) {
|
|
|
55
55
|
const [setting, setSetting] = useState(false);
|
|
56
56
|
const text = useMemo(() => textFor(languagesOf(settings)), [settings.language]);
|
|
57
57
|
const sound = settings.sound;
|
|
58
|
+
// The sounds off hush the voice too: it is one of them.
|
|
59
|
+
const voiced = settings.voice && sound;
|
|
58
60
|
useEffect(() => void (document.documentElement.lang = text.language), [text]);
|
|
59
61
|
const apply = (next) => {
|
|
60
62
|
setSettings(next);
|
|
@@ -132,6 +134,20 @@ export function App({ start }) {
|
|
|
132
134
|
const name = served?.opponent ?? text.ai;
|
|
133
135
|
// Under it, the model and effort the AI played its last move with (D-82).
|
|
134
136
|
const signed = served?.aiModel === undefined ? undefined : signature(served.aiModel.model, served.aiModel.effort);
|
|
137
|
+
// A new game that waited for the chat and could not start says why on the board, as one refused at once (D-107): once, and never one from before the
|
|
138
|
+
// page loaded.
|
|
139
|
+
const waitRefused = served?.table?.refused;
|
|
140
|
+
const waitRefusedSeen = useRef(undefined);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (served === undefined) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const number = waitRefused?.number ?? 0;
|
|
146
|
+
if (waitRefused !== undefined && waitRefusedSeen.current !== undefined && number > waitRefusedSeen.current) {
|
|
147
|
+
setRefusal({ text: refusedText(waitRefused, text, name) ?? "" });
|
|
148
|
+
}
|
|
149
|
+
waitRefusedSeen.current = number;
|
|
150
|
+
}, [served === undefined, waitRefused?.number]);
|
|
135
151
|
// The game is replayed from its PGN once for each one the server sends, not at every click. Once no game goes on, the game in analysis shows in its place
|
|
136
152
|
// (D-69), with the AI's badges and notes: replayed again only when its review changed.
|
|
137
153
|
const game = served?.game;
|
|
@@ -153,8 +169,10 @@ export function App({ start }) {
|
|
|
153
169
|
const analyst = annotator === undefined ? name : aiNameOf(annotator.annotator, annotator.annotator) ?? text.ai;
|
|
154
170
|
const material = useMemo(() => materialOf(live.position.fen), [live]);
|
|
155
171
|
const pieces = useMemo(() => piecesOf(live.position.fen), [live]);
|
|
172
|
+
// How many of the game's first plies a line of the book reaches, as the server counts them (D-68).
|
|
173
|
+
const bookPlies = studied !== undefined ? studied.book : game?.book ?? 0;
|
|
156
174
|
// 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).
|
|
157
|
-
const inBook = studied === undefined && settings.book ?
|
|
175
|
+
const inBook = studied === undefined && settings.book ? bookPlies : 0;
|
|
158
176
|
// 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
|
|
159
177
|
// coach's come from its own file, never in the PGN the opponent may read (D-88).
|
|
160
178
|
const judged = !going || settings.helps;
|
|
@@ -230,6 +248,8 @@ export function App({ start }) {
|
|
|
230
248
|
// In analysis, the opening the move shown has reached, while the book knows the moves (D-69).
|
|
231
249
|
const bookAt = studied === undefined || book === undefined || at === 0 || at > studied.book ? undefined : { tags: live.record.tags, moves: live.record.moves.slice(0, at) };
|
|
232
250
|
const shownOpening = useMemo(() => bookAt === undefined || book === undefined ? undefined : openingOf(bookAt, book), [live, book, bookAt === undefined ? 0 : at]);
|
|
251
|
+
// Whether the move shown is still the book's, whatever the badges show: the card says the move the game left it at, once it is past it.
|
|
252
|
+
const leftBook = outOfBookMove(at, bookPlies);
|
|
233
253
|
// A jump of more than one move shows the position at once: only a step slides its pieces.
|
|
234
254
|
const shownBefore = useRef(at);
|
|
235
255
|
const jumped = Math.abs(shownBefore.current - at) > 1;
|
|
@@ -328,6 +348,8 @@ export function App({ start }) {
|
|
|
328
348
|
// With nobody at it, a review not finished says so, and the player brings the game back to the AI (D-109).
|
|
329
349
|
const completeIt = summary === undefined && studied?.incomplete === true && !offline ? () => void studyGame({ game: studied.name }) : undefined;
|
|
330
350
|
const closing = studied === undefined ? undefined : closingOf(live.record);
|
|
351
|
+
// How far the review has got, counted as status counts it for the AI: who is ahead after how many of the game's moves.
|
|
352
|
+
const count = reviewing || completeIt !== undefined ? reviewCount(plies) : undefined;
|
|
331
353
|
// In analysis the AI's card says who played that game, as its PGN keeps it (D-82), not who plays now.
|
|
332
354
|
const playedBy = studied === undefined ? undefined : aiModelIn(live.record);
|
|
333
355
|
const talk = game?.talk ?? served.talk ?? [];
|
|
@@ -448,11 +470,11 @@ export function App({ start }) {
|
|
|
448
470
|
: null, _jsxs("main", { class: `app${flipped ? " flipped" : ""}${folds.talk === "open" ? "" : ` talk-${folds.talk}`}${folds.game === "folded" ? " folded" : ""}`, inert: offline, children: [header, sides === undefined
|
|
449
471
|
? (_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 }))
|
|
450
472
|
: (_jsx(TalkFaces, { name: sides[ai].name, aiPicture: pictureOf(sides[ai], profile, served.profiles, 40, mood), you: sides[player].name, picture: pictureOf(sides[player], profile, served.profiles, 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 && studied !== undefined
|
|
451
|
-
? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[ai].name, children: [studiedCard(ai, "ai"), asleepNote, _jsx(AiNote, { ...(reviewed === undefined ? {} : { ply: reviewed }), ...(shownOpening === undefined ? {} : { opening: shownOpening }), ...(at === total && closing !== undefined ? { closing } : {}), ...(at === 0 && summary !== undefined ? { summary } : {}), ...(reviewing ? { reviewing: true } : {}), ...(completeIt === undefined ? {} : { onComplete: completeIt }), name: analyst, by: annotator, mood: mood, ...(
|
|
473
|
+
? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[ai].name, children: [studiedCard(ai, "ai"), asleepNote, _jsx(AiNote, { ...(reviewed === undefined ? {} : { ply: reviewed }), ...(shownOpening === undefined ? {} : { opening: shownOpening }), ...(at === total && closing !== undefined ? { closing } : {}), ...(at === 0 && summary !== undefined ? { summary } : {}), ...(reviewing ? { reviewing: true } : {}), ...(count === undefined ? {} : { count }), ...(completeIt === undefined ? {} : { onComplete: completeIt }), name: analyst, by: annotator, mood: mood, ...(voiced ? { voice: text } : {}), text: text })] }), _jsxs("section", { class: "you-block", "aria-label": sides[player].name, children: [_jsx("div", { class: "chat", children: _jsx(Compose, { lines: analysisTalk(talk, studied.name), newestFirst: false, spent: 0, draft: draft, onDraft: (words) => {
|
|
452
474
|
setDraft(words);
|
|
453
475
|
setUnsent(undefined);
|
|
454
476
|
}, onSend: () => sendWords({ game: studied.name, at }), canMove: false, name: name, problem: unsent, about: { ...(at === 0 ? {} : { ply: plies[at - 1] }), onShow: goTo }, text: text }) }), studiedCard(player, "you")] })] }))
|
|
455
|
-
: (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": name, children: [_jsx(PlayerCard, { place: "ai", picture: thinker(48), name: table === undefined ? name : _jsx(AiNameButton, { name: name, table: table, onOpen: askChat, text: text }), 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, ...(
|
|
477
|
+
: (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": name, children: [_jsx(PlayerCard, { place: "ai", picture: thinker(48), name: table === undefined ? name : _jsx(AiNameButton, { name: name, table: table, onOpen: askChat, text: text }), 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, ...(voiced ? { voice: text } : {}) })] }), _jsxs("section", { class: "you-block", "aria-label": profile.name, children: [_jsx(Compose, { lines: said, newestFirst: flipped,
|
|
456
478
|
// Between games, and before the first, the player writes freely, a line at a time, as the AI does (D-74, D-77).
|
|
457
479
|
spent: going ? playerSpent(plies, ai, talk) : 0, draft: draft, onDraft: (words) => {
|
|
458
480
|
setDraft(words);
|
|
@@ -463,11 +485,12 @@ export function App({ start }) {
|
|
|
463
485
|
// A press on the board while the player looks back brings the game as it is: the next one plays.
|
|
464
486
|
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: boardEnd, 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 ? undefined : refusal ?? (linkIgnored === undefined ? undefined : { text: text.link[linkIgnored] }), 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" && (asleepNote !== null || clockWaits)
|
|
465
487
|
? (_jsxs("div", { class: "over-board", children: [asleepNote, startClocks] }))
|
|
466
|
-
: null] }), studied === undefined ? null : (_jsxs("div", { class: "board-steps", children: [_jsx(Steps, { shown: at, total: total, onStep: step, text: text }), standings === undefined ? null : _jsx(LeadChart, { plies: plies, standings: standings, shown: at, onShow: goTo, text: text })] })), helps === undefined ? null : (_jsxs("div", { class: "board-map", children: [legendAt(true), _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, legend: legend, text: text })), ...(helps === undefined && legendRows.length > 0 ? { legend } : {}), ...(standings === undefined ? {} : { standings }), ...(studied === undefined ? {} : {
|
|
488
|
+
: null] }), studied === undefined ? null : (_jsxs("div", { class: "board-steps", children: [_jsx(Steps, { shown: at, total: total, onStep: step, text: text }), standings === undefined ? null : _jsx(LeadChart, { plies: plies, standings: standings, shown: at, onShow: goTo, text: text })] })), helps === undefined ? null : (_jsxs("div", { class: "board-map", children: [legendAt(true), _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, ...(leftBook === undefined ? {} : { outOfBook: leftBook }), 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, legend: legend, text: text })), ...(helps === undefined && legendRows.length > 0 ? { legend } : {}), ...(standings === undefined ? {} : { standings }), ...(studied === undefined ? {} : {
|
|
467
489
|
analysis: {
|
|
468
490
|
by: analyst,
|
|
469
491
|
...(summary === undefined ? {} : { summary }),
|
|
470
492
|
...(reviewing ? { reviewing: true } : {}),
|
|
493
|
+
...(count === undefined ? {} : { count }),
|
|
471
494
|
...(completeIt === undefined ? {} : { onComplete: completeIt }),
|
|
472
495
|
},
|
|
473
496
|
}), 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 && replayed.record.moves.length > 0 ? { onAnalyse: () => void studyGame({ last: true }) } : {}), text: text }), _jsx(PageFoot, { version: served.version, text: text })] })] }) }));
|
package/dist/page/app/served.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// What the page asks the server (D-47): the game of the player's folder, read again each time it changes, and the player's moves, words and choices sent
|
|
2
2
|
// there, refused in the player's language.
|
|
3
3
|
import { useEffect, useRef, useState } from "preact/hooks";
|
|
4
|
+
import { closesForNewer, eventsAddress } from "../layout/window.js";
|
|
4
5
|
import { watchServed } from "./watch.js";
|
|
5
6
|
/**
|
|
6
7
|
* The game of the player's folder, read from the server, and read again each time the server says it changed, whoever changed it; `onNewGame` when the chat
|
|
@@ -18,9 +19,9 @@ export function useServed(onNewGame) {
|
|
|
18
19
|
const watch = watchServed({
|
|
19
20
|
read: () => fetch("/api/game").then((response) => response.json()),
|
|
20
21
|
connect: (handlers) => {
|
|
21
|
-
const events = new EventSource(
|
|
22
|
+
const events = new EventSource(eventsAddress());
|
|
22
23
|
events.onopen = handlers.open;
|
|
23
|
-
events.onmessage = (event) => handlers.message(event.data);
|
|
24
|
+
events.onmessage = (event) => closesForNewer(event.data) || handlers.message(event.data);
|
|
24
25
|
events.onerror = handlers.error;
|
|
25
26
|
return events;
|
|
26
27
|
},
|
|
@@ -54,13 +55,17 @@ export async function ask(path, body, text, name) {
|
|
|
54
55
|
return { answer: await response.json().catch(() => ({})) };
|
|
55
56
|
}
|
|
56
57
|
const refused = (await response.json());
|
|
57
|
-
|
|
58
|
-
return { refused: { text: known?.(refused.most ?? 0, name) ?? refused.error ?? response.statusText, words: refused.refusal?.startsWith("words-") === true } };
|
|
58
|
+
return { refused: { text: refusedText(refused, text, name) ?? response.statusText, words: refused.refusal?.startsWith("words-") === true } };
|
|
59
59
|
}
|
|
60
60
|
catch (error) {
|
|
61
61
|
return { refused: { text: String(error), words: false } };
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
/** A refusal of the server's in the player's language when the page knows its code, with the host's name where it sends the player; else its words. */
|
|
65
|
+
export function refusedText(refused, text, name) {
|
|
66
|
+
const known = refused.refusal === undefined ? undefined : text.refused[refused.refusal];
|
|
67
|
+
return known?.(refused.most ?? 0, name) ?? refused.error;
|
|
68
|
+
}
|
|
64
69
|
/** 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. */
|
|
65
70
|
export function sendMove(move, words, text, name) {
|
|
66
71
|
const message = words.trim();
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
.torretta .light {
|
|
11
11
|
fill: var(--torretta-light);
|
|
12
|
-
transition: fill
|
|
12
|
+
transition: fill 200ms;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.torretta.mood-worried .light {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.torretta.mood-thoughtful .light {
|
|
20
|
-
animation: ponder
|
|
20
|
+
animation: ponder 2400ms ease-in-out infinite;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.torretta.mood-sad .light {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
.torretta .expression.changed {
|
|
34
34
|
transform-box: fill-box;
|
|
35
35
|
transform-origin: center;
|
|
36
|
-
animation: face-in
|
|
36
|
+
animation: face-in 160ms ease-out;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
@keyframes face-in {
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
.torretta .eyes {
|
|
81
81
|
transform-box: fill-box;
|
|
82
82
|
transform-origin: center;
|
|
83
|
-
animation: blink
|
|
83
|
+
animation: blink 6000ms infinite;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
@keyframes blink {
|
package/dist/page/base.css
CHANGED
|
@@ -789,7 +789,7 @@
|
|
|
789
789
|
.notice.right,
|
|
790
790
|
.notice.left {
|
|
791
791
|
top: calc((var(--row) + 0.5) * 12.5cqw);
|
|
792
|
-
max-width: min(calc(
|
|
792
|
+
max-width: min(calc(var(--room) * 12.5cqw - 12px), 340px);
|
|
793
793
|
}
|
|
794
794
|
|
|
795
795
|
.notice.right {
|
|
@@ -887,7 +887,9 @@
|
|
|
887
887
|
}
|
|
888
888
|
|
|
889
889
|
.promotion-piece:hover {
|
|
890
|
-
box-shadow:
|
|
890
|
+
box-shadow:
|
|
891
|
+
var(--shadow),
|
|
892
|
+
inset 0 0 0 max(2px, 0.625cqw) var(--hover-edge);
|
|
891
893
|
}
|
|
892
894
|
|
|
893
895
|
/* White inside dark reads on every square and every theme. */
|
|
@@ -622,7 +622,9 @@ h3 + .switch {
|
|
|
622
622
|
|
|
623
623
|
/* A game without a result: its star on a coin of the card's colour, readable where the four squares meet */
|
|
624
624
|
.analyses .tile.open {
|
|
625
|
-
background:
|
|
625
|
+
background:
|
|
626
|
+
radial-gradient(circle, var(--card) 0 11px, transparent 11.5px),
|
|
627
|
+
conic-gradient(var(--dark-square) 0 25%, var(--light-square) 0 50%, var(--dark-square) 0 75%, var(--light-square) 0);
|
|
626
628
|
color: var(--soft);
|
|
627
629
|
font-size: 18px;
|
|
628
630
|
font-weight: 700;
|
package/dist/page/game/column.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import {} from "../../core/index.js";
|
|
3
3
|
import { LeadBar, LeadChart } from "../lead/lead.js";
|
|
4
4
|
import { standingAt } from "../lead/view.js";
|
|
5
5
|
import { Piece } from "../pieces/pieces.js";
|
|
6
6
|
import { FoldButton, useCardFold } from "./card-fold.js";
|
|
7
7
|
import { MoveList, Steps } from "./move-list.js";
|
|
8
8
|
import { ResultTile } from "./result-tile.js";
|
|
9
|
+
import { ReviewProgress } from "./review-progress.js";
|
|
9
10
|
/**
|
|
10
11
|
* The column, which the player folds with its tab; under the board, where three columns would leave the board small, always open. A game in analysis
|
|
11
12
|
* (D-69) says above the moves that the AI is going over it, until its word on the whole game comes, which the talk shows.
|
|
12
13
|
*/
|
|
13
|
-
export function GameColumn({ plies, result, how, lit, started, move, toMove, opening, helps, legend, standings, analysis, shownPlies, onShow, onStep, onDownload, onSaveMoves, onAnalyse, text }) {
|
|
14
|
+
export function GameColumn({ plies, result, how, lit, started, move, toMove, opening, outOfBook, helps, legend, standings, analysis, shownPlies, onShow, onStep, onDownload, onSaveMoves, onAnalyse, text }) {
|
|
14
15
|
const bar = standings === undefined ? undefined : _jsx(LeadBar, { standing: standingAt(standings, shownPlies), shown: shownPlies, text: text });
|
|
15
16
|
return (_jsx("aside", { class: "side", id: "game", "aria-label": text.game, children: started === undefined && analysis === undefined
|
|
16
17
|
? (_jsxs(_Fragment, { children: [_jsx("p", { class: "card empty", children: text.noGame }), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseLast, onAnalyse: onAnalyse })] }))
|
|
17
|
-
: (_jsxs(_Fragment, { children: [_jsxs("div", { class: "side-cards", children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, bar: bar, text: text }), analysis?.reviewing !== true ? null : (_jsxs("
|
|
18
|
+
: (_jsxs(_Fragment, { children: [_jsxs("div", { class: "side-cards", children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, bar: bar, text: text }), analysis?.reviewing !== true ? null : (_jsxs("div", { class: "card review waiting", children: [_jsx("p", { role: "status", children: text.reviewing(analysis.by) }), analysis.count === undefined ? null : _jsx(ReviewProgress, { count: analysis.count, label: text.reviewing(analysis.by) })] })), analysis?.onComplete === undefined ? null : (_jsxs("div", { class: "card review incomplete", children: [_jsxs("p", { children: [_jsx("span", { role: "status", children: text.incomplete }), _jsx("button", { type: "button", title: text.completeHint, "aria-label": text.completeHint, onClick: analysis.onComplete, children: text.complete })] }), analysis.count === undefined || analysis.count.done === 0 ? null : _jsx(ReviewProgress, { count: analysis.count, label: text.incomplete, stopped: true })] })), analysis === undefined || standings === undefined ? null : _jsx(LeadChart, { plies: plies, standings: standings, shown: shownPlies, onShow: onShow, text: text }), opening === undefined ? null : _jsx(OpeningCard, { opening: opening, ...(outOfBook === undefined ? {} : { outOfBook }), text: text }), helps] }), _jsxs("div", { class: "side-foot", children: [result !== undefined && plies.length === 0 ? null : _jsx(MoveList, { plies: plies, result: result, shown: shownPlies, onShow: onShow, tools: legend, text: text }), onDownload === undefined && onSaveMoves === undefined ? null : (_jsxs("div", { class: "card analysis-actions", children: [onDownload === undefined ? null : _jsx(SaveButton, { label: text.study.download, hint: text.study.downloadHint, onSave: onDownload }), onSaveMoves === undefined ? null : _jsx(SaveButton, { label: text.saveMoves, hint: text.saveMovesHint, onSave: onSaveMoves })] })), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseIt, onAnalyse: onAnalyse }), _jsx(Steps, { shown: shownPlies, total: plies.length, onStep: onStep, text: text })] })] })) }));
|
|
18
19
|
}
|
|
19
20
|
/** A button that saves the game as a file in the player's downloads, its tooltip saying what the file holds. */
|
|
20
21
|
function SaveButton({ label, hint, onSave }) {
|
|
@@ -48,12 +49,13 @@ function GameCard({ plies, result, how, lit, started, move, toMove, bar, text })
|
|
|
48
49
|
* The opening by Lichess's book: a book, crossed out once the game left it, and the family, which fold the card to them (Stefano); the variation under them,
|
|
49
50
|
* and the move the game left the book at.
|
|
50
51
|
*/
|
|
51
|
-
function OpeningCard({ opening, text }) {
|
|
52
|
+
function OpeningCard({ opening, outOfBook, text }) {
|
|
52
53
|
const [foldedHere, toggleFold] = useCardFold("opening");
|
|
53
|
-
const
|
|
54
|
+
const inBook = outOfBook === undefined;
|
|
55
|
+
const book = inBook ? text.inBook : `${text.outOfBook} ${outOfBook}`;
|
|
54
56
|
// The family alone has nothing under it to fold: no button, no chevron (Stefano).
|
|
55
|
-
const foldable = opening.variation !== undefined || !
|
|
57
|
+
const foldable = opening.variation !== undefined || !inBook;
|
|
56
58
|
const folded = foldable && foldedHere;
|
|
57
|
-
const title = (_jsxs(_Fragment, { children: [_jsx("span", { class: `book${
|
|
58
|
-
return (_jsxs("section", { class: `card opening${folded ? " folded" : ""}`, "aria-label": text.opening, children: [_jsx("h3", { class: "family", children: foldable ? _jsx(FoldButton, { folded: folded, onFold: toggleFold, text: text, children: title }) : title }), folded || opening.variation === undefined ? null : _jsx("p", { class: "variation", children: opening.variation }), folded ||
|
|
59
|
+
const title = (_jsxs(_Fragment, { children: [_jsx("span", { class: `book${inBook ? "" : " out"}`, title: book, role: "img", "aria-label": book, children: _jsxs("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { d: "M12 6.5C10 5 7 4.5 4 5V18C7 17.5 10 18 12 19.5C14 18 17 17.5 20 18V5C17 4.5 14 5 12 6.5ZM12 6.5V19.5" }), inBook ? null : _jsx("path", { d: "M3.5 3.5L20.5 20.5" })] }) }), _jsx("b", { children: opening.family })] }));
|
|
60
|
+
return (_jsxs("section", { class: `card opening${folded ? " folded" : ""}`, "aria-label": text.opening, children: [_jsx("h3", { class: "family", children: foldable ? _jsx(FoldButton, { folded: folded, onFold: toggleFold, text: text, children: title }) : title }), folded || opening.variation === undefined ? null : _jsx("p", { class: "variation", children: opening.variation }), folded || inBook ? null : _jsxs("p", { class: "out", children: [text.outOfBook, " ", outOfBook] })] }));
|
|
59
61
|
}
|
package/dist/page/game/game.css
CHANGED
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
width: 2px;
|
|
229
229
|
margin-left: -1px;
|
|
230
230
|
background: #ffc450;
|
|
231
|
-
box-shadow: 0 0 0 1px
|
|
231
|
+
box-shadow: 0 0 0 1px rgba(20, 23, 27, 0.6);
|
|
232
232
|
pointer-events: none;
|
|
233
233
|
}
|
|
234
234
|
|
|
@@ -319,24 +319,104 @@
|
|
|
319
319
|
line-height: 1.45;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
/* The line that says the AI reviews, and under it how far it has got */
|
|
322
323
|
.review.waiting,
|
|
323
324
|
.say.reviewing {
|
|
324
|
-
display:
|
|
325
|
-
align-items: center;
|
|
325
|
+
display: grid;
|
|
326
326
|
gap: 8px;
|
|
327
327
|
margin: 0;
|
|
328
328
|
color: var(--soft);
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
/* A review nobody is finishing: the note, and beside it the button that brings the game back to the AI (D-109); neutral, as no error (Zoe)
|
|
331
|
+
/* A review nobody is finishing: the note, and beside it the button that brings the game back to the AI (D-109); neutral, as no error (Zoe). Under them,
|
|
332
|
+
how far it got */
|
|
332
333
|
.review.incomplete,
|
|
333
334
|
.say.incomplete {
|
|
335
|
+
display: grid;
|
|
336
|
+
gap: 8px;
|
|
337
|
+
margin: 0;
|
|
338
|
+
color: var(--soft);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.review.incomplete > p,
|
|
342
|
+
.say.incomplete > p {
|
|
334
343
|
display: flex;
|
|
335
344
|
align-items: center;
|
|
336
345
|
justify-content: space-between;
|
|
337
346
|
gap: 8px;
|
|
338
|
-
|
|
339
|
-
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* How far a review has got: a thin bar in the accent and the count, 12/33. Before the first move a short run goes back and forth; full, while the word on
|
|
350
|
+
the whole game is written, it breathes; stopped unfinished, it stands still in the soft ink */
|
|
351
|
+
.review-progress {
|
|
352
|
+
display: flex;
|
|
353
|
+
align-items: center;
|
|
354
|
+
gap: 8px;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.review-progress .track {
|
|
358
|
+
flex: 1;
|
|
359
|
+
height: 4px;
|
|
360
|
+
overflow: hidden;
|
|
361
|
+
border-radius: 2px;
|
|
362
|
+
background: color-mix(in srgb, var(--accent) 20%, transparent);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.review-progress .fill {
|
|
366
|
+
display: block;
|
|
367
|
+
width: 0;
|
|
368
|
+
height: 100%;
|
|
369
|
+
border-radius: inherit;
|
|
370
|
+
background: var(--accent);
|
|
371
|
+
transition: width 300ms ease-out;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.review-progress .count {
|
|
375
|
+
flex: none;
|
|
376
|
+
font-size: 12px;
|
|
377
|
+
font-variant-numeric: tabular-nums;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.review-progress.waiting .fill {
|
|
381
|
+
width: 30%;
|
|
382
|
+
animation: review-run 1400ms ease-in-out infinite alternate;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.review-progress.writing .fill {
|
|
386
|
+
animation: review-breath 1600ms ease-in-out infinite;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.review-progress.stopped .track {
|
|
390
|
+
background: color-mix(in srgb, var(--soft) 20%, transparent);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.review-progress.stopped .fill {
|
|
394
|
+
background: var(--soft);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@keyframes review-run {
|
|
398
|
+
to {
|
|
399
|
+
translate: 233%;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
@keyframes review-breath {
|
|
404
|
+
50% {
|
|
405
|
+
opacity: 0.45;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* Still: before the first move a faint full bar, the count then grows without sliding */
|
|
410
|
+
@media (prefers-reduced-motion: reduce) {
|
|
411
|
+
.review-progress .fill {
|
|
412
|
+
transition: none;
|
|
413
|
+
animation: none;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.review-progress.waiting .fill {
|
|
417
|
+
width: 100%;
|
|
418
|
+
opacity: 0.35;
|
|
419
|
+
}
|
|
340
420
|
}
|
|
341
421
|
|
|
342
422
|
.review.incomplete button,
|
package/dist/page/game/plies.js
CHANGED
|
@@ -91,3 +91,7 @@ export function playerSpent(plies, ai, talk) {
|
|
|
91
91
|
function judged({ review }) {
|
|
92
92
|
return review?.note !== undefined || (review?.badge !== undefined && review.badge !== "book");
|
|
93
93
|
}
|
|
94
|
+
/** How far a review has got, as status tells the AI: the moves the analyst has said who is ahead after, of all the game's. */
|
|
95
|
+
export function reviewCount(plies) {
|
|
96
|
+
return { done: plies.filter((ply) => ply.lead !== undefined).length, total: plies.length };
|
|
97
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The line that says the analyst works, and under it a thin bar with the count, 12/33: a bar that runs back and forth before the first move is said, full
|
|
4
|
+
* and breathing while the word on the whole game is written; still, in the soft ink, when the review stopped unfinished.
|
|
5
|
+
*/
|
|
6
|
+
export function ReviewProgress({ count, label, stopped = false }) {
|
|
7
|
+
const { done, total } = count;
|
|
8
|
+
const started = done > 0;
|
|
9
|
+
const state = stopped ? " stopped" : !started ? " waiting" : done >= total ? " writing" : "";
|
|
10
|
+
return (_jsxs("span", { class: `review-progress${state}`, role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": total, ...(started || stopped ? { "aria-valuenow": done, "aria-valuetext": `${done}/${total}` } : {}), children: [_jsx("span", { class: "track", "aria-hidden": "true", children: _jsx("span", { class: "fill", style: started ? { width: `${(100 * done) / Math.max(total, 1)}%` } : undefined }) }), started || stopped ? _jsxs("span", { class: "count", "aria-hidden": "true", children: [done, "/", total] }) : null] }));
|
|
11
|
+
}
|
package/dist/page/guide/en.md
CHANGED
|
@@ -18,7 +18,7 @@ No chess engine plays for the AI: every move and every judgment is its own. Ches
|
|
|
18
18
|
|
|
19
19
|
### Getting started
|
|
20
20
|
|
|
21
|
-
1. Click the **AI's name**, at the top left, and choose its chat among those on your computer: **Claude Code** or **Codex**. It opens in a window of its own: keep it open while you play. The first time it asks some questions. Whether you trust the folder: **Yes
|
|
21
|
+
1. Click the **AI's name**, at the top left, and choose its chat among those on your computer: **Claude Code** or **Codex**. It opens in a window of its own: keep it open while you play. The first time it asks some questions. Whether you trust the folder: **Yes** (in Claude Code No is chosen already: go down to Yes with the arrow keys, then Enter). With Codex, about the hooks, Chesspal's two helpers: **Trust all and continue**. On Windows Codex also asks whether to set up its sandbox, the protected space it works in: choose the first option, and yes when Windows asks for permission.
|
|
22
22
|
2. Press **New game**, choose your colour, the clock and the AI's level, and press **Start**.
|
|
23
23
|
3. Move: the AI answers on the board, and with a few words under its name.
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ No chess engine plays for the AI: every move and every judgment is its own. Ches
|
|
|
35
35
|
|
|
36
36
|

|
|
37
37
|
|
|
38
|
-
- You talk with the AI in the **box under its name**, a few words with each move: it reads them with your move. You can also write to it in its window.
|
|
38
|
+
- You talk with the AI in the **box under its name**, a few words with each move: it reads them with your move. You can also write to it in its window. With Codex, if you write there while it waits for your move, press **Esc** after Enter: else it reads your message only after your move.
|
|
39
39
|
- A **move written** in the chat counts too: `e4`, `Nf3`, with the English letters of the pieces (K Q R B N), or in words.
|
|
40
40
|
- **The AI's name** opens the choice of its chat: Claude Code or Codex, the model (which AI: some are faster, others stronger) and how much it reasons. The more it reasons, the slower it plays. If you change them, the chat starts afresh in the same window: the AI forgets what you talked about, and the game stays as it is.
|
|
41
41
|
- At each **new game**, the chat the page opened starts afresh by itself, once the AI has stopped thinking: a long chat makes the AI slower.
|
package/dist/page/guide/it.md
CHANGED
|
@@ -18,7 +18,7 @@ Nessun motore di scacchi gioca al posto dell'AI: ogni mossa e ogni giudizio sono
|
|
|
18
18
|
|
|
19
19
|
### Per cominciare
|
|
20
20
|
|
|
21
|
-
1. Clicca il **nome dell'AI**, in alto a sinistra, e scegli la chat fra quelle che hai sul computer: **Claude Code** o **Codex**. Si apre in una finestra sua: tienila aperta mentre giochi. La prima volta fa qualche domanda. Se ti fidi della cartella: **S
|
|
21
|
+
1. Clicca il **nome dell'AI**, in alto a sinistra, e scegli la chat fra quelle che hai sul computer: **Claude Code** o **Codex**. Si apre in una finestra sua: tienila aperta mentre giochi. La prima volta fa qualche domanda. Se ti fidi della cartella: **Sì** (in Claude Code è già scelto No: scendi su Yes con le frecce, poi Invio). Con Codex, sugli hook, i due aiutanti di Chesspal: **Trust all and continue**. Su Windows Codex chiede anche se preparare la sua sandbox, lo spazio protetto dove lavora: scegli la prima opzione, e sì quando Windows chiede il permesso.
|
|
22
22
|
2. Premi **Nuova partita**, scegli il colore, l'orologio e il livello dell'AI, e premi **Inizia**.
|
|
23
23
|
3. Muovi: l'AI risponde sulla scacchiera, e con qualche parola sotto il suo nome.
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ Nessun motore di scacchi gioca al posto dell'AI: ogni mossa e ogni giudizio sono
|
|
|
35
35
|
|
|
36
36
|

|
|
37
37
|
|
|
38
|
-
- Parli con l'AI nella **casella sotto il suo nome**, qualche parola a ogni mossa: le legge con la tua mossa. Puoi scriverle anche nella sua finestra.
|
|
38
|
+
- Parli con l'AI nella **casella sotto il suo nome**, qualche parola a ogni mossa: le legge con la tua mossa. Puoi scriverle anche nella sua finestra. Con Codex, se gli scrivi lì mentre aspetta la tua mossa, dopo Invio premi **Esc**: se no legge il messaggio solo dopo la tua mossa.
|
|
39
39
|
- Anche una **mossa scritta** nella chat vale: `e4`, `Nf3`, con le lettere inglesi dei pezzi (K Q R B N), o a parole.
|
|
40
40
|
- **Il nome dell'AI** apre la scelta della chat: Claude Code o Codex, il modello (quale AI: alcune sono più veloci, altre più forti) e quanto ragiona. Più ragiona, più gioca lenta. Se cambi, la chat riparte da capo nella stessa finestra: l'AI dimentica la chiacchierata di prima, la partita resta com'è.
|
|
41
41
|
- A ogni **nuova partita** la chat aperta dalla pagina riparte da capo da sola, se l'AI ha finito di pensare: una chat lunga rende l'AI più lenta.
|
|
Binary file
|