chesspal 0.0.1 → 0.1.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 +136 -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 +8 -0
- package/dist/cli/command-line.js +178 -0
- package/dist/commands/ai-move.d.ts +37 -0
- package/dist/commands/ai-move.js +92 -0
- package/dist/commands/ai-say.d.ts +20 -0
- package/dist/commands/ai-say.js +40 -0
- package/dist/commands/analyse-game.d.ts +61 -0
- package/dist/commands/analyse-game.js +238 -0
- package/dist/commands/annotate-game.d.ts +35 -0
- package/dist/commands/annotate-game.js +78 -0
- package/dist/commands/clock.d.ts +15 -0
- package/dist/commands/clock.js +45 -0
- package/dist/commands/context.d.ts +56 -0
- package/dist/commands/context.js +36 -0
- package/dist/commands/draw-offer.d.ts +10 -0
- package/dist/commands/draw-offer.js +43 -0
- package/dist/commands/drawings.d.ts +15 -0
- package/dist/commands/drawings.js +23 -0
- package/dist/commands/end-game.d.ts +23 -0
- package/dist/commands/end-game.js +64 -0
- package/dist/commands/export-game.d.ts +14 -0
- package/dist/commands/export-game.js +26 -0
- package/dist/commands/game-summary.d.ts +95 -0
- package/dist/commands/game-summary.js +239 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +19 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +73 -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 +61 -0
- package/dist/commands/new-game.d.ts +69 -0
- package/dist/commands/new-game.js +215 -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 +44 -0
- package/dist/commands/play-move.js +102 -0
- package/dist/commands/player-move.d.ts +17 -0
- package/dist/commands/player-move.js +44 -0
- package/dist/commands/player-say.d.ts +5 -0
- package/dist/commands/player-say.js +16 -0
- package/dist/commands/ponder.d.ts +42 -0
- package/dist/commands/ponder.js +167 -0
- package/dist/commands/position.d.ts +14 -0
- package/dist/commands/position.js +128 -0
- package/dist/commands/profiles.d.ts +60 -0
- package/dist/commands/profiles.js +129 -0
- package/dist/commands/registry.d.ts +54 -0
- package/dist/commands/registry.js +61 -0
- package/dist/commands/say.d.ts +16 -0
- package/dist/commands/say.js +84 -0
- package/dist/commands/status.d.ts +29 -0
- package/dist/commands/status.js +103 -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 +122 -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 +93 -0
- package/dist/core/game.js +348 -0
- package/dist/core/illegal.d.ts +19 -0
- package/dist/core/illegal.js +141 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.js +11 -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 +23 -0
- package/dist/core/moods.js +45 -0
- package/dist/core/openings.d.ts +37 -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 +74 -0
- package/dist/core/review.js +126 -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 +42 -0
- package/dist/games/archive.js +168 -0
- package/dist/games/chat-start.d.ts +16 -0
- package/dist/games/chat-start.js +26 -0
- package/dist/games/current-game.d.ts +54 -0
- package/dist/games/current-game.js +128 -0
- package/dist/games/files.d.ts +9 -0
- package/dist/games/files.js +52 -0
- package/dist/games/index.d.ts +10 -0
- package/dist/games/index.js +11 -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 +40 -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/host-files.d.ts +10 -0
- package/dist/host-files/host-files.js +167 -0
- package/dist/host-files/index.d.ts +2 -0
- package/dist/host-files/index.js +3 -0
- package/dist/host-files/ponder-agents.d.ts +12 -0
- package/dist/host-files/ponder-agents.js +40 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/page/app.js +576 -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.js +16 -0
- package/dist/page/badges.js +50 -0
- package/dist/page/board.js +566 -0
- package/dist/page/browse.js +17 -0
- package/dist/page/clock-time.js +12 -0
- package/dist/page/dialog.js +26 -0
- package/dist/page/ending.js +73 -0
- package/dist/page/explain.js +58 -0
- package/dist/page/fold-tab.js +9 -0
- package/dist/page/folds.js +43 -0
- package/dist/page/game-column.js +44 -0
- package/dist/page/header.js +27 -0
- package/dist/page/helps-view.js +51 -0
- package/dist/page/helps.js +90 -0
- package/dist/page/main.js +8 -0
- package/dist/page/model-name.js +20 -0
- package/dist/page/move-list.js +68 -0
- package/dist/page/new-game-dialog.js +42 -0
- package/dist/page/opening-book.js +14 -0
- package/dist/page/pieces/CREDITS.md +36 -0
- package/dist/page/pieces/cburnett-small.svg +20 -0
- package/dist/page/pieces/cburnett.svg +20 -0
- package/dist/page/pieces/nitida-small.svg +19 -0
- package/dist/page/pieces/nitida.svg +20 -0
- package/dist/page/pieces/torretta-small.svg +19 -0
- package/dist/page/pieces/torretta.svg +19 -0
- package/dist/page/pieces.js +28 -0
- package/dist/page/player-card.js +60 -0
- package/dist/page/plies.js +62 -0
- package/dist/page/profile-dialogs.js +123 -0
- package/dist/page/profile.js +95 -0
- package/dist/page/settings-panel.js +32 -0
- package/dist/page/settings.js +76 -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.js +105 -0
- package/dist/page/studied.js +14 -0
- package/dist/page/study-dialog.js +58 -0
- package/dist/page/style.css +4398 -0
- package/dist/page/talk.js +172 -0
- package/dist/page/text.js +593 -0
- package/dist/page/torretta.js +38 -0
- package/dist/page/voice.js +38 -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 +58 -0
- package/dist/profiles/profiles.js +237 -0
- package/dist/profiles/settings.d.ts +12 -0
- package/dist/profiles/settings.js +46 -0
- package/dist/server/analyses.d.ts +17 -0
- package/dist/server/analyses.js +46 -0
- package/dist/server/bell.d.ts +60 -0
- package/dist/server/bell.js +209 -0
- package/dist/server/caller.d.ts +10 -0
- package/dist/server/caller.js +99 -0
- package/dist/server/http.d.ts +16 -0
- package/dist/server/http.js +57 -0
- package/dist/server/mcp.d.ts +14 -0
- package/dist/server/mcp.js +179 -0
- package/dist/server/page.d.ts +46 -0
- package/dist/server/page.js +657 -0
- package/dist/server/profile-routes.d.ts +19 -0
- package/dist/server/profile-routes.js +186 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/instructions/analyst.md +8 -0
- package/instructions/opponent.md +15 -0
- package/instructions/ponder.md +9 -0
- package/package.json +66 -5
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// The players' profiles (D-23), on the command line and the board page: listed, made, put in use, with their picture (D-45). The AI reads only who plays.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { endOf } from "../games/index.js";
|
|
6
|
+
import { activateProfile, activeProfile, createProfile, drawnAvatars, gameFolder, levels, listProfiles, nameLimit, ratingLimits, ratingSites, removeOwnImage, saveOwnImage, updateProfile, } from "../profiles/index.js";
|
|
7
|
+
import { gameAt } from "./clock.js";
|
|
8
|
+
import { defineCommand } from "./registry.js";
|
|
9
|
+
/** A change of profile refused while a game goes on: the page says it in the player's language. */
|
|
10
|
+
export class GameGoingOn extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super("A game is going on in this profile: finish it, or resign, before changing profile. Nothing changed.");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** The profiles, the one in use first. */
|
|
16
|
+
export const profiles = defineCommand({
|
|
17
|
+
name: "profiles",
|
|
18
|
+
description: "Lists the players' profiles, the one in use marked with *. Example: chesspal profiles.",
|
|
19
|
+
input: z.object({}),
|
|
20
|
+
cliOnly: true,
|
|
21
|
+
readOnly: true,
|
|
22
|
+
idempotent: true,
|
|
23
|
+
run: (_input, context) => ({ profiles: listProfiles(context.home()), active: activeProfile(context.home()).id }),
|
|
24
|
+
format: ({ profiles, active }) => profiles.map((profile) => `${profile.id === active ? "*" : " "} ${profile.id}: ${playerText(profile)}`).join("\n"),
|
|
25
|
+
});
|
|
26
|
+
/** A new profile, put in use once the game going on has ended; the one Chesspal made at the first start, while new, becomes it, game and all (D-71). */
|
|
27
|
+
export const newProfile = defineCommand({
|
|
28
|
+
name: "new_profile",
|
|
29
|
+
description: "Makes a player's profile and puts it in use: their games, picture and page settings go in profiles/<id>/ of Chesspal's data folder; the "
|
|
30
|
+
+ "profile Chesspal made at the first start becomes this one, its folder renamed, while nobody has completed it. Example: chesspal new_profile Anna --level regular "
|
|
31
|
+
+ "--rating 1450 --site lichess.",
|
|
32
|
+
input: z.object({
|
|
33
|
+
name: z.string().trim().min(1).max(nameLimit).regex(/^[^\p{Cc}]+$/u, "one line of text").describe("the player's name"),
|
|
34
|
+
level: z.enum(levels).optional().describe("how they play: learning, casual, regular or tournament"),
|
|
35
|
+
rating: z.int().min(ratingLimits.lowest).max(ratingLimits.highest).optional().describe("their rating, with site"),
|
|
36
|
+
site: z.enum(ratingSites).optional().describe("where the rating comes from: lichess, chess.com or fide"),
|
|
37
|
+
avatar: z.enum(drawnAvatars).optional().describe(`a drawn avatar: ${drawnAvatars.join(", ")}`),
|
|
38
|
+
}),
|
|
39
|
+
cliOnly: true,
|
|
40
|
+
positionals: ["name"],
|
|
41
|
+
run: ({ name, level, rating, site, avatar }, context) => {
|
|
42
|
+
if ((rating === undefined) !== (site === undefined)) {
|
|
43
|
+
throw new Error("Give the rating and its site together, such as --rating 1450 --site lichess, or neither. Nothing changed.");
|
|
44
|
+
}
|
|
45
|
+
if (activeProfile(context.home()).new !== true) {
|
|
46
|
+
refuseWhilePlaying(context);
|
|
47
|
+
}
|
|
48
|
+
return createProfile(context.home(), {
|
|
49
|
+
name,
|
|
50
|
+
...(level === undefined ? {} : { level }),
|
|
51
|
+
...(rating === undefined || site === undefined ? {} : { rating: { value: rating, site } }),
|
|
52
|
+
...(avatar === undefined ? {} : { avatar }),
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
format: (profile) => `Profile ${profile.id} made, and in use: ${playerText(profile)}.`,
|
|
56
|
+
});
|
|
57
|
+
/** Another profile put in use, once the game going on has ended. */
|
|
58
|
+
export const useProfile = defineCommand({
|
|
59
|
+
name: "use_profile",
|
|
60
|
+
description: "Puts another player's profile in use, with its games; refused while a game is going on. Example: chesspal use_profile anna.",
|
|
61
|
+
input: z.object({ profile: z.string().min(1).describe("the profile, as chesspal profiles lists it") }),
|
|
62
|
+
cliOnly: true,
|
|
63
|
+
idempotent: true,
|
|
64
|
+
positionals: ["profile"],
|
|
65
|
+
run: ({ profile }, context) => {
|
|
66
|
+
const active = activeProfile(context.home());
|
|
67
|
+
if (active.id === profile) {
|
|
68
|
+
return active;
|
|
69
|
+
}
|
|
70
|
+
refuseWhilePlaying(context);
|
|
71
|
+
return activateProfile(context.home(), profile);
|
|
72
|
+
},
|
|
73
|
+
format: (profile) => `Profile ${profile.id} in use: ${playerText(profile)}.`,
|
|
74
|
+
});
|
|
75
|
+
/** The picture of the profile in use: a drawn avatar, or an image of the player's, which replaces any before. */
|
|
76
|
+
export const setAvatar = defineCommand({
|
|
77
|
+
name: "set_avatar",
|
|
78
|
+
description: `Sets the picture of the profile in use: a drawn avatar (${drawnAvatars.join(", ")}) or the path of a PNG, JPEG or WebP image of `
|
|
79
|
+
+ "1 MB at most. Example: chesspal set_avatar owl.",
|
|
80
|
+
input: z.object({ avatar: z.string().min(1).describe("a drawn avatar, or the path of an image") }),
|
|
81
|
+
cliOnly: true,
|
|
82
|
+
idempotent: true,
|
|
83
|
+
positionals: ["avatar"],
|
|
84
|
+
run: ({ avatar }, context) => {
|
|
85
|
+
const profile = activeProfile(context.home());
|
|
86
|
+
const drawn = drawnAvatars.find((one) => one === avatar);
|
|
87
|
+
if (drawn === undefined) {
|
|
88
|
+
saveOwnImage(context.home(), profile.id, imageFile(resolve(context.folder, avatar)));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
chooseDrawnAvatar(context, profile.id, drawn);
|
|
92
|
+
}
|
|
93
|
+
return { profile: profile.id, avatar };
|
|
94
|
+
},
|
|
95
|
+
format: ({ profile, avatar }) => `The picture of ${profile} is ${avatar}.`,
|
|
96
|
+
});
|
|
97
|
+
/** A drawn avatar chosen for a profile: the player's own image goes, or it would still show. */
|
|
98
|
+
export function chooseDrawnAvatar(context, id, avatar) {
|
|
99
|
+
updateProfile(context.home(), id, { avatar });
|
|
100
|
+
removeOwnImage(context.home(), id);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Who plays, as the AI reads it in status and new_game: their name, how they play and their rating; nothing while the profile is new, named after the
|
|
104
|
+
* system's account until the player completes it (D-71).
|
|
105
|
+
*/
|
|
106
|
+
export function playerLine(context) {
|
|
107
|
+
const profile = activeProfile(context.home());
|
|
108
|
+
return profile.new === true ? undefined : `Player: ${playerText(profile)}.`;
|
|
109
|
+
}
|
|
110
|
+
/** Throws while a game goes on in the profile in use. */
|
|
111
|
+
export function refuseWhilePlaying(context) {
|
|
112
|
+
const current = gameAt(gameFolder(context.home()), context.now());
|
|
113
|
+
if (current !== undefined && endOf(current.game) === undefined) {
|
|
114
|
+
throw new GameGoingOn();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const levelText = { learning: "has just learned", casual: "plays now and then", regular: "plays often", tournament: "plays tournaments" };
|
|
118
|
+
const siteText = { "lichess": "Lichess", "chess.com": "Chess.com", "fide": "FIDE" };
|
|
119
|
+
function playerText({ name, level, rating }) {
|
|
120
|
+
return [name, ...(level === undefined ? [] : [levelText[level]]), ...(rating === undefined ? [] : [`${siteText[rating.site]} ${rating.value}`])].join(", ");
|
|
121
|
+
}
|
|
122
|
+
function imageFile(path) {
|
|
123
|
+
try {
|
|
124
|
+
return readFileSync(path);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
throw new Error(`${path} cannot be read (${error.message}): give a drawn avatar's name, or the path of an image. Nothing changed.`, { cause: error });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Context } from "./context.ts";
|
|
3
|
+
/** A command is defined once: the command line and the MCP server are generated from it (D-5), and later the page's actions. */
|
|
4
|
+
export interface Command<Schema extends z.ZodObject = z.ZodObject, Result = unknown> {
|
|
5
|
+
name: string;
|
|
6
|
+
/** Written for an AI: what the command does (the first sentence, shown in the list of commands), when to use it, an example. */
|
|
7
|
+
description: string;
|
|
8
|
+
input: Schema;
|
|
9
|
+
/** The command changes nothing: hosts may run it without asking. */
|
|
10
|
+
readOnly?: boolean;
|
|
11
|
+
/** The command deletes or overwrites what the player keeps, such as a game: hosts may ask first. */
|
|
12
|
+
destructive?: boolean;
|
|
13
|
+
/** Calling the command again with the same input changes nothing more. */
|
|
14
|
+
idempotent?: boolean;
|
|
15
|
+
/** Hosts load the tool at once instead of on demand: for the ones the AI calls at the start of every chat. */
|
|
16
|
+
alwaysLoad?: boolean;
|
|
17
|
+
/** Its answer shows the chat the game as it is: the chat may move on it, and on no other (D-71). */
|
|
18
|
+
showsGame?: boolean;
|
|
19
|
+
/** An agent calls it apart from the chat, as ponder (D-72): the doorbell's record of what the chat has read stays as it was. */
|
|
20
|
+
byAgent?: boolean;
|
|
21
|
+
/** The only host the MCP server offers it to, by the client's name, as ponder to Claude Code: elsewhere its description would cost tokens for nothing. */
|
|
22
|
+
host?: string;
|
|
23
|
+
/** Only the command line has it, as init: the MCP server leaves it out, and its description costs the AI no tokens. */
|
|
24
|
+
cliOnly?: boolean;
|
|
25
|
+
/** Fields the command line takes as positional arguments, in order. */
|
|
26
|
+
positionals?: Extract<keyof Schema["shape"], string>[];
|
|
27
|
+
/** Returns a value, never prints: the MCP server speaks on stdout, and only the command line writes there. */
|
|
28
|
+
run(input: z.infer<Schema>, context: Context): Result | Promise<Result>;
|
|
29
|
+
/** Plain-text rendering of the result, for the AI and the terminal alike; the context, for a command that speaks to the player, as init. */
|
|
30
|
+
format(result: Result, context?: Context): string;
|
|
31
|
+
}
|
|
32
|
+
export type AnyCommand = Command<any, any>;
|
|
33
|
+
/** Checks a command's types where it is written; the command is used as it is. */
|
|
34
|
+
export declare function defineCommand<Schema extends z.ZodObject, Result>(command: Command<Schema, Result>): Command<Schema, Result>;
|
|
35
|
+
/** One sentence of the AI's, kept in a PGN comment: no brackets or braces, which would break it. */
|
|
36
|
+
export declare function sentence(most: number): z.ZodString;
|
|
37
|
+
/**
|
|
38
|
+
* Why an input is refused, in words an AI can act on, the same on the command line and in MCP: one sentence for each problem, each parameter written as
|
|
39
|
+
* the caller writes it (colour, --colour).
|
|
40
|
+
*/
|
|
41
|
+
export declare function inputProblems(command: string, issues: readonly z.core.$ZodIssue[], given: Record<string, unknown>, written: (name: string) => string): string;
|
|
42
|
+
/**
|
|
43
|
+
* What keeps a field of a command's input from being given on the command line: one named no-x, read as turning x off, or json, read as answering in JSON;
|
|
44
|
+
* undefined for any other. A positional is never an option, so json is its name freely.
|
|
45
|
+
*/
|
|
46
|
+
export declare function commandLineFieldProblem(command: string, name: string, positional: boolean): string | undefined;
|
|
47
|
+
/** Whether a field of a command's input is a flag, true or false, as --hidden. */
|
|
48
|
+
export declare function isBoolean(field: z.ZodType): boolean;
|
|
49
|
+
/** A list option can be given more than once, as in --tag a --tag b. */
|
|
50
|
+
export declare function isList(field: z.ZodType): boolean;
|
|
51
|
+
/** The type of a field of a command's input, past optional, default and nullable: string, boolean, array… */
|
|
52
|
+
export declare function baseType(field: z.ZodType): string;
|
|
53
|
+
/** Whether a field takes numbers, alone or in a list: the command line reads them as text and turns them into numbers. */
|
|
54
|
+
export declare function takesNumbers(field: z.ZodType): boolean;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Checks a command's types where it is written; the command is used as it is. */
|
|
3
|
+
export function defineCommand(command) {
|
|
4
|
+
return command;
|
|
5
|
+
}
|
|
6
|
+
/** One sentence of the AI's, kept in a PGN comment: no brackets or braces, which would break it. */
|
|
7
|
+
export function sentence(most) {
|
|
8
|
+
return z.string().trim().min(1, "write one sentence").max(most, `write one sentence of ${most} characters at most`).regex(/^[^[\]{}]*$/, "write it without [ ] { }");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Why an input is refused, in words an AI can act on, the same on the command line and in MCP: one sentence for each problem, each parameter written as
|
|
12
|
+
* the caller writes it (colour, --colour).
|
|
13
|
+
*/
|
|
14
|
+
export function inputProblems(command, issues, given, written) {
|
|
15
|
+
return issues.map((issue) => inputIssue(command, issue, given, written)).join(" ");
|
|
16
|
+
}
|
|
17
|
+
function inputIssue(command, issue, given, written) {
|
|
18
|
+
const path = issue.path.map(String).join(".");
|
|
19
|
+
if (issue.code === "unrecognized_keys") {
|
|
20
|
+
return path === "" ? `${command} has no parameter ${issue.keys.map(written).join(", ")}.` : `${written(path)} has no ${issue.keys.join(", ")}.`;
|
|
21
|
+
}
|
|
22
|
+
// A parameter left out, whatever zod calls it.
|
|
23
|
+
if (issue.path.length === 1 && given[path] === undefined) {
|
|
24
|
+
return `${command} needs ${written(path)}.`;
|
|
25
|
+
}
|
|
26
|
+
return path === "" ? `${issue.message}.` : `${written(path)}: ${issue.message}.`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What keeps a field of a command's input from being given on the command line: one named no-x, read as turning x off, or json, read as answering in JSON;
|
|
30
|
+
* undefined for any other. A positional is never an option, so json is its name freely.
|
|
31
|
+
*/
|
|
32
|
+
export function commandLineFieldProblem(command, name, positional) {
|
|
33
|
+
if (name.startsWith("no-")) {
|
|
34
|
+
return `${command} has a field named ${name}, and none may start with no-, which the command line reads as turning ${name.slice(3)} off: rename it.`;
|
|
35
|
+
}
|
|
36
|
+
return name === "json" && !positional ? `${command} has a field named json, which the command line reads as answering in JSON: rename it.` : undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Whether a field of a command's input is a flag, true or false, as --hidden. */
|
|
39
|
+
export function isBoolean(field) {
|
|
40
|
+
return baseDefinition(field).type === "boolean";
|
|
41
|
+
}
|
|
42
|
+
/** A list option can be given more than once, as in --tag a --tag b. */
|
|
43
|
+
export function isList(field) {
|
|
44
|
+
return baseDefinition(field).type === "array";
|
|
45
|
+
}
|
|
46
|
+
/** The type of a field of a command's input, past optional, default and nullable: string, boolean, array… */
|
|
47
|
+
export function baseType(field) {
|
|
48
|
+
return baseDefinition(field).type;
|
|
49
|
+
}
|
|
50
|
+
/** Whether a field takes numbers, alone or in a list: the command line reads them as text and turns them into numbers. */
|
|
51
|
+
export function takesNumbers(field) {
|
|
52
|
+
const definition = baseDefinition(field);
|
|
53
|
+
return definition.type === "number" || (definition.type === "array" && definition.element !== undefined && baseType(definition.element) === "number");
|
|
54
|
+
}
|
|
55
|
+
function baseDefinition(field) {
|
|
56
|
+
let definition = field.def;
|
|
57
|
+
while (definition.innerType && ["optional", "default", "nullable"].includes(definition.type)) {
|
|
58
|
+
definition = definition.innerType.def;
|
|
59
|
+
}
|
|
60
|
+
return definition;
|
|
61
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Mood } from "../core/index.ts";
|
|
2
|
+
import { type Sayer, type Talk, type Words } from "../games/index.ts";
|
|
3
|
+
import type { Context } from "./context.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Adds a line to the talk of the current game, going on or just over, and saves it; the AI's mood once the game is over, which the archive keeps too (D-70).
|
|
6
|
+
* With no game the line goes in the talk before it (D-77). A line refused changes nothing: it throws, in words for whoever spoke.
|
|
7
|
+
*/
|
|
8
|
+
export declare function say(folder: string, who: Sayer, text: string, at: Date, mood?: Mood): Talk;
|
|
9
|
+
/** What the player wrote on the board page that the AI has not been given in this chat, given now: each game tool gives it once. */
|
|
10
|
+
export declare function newWords(context: Context): string[];
|
|
11
|
+
/** Every word of the player's the AI has not answered, given again: status gives them all, for a chat that lost them in a summary. */
|
|
12
|
+
export declare function unansweredWords(context: Context): string[];
|
|
13
|
+
/** What the player wrote on the board page that the AI has not been given in this chat, still to give: the AI's move would count as its answer. */
|
|
14
|
+
export declare function waitingWords(context: Context, given?: ReadonlySet<string>): Words[];
|
|
15
|
+
/** Gives the words to the AI: from now on they count as given in this chat. Their text. */
|
|
16
|
+
export declare function give(context: Context, words: readonly Words[]): string[];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// The talk on the board page (D-51, T-65): a line of the player's or the AI's, saved with the game, and the player's words the AI has not been given yet.
|
|
2
|
+
import { endWithMood } from "../core/index.js";
|
|
3
|
+
import { archiveEnded, endOf, loadGame, loadTableTalk, saveGame, saveTableTalk, talkLimits, talkLine, unheardWords } from "../games/index.js";
|
|
4
|
+
import { gameFolder, helpsIn } from "../profiles/index.js";
|
|
5
|
+
import { gameAt } from "./clock.js";
|
|
6
|
+
/**
|
|
7
|
+
* Adds a line to the talk of the current game, going on or just over, and saves it; the AI's mood once the game is over, which the archive keeps too (D-70).
|
|
8
|
+
* With no game the line goes in the talk before it (D-77). A line refused changes nothing: it throws, in words for whoever spoke.
|
|
9
|
+
*/
|
|
10
|
+
export function say(folder, who, text, at, mood) {
|
|
11
|
+
const current = gameAt(folder, at);
|
|
12
|
+
if (current === undefined) {
|
|
13
|
+
return sayAtTable(folder, who, text, at, mood);
|
|
14
|
+
}
|
|
15
|
+
if (mood !== undefined && endOf(current.game) === undefined) {
|
|
16
|
+
throw new Error("While the game goes on your mood comes with ai_move: say it without mood. Nothing changed.");
|
|
17
|
+
}
|
|
18
|
+
// With the helps on the AI writes as long as the analyst (D-78).
|
|
19
|
+
current.talk.push(talkLine(current, who, text, at, undefined, aiMost(folder, who, text)));
|
|
20
|
+
if (mood !== undefined) {
|
|
21
|
+
endWithMood(current.game, mood);
|
|
22
|
+
}
|
|
23
|
+
saveGame(folder, current);
|
|
24
|
+
if (mood !== undefined) {
|
|
25
|
+
archiveEnded(folder, current);
|
|
26
|
+
}
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
/** The most a line of the AI's may hold in the games of `folder`: an analyst's note with the helps on (D-78); a longer one with them off is refused. */
|
|
30
|
+
function aiMost(folder, who, text) {
|
|
31
|
+
if (who !== "ai") {
|
|
32
|
+
return talkLimits.ai;
|
|
33
|
+
}
|
|
34
|
+
if (helpsIn(folder)) {
|
|
35
|
+
return talkLimits.aiHelps;
|
|
36
|
+
}
|
|
37
|
+
if (text.trim().length > talkLimits.ai) {
|
|
38
|
+
throw new Error(`The player has the helps off: write at most ${talkLimits.ai} characters. Nothing changed.`);
|
|
39
|
+
}
|
|
40
|
+
return talkLimits.ai;
|
|
41
|
+
}
|
|
42
|
+
/** What the player wrote on the board page that the AI has not been given in this chat, given now: each game tool gives it once. */
|
|
43
|
+
export function newWords(context) {
|
|
44
|
+
return give(context, waitingWords(context));
|
|
45
|
+
}
|
|
46
|
+
/** Every word of the player's the AI has not answered, given again: status gives them all, for a chat that lost them in a summary. */
|
|
47
|
+
export function unansweredWords(context) {
|
|
48
|
+
return give(context, waitingWords(context, new Set()));
|
|
49
|
+
}
|
|
50
|
+
/** What the player wrote on the board page that the AI has not been given in this chat, still to give: the AI's move would count as its answer. */
|
|
51
|
+
export function waitingWords(context, given = context.given) {
|
|
52
|
+
try {
|
|
53
|
+
const folder = gameFolder(context.home());
|
|
54
|
+
return unheardWords(loadGame(folder) ?? loadTableTalk(folder), given);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// A game.json that cannot be read: the tool says so in its own words.
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Gives the words to the AI: from now on they count as given in this chat. Their text. */
|
|
62
|
+
export function give(context, words) {
|
|
63
|
+
const before = context.given.size;
|
|
64
|
+
for (const { key } of words) {
|
|
65
|
+
context.given.add(key);
|
|
66
|
+
}
|
|
67
|
+
if (context.given.size > before) {
|
|
68
|
+
context.heard?.();
|
|
69
|
+
}
|
|
70
|
+
return words.map(({ text }) => text);
|
|
71
|
+
}
|
|
72
|
+
/** A line of the talk with no game, which starts it the first time; no mood, which only the end of a game takes. */
|
|
73
|
+
function sayAtTable(folder, who, text, at, mood) {
|
|
74
|
+
if (who === "agent") {
|
|
75
|
+
throw new Error("There is no game to think on. Nothing changed.");
|
|
76
|
+
}
|
|
77
|
+
if (mood !== undefined) {
|
|
78
|
+
throw new Error("There is no game: a mood goes with a game's end. Say it without mood. Nothing changed.");
|
|
79
|
+
}
|
|
80
|
+
const before = loadTableTalk(folder);
|
|
81
|
+
const table = { started: before.started === "" ? at.toISOString() : before.started, talk: [...before.talk, talkLine(before, who, text, at, undefined, aiMost(folder, who, text))] };
|
|
82
|
+
saveTableTalk(folder, table);
|
|
83
|
+
return table;
|
|
84
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type GameSummary, type Speech } from "./game-summary.ts";
|
|
3
|
+
interface Status {
|
|
4
|
+
version: string;
|
|
5
|
+
folder: string;
|
|
6
|
+
/** The board page's address, where the player plays when the chat serves it. */
|
|
7
|
+
page?: string;
|
|
8
|
+
/** No browser has the board page open: the player needs its address first. */
|
|
9
|
+
pageClosed?: true;
|
|
10
|
+
/** Who plays, from the profile in use (D-23), once the player has completed it (D-71). */
|
|
11
|
+
player?: string;
|
|
12
|
+
/** The player's language when it is not English. */
|
|
13
|
+
speech?: Speech;
|
|
14
|
+
game?: GameSummary;
|
|
15
|
+
/** Why the game on disk cannot be read, when it cannot. */
|
|
16
|
+
problem?: string;
|
|
17
|
+
/** What the player wrote on the board page that the AI has not answered yet, all of it (T-65). */
|
|
18
|
+
words?: string[];
|
|
19
|
+
/** The game in analysis on the board page once none goes on (D-69), which the player may have brought there (T-92). */
|
|
20
|
+
analysis?: {
|
|
21
|
+
game: string;
|
|
22
|
+
brought?: true;
|
|
23
|
+
};
|
|
24
|
+
/** A game going on from before this chat, told once a chat: the player says whether to go on with it. */
|
|
25
|
+
fromBefore?: true;
|
|
26
|
+
}
|
|
27
|
+
/** What Chesspal sees now, the first call of a chat: a dashboard under 150 tokens while the player is to move, with the next step. */
|
|
28
|
+
export declare const status: import("./registry.ts").Command<z.ZodObject<{}, z.core.$strip>, Status>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { archivedName, cleanSince, endOf, gameInAnalysis, lastChatStart } from "../games/index.js";
|
|
3
|
+
import { gameFolder } from "../profiles/index.js";
|
|
4
|
+
import { version } from "../version.js";
|
|
5
|
+
import { gameAt } from "./clock.js";
|
|
6
|
+
import { closingLines, newGameStep, positionLines, sidesLine, speechLine, speechOf, summaryOf, withThought, wordLines } from "./game-summary.js";
|
|
7
|
+
import { levelText } from "./levels.js";
|
|
8
|
+
import { closedPage, pageGiven, pageLine } from "./page-link.js";
|
|
9
|
+
import { playerLine } from "./profiles.js";
|
|
10
|
+
import { defineCommand } from "./registry.js";
|
|
11
|
+
import { unansweredWords } from "./say.js";
|
|
12
|
+
/** What Chesspal sees now, the first call of a chat: a dashboard under 150 tokens while the player is to move, with the next step. */
|
|
13
|
+
export const status = defineCommand({
|
|
14
|
+
name: "status",
|
|
15
|
+
description: "Shows what Chesspal sees now: its version, the profile's folder and the game, with your next step. Call it at the start of every chat, "
|
|
16
|
+
+ "before anything else.",
|
|
17
|
+
input: z.object({}),
|
|
18
|
+
// Read only for the host: a flag that has fallen is written, as the time already decided.
|
|
19
|
+
readOnly: true,
|
|
20
|
+
alwaysLoad: true,
|
|
21
|
+
showsGame: true,
|
|
22
|
+
run: (_input, context) => {
|
|
23
|
+
// The folder is the profile's, in the data folder (D-71): the names of its games are relative to it.
|
|
24
|
+
const folder = gameFolder(context.home());
|
|
25
|
+
const player = playerLine(context);
|
|
26
|
+
const speech = speechOf(folder, context.language);
|
|
27
|
+
const status = { version, folder, ...(player === undefined ? {} : { player }), ...(speech === undefined ? {} : { speech }) };
|
|
28
|
+
if (context.page !== undefined) {
|
|
29
|
+
status.page = context.page;
|
|
30
|
+
}
|
|
31
|
+
if (closedPage(context) !== undefined) {
|
|
32
|
+
status.pageClosed = true;
|
|
33
|
+
}
|
|
34
|
+
const first = firstInChat(context);
|
|
35
|
+
try {
|
|
36
|
+
const current = gameAt(folder, context.now());
|
|
37
|
+
// With no game the player may have written before the first (D-77).
|
|
38
|
+
const words = unansweredWords(context);
|
|
39
|
+
if (words.length > 0) {
|
|
40
|
+
status.words = words;
|
|
41
|
+
}
|
|
42
|
+
if (current !== undefined) {
|
|
43
|
+
status.game = withThought(summaryOf(current, context.now(), archivedName(folder, current)), folder, current);
|
|
44
|
+
if (status.pageClosed === true) {
|
|
45
|
+
pageGiven(context, current.started);
|
|
46
|
+
}
|
|
47
|
+
if (first && endOf(current.game) === undefined && fromBeforeChat(context, current.started)) {
|
|
48
|
+
status.fromBefore = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const analysis = current === undefined || status.game?.end !== undefined ? gameInAnalysis(folder) : undefined;
|
|
52
|
+
if (analysis !== undefined) {
|
|
53
|
+
status.analysis = { game: analysis.game, ...(analysis.brought === true ? { brought: true } : {}) };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
status.problem = error.message;
|
|
58
|
+
}
|
|
59
|
+
return status;
|
|
60
|
+
},
|
|
61
|
+
format: ({ version, folder, page, pageClosed, player, speech, game, problem, words = [], analysis, fromBefore }) => [
|
|
62
|
+
`Chesspal ${version}. Folder: ${folder}`,
|
|
63
|
+
...(page === undefined ? [] : [pageLine(page, pageClosed === true)]),
|
|
64
|
+
...(player === undefined ? [] : [player]),
|
|
65
|
+
...(speech === undefined ? [] : [speechLine(speech)]),
|
|
66
|
+
...(fromBefore === true ? [fromBeforeLine] : []),
|
|
67
|
+
...(problem !== undefined ? [problem] : gameLines(game, words, analysis?.brought === true)),
|
|
68
|
+
...(analysis === undefined ? [] : [analysisLine(analysis)]),
|
|
69
|
+
].join("\n"),
|
|
70
|
+
});
|
|
71
|
+
// The game going on as a chat starts: the player may have come back for it, or want another.
|
|
72
|
+
const fromBeforeLine = "A game from before this chat is going on: ask the player whether to go on with it. For a new one, " + newGameStep;
|
|
73
|
+
/**
|
|
74
|
+
* Whether this is the chat's first status: once a chat where the MCP server keeps what it told, again in a chat cleared or summarized, since its start keys
|
|
75
|
+
* it (D-57); never on the command line.
|
|
76
|
+
*/
|
|
77
|
+
function firstInChat(context) {
|
|
78
|
+
const key = `game before ${lastChatStart(context.folder)?.at ?? ""}`;
|
|
79
|
+
if (context.told === undefined || context.told.has(key)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
context.told.add(key);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/** Whether a game began before the chat: one a summarized or resumed chat still holds is its own; without the host's word, any game is. */
|
|
86
|
+
function fromBeforeChat(context, started) {
|
|
87
|
+
const start = lastChatStart(context.folder);
|
|
88
|
+
return start === undefined || cleanSince(start, started);
|
|
89
|
+
}
|
|
90
|
+
/** The game in analysis; the next step when the player has just brought it on the board page (T-92). */
|
|
91
|
+
function analysisLine({ game, brought }) {
|
|
92
|
+
return brought === true
|
|
93
|
+
? `The player brought ${game} on the board page to analyse it. Next: call analyse_game {"game": "${game}"}, then keep your review on that page with annotate_game, and go over it with them.`
|
|
94
|
+
: `In analysis on the board page: ${game}; analyse_game {"game": "${game}"} goes over it with the player.`;
|
|
95
|
+
}
|
|
96
|
+
/** The game's lines; a game the player brought to analyse takes the place of the next step (T-92). */
|
|
97
|
+
function gameLines(game, words, brought) {
|
|
98
|
+
if (game === undefined) {
|
|
99
|
+
return ["No game.", ...(brought ? wordLines(words) : closingLines(game, words))];
|
|
100
|
+
}
|
|
101
|
+
const moves = game.lastMoves === "" ? [] : [`Moves: ${game.lastMoves}`];
|
|
102
|
+
return [`Game: ${sidesLine(game.ai)}, ${levelText(game.level)}.`, ...moves, ...positionLines(game), ...(brought ? wordLines(words) : closingLines(game, words))];
|
|
103
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// The games kept to study (D-68): played elsewhere and brought to analyse_game as a PGN, in the folder of the profile that brought them.
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { keptGame, keptGames } from "../games/index.js";
|
|
4
|
+
import { gameFolder } from "../profiles/index.js";
|
|
5
|
+
import { openingName } from "./opening-book.js";
|
|
6
|
+
import { defineCommand } from "./registry.js";
|
|
7
|
+
import { knownTag } from "./study.js";
|
|
8
|
+
/** The games kept to study, one line each, the latest kept first. */
|
|
9
|
+
export const studyGames = defineCommand({
|
|
10
|
+
name: "study_games",
|
|
11
|
+
description: "Lists the games kept to study, which the player brought from elsewhere, the latest kept first: who played, when, "
|
|
12
|
+
+ "the result, the opening, how long, and its name. analyse_game with pgn keeps a game here; analyse_game, look and position take its name as game. "
|
|
13
|
+
+ "Example: study_games {}.",
|
|
14
|
+
input: z.object({}),
|
|
15
|
+
readOnly: true,
|
|
16
|
+
idempotent: true,
|
|
17
|
+
run: (_input, context) => {
|
|
18
|
+
const folder = gameFolder(context.home());
|
|
19
|
+
return { games: keptGames(folder).flatMap((name) => studyLines(folder, name)) };
|
|
20
|
+
},
|
|
21
|
+
format: ({ games }) => (games.length === 0 ? "No game kept to study yet: analyse_game with the PGN of a game played elsewhere keeps it here." : games.join("\n")),
|
|
22
|
+
});
|
|
23
|
+
/** Ann vs Bob, 2026.10.01: 1-0, C23 Bishop's Opening, 4 moves. study/2026-11-04-153012.pgn; none for a game that cannot be read. */
|
|
24
|
+
function studyLines(root, name) {
|
|
25
|
+
let record;
|
|
26
|
+
try {
|
|
27
|
+
record = keptGame(root, name);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// A game that cannot be read stays out of the list.
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const { tags, moves } = record;
|
|
34
|
+
const date = knownTag(tags.Date);
|
|
35
|
+
const result = tags.Result === undefined || tags.Result === "*" ? "result unknown" : tags.Result;
|
|
36
|
+
const opening = openingName(record);
|
|
37
|
+
const count = Math.ceil(moves.length / 2);
|
|
38
|
+
const players = `${tags.White ?? "?"} vs ${tags.Black ?? "?"}${date === undefined ? "" : `, ${date}`}`;
|
|
39
|
+
return [`${players}: ${result}${opening === undefined ? "" : `, ${opening}`}, ${count} ${count === 1 ? "move" : "moves"}. ${name}`];
|
|
40
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Game, type GameRecord } from "../core/index.ts";
|
|
3
|
+
import { type CurrentGame } from "../games/index.ts";
|
|
4
|
+
import type { Context } from "./context.ts";
|
|
5
|
+
/** What look and position take to show any position of a game that is over. */
|
|
6
|
+
export declare const studyInput: {
|
|
7
|
+
game: z.ZodOptional<z.ZodString>;
|
|
8
|
+
move: z.ZodOptional<z.ZodString>;
|
|
9
|
+
};
|
|
10
|
+
/** A game to study: its record, and its name in the archive when it is there. */
|
|
11
|
+
export interface Studied {
|
|
12
|
+
readonly record: GameRecord;
|
|
13
|
+
readonly name?: string;
|
|
14
|
+
}
|
|
15
|
+
/** A position of a game: the game as it stood after a move, and that move as the analysis writes it (12...Kf8); none at the start. */
|
|
16
|
+
export interface StudiedPosition {
|
|
17
|
+
readonly game: Game;
|
|
18
|
+
readonly move?: string;
|
|
19
|
+
}
|
|
20
|
+
/** The current game when it is over, or none; refused while it goes on, for every game: games are analysed once the one being played is over. */
|
|
21
|
+
export declare function noGameGoingOn(context: Context): CurrentGame | undefined;
|
|
22
|
+
/** The game played or kept to study by that name; without one, the current game over, or else the latest of the archive; refused while a game goes on. */
|
|
23
|
+
export declare function gameToStudy(context: Context, name?: string): Studied;
|
|
24
|
+
/** A game played or kept to study by its name as the lists write it, or without its folder or its .pgn; refused when it is not there. */
|
|
25
|
+
export declare function namedGame(context: Context, name: string): Required<Studied>;
|
|
26
|
+
/** The index of a move in the game's record, given as analyse_game numbers it: 12. for White's, 12... for Black's, 12...Kf8 too. */
|
|
27
|
+
export declare function moveIndex(record: GameRecord, move: string): number;
|
|
28
|
+
/** The number of each move of a game as the analysis writes it: 1. for White's first move, 1... for Black's. */
|
|
29
|
+
export declare function moveNumbers(record: GameRecord): string[];
|
|
30
|
+
/**
|
|
31
|
+
* The game as it stood after a move, given by its number (12. for White's, 12... for Black's) and, if wanted, the move itself (12...Kf8), which decides
|
|
32
|
+
* the side when the dots do not; after the last move without one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function positionAfter(record: GameRecord, move?: string): StudiedPosition;
|
|
35
|
+
/** A tag PGN leaves unknown with ? or -, as nothing. */
|
|
36
|
+
export declare function knownTag(tag: string | undefined): string | undefined;
|
|
37
|
+
/** Where a position of a game studied stands: After 12...Kf8 in games/2026-11-02-153012.pgn: White to move. */
|
|
38
|
+
export declare function positionLine({ game, move }: StudiedPosition, name?: string): string;
|