chesspal 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -2
- package/dist/cli/chat-hook.d.ts +2 -0
- package/dist/cli/chat-hook.js +33 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +28 -0
- package/dist/cli/command-line.d.ts +7 -0
- package/dist/cli/command-line.js +177 -0
- package/dist/commands/agents.d.ts +26 -0
- package/dist/commands/agents.js +72 -0
- package/dist/commands/ai-move.d.ts +28 -0
- package/dist/commands/ai-move.js +85 -0
- package/dist/commands/ai-say.d.ts +22 -0
- package/dist/commands/ai-say.js +58 -0
- package/dist/commands/analyse-game.d.ts +65 -0
- package/dist/commands/analyse-game.js +265 -0
- package/dist/commands/annotate-game.d.ts +39 -0
- package/dist/commands/annotate-game.js +131 -0
- package/dist/commands/answer-lines.d.ts +28 -0
- package/dist/commands/answer-lines.js +144 -0
- package/dist/commands/clock.d.ts +15 -0
- package/dist/commands/clock.js +45 -0
- package/dist/commands/coach.d.ts +45 -0
- package/dist/commands/coach.js +344 -0
- package/dist/commands/context.d.ts +67 -0
- package/dist/commands/context.js +37 -0
- package/dist/commands/delete-game.d.ts +7 -0
- package/dist/commands/delete-game.js +30 -0
- package/dist/commands/delete-review.d.ts +8 -0
- package/dist/commands/delete-review.js +34 -0
- package/dist/commands/draw-offer.d.ts +10 -0
- package/dist/commands/draw-offer.js +43 -0
- package/dist/commands/drawings.d.ts +13 -0
- package/dist/commands/drawings.js +25 -0
- package/dist/commands/end-game.d.ts +23 -0
- package/dist/commands/end-game.js +67 -0
- package/dist/commands/export-game.d.ts +14 -0
- package/dist/commands/export-game.js +27 -0
- package/dist/commands/game-summary.d.ts +78 -0
- package/dist/commands/game-summary.js +102 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.js +64 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +73 -0
- package/dist/commands/lead-input.d.ts +8 -0
- package/dist/commands/lead-input.js +21 -0
- package/dist/commands/levels.d.ts +9 -0
- package/dist/commands/levels.js +25 -0
- package/dist/commands/look.d.ts +31 -0
- package/dist/commands/look.js +64 -0
- package/dist/commands/new-game.d.ts +68 -0
- package/dist/commands/new-game.js +218 -0
- package/dist/commands/opening-book.d.ts +13 -0
- package/dist/commands/opening-book.js +84 -0
- package/dist/commands/page-link.d.ts +11 -0
- package/dist/commands/page-link.js +28 -0
- package/dist/commands/past-games.d.ts +11 -0
- package/dist/commands/past-games.js +47 -0
- package/dist/commands/play-move.d.ts +37 -0
- package/dist/commands/play-move.js +94 -0
- package/dist/commands/player-move.d.ts +13 -0
- package/dist/commands/player-move.js +51 -0
- package/dist/commands/player-say.d.ts +5 -0
- package/dist/commands/player-say.js +16 -0
- package/dist/commands/ponder.d.ts +52 -0
- package/dist/commands/ponder.js +179 -0
- package/dist/commands/position.d.ts +14 -0
- package/dist/commands/position.js +130 -0
- package/dist/commands/profiles.d.ts +69 -0
- package/dist/commands/profiles.js +163 -0
- package/dist/commands/registry.d.ts +59 -0
- package/dist/commands/registry.js +62 -0
- package/dist/commands/say.d.ts +16 -0
- package/dist/commands/say.js +84 -0
- package/dist/commands/speech.d.ts +8 -0
- package/dist/commands/speech.js +20 -0
- package/dist/commands/status.d.ts +35 -0
- package/dist/commands/status.js +128 -0
- package/dist/commands/study-games.d.ts +5 -0
- package/dist/commands/study-games.js +40 -0
- package/dist/commands/study.d.ts +38 -0
- package/dist/commands/study.js +106 -0
- package/dist/commands/view.d.ts +14 -0
- package/dist/commands/view.js +125 -0
- package/dist/core/board-text.d.ts +7 -0
- package/dist/core/board-text.js +18 -0
- package/dist/core/book.json +1 -0
- package/dist/core/clock.d.ts +45 -0
- package/dist/core/clock.js +109 -0
- package/dist/core/facts.d.ts +55 -0
- package/dist/core/facts.js +175 -0
- package/dist/core/game.d.ts +95 -0
- package/dist/core/game.js +352 -0
- package/dist/core/illegal.d.ts +19 -0
- package/dist/core/illegal.js +141 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +12 -0
- package/dist/core/lead.d.ts +15 -0
- package/dist/core/lead.js +40 -0
- package/dist/core/levels.d.ts +20 -0
- package/dist/core/levels.js +33 -0
- package/dist/core/material.d.ts +9 -0
- package/dist/core/material.js +12 -0
- package/dist/core/moods.d.ts +21 -0
- package/dist/core/moods.js +45 -0
- package/dist/core/openings.d.ts +35 -0
- package/dist/core/openings.js +98 -0
- package/dist/core/openings.json +1 -0
- package/dist/core/pgn.d.ts +10 -0
- package/dist/core/pgn.js +213 -0
- package/dist/core/profile.d.ts +117 -0
- package/dist/core/profile.js +232 -0
- package/dist/core/record.d.ts +21 -0
- package/dist/core/record.js +25 -0
- package/dist/core/review.d.ts +80 -0
- package/dist/core/review.js +146 -0
- package/dist/games/ai-model.d.ts +5 -0
- package/dist/games/ai-model.js +15 -0
- package/dist/games/analysis.d.ts +12 -0
- package/dist/games/analysis.js +26 -0
- package/dist/games/archive.d.ts +63 -0
- package/dist/games/archive.js +242 -0
- package/dist/games/chat-start.d.ts +16 -0
- package/dist/games/chat-start.js +26 -0
- package/dist/games/coach.d.ts +57 -0
- package/dist/games/coach.js +144 -0
- package/dist/games/current-game.d.ts +56 -0
- package/dist/games/current-game.js +134 -0
- package/dist/games/files.d.ts +9 -0
- package/dist/games/files.js +53 -0
- package/dist/games/index.d.ts +11 -0
- package/dist/games/index.js +12 -0
- package/dist/games/plans.d.ts +9 -0
- package/dist/games/plans.js +23 -0
- package/dist/games/table-talk.d.ts +12 -0
- package/dist/games/table-talk.js +25 -0
- package/dist/games/talk.d.ts +36 -0
- package/dist/games/talk.js +131 -0
- package/dist/games/thoughts.d.ts +15 -0
- package/dist/games/thoughts.js +60 -0
- package/dist/home/data-folder.d.ts +12 -0
- package/dist/home/data-folder.js +55 -0
- package/dist/home/index.d.ts +2 -0
- package/dist/home/index.js +3 -0
- package/dist/home/open-folder.d.ts +2 -0
- package/dist/home/open-folder.js +14 -0
- package/dist/host-files/agents.d.ts +20 -0
- package/dist/host-files/agents.js +53 -0
- package/dist/host-files/host-files.d.ts +15 -0
- package/dist/host-files/host-files.js +182 -0
- package/dist/host-files/index.d.ts +2 -0
- package/dist/host-files/index.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/page/app/anchor.js +88 -0
- package/dist/page/app/app.js +458 -0
- package/dist/page/app/keys.js +94 -0
- package/dist/page/app/phase.js +30 -0
- package/dist/page/app/served.js +73 -0
- package/dist/page/avatars/avatars.js +19 -0
- package/dist/page/avatars/beanie.webp +0 -0
- package/dist/page/avatars/beard.webp +0 -0
- package/dist/page/avatars/bun.webp +0 -0
- package/dist/page/avatars/cat.webp +0 -0
- package/dist/page/avatars/curls.webp +0 -0
- package/dist/page/avatars/glasses.webp +0 -0
- package/dist/page/avatars/moustache.webp +0 -0
- package/dist/page/avatars/owl.webp +0 -0
- package/dist/page/avatars/short-hair.webp +0 -0
- package/dist/page/avatars/silver-bob.webp +0 -0
- package/dist/page/avatars/thought.js +10 -0
- package/dist/page/avatars/torretta.js +38 -0
- package/dist/page/base.css +160 -0
- package/dist/page/board/board.css +959 -0
- package/dist/page/board/board.js +125 -0
- package/dist/page/board/explain.js +58 -0
- package/dist/page/board/marks.js +160 -0
- package/dist/page/board/motion.js +71 -0
- package/dist/page/board/pointer.js +239 -0
- package/dist/page/board/squares.js +26 -0
- package/dist/page/dialogs/analyses.js +195 -0
- package/dist/page/dialogs/dialog.js +26 -0
- package/dist/page/dialogs/dialogs.css +1228 -0
- package/dist/page/dialogs/new-game.js +42 -0
- package/dist/page/dialogs/profiles.js +152 -0
- package/dist/page/dialogs/search.js +31 -0
- package/dist/page/dialogs/settings.js +42 -0
- package/dist/page/dialogs/studied.js +39 -0
- package/dist/page/dialogs/study.js +51 -0
- package/dist/page/game/badges.js +50 -0
- package/dist/page/game/browse.js +17 -0
- package/dist/page/game/card-fold.js +40 -0
- package/dist/page/game/column.js +51 -0
- package/dist/page/game/ending.js +73 -0
- package/dist/page/game/game.css +577 -0
- package/dist/page/game/move-list.js +72 -0
- package/dist/page/game/opening-book.js +14 -0
- package/dist/page/game/plies.js +82 -0
- package/dist/page/game/result-tile.js +14 -0
- package/dist/page/helps/helps.css +689 -0
- package/dist/page/helps/helps.js +220 -0
- package/dist/page/helps/view.js +127 -0
- package/dist/page/layout/fold-tab.js +11 -0
- package/dist/page/layout/folds.js +43 -0
- package/dist/page/layout/header.js +28 -0
- package/dist/page/layout/layout.css +834 -0
- package/dist/page/lead/lead.js +82 -0
- package/dist/page/lead/view.js +38 -0
- package/dist/page/main.js +8 -0
- package/dist/page/pieces/CREDITS.md +19 -0
- package/dist/page/pieces/capablanca-small.svg +20 -0
- package/dist/page/pieces/capablanca.svg +22 -0
- package/dist/page/pieces/nitida-small.svg +20 -0
- package/dist/page/pieces/nitida.svg +21 -0
- package/dist/page/pieces/pieces.js +28 -0
- package/dist/page/pieces/torretta-small.svg +20 -0
- package/dist/page/pieces/torretta.svg +21 -0
- package/dist/page/players/card.js +56 -0
- package/dist/page/players/clock-time.js +12 -0
- package/dist/page/players/model-name.js +20 -0
- package/dist/page/profile/profile.js +99 -0
- package/dist/page/profile/settings.js +77 -0
- package/dist/page/sounds/capture.ogg +0 -0
- package/dist/page/sounds/check.ogg +0 -0
- package/dist/page/sounds/draw.ogg +0 -0
- package/dist/page/sounds/flag.ogg +0 -0
- package/dist/page/sounds/mate.ogg +0 -0
- package/dist/page/sounds/move.ogg +0 -0
- package/dist/page/sounds/offer.ogg +0 -0
- package/dist/page/sounds/resign.ogg +0 -0
- package/dist/page/sounds/sounds.js +105 -0
- package/dist/page/style.css +9 -0
- package/dist/page/talk/talk.css +958 -0
- package/dist/page/talk/talk.js +165 -0
- package/dist/page/talk/voice.js +38 -0
- package/dist/page/text.js +711 -0
- package/dist/page/theme.css +292 -0
- package/dist/profiles/avatar.d.ts +23 -0
- package/dist/profiles/avatar.js +84 -0
- package/dist/profiles/front-matter.d.ts +9 -0
- package/dist/profiles/front-matter.js +33 -0
- package/dist/profiles/index.d.ts +3 -0
- package/dist/profiles/index.js +5 -0
- package/dist/profiles/profiles.d.ts +69 -0
- package/dist/profiles/profiles.js +254 -0
- package/dist/profiles/settings.d.ts +12 -0
- package/dist/profiles/settings.js +46 -0
- package/dist/server/bell.d.ts +68 -0
- package/dist/server/bell.js +225 -0
- package/dist/server/caller.d.ts +21 -0
- package/dist/server/caller.js +132 -0
- package/dist/server/mcp.d.ts +13 -0
- package/dist/server/mcp.js +207 -0
- package/dist/server/page/analyses.d.ts +23 -0
- package/dist/server/page/analyses.js +65 -0
- package/dist/server/page/files.d.ts +5 -0
- package/dist/server/page/files.js +92 -0
- package/dist/server/page/game.d.ts +6 -0
- package/dist/server/page/game.js +381 -0
- package/dist/server/page/http.d.ts +29 -0
- package/dist/server/page/http.js +68 -0
- package/dist/server/page/page.d.ts +41 -0
- package/dist/server/page/page.js +229 -0
- package/dist/server/page/profiles.d.ts +19 -0
- package/dist/server/page/profiles.js +215 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/instructions/analysis.md +4 -0
- package/instructions/analyst-agent.md +8 -0
- package/instructions/analyst.md +4 -0
- package/instructions/coach.md +11 -0
- package/instructions/opponent.md +15 -0
- package/instructions/ponder.md +9 -0
- package/package.json +66 -5
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** What a command runs with, besides its input: given by the command line or the MCP server, and by the tests. */
|
|
2
|
+
export interface Context {
|
|
3
|
+
/** The folder Chesspal runs in: the player's, where the AI host started it, which keeps only the host's files (D-71). */
|
|
4
|
+
folder: string;
|
|
5
|
+
/** The player's data folder (D-71), with a profile in it: the profiles, their games and the games kept to study, the same for every chat. */
|
|
6
|
+
home: () => string;
|
|
7
|
+
/** A number from 0 to 1, as Math.random gives: the colour drawn for the AI. */
|
|
8
|
+
random: () => number;
|
|
9
|
+
/** The time now: when a game starts. */
|
|
10
|
+
now: () => Date;
|
|
11
|
+
/** Waits a while, in milliseconds: a tool that waits for the player, as ponder does; the tests move their clock instead. */
|
|
12
|
+
pause?: (milliseconds: number) => Promise<void>;
|
|
13
|
+
/** The operating system, as process.platform names it: how a host starts the server. */
|
|
14
|
+
platform: string;
|
|
15
|
+
/** The player's language, as the system names it (it-IT): the command line's words to the player, in English when none or another. */
|
|
16
|
+
language?: string;
|
|
17
|
+
/** The address of the board page, when the MCP server serves it (D-47). */
|
|
18
|
+
page?: string;
|
|
19
|
+
/** Whether a browser has the board page open now, when the MCP server serves it. */
|
|
20
|
+
pageOpen?: () => boolean;
|
|
21
|
+
/** Opens a folder in the system's file manager: the data folder, from the board page; whether it could; none in the tests. */
|
|
22
|
+
openFolder?: (folder: string) => Promise<boolean>;
|
|
23
|
+
/** Opens the New game window on the board pages open now, where the player chooses the game: whether one was open. */
|
|
24
|
+
askNewGame?: () => boolean;
|
|
25
|
+
/** The AI host, as it names itself to the MCP server: claude-code, which notes how each chat starts (D-57); none on the command line. */
|
|
26
|
+
host?: () => string | undefined;
|
|
27
|
+
/** The player's words on the board page given to the AI in this process, by line: each comes once. */
|
|
28
|
+
readonly given: Set<string>;
|
|
29
|
+
/** Told when the board page has news that no file holds: words the AI was given, which it shows read (D-53), and the AI's model (D-82). */
|
|
30
|
+
heard?: () => void;
|
|
31
|
+
/** Told when the chat calls a tool, before it runs: the board page hears the chat listens, and the AI's clock, held while nobody did, starts. */
|
|
32
|
+
calls?: () => void;
|
|
33
|
+
/** Told when the AI starts thinking on the player's time (D-72), with the position, and when it is done: the board page shows it. */
|
|
34
|
+
ponders?: (position: SeenPosition | undefined) => void;
|
|
35
|
+
/** The game the chat saw when its last tool ended, its folder and its stamp: the doorbell wakes it only for a game changed since. */
|
|
36
|
+
seen?: string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* The position the last tool that showed the game showed the chat: its moves are refused on a game changed since, by another chat or on the page
|
|
39
|
+
* (D-71); null in a chat no tool has shown the game yet, none outside a chat.
|
|
40
|
+
*/
|
|
41
|
+
positionSeen?: SeenPosition | null | undefined;
|
|
42
|
+
/** The instructions the MCP server gave in this chat, by their name and the chat's start: each comes once a chat (D-68); none on the command line. */
|
|
43
|
+
told?: Set<string>;
|
|
44
|
+
/** Who makes this tool call, as far as the host ties it to the call (D-81); none on the command line. */
|
|
45
|
+
caller?: () => Promise<Caller | undefined>;
|
|
46
|
+
/** The agent that makes this call of a tool it shares with the chat, as the analyst (D-89); none for the chat, or when it cannot be told. */
|
|
47
|
+
agent?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Who made a tool call: the model, else the host's name, never what the AI says of itself; the effort only when the host gives it. */
|
|
50
|
+
export interface Caller {
|
|
51
|
+
readonly annotator: string;
|
|
52
|
+
readonly effort?: string;
|
|
53
|
+
/** The annotator is the host's name: the model is not known. */
|
|
54
|
+
readonly hostOnly?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Where the game a chat sees stands: the game by its start, none when there is none, and how many plies it has. Not by its folder, which a profile
|
|
58
|
+
* completed renames (D-71); a game in another profile started at another time.
|
|
59
|
+
*/
|
|
60
|
+
export interface SeenPosition {
|
|
61
|
+
readonly started?: string;
|
|
62
|
+
readonly plies: number;
|
|
63
|
+
}
|
|
64
|
+
/** The context of a process started in the player's folder: the data folder is found at the first command that needs it, once. */
|
|
65
|
+
export declare function defaultContext(): Context;
|
|
66
|
+
/** A data folder with a profile in it, the first made when there is none, named after the system's account (D-71). */
|
|
67
|
+
export declare function withProfile(folder: string, account: string): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// What every command runs with besides its input, injected by the command line, the MCP server or the tests: the folders, the time, the randomness.
|
|
2
|
+
import { userInfo } from "node:os";
|
|
3
|
+
import { dataFolder, openFolder } from "../home/index.js";
|
|
4
|
+
import { ensureProfile } from "../profiles/index.js";
|
|
5
|
+
/** The context of a process started in the player's folder: the data folder is found at the first command that needs it, once. */
|
|
6
|
+
export function defaultContext() {
|
|
7
|
+
let found;
|
|
8
|
+
const home = () => withProfile(found ??= dataFolder(), accountName());
|
|
9
|
+
return {
|
|
10
|
+
folder: process.cwd(),
|
|
11
|
+
home,
|
|
12
|
+
random: Math.random,
|
|
13
|
+
now: () => new Date(),
|
|
14
|
+
platform: process.platform,
|
|
15
|
+
language: systemLanguage(),
|
|
16
|
+
given: new Set(),
|
|
17
|
+
openFolder: (folder) => openFolder(folder, process.platform),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** A data folder with a profile in it, the first made when there is none, named after the system's account (D-71). */
|
|
21
|
+
export function withProfile(folder, account) {
|
|
22
|
+
ensureProfile(folder, account);
|
|
23
|
+
return folder;
|
|
24
|
+
}
|
|
25
|
+
/** The name the system knows the user by, for the first profile: Player when it has none. */
|
|
26
|
+
function accountName() {
|
|
27
|
+
try {
|
|
28
|
+
return userInfo().username.trim() || "Player";
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return "Player";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** The system's language, as Node reads it: the user's locale on Windows, LANG and LC_ALL elsewhere. */
|
|
35
|
+
function systemLanguage() {
|
|
36
|
+
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
37
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Deletes a game of the profile in use, by its name as past_games or study_games writes it. */
|
|
3
|
+
export declare const deleteGame: import("./registry.ts").Command<z.ZodObject<{
|
|
4
|
+
game: z.ZodString;
|
|
5
|
+
}, z.core.$strip>, {
|
|
6
|
+
deleted: string;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// A game deleted from a profile (D-92): played here or kept to study, with its review. Only the command line and the page delete, never the AI.
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { deleteGame as deleteGameFile, endAnalysis, gameInAnalysis, isOverOnTable, listedName, removeGame } from "../games/index.js";
|
|
4
|
+
import { gameFolder } from "../profiles/index.js";
|
|
5
|
+
import { defineCommand } from "./registry.js";
|
|
6
|
+
/** Deletes a game of the profile in use, by its name as past_games or study_games writes it. */
|
|
7
|
+
export const deleteGame = defineCommand({
|
|
8
|
+
name: "delete_game",
|
|
9
|
+
description: "Deletes a game of the profile in use, played here or kept to study, with its review: it cannot be undone. "
|
|
10
|
+
+ "Example: chesspal delete_game games/2026-11-02-153012.pgn.",
|
|
11
|
+
input: z.object({ game: z.string().min(1).describe("the game, as past_games or study_games writes it") }),
|
|
12
|
+
cliOnly: true,
|
|
13
|
+
destructive: true,
|
|
14
|
+
positionals: ["game"],
|
|
15
|
+
run: ({ game }, context) => {
|
|
16
|
+
const folder = gameFolder(context.home());
|
|
17
|
+
const name = listedName(folder, game);
|
|
18
|
+
// Asked before deleting, so that nothing fails halfway: the game over still on the table would go back to the archive with the next game.
|
|
19
|
+
const onTable = isOverOnTable(folder, name);
|
|
20
|
+
deleteGameFile(folder, name);
|
|
21
|
+
if (gameInAnalysis(folder)?.game === name) {
|
|
22
|
+
endAnalysis(folder);
|
|
23
|
+
}
|
|
24
|
+
if (onTable) {
|
|
25
|
+
removeGame(folder);
|
|
26
|
+
}
|
|
27
|
+
return { deleted: name };
|
|
28
|
+
},
|
|
29
|
+
format: ({ deleted }) => `${deleted} deleted, with its review.`,
|
|
30
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Deletes the review of a game of the profile in use, by its name as past_games or study_games writes it. */
|
|
3
|
+
export declare const deleteReview: import("./registry.ts").Command<z.ZodObject<{
|
|
4
|
+
game: z.ZodString;
|
|
5
|
+
}, z.core.$strip>, {
|
|
6
|
+
game: string;
|
|
7
|
+
reviewed: boolean;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// A game's review deleted (D-92): the game stays, to analyse again, with another model or effort or after an update. Only the command line and the page
|
|
2
|
+
// delete, never the AI.
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { isReviewed, unreviewed } from "../core/index.js";
|
|
5
|
+
import { forgetCoach, gameInAnalysis, isOverOnTable, rewriteGame, startAnalysis } from "../games/index.js";
|
|
6
|
+
import { gameFolder } from "../profiles/index.js";
|
|
7
|
+
import { defineCommand } from "./registry.js";
|
|
8
|
+
import { namedGame } from "./study.js";
|
|
9
|
+
/** Deletes the review of a game of the profile in use, by its name as past_games or study_games writes it. */
|
|
10
|
+
export const deleteReview = defineCommand({
|
|
11
|
+
name: "delete_review",
|
|
12
|
+
description: "Deletes the review of a game of the profile in use: the badges, notes, drawings and estimates on its moves, the word on the game and who "
|
|
13
|
+
+ "signed it, the coach's judgments too; the game stays, to analyse again. Example: chesspal delete_review games/2026-11-02-153012.pgn.",
|
|
14
|
+
input: z.object({ game: z.string().min(1).describe("the game, as past_games or study_games writes it") }),
|
|
15
|
+
cliOnly: true,
|
|
16
|
+
destructive: true,
|
|
17
|
+
idempotent: true,
|
|
18
|
+
positionals: ["game"],
|
|
19
|
+
run: ({ game }, context) => {
|
|
20
|
+
const folder = gameFolder(context.home());
|
|
21
|
+
const { record, name } = namedGame(context, game);
|
|
22
|
+
rewriteGame(folder, name, unreviewed(record));
|
|
23
|
+
// The coach's judgments on the game just over would go back into it.
|
|
24
|
+
if (isOverOnTable(folder, name)) {
|
|
25
|
+
forgetCoach(folder);
|
|
26
|
+
}
|
|
27
|
+
// The page showing the game hears of it, and shows it without its review.
|
|
28
|
+
if (gameInAnalysis(folder)?.game === name) {
|
|
29
|
+
startAnalysis(folder, name, context.now());
|
|
30
|
+
}
|
|
31
|
+
return { game: name, reviewed: isReviewed(record) };
|
|
32
|
+
},
|
|
33
|
+
format: ({ game, reviewed }) => `${game}: ${reviewed ? "review deleted" : "it had no review; the judgments on its moves are deleted"}. analyse_game goes over it again.`,
|
|
34
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context } from "./context.ts";
|
|
2
|
+
/** A draw offer or answer refused, and why, for the page to say it in the player's language: one stands already, none stands, or no side has moved yet. */
|
|
3
|
+
export declare class DrawRefused extends Error {
|
|
4
|
+
readonly reason: "offered" | "none" | "early";
|
|
5
|
+
constructor(message: string, reason: DrawRefused["reason"]);
|
|
6
|
+
}
|
|
7
|
+
/** The player offers a draw on their turn, once each side has moved: it stands until the AI accepts it or moves, and one stands at a time. */
|
|
8
|
+
export declare function offerDraw(context: Context): void;
|
|
9
|
+
/** The player's answer to the AI's draw offer: accepted, the game ends drawn by agreement; declined, it goes on. */
|
|
10
|
+
export declare function answerDraw(context: Context, accept: boolean): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// The draw offers of the board page (D-65): the player's, made on their turn as on Lichess, and their answer to the AI's, which ai_move makes with its move.
|
|
2
|
+
import { bothSidesMoved, drawOfferOf, saveGame } from "../games/index.js";
|
|
3
|
+
import { gameFolder } from "../profiles/index.js";
|
|
4
|
+
import { endGame } from "./end-game.js";
|
|
5
|
+
import { gameGoingOn } from "./game-summary.js";
|
|
6
|
+
/** A draw offer or answer refused, and why, for the page to say it in the player's language: one stands already, none stands, or no side has moved yet. */
|
|
7
|
+
export class DrawRefused extends Error {
|
|
8
|
+
reason;
|
|
9
|
+
constructor(message, reason) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.reason = reason;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** The player offers a draw on their turn, once each side has moved: it stands until the AI accepts it or moves, and one stands at a time. */
|
|
15
|
+
export function offerDraw(context) {
|
|
16
|
+
const folder = gameFolder(context.home());
|
|
17
|
+
const current = gameGoingOn(folder, context.now());
|
|
18
|
+
const offer = drawOfferOf(current);
|
|
19
|
+
if (current.game.position.turn === current.ai) {
|
|
20
|
+
throw new Error("Offer a draw on your turn: wait for the AI's move. Nothing changed.");
|
|
21
|
+
}
|
|
22
|
+
if (!bothSidesMoved(current.game)) {
|
|
23
|
+
throw new DrawRefused("A draw can be offered once each side has made a move. Nothing changed.", "early");
|
|
24
|
+
}
|
|
25
|
+
if (offer !== undefined) {
|
|
26
|
+
throw new DrawRefused(offer === "player" ? "You have offered a draw already: it stands until the AI's move. Nothing changed." : "The AI offers a draw: accept it or decline it. Nothing changed.", "offered");
|
|
27
|
+
}
|
|
28
|
+
saveGame(folder, { ...current, drawOffer: "player" });
|
|
29
|
+
}
|
|
30
|
+
/** The player's answer to the AI's draw offer: accepted, the game ends drawn by agreement; declined, it goes on. */
|
|
31
|
+
export function answerDraw(context, accept) {
|
|
32
|
+
const folder = gameFolder(context.home());
|
|
33
|
+
const current = gameGoingOn(folder, context.now());
|
|
34
|
+
if (drawOfferOf(current) !== "ai") {
|
|
35
|
+
throw new DrawRefused("The AI offers no draw to answer. Nothing changed.", "none");
|
|
36
|
+
}
|
|
37
|
+
if (accept) {
|
|
38
|
+
endGame.run({ result: "draw_agreed" }, context);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
saveGame(folder, { ...current, drawOffer: undefined });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type DrawnArrow, type DrawnSquare } from "../core/index.ts";
|
|
3
|
+
/** A note of the AI's on a move, as its input reads it: on one line of commands, so without brackets or braces. */
|
|
4
|
+
export declare const noteInput: z.ZodString;
|
|
5
|
+
/** The arrows the AI draws, as their input reads them. */
|
|
6
|
+
export declare const arrowsInput: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
|
+
/** The squares the AI marks, as their input reads them. */
|
|
8
|
+
export declare const squaresInput: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
9
|
+
/** The arrows and squares drawn, those that read; none of either left out. */
|
|
10
|
+
export declare function drawingsOf(arrows?: readonly string[], squares?: readonly string[]): {
|
|
11
|
+
arrows?: DrawnArrow[];
|
|
12
|
+
squares?: DrawnSquare[];
|
|
13
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// What the AI draws on the board to show what it says, as the analyst (D-69) and the opponent with the helps on (D-78): arrows and squares, as PGN's
|
|
2
|
+
// %cal and %csl write them.
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { arrowOf, squareOf } from "../core/index.js";
|
|
5
|
+
/** The most a note of the AI's on a move may hold: a few sentences. */
|
|
6
|
+
const noteLimit = 700;
|
|
7
|
+
// The most arrows, and the most squares, on one move.
|
|
8
|
+
const drawLimit = 6;
|
|
9
|
+
/** A note of the AI's on a move, as its input reads it: on one line of commands, so without brackets or braces. */
|
|
10
|
+
export const noteInput = z.string().trim().min(1).max(noteLimit).regex(/^[^[\]{}]*$/, "write it without [ ] { }");
|
|
11
|
+
// A drawing as the AI may write it, spaced or with a dash, as PGN writes it: "R f2 e1" and "Rf2-e1" are Rf2e1 (Priya).
|
|
12
|
+
const drawing = z.string().overwrite((text) => text.replace(/[\s-]+/g, ""));
|
|
13
|
+
/** The arrows the AI draws, as their input reads them. */
|
|
14
|
+
export const arrowsInput = z.array(drawing.regex(/^[GRYB][a-h][1-8][a-h][1-8]$/, "an arrow is a colour's letter and two squares: Gf6e4; a single square goes in squares")
|
|
15
|
+
.refine((arrow) => arrow.slice(1, 3) !== arrow.slice(3, 5), "an arrow goes from a square to another: a single square goes in squares")).max(drawLimit).optional()
|
|
16
|
+
.describe("arrows on the board, as PGN's %cal writes them: Rf2e1. G green: the better move or the good idea; R red: a threat or the mistake; Y yellow: another way; B blue: a plan.");
|
|
17
|
+
/** The squares the AI marks, as their input reads them. */
|
|
18
|
+
export const squaresInput = z.array(drawing.regex(/^[GRYB][a-h][1-8]$/, "a square is a colour's letter and the square: Rf2")).max(drawLimit).optional()
|
|
19
|
+
.describe("squares to mark, as PGN's %csl writes them: Rf2; colours as for arrows");
|
|
20
|
+
/** The arrows and squares drawn, those that read; none of either left out. */
|
|
21
|
+
export function drawingsOf(arrows = [], squares = []) {
|
|
22
|
+
const drawnArrows = arrows.map(arrowOf).filter((arrow) => arrow !== undefined);
|
|
23
|
+
const drawnSquares = squares.map(squareOf).filter((square) => square !== undefined);
|
|
24
|
+
return { ...(drawnArrows.length === 0 ? {} : { arrows: drawnArrows }), ...(drawnSquares.length === 0 ? {} : { squares: drawnSquares }) };
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type GameSummary } from "./game-summary.ts";
|
|
3
|
+
interface Ended {
|
|
4
|
+
game: GameSummary;
|
|
5
|
+
warning?: string;
|
|
6
|
+
}
|
|
7
|
+
/** The endings the board cannot see: a resignation, a draw agreed on an offer (D-65). Checkmate, stalemate and the draws by rule end the game by themselves (D-37). */
|
|
8
|
+
export declare const endGame: import("./registry.ts").Command<z.ZodObject<{
|
|
9
|
+
result: z.ZodEnum<{
|
|
10
|
+
player_resigns: "player_resigns";
|
|
11
|
+
ai_resigns: "ai_resigns";
|
|
12
|
+
draw_agreed: "draw_agreed";
|
|
13
|
+
}>;
|
|
14
|
+
mood: z.ZodOptional<z.ZodEnum<{
|
|
15
|
+
neutral: "neutral";
|
|
16
|
+
happy: "happy";
|
|
17
|
+
surprised: "surprised";
|
|
18
|
+
worried: "worried";
|
|
19
|
+
thoughtful: "thoughtful";
|
|
20
|
+
sad: "sad";
|
|
21
|
+
}>>;
|
|
22
|
+
}, z.core.$strip>, Ended>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { canMate, endWithMood, Game, moods, timeLeft } from "../core/index.js";
|
|
3
|
+
import { archiveEnded, bothSidesMoved, drawOfferOf, saveGame } from "../games/index.js";
|
|
4
|
+
import { gameFolder } from "../profiles/index.js";
|
|
5
|
+
import { withAnalyst } from "./agents.js";
|
|
6
|
+
import { nextStep, positionLines } from "./answer-lines.js";
|
|
7
|
+
import { gameGoingOn, summaryOf } from "./game-summary.js";
|
|
8
|
+
import { refuseUnseenPosition } from "./play-move.js";
|
|
9
|
+
import { defineCommand } from "./registry.js";
|
|
10
|
+
import { colourName } from "./view.js";
|
|
11
|
+
/** The endings the board cannot see: a resignation, a draw agreed on an offer (D-65). Checkmate, stalemate and the draws by rule end the game by themselves (D-37). */
|
|
12
|
+
export const endGame = defineCommand({
|
|
13
|
+
name: "end_game",
|
|
14
|
+
description: "Ends the game by a resignation or a draw agreed; checkmate, stalemate and the draws by rule end it by themselves. Call it to resign, when "
|
|
15
|
+
+ 'the player resigns in the chat, or to agree a draw while an offer stands: theirs, or yours they accept in the chat. Example: end_game {result: "player_resigns"}.',
|
|
16
|
+
input: z.object({
|
|
17
|
+
result: z.enum(["player_resigns", "ai_resigns", "draw_agreed"]).describe("how the game ends"),
|
|
18
|
+
mood: z.enum(moods).optional().describe("how you feel as the game ends, which your face on the board page keeps"),
|
|
19
|
+
}),
|
|
20
|
+
idempotent: true,
|
|
21
|
+
showsGame: true,
|
|
22
|
+
positionals: ["result"],
|
|
23
|
+
run: ({ result, mood }, context) => {
|
|
24
|
+
const now = context.now();
|
|
25
|
+
const folder = gameFolder(context.home());
|
|
26
|
+
refuseUnseenPosition(context, folder);
|
|
27
|
+
const current = gameGoingOn(folder, now);
|
|
28
|
+
if (result === "draw_agreed") {
|
|
29
|
+
refuseDraw(current);
|
|
30
|
+
}
|
|
31
|
+
const ending = result === "draw_agreed" ? { Result: "1/2-1/2", Termination: "draw agreed" } : resignation(current, result === "player_resigns" ? player(current) : current.ai);
|
|
32
|
+
const { record } = current.game;
|
|
33
|
+
const game = new Game({ ...record, tags: { ...record.tags, ...ending } });
|
|
34
|
+
if (mood !== undefined) {
|
|
35
|
+
endWithMood(game, mood);
|
|
36
|
+
}
|
|
37
|
+
// The clock stops at the end, with the time the side to move had spent.
|
|
38
|
+
const ended = current.clock === undefined
|
|
39
|
+
? { ...current, game }
|
|
40
|
+
: { ...current, game, clock: { ...current.clock, left: timeLeft(current.clock, current.game.position.turn, now), since: now.toISOString() } };
|
|
41
|
+
saveGame(folder, ended);
|
|
42
|
+
const { archive, warning } = archiveEnded(folder, ended);
|
|
43
|
+
const summary = withAnalyst(summaryOf(ended, now, archive), context);
|
|
44
|
+
return warning === undefined ? { game: summary } : { game: summary, warning };
|
|
45
|
+
},
|
|
46
|
+
format: ({ game, warning }) => [...positionLines(game), ...(warning === undefined ? [] : [warning]), nextStep(game)].join("\n"),
|
|
47
|
+
});
|
|
48
|
+
/** A draw is agreed only on an offer that stands, once each side has moved (FIDE 5.2.3). */
|
|
49
|
+
function refuseDraw(current) {
|
|
50
|
+
if (!bothSidesMoved(current.game)) {
|
|
51
|
+
throw new Error("A draw is agreed only once each side has made a move. Nothing changed.");
|
|
52
|
+
}
|
|
53
|
+
if (drawOfferOf(current) === undefined) {
|
|
54
|
+
throw new Error("No draw offer stands: the player offers one with their move (player_move with offer_draw), you with yours (ai_move with offer_draw). Nothing changed.");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** The side that resigns loses, unless the other could not mate by any series of legal moves: then the game is drawn (FIDE 5.1.2). */
|
|
58
|
+
function resignation(current, loser) {
|
|
59
|
+
const winner = loser === "white" ? "black" : "white";
|
|
60
|
+
if (canMate(current.game.position.fen, winner)) {
|
|
61
|
+
return { Result: loser === "white" ? "0-1" : "1-0", Termination: `${colourName(loser)} resigns` };
|
|
62
|
+
}
|
|
63
|
+
return { Result: "1/2-1/2", Termination: `${colourName(loser)} resigns and ${colourName(winner)} cannot mate` };
|
|
64
|
+
}
|
|
65
|
+
function player(current) {
|
|
66
|
+
return current.ai === "white" ? "black" : "white";
|
|
67
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
interface Exported {
|
|
3
|
+
format: "pgn" | "moves" | "fen";
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
/** The game as PGN or its position as FEN, for the player to copy or keep: the plans stay hidden while it goes on (D-29); the moves alone for another site. */
|
|
7
|
+
export declare const exportGame: import("./registry.ts").Command<z.ZodObject<{
|
|
8
|
+
format: z.ZodEnum<{
|
|
9
|
+
fen: "fen";
|
|
10
|
+
moves: "moves";
|
|
11
|
+
pgn: "pgn";
|
|
12
|
+
}>;
|
|
13
|
+
}, z.core.$strip>, Exported>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { movesPgn, playerPgn } from "../games/index.js";
|
|
3
|
+
import { gameFolder } from "../profiles/index.js";
|
|
4
|
+
import { gameAt } from "./clock.js";
|
|
5
|
+
import { defineCommand } from "./registry.js";
|
|
6
|
+
/** The game as PGN or its position as FEN, for the player to copy or keep: the plans stay hidden while it goes on (D-29); the moves alone for another site. */
|
|
7
|
+
export const exportGame = defineCommand({
|
|
8
|
+
name: "export",
|
|
9
|
+
description: "Gives the game as PGN, its moves alone as PGN for Lichess or chess.com, or its position as FEN, for the player to copy or keep. While the game goes on, "
|
|
10
|
+
+ 'the PGN leaves out your plans. Example: export {format: "moves"}.',
|
|
11
|
+
input: z.object({
|
|
12
|
+
format: z.enum(["pgn", "moves", "fen"]).describe("pgn for the whole game with its comments, moves for the moves alone, to load on Lichess or chess.com, fen for the position"),
|
|
13
|
+
}),
|
|
14
|
+
// Read only for the host: a flag that has fallen is written, as the time already decided.
|
|
15
|
+
readOnly: true,
|
|
16
|
+
positionals: ["format"],
|
|
17
|
+
run: ({ format }, context) => {
|
|
18
|
+
const folder = gameFolder(context.home());
|
|
19
|
+
const current = gameAt(folder, context.now());
|
|
20
|
+
if (current === undefined) {
|
|
21
|
+
throw new Error("There is no game to export: start one with new_game.");
|
|
22
|
+
}
|
|
23
|
+
const text = format === "fen" ? current.game.position.fen : format === "moves" ? movesPgn(current) : playerPgn(folder, current);
|
|
24
|
+
return { format, text };
|
|
25
|
+
},
|
|
26
|
+
format: (result) => result.text,
|
|
27
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type AiLevel, type Facts, type GameRecord, type MoodNow, type Side, type Square } from "../core/index.ts";
|
|
2
|
+
import { type CurrentGame, type Speaker, type Thought } from "../games/index.ts";
|
|
3
|
+
import { type ClockSummary } from "./clock.ts";
|
|
4
|
+
/** The current game, as the commands give it. */
|
|
5
|
+
export interface GameSummary {
|
|
6
|
+
ai: Side;
|
|
7
|
+
/** The AI's level: the layers of facts it gets (D-60). */
|
|
8
|
+
level: AiLevel;
|
|
9
|
+
turn: Side;
|
|
10
|
+
moveNumber: number;
|
|
11
|
+
fen: string;
|
|
12
|
+
/** The last moves, numbered: 3.Bc4 Nf6 4.Ng5 d5. */
|
|
13
|
+
lastMoves: string;
|
|
14
|
+
/** The last move, numbered, with its squares. */
|
|
15
|
+
lastMove?: LastMove;
|
|
16
|
+
/** The opening by Lichess's book (D-15): ECO and name, and the move the game left the book at. */
|
|
17
|
+
opening?: string;
|
|
18
|
+
/** When the AI is to move: the facts of the position (D-26); below the club level, only the check and the legal moves of each piece are shown. */
|
|
19
|
+
facts?: Facts;
|
|
20
|
+
/** When the AI is to move at the master level, in the book: the moves it knows from the position (D-15, D-60). */
|
|
21
|
+
book?: readonly string[];
|
|
22
|
+
/** When the AI is to move: the plan it wrote last. */
|
|
23
|
+
plan?: string;
|
|
24
|
+
/** When the AI is to move: what it thought of the player's move on their time (D-72). */
|
|
25
|
+
thought?: Thought;
|
|
26
|
+
/** When the AI is to move: the helps on, so it may teach (D-78), with a coach that judges the player's moves (D-88), or turned off since it drew on a move. */
|
|
27
|
+
helps?: "on" | "coached" | "off now";
|
|
28
|
+
/** In a game with a time control: the time each side had left when the command ran (D-13). */
|
|
29
|
+
clock?: ClockSummary;
|
|
30
|
+
/** Who offered a draw, while the offer stands (D-65). */
|
|
31
|
+
drawOffer?: Speaker;
|
|
32
|
+
/** The AI's mood while it lasts, in a game going on (D-70). */
|
|
33
|
+
mood?: MoodNow;
|
|
34
|
+
/**
|
|
35
|
+
* When the game is over: how it ended, its name in the archive once it is there, and, where an analyst agent runs (D-89), whether the chat offers it or
|
|
36
|
+
* goes over the review it did.
|
|
37
|
+
*/
|
|
38
|
+
end?: {
|
|
39
|
+
result: string;
|
|
40
|
+
termination?: string;
|
|
41
|
+
archive?: string;
|
|
42
|
+
analyst?: "offer" | "done";
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** A move numbered as in the game (4.Ng5, 4...d5), with the squares it went from and to. */
|
|
46
|
+
export interface LastMove {
|
|
47
|
+
move: string;
|
|
48
|
+
from: Square;
|
|
49
|
+
to: Square;
|
|
50
|
+
}
|
|
51
|
+
/** A move played by the AI or the player, with where the game stands after it. */
|
|
52
|
+
export interface MovePlayed {
|
|
53
|
+
/** The move, numbered as in the game: 4.Ng5, 4...d5. */
|
|
54
|
+
move: string;
|
|
55
|
+
/** A + or # the move was written with and does not give. */
|
|
56
|
+
note?: string;
|
|
57
|
+
game: GameSummary;
|
|
58
|
+
/** What went wrong after the move was saved, such as the archive not written. */
|
|
59
|
+
warning?: string;
|
|
60
|
+
/** What the player wrote on the board page that the AI has not been given yet. */
|
|
61
|
+
words?: string[];
|
|
62
|
+
}
|
|
63
|
+
/** The game as the commands tell it; a game over with the name it has in the archive, when it is there. */
|
|
64
|
+
export declare function summaryOf(current: CurrentGame, now: Date, archive?: string): GameSummary;
|
|
65
|
+
/**
|
|
66
|
+
* The summary with what the AI reads when it is to move: what it thought of the player's last move on their time (D-72), and whether the helps are on (D-78),
|
|
67
|
+
* with a coach when `coached`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function withThought(summary: GameSummary, folder: string, current: CurrentGame, coached: boolean): GameSummary;
|
|
70
|
+
/** The current game `now`, refused when there is none or when it is over, on time too. */
|
|
71
|
+
export declare function gameGoingOn(folder: string, now: Date): CurrentGame;
|
|
72
|
+
/** How a game ended, in words: its result, and how it came when known. */
|
|
73
|
+
export declare function endText({ result, termination }: {
|
|
74
|
+
result: string;
|
|
75
|
+
termination?: string;
|
|
76
|
+
}): string;
|
|
77
|
+
/** The last plies of a record, numbered as a player writes them, from the move number and side of its FEN. */
|
|
78
|
+
export declare function lastMoves(record: GameRecord, count: number): string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// The current game as the commands give it: plain data for the command line's JSON, from which answer-lines.ts writes the lines an AI reads.
|
|
2
|
+
import { levelOf, moodAt, sees } from "../core/index.js";
|
|
3
|
+
import { currentPlan, drawOfferOf, endOf, thoughtForLastMove } from "../games/index.js";
|
|
4
|
+
import { helpsIn } from "../profiles/index.js";
|
|
5
|
+
import { clockSummaryOf, gameAt } from "./clock.js";
|
|
6
|
+
import { knownMoves, openingLine } from "./opening-book.js";
|
|
7
|
+
/** The game as the commands tell it; a game over with the name it has in the archive, when it is there. */
|
|
8
|
+
export function summaryOf(current, now, archive) {
|
|
9
|
+
const { game, ai } = current;
|
|
10
|
+
const { position, record, lastMove } = game;
|
|
11
|
+
const end = endOf(game);
|
|
12
|
+
const level = levelOf(record);
|
|
13
|
+
const summary = { ai, level, turn: position.turn, moveNumber: position.moveNumber, fen: position.fen, lastMoves: lastMoves(record, 6) };
|
|
14
|
+
if (lastMove !== undefined) {
|
|
15
|
+
summary.lastMove = { move: lastMoves(record, 1), from: lastMove.from, to: lastMove.to };
|
|
16
|
+
}
|
|
17
|
+
const opening = openingLine(record);
|
|
18
|
+
const clock = clockSummaryOf(current, now);
|
|
19
|
+
const drawOffer = drawOfferOf(current);
|
|
20
|
+
if (drawOffer !== undefined) {
|
|
21
|
+
summary.drawOffer = drawOffer;
|
|
22
|
+
}
|
|
23
|
+
const mood = moodAt(record, ai);
|
|
24
|
+
if (end === undefined && mood.mood !== "neutral") {
|
|
25
|
+
summary.mood = mood;
|
|
26
|
+
}
|
|
27
|
+
if (clock !== undefined) {
|
|
28
|
+
summary.clock = clock;
|
|
29
|
+
}
|
|
30
|
+
if (opening !== undefined) {
|
|
31
|
+
summary.opening = opening;
|
|
32
|
+
}
|
|
33
|
+
if (end !== undefined) {
|
|
34
|
+
summary.end = archive === undefined ? end : { ...end, archive };
|
|
35
|
+
}
|
|
36
|
+
else if (position.turn === ai) {
|
|
37
|
+
addAiFacts(summary, current);
|
|
38
|
+
}
|
|
39
|
+
return summary;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The summary with what the AI reads when it is to move: what it thought of the player's last move on their time (D-72), and whether the helps are on (D-78),
|
|
43
|
+
* with a coach when `coached`.
|
|
44
|
+
*/
|
|
45
|
+
export function withThought(summary, folder, current, coached) {
|
|
46
|
+
if (summary.turn !== summary.ai || summary.end !== undefined) {
|
|
47
|
+
return summary;
|
|
48
|
+
}
|
|
49
|
+
const thought = thoughtForLastMove(folder, current);
|
|
50
|
+
const helps = helpsIn(folder) ? (coached ? "coached" : "on") : judgedBefore(current) ? "off now" : undefined;
|
|
51
|
+
return { ...summary, ...(thought === undefined ? {} : { thought }), ...(helps === undefined ? {} : { helps }) };
|
|
52
|
+
}
|
|
53
|
+
/** The current game `now`, refused when there is none or when it is over, on time too. */
|
|
54
|
+
export function gameGoingOn(folder, now) {
|
|
55
|
+
const current = gameAt(folder, now);
|
|
56
|
+
if (current === undefined) {
|
|
57
|
+
throw new Error("There is no game going on: start one with new_game. Nothing changed.");
|
|
58
|
+
}
|
|
59
|
+
const end = endOf(current.game);
|
|
60
|
+
if (end !== undefined) {
|
|
61
|
+
throw new Error(`The game is over (${endText(end)}): new_game starts another. Nothing changed.`);
|
|
62
|
+
}
|
|
63
|
+
return current;
|
|
64
|
+
}
|
|
65
|
+
/** How a game ended, in words: its result, and how it came when known. */
|
|
66
|
+
export function endText({ result, termination }) {
|
|
67
|
+
return termination === undefined ? result : `${result}, ${termination}`;
|
|
68
|
+
}
|
|
69
|
+
/** The last plies of a record, numbered as a player writes them, from the move number and side of its FEN. */
|
|
70
|
+
export function lastMoves(record, count) {
|
|
71
|
+
const [, turn, , , , number] = (record.tags.FEN ?? "").split(" ");
|
|
72
|
+
const startNumber = Number(number) || 1;
|
|
73
|
+
// The ply count from white's move of the first move number: 1 when black moves first.
|
|
74
|
+
const offset = turn === "b" ? 1 : 0;
|
|
75
|
+
const first = Math.max(0, record.moves.length - count);
|
|
76
|
+
return record.moves.slice(first).map((move, index) => {
|
|
77
|
+
const ply = first + index + offset;
|
|
78
|
+
const moveNumber = startNumber + Math.floor(ply / 2);
|
|
79
|
+
if (ply % 2 === 0) {
|
|
80
|
+
return `${moveNumber}.${move.san}`;
|
|
81
|
+
}
|
|
82
|
+
return index === 0 ? `${moveNumber}...${move.san}` : move.san;
|
|
83
|
+
}).join(" ");
|
|
84
|
+
}
|
|
85
|
+
/** Whether the AI judged or drew on a move of this game, which only the helps allow: a sign they were on. */
|
|
86
|
+
function judgedBefore(current) {
|
|
87
|
+
return current.game.record.moves.some((move) => /\[%(badge|review|cal|csl) /.test(move.comment ?? ""));
|
|
88
|
+
}
|
|
89
|
+
/** What the AI gets when it is to move: the facts its level sees (D-60), and its plan. */
|
|
90
|
+
function addAiFacts(summary, { game }) {
|
|
91
|
+
const plan = currentPlan(game.record);
|
|
92
|
+
summary.facts = game.facts;
|
|
93
|
+
if (sees(summary.level, "theory")) {
|
|
94
|
+
const book = knownMoves(summary.fen);
|
|
95
|
+
if (book.length > 0) {
|
|
96
|
+
summary.book = book;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (plan !== undefined) {
|
|
100
|
+
summary.plan = plan;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AnyCommand } from "./registry.ts";
|
|
2
|
+
export { aiMove } from "./ai-move.ts";
|
|
3
|
+
export { analyseGame } from "./analyse-game.ts";
|
|
4
|
+
export { gameAt } from "./clock.ts";
|
|
5
|
+
export { type Caller, type Context, defaultContext, type SeenPosition } from "./context.ts";
|
|
6
|
+
export { deleteGame } from "./delete-game.ts";
|
|
7
|
+
export { deleteReview } from "./delete-review.ts";
|
|
8
|
+
export { answerDraw, DrawRefused, offerDraw } from "./draw-offer.ts";
|
|
9
|
+
export { endGame } from "./end-game.ts";
|
|
10
|
+
export { ChatNotClear, newGame, startNewGame } from "./new-game.ts";
|
|
11
|
+
export { bookOpening, bookPlies } from "./opening-book.ts";
|
|
12
|
+
export { playMove, PositionChanged, positionIn, samePosition } from "./play-move.ts";
|
|
13
|
+
export { chooseDrawnAvatar, GameGoingOn, newProfile, setAccounts, useProfile } from "./profiles.ts";
|
|
14
|
+
export { type AnyCommand, baseType, type Command, commandLineFieldProblem, inputProblems, isBoolean, isList, takesNumbers } from "./registry.ts";
|
|
15
|
+
export { say } from "./say.ts";
|
|
16
|
+
export { gameToStudy, namedGame } from "./study.ts";
|
|
17
|
+
/** Every command of Chesspal, in the order the command line lists them and the MCP server offers them. */
|
|
18
|
+
export declare const commands: AnyCommand[];
|