chesspal 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/cli/cli.js +7 -2
  2. package/dist/cli/command-line.js +5 -3
  3. package/dist/cli/prompt-hook.d.ts +17 -0
  4. package/dist/cli/prompt-hook.js +100 -0
  5. package/dist/commands/analysis/analyse.js +8 -14
  6. package/dist/commands/analysis/annotate.d.ts +8 -0
  7. package/dist/commands/analysis/annotate.js +19 -3
  8. package/dist/commands/answers/agents.d.ts +7 -5
  9. package/dist/commands/answers/agents.js +16 -9
  10. package/dist/commands/answers/clock.js +2 -1
  11. package/dist/commands/answers/drawings.js +6 -2
  12. package/dist/commands/answers/index.d.ts +4 -3
  13. package/dist/commands/answers/index.js +4 -3
  14. package/dist/commands/answers/lines.d.ts +5 -3
  15. package/dist/commands/answers/lines.js +14 -7
  16. package/dist/commands/answers/opening-book.d.ts +13 -7
  17. package/dist/commands/answers/opening-book.js +34 -38
  18. package/dist/commands/answers/opening-tags.d.ts +4 -0
  19. package/dist/commands/answers/opening-tags.js +11 -0
  20. package/dist/commands/answers/play-move.d.ts +3 -1
  21. package/dist/commands/answers/play-move.js +9 -6
  22. package/dist/commands/answers/say.js +2 -1
  23. package/dist/commands/answers/summary.d.ts +5 -2
  24. package/dist/commands/answers/summary.js +16 -5
  25. package/dist/commands/game/ai-move.js +4 -4
  26. package/dist/commands/game/coach.js +7 -6
  27. package/dist/commands/game/end.js +2 -2
  28. package/dist/commands/game/export.d.ts +1 -1
  29. package/dist/commands/game/index.d.ts +1 -1
  30. package/dist/commands/game/index.js +1 -1
  31. package/dist/commands/game/new.js +41 -11
  32. package/dist/commands/game/player-move.d.ts +16 -0
  33. package/dist/commands/game/player-move.js +69 -19
  34. package/dist/commands/game/ponder.js +4 -1
  35. package/dist/commands/game/status.d.ts +1 -1
  36. package/dist/commands/game/status.js +6 -6
  37. package/dist/commands/index.d.ts +3 -3
  38. package/dist/commands/index.js +3 -3
  39. package/dist/commands/profiles/past-games.d.ts +1 -1
  40. package/dist/commands/profiles/past-games.js +21 -10
  41. package/dist/commands/registry/context.d.ts +2 -0
  42. package/dist/commands/registry/context.js +5 -0
  43. package/dist/commands/registry/index.d.ts +1 -1
  44. package/dist/commands/registry/index.js +1 -1
  45. package/dist/commands/talk/ai-say.d.ts +1 -1
  46. package/dist/commands/talk/ai-say.js +6 -5
  47. package/dist/core/game.d.ts +13 -0
  48. package/dist/core/game.js +29 -3
  49. package/dist/core/index.d.ts +4 -4
  50. package/dist/core/index.js +3 -3
  51. package/dist/core/openings.d.ts +20 -6
  52. package/dist/core/openings.js +45 -22
  53. package/dist/core/pgn.d.ts +4 -0
  54. package/dist/core/pgn.js +35 -6
  55. package/dist/games/archive.d.ts +25 -5
  56. package/dist/games/archive.js +52 -19
  57. package/dist/games/chat-start.d.ts +2 -2
  58. package/dist/games/chat-start.js +6 -1
  59. package/dist/games/current-game.d.ts +2 -2
  60. package/dist/games/current-game.js +3 -2
  61. package/dist/games/index.d.ts +2 -1
  62. package/dist/games/index.js +2 -1
  63. package/dist/games/position-shown.d.ts +24 -0
  64. package/dist/games/position-shown.js +51 -0
  65. package/dist/home/index.d.ts +1 -1
  66. package/dist/home/index.js +1 -1
  67. package/dist/home/same-path.d.ts +6 -1
  68. package/dist/home/same-path.js +22 -3
  69. package/dist/host-files/host-files.d.ts +5 -0
  70. package/dist/host-files/host-files.js +13 -4
  71. package/dist/host-files/index.d.ts +1 -1
  72. package/dist/host-files/index.js +1 -1
  73. package/dist/page/app/app.js +30 -7
  74. package/dist/page/app/served.js +9 -4
  75. package/dist/page/avatars/torretta.css +4 -4
  76. package/dist/page/base.css +1 -1
  77. package/dist/page/board/board.css +4 -2
  78. package/dist/page/dialogs/dialogs.css +3 -1
  79. package/dist/page/game/column.js +10 -8
  80. package/dist/page/game/game.css +86 -6
  81. package/dist/page/game/plies.js +4 -0
  82. package/dist/page/game/review-progress.js +11 -0
  83. package/dist/page/guide/drawings/drawings.css +0 -1
  84. package/dist/page/guide/en.md +2 -2
  85. package/dist/page/guide/it.md +2 -2
  86. package/dist/page/layout/icon.ico +0 -0
  87. package/dist/page/layout/layout.css +32 -15
  88. package/dist/page/layout/window.js +41 -2
  89. package/dist/page/legend/legend.css +4 -16
  90. package/dist/page/legend/legend.js +28 -15
  91. package/dist/page/profile/settings.js +1 -0
  92. package/dist/page/table/action.js +42 -0
  93. package/dist/page/table/table.css +57 -39
  94. package/dist/page/table/window.js +67 -67
  95. package/dist/page/talk/talk.css +52 -9
  96. package/dist/page/talk/talk.js +65 -20
  97. package/dist/page/talk/voice.js +92 -21
  98. package/dist/page/text.js +24 -8
  99. package/dist/server/bell/bell.d.ts +24 -27
  100. package/dist/server/bell/bell.js +79 -143
  101. package/dist/server/bell/index.d.ts +1 -1
  102. package/dist/server/bell/index.js +2 -2
  103. package/dist/server/mcp/mcp.js +49 -7
  104. package/dist/server/page/analyses.js +15 -4
  105. package/dist/server/page/files.js +3 -1
  106. package/dist/server/page/game.d.ts +5 -0
  107. package/dist/server/page/game.js +78 -13
  108. package/dist/server/page/http.d.ts +1 -1
  109. package/dist/server/page/page.d.ts +14 -9
  110. package/dist/server/page/page.js +111 -26
  111. package/dist/server/page/table.d.ts +38 -4
  112. package/dist/server/page/table.js +61 -23
  113. package/dist/server/page/windows.d.ts +22 -0
  114. package/dist/server/page/windows.js +45 -0
  115. package/dist/server/ports/index.d.ts +1 -0
  116. package/dist/server/ports/index.js +2 -0
  117. package/dist/server/ports/ports.d.ts +78 -0
  118. package/dist/server/ports/ports.js +222 -0
  119. package/dist/server/table/browser.d.ts +22 -6
  120. package/dist/server/table/browser.js +98 -20
  121. package/dist/server/table/hosts/hooks.d.ts +6 -0
  122. package/dist/server/table/hosts/hooks.js +38 -0
  123. package/dist/server/table/hosts/hosts.d.ts +13 -5
  124. package/dist/server/table/hosts/hosts.js +16 -10
  125. package/dist/server/table/hosts/index.d.ts +1 -0
  126. package/dist/server/table/hosts/index.js +1 -0
  127. package/dist/server/table/hosts/play.d.ts +6 -4
  128. package/dist/server/table/hosts/play.js +58 -16
  129. package/dist/server/table/index.d.ts +1 -1
  130. package/dist/server/table/launch.d.ts +5 -4
  131. package/dist/server/table/launch.js +18 -5
  132. package/dist/server/table/link.js +1 -0
  133. package/dist/server/table/table.d.ts +7 -0
  134. package/dist/server/table/table.js +39 -11
  135. package/instructions/opponent.md +1 -1
  136. package/package.json +1 -1
package/dist/cli/cli.js CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { runCommandLine } from "./command-line.js";
3
2
  const args = process.argv.slice(2);
4
3
  // The table's own words (D-104): alone or open, its page; table, icon (--remove), play <host>. The host's: mcp, its server; listen, its doorbell; chat, how the chat
5
- // started. Everything else is a command.
4
+ // started; prompt, the move the player wrote alone. Everything else is a command. Each loads only what it needs: prompt runs on every message of the chat.
6
5
  if (args.length === 0) {
7
6
  await openTableHere(true);
8
7
  }
@@ -40,7 +39,12 @@ else if (args.length === 1 && args[0] === "chat") {
40
39
  const { recordChatStart } = await import("./chat-hook.js");
41
40
  await recordChatStart();
42
41
  }
42
+ else if (args.length === 1 && args[0] === "prompt") {
43
+ const { answerPrompt } = await import("./prompt-hook.js");
44
+ await answerPrompt();
45
+ }
43
46
  else {
47
+ const { runCommandLine } = await import("./command-line.js");
44
48
  const { output, exitCode } = await runCommandLine(args);
45
49
  if (exitCode === 0) {
46
50
  console.log(output);
@@ -114,3 +118,4 @@ async function serveTable() {
114
118
  process.exitCode = 1;
115
119
  }
116
120
  }
121
+ export {};
@@ -3,11 +3,12 @@ import { parseArgs } from "node:util";
3
3
  import { baseType, commandLineFieldProblem, commands, defaultContext, inputProblems, isBoolean, isList, takesNumbers } from "../commands/index.js";
4
4
  import { rerunIfChanged } from "../games/index.js";
5
5
  import { version } from "../version.js";
6
- // The commands that exist only on the command line: cli.ts starts the table, opens its page, puts its icon, starts the server, the doorbell or the chat's
7
- // note, when one comes alone.
6
+ // The commands that exist only on the command line: cli.ts starts the table, opens its page, puts its icon, starts the server, the doorbell, the chat's
7
+ // note or the move written alone, when one comes alone.
8
8
  const mcpDescription = "Start the MCP server for an AI host. Through it the host uses these commands as tools; the host starts it, with no options.";
9
9
  const listenDescription = "Wait for the player's move on the board page and wake the chat. Claude Code starts it at the end of each turn, and Codex with --codex, as init sets them up.";
10
10
  const chatDescription = "Note how the chat started: new, cleared, resumed or compacted. Claude Code and Codex run it when a chat starts, as init sets them up.";
11
+ const promptDescription = "Play the move the player wrote alone in the chat, before the AI reads it, and tell the AI the position. Claude Code runs it on each message, as init sets it up.";
11
12
  const tableDescription = "Serve the board page with no chat, until no browser has had it open for five minutes. `chesspal` alone starts it apart and opens the page.";
12
13
  const openDescription = "Open the board page as `chesspal` alone does, without asking about the icon: what the icon runs.";
13
14
  const iconDescription = "Put Chesspal's icon on the desktop and in the menu: it opens the board page as `chesspal` alone does, with no terminal. `chesspal icon --remove` takes it away.";
@@ -16,7 +17,7 @@ const playDescription = "Start the AI in this terminal, in Claude Code or Codex,
16
17
  const initForPlayer = "Set up a folder of your own to play in, from Claude Code, Codex or the Claude desktop app.";
17
18
  const iconRemoveForPlayer = "Take Chesspal's icon away, before you uninstall it.";
18
19
  // Those of them that take no options, by name.
19
- const plainCommands = new Map([["mcp", mcpDescription], ["chat", chatDescription], ["table", tableDescription], ["open", openDescription], ["icon", iconDescription]]);
20
+ const plainCommands = new Map([["mcp", mcpDescription], ["chat", chatDescription], ["prompt", promptDescription], ["table", tableDescription], ["open", openDescription], ["icon", iconDescription]]);
20
21
  /** The `chesspal` command line: finds the command, validates its input, runs it and renders the result. */
21
22
  export async function runCommandLine(argv, context = defaultContext(), list = commands) {
22
23
  const [commandName, ...args] = argv;
@@ -175,6 +176,7 @@ function generalHelp(list) {
175
176
  line("mcp", mcpDescription),
176
177
  line("listen", listenDescription),
177
178
  line("chat", chatDescription),
179
+ line("prompt", promptDescription),
178
180
  "",
179
181
  "Run `chesspal <command> --help` for its options.",
180
182
  ].join("\n");
@@ -0,0 +1,17 @@
1
+ import type { Context } from "../commands/index.ts";
2
+ /**
3
+ * Reads the hook's input, tells the chat's page the chat is at work and, when the prompt is a move Chesspal plays, writes what the AI is told; it never
4
+ * blocks the prompt.
5
+ */
6
+ export declare function answerPrompt(input?: NodeJS.ReadableStream, sent?: Date): Promise<void>;
7
+ /**
8
+ * What the hook writes, the JSON Claude Code reads, when the prompt is one move and Chesspal played it; undefined, with nothing changed, for any other
9
+ * prompt or a move refused. `contextOf` gives what the move is played with, in the chat's folder; it then notes there the position shown to the chat.
10
+ */
11
+ export declare function promptAnswer(input: string, contextOf: (hook: HookInput) => Promise<Context>): Promise<string | undefined>;
12
+ /** What the hook reads of Claude Code's input: the prompt trimmed, the chat's session and the folder it runs in. */
13
+ export interface HookInput {
14
+ prompt?: string;
15
+ session?: string;
16
+ cwd?: string;
17
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Reads the hook's input, tells the chat's page the chat is at work and, when the prompt is a move Chesspal plays, writes what the AI is told; it never
3
+ * blocks the prompt.
4
+ */
5
+ export async function answerPrompt(input = process.stdin, sent = new Date()) {
6
+ try {
7
+ const text = await textOf(input);
8
+ const [answer] = await Promise.all([promptAnswer(text, (hook) => hookContext(hook, sent)), tellPage(hookInput(text))]);
9
+ if (answer !== undefined) {
10
+ process.stdout.write(`${answer}\n`);
11
+ }
12
+ }
13
+ catch (error) {
14
+ process.stderr.write(`Chesspal could not play the move written in the chat: ${String(error.message ?? error).replace(/\s+/g, " ")}\n`);
15
+ }
16
+ }
17
+ /**
18
+ * What the hook writes, the JSON Claude Code reads, when the prompt is one move and Chesspal played it; undefined, with nothing changed, for any other
19
+ * prompt or a move refused. `contextOf` gives what the move is played with, in the chat's folder; it then notes there the position shown to the chat.
20
+ */
21
+ export async function promptAnswer(input, contextOf) {
22
+ const hook = hookInput(input);
23
+ if (hook.prompt === undefined || !moveShape.test(hook.prompt) || hook.session === undefined) {
24
+ return undefined;
25
+ }
26
+ const [context, { playMoveSentAlone }, { hostProcess, notePositionShown }] = await Promise.all([contextOf(hook), import("../commands/index.js"), import("../games/index.js")]);
27
+ let played;
28
+ try {
29
+ played = playMoveSentAlone(hook.prompt, context);
30
+ }
31
+ catch (error) {
32
+ // Chesspal's refusals end so: the game as it was, the AI reads the prompt and answers it.
33
+ if (error instanceof Error && error.message.endsWith("Nothing changed.")) {
34
+ return undefined;
35
+ }
36
+ throw error;
37
+ }
38
+ try {
39
+ const { position, stamp, given } = played;
40
+ const host = hostProcess();
41
+ notePositionShown(context.folder, {
42
+ session: hook.session,
43
+ ...(host === undefined ? {} : { process: host }),
44
+ ...(stamp === undefined ? {} : { stamp }),
45
+ ...position,
46
+ at: context.now().toISOString(),
47
+ given,
48
+ });
49
+ }
50
+ catch (error) {
51
+ // The move is played: the AI hears it all the same, and its server asks it for status before its own, which may give the words again.
52
+ process.stderr.write(`Chesspal could not note the position it showed the chat: ${error.message}\n`);
53
+ }
54
+ return JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: played.answer } });
55
+ }
56
+ // One move and nothing else, as player_move takes it: SAN with K, Q, R, B and N, small or as figurines, or with the letters of other languages that name
57
+ // one piece (Cf3, Tf1, Dd1, e8=D), or as squares, or castling; a check or mate sign.
58
+ const moveShape = /^(?:[KQRBNkqrbnCSAFLTDt♔-♟]?[a-h]?[1-8]?[-x:×–—−]?[a-h][1-8](?:=?[QRBNqrbnCSAFLTD])?|[0Oo]-[0Oo](?:-[0Oo])?)[+#]?$/u;
59
+ /**
60
+ * Tells the chat's page that the prompt starts its turn: the doorbell lets the hook of the turn before go, and a new game asked on the page waits for
61
+ * the turn's end. A slash command or a shell line may start no turn, and so no Stop hook to end it: the page is not told.
62
+ */
63
+ async function tellPage(hook) {
64
+ if (hook.prompt === undefined || /^[/!]/.test(hook.prompt)) {
65
+ return;
66
+ }
67
+ // A page not told leaves the doorbell as it was: the move, when the prompt is one, is played all the same.
68
+ await import("../server/ports/index.js").then(({ tellChatAtWork }) => tellChatAtWork(hook), () => false);
69
+ }
70
+ /** The context of the chat's folder: the data folder its server is given, the moment the player sent the move, and Claude Code, which runs this hook. */
71
+ async function hookContext(hook, sent) {
72
+ const [{ defaultContext }, { chatFolder, serverHome }] = await Promise.all([import("../commands/index.js"), import("../server/ports/index.js")]);
73
+ const folder = chatFolder(hook);
74
+ const home = serverHome(folder);
75
+ // A playtest's data folder, which .mcp.json gives the server and not the hook.
76
+ if (home !== undefined) {
77
+ process.env.CHESSPAL_HOME = home;
78
+ }
79
+ return { ...defaultContext(), folder, now: () => sent, host: () => "claude-code" };
80
+ }
81
+ async function textOf(input) {
82
+ let text = "";
83
+ for await (const chunk of input) {
84
+ text += String(chunk);
85
+ }
86
+ return text;
87
+ }
88
+ function hookInput(text) {
89
+ try {
90
+ const { prompt, session_id: session, cwd } = JSON.parse(text);
91
+ return {
92
+ ...(typeof prompt === "string" ? { prompt: prompt.trim() } : {}),
93
+ ...(typeof session === "string" ? { session } : {}),
94
+ ...(typeof cwd === "string" ? { cwd } : {}),
95
+ };
96
+ }
97
+ catch {
98
+ return {};
99
+ }
100
+ }
@@ -5,7 +5,7 @@ import { gameInAnalysis, isGameName, keepToStudy, lastChatStart, planOf, rewrite
5
5
  import { analystAgent, instructionsFile } from "../../host-files/index.js";
6
6
  import { activeProfile, gameFolder } from "../../profiles/index.js";
7
7
  import { pointsAtSite, siteGameName, siteOfGameName } from "../../sites/index.js";
8
- import { analystOf, anotherAnalyst, balanceText, bookOpening, bookReaches, colourName, gameToStudy, knownTag, moveNumbers, namedGame, noGameGoingOn, riskLine, shownMove, speechLine, speechOf, tacticsText, } from "../answers/index.js";
8
+ import { analystOf, anotherAnalyst, balanceText, bookOpening, bookPlace, bookPlies, colourName, gameToStudy, knownTag, moveNumbers, namedGame, noGameGoingOn, riskLine, shownMove, speechLine, speechOf, tacticsText, } from "../answers/index.js";
9
9
  import { playerLine, siteText } from "../profiles/index.js";
10
10
  import { defineCommand } from "../registry/index.js";
11
11
  import { profileSiteGames } from "./site-games.js";
@@ -49,7 +49,7 @@ export const analyseGame = defineCommand({
49
49
  }
50
50
  const analysis = analysisOf(studied, activeProfile(context.home()));
51
51
  if (studied.name !== undefined) {
52
- markBook(context, { record: studied.record, name: studied.name }, analysis.moves);
52
+ markBook(context, { record: studied.record, name: studied.name });
53
53
  startAnalysis(gameFolder(context.home()), studied.name, context.now());
54
54
  }
55
55
  // The analyst's agent has its instructions in its own file, and the player's language in its prompt or, when Chesspal knows it, here (D-89);
@@ -148,8 +148,8 @@ async function siteGamePgn(context, name) {
148
148
  * The opening book's moves marked as such in the game kept (D-69): a fact, not a judgment, so Chesspal puts the badge itself on each move from the start to the
149
149
  * last one the book knows, where the AI gave none; a game from another position has none.
150
150
  */
151
- function markBook(context, { record, name }, moves) {
152
- const inBook = bookPliesOfFacts(record, moves);
151
+ function markBook(context, { record, name }) {
152
+ const inBook = bookPlies(record);
153
153
  const unmarked = record.moves.slice(0, inBook).some((move) => reviewOf(move.comment)?.badge === undefined);
154
154
  if (!unmarked) {
155
155
  return;
@@ -160,11 +160,6 @@ function markBook(context, { record, name }, moves) {
160
160
  });
161
161
  rewriteGame(gameFolder(context.home()), name, { ...record, moves: marked });
162
162
  }
163
- /** How many plies from the start the opening book knows, as opening-book's bookPlies counts them, read from the facts already found: none from another position. */
164
- function bookPliesOfFacts(record, moves) {
165
- const firstOut = moves.findIndex((move) => move.outOfBook);
166
- return record.tags.FEN !== undefined ? 0 : firstOut >= 0 ? firstOut : moves.length;
167
- }
168
163
  function analysisOf({ record, name }, profile) {
169
164
  const { tags } = record;
170
165
  const ai = sideNamed(record, "AI");
@@ -186,7 +181,7 @@ function analysisOf({ record, name }, profile) {
186
181
  termination: ended?.termination,
187
182
  opening: opening === undefined ? undefined : `${opening.eco} ${opening.name}`,
188
183
  inBook: opening !== undefined && !moves.some((move) => move.outOfBook) ? true : undefined,
189
- bookTo: opening === undefined ? undefined : moves[bookPliesOfFacts(record, moves) - 1]?.move,
184
+ bookTo: opening === undefined ? undefined : moves[bookPlies(record) - 1]?.move,
190
185
  review: [gameReviewOf(record), closingOf(record)].filter((word) => word !== undefined).join(" ") || undefined,
191
186
  name,
192
187
  start: tags.FEN === undefined ? undefined : { fen: tags.FEN, ...start },
@@ -200,7 +195,8 @@ function movesOf(record, ai) {
200
195
  const increment = timeControlOfTag(record.tags.TimeControl ?? "")?.increment;
201
196
  // The time each side had left after its last move: the first move of each side spends no time it can tell, as the sites start their clocks apart.
202
197
  const left = {};
203
- let inBook = record.tags.FEN === undefined && bookReaches(game.position.fen);
198
+ // The first move no line of the book reaches is out of book (D-68); a game from another position has no book.
199
+ const leftBook = bookPlace(record)?.plies;
204
200
  let before = game.facts;
205
201
  let balance = materialOf(game.position.fen).balance;
206
202
  const start = { atRisk: riskLine(before), oneLegal: oneLegalMove(before), mates: matesOf(before), tactics: tacticsText(before, game.possibilities, true) };
@@ -212,8 +208,6 @@ function movesOf(record, ai) {
212
208
  if (!played.played) {
213
209
  throw new Error(`The game's move ${numbers[ply]}${san} cannot be played: ${played.message} Fix the game's PGN.`);
214
210
  }
215
- const outOfBook = inBook && !bookReaches(game.position.fen);
216
- inBook &&= !outOfBook;
217
211
  const facts = game.facts;
218
212
  const over = game.outcome.over;
219
213
  const now = materialOf(game.position.fen).balance;
@@ -230,7 +224,7 @@ function movesOf(record, ai) {
230
224
  oneLegal: over ? undefined : oneLegalMove(facts),
231
225
  mates: over ? undefined : matesOf(facts),
232
226
  tactics: over ? undefined : tacticsText(facts, game.possibilities, true),
233
- outOfBook: outOfBook ? true : undefined,
227
+ outOfBook: ply === leftBook ? true : undefined,
234
228
  drawOffer: comment?.includes("(=)") === true ? true : undefined,
235
229
  said: side === ai ? saidIn(comment) : undefined,
236
230
  review: reviewOf(comment),
@@ -14,6 +14,14 @@ interface Annotated {
14
14
  left: string[];
15
15
  /** The chat corrected a note of a review that already has its word on the game. */
16
16
  summaryStands: boolean;
17
+ /** Where this call wrote a move with another language's letters (5., your summary), and the first such move: the page shows them as plain text (D-87). */
18
+ lettered: {
19
+ where: string[];
20
+ first?: {
21
+ written: string;
22
+ san: string;
23
+ };
24
+ };
17
25
  }
18
26
  /** The AI's badges and notes on a game over, and its word on the whole game, written into the game's PGN (D-69). */
19
27
  export declare const annotateGame: import("../registry/registry.ts").Command<z.ZodObject<{
@@ -1,7 +1,7 @@
1
1
  // The AI's review of a game over (D-69): a badge on each move that matters, as chess.com marks them, with a note when it needs one, and its word on the
2
2
  // whole game, kept in the game's PGN for the board page. The judgment is the AI's alone: Chesspal gives no criteria (D-6, D-43).
3
3
  import { z } from "zod";
4
- import { annotatorOf, badges, Game, gameReviewOf, leadOf, reviewed, reviewOf, withAnnotator, withGameReview, withLead } from "../../core/index.js";
4
+ import { annotatorOf, badges, figurinesOf, foreignMovesIn, Game, gameReviewOf, leadOf, reviewed, reviewOf, withAnnotator, withGameReview, withLead } from "../../core/index.js";
5
5
  import { gameInAnalysis, rewriteGame, startAnalysis } from "../../games/index.js";
6
6
  import { analystAgent } from "../../host-files/index.js";
7
7
  import { gameFolder } from "../../profiles/index.js";
@@ -105,9 +105,10 @@ export const annotateGame = defineCommand({
105
105
  left: [...corrected],
106
106
  // A correction after the review's word on the game: that word may now say otherwise (Priya).
107
107
  summaryStands: correction && gameReviewOf(studied.record) !== undefined,
108
+ lettered: letteredIn([...notes.map(({ move, note }) => [move, note]), ["your summary", summary], ["your last word", closing]]),
108
109
  };
109
110
  },
110
- format: ({ game, marked, summary, closing, bookUnnoted, estimated, unestimated, left, summaryStands }) => {
111
+ format: ({ game, marked, summary, closing, bookUnnoted, estimated, unestimated, left, summaryStands, lettered }) => {
111
112
  const kept = [
112
113
  marked === 0 ? undefined : `${marked} ${marked === 1 ? "move" : "moves"} marked`,
113
114
  estimated === 0 ? undefined : `who is ahead after ${estimated} ${estimated === 1 ? "move" : "moves"}`,
@@ -129,9 +130,24 @@ export const annotateGame = defineCommand({
129
130
  : [`${left.join(" ")} ${one ? "keeps the note" : "keep the notes"} the AI wrote with the player: leave ${one ? "it" : "them"}, and don't contradict ${one ? "it" : "them"} in your summary${summary ? "; if this one does, give it again" : ""}.`];
130
131
  const head = kept.length === 0 ? `Nothing new kept in ${game}.` : `Kept in ${game}: ${[kept.slice(0, -1).join(", "), kept.at(-1)].filter((part) => part !== "").join(" and ")}. The board page shows them.`;
131
132
  const stands = summaryStands ? ["The review's word on the game stays as written: if it says otherwise, give summary again."] : [];
132
- return [head, ...stands, ...corrected, ...book, ...ahead].join("\n");
133
+ const single = lettered.where.length === 1;
134
+ const letters = lettered.first === undefined
135
+ ? []
136
+ : [
137
+ `${lettered.where.join(", ")} ${single ? "writes" : "write"} moves with another language's letters, which the page shows as plain text: give ${single ? "it" : "them"} `
138
+ + `again with the figurines (${figurinesOf(lettered.first.san)} for ${lettered.first.written}).`,
139
+ ];
140
+ return [head, ...stands, ...corrected, ...letters, ...book, ...ahead].join("\n");
133
141
  },
134
142
  });
143
+ /** Where the texts of a call write a move with another language's letters, by the move or the part they were given for, and the first such move. */
144
+ function letteredIn(texts) {
145
+ const found = texts.flatMap(([where, text]) => {
146
+ const [first] = foreignMovesIn(text ?? "");
147
+ return first === undefined ? [] : [{ where, first }];
148
+ });
149
+ return { where: [...new Set(found.map(({ where }) => where))], ...(found[0] === undefined ? {} : { first: found[0].first }) };
150
+ }
135
151
  /** The moves without who is ahead after them, as analyse_game numbers them: all but the last when the board ended the game there, which says it. */
136
152
  function unestimatedMoves(record, numbers, over) {
137
153
  return record.moves.flatMap((move, index) => leadOf(move.comment) !== undefined || (over && index === record.moves.length - 1) ? [] : [numbers[index]]);
@@ -25,15 +25,15 @@ export declare function coachRuns(context: Context): boolean;
25
25
  /** Whether one of Chesspal's agents runs beside this chat: in a host that starts agents, with its file in the player's folder. */
26
26
  export declare function agentRuns(context: Context, agent: string): boolean;
27
27
  /**
28
- * Whether to start the coach after the player's own move has ended the game on the board, with the helps on: it judges that move too (D-88). Told again
29
- * until it starts, since a chat woken by the page may lose the line on its way to its words (Priya).
28
+ * Whether to start the coach once a move has ended the game on the board, with the helps on: it judges the player's last move, their own that ended it too
29
+ * (D-88). Told again until it starts, since a chat busy telling the end may lose the line on its way to its words (Priya).
30
30
  */
31
31
  export declare function coachOnEnd(context: Context, folder: string, current: CurrentGame): boolean;
32
32
  /**
33
- * How to start the coach on the player's move that ended the game: whatever the player asks, beside the analyst when the chat starts it too, since a chat on
34
- * a quick model started only the agent that answered the player (Priya).
33
+ * How to start the coach once a move ended the game, on the player's last move, theirs that ended it or the one before the AI's: whatever the player asks,
34
+ * beside the analyst when the chat starts it too, since a chat on a quick model started only the agent that answered the player (Priya).
35
35
  */
36
- export declare const coachOnEndLine: string;
36
+ export declare function coachOnEndLine({ turn, ai }: GameSummary): string;
37
37
  /**
38
38
  * How to start the agents, in one message, the one that thinks first, since it needs the time (Priya), and that the player sees nothing of
39
39
  * them: their end needs no answer either. The end of the last move's agents, told just after, must not take their place (Priya).
@@ -49,5 +49,7 @@ export declare function goneOver(context: Context, game: string): boolean;
49
49
  * agent with it, or it has written nothing for half an hour, as one long dead (D-109): a quiet minute proves nothing, since an analyst may think long.
50
50
  */
51
51
  export declare function analystOf(context: Context, game: string): string | undefined;
52
+ /** When the analyst that holds a game counts as gone if it writes nothing before: none while no analyst holds one. */
53
+ export declare function analystQuietAt(context: Context): Date | undefined;
52
54
  /** What stops an analyst agent on a game another analyst has taken: the chat hears of the review from that one (Priya). */
53
55
  export declare const anotherAnalyst: string;
@@ -66,20 +66,22 @@ export function agentRuns(context, agent) {
66
66
  return host !== undefined && agentHosts.includes(host) && hasAgent(context.folder, agent, host);
67
67
  }
68
68
  /**
69
- * Whether to start the coach after the player's own move has ended the game on the board, with the helps on: it judges that move too (D-88). Told again
70
- * until it starts, since a chat woken by the page may lose the line on its way to its words (Priya).
69
+ * Whether to start the coach once a move has ended the game on the board, with the helps on: it judges the player's last move, their own that ended it too
70
+ * (D-88). Told again until it starts, since a chat busy telling the end may lose the line on its way to its words (Priya).
71
71
  */
72
72
  export function coachOnEnd(context, folder, current) {
73
- const { game, ai } = current;
74
- return game.outcome.over && game.position.turn === ai && helpsIn(folder) && coachRuns(context) && !coachStarted(folder, current);
73
+ return current.game.outcome.over && helpsIn(folder) && coachRuns(context) && !coachStarted(folder, current);
75
74
  }
76
75
  /**
77
- * How to start the coach on the player's move that ended the game: whatever the player asks, beside the analyst when the chat starts it too, since a chat on
78
- * a quick model started only the agent that answered the player (Priya).
76
+ * How to start the coach once a move ended the game, on the player's last move, theirs that ended it or the one before the AI's: whatever the player asks,
77
+ * beside the analyst when the chat starts it too, since a chat on a quick model started only the agent that answered the player (Priya).
79
78
  */
80
- export const coachOnEndLine = `Coach the player: start the ${coachAgent} agent in the background with the prompt "Coach.", whatever the player asks: it judges `
81
- + "the move that ended the game, apart from the analyst; when you start the analyst too, start both in one message. Start it even when an agent of the move "
82
- + "before has just ended, and write nothing about it in the chat, now or when it ends.";
79
+ export function coachOnEndLine({ turn, ai }) {
80
+ const judged = turn === ai ? "the move that ended the game" : "the player's last move, the one before the end";
81
+ return `Coach the player: start the ${coachAgent} agent in the background with the prompt "Coach.", whatever the player asks: it judges ${judged}, apart `
82
+ + "from the analyst; when you start the analyst too, start both in one message. Start it even when an agent of the move before has just ended, and write "
83
+ + "nothing about it in the chat, now or when it ends.";
84
+ }
83
85
  /**
84
86
  * How to start the agents, in one message, the one that thinks first, since it needs the time (Priya), and that the player sees nothing of
85
87
  * them: their end needs no answer either. The end of the last move's agents, told just after, must not take their place (Priya).
@@ -123,6 +125,11 @@ export function analystOf(context, game) {
123
125
  const quiet = analysis !== undefined && context.now().getTime() - Date.parse(analysis.at) > analystQuiet;
124
126
  return taken?.game === game && !restarted && !quiet ? taken.run : undefined;
125
127
  }
128
+ /** When the analyst that holds a game counts as gone if it writes nothing before: none while no analyst holds one. */
129
+ export function analystQuietAt(context) {
130
+ const analysis = gameInAnalysis(gameFolder(context.home()));
131
+ return analysis?.analyst === undefined ? undefined : new Date(Date.parse(analysis.at) + analystQuiet);
132
+ }
126
133
  /** How long an analyst may write nothing before it counts as gone. */
127
134
  const analystQuiet = 30 * 60 * 1000;
128
135
  /** What stops an analyst agent on a game another analyst has taken: the chat hears of the review from that one (Priya). */
@@ -1,6 +1,7 @@
1
1
  // The clock in the commands (D-13): a flag that falls ends the game as soon as a command reads it, and the clock's line for the AI.
2
2
  import { canMate, clockText, Game, timeControlText, timeLeft } from "../../core/index.js";
3
3
  import { archiveEnded, endOf, loadGame, saveGame } from "../../games/index.js";
4
+ import { openingTags } from "./opening-tags.js";
4
5
  import { colourName } from "./view.js";
5
6
  /** The current game of a folder `now`, or undefined: a game whose side to move has run out of time is over, saved and archived first. */
6
7
  export function gameAt(folder, now) {
@@ -15,7 +16,7 @@ export function gameAt(folder, now) {
15
16
  const ended = outOfTime(current, turn, now);
16
17
  saveGame(folder, ended);
17
18
  // An archive that cannot be written leaves the game in game.json, where status shows it, until a new game archives it.
18
- archiveEnded(folder, ended);
19
+ archiveEnded(folder, ended, openingTags(ended.game.record));
19
20
  return ended;
20
21
  }
21
22
  /** The clock of a game `now`, as the AI reads it; none in a game without one. */
@@ -12,10 +12,10 @@ export const noteInput = z.string().trim().min(1).max(noteLimit).regex(/^[^[\]{}
12
12
  const drawing = z.string().overwrite((text) => text.replace(/[\s-]+/g, ""));
13
13
  /** The arrows the AI draws, as their input reads them. */
14
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()
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().overwrite(noneIfEmpty)
16
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
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()
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().overwrite(noneIfEmpty)
19
19
  .describe("squares to mark, as PGN's %csl writes them: Rf2; colours as for arrows");
20
20
  /** The arrows and squares drawn, those that read; none of either left out. */
21
21
  export function drawingsOf(arrows = [], squares = []) {
@@ -23,3 +23,7 @@ export function drawingsOf(arrows = [], squares = []) {
23
23
  const drawnSquares = squares.map(squareOf).filter((square) => square !== undefined);
24
24
  return { ...(drawnArrows.length === 0 ? {} : { arrows: drawnArrows }), ...(drawnSquares.length === 0 ? {} : { squares: drawnSquares }) };
25
25
  }
26
+ // An empty list draws nothing, as one left out: a host may send every parameter of the schema (Priya).
27
+ function noneIfEmpty(list) {
28
+ return list?.length === 0 ? undefined : list;
29
+ }
@@ -1,12 +1,13 @@
1
- export { agentRuns, agentsLine, analystLine, analystOf, analystOnView, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.ts";
1
+ export { agentRuns, agentsLine, analystLine, analystOf, analystOnView, analystQuietAt, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.ts";
2
2
  export { clockLine, clockSummaryOf, gameAt } from "./clock.ts";
3
3
  export { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.ts";
4
4
  export { aheadWords, leadFrom, leadInput } from "./lead-input.ts";
5
5
  export { closingLines, headLines, lastMoveText, moveLines, newGameStep, nextStep, positionLines, shownMove, shownMoves, sidesLine, wordLines } from "./lines.ts";
6
- export { bookOpening, bookPlies, bookReaches, knownMoves, openingLine, openingName } from "./opening-book.ts";
6
+ export { bookOpening, bookPlace, bookPlies, knownMoves, openingLine, openingName } from "./opening-book.ts";
7
+ export { openingTags } from "./opening-tags.ts";
7
8
  export { playMove, PositionChanged, positionIn, refuseOtherGame, refuseUnseenPosition, samePosition } from "./play-move.ts";
8
9
  export { give, newWords, say, unansweredWords, waitingWords, type Written } from "./say.ts";
9
10
  export { type Speech, speechLine, speechOf } from "./speech.ts";
10
11
  export { gameShown, gameToStudy, knownTag, moveIndex, moveNumbers, namedGame, noGameGoingOn, positionAfter, positionLine, shownPosition, type Studied, studyInput } from "./study.ts";
11
- export { gameGoingOn, type GameSummary, lastMoves, type MovePlayed, summaryOf, withThought } from "./summary.ts";
12
+ export { endText, gameGoingOn, type GameSummary, lastMoves, type MovePlayed, summaryOf, withThought } from "./summary.ts";
12
13
  export { balanceText, boardView, colourName, compactView, fullView, materialText, riskLine, tacticsText } from "./view.ts";
@@ -1,14 +1,15 @@
1
1
  // What the commands share to answer the AI: the lines it reads, the game's summary and view, the clock, the talk, the moves played, the games to study.
2
2
  // Above the registry only, below every other command folder.
3
- export { agentRuns, agentsLine, analystLine, analystOf, analystOnView, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.js";
3
+ export { agentRuns, agentsLine, analystLine, analystOf, analystOnView, analystQuietAt, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.js";
4
4
  export { clockLine, clockSummaryOf, gameAt } from "./clock.js";
5
5
  export { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.js";
6
6
  export { aheadWords, leadFrom, leadInput } from "./lead-input.js";
7
7
  export { closingLines, headLines, lastMoveText, moveLines, newGameStep, nextStep, positionLines, shownMove, shownMoves, sidesLine, wordLines } from "./lines.js";
8
- export { bookOpening, bookPlies, bookReaches, knownMoves, openingLine, openingName } from "./opening-book.js";
8
+ export { bookOpening, bookPlace, bookPlies, knownMoves, openingLine, openingName } from "./opening-book.js";
9
+ export { openingTags } from "./opening-tags.js";
9
10
  export { playMove, PositionChanged, positionIn, refuseOtherGame, refuseUnseenPosition, samePosition } from "./play-move.js";
10
11
  export { give, newWords, say, unansweredWords, waitingWords } from "./say.js";
11
12
  export { speechLine, speechOf } from "./speech.js";
12
13
  export { gameShown, gameToStudy, knownTag, moveIndex, moveNumbers, namedGame, noGameGoingOn, positionAfter, positionLine, shownPosition, studyInput } from "./study.js";
13
- export { gameGoingOn, lastMoves, summaryOf, withThought } from "./summary.js";
14
+ export { endText, gameGoingOn, lastMoves, summaryOf, withThought } from "./summary.js";
14
15
  export { balanceText, boardView, colourName, compactView, fullView, materialText, riskLine, tacticsText } from "./view.js";
@@ -17,7 +17,7 @@ export declare function shownMoves(moves: string): string;
17
17
  /** The lines after a move, which the line before named: its note, where the game stands, a warning. closingLines end them. */
18
18
  export declare function moveLines({ note, game, warning }: MovePlayed): string[];
19
19
  /** The last lines of a tool's answer: the player's words from the board page, then the next step, which stays the last line. */
20
- export declare function closingLines(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean): string[];
20
+ export declare function closingLines(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean, doorbell?: boolean): string[];
21
21
  /**
22
22
  * The player's words from the board page, one line each, in analysis with the move the page showed (D-101): lines they broke are joined, so no line of
23
23
  * theirs reads as one of Chesspal's.
@@ -25,9 +25,11 @@ export declare function closingLines(game: GameSummary | undefined, words?: read
25
25
  export declare function wordLines(words: readonly Written[]): string[];
26
26
  /**
27
27
  * What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
28
- * board page open in no browser, a new game starts there, never in the chat.
28
+ * board page open in no browser, a new game starts there, never in the chat. In a host with no `doorbell`, the player tells the chat what they did on the page.
29
29
  */
30
- export declare function nextStep(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean): string;
30
+ export declare function nextStep(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean, doorbell?: boolean): string;
31
+ /** What the AI does with the player's move in a host with no doorbell: one played on the board page they tell it of, one written in the chat it plays. */
32
+ export declare const toldMove = "when they tell you they moved on the board page, call status; when they write it here, call player_move with it.";
31
33
  /** How a new game starts: the player chooses it in the board page's window, which new_game without choices opens (D-85). */
32
34
  export declare const newGameStep = "call new_game without choices: it opens the board page's New game window, where the player chooses the colour, the clock and the level.";
33
35
  /** Who plays what, from the AI's side. */
@@ -72,8 +72,8 @@ export function moveLines({ note, game, warning }) {
72
72
  return [...(note === undefined ? [] : [`Note: ${note}`]), ...positionLines(game, false), ...(warning === undefined ? [] : [warning])];
73
73
  }
74
74
  /** The last lines of a tool's answer: the player's words from the board page, then the next step, which stays the last line. */
75
- export function closingLines(game, words = [], pageClosed = false) {
76
- return [...wordLines(words), nextStep(game, words, pageClosed)];
75
+ export function closingLines(game, words = [], pageClosed = false, doorbell = true) {
76
+ return [...wordLines(words), nextStep(game, words, pageClosed, doorbell)];
77
77
  }
78
78
  /**
79
79
  * The player's words from the board page, one line each, in analysis with the move the page showed (D-101): lines they broke are joined, so no line of
@@ -84,9 +84,9 @@ export function wordLines(words) {
84
84
  }
85
85
  /**
86
86
  * What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
87
- * board page open in no browser, a new game starts there, never in the chat.
87
+ * board page open in no browser, a new game starts there, never in the chat. In a host with no `doorbell`, the player tells the chat what they did on the page.
88
88
  */
89
- export function nextStep(game, words = [], pageClosed = false) {
89
+ export function nextStep(game, words = [], pageClosed = false, doorbell = true) {
90
90
  const step = pageClosed ? closedPageGameStep : newGameStep;
91
91
  // Words written on a game in analysis: the chat answers them there, and corrects the review where they show it wrong (D-101).
92
92
  if (words.some((word) => word.place !== undefined) && (game === undefined || game.end !== undefined)) {
@@ -114,7 +114,9 @@ export function nextStep(game, words = [], pageClosed = false) {
114
114
  : `Next: answer the player with ai_say.${analysis} When they want another game, ${step}`;
115
115
  }
116
116
  if (game.turn === game.ai && game.clock?.waiting === true) {
117
- return "Next: don't move yet: the player starts the game with Start on the board page, and the doorbell wakes you then. End your turn.";
117
+ return doorbell
118
+ ? "Next: don't move yet: the player starts the game with Start on the board page, and the doorbell wakes you then. End your turn."
119
+ : "Next: don't move yet: the player starts the game with Start on the board page. End your turn, and call status when they tell you they did.";
118
120
  }
119
121
  if (game.turn === game.ai) {
120
122
  return words.length === 0
@@ -124,8 +126,13 @@ export function nextStep(game, words = [], pageClosed = false) {
124
126
  if (game.drawOffer === "player") {
125
127
  return "Next: accept the draw with end_game draw_agreed, or answer with ai_say and wait for the player's move.";
126
128
  }
127
- return words.length === 0 ? "Next: wait for the player's move, then call player_move with it." : "Next: answer the player with ai_say, then wait for their move.";
129
+ if (words.length > 0) {
130
+ return "Next: answer the player with ai_say, then wait for their move.";
131
+ }
132
+ return doorbell ? "Next: wait for the player's move, then call player_move with it." : `Next: wait for the player's move: ${toldMove}`;
128
133
  }
134
+ /** What the AI does with the player's move in a host with no doorbell: one played on the board page they tell it of, one written in the chat it plays. */
135
+ export const toldMove = "when they tell you they moved on the board page, call status; when they write it here, call player_move with it.";
129
136
  /** How a new game starts: the player chooses it in the board page's window, which new_game without choices opens (D-85). */
130
137
  export const newGameStep = "call new_game without choices: it opens the board page's New game window, where the player chooses the colour, the clock and the level.";
131
138
  // With the page open in no browser the window cannot open: the player opens the page and presses New game.
@@ -165,7 +172,7 @@ function whereLine(game) {
165
172
  if (game.end !== undefined) {
166
173
  // Saved where the player's games are: the AI says so, never the file, which past_games names when it is needed.
167
174
  const archived = game.end.archive === undefined ? "" : " Saved with the player's games.";
168
- return `Game over: ${endText(game.end)}.${archived}`;
175
+ return `Game over: ${endText(game.end, game.ai)}.${archived}`;
169
176
  }
170
177
  return `${game.turn === game.ai ? "You" : "Player"} (${colourName(game.turn)}) to move, move ${game.moveNumber}.`;
171
178
  }
@@ -1,13 +1,19 @@
1
- import { type GameRecord, type Opening } from "../../core/index.ts";
2
- /** The game's opening as the AI reads it: ECO and name, and the move it left the book at; none outside the book, or where the build made no index. */
3
- export declare function openingLine(record: GameRecord): string | undefined;
1
+ import { type BookPlace, Game, type GameRecord, type Opening } from "../../core/index.ts";
2
+ /**
3
+ * The game's opening as the AI reads it: ECO and name, and the move it left the book at (D-68); none outside the book, or where the build made no index.
4
+ * A live game gives the positions it went through; a record alone is replayed.
5
+ */
6
+ export declare function openingLine(game: Game | GameRecord): string | undefined;
4
7
  /** The moves the book knows from a position (D-15), for the AI at the master level; none out of the book, or where the build made no book. */
5
8
  export declare function knownMoves(fen: string): readonly string[];
6
- /** How many plies from the start the book knows, to the last before the game left it (D-69); none from another position. */
7
- export declare function bookPlies(record: GameRecord): number;
8
- /** Whether a line of Lichess's openings reaches a position, by any order of moves (D-68); never where the build made no book. */
9
- export declare function bookReaches(fen: string): boolean;
9
+ /** How many plies from the start the book knows, to the last before the game left it (D-68, D-69); none from another position. */
10
+ export declare function bookPlies(game: Game | GameRecord): number;
10
11
  /** The opening a game reached, ECO and name, without where it left the book: for the games in the archive. */
11
12
  export declare function openingName(record: GameRecord): string | undefined;
12
13
  /** The last named opening a game reached, and at which ply; none outside the book, or where the build made no index. */
13
14
  export declare function bookOpening(record: GameRecord): Opening | undefined;
15
+ /**
16
+ * Where a game stands in the book, its opening and its plies the book knows, once per record: from the positions a live game went through, or a record
17
+ * replayed; none from another position, or where the build made no index.
18
+ */
19
+ export declare function bookPlace(game: Game | GameRecord): BookPlace | undefined;