chesspal 0.3.5 → 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 (191) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/cli.js +17 -4
  3. package/dist/cli/command-line.js +22 -12
  4. package/dist/cli/icon.d.ts +2 -0
  5. package/dist/cli/icon.js +11 -1
  6. package/dist/cli/prompt-hook.d.ts +17 -0
  7. package/dist/cli/prompt-hook.js +100 -0
  8. package/dist/commands/analysis/analyse.d.ts +2 -2
  9. package/dist/commands/analysis/analyse.js +10 -16
  10. package/dist/commands/analysis/annotate.d.ts +8 -0
  11. package/dist/commands/analysis/annotate.js +19 -3
  12. package/dist/commands/answers/agents.d.ts +17 -7
  13. package/dist/commands/answers/agents.js +46 -16
  14. package/dist/commands/answers/clock.js +2 -1
  15. package/dist/commands/answers/drawings.js +6 -2
  16. package/dist/commands/answers/index.d.ts +4 -3
  17. package/dist/commands/answers/index.js +4 -3
  18. package/dist/commands/answers/lines.d.ts +5 -3
  19. package/dist/commands/answers/lines.js +17 -9
  20. package/dist/commands/answers/opening-book.d.ts +13 -7
  21. package/dist/commands/answers/opening-book.js +34 -38
  22. package/dist/commands/answers/opening-tags.d.ts +4 -0
  23. package/dist/commands/answers/opening-tags.js +11 -0
  24. package/dist/commands/answers/play-move.d.ts +3 -1
  25. package/dist/commands/answers/play-move.js +9 -6
  26. package/dist/commands/answers/say.js +2 -1
  27. package/dist/commands/answers/summary.d.ts +7 -3
  28. package/dist/commands/answers/summary.js +16 -5
  29. package/dist/commands/answers/view.d.ts +1 -1
  30. package/dist/commands/answers/view.js +2 -2
  31. package/dist/commands/game/ai-move.js +4 -4
  32. package/dist/commands/game/coach.js +7 -6
  33. package/dist/commands/game/end.js +2 -2
  34. package/dist/commands/game/export.d.ts +1 -1
  35. package/dist/commands/game/index.d.ts +1 -1
  36. package/dist/commands/game/index.js +1 -1
  37. package/dist/commands/game/look.d.ts +7 -3
  38. package/dist/commands/game/look.js +9 -4
  39. package/dist/commands/game/new.js +42 -12
  40. package/dist/commands/game/player-move.d.ts +16 -0
  41. package/dist/commands/game/player-move.js +69 -19
  42. package/dist/commands/game/ponder.js +7 -4
  43. package/dist/commands/game/position.d.ts +2 -1
  44. package/dist/commands/game/position.js +9 -4
  45. package/dist/commands/game/status.d.ts +4 -2
  46. package/dist/commands/game/status.js +24 -14
  47. package/dist/commands/index.d.ts +3 -3
  48. package/dist/commands/index.js +3 -3
  49. package/dist/commands/profiles/past-games.d.ts +7 -2
  50. package/dist/commands/profiles/past-games.js +45 -18
  51. package/dist/commands/registry/context.d.ts +2 -0
  52. package/dist/commands/registry/context.js +5 -0
  53. package/dist/commands/registry/index.d.ts +1 -1
  54. package/dist/commands/registry/index.js +1 -1
  55. package/dist/commands/talk/ai-say.d.ts +1 -1
  56. package/dist/commands/talk/ai-say.js +7 -6
  57. package/dist/core/game.d.ts +13 -0
  58. package/dist/core/game.js +29 -3
  59. package/dist/core/index.d.ts +4 -4
  60. package/dist/core/index.js +3 -3
  61. package/dist/core/openings.d.ts +20 -6
  62. package/dist/core/openings.js +45 -22
  63. package/dist/core/pgn.d.ts +4 -0
  64. package/dist/core/pgn.js +35 -6
  65. package/dist/games/analysis.d.ts +7 -4
  66. package/dist/games/analysis.js +5 -3
  67. package/dist/games/archive.d.ts +25 -5
  68. package/dist/games/archive.js +52 -19
  69. package/dist/games/chat-start.d.ts +2 -2
  70. package/dist/games/chat-start.js +6 -1
  71. package/dist/games/current-game.d.ts +2 -2
  72. package/dist/games/current-game.js +3 -2
  73. package/dist/games/index.d.ts +3 -2
  74. package/dist/games/index.js +3 -2
  75. package/dist/games/position-shown.d.ts +24 -0
  76. package/dist/games/position-shown.js +51 -0
  77. package/dist/games/thoughts.d.ts +12 -4
  78. package/dist/games/thoughts.js +27 -10
  79. package/dist/home/index.d.ts +1 -0
  80. package/dist/home/index.js +1 -0
  81. package/dist/home/same-path.d.ts +7 -0
  82. package/dist/home/same-path.js +24 -0
  83. package/dist/host-files/host-files.d.ts +5 -0
  84. package/dist/host-files/host-files.js +21 -7
  85. package/dist/host-files/index.d.ts +1 -1
  86. package/dist/host-files/index.js +1 -1
  87. package/dist/page/app/anchor.js +15 -11
  88. package/dist/page/app/app.js +49 -14
  89. package/dist/page/app/served.js +30 -38
  90. package/dist/page/app/watch.js +97 -0
  91. package/dist/page/avatars/torretta.css +108 -0
  92. package/dist/page/base.css +1 -1
  93. package/dist/page/board/board.css +11 -8
  94. package/dist/page/board/marks.js +2 -2
  95. package/dist/page/board/pointer.js +2 -2
  96. package/dist/page/dialogs/analyses.js +5 -3
  97. package/dist/page/dialogs/dialogs.css +3 -1
  98. package/dist/page/dialogs/rows.js +72 -0
  99. package/dist/page/dialogs/site-games.js +3 -1
  100. package/dist/page/game/column.js +10 -8
  101. package/dist/page/game/game.css +137 -16
  102. package/dist/page/game/move-list.js +7 -5
  103. package/dist/page/game/plies.js +4 -0
  104. package/dist/page/game/review-progress.js +11 -0
  105. package/dist/page/guide/drawings/drawings.css +0 -1
  106. package/dist/page/guide/en.md +2 -2
  107. package/dist/page/guide/it.md +2 -2
  108. package/dist/page/helps/helps.css +5 -5
  109. package/dist/page/helps/helps.js +3 -181
  110. package/dist/page/helps/rows.js +16 -0
  111. package/dist/page/helps/strategy.js +105 -0
  112. package/dist/page/helps/tactics.js +69 -0
  113. package/dist/page/layout/header.js +49 -6
  114. package/dist/page/layout/icon.ico +0 -0
  115. package/dist/page/layout/layout.css +49 -17
  116. package/dist/page/layout/window.js +45 -6
  117. package/dist/page/lead/lead.js +41 -4
  118. package/dist/page/legend/legend.css +4 -16
  119. package/dist/page/legend/legend.js +28 -15
  120. package/dist/page/profile/settings.js +1 -0
  121. package/dist/page/sounds/sounds.js +1 -1
  122. package/dist/page/style.css +1 -0
  123. package/dist/page/table/action.js +42 -0
  124. package/dist/page/table/table.css +57 -39
  125. package/dist/page/table/window.js +67 -67
  126. package/dist/page/talk/talk.css +52 -118
  127. package/dist/page/talk/talk.js +65 -20
  128. package/dist/page/talk/voice.js +92 -21
  129. package/dist/page/text.js +34 -8
  130. package/dist/page/theme.css +13 -0
  131. package/dist/profiles/profiles.js +5 -1
  132. package/dist/server/bell/bell.d.ts +24 -29
  133. package/dist/server/bell/bell.js +79 -133
  134. package/dist/server/bell/index.d.ts +1 -1
  135. package/dist/server/bell/index.js +2 -2
  136. package/dist/server/mcp/mcp.js +49 -7
  137. package/dist/server/page/analyses.js +15 -4
  138. package/dist/server/page/chat.d.ts +5 -1
  139. package/dist/server/page/chat.js +5 -1
  140. package/dist/server/page/files.js +3 -1
  141. package/dist/server/page/game.d.ts +5 -0
  142. package/dist/server/page/game.js +95 -15
  143. package/dist/server/page/http.d.ts +1 -1
  144. package/dist/server/page/page.d.ts +14 -9
  145. package/dist/server/page/page.js +111 -26
  146. package/dist/server/page/table.d.ts +40 -5
  147. package/dist/server/page/table.js +65 -25
  148. package/dist/server/page/update.d.ts +1 -1
  149. package/dist/server/page/update.js +4 -4
  150. package/dist/server/page/windows.d.ts +22 -0
  151. package/dist/server/page/windows.js +45 -0
  152. package/dist/server/ports/index.d.ts +1 -0
  153. package/dist/server/ports/index.js +2 -0
  154. package/dist/server/ports/ports.d.ts +78 -0
  155. package/dist/server/ports/ports.js +222 -0
  156. package/dist/server/table/browser.d.ts +22 -6
  157. package/dist/server/table/browser.js +98 -20
  158. package/dist/server/table/hosts/chats.d.ts +5 -0
  159. package/dist/server/table/hosts/chats.js +11 -4
  160. package/dist/server/table/hosts/hooks.d.ts +6 -0
  161. package/dist/server/table/hosts/hooks.js +38 -0
  162. package/dist/server/table/hosts/hosts.d.ts +13 -5
  163. package/dist/server/table/hosts/hosts.js +16 -10
  164. package/dist/server/table/hosts/index.d.ts +1 -0
  165. package/dist/server/table/hosts/index.js +1 -0
  166. package/dist/server/table/hosts/play.d.ts +7 -3
  167. package/dist/server/table/hosts/play.js +63 -9
  168. package/dist/server/table/icon/index.d.ts +2 -0
  169. package/dist/server/table/icon/index.js +16 -2
  170. package/dist/server/table/index.d.ts +2 -2
  171. package/dist/server/table/index.js +1 -1
  172. package/dist/server/table/launch.d.ts +5 -4
  173. package/dist/server/table/launch.js +18 -5
  174. package/dist/server/table/link.js +4 -1
  175. package/dist/server/table/table.d.ts +7 -0
  176. package/dist/server/table/table.js +40 -12
  177. package/dist/sites/index.d.ts +1 -1
  178. package/dist/sites/index.js +1 -1
  179. package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
  180. package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
  181. package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
  182. package/dist/sites/{choice.js → ratings/choice.js} +1 -1
  183. package/dist/sites/ratings/index.d.ts +1 -0
  184. package/dist/sites/ratings/index.js +2 -0
  185. package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
  186. package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
  187. package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
  188. package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
  189. package/instructions/opponent.md +1 -1
  190. package/instructions/start.md +1 -1
  191. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { writePgn } from "../../core/index.js";
3
- import { archivedGame, archivedGames, archivedWhen } from "../../games/index.js";
3
+ import { archivedGame, archivedGames, archivedTags, archivedWhen, hasListTags } from "../../games/index.js";
4
4
  import { gameFolder } from "../../profiles/index.js";
5
5
  import { openingName } from "../answers/index.js";
6
6
  import { defineCommand } from "../registry/index.js";
@@ -8,28 +8,43 @@ import { defineCommand } from "../registry/index.js";
8
8
  export const pastGames = defineCommand({
9
9
  name: "past_games",
10
10
  description: "Lists the games played with this player, the latest first: when, your colour, the result, the opening, how long, and its name in the "
11
- + "archive. With game, that game's PGN with your comments and plans; to comment a game with the player, analyse_game. new_game already gives the "
12
- + "last three. Example: past_games {}.",
13
- input: z.object({ game: z.string().optional().describe("a game's name, as the list writes it: its PGN") }),
11
+ + "archive. With search, only the games whose line holds every word of it; the colour and the result are yours, the AI's. With game, that game's "
12
+ + "PGN with your comments and plans; to comment a game with the player, analyse_game. new_game already gives the last three. Example, the "
13
+ + 'Sicilians the player lost: past_games {search: "Sicilian won"}.',
14
+ input: z.object({
15
+ search: z.string().optional().describe("words the game's line must all hold: a date, your colour, won, lost, drew or unfinished as you did, an opening"),
16
+ game: z.string().optional().describe("a game's name, as the list writes it: its PGN"),
17
+ }),
14
18
  readOnly: true,
15
19
  idempotent: true,
16
- run: ({ game }, context) => {
20
+ run: ({ search, game }, context) => {
17
21
  const folder = gameFolder(context.home());
18
- return game === undefined ? { games: pastGameLines(folder, listed) } : { pgn: writePgn(archivedGame(folder, game)) };
22
+ return game === undefined ? { games: pastGameLines(folder, listed, search), searched: search?.trim() ? true : undefined } : { pgn: writePgn(archivedGame(folder, game)) };
23
+ },
24
+ format: (past) => {
25
+ if ("pgn" in past) {
26
+ return past.pgn;
27
+ }
28
+ return past.games.length > 0 ? past.games.join("\n") : past.searched ? "No game played holds all those words." : "No game played yet.";
19
29
  },
20
- format: (past) => ("pgn" in past ? past.pgn : past.games.length === 0 ? "No game played yet." : past.games.join("\n")),
21
30
  });
22
- /** The latest games of the archive, one line each as the AI reads them: none when there is no archive. A game closed before any move is no game played. */
23
- export function pastGameLines(folder, count) {
31
+ /**
32
+ * The latest games of the archive, one line each as the AI reads them, those holding every word searched: none when there is no archive. A game closed
33
+ * before any move is no game played. A game is read from its tags alone when the archive wrote the opening's and the plies', or else whole.
34
+ */
35
+ export function pastGameLines(folder, count, search = "") {
36
+ const words = search.toLowerCase().split(/\s+/).filter((word) => word !== "");
24
37
  const lines = [];
25
38
  for (const name of archivedGames(folder)) {
26
39
  if (lines.length === count) {
27
40
  break;
28
41
  }
29
42
  try {
30
- const record = archivedGame(folder, name);
31
- if (record.moves.length > 0) {
32
- lines.push(pastGameLine(name, record));
43
+ const line = pastGameLine(folder, name);
44
+ // The ending names the player's colour too, as "(Black resigns)": a colour searched is the AI's alone.
45
+ const searched = line?.replace(/ \((?:White|Black) [^)]*\)/, "").toLowerCase();
46
+ if (line !== undefined && words.every((word) => searched.includes(word))) {
47
+ lines.push(line);
33
48
  }
34
49
  }
35
50
  catch {
@@ -39,14 +54,26 @@ export function pastGameLines(folder, count) {
39
54
  return lines;
40
55
  }
41
56
  const listed = 20;
57
+ /** A game's line, from its tags when they say all of it, or else from the whole game: none for a game closed before any move. */
58
+ function pastGameLine(folder, name) {
59
+ const tags = archivedTags(folder, name);
60
+ const { ECO: eco, Opening: opening, PlyCount: plies } = tags;
61
+ if (hasListTags(tags)) {
62
+ return lineOf(name, tags, Number(plies), eco === undefined || opening === undefined ? undefined : `${eco} ${opening}`);
63
+ }
64
+ const record = archivedGame(folder, name);
65
+ return lineOf(name, record.tags, record.moves.length, openingName(record));
66
+ }
42
67
  /** 2026-11-03 12:04, you (AI) played Black and won 0-1 (White resigns), B50 Sicilian Defense, 15 moves. games/2026-11-03-120423.pgn */
43
- function pastGameLine(name, record) {
44
- const ai = record.tags.Black === "AI" ? "black" : "white";
45
- const result = record.tags.Result ?? "*";
68
+ function lineOf(name, tags, plies, opening) {
69
+ if (plies === 0) {
70
+ return undefined;
71
+ }
72
+ const ai = tags.Black === "AI" ? "black" : "white";
73
+ const result = tags.Result ?? "*";
46
74
  const outcome = result === "*" ? ", unfinished" : ` and ${resultFor(result, ai)} ${result}`;
47
- const termination = record.tags.Termination === undefined || result === "*" ? "" : ` (${record.tags.Termination})`;
48
- const opening = openingName(record);
49
- const moves = Math.ceil(record.moves.length / 2);
75
+ const termination = tags.Termination === undefined || result === "*" ? "" : ` (${tags.Termination})`;
76
+ const moves = Math.ceil(plies / 2);
50
77
  return `${archivedWhen(name)}, you (AI) played ${ai === "white" ? "White" : "Black"}${outcome}${termination}${opening === undefined ? "" : `, ${opening}`}, ${moves} moves. ${name}`;
51
78
  }
52
79
  function resultFor(result, ai) {
@@ -74,6 +74,8 @@ export interface SeenPosition {
74
74
  }
75
75
  /** The context of a process started in the player's folder: the data folder is found at the first command that needs it, once. */
76
76
  export declare function defaultContext(): Context;
77
+ /** The data folder for one tool call: looked for, its profile made when missing, at its first use in the call alone; the next call looks again. */
78
+ export declare function homeForOneCall(context: Context): () => string;
77
79
  /** Whether the player's language is Italian: the command line speaks Italian to them then, English otherwise (D-32). */
78
80
  export declare function speaksItalian(language: string | undefined): boolean;
79
81
  /** A data folder with a profile in it, the first made when there is none, named after the system's account (D-71). */
@@ -18,6 +18,11 @@ export function defaultContext() {
18
18
  fetch: globalThis.fetch,
19
19
  };
20
20
  }
21
+ /** The data folder for one tool call: looked for, its profile made when missing, at its first use in the call alone; the next call looks again. */
22
+ export function homeForOneCall(context) {
23
+ let found;
24
+ return () => (found ??= context.home());
25
+ }
21
26
  /** Whether the player's language is Italian: the command line speaks Italian to them then, English otherwise (D-32). */
22
27
  export function speaksItalian(language) {
23
28
  return language?.slice(0, 2).toLowerCase() === "it";
@@ -1,2 +1,2 @@
1
- export { type Caller, type Context, defaultContext, type SeenPosition, speaksItalian } from "./context.ts";
1
+ export { type Caller, type Context, defaultContext, homeForOneCall, type SeenPosition, speaksItalian } from "./context.ts";
2
2
  export { type AnyCommand, baseType, type Command, commandLineFieldProblem, defineCommand, inputProblems, isBoolean, isList, sentence, takesNumbers } from "./registry.ts";
@@ -1,3 +1,3 @@
1
1
  // What a command is and what it runs with: the definition and the reading of its input, and the context of folders, time and randomness.
2
- export { defaultContext, speaksItalian } from "./context.js";
2
+ export { defaultContext, homeForOneCall, speaksItalian } from "./context.js";
3
3
  export { baseType, commandLineFieldProblem, defineCommand, inputProblems, isBoolean, isList, sentence, takesNumbers } from "./registry.js";
@@ -4,7 +4,7 @@ interface Said {
4
4
  /** The game, none when the AI spoke before the first (D-77). */
5
5
  game?: GameSummary;
6
6
  words: Written[];
7
- /** The coach yet to start on the player's move that ended the game (D-88). */
7
+ /** The coach yet to start once a move ended the game (D-88). */
8
8
  coach?: true;
9
9
  }
10
10
  /** The AI's words on the board page without a move, read only there, on the player's time: an answer, or a word it has to say (D-54, D-72). */
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { moods } from "../../core/index.js";
3
3
  import { archivedName, talkLimits } from "../../games/index.js";
4
+ import { hasDoorbell } from "../../host-files/index.js";
4
5
  import { gameFolder } from "../../profiles/index.js";
5
6
  import { analystLine, coachOnEnd, coachOnEndLine, give, newGameStep, nextStep, refuseOtherGame, reviewedLine, say, summaryOf, waitingWords, withAnalyst, wordLines } from "../answers/index.js";
6
7
  import { defineCommand } from "../registry/index.js";
@@ -28,26 +29,26 @@ export const aiSay = defineCommand({
28
29
  // The chat woken by the player's ending move on the page may have gone to its words without the coach (Priya).
29
30
  return { game: withAnalyst(summaryOf(current, context.now(), archivedName(folder, current)), context, current), words, ...(coachOnEnd(context, folder, current) ? { coach: true } : {}) };
30
31
  },
31
- format: ({ game, words, coach }) => [
32
+ format: ({ game, words, coach }, context) => [
32
33
  "Said on the board page, where the player reads it: don't write it again in the chat.",
33
34
  ...wordLines(words),
34
- ...(coach === true ? [coachOnEndLine] : []),
35
- afterWords(game, words),
35
+ ...(coach === true && game !== undefined ? [coachOnEndLine(game)] : []),
36
+ afterWords(game, words, hasDoorbell(context?.host?.())),
36
37
  ].join("\n"),
37
38
  });
38
39
  /** The next step once the AI has spoken: the end of a game over has been told already, and the player's draw offer answered in words. */
39
- function afterWords(game, words) {
40
+ function afterWords(game, words, doorbell) {
40
41
  if (words.length === 0 && game?.end !== undefined) {
41
42
  // The offer to go over the game, made in the AI's words, waits for the player's yes (D-89).
42
43
  if (game.end.analyst === "done") {
43
44
  return `Next: ${reviewedLine(game.end.archive)} When they want another game, ${newGameStep}`;
44
45
  }
45
46
  return game.end.analyst === "offer"
46
- ? `Next: when the player wants this game gone over, ${analystLine(game.end.archive)} When they want another game, ${newGameStep}`
47
+ ? `Next: when the player wants this game gone over, ${analystLine(game.end.archive, game.end.codex === true)} When they want another game, ${newGameStep}`
47
48
  : `Next: when the player wants another game, ${newGameStep}`;
48
49
  }
49
50
  if (words.length === 0 && game?.drawOffer === "player" && game.turn !== game.ai) {
50
51
  return "Next: accept the draw with end_game draw_agreed, or wait for the player's move.";
51
52
  }
52
- return nextStep(game, words);
53
+ return nextStep(game, words, false, doorbell);
53
54
  }
@@ -58,6 +58,7 @@ export declare class Game {
58
58
  private readonly moves;
59
59
  private last;
60
60
  private readonly keys;
61
+ private readonly positions;
61
62
  private readonly castled;
62
63
  private keptPosition;
63
64
  private keptOutcome;
@@ -70,6 +71,8 @@ export declare class Game {
70
71
  get outcome(): Outcome;
71
72
  /** The game as plain data, frozen; when the board ends the game, its Result tag is the board's, unless the record gave one. */
72
73
  get record(): GameRecord;
74
+ /** The key of every position the game has stood in, its start first, as the openings' book keys them (D-68): no replay to look the book up. */
75
+ get positionKeys(): readonly string[];
73
76
  /** The last move played, with its squares; none before the first. */
74
77
  get lastMove(): Move | undefined;
75
78
  /** What the position shows (D-26): attackers and defenders, pieces at risk, pins, checks, the moves of each piece; computed once for each position. */
@@ -98,3 +101,13 @@ export declare class Game {
98
101
  }
99
102
  /** A text with SAN's letters and signs for the figurines and signs others write: ♘f3 as Nf3, × as x; a pawn's figurine says nothing. */
100
103
  export declare function withSanLetters(text: string): string;
104
+ /** The moves a text writes with a piece's letter of another language, each with its SAN: Cxe5 as Nxe5. */
105
+ export declare function foreignMovesIn(text: string): {
106
+ written: string;
107
+ san: string;
108
+ }[];
109
+ /**
110
+ * A player's move written with a piece's letter of another language that names one piece only, in SAN's (D-113): Cf3 as Nf3, Tf1 as Rf1, e8=D as e8=Q;
111
+ * a small t too, which is no file. Any other text as it is: B stays the bishop, K the king, R the rook, a small letter a file.
112
+ */
113
+ export declare function withSanPieces(move: string): string;
package/dist/core/game.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { Chess, validateFen } from "chess.js";
3
3
  import { factsOf } from "./facts.js";
4
4
  import { whyIllegal } from "./illegal.js";
5
+ import { positionKey } from "./openings.js";
5
6
  import { possibilitiesOf, threatsOf } from "./possibilities.js";
6
7
  import { profileOf } from "./profile.js";
7
8
  import { cleanComment, recordedMove } from "./record.js";
@@ -17,6 +18,8 @@ export class Game {
17
18
  last;
18
19
  // The key of every position the game has stood in, the first included, and how each side castled: for the profile's repetitions and castling.
19
20
  keys;
21
+ // The same positions keyed as the openings' book keys them, its FEN without the move counters.
22
+ positions;
20
23
  castled = {};
21
24
  keptPosition;
22
25
  keptOutcome;
@@ -26,6 +29,7 @@ export class Game {
26
29
  this.chess = startingBoard(record.tags.FEN);
27
30
  // The first key without an en passant square no pawn can use, which the FEN may give: the position then repeats as the rules count it.
28
31
  this.keys = [new Chess(this.chess.fen()).hash()];
32
+ this.positions = [positionKey(this.chess.fen())];
29
33
  this.tags = { ...record.tags };
30
34
  this.comment = cleanComment(record.comment ?? "") || undefined;
31
35
  for (const { san, nags, comment } of record.moves) {
@@ -58,6 +62,10 @@ export class Game {
58
62
  this.keptRecord ??= this.snapshot();
59
63
  return this.keptRecord;
60
64
  }
65
+ /** The key of every position the game has stood in, its start first, as the openings' book keys them (D-68): no replay to look the book up. */
66
+ get positionKeys() {
67
+ return this.positions;
68
+ }
61
69
  /** The last move played, with its squares; none before the first. */
62
70
  get lastMove() {
63
71
  return this.last;
@@ -129,11 +137,10 @@ export class Game {
129
137
  if ("refusal" in move) {
130
138
  return move;
131
139
  }
132
- if (exact === undefined) {
133
- this.chess.move({ from: move.from, to: move.to, promotion: move.promotion });
134
- }
140
+ const after = exact === undefined ? this.chess.move({ from: move.from, to: move.to, promotion: move.promotion }).after : exact.after;
135
141
  this.last = move;
136
142
  this.keys.push(this.chess.hash());
143
+ this.positions.push(positionKey(after));
137
144
  if (move.san.startsWith("O-O")) {
138
145
  this.castled[mover] = move.san.startsWith("O-O-O") ? "O-O-O" : "O-O";
139
146
  }
@@ -223,6 +230,20 @@ const asciiOf = {
223
230
  export function withSanLetters(text) {
224
231
  return text.replace(/[︎️]/g, "").replace(/[–—−×♔-♟]/g, (sign) => asciiOf[sign] ?? sign);
225
232
  }
233
+ /** The moves a text writes with a piece's letter of another language, each with its SAN: Cxe5 as Nxe5. */
234
+ export function foreignMovesIn(text) {
235
+ return [...text.matchAll(foreignMove)]
236
+ .map(([written]) => ({ written, san: `${foreignPieces[written[0]][1]}${written.slice(1)}` }));
237
+ }
238
+ /**
239
+ * A player's move written with a piece's letter of another language that names one piece only, in SAN's (D-113): Cf3 as Nf3, Tf1 as Rf1, e8=D as e8=Q;
240
+ * a small t too, which is no file. Any other text as it is: B stays the bishop, K the king, R the rook, a small letter a file.
241
+ */
242
+ export function withSanPieces(move) {
243
+ return move
244
+ .replace(foreignPieceMove, (_, before, letter) => `${before}${foreignPieces[letter.toUpperCase()][1]}`)
245
+ .replace(foreignPromotion, (_, before, letter) => `${before}${foreignPieces[letter][1]}`);
246
+ }
226
247
  // The pieces' letters of other languages that name one piece only: the Italian, French, Spanish and German ones; R is the rook in SAN.
227
248
  const foreignPieces = {
228
249
  C: ["knight", "N"],
@@ -233,6 +254,11 @@ const foreignPieces = {
233
254
  T: ["rook", "R"],
234
255
  D: ["queen", "Q"],
235
256
  };
257
+ // A move written with one of those letters, standing alone in a text.
258
+ const foreignMove = new RegExp(`(?<![\\p{L}\\d])[${Object.keys(foreignPieces).join("")}][a-h]?[1-8]?x?[a-h][1-8][+#]?(?![\\p{L}\\d])`, "gu");
259
+ // A whole move with such a letter, after its number if any: Tf1, 4. Cxe5+, D d1; and a pawn's move to the last rank that becomes such a piece: e8=D, exd1T.
260
+ const foreignPieceMove = /^(\s*(?:\d+\s*(?:\.[\s.]*|…\s*))?)([CSAFLTDt])(?=\s*[a-h]?[1-8]?[-x:×]?[a-h][1-8][+#]*[!?]*\s*$)/;
261
+ const foreignPromotion = /^(\s*(?:\d+\s*(?:\.[\s.]*|…\s*))?[a-h]?[1-8]?[-x:×]?[a-h][18]=?)([CSAFLTD])(?=[+#]*[!?]*\s*$)/;
236
262
  // The legal moves of the positions seen last, by their key: about 4 KB each.
237
263
  const legalByPosition = new Map();
238
264
  const positionsKept = 1_000;
@@ -3,7 +3,7 @@ export { boardText, figurinesOf } from "./board-text.ts";
3
3
  export { canMate, clockAfterMove, clockOfComment, clockStarted, clockTag, clockText, startClock, timeControlOf, timeControlOfTag, timeControlTag, timeControlText, timeLeft } from "./clock.ts";
4
4
  export type { Clock, TimeControl } from "./clock.ts";
5
5
  export type { Facts, PieceFacts, PieceLetter, Pin, Skewer } from "./facts.ts";
6
- export { Game, withSanLetters } from "./game.ts";
6
+ export { foreignMovesIn, Game, withSanLetters, withSanPieces } from "./game.ts";
7
7
  export type { Ending, Move, Outcome, Played, Position, Promotion, Refusal, Side, Square } from "./game.ts";
8
8
  export type { Illegal, IllegalReason } from "./illegal.ts";
9
9
  export { type Lead, leadOf, leadText, readLead, withLead } from "./lead.ts";
@@ -12,9 +12,9 @@ export type { AiLevel, Layer } from "./levels.ts";
12
12
  export { materialOf } from "./material.ts";
13
13
  export type { Material } from "./material.ts";
14
14
  export { endWithMood, type Mood, moodAt, type MoodNow, moodOf, moods, moodTag, withoutMoods } from "./moods.ts";
15
- export { bookMoves, indexBook, indexOpenings, inOpenings, openingOf, outOfBookMove } from "./openings.ts";
16
- export type { Opening, OpeningBook, OpeningIndex } from "./openings.ts";
17
- export { pgnDate, readPgn, writePgn } from "./pgn.ts";
15
+ export { bookMoves, bookPlaceOf, indexBook, indexOpenings, openingOf, outOfBookMove, positionKeysOf } from "./openings.ts";
16
+ export type { BookPlace, Opening, OpeningBook, OpeningIndex } from "./openings.ts";
17
+ export { pgnDate, readPgn, readPgnTags, readPgnUnplayed, writePgn } from "./pgn.ts";
18
18
  export type { Capture, Chances, DoubleAttack, Possibilities, StalemateTrap } from "./possibilities.ts";
19
19
  export type { BishopFacts, CentreSquare, KingFacts, Outpost, PawnFacts, PawnSquare, Phase, Profile, SideProfile } from "./profile.ts";
20
20
  export { movesOnly } from "./record.ts";
@@ -2,12 +2,12 @@
2
2
  export { accountSide } from "./accounts.js";
3
3
  export { boardText, figurinesOf } from "./board-text.js";
4
4
  export { canMate, clockAfterMove, clockOfComment, clockStarted, clockTag, clockText, startClock, timeControlOf, timeControlOfTag, timeControlTag, timeControlText, timeLeft } from "./clock.js";
5
- export { Game, withSanLetters } from "./game.js";
5
+ export { foreignMovesIn, Game, withSanLetters, withSanPieces } from "./game.js";
6
6
  export { leadOf, leadText, readLead, withLead } from "./lead.js";
7
7
  export { aiLevels, layersOf, levelForPlayer, levelOf, levelTag, sees, suggestedLevel } from "./levels.js";
8
8
  export { materialOf } from "./material.js";
9
9
  export { endWithMood, moodAt, moodOf, moods, moodTag, withoutMoods } from "./moods.js";
10
- export { bookMoves, indexBook, indexOpenings, inOpenings, openingOf, outOfBookMove } from "./openings.js";
11
- export { pgnDate, readPgn, writePgn } from "./pgn.js";
10
+ export { bookMoves, bookPlaceOf, indexBook, indexOpenings, openingOf, outOfBookMove, positionKeysOf } from "./openings.js";
11
+ export { pgnDate, readPgn, readPgnTags, readPgnUnplayed, writePgn } from "./pgn.js";
12
12
  export { movesOnly } from "./record.js";
13
13
  export { aiHostIn, aiModelIn, aiNameOf, annotatorOf, arrowOf, badges, closingOf, drawingsIn, drawingsText, gameReviewOf, isReviewed, reviewed, reviewOf, squareOf, unreviewed, unsigned, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.js";
@@ -12,8 +12,11 @@ export interface Opening {
12
12
  readonly variation?: string;
13
13
  /** How many plies the game had when it reached the opening's position. */
14
14
  readonly ply: number;
15
- /** Whether the game's position now is a named one; when not, it left the book at the ply after `ply`. */
16
- readonly inBook: boolean;
15
+ }
16
+ /** Where a game from the standard position stands in the book: the last named opening it reached, and how many of its first plies a line of the book reaches (D-68). */
17
+ export interface BookPlace {
18
+ readonly opening?: Opening;
19
+ readonly plies: number;
17
20
  }
18
21
  /** The book's moves: each position on a line of Lichess's files to the moves its lines play from it, the theory the AI knows at the master level (D-15, D-60). */
19
22
  export type OpeningBook = Readonly<Record<string, readonly string[]>>;
@@ -27,9 +30,20 @@ export declare function indexOpenings(tsv: string): Record<string, [string, stri
27
30
  export declare function indexBook(tsv: string): Record<string, string[]>;
28
31
  /** The moves the book knows from a position, in SAN; none out of the book. */
29
32
  export declare function bookMoves(fen: string, book: OpeningBook): readonly string[];
30
- /** Whether a line of the book reaches a position, by any order of moves: one its lines play on from, or one an opening is named after. */
31
- export declare function inOpenings(fen: string, index: OpeningIndex, book: OpeningBook): boolean;
32
33
  /** The opening of a game from the standard position, the last named one its moves reached; none before the first, or from another position. */
33
34
  export declare function openingOf(record: GameRecord, index: OpeningIndex): Opening | undefined;
34
- /** The move number at which a game that reached an opening at `ply` left the book. */
35
- export declare function outOfBookMove(opening: Opening): number;
35
+ /**
36
+ * Where a game stands in the book, from the keys of the positions it stood in, its start first: the last named position its moves reached, and its first
37
+ * plies whose position a line of the book reaches, by any order of moves, one its lines play on from or one an opening is named after; without the book,
38
+ * no ply is counted. None from another position than the standard one, whatever its FEN tag says.
39
+ */
40
+ export declare function bookPlaceOf(keys: readonly string[], index: OpeningIndex, book?: OpeningBook): BookPlace | undefined;
41
+ /** The keys of a record's positions, its start first, replayed to the book's depth for a game from the standard position: for a record no live game holds. */
42
+ export declare function positionKeysOf(record: GameRecord): readonly string[];
43
+ /** A position without its move counters, the key of the index and the book: the same position reached in another order is the same key. */
44
+ export declare function positionKey(fen: string): string;
45
+ /**
46
+ * The move number at which a game from the standard position left the book (D-68), the move of its first ply past the `inBook` plies a line of the book
47
+ * reaches; none while all its `plies` are the book's.
48
+ */
49
+ export declare function outOfBookMove(plies: number, inBook: number): number | undefined;
@@ -2,6 +2,7 @@
2
2
  import { Chess } from "chess.js";
3
3
  /** No line of the book is longer: past it, no position is looked up. */
4
4
  const bookDepth = 40;
5
+ const standardStart = positionKey(new Chess().fen());
5
6
  /** The index of the openings in Lichess's TSV files (eco, name, pgn), each line keyed by the position its moves reach. */
6
7
  export function indexOpenings(tsv) {
7
8
  const index = {};
@@ -37,39 +38,61 @@ export function indexBook(tsv) {
37
38
  export function bookMoves(fen, book) {
38
39
  return book[positionKey(fen)] ?? [];
39
40
  }
40
- /** Whether a line of the book reaches a position, by any order of moves: one its lines play on from, or one an opening is named after. */
41
- export function inOpenings(fen, index, book) {
42
- const key = positionKey(fen);
43
- return book[key] !== undefined || index[key] !== undefined;
44
- }
45
41
  /** The opening of a game from the standard position, the last named one its moves reached; none before the first, or from another position. */
46
42
  export function openingOf(record, index) {
47
- if (record.tags.FEN !== undefined && record.tags.FEN !== new Chess().fen()) {
43
+ return bookPlaceOf(positionKeysOf(record), index)?.opening;
44
+ }
45
+ /**
46
+ * Where a game stands in the book, from the keys of the positions it stood in, its start first: the last named position its moves reached, and its first
47
+ * plies whose position a line of the book reaches, by any order of moves, one its lines play on from or one an opening is named after; without the book,
48
+ * no ply is counted. None from another position than the standard one, whatever its FEN tag says.
49
+ */
50
+ export function bookPlaceOf(keys, index, book) {
51
+ if (!standardStartKey(keys[0])) {
48
52
  return undefined;
49
53
  }
50
- const chess = new Chess();
51
- const plies = Math.min(record.moves.length, bookDepth);
54
+ const last = Math.min(keys.length - 1, bookDepth);
52
55
  let found;
53
- for (let ply = 1; ply <= plies; ply++) {
54
- chess.move(record.moves[ply - 1].san);
55
- const named = index[positionKey(chess.fen())];
56
+ let plies = 0;
57
+ for (let ply = 1; ply <= last; ply++) {
58
+ const key = keys[ply];
59
+ const named = index[key];
56
60
  if (named !== undefined) {
57
61
  found = { named, ply };
58
62
  }
63
+ if (book !== undefined && plies === ply - 1 && (named !== undefined || book[key] !== undefined)) {
64
+ plies = ply;
65
+ }
59
66
  }
60
- if (found === undefined) {
61
- return undefined;
67
+ return found === undefined ? { plies } : { opening: openingNamed(found.named, found.ply), plies };
68
+ }
69
+ /** The keys of a record's positions, its start first, replayed to the book's depth for a game from the standard position: for a record no live game holds. */
70
+ export function positionKeysOf(record) {
71
+ const start = positionKey(record.tags.FEN ?? new Chess().fen());
72
+ if (!standardStartKey(start)) {
73
+ return [start];
62
74
  }
63
- const [eco, name] = found.named;
75
+ const chess = new Chess();
76
+ return [start, ...record.moves.slice(0, bookDepth).map(({ san }) => positionKey(chess.move(san).after))];
77
+ }
78
+ /** A position without its move counters, the key of the index and the book: the same position reached in another order is the same key. */
79
+ export function positionKey(fen) {
80
+ return fen.split(" ").slice(0, 4).join(" ");
81
+ }
82
+ /**
83
+ * The move number at which a game from the standard position left the book (D-68), the move of its first ply past the `inBook` plies a line of the book
84
+ * reaches; none while all its `plies` are the book's.
85
+ */
86
+ export function outOfBookMove(plies, inBook) {
87
+ return plies > inBook ? Math.floor(inBook / 2) + 1 : undefined;
88
+ }
89
+ /** The opening a name of the index stands for, reached at `ply`. */
90
+ function openingNamed([eco, name], ply) {
64
91
  const colon = name.indexOf(":");
65
92
  const family = colon < 0 ? name : name.slice(0, colon);
66
- const opening = { eco, name, family, ply: found.ply, inBook: found.ply === record.moves.length };
93
+ const opening = { eco, name, family, ply };
67
94
  return colon < 0 ? opening : { ...opening, variation: name.slice(colon + 1).trim() };
68
95
  }
69
- /** The move number at which a game that reached an opening at `ply` left the book. */
70
- export function outOfBookMove(opening) {
71
- return Math.floor(opening.ply / 2) + 1;
72
- }
73
96
  /** The moves from a position that at least one line in fifty plays, the most played first, with the trap's name when all their lines are traps. */
74
97
  function bookOf(counts) {
75
98
  const total = [...counts.values()].reduce((sum, count) => sum + count.lines, 0);
@@ -92,7 +115,7 @@ function linesOf(tsv) {
92
115
  return [{ eco, name, moves }];
93
116
  });
94
117
  }
95
- /** A position without its move counters: the same position reached in another order is the same key. */
96
- function positionKey(fen) {
97
- return fen.split(" ").slice(0, 4).join(" ");
118
+ /** Whether a position's key is the standard start's: the one check of where the book applies. */
119
+ function standardStartKey(key) {
120
+ return key === standardStart;
98
121
  }
@@ -6,5 +6,9 @@ import { type GameRecord } from "./record.ts";
6
6
  export declare function writePgn(record: GameRecord): string;
7
7
  /** The games of a PGN text, with their tags, moves, NAGs (! and ? too) and comments; variations are skipped. Every move is checked: an error names its line. */
8
8
  export declare function readPgn(text: string): GameRecord[];
9
+ /** The games of a PGN text as readPgn gives them, but with their moves as written, neither checked nor played: for what the tags and comments say, quickly. */
10
+ export declare function readPgnUnplayed(text: string): GameRecord[];
11
+ /** The tags of a PGN text's first game, read without its moves: none checked, none played. */
12
+ export declare function readPgnTags(text: string): Readonly<Record<string, string>>;
9
13
  /** The day of a date, in its own time zone, as PGN's Date tag writes it: 2026.11.02. */
10
14
  export declare function pgnDate(date: Date): string;
package/dist/core/pgn.js CHANGED
@@ -14,7 +14,25 @@ export function writePgn(record) {
14
14
  }
15
15
  /** The games of a PGN text, with their tags, moves, NAGs (! and ? too) and comments; variations are skipped. Every move is checked: an error names its line. */
16
16
  export function readPgn(text) {
17
- return new PgnReader(text).read();
17
+ return new PgnReader(text, true).read();
18
+ }
19
+ /** The games of a PGN text as readPgn gives them, but with their moves as written, neither checked nor played: for what the tags and comments say, quickly. */
20
+ export function readPgnUnplayed(text) {
21
+ return new PgnReader(text, false).read();
22
+ }
23
+ /** The tags of a PGN text's first game, read without its moves: none checked, none played. */
24
+ export function readPgnTags(text) {
25
+ const tags = {};
26
+ for (const line of text.split("\n")) {
27
+ const tag = tagPattern.exec(line);
28
+ if (tag !== null) {
29
+ tags[tag[1]] = tag[2].replace(/\\(.)/g, "$1");
30
+ }
31
+ else if (line.trim() !== "" || Object.keys(tags).length > 0) {
32
+ break;
33
+ }
34
+ }
35
+ return tags;
18
36
  }
19
37
  /** The day of a date, in its own time zone, as PGN's Date tag writes it: 2026.11.02. */
20
38
  export function pgnDate(date) {
@@ -24,6 +42,8 @@ export function pgnDate(date) {
24
42
  // The seven tags of every PGN, in their order, with the value for an unknown one.
25
43
  const sevenTags = [["Event", "?"], ["Site", "?"], ["Date", "????.??.??"], ["Round", "?"], ["White", "?"], ["Black", "?"], ["Result", "*"]];
26
44
  const sevenTagNames = new Set(sevenTags.map(([name]) => name));
45
+ // A line holding a tag alone, as writePgn writes it.
46
+ const tagPattern = /^\s*\[\s*(\w+)\s*"((?:[^"\\]|\\.)*)"\s*\]\s*$/;
27
47
  // The judgments written after a move, NAGs $1 to $6.
28
48
  const glyphs = ["!", "?", "!!", "??", "!?", "?!"];
29
49
  const nagOfGlyph = new Map(glyphs.map((glyph, index) => [glyph, index + 1]));
@@ -33,16 +53,20 @@ const lineLength = 79;
33
53
  const tokenPattern = /\s+|(?<![^\n])%[^\n]*|;[^\n]*|\[\s*(\w+)\s*"((?:[^"\\]|\\.)*)"\s*\]|\{([^}]*)\}|\$(\d+)|([()])|\d+\s*\.+|\.+|(1-0|0-1|1\/2-1\/2|\*)|([^\s{}()[\]";$]+)/y;
34
54
  class PgnReader {
35
55
  text;
56
+ // Whether the moves are played, so checked and written as Chesspal writes them, or taken as written.
57
+ plays;
36
58
  records = [];
37
59
  tags = [];
38
60
  comments = [];
39
61
  moves = [];
40
62
  game;
63
+ begun = false;
41
64
  result;
42
65
  depth = 0;
43
66
  at = 0;
44
- constructor(text) {
67
+ constructor(text, plays) {
45
68
  this.text = text;
69
+ this.plays = plays;
46
70
  }
47
71
  read() {
48
72
  for (let index = 0; index < this.text.length; index = tokenPattern.lastIndex) {
@@ -94,7 +118,7 @@ class PgnReader {
94
118
  }
95
119
  tag(name, value) {
96
120
  // A tag after the moves starts the next game, when the last one had no result.
97
- if (this.game !== undefined) {
121
+ if (this.begun) {
98
122
  this.finish();
99
123
  }
100
124
  this.tags.push([name, value]);
@@ -106,7 +130,10 @@ class PgnReader {
106
130
  }
107
131
  const [, san = "", glyph = ""] = /^(.*?)([!?]*)$/.exec(token) ?? [];
108
132
  const game = this.started();
109
- if (san !== "") {
133
+ if (san !== "" && game === undefined) {
134
+ this.moves.push({ san, nags: [], comments: [] });
135
+ }
136
+ else if (san !== "" && game !== undefined) {
110
137
  const played = game.playRecorded(san);
111
138
  if (!played.played) {
112
139
  throw this.error(this.at, played.message, game.position.moveNumber);
@@ -129,9 +156,10 @@ class PgnReader {
129
156
  // A NAG before the first move judges nothing: skipped.
130
157
  this.moves.at(-1)?.nags.push(nag);
131
158
  }
132
- /** The game the moves are played on, made from the tags at the first move. */
159
+ /** The game the moves are played on, made from the tags at the first move; none when they are taken as written. */
133
160
  started() {
134
- if (this.game === undefined) {
161
+ this.begun = true;
162
+ if (this.game === undefined && this.plays) {
135
163
  try {
136
164
  this.game = new Game({ tags: Object.fromEntries(this.tags), moves: [] });
137
165
  }
@@ -156,6 +184,7 @@ class PgnReader {
156
184
  this.comments = [];
157
185
  this.moves = [];
158
186
  this.game = undefined;
187
+ this.begun = false;
159
188
  this.result = undefined;
160
189
  }
161
190
  error(index, message, moveNumber) {
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * The game in analysis: its name as the lists write it, when the analysis last changed it, whether the player brought it on the page and the AI has not
3
- * gone over it yet, the moves whose note the chat corrected with the player, which the analyst leaves (D-101), and the run of the analyst that took it.
3
+ * gone over it yet, and whether they asked there for a new review of it; the moves whose note the chat corrected with the player, which the analyst leaves
4
+ * (D-101), and the run of the analyst that took it.
4
5
  */
5
6
  export interface Analysis {
6
7
  readonly game: string;
7
8
  readonly at: string;
8
9
  readonly brought?: true;
10
+ readonly redo?: true;
9
11
  readonly corrected?: readonly string[];
10
12
  readonly analyst?: AnalystRun;
11
13
  }
@@ -16,11 +18,12 @@ export interface AnalystRun {
16
18
  readonly since: string;
17
19
  }
18
20
  /**
19
- * How a game is named in analysis: brought to the page by the player, with the moves the chat just corrected, taken by a run of the analyst, or afresh,
20
- * without the corrections and the analyst it had.
21
+ * How a game is named in analysis: brought to the page by the player, for a new review too, with the moves the chat just corrected, taken by a run of the
22
+ * analyst, or afresh, without the corrections and the analyst it had.
21
23
  */
22
24
  export interface AnalysisStart {
23
25
  readonly brought?: boolean;
26
+ readonly redo?: boolean;
24
27
  readonly corrected?: readonly string[];
25
28
  readonly analyst?: AnalystRun;
26
29
  readonly afresh?: boolean;
@@ -30,7 +33,7 @@ export interface AnalysisStart {
30
33
  * unless it starts afresh, in one write, so that the page never sees it leave the analysis; the analyst stays with the game it took, whichever the page
31
34
  * opens meanwhile (Federico), and goes when that game starts afresh.
32
35
  */
33
- export declare function startAnalysis(folder: string, game: string, now: Date, { brought, corrected, analyst, afresh }?: AnalysisStart): void;
36
+ export declare function startAnalysis(folder: string, game: string, now: Date, { brought, redo, corrected, analyst, afresh }?: AnalysisStart): void;
34
37
  /** Lets the analyst go from the game it took, when that game's review is deleted while the page shows another. */
35
38
  export declare function forgetAnalyst(folder: string): void;
36
39
  /** The game in analysis, or none when there is none, or analysis.json cannot be read. */