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,15 +1,16 @@
|
|
|
1
1
|
// The analyst's tool (D-21, D-68): a game over, move by move in one call, facts only and never a judgment, with the analyst's instructions once in a chat.
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
2
|
import { z } from "zod";
|
|
4
|
-
import { clockOfComment, clockText, closingOf, Game, gameReviewOf, levelOf, materialOf, readPgn, reviewed, reviewOf, timeControlOfTag, timeControlText, withGameReview, } from "../core/index.js";
|
|
5
|
-
import { keepToStudy, lastChatStart, planOf, rewriteGame, startAnalysis } from "../games/index.js";
|
|
3
|
+
import { clockOfComment, clockText, closingOf, Game, gameReviewOf, isReviewed, leadOf, leadText, levelOf, materialOf, readPgn, reviewed, reviewOf, timeControlOfTag, timeControlText, withGameReview, withLead, } from "../core/index.js";
|
|
4
|
+
import { isGameName, keepToStudy, lastChatStart, planOf, rewriteGame, startAnalysis } from "../games/index.js";
|
|
5
|
+
import { analystAgent, instructions } from "../host-files/index.js";
|
|
6
6
|
import { activeProfile, gameFolder } from "../profiles/index.js";
|
|
7
7
|
import { bookOpening, bookReaches } from "./opening-book.js";
|
|
8
8
|
import { defineCommand } from "./registry.js";
|
|
9
|
+
import { speechLine, speechOf } from "./speech.js";
|
|
9
10
|
import { gameToStudy, knownTag, moveNumbers, namedGame, noGameGoingOn } from "./study.js";
|
|
10
11
|
import { balanceText, colourName, riskLine } from "./view.js";
|
|
11
12
|
// The analyst's instructions, read at the first analysis.
|
|
12
|
-
let
|
|
13
|
+
let analystText;
|
|
13
14
|
/** A game over move by move, for the analyst (D-68): the current one, one of the archive or kept to study, or one the player brings as a PGN, kept to study. */
|
|
14
15
|
export const analyseGame = defineCommand({
|
|
15
16
|
name: "analyse_game",
|
|
@@ -22,20 +23,37 @@ export const analyseGame = defineCommand({
|
|
|
22
23
|
game: z.string().optional().describe("a game's name, as past_games, study_games or analyse_game writes it"),
|
|
23
24
|
pgn: z.string().optional().describe("a game played elsewhere, in PGN: its tags and its moves, with their [%clk] and a review exported from the board page if it has them; other comments and evals can go"),
|
|
24
25
|
}),
|
|
26
|
+
sharedWith: [analystAgent],
|
|
25
27
|
run: ({ game, pgn }, context) => {
|
|
26
28
|
if (game !== undefined && pgn !== undefined) {
|
|
27
29
|
throw new Error("Give game or pgn, not both: game names a game of the archive, pgn brings one played elsewhere.");
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
// A game's name given as pgn, as an AI often gives it, is that game (Priya).
|
|
32
|
+
const named = pgn !== undefined && isGameName(pgn) ? pgn.trim() : game;
|
|
33
|
+
const studied = pgn === undefined || named !== game ? gameToStudy(context, named) : importedGame(context, pgn);
|
|
34
|
+
// One analyst a game: the chat goes over the review there is (Stefano).
|
|
35
|
+
if (context.agent === analystAgent && isReviewed(studied.record)) {
|
|
36
|
+
throw new Error("Stop now: this game has its review already. End with one line for the AI that started you: go over that review with the player.");
|
|
37
|
+
}
|
|
30
38
|
const analysis = analysisOf(studied, activeProfile(context.home()).name);
|
|
31
39
|
if (studied.name !== undefined) {
|
|
32
40
|
markBook(context, { record: studied.record, name: studied.name }, analysis.moves);
|
|
33
41
|
startAnalysis(gameFolder(context.home()), studied.name, context.now());
|
|
34
42
|
}
|
|
43
|
+
// The analyst's agent has its instructions in its own file, and the player's language in its prompt or, when Chesspal knows it, here (D-89).
|
|
44
|
+
if (context.agent === analystAgent) {
|
|
45
|
+
const speech = speechOf(gameFolder(context.home()), context.language);
|
|
46
|
+
return speech === undefined ? analysis : { ...analysis, speech: speechLine(speech) };
|
|
47
|
+
}
|
|
35
48
|
const told = analystInstructions(context);
|
|
36
49
|
return told === undefined ? analysis : { instructions: told, ...analysis };
|
|
37
50
|
},
|
|
38
|
-
format: (analysis) => [
|
|
51
|
+
format: (analysis) => [
|
|
52
|
+
...(analysis.instructions === undefined ? [] : [analysis.instructions, ""]),
|
|
53
|
+
...(analysis.speech === undefined ? [] : [analysis.speech]),
|
|
54
|
+
...analysisHead(analysis),
|
|
55
|
+
...analysis.moves.map(moveLine),
|
|
56
|
+
].join("\n"),
|
|
39
57
|
});
|
|
40
58
|
/** A game the player brings as a PGN, checked move by move and kept to study with its clock and Chesspal's own review: no other comment, no engine's evaluation (D-6). */
|
|
41
59
|
function importedGame(context, pgn) {
|
|
@@ -49,14 +67,16 @@ function importedGame(context, pgn) {
|
|
|
49
67
|
throw new Error(records.length === 0 ? "The PGN holds no game: give it whole, its tags and its moves." : `The PGN holds ${records.length} games: give one at a time.`);
|
|
50
68
|
}
|
|
51
69
|
const [{ tags, moves }] = records;
|
|
52
|
-
// A game analysed here and exported comes back with its review
|
|
70
|
+
// A game analysed here and exported comes back with its review: Chesspal's own marks stay, as the clock; any other comment goes.
|
|
53
71
|
const brought = {
|
|
54
72
|
tags,
|
|
55
73
|
moves: moves.map(({ san, comment }) => {
|
|
56
74
|
const clock = /\[%clk [^\]]*\]/.exec(comment ?? "")?.[0];
|
|
57
75
|
const move = clock === undefined ? { san } : { san, comment: clock };
|
|
58
76
|
const review = reviewOf(comment);
|
|
59
|
-
|
|
77
|
+
const lead = leadOf(comment);
|
|
78
|
+
const kept = review === undefined ? move : reviewed(move, review);
|
|
79
|
+
return lead === undefined ? kept : withLead(kept, lead);
|
|
60
80
|
}),
|
|
61
81
|
};
|
|
62
82
|
const summary = gameReviewOf(records[0]);
|
|
@@ -72,7 +92,7 @@ function importedGame(context, pgn) {
|
|
|
72
92
|
* last one the book knows, where the AI gave none; a game from another position has none.
|
|
73
93
|
*/
|
|
74
94
|
function markBook(context, { record, name }, moves) {
|
|
75
|
-
const inBook =
|
|
95
|
+
const inBook = bookPliesOfFacts(record, moves);
|
|
76
96
|
const unmarked = record.moves.slice(0, inBook).some((move) => reviewOf(move.comment)?.badge === undefined);
|
|
77
97
|
if (!unmarked) {
|
|
78
98
|
return;
|
|
@@ -83,8 +103,8 @@ function markBook(context, { record, name }, moves) {
|
|
|
83
103
|
});
|
|
84
104
|
rewriteGame(gameFolder(context.home()), name, { ...record, moves: marked });
|
|
85
105
|
}
|
|
86
|
-
/** How many plies from the start the opening book knows
|
|
87
|
-
function
|
|
106
|
+
/** How many plies from the start the opening book knows, as opening-book's bookPlies counts them, read from the facts already found: none from another position. */
|
|
107
|
+
function bookPliesOfFacts(record, moves) {
|
|
88
108
|
const firstOut = moves.findIndex((move) => move.outOfBook);
|
|
89
109
|
return record.tags.FEN !== undefined ? 0 : firstOut >= 0 ? firstOut : moves.length;
|
|
90
110
|
}
|
|
@@ -108,7 +128,7 @@ function analysisOf({ record, name }, playerName) {
|
|
|
108
128
|
termination: ended?.termination,
|
|
109
129
|
opening: opening === undefined ? undefined : `${opening.eco} ${opening.name}`,
|
|
110
130
|
inBook: opening !== undefined && !moves.some((move) => move.outOfBook) ? true : undefined,
|
|
111
|
-
bookTo: opening === undefined ? undefined : moves[
|
|
131
|
+
bookTo: opening === undefined ? undefined : moves[bookPliesOfFacts(record, moves) - 1]?.move,
|
|
112
132
|
review: [gameReviewOf(record), closingOf(record)].filter((word) => word !== undefined).join(" ") || undefined,
|
|
113
133
|
name,
|
|
114
134
|
start: tags.FEN === undefined ? undefined : { fen: tags.FEN, ...start },
|
|
@@ -155,6 +175,7 @@ function movesOf(record, ai) {
|
|
|
155
175
|
said: side === ai ? saidIn(comment) : undefined,
|
|
156
176
|
review: reviewOf(comment),
|
|
157
177
|
plan: side === ai ? planOf(comment) : undefined,
|
|
178
|
+
lead: estimateOf(comment),
|
|
158
179
|
};
|
|
159
180
|
if (clock !== undefined) {
|
|
160
181
|
left[side] = clock;
|
|
@@ -167,7 +188,7 @@ function movesOf(record, ai) {
|
|
|
167
188
|
return { start, moves, outcome: game.outcome };
|
|
168
189
|
}
|
|
169
190
|
/** The head of the analysis: who played, when and how fast, how it ended, the opening, and the game's name for look and position. */
|
|
170
|
-
function
|
|
191
|
+
function analysisHead(analysis) {
|
|
171
192
|
const { ai, level, player } = analysis;
|
|
172
193
|
const named = (side) => `${side === "white" ? analysis.white : analysis.black}${side === player ? " (the player)" : ""}`;
|
|
173
194
|
const players = ai === undefined
|
|
@@ -185,7 +206,7 @@ function headLines(analysis) {
|
|
|
185
206
|
const review = analysis.review === undefined ? [] : [`Your review: ${analysis.review}`];
|
|
186
207
|
return [head, ...opening, ...review, `${name}.`, ...start];
|
|
187
208
|
}
|
|
188
|
-
/** 12...Nxe5: took P; spent 0:12, 4:32 left; material White +1; at risk: undefended Black Nf6; mate in one: Qh7#; out of book; you said "…"; your plan:
|
|
209
|
+
/** 12...Nxe5: took P; spent 0:12, 4:32 left; material White +1; at risk: undefended Black Nf6; mate in one: Qh7#; out of book; you said "…"; your plan: …; ahead -1.5 */
|
|
189
210
|
function moveLine(move) {
|
|
190
211
|
const left = move.left === undefined ? undefined : `${clockText(move.left)} left`;
|
|
191
212
|
const parts = [
|
|
@@ -200,6 +221,7 @@ function moveLine(move) {
|
|
|
200
221
|
move.plan === undefined ? undefined : `your plan: ${move.plan}`,
|
|
201
222
|
// The book's badge alone is Chesspal's, not the AI's word: the line says the move left the book where it did.
|
|
202
223
|
move.review === undefined || (move.review.badge === "book" && move.review.note === undefined) ? undefined : reviewLine(move.review),
|
|
224
|
+
move.lead === undefined ? undefined : `ahead ${move.lead}`,
|
|
203
225
|
].filter((part) => part !== undefined);
|
|
204
226
|
return parts.length === 0 ? move.move : `${move.move}: ${parts.join("; ")}`;
|
|
205
227
|
}
|
|
@@ -213,8 +235,13 @@ function analystInstructions(context) {
|
|
|
213
235
|
return undefined;
|
|
214
236
|
}
|
|
215
237
|
context.told.add(key);
|
|
216
|
-
|
|
217
|
-
return
|
|
238
|
+
analystText ??= `${instructions("analyst")}\n${instructions("analysis")}`;
|
|
239
|
+
return analystText;
|
|
240
|
+
}
|
|
241
|
+
/** Who is ahead after a move, as the AI estimated it with annotate_game or the coach during the game (D-90). */
|
|
242
|
+
function estimateOf(comment) {
|
|
243
|
+
const lead = leadOf(comment);
|
|
244
|
+
return lead === undefined ? undefined : leadText(lead);
|
|
218
245
|
}
|
|
219
246
|
/** The mates in one the side to move has; none when it has none. */
|
|
220
247
|
function matesOf(facts) {
|
|
@@ -7,6 +7,9 @@ interface Annotated {
|
|
|
7
7
|
closing: boolean;
|
|
8
8
|
/** The opening book's moves still without a note of the AI's, as analyse_game numbers them. */
|
|
9
9
|
bookUnnoted: string[];
|
|
10
|
+
/** How many moves took who is ahead after them, and those still without it, as analyse_game numbers them (D-90). */
|
|
11
|
+
estimated: number;
|
|
12
|
+
unestimated: string[];
|
|
10
13
|
}
|
|
11
14
|
/** The AI's badges and notes on a game over, and its word on the whole game, written into the game's PGN (D-69). */
|
|
12
15
|
export declare const annotateGame: import("./registry.ts").Command<z.ZodObject<{
|
|
@@ -29,6 +32,7 @@ export declare const annotateGame: import("./registry.ts").Command<z.ZodObject<{
|
|
|
29
32
|
arrows: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
30
33
|
squares: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
31
34
|
}, z.core.$strip>>>;
|
|
35
|
+
ahead: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
|
|
32
36
|
summary: z.ZodOptional<z.ZodString>;
|
|
33
37
|
closing: z.ZodOptional<z.ZodString>;
|
|
34
38
|
}, z.core.$strip>, Annotated>;
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
// The AI's review of a game over (D-69): a badge on each move that matters, as chess.com marks them, with a note when it needs one, and its word on the
|
|
2
2
|
// whole game, kept in the game's PGN for the board page. The judgment is the AI's alone: Chesspal gives no criteria (D-6, D-43).
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { annotatorOf, badges, reviewed, reviewOf, withAnnotator, withGameReview } from "../core/index.js";
|
|
4
|
+
import { annotatorOf, badges, Game, leadOf, reviewed, reviewOf, withAnnotator, withGameReview, withLead } from "../core/index.js";
|
|
5
5
|
import { gameInAnalysis, rewriteGame, startAnalysis } from "../games/index.js";
|
|
6
|
+
import { analystAgent } from "../host-files/index.js";
|
|
6
7
|
import { gameFolder } from "../profiles/index.js";
|
|
7
8
|
import { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.js";
|
|
9
|
+
import { leadFrom, leadInput } from "./lead-input.js";
|
|
8
10
|
import { defineCommand } from "./registry.js";
|
|
9
11
|
import { moveIndex, moveNumbers, namedGame, noGameGoingOn } from "./study.js";
|
|
10
12
|
const summaryLimit = 1500;
|
|
11
13
|
const closingLimit = 400;
|
|
14
|
+
// How many moves still without who is ahead the answer names; the rest it counts.
|
|
15
|
+
const unestimatedShown = 10;
|
|
12
16
|
/** The AI's badges and notes on a game over, and its word on the whole game, written into the game's PGN (D-69). */
|
|
13
17
|
export const annotateGame = defineCommand({
|
|
14
18
|
name: "annotate_game",
|
|
15
19
|
description: "Keeps your review of a game that is over for the board page: on each move that matters a badge, as chess.com has them, with a short note "
|
|
16
20
|
+ "when it needs one, or a note alone on a move worth a word without a badge; your word on the whole game; and a last short word on how it ended, "
|
|
17
|
-
+ "its win or loss. The judgment is yours: mark the moves you would point out, not every move. A note can draw arrows and colour squares on the board to show what it says. The opening book's moves have their book badge already: give each a short note too, what it is for, in the player's words; without one the page shows the opening's name from the book, in English. A move marked again takes its new badge and note. Notes in the player's language and in words, as in the chat; **two asterisks** put the few words that matter in bold, a square or a file too (**g7**, the **h** pawn). Without game, the game analyse_game gave last. Never "
|
|
18
|
-
+ 'while a game goes on. Example: annotate_game {notes: [{move: "12...", badge: "blunder", note: "leaves the queen to the knight"}],
|
|
21
|
+
+ "its win or loss; and who is ahead after every move as ahead, for the page's bar and chart. The judgment is yours: mark the moves you would point out, not every move. A note can draw arrows and colour squares on the board to show what it says. The opening book's moves have their book badge already: give each a short note too, what it is for, in the player's words; without one the page shows the opening's name from the book, in English. A move marked again takes its new badge and note. Notes in the player's language and in words, as in the chat; **two asterisks** put the few words that matter in bold, a square or a file too (**g7**, the **h** pawn). Without game, the game analyse_game gave last. Never "
|
|
22
|
+
+ 'while a game goes on. Example: annotate_game {notes: [{move: "12...", badge: "blunder", note: "leaves the queen to the knight"}], ahead: {"1.": 0.3, "1...": 0.2, "12...": 3.5, "13.": "#4"}, '
|
|
23
|
+
+ 'summary: "…", closing: "…"}.',
|
|
19
24
|
input: z.object({
|
|
20
25
|
game: z.string().optional().describe("the game's name, as analyse_game writes it; without it, the game analysed last"),
|
|
21
26
|
notes: z.array(z.object({
|
|
@@ -25,14 +30,23 @@ export const annotateGame = defineCommand({
|
|
|
25
30
|
arrows: arrowsInput,
|
|
26
31
|
squares: squaresInput,
|
|
27
32
|
})).optional().describe("the moves that matter, each with its badge, its note, or both"),
|
|
33
|
+
ahead: z.record(z.string(), leadInput).optional().describe("who is ahead after each move, by the move as analyse_game numbers it: pawns from White's side as chess.com shows it, 1.5 for White, -3 for Black, 0 for "
|
|
34
|
+
+ 'even, or a mate you see in moves of the side that mates, "#3" for White\'s, "#-3" for Black\'s; every move, in one call or a few; a move given again '
|
|
35
|
+
+ "takes its new figure"),
|
|
28
36
|
summary: z.string().max(summaryLimit).optional().describe("your word on the whole game: a short paragraph, with a few points as lines starting with - if it helps; the moves have their own notes"),
|
|
29
37
|
closing: z.string().max(closingLimit).optional().describe("your last word on how the game ended, its win or loss, in a sentence or two: the page shows it at the end"),
|
|
30
38
|
}),
|
|
31
|
-
|
|
39
|
+
sharedWith: [analystAgent],
|
|
40
|
+
run: async ({ game, notes = [], ahead = {}, summary, closing }, context) => {
|
|
32
41
|
noGameGoingOn(context);
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
const estimates = Object.entries(ahead);
|
|
43
|
+
if (notes.length === 0 && estimates.length === 0 && summary === undefined && closing === undefined) {
|
|
44
|
+
throw new Error("Give notes on the moves that matter, who is ahead after each move as ahead, your word on the whole game as summary, your last word on the ending as "
|
|
45
|
+
+ "closing, or all of them. Nothing changed.");
|
|
35
46
|
}
|
|
47
|
+
// Who reviews it, before the game is read: no wait between reading and writing it, in which the coach's judgment or another review written meanwhile
|
|
48
|
+
// would be lost (Federico).
|
|
49
|
+
const caller = await context.caller?.();
|
|
36
50
|
const studied = game === undefined ? gameAnalysed(context) : namedGame(context, game);
|
|
37
51
|
// Every move is checked before the game is written: all or nothing.
|
|
38
52
|
const moves = [...studied.record.moves];
|
|
@@ -40,13 +54,16 @@ export const annotateGame = defineCommand({
|
|
|
40
54
|
if (badge === undefined && note === undefined) {
|
|
41
55
|
throw new Error(`Give ${move} a badge, a note, or both: arrows and squares go with them. Nothing changed.`);
|
|
42
56
|
}
|
|
43
|
-
const index =
|
|
57
|
+
const index = indexIn(studied.record, move, "notes");
|
|
44
58
|
// A note alone on a move of the book keeps its book badge: a fact Chesspal put there, not the AI's judgment.
|
|
45
59
|
const kept = badge ?? (reviewOf(moves[index].comment)?.badge === "book" ? "book" : undefined);
|
|
46
60
|
moves[index] = reviewed(moves[index], { ...(kept === undefined ? {} : { badge: kept }), ...(note === undefined ? {} : { note }), ...drawingsOf(arrows, squares) });
|
|
47
61
|
}
|
|
62
|
+
for (const [move, lead] of estimates) {
|
|
63
|
+
const index = indexIn(studied.record, move, "ahead");
|
|
64
|
+
moves[index] = withLead(moves[index], leadFrom(lead));
|
|
65
|
+
}
|
|
48
66
|
// Who reviewed it, as far as the host says it for certain (D-81): on the command line, or by a host that names no model over one that did, as it was.
|
|
49
|
-
const caller = await context.caller?.();
|
|
50
67
|
const kept = caller === undefined || (caller.hostOnly === true && annotatorOf(studied.record) !== undefined);
|
|
51
68
|
const marked = { ...(kept ? studied.record : withAnnotator(studied.record, caller)), moves };
|
|
52
69
|
rewriteGame(gameFolder(context.home()), studied.name, summary === undefined && closing === undefined ? marked : withGameReview(marked, { ...(summary === undefined ? {} : { summary }), ...(closing === undefined ? {} : { closing }) }));
|
|
@@ -56,20 +73,56 @@ export const annotateGame = defineCommand({
|
|
|
56
73
|
const review = reviewOf(move.comment);
|
|
57
74
|
return review?.badge === "book" && review.note === undefined ? [numbers[index]] : [];
|
|
58
75
|
});
|
|
59
|
-
return {
|
|
76
|
+
return {
|
|
77
|
+
game: studied.name,
|
|
78
|
+
marked: notes.length,
|
|
79
|
+
summary: summary !== undefined,
|
|
80
|
+
closing: closing !== undefined,
|
|
81
|
+
bookUnnoted,
|
|
82
|
+
estimated: estimates.length,
|
|
83
|
+
// Asked every time, of an older review too: an analyst whose instructions predate the estimates learns of them here.
|
|
84
|
+
unestimated: unestimatedMoves(marked, numbers),
|
|
85
|
+
};
|
|
60
86
|
},
|
|
61
|
-
format: ({ game, marked, summary, closing, bookUnnoted }) => {
|
|
62
|
-
const kept = [
|
|
87
|
+
format: ({ game, marked, summary, closing, bookUnnoted, estimated, unestimated }) => {
|
|
88
|
+
const kept = [
|
|
89
|
+
marked === 0 ? undefined : `${marked} ${marked === 1 ? "move" : "moves"} marked`,
|
|
90
|
+
estimated === 0 ? undefined : `who is ahead after ${estimated} ${estimated === 1 ? "move" : "moves"}`,
|
|
91
|
+
summary ? "your word on the game" : undefined,
|
|
92
|
+
closing ? "your last word" : undefined,
|
|
93
|
+
].filter((part) => part !== undefined);
|
|
63
94
|
const book = bookUnnoted.length === 0
|
|
64
95
|
? []
|
|
65
96
|
: [
|
|
66
97
|
`${bookUnnoted.length === 1 ? "The book's move" : "The book's moves"} ${bookUnnoted.join(" ")} ${bookUnnoted.length === 1 ? "has" : "have"} no note of yours: `
|
|
67
98
|
+ "the page shows the book's English name there. A short note on each, in the player's words, takes its place.",
|
|
68
99
|
];
|
|
69
|
-
|
|
100
|
+
const shown = unestimated.slice(0, unestimatedShown).join(" ");
|
|
101
|
+
const more = unestimated.length > unestimatedShown ? ` and ${unestimated.length - unestimatedShown} more` : "";
|
|
102
|
+
const ahead = unestimated.length === 0 ? [] : [`Who is ahead is still missing after ${shown}${more}: give it with ahead, for the page's bar and chart.`];
|
|
103
|
+
return [`Kept in ${game}: ${[kept.slice(0, -1).join(", "), kept.at(-1)].filter((part) => part !== "").join(" and ")}. The board page shows them.`, ...book, ...ahead].join("\n");
|
|
70
104
|
},
|
|
71
105
|
});
|
|
106
|
+
/** The moves without who is ahead after them, as analyse_game numbers them: all but the last when the board ended the game there, which says it. */
|
|
107
|
+
function unestimatedMoves(record, numbers) {
|
|
108
|
+
const ended = new Game(record).outcome.over;
|
|
109
|
+
return record.moves.flatMap((move, index) => leadOf(move.comment) !== undefined || (ended && index === record.moves.length - 1) ? [] : [numbers[index]]);
|
|
110
|
+
}
|
|
111
|
+
/** A move given in notes or in ahead, as analyse_game numbers it, refused with where it was given (Priya). */
|
|
112
|
+
function indexIn(record, move, field) {
|
|
113
|
+
try {
|
|
114
|
+
return moveIndex(record, move);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
const message = error.message;
|
|
118
|
+
throw new Error(`In ${field}, ${message.charAt(0).toLowerCase()}${message.slice(1)} Nothing changed.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
72
121
|
function gameAnalysed(context) {
|
|
122
|
+
// The game in analysis follows the board page, which the player may turn to another game while an agent works (Federico).
|
|
123
|
+
if (context.agent !== undefined) {
|
|
124
|
+
throw new Error("Give the game's name as game, as analyse_game named it: the board page may have opened another game since. Nothing changed.");
|
|
125
|
+
}
|
|
73
126
|
const analysis = gameInAnalysis(gameFolder(context.home()));
|
|
74
127
|
if (analysis === undefined) {
|
|
75
128
|
throw new Error("No game is in analysis: call analyse_game first, or give the game's name as game. Nothing changed.");
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Side } from "../core/index.ts";
|
|
2
|
+
import { type GameSummary, type LastMove, type MovePlayed } from "./game-summary.ts";
|
|
3
|
+
/** Where the game stands: the AI's position to decide on, with its last move unless the lines before named it; the player's turn; or how it ended. */
|
|
4
|
+
export declare function positionLines(game: GameSummary, withLastMove?: boolean): string[];
|
|
5
|
+
/** The head of a view: where the game stands, the last move unless the lines before named it, the opening, the material as the player sees it, and the FEN. */
|
|
6
|
+
export declare function headLines(game: GameSummary, withLastMove?: boolean): string[];
|
|
7
|
+
/** The last move, as the player is shown it, with its squares: 4.♞g5 (f3-g5). */
|
|
8
|
+
export declare function lastMoveText({ move, from, to }: LastMove): string;
|
|
9
|
+
/**
|
|
10
|
+
* A numbered move as the player is shown it, with the pieces' figurines (D-86): the form alone, without the SAN, which the AI copied into its message
|
|
11
|
+
* (Priya, test of D-86): 4.Ng5 is 4.♞g5.
|
|
12
|
+
*/
|
|
13
|
+
export declare function shownMove(move: string): string;
|
|
14
|
+
/** The lines after a move, which the line before named: its note, where the game stands, a warning. closingLines end them. */
|
|
15
|
+
export declare function moveLines({ note, game, warning }: MovePlayed): string[];
|
|
16
|
+
/** The last lines of a tool's answer: the player's words from the board page, then the next step, which stays the last line. */
|
|
17
|
+
export declare function closingLines(game: GameSummary | undefined, words?: readonly string[], pageClosed?: boolean): string[];
|
|
18
|
+
/** The player's words from the board page, one line each: lines they broke are joined, so no line of theirs reads as one of Chesspal's. */
|
|
19
|
+
export declare function wordLines(words: readonly string[]): string[];
|
|
20
|
+
/**
|
|
21
|
+
* What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
|
|
22
|
+
* board page open in no browser, a new game starts there, never in the chat.
|
|
23
|
+
*/
|
|
24
|
+
export declare function nextStep(game: GameSummary | undefined, words?: readonly string[], pageClosed?: boolean): string;
|
|
25
|
+
/** How a new game starts: the player chooses it in the board page's window, which new_game without choices opens (D-85). */
|
|
26
|
+
export declare const newGameStep = "call new_game without choices: it opens the board page's New game window, where the player chooses the colour, the clock and the level.";
|
|
27
|
+
/** Who plays what, from the AI's side. */
|
|
28
|
+
export declare function sidesLine(ai: Side): string;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// The lines an AI reads in a tool's answer about the current game: where it stands, the move just played, the player's words and what to do next.
|
|
2
|
+
import { figurinesOf, materialOf, sees } from "../core/index.js";
|
|
3
|
+
import { talkLimits } from "../games/index.js";
|
|
4
|
+
import { onQuickModel } from "../host-files/index.js";
|
|
5
|
+
import { analystLine, reviewedLine } from "./agents.js";
|
|
6
|
+
import { clockLine } from "./clock.js";
|
|
7
|
+
import { endText } from "./game-summary.js";
|
|
8
|
+
import { boardView, colourName, compactView, materialText } from "./view.js";
|
|
9
|
+
/** Where the game stands: the AI's position to decide on, with its last move unless the lines before named it; the player's turn; or how it ended. */
|
|
10
|
+
export function positionLines(game, withLastMove = true) {
|
|
11
|
+
if (game.end !== undefined || game.turn !== game.ai) {
|
|
12
|
+
return [whereLine(game), ...offerLines(game), ...clockLines(game), ...moodLines(game)];
|
|
13
|
+
}
|
|
14
|
+
return [
|
|
15
|
+
...headLines(game, withLastMove),
|
|
16
|
+
...(game.facts === undefined ? [] : (sees(game.level, "tactics") ? compactView : boardView)(game.facts, game.turn)),
|
|
17
|
+
...(game.book === undefined ? [] : [`Book moves, most named lines first: ${game.book.join(", ")}`]),
|
|
18
|
+
...(game.plan === undefined ? [] : [`Plan: ${game.plan}`]),
|
|
19
|
+
...(game.thought === undefined ? [] : [thoughtLine(game.thought, game.level)]),
|
|
20
|
+
...moodLines(game),
|
|
21
|
+
...(game.helps === "coached" ? [`${helpsLine} ${coachLine}`] : game.helps === "on" ? [helpsLine] : game.helps === "off now" ? [helpsOffLine] : []),
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
/** The helps turned on: the AI may teach as the analyst does (D-62, D-78), never playing for the player. */
|
|
25
|
+
const helpsLine = "Helps on: the player wants to learn. Tell your intentions, never their best move; draw arrows and squares to show what you say, and write up to "
|
|
26
|
+
+ `${talkLimits.aiHelps} characters.`;
|
|
27
|
+
/**
|
|
28
|
+
* Where a coach runs, a coach, not the AI, judges the player's moves, and the AI knows it, so that it neither makes up what the coach said nor looks for it
|
|
29
|
+
* (D-88, Priya); where none runs, nobody judges them before the analyst (D-80).
|
|
30
|
+
*/
|
|
31
|
+
const coachLine = "A coach, not you, judges their moves and gives them hints on the board page; you don't see what it writes.";
|
|
32
|
+
/** The helps turned off during a game: what the AI said and drew before no longer holds (Priya, round of D-78). */
|
|
33
|
+
const helpsOffLine = `Helps off now: play as at a tournament again: no intentions or arrows, comments of ${talkLimits.ai} characters at most.`;
|
|
34
|
+
/** The head of a view: where the game stands, the last move unless the lines before named it, the opening, the material as the player sees it, and the FEN. */
|
|
35
|
+
export function headLines(game, withLastMove = true) {
|
|
36
|
+
return [
|
|
37
|
+
whereLine(game),
|
|
38
|
+
...offerLines(game),
|
|
39
|
+
...(withLastMove && game.lastMove !== undefined ? [`Last move: ${lastMoveText(game.lastMove)}.`] : []),
|
|
40
|
+
...(game.opening === undefined ? [] : [`Opening: ${game.opening}`]),
|
|
41
|
+
`Material: ${materialText(materialOf(game.fen))}`,
|
|
42
|
+
...clockLines(game),
|
|
43
|
+
`FEN: ${game.fen}`,
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
/** The last move, as the player is shown it, with its squares: 4.♞g5 (f3-g5). */
|
|
47
|
+
export function lastMoveText({ move, from, to }) {
|
|
48
|
+
return `${shownMove(move)} (${from}-${to})`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A numbered move as the player is shown it, with the pieces' figurines (D-86): the form alone, without the SAN, which the AI copied into its message
|
|
52
|
+
* (Priya, test of D-86): 4.Ng5 is 4.♞g5.
|
|
53
|
+
*/
|
|
54
|
+
export function shownMove(move) {
|
|
55
|
+
const [, number = "", san = move] = /^(\d+\.+)(.+)$/.exec(move) ?? [];
|
|
56
|
+
return `${number}${figurinesOf(san)}`;
|
|
57
|
+
}
|
|
58
|
+
/** The lines after a move, which the line before named: its note, where the game stands, a warning. closingLines end them. */
|
|
59
|
+
export function moveLines({ note, game, warning }) {
|
|
60
|
+
return [...(note === undefined ? [] : [`Note: ${note}`]), ...positionLines(game, false), ...(warning === undefined ? [] : [warning])];
|
|
61
|
+
}
|
|
62
|
+
/** The last lines of a tool's answer: the player's words from the board page, then the next step, which stays the last line. */
|
|
63
|
+
export function closingLines(game, words = [], pageClosed = false) {
|
|
64
|
+
return [...wordLines(words), nextStep(game, words, pageClosed)];
|
|
65
|
+
}
|
|
66
|
+
/** The player's words from the board page, one line each: lines they broke are joined, so no line of theirs reads as one of Chesspal's. */
|
|
67
|
+
export function wordLines(words) {
|
|
68
|
+
return words.map((text) => `Player wrote on the board page: «${text.replace(/\s+/g, " ")}»`);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
|
|
72
|
+
* board page open in no browser, a new game starts there, never in the chat.
|
|
73
|
+
*/
|
|
74
|
+
export function nextStep(game, words = [], pageClosed = false) {
|
|
75
|
+
const step = pageClosed ? closedPageGameStep : newGameStep;
|
|
76
|
+
if (game === undefined) {
|
|
77
|
+
return words.length === 0 ? `Next: when the player wants a game, ${step}` : `Next: answer the player with ai_say. When they want a game, ${step}`;
|
|
78
|
+
}
|
|
79
|
+
if (game.end !== undefined) {
|
|
80
|
+
const analysis = game.end.analyst === "offer"
|
|
81
|
+
? ` To go over it, on their yes, ${analystLine(game.end.archive)}`
|
|
82
|
+
: game.end.analyst === "done"
|
|
83
|
+
? ` ${reviewedLine(game.end.archive)}`
|
|
84
|
+
: "";
|
|
85
|
+
return words.length === 0
|
|
86
|
+
? "Next: tell the player how the game ended, with ai_say and how you feel as its mood if they play on the board page; then offer them, in words, another game or "
|
|
87
|
+
+ `to go over this one${analysis === "" ? " (analyse_game)." : `.${analysis}`} For another, ${step}`
|
|
88
|
+
: `Next: answer the player with ai_say.${analysis} When they want another game, ${step}`;
|
|
89
|
+
}
|
|
90
|
+
if (game.turn === game.ai && game.clock?.waiting === true) {
|
|
91
|
+
return "Next: don't move yet: the player starts the game with Start on the board page, and the doorbell wakes you then. End your turn.";
|
|
92
|
+
}
|
|
93
|
+
if (game.turn === game.ai) {
|
|
94
|
+
return words.length === 0
|
|
95
|
+
? "Next: choose your move, then call ai_move with it, your plan and any comment."
|
|
96
|
+
: "Next: choose your move, then call ai_move with it, your plan and a comment that answers the player.";
|
|
97
|
+
}
|
|
98
|
+
if (game.drawOffer === "player") {
|
|
99
|
+
return "Next: accept the draw with end_game draw_agreed, or answer with ai_say and wait for the player's move.";
|
|
100
|
+
}
|
|
101
|
+
return words.length === 0 ? "Next: wait for the player's move, then call player_move with it." : "Next: answer the player with ai_say, then wait for their move.";
|
|
102
|
+
}
|
|
103
|
+
/** How a new game starts: the player chooses it in the board page's window, which new_game without choices opens (D-85). */
|
|
104
|
+
export const newGameStep = "call new_game without choices: it opens the board page's New game window, where the player chooses the colour, the clock and the level.";
|
|
105
|
+
// With the page open in no browser the window cannot open: the player opens the page and presses New game.
|
|
106
|
+
const closedPageGameStep = "they start it on the board page with its New game button, where they choose the colour, the clock and the level: tell them so with "
|
|
107
|
+
+ "the page's address, and never offer to set it up in the chat.";
|
|
108
|
+
/** Who plays what, from the AI's side. */
|
|
109
|
+
export function sidesLine(ai) {
|
|
110
|
+
return `you play ${colourName(ai)}, the player ${colourName(ai === "white" ? "black" : "white")}`;
|
|
111
|
+
}
|
|
112
|
+
/** The draw offer that stands, and how to answer it or what becomes of it (D-65). */
|
|
113
|
+
function offerLines({ drawOffer, turn, ai }) {
|
|
114
|
+
if (drawOffer === "ai") {
|
|
115
|
+
return ["Your draw offer stands until the player moves; if they accept it in the chat, end_game draw_agreed ends the game."];
|
|
116
|
+
}
|
|
117
|
+
if (drawOffer === "player") {
|
|
118
|
+
return [turn === ai ? "The player offers a draw: end_game draw_agreed accepts it; your move declines it." : "The player offers a draw before their move; your next move declines it."];
|
|
119
|
+
}
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
/** What the AI thought on the player's time, saying by which model and that Chesspal checked only that it is legal: the facts do not cover every blunder (Anatolij). */
|
|
123
|
+
function thoughtLine({ move, why }, level) {
|
|
124
|
+
return `Thought on their time ${onQuickModel(level) ? "by a quicker model" : "by your model"} (legal, not checked): ${move}, ${why}`;
|
|
125
|
+
}
|
|
126
|
+
/** The AI's mood while it lasts, and how long its face shows it (D-70). */
|
|
127
|
+
function moodLines({ mood }) {
|
|
128
|
+
if (mood === undefined) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
const shown = mood.left === 0 ? "until your next move" : `for ${mood.left} more of your moves`;
|
|
132
|
+
return [`Your mood: ${mood.mood}, on your face ${shown}, then neutral by itself.`];
|
|
133
|
+
}
|
|
134
|
+
/** Who is to move, or how the game ended. */
|
|
135
|
+
function clockLines(game) {
|
|
136
|
+
return game.clock === undefined ? [] : [clockLine(game.clock, game.ai)];
|
|
137
|
+
}
|
|
138
|
+
function whereLine(game) {
|
|
139
|
+
if (game.end !== undefined) {
|
|
140
|
+
const archived = game.end.archive === undefined ? "" : ` Archived in ${game.end.archive}.`;
|
|
141
|
+
return `Game over: ${endText(game.end)}.${archived}`;
|
|
142
|
+
}
|
|
143
|
+
return `${game.turn === game.ai ? "You" : "Player"} (${colourName(game.turn)}) to move, move ${game.moveNumber}.`;
|
|
144
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** What coach answers: stop, the moves to work on, the words kept and what comes next, or still the player's move; with the game's key and the position. */
|
|
3
|
+
type Coached = {
|
|
4
|
+
stop: "moved" | "waited" | "helps" | "started";
|
|
5
|
+
} | {
|
|
6
|
+
start: string[];
|
|
7
|
+
} | {
|
|
8
|
+
kept: Place;
|
|
9
|
+
then: "lead" | "wait" | "moved" | "over";
|
|
10
|
+
saved: string[];
|
|
11
|
+
hintLeft: boolean;
|
|
12
|
+
} | {
|
|
13
|
+
waiting: Place;
|
|
14
|
+
};
|
|
15
|
+
/** The game, by its key, and the position in it the coach works on. */
|
|
16
|
+
interface Place {
|
|
17
|
+
readonly game: string;
|
|
18
|
+
readonly position: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The coach (D-80, D-88), for the agent that judges the player's moves and gives them hints with the helps on: every fact, never the opponent's plan; its
|
|
22
|
+
* words go to the board page alone, never to the opponent. Its calls are not the chat's, and the server refuses them from the chat. Offered to Claude Code alone.
|
|
23
|
+
*/
|
|
24
|
+
export declare const coach: import("./registry.ts").Command<z.ZodObject<{
|
|
25
|
+
game: z.ZodOptional<z.ZodString>;
|
|
26
|
+
position: z.ZodOptional<z.ZodInt>;
|
|
27
|
+
ahead: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
|
|
28
|
+
badge: z.ZodOptional<z.ZodEnum<{
|
|
29
|
+
brilliant: "brilliant";
|
|
30
|
+
great: "great";
|
|
31
|
+
best: "best";
|
|
32
|
+
excellent: "excellent";
|
|
33
|
+
good: "good";
|
|
34
|
+
inaccuracy: "inaccuracy";
|
|
35
|
+
mistake: "mistake";
|
|
36
|
+
miss: "miss";
|
|
37
|
+
blunder: "blunder";
|
|
38
|
+
}>>;
|
|
39
|
+
note: z.ZodOptional<z.ZodString>;
|
|
40
|
+
hint: z.ZodOptional<z.ZodString>;
|
|
41
|
+
arrows: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
42
|
+
squares: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
43
|
+
done: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
}, z.core.$strip>, Coached>;
|
|
45
|
+
export {};
|