chesspal 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/README.md +136 -2
  2. package/dist/cli/chat-hook.d.ts +2 -0
  3. package/dist/cli/chat-hook.js +33 -0
  4. package/dist/cli/cli.d.ts +2 -0
  5. package/dist/cli/cli.js +28 -0
  6. package/dist/cli/command-line.d.ts +8 -0
  7. package/dist/cli/command-line.js +178 -0
  8. package/dist/commands/ai-move.d.ts +37 -0
  9. package/dist/commands/ai-move.js +92 -0
  10. package/dist/commands/ai-say.d.ts +20 -0
  11. package/dist/commands/ai-say.js +40 -0
  12. package/dist/commands/analyse-game.d.ts +61 -0
  13. package/dist/commands/analyse-game.js +238 -0
  14. package/dist/commands/annotate-game.d.ts +35 -0
  15. package/dist/commands/annotate-game.js +78 -0
  16. package/dist/commands/clock.d.ts +15 -0
  17. package/dist/commands/clock.js +45 -0
  18. package/dist/commands/context.d.ts +56 -0
  19. package/dist/commands/context.js +36 -0
  20. package/dist/commands/draw-offer.d.ts +10 -0
  21. package/dist/commands/draw-offer.js +43 -0
  22. package/dist/commands/drawings.d.ts +15 -0
  23. package/dist/commands/drawings.js +23 -0
  24. package/dist/commands/end-game.d.ts +23 -0
  25. package/dist/commands/end-game.js +64 -0
  26. package/dist/commands/export-game.d.ts +14 -0
  27. package/dist/commands/export-game.js +26 -0
  28. package/dist/commands/game-summary.d.ts +95 -0
  29. package/dist/commands/game-summary.js +239 -0
  30. package/dist/commands/index.d.ts +3 -0
  31. package/dist/commands/index.js +19 -0
  32. package/dist/commands/init.d.ts +15 -0
  33. package/dist/commands/init.js +73 -0
  34. package/dist/commands/levels.d.ts +9 -0
  35. package/dist/commands/levels.js +25 -0
  36. package/dist/commands/look.d.ts +31 -0
  37. package/dist/commands/look.js +61 -0
  38. package/dist/commands/new-game.d.ts +69 -0
  39. package/dist/commands/new-game.js +215 -0
  40. package/dist/commands/opening-book.d.ts +13 -0
  41. package/dist/commands/opening-book.js +84 -0
  42. package/dist/commands/page-link.d.ts +11 -0
  43. package/dist/commands/page-link.js +28 -0
  44. package/dist/commands/past-games.d.ts +11 -0
  45. package/dist/commands/past-games.js +47 -0
  46. package/dist/commands/play-move.d.ts +44 -0
  47. package/dist/commands/play-move.js +102 -0
  48. package/dist/commands/player-move.d.ts +17 -0
  49. package/dist/commands/player-move.js +44 -0
  50. package/dist/commands/player-say.d.ts +5 -0
  51. package/dist/commands/player-say.js +16 -0
  52. package/dist/commands/ponder.d.ts +42 -0
  53. package/dist/commands/ponder.js +167 -0
  54. package/dist/commands/position.d.ts +14 -0
  55. package/dist/commands/position.js +128 -0
  56. package/dist/commands/profiles.d.ts +60 -0
  57. package/dist/commands/profiles.js +129 -0
  58. package/dist/commands/registry.d.ts +54 -0
  59. package/dist/commands/registry.js +61 -0
  60. package/dist/commands/say.d.ts +16 -0
  61. package/dist/commands/say.js +84 -0
  62. package/dist/commands/status.d.ts +29 -0
  63. package/dist/commands/status.js +103 -0
  64. package/dist/commands/study-games.d.ts +5 -0
  65. package/dist/commands/study-games.js +40 -0
  66. package/dist/commands/study.d.ts +38 -0
  67. package/dist/commands/study.js +122 -0
  68. package/dist/commands/view.d.ts +14 -0
  69. package/dist/commands/view.js +125 -0
  70. package/dist/core/board-text.d.ts +7 -0
  71. package/dist/core/board-text.js +18 -0
  72. package/dist/core/book.json +1 -0
  73. package/dist/core/clock.d.ts +45 -0
  74. package/dist/core/clock.js +109 -0
  75. package/dist/core/facts.d.ts +55 -0
  76. package/dist/core/facts.js +175 -0
  77. package/dist/core/game.d.ts +93 -0
  78. package/dist/core/game.js +348 -0
  79. package/dist/core/illegal.d.ts +19 -0
  80. package/dist/core/illegal.js +141 -0
  81. package/dist/core/index.d.ts +19 -0
  82. package/dist/core/index.js +11 -0
  83. package/dist/core/levels.d.ts +20 -0
  84. package/dist/core/levels.js +33 -0
  85. package/dist/core/material.d.ts +9 -0
  86. package/dist/core/material.js +12 -0
  87. package/dist/core/moods.d.ts +23 -0
  88. package/dist/core/moods.js +45 -0
  89. package/dist/core/openings.d.ts +37 -0
  90. package/dist/core/openings.js +98 -0
  91. package/dist/core/openings.json +1 -0
  92. package/dist/core/pgn.d.ts +10 -0
  93. package/dist/core/pgn.js +213 -0
  94. package/dist/core/profile.d.ts +117 -0
  95. package/dist/core/profile.js +232 -0
  96. package/dist/core/record.d.ts +21 -0
  97. package/dist/core/record.js +25 -0
  98. package/dist/core/review.d.ts +74 -0
  99. package/dist/core/review.js +126 -0
  100. package/dist/games/ai-model.d.ts +5 -0
  101. package/dist/games/ai-model.js +15 -0
  102. package/dist/games/analysis.d.ts +12 -0
  103. package/dist/games/analysis.js +26 -0
  104. package/dist/games/archive.d.ts +42 -0
  105. package/dist/games/archive.js +168 -0
  106. package/dist/games/chat-start.d.ts +16 -0
  107. package/dist/games/chat-start.js +26 -0
  108. package/dist/games/current-game.d.ts +54 -0
  109. package/dist/games/current-game.js +128 -0
  110. package/dist/games/files.d.ts +9 -0
  111. package/dist/games/files.js +52 -0
  112. package/dist/games/index.d.ts +10 -0
  113. package/dist/games/index.js +11 -0
  114. package/dist/games/plans.d.ts +9 -0
  115. package/dist/games/plans.js +23 -0
  116. package/dist/games/table-talk.d.ts +12 -0
  117. package/dist/games/table-talk.js +25 -0
  118. package/dist/games/talk.d.ts +40 -0
  119. package/dist/games/talk.js +131 -0
  120. package/dist/games/thoughts.d.ts +15 -0
  121. package/dist/games/thoughts.js +60 -0
  122. package/dist/home/data-folder.d.ts +12 -0
  123. package/dist/home/data-folder.js +55 -0
  124. package/dist/home/index.d.ts +2 -0
  125. package/dist/home/index.js +3 -0
  126. package/dist/home/open-folder.d.ts +2 -0
  127. package/dist/home/open-folder.js +14 -0
  128. package/dist/host-files/host-files.d.ts +10 -0
  129. package/dist/host-files/host-files.js +167 -0
  130. package/dist/host-files/index.d.ts +2 -0
  131. package/dist/host-files/index.js +3 -0
  132. package/dist/host-files/ponder-agents.d.ts +12 -0
  133. package/dist/host-files/ponder-agents.js +40 -0
  134. package/dist/index.d.ts +4 -0
  135. package/dist/index.js +2 -0
  136. package/dist/page/app.js +576 -0
  137. package/dist/page/avatars/beanie.webp +0 -0
  138. package/dist/page/avatars/beard.webp +0 -0
  139. package/dist/page/avatars/bun.webp +0 -0
  140. package/dist/page/avatars/cat.webp +0 -0
  141. package/dist/page/avatars/curls.webp +0 -0
  142. package/dist/page/avatars/glasses.webp +0 -0
  143. package/dist/page/avatars/moustache.webp +0 -0
  144. package/dist/page/avatars/owl.webp +0 -0
  145. package/dist/page/avatars/short-hair.webp +0 -0
  146. package/dist/page/avatars/silver-bob.webp +0 -0
  147. package/dist/page/avatars.js +16 -0
  148. package/dist/page/badges.js +50 -0
  149. package/dist/page/board.js +566 -0
  150. package/dist/page/browse.js +17 -0
  151. package/dist/page/clock-time.js +12 -0
  152. package/dist/page/dialog.js +26 -0
  153. package/dist/page/ending.js +73 -0
  154. package/dist/page/explain.js +58 -0
  155. package/dist/page/fold-tab.js +9 -0
  156. package/dist/page/folds.js +43 -0
  157. package/dist/page/game-column.js +44 -0
  158. package/dist/page/header.js +27 -0
  159. package/dist/page/helps-view.js +51 -0
  160. package/dist/page/helps.js +90 -0
  161. package/dist/page/main.js +8 -0
  162. package/dist/page/model-name.js +20 -0
  163. package/dist/page/move-list.js +68 -0
  164. package/dist/page/new-game-dialog.js +42 -0
  165. package/dist/page/opening-book.js +14 -0
  166. package/dist/page/pieces/CREDITS.md +36 -0
  167. package/dist/page/pieces/cburnett-small.svg +20 -0
  168. package/dist/page/pieces/cburnett.svg +20 -0
  169. package/dist/page/pieces/nitida-small.svg +19 -0
  170. package/dist/page/pieces/nitida.svg +20 -0
  171. package/dist/page/pieces/torretta-small.svg +19 -0
  172. package/dist/page/pieces/torretta.svg +19 -0
  173. package/dist/page/pieces.js +28 -0
  174. package/dist/page/player-card.js +60 -0
  175. package/dist/page/plies.js +62 -0
  176. package/dist/page/profile-dialogs.js +123 -0
  177. package/dist/page/profile.js +95 -0
  178. package/dist/page/settings-panel.js +32 -0
  179. package/dist/page/settings.js +76 -0
  180. package/dist/page/sounds/capture.ogg +0 -0
  181. package/dist/page/sounds/check.ogg +0 -0
  182. package/dist/page/sounds/draw.ogg +0 -0
  183. package/dist/page/sounds/flag.ogg +0 -0
  184. package/dist/page/sounds/mate.ogg +0 -0
  185. package/dist/page/sounds/move.ogg +0 -0
  186. package/dist/page/sounds/offer.ogg +0 -0
  187. package/dist/page/sounds/resign.ogg +0 -0
  188. package/dist/page/sounds.js +105 -0
  189. package/dist/page/studied.js +14 -0
  190. package/dist/page/study-dialog.js +58 -0
  191. package/dist/page/style.css +4398 -0
  192. package/dist/page/talk.js +172 -0
  193. package/dist/page/text.js +593 -0
  194. package/dist/page/torretta.js +38 -0
  195. package/dist/page/voice.js +38 -0
  196. package/dist/profiles/avatar.d.ts +23 -0
  197. package/dist/profiles/avatar.js +84 -0
  198. package/dist/profiles/front-matter.d.ts +9 -0
  199. package/dist/profiles/front-matter.js +33 -0
  200. package/dist/profiles/index.d.ts +3 -0
  201. package/dist/profiles/index.js +5 -0
  202. package/dist/profiles/profiles.d.ts +58 -0
  203. package/dist/profiles/profiles.js +237 -0
  204. package/dist/profiles/settings.d.ts +12 -0
  205. package/dist/profiles/settings.js +46 -0
  206. package/dist/server/analyses.d.ts +17 -0
  207. package/dist/server/analyses.js +46 -0
  208. package/dist/server/bell.d.ts +60 -0
  209. package/dist/server/bell.js +209 -0
  210. package/dist/server/caller.d.ts +10 -0
  211. package/dist/server/caller.js +99 -0
  212. package/dist/server/http.d.ts +16 -0
  213. package/dist/server/http.js +57 -0
  214. package/dist/server/mcp.d.ts +14 -0
  215. package/dist/server/mcp.js +179 -0
  216. package/dist/server/page.d.ts +46 -0
  217. package/dist/server/page.js +657 -0
  218. package/dist/server/profile-routes.d.ts +19 -0
  219. package/dist/server/profile-routes.js +186 -0
  220. package/dist/version.d.ts +2 -0
  221. package/dist/version.js +3 -0
  222. package/instructions/analyst.md +8 -0
  223. package/instructions/opponent.md +15 -0
  224. package/instructions/ponder.md +9 -0
  225. package/package.json +66 -5
@@ -0,0 +1,45 @@
1
+ /** The moods of the AI, which the Torretta shows. */
2
+ export const moods = ["neutral", "happy", "surprised", "worried", "thoughtful", "sad"];
3
+ /** How many of the AI's moves each mood lasts, the one it comes with included: a surprise is a flash (Julián, round of T-107). */
4
+ export const moodMoves = { neutral: 0, happy: 3, surprised: 1, worried: 3, thoughtful: 3, sad: 3 };
5
+ /** The mood as a move's comment carries it. */
6
+ export function moodTag(mood) {
7
+ return `[%mood ${mood}]`;
8
+ }
9
+ /** The mood a move's comment carries, if it carries one. */
10
+ export function moodOf(comment) {
11
+ const found = moodPattern.exec(comment ?? "")?.[1];
12
+ return moods.find((mood) => mood === found);
13
+ }
14
+ /** Gives the AI's mood on the last move of a game that has ended, in place of the one it had: the face the game ends with. A game without moves keeps none. */
15
+ export function endWithMood(game, mood) {
16
+ const plies = game.record.moves.length;
17
+ const comment = game.record.moves[plies - 1]?.comment?.replace(moodPattern, "");
18
+ if (plies > 0) {
19
+ game.annotate(plies, { comment: comment === undefined || comment === "" ? moodTag(mood) : `${comment} ${moodTag(mood)}` });
20
+ }
21
+ }
22
+ /** The AI's mood after the first `plies` moves of a record: the last it gave, while it lasts, else neutral. A mood on the player's move is the one the game ended with. */
23
+ export function moodAt(record, ai, plies = record.moves.length) {
24
+ // The ply count from white's move of the first move number: 1 when black moves first.
25
+ const offset = record.tags.FEN?.split(" ")[1] === "b" ? 1 : 0;
26
+ const aiMoves = (index) => ((index + offset) % 2 === 0 ? "white" : "black") === ai;
27
+ let since = 0;
28
+ for (let index = Math.min(plies, record.moves.length) - 1; index >= 0; index--) {
29
+ const mood = moodOf(record.moves[index]?.comment);
30
+ if (mood !== undefined) {
31
+ const left = moodMoves[mood] - 1 - since;
32
+ return mood === "neutral" || left < 0 ? neutral : { mood, left };
33
+ }
34
+ if (aiMoves(index)) {
35
+ since++;
36
+ }
37
+ }
38
+ return neutral;
39
+ }
40
+ /** The record without moods: another game's are not the AI's. */
41
+ export function withoutMoods(record) {
42
+ return { ...record, moves: record.moves.map((move) => ({ ...move, comment: move.comment?.replace(moodPattern, "") })) };
43
+ }
44
+ const neutral = { mood: "neutral", left: 0 };
45
+ const moodPattern = /\s*\[%mood ([a-z]+)\]/;
@@ -0,0 +1,37 @@
1
+ import type { GameRecord } from "./record.ts";
2
+ /** The openings by position: the position's key (its FEN without the move counters) to its ECO code and name. */
3
+ export type OpeningIndex = Readonly<Record<string, readonly [eco: string, name: string]>>;
4
+ /** The opening a game is in, or the last one it was in: its ECO code, its name split in family and variation, and the ply that reached it. */
5
+ export interface Opening {
6
+ readonly eco: string;
7
+ /** The whole name, as Lichess writes it: Sicilian Defense: Najdorf Variation, English Attack. */
8
+ readonly name: string;
9
+ /** The name before the colon: Sicilian Defense. */
10
+ readonly family: string;
11
+ /** The name after the colon, when there is one: Najdorf Variation, English Attack. */
12
+ readonly variation?: string;
13
+ /** How many plies the game had when it reached the opening's position. */
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;
17
+ }
18
+ /** No line of the book is longer: past it, no position is looked up. */
19
+ export declare const bookDepth = 40;
20
+ /** 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). */
21
+ export type OpeningBook = Readonly<Record<string, readonly string[]>>;
22
+ /** The index of the openings in Lichess's TSV files (eco, name, pgn), each line keyed by the position its moves reach. */
23
+ export declare function indexOpenings(tsv: string): Record<string, [string, string]>;
24
+ /**
25
+ * The book of Lichess's TSV files: every position on their lines, keyed as the index keys them, to the moves the lines play from it, the move most lines go
26
+ * through first: how much theory a move has, never how good it is. A move under one line in fifty from its position is left out; one whose lines are all named
27
+ * traps carries the trap's name, as the files write it: Nxe5 (Mortimer Trap).
28
+ */
29
+ export declare function indexBook(tsv: string): Record<string, string[]>;
30
+ /** The moves the book knows from a position, in SAN; none out of the book. */
31
+ export declare function bookMoves(fen: string, book: OpeningBook): readonly string[];
32
+ /** 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. */
33
+ export declare function inOpenings(fen: string, index: OpeningIndex, book: OpeningBook): boolean;
34
+ /** The opening of a game from the standard position, the last named one its moves reached; none before the first, or from another position. */
35
+ export declare function openingOf(record: GameRecord, index: OpeningIndex): Opening | undefined;
36
+ /** The move number at which a game that reached an opening at `ply` left the book. */
37
+ export declare function outOfBookMove(opening: Opening): number;
@@ -0,0 +1,98 @@
1
+ // The openings by position (D-15, D-16): Lichess's names, indexed by the position each line reaches, so transpositions find them too.
2
+ import { Chess } from "chess.js";
3
+ /** No line of the book is longer: past it, no position is looked up. */
4
+ export const bookDepth = 40;
5
+ /** The index of the openings in Lichess's TSV files (eco, name, pgn), each line keyed by the position its moves reach. */
6
+ export function indexOpenings(tsv) {
7
+ const index = {};
8
+ for (const { eco, name, moves } of linesOf(tsv)) {
9
+ const chess = new Chess();
10
+ moves.forEach((move) => chess.move(move));
11
+ index[positionKey(chess.fen())] = [eco, name];
12
+ }
13
+ return index;
14
+ }
15
+ /**
16
+ * The book of Lichess's TSV files: every position on their lines, keyed as the index keys them, to the moves the lines play from it, the move most lines go
17
+ * through first: how much theory a move has, never how good it is. A move under one line in fifty from its position is left out; one whose lines are all named
18
+ * traps carries the trap's name, as the files write it: Nxe5 (Mortimer Trap).
19
+ */
20
+ export function indexBook(tsv) {
21
+ const lines = new Map();
22
+ for (const { name, moves } of linesOf(tsv)) {
23
+ const trap = name.split(/[:,]/).map((part) => part.trim()).find((part) => /\bTrap\b/.test(part));
24
+ const chess = new Chess();
25
+ for (const move of moves) {
26
+ const key = positionKey(chess.fen());
27
+ const san = chess.move(move).san;
28
+ const counts = lines.get(key) ?? new Map();
29
+ const count = counts.get(san) ?? { lines: 0, traps: [] };
30
+ counts.set(san, { lines: count.lines + 1, traps: trap === undefined ? count.traps : [...count.traps, trap] });
31
+ lines.set(key, counts);
32
+ }
33
+ }
34
+ return Object.fromEntries([...lines].map(([key, counts]) => [key, bookOf(counts)]));
35
+ }
36
+ /** The moves the book knows from a position, in SAN; none out of the book. */
37
+ export function bookMoves(fen, book) {
38
+ return book[positionKey(fen)] ?? [];
39
+ }
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
+ /** 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
+ export function openingOf(record, index) {
47
+ if (record.tags.FEN !== undefined && record.tags.FEN !== new Chess().fen()) {
48
+ return undefined;
49
+ }
50
+ const chess = new Chess();
51
+ const plies = Math.min(record.moves.length, bookDepth);
52
+ 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
+ if (named !== undefined) {
57
+ found = { named, ply };
58
+ }
59
+ }
60
+ if (found === undefined) {
61
+ return undefined;
62
+ }
63
+ const [eco, name] = found.named;
64
+ const colon = name.indexOf(":");
65
+ const family = colon < 0 ? name : name.slice(0, colon);
66
+ const opening = { eco, name, family, ply: found.ply, inBook: found.ply === record.moves.length };
67
+ return colon < 0 ? opening : { ...opening, variation: name.slice(colon + 1).trim() };
68
+ }
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
+ /** 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
+ function bookOf(counts) {
75
+ const total = [...counts.values()].reduce((sum, count) => sum + count.lines, 0);
76
+ return [...counts]
77
+ .filter(([, count]) => count.lines * 50 >= total)
78
+ .sort(([, one], [, other]) => other.lines - one.lines)
79
+ .map(([san, count]) => count.traps.length === count.lines ? `${san} (${[...new Set(count.traps)].join(" / ")})` : san);
80
+ }
81
+ /** The lines of Lichess's TSV files, their moves in SAN; one past the book's depth throws. */
82
+ function linesOf(tsv) {
83
+ return tsv.split(/\r?\n/).flatMap((line) => {
84
+ const [eco, name, pgn] = line.split("\t");
85
+ if (eco === undefined || name === undefined || pgn === undefined || eco === "eco") {
86
+ return [];
87
+ }
88
+ const moves = pgn.split(/\s+/).filter((token) => token !== "" && !/^\d+\.+$/.test(token));
89
+ if (moves.length > bookDepth) {
90
+ throw new Error(`The opening ${eco} ${name} is ${moves.length} plies long, past the book's depth of ${bookDepth}: raise bookDepth.`);
91
+ }
92
+ return [{ eco, name, moves }];
93
+ });
94
+ }
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(" ");
98
+ }