chesspal 0.2.3 → 0.3.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 (214) hide show
  1. package/README.md +11 -12
  2. package/dist/cli/cli.js +83 -3
  3. package/dist/cli/command-line.js +26 -9
  4. package/dist/cli/icon.d.ts +25 -0
  5. package/dist/cli/icon.js +80 -0
  6. package/dist/commands/agents.d.ts +19 -2
  7. package/dist/commands/agents.js +41 -14
  8. package/dist/commands/ai-say.d.ts +2 -1
  9. package/dist/commands/ai-say.js +3 -4
  10. package/dist/commands/analyse-game.d.ts +6 -0
  11. package/dist/commands/analyse-game.js +51 -15
  12. package/dist/commands/annotate-game.d.ts +4 -0
  13. package/dist/commands/annotate-game.js +60 -20
  14. package/dist/commands/answer-lines.d.ts +10 -4
  15. package/dist/commands/answer-lines.js +30 -4
  16. package/dist/commands/coach.js +6 -4
  17. package/dist/commands/context.d.ts +11 -1
  18. package/dist/commands/context.js +5 -0
  19. package/dist/commands/delete-review.js +9 -4
  20. package/dist/commands/game-summary.d.ts +9 -2
  21. package/dist/commands/game-summary.js +11 -0
  22. package/dist/commands/index.d.ts +4 -3
  23. package/dist/commands/index.js +8 -5
  24. package/dist/commands/init.js +2 -4
  25. package/dist/commands/instructions.d.ts +9 -0
  26. package/dist/commands/instructions.js +30 -0
  27. package/dist/commands/lead-input.d.ts +1 -1
  28. package/dist/commands/lead-input.js +7 -4
  29. package/dist/commands/look.d.ts +9 -5
  30. package/dist/commands/look.js +19 -9
  31. package/dist/commands/page-link.js +6 -5
  32. package/dist/commands/past-games.d.ts +1 -1
  33. package/dist/commands/past-games.js +12 -5
  34. package/dist/commands/player-move.js +2 -3
  35. package/dist/commands/ponder.js +1 -1
  36. package/dist/commands/position.js +2 -2
  37. package/dist/commands/profiles.d.ts +23 -9
  38. package/dist/commands/profiles.js +76 -35
  39. package/dist/commands/ratings.d.ts +8 -0
  40. package/dist/commands/ratings.js +45 -0
  41. package/dist/commands/registry.d.ts +1 -1
  42. package/dist/commands/registry.js +3 -2
  43. package/dist/commands/say.d.ts +11 -6
  44. package/dist/commands/say.js +21 -13
  45. package/dist/commands/status.d.ts +6 -2
  46. package/dist/commands/status.js +70 -26
  47. package/dist/commands/study.d.ts +11 -1
  48. package/dist/commands/study.js +39 -7
  49. package/dist/commands/view.d.ts +17 -5
  50. package/dist/commands/view.js +67 -10
  51. package/dist/core/facts.d.ts +14 -2
  52. package/dist/core/facts.js +32 -7
  53. package/dist/core/game.d.ts +5 -0
  54. package/dist/core/game.js +9 -0
  55. package/dist/core/index.d.ts +2 -1
  56. package/dist/core/index.js +1 -1
  57. package/dist/core/placement.d.ts +63 -0
  58. package/dist/core/placement.js +259 -0
  59. package/dist/core/possibilities.d.ts +38 -0
  60. package/dist/core/possibilities.js +254 -0
  61. package/dist/core/review.d.ts +6 -1
  62. package/dist/core/review.js +21 -6
  63. package/dist/games/ai-model.d.ts +4 -2
  64. package/dist/games/ai-model.js +10 -1
  65. package/dist/games/analysis.d.ts +31 -4
  66. package/dist/games/analysis.js +44 -5
  67. package/dist/games/archive.d.ts +6 -3
  68. package/dist/games/archive.js +13 -5
  69. package/dist/games/current-game.d.ts +26 -2
  70. package/dist/games/current-game.js +57 -21
  71. package/dist/games/files.d.ts +7 -0
  72. package/dist/games/files.js +48 -1
  73. package/dist/games/index.d.ts +4 -4
  74. package/dist/games/index.js +4 -4
  75. package/dist/games/table-talk.d.ts +2 -0
  76. package/dist/games/table-talk.js +5 -1
  77. package/dist/games/talk.d.ts +4 -3
  78. package/dist/games/talk.js +3 -3
  79. package/dist/home/data-folder.d.ts +2 -0
  80. package/dist/home/data-folder.js +5 -1
  81. package/dist/home/index.d.ts +1 -1
  82. package/dist/home/index.js +1 -1
  83. package/dist/host-files/agents.d.ts +2 -2
  84. package/dist/host-files/agents.js +12 -6
  85. package/dist/host-files/host-files.d.ts +5 -3
  86. package/dist/host-files/host-files.js +15 -8
  87. package/dist/host-files/index.d.ts +2 -2
  88. package/dist/host-files/index.js +2 -2
  89. package/dist/page/app/app.js +59 -22
  90. package/dist/page/app/keys.js +16 -2
  91. package/dist/page/app/served.js +2 -2
  92. package/dist/page/app/title.js +17 -0
  93. package/dist/page/avatars/torretta.js +4 -4
  94. package/dist/page/board/board.js +2 -1
  95. package/dist/page/board/marks.js +4 -24
  96. package/dist/page/board/signs.js +23 -0
  97. package/dist/page/dialogs/analyses.js +4 -4
  98. package/dist/page/dialogs/dialog.js +4 -1
  99. package/dist/page/dialogs/dialogs.css +84 -10
  100. package/dist/page/dialogs/profiles.js +95 -42
  101. package/dist/page/game/column.js +6 -2
  102. package/dist/page/game/game.css +12 -0
  103. package/dist/page/game/move-list.js +2 -2
  104. package/dist/page/game/plies.js +11 -0
  105. package/dist/page/guide/blocks.js +67 -0
  106. package/dist/page/guide/en.md +66 -0
  107. package/dist/page/guide/foot.js +18 -0
  108. package/dist/page/guide/guide.css +200 -0
  109. package/dist/page/guide/guide.js +89 -0
  110. package/dist/page/guide/it.md +66 -0
  111. package/dist/page/guide/markdown.js +67 -0
  112. package/dist/page/helps/helps.css +10 -11
  113. package/dist/page/helps/helps.js +15 -15
  114. package/dist/page/layout/favicon.svg +13 -0
  115. package/dist/page/layout/header.js +3 -3
  116. package/dist/page/layout/icon.icns +0 -0
  117. package/dist/page/layout/icon.ico +0 -0
  118. package/dist/page/layout/icon.png +0 -0
  119. package/dist/page/layout/layout.css +140 -9
  120. package/dist/page/layout/window.js +45 -0
  121. package/dist/page/legend/legend.css +203 -0
  122. package/dist/page/legend/legend.js +169 -0
  123. package/dist/page/legend/view.js +54 -0
  124. package/dist/page/main.js +4 -1
  125. package/dist/page/players/card.js +1 -1
  126. package/dist/page/profile/profile.js +14 -9
  127. package/dist/page/profile/settings.js +9 -0
  128. package/dist/page/style.css +4 -0
  129. package/dist/page/table/opener.css +65 -0
  130. package/dist/page/table/opener.js +59 -0
  131. package/dist/page/talk/chat.css +97 -0
  132. package/dist/page/talk/talk.css +38 -26
  133. package/dist/page/talk/talk.js +31 -7
  134. package/dist/page/text.js +120 -18
  135. package/dist/page/theme.css +7 -0
  136. package/dist/profiles/index.d.ts +2 -1
  137. package/dist/profiles/index.js +2 -1
  138. package/dist/profiles/profiles.d.ts +10 -15
  139. package/dist/profiles/profiles.js +30 -15
  140. package/dist/profiles/ratings.d.ts +35 -0
  141. package/dist/profiles/ratings.js +61 -0
  142. package/dist/server/bell.d.ts +24 -3
  143. package/dist/server/bell.js +84 -10
  144. package/dist/server/caller.d.ts +2 -1
  145. package/dist/server/caller.js +3 -2
  146. package/dist/server/mcp.d.ts +7 -3
  147. package/dist/server/mcp.js +106 -56
  148. package/dist/server/page/analyses.d.ts +2 -0
  149. package/dist/server/page/analyses.js +20 -5
  150. package/dist/server/page/attach.d.ts +4 -0
  151. package/dist/server/page/attach.js +112 -0
  152. package/dist/server/page/chat.d.ts +60 -0
  153. package/dist/server/page/chat.js +79 -0
  154. package/dist/server/page/events.d.ts +7 -0
  155. package/dist/server/page/events.js +20 -0
  156. package/dist/server/page/files.js +10 -1
  157. package/dist/server/page/game.js +62 -29
  158. package/dist/server/page/page.d.ts +47 -7
  159. package/dist/server/page/page.js +74 -51
  160. package/dist/server/page/profiles.d.ts +5 -0
  161. package/dist/server/page/profiles.js +26 -10
  162. package/dist/server/page/table.d.ts +39 -0
  163. package/dist/server/page/table.js +102 -0
  164. package/dist/server/table/browser.d.ts +6 -0
  165. package/dist/server/table/browser.js +48 -0
  166. package/dist/server/table/hosts/chats.d.ts +7 -0
  167. package/dist/server/table/hosts/chats.js +31 -0
  168. package/dist/server/table/hosts/find.d.ts +2 -0
  169. package/dist/server/table/hosts/find.js +20 -0
  170. package/dist/server/table/hosts/hosts.d.ts +34 -0
  171. package/dist/server/table/hosts/hosts.js +49 -0
  172. package/dist/server/table/hosts/index.d.ts +3 -0
  173. package/dist/server/table/hosts/index.js +4 -0
  174. package/dist/server/table/hosts/play.d.ts +25 -0
  175. package/dist/server/table/hosts/play.js +152 -0
  176. package/dist/server/table/hosts/seat.d.ts +25 -0
  177. package/dist/server/table/hosts/seat.js +74 -0
  178. package/dist/server/table/hosts/terminal.d.ts +6 -0
  179. package/dist/server/table/hosts/terminal.js +63 -0
  180. package/dist/server/table/icon/index.d.ts +32 -0
  181. package/dist/server/table/icon/index.js +83 -0
  182. package/dist/server/table/icon/linux.d.ts +5 -0
  183. package/dist/server/table/icon/linux.js +47 -0
  184. package/dist/server/table/icon/macos.d.ts +3 -0
  185. package/dist/server/table/icon/macos.js +38 -0
  186. package/dist/server/table/icon/windows.d.ts +3 -0
  187. package/dist/server/table/icon/windows.js +48 -0
  188. package/dist/server/table/index.d.ts +6 -0
  189. package/dist/server/table/index.js +7 -0
  190. package/dist/server/table/launch.d.ts +19 -0
  191. package/dist/server/table/launch.js +78 -0
  192. package/dist/server/table/link.d.ts +10 -0
  193. package/dist/server/table/link.js +230 -0
  194. package/dist/server/table/table.d.ts +35 -0
  195. package/dist/server/table/table.js +94 -0
  196. package/dist/sites/chesscom.d.ts +4 -0
  197. package/dist/sites/chesscom.js +25 -0
  198. package/dist/sites/choice.d.ts +17 -0
  199. package/dist/sites/choice.js +13 -0
  200. package/dist/sites/index.d.ts +2 -0
  201. package/dist/sites/index.js +4 -0
  202. package/dist/sites/lichess.d.ts +4 -0
  203. package/dist/sites/lichess.js +32 -0
  204. package/dist/sites/request.d.ts +11 -0
  205. package/dist/sites/request.js +15 -0
  206. package/dist/sites/sites.d.ts +6 -0
  207. package/dist/sites/sites.js +33 -0
  208. package/instructions/analysis.md +1 -1
  209. package/instructions/analyst-agent.md +1 -1
  210. package/instructions/coach.md +2 -1
  211. package/instructions/opponent.md +6 -6
  212. package/instructions/ponder.md +1 -0
  213. package/instructions/start.md +1 -0
  214. package/package.json +1 -1
package/dist/page/text.js CHANGED
@@ -64,7 +64,7 @@ const english = {
64
64
  noGame: "No game yet: press + to start one, or ask for one in the chat.",
65
65
  foldCard: "Fold the card",
66
66
  openCard: "Open the card",
67
- saveMoves: "Moves-only PGN",
67
+ saveMoves: "PGN",
68
68
  saveMovesHint: "The moves alone, to open on Lichess or chess.com",
69
69
  noGameNow: "No game in progress",
70
70
  analysing: "Analysis in progress",
@@ -103,9 +103,12 @@ const english = {
103
103
  send: "Send",
104
104
  sent: "Sent",
105
105
  withMove: "Or move: your words go with it",
106
+ onMove: "On",
107
+ onWholeGame: "On the whole game",
106
108
  talk: "The talk",
107
109
  move: "Move",
108
110
  draw: "draw",
111
+ aborted: "Game aborted",
109
112
  checkmate: "checkmate",
110
113
  ended: {
111
114
  checkmate: "checkmate",
@@ -129,6 +132,19 @@ const english = {
129
132
  startClocks: "Start",
130
133
  thinking: { move: "Thinking about its move", ponder: "Thinking while you play" },
131
134
  asleep: (name) => `${name} is not listening: write a message, such as "hi", in the window where you opened ${name}, not here. Its clock waits until it answers.`,
135
+ asleepInAnalysis: (name) => `${name} is not listening: write to it in the window where you opened it, not here.`,
136
+ table: {
137
+ label: "Open a chat",
138
+ noChat: (name) => `To play with ${name}, open its chat: it starts in a window of its own, to keep open.`,
139
+ install: (name) => `To play with ${name}, install Claude Code or Codex, then open this page again.`,
140
+ model: "Model",
141
+ effort: "Effort",
142
+ asHost: (host) => `as set in ${host}`,
143
+ opening: "Opening the chat… In the terminal, if it asks whether you trust the folder, choose Yes with the arrow keys and press Enter.",
144
+ again: (name) => `${name} starts again with a clean chat for the new game…`,
145
+ failed: "The terminal did not open: type the command below in a terminal.",
146
+ typeIt: "Or type in a terminal:",
147
+ },
132
148
  commandHere: (name) => `/clear and the other commands go in the window where you opened ${name}, not here.`,
133
149
  refused: {
134
150
  "no-game": () => "There is no game going on: press + to start one, or ask for one in the chat.",
@@ -149,6 +165,8 @@ const english = {
149
165
  piece: (letter, side) => `${side} ${englishNames[letter]}`,
150
166
  taken: (pieces, ahead) => `Pieces taken: ${pieces.length === 0 ? "none" : pieces.join(", ")}${ahead > 0 ? `; ahead by ${ahead}` : ""}`,
151
167
  writeTo: (name) => `Write to ${name}…`,
168
+ askAbout: (name) => `Ask ${name} about this game…`,
169
+ goTo: (move) => `Go to ${move}`,
152
170
  readBy: (name) => `Read by ${name}`,
153
171
  wrote: (name) => `${name} wrote`,
154
172
  offers: "offers a draw",
@@ -182,14 +200,15 @@ const english = {
182
200
  learning: ["I've just learned", "I know the rules, little more"],
183
201
  casual: ["I play now and then", "With friends or on my phone"],
184
202
  regular: ["I play often", "Online or at a club"],
185
- tournament: ["I play tournaments", "I have a FIDE rating"],
203
+ tournament: ["I play tournaments", "At a club, with a rating"],
186
204
  },
187
- rating: "Do you have a rating?",
188
205
  optional: "optional",
189
- ratingPlaceholder: "e.g. 1450",
190
- ratingLabel: "Rating",
191
- siteLabel: "Where",
192
- sites: { "lichess": "on Lichess", "chess.com": "on Chess.com", "fide": "FIDE" },
206
+ privacy: "We only read your public rating. No password.",
207
+ speeds: { rapid: "rapid", classical: "classical", blitz: "blitz" },
208
+ ratingTitle: (speed, played) => (played === undefined ? speed : `${speed} · last game ${played}`),
209
+ notFound: (site) => `${site} has no player of this name`,
210
+ unreached: (site) => `${site} isn't answering: we'll try again later`,
211
+ pending: "Reading your rating…",
193
212
  start: "Start playing",
194
213
  create: "Done",
195
214
  newProfile: "Add a player",
@@ -197,12 +216,17 @@ const english = {
197
216
  eachOwn: "Each player has their own games and settings.",
198
217
  changePlayer: "Change player",
199
218
  back: "Back",
200
- changePicture: "Change picture",
201
- yourPicture: "Your picture: change it",
219
+ yourProfile: "Your profile",
220
+ editProfile: "Edit your profile",
221
+ yourPicture: "Your picture: edit your profile",
202
222
  avatars: { "short-hair": "Short hair", bun: "Bun", glasses: "Glasses", beanie: "Beanie", beard: "Beard", curls: "Curls", moustache: "Moustache", "silver-bob": "Silver bob", cat: "Cat", owl: "Owl" },
203
223
  whilePlaying: "Finish the game, or resign, before changing player.",
224
+ accounts: "Do you play on Lichess, Chess.com or in FIDE tournaments?",
204
225
  username: "your username",
205
226
  accountTitle: "With your name there, a game you load from there knows which side is yours.",
227
+ fidePlaceholder: "your rating",
228
+ fideTitle: "Your FIDE rating, as FIDE lists it: Chesspal cannot read it by itself.",
229
+ fideRange: (lowest, highest) => `A FIDE rating goes from ${lowest} to ${highest}`,
206
230
  badImage: "That picture won't open: try another photo (the ones from a phone usually work).",
207
231
  },
208
232
  start: {
@@ -325,6 +349,33 @@ const english = {
325
349
  pawnSquare: (pawn, caught) => `The enemy king is ${caught ? "inside" : "outside"} the square of the pawn on ${pawn}`,
326
350
  opposition: (side) => `${side === "white" ? "White" : "Black"} has the opposition`,
327
351
  },
352
+ legend: {
353
+ title: "Marks on the board",
354
+ hint: "Marks on the board (?)",
355
+ none: "No marks on the board now",
356
+ badge: "the move's judgment",
357
+ sign: (kind, tone) => `${tone === "threat" ? "Your" : tone === "chance" ? "Opponent's" : "A"} ${{ risk: "piece at risk", pin: "pinned piece", skewer: "skewered piece" }[kind]}`,
358
+ pinLine: "The pin's line",
359
+ skewerLine: "The skewer's line",
360
+ drawnBy: (name) => `drawn by ${name}`,
361
+ drawnByCoach: "drawn by the coach",
362
+ control: "How many of White's pieces and of Black's reach the square",
363
+ ends: { crown: "Won", mate: "Checkmated", flag: "Resigned", half: "Draw, half a point each" },
364
+ },
365
+ guide: {
366
+ title: "Guide",
367
+ contents: "Contents",
368
+ onGitHub: "Chesspal on GitHub",
369
+ onNpm: "Chesspal on npm",
370
+ support: "Support Chesspal",
371
+ credits: "Credits",
372
+ creditLines: [
373
+ "The rules: [chess.js](https://github.com/jhlywa/chess.js), under the BSD 2-Clause license.",
374
+ "The openings' names: Lichess's [chess-openings](https://github.com/lichess-org/chess-openings), under CC0.",
375
+ "The pieces: Nitida, Torretta and Capablanca are Chesspal's own, the last after the diagrams of Capablanca's *Chess Fundamentals* (1921), under CC BY 4.0: [their credits](https://github.com/stefanocaronia/chesspal/blob/main/src/page/pieces/CREDITS.md).",
376
+ "The sounds: Chesspal's own.",
377
+ ],
378
+ },
328
379
  };
329
380
  const italian = {
330
381
  language: "it",
@@ -385,7 +436,7 @@ const italian = {
385
436
  noGame: "Nessuna partita: premi + per iniziarne una, o chiedila nella chat.",
386
437
  foldCard: "Chiudi la scheda",
387
438
  openCard: "Apri la scheda",
388
- saveMoves: "PGN solo mosse",
439
+ saveMoves: "PGN",
389
440
  saveMovesHint: "Solo le mosse, da aprire su Lichess o chess.com",
390
441
  noGameNow: "Nessuna partita in corso",
391
442
  analysing: "Analisi in corso",
@@ -424,9 +475,12 @@ const italian = {
424
475
  send: "Manda",
425
476
  sent: "Mandato",
426
477
  withMove: "Oppure muovi: parte con la mossa",
478
+ onMove: "Su",
479
+ onWholeGame: "Su tutta la partita",
427
480
  talk: "Conversazione",
428
481
  move: "Mossa",
429
482
  draw: "patta",
483
+ aborted: "Partita annullata",
430
484
  checkmate: "scacco matto",
431
485
  ended: {
432
486
  checkmate: "scacco matto",
@@ -450,6 +504,19 @@ const italian = {
450
504
  startClocks: "Inizia",
451
505
  thinking: { move: "Sta pensando alla sua mossa", ponder: "Pensa mentre giochi tu" },
452
506
  asleep: (name) => `${name} non ti sta ascoltando: scrivi un messaggio, per esempio "ciao", nella finestra dove hai aperto ${name}, non qui. Il suo orologio aspetta che risponda.`,
507
+ asleepInAnalysis: (name) => `${name} non ti sta ascoltando: scrivigli nella finestra dove l'hai aperto, non qui.`,
508
+ table: {
509
+ label: "Apri una chat",
510
+ noChat: (name) => `Per giocare con ${name}, apri la sua chat: parte in una finestra a parte, da tenere aperta.`,
511
+ install: (name) => `Per giocare con ${name}, installa Claude Code o Codex, poi apri di nuovo questa pagina.`,
512
+ model: "Modello",
513
+ effort: "Ragionamento",
514
+ asHost: (host) => `come è impostato in ${host}`,
515
+ opening: "Apro la chat… Nel terminale, se ti chiede se ti fidi della cartella, scegli Yes con le frecce e premi Invio.",
516
+ again: (name) => `${name} riparte con una chat pulita per la nuova partita…`,
517
+ failed: "Il terminale non si è aperto: scrivi il comando qui sotto in un terminale.",
518
+ typeIt: "Oppure scrivi in un terminale:",
519
+ },
453
520
  commandHere: (name) => `/clear e gli altri comandi vanno scritti nella finestra dove hai aperto ${name}, non qui.`,
454
521
  refused: {
455
522
  "no-game": () => "Nessuna partita in corso: premi + per iniziarne una, o chiedila nella chat.",
@@ -471,6 +538,8 @@ const italian = {
471
538
  piece: (letter, side) => `${italianNames[letter]} ${side === "white" ? (letter === "Q" || letter === "R" ? "bianca" : "bianco") : (letter === "Q" || letter === "R" ? "nera" : "nero")}`,
472
539
  taken: (pieces, ahead) => `Pezzi presi: ${pieces.length === 0 ? "nessuno" : pieces.join(", ")}${ahead > 0 ? `; avanti di ${ahead}` : ""}`,
473
540
  writeTo: (name) => `Scrivi a ${name}…`,
541
+ askAbout: (name) => `Chiedi ${name === "AI" ? "all'AI" : `a ${name}`} di questa partita…`,
542
+ goTo: (move) => `Vai a ${move}`,
474
543
  readBy: (name) => `Letto da ${name}`,
475
544
  wrote: (name) => `${name} ha scritto`,
476
545
  offers: "propone patta",
@@ -504,14 +573,15 @@ const italian = {
504
573
  learning: ["Ho appena imparato", "Conosco le regole, poco di più"],
505
574
  casual: ["Gioco ogni tanto", "Con amici o sul telefono"],
506
575
  regular: ["Gioco spesso", "Online o al circolo"],
507
- tournament: ["Gioco a torneo", "Ho un punteggio FIDE"],
576
+ tournament: ["Gioco a torneo", "Al circolo, con punteggio"],
508
577
  },
509
- rating: "Hai un punteggio?",
510
578
  optional: "facoltativo",
511
- ratingPlaceholder: "es. 1450",
512
- ratingLabel: "Punteggio",
513
- siteLabel: "Dove",
514
- sites: { "lichess": "su Lichess", "chess.com": "su Chess.com", "fide": "FIDE" },
579
+ privacy: "Leggiamo solo il tuo punteggio pubblico. Niente password.",
580
+ speeds: { rapid: "rapid", classical: "classica", blitz: "blitz" },
581
+ ratingTitle: (speed, played) => (played === undefined ? speed : `${speed} · ultima partita il ${played.split("-").reverse().join("/")}`),
582
+ notFound: (site) => `Su ${site} non trovo questo nome`,
583
+ unreached: (site) => `${site} non risponde: riproviamo più tardi`,
584
+ pending: "Leggo il tuo punteggio…",
515
585
  start: "Inizia a giocare",
516
586
  create: "Fatto",
517
587
  newProfile: "Aggiungi un giocatore",
@@ -519,12 +589,17 @@ const italian = {
519
589
  eachOwn: "Ognuno ha le sue partite e le sue impostazioni.",
520
590
  changePlayer: "Cambia giocatore",
521
591
  back: "Indietro",
522
- changePicture: "Cambia immagine",
523
- yourPicture: "La tua immagine: cambiala",
592
+ yourProfile: "Il tuo profilo",
593
+ editProfile: "Modifica il profilo",
594
+ yourPicture: "La tua immagine: modifica il profilo",
524
595
  avatars: { "short-hair": "Capelli corti", bun: "Chignon", glasses: "Occhiali", beanie: "Berretto", beard: "Barba", curls: "Ricci", moustache: "Baffi", "silver-bob": "Caschetto grigio", cat: "Gatto", owl: "Gufo" },
525
596
  whilePlaying: "Finisci la partita, o abbandona, prima di cambiare giocatore.",
597
+ accounts: "Giochi su Lichess, Chess.com o nei tornei FIDE?",
526
598
  username: "il tuo nome utente",
527
599
  accountTitle: "Col tuo nome lì, una partita che carichi da lì sa qual è la tua parte.",
600
+ fidePlaceholder: "il tuo punteggio",
601
+ fideTitle: "Il tuo punteggio FIDE, come lo trovi sul sito della FIDE: Chesspal non può leggerlo da sé.",
602
+ fideRange: (lowest, highest) => `Un punteggio FIDE va da ${lowest} a ${highest}`,
528
603
  badImage: "Questa immagine non si apre: prova con un'altra foto (quelle del telefono di solito vanno bene).",
529
604
  },
530
605
  start: {
@@ -647,6 +722,33 @@ const italian = {
647
722
  pawnSquare: (pawn, caught) => `Il re nemico è ${caught ? "dentro il" : "fuori dal"} quadrato del pedone in ${pawn}`,
648
723
  opposition: (side) => `L'opposizione è del ${side === "white" ? "Bianco" : "Nero"}`,
649
724
  },
725
+ legend: {
726
+ title: "Segni sulla scacchiera",
727
+ hint: "Segni sulla scacchiera (?)",
728
+ none: "Nessun segno sulla scacchiera ora",
729
+ badge: "il giudizio della mossa",
730
+ sign: (kind, tone) => `${tone === "threat" ? "Tuo pezzo" : tone === "chance" ? "Pezzo avversario" : "Pezzo"} ${{ risk: "in pericolo", pin: "inchiodato", skewer: "infilato" }[kind]}`,
731
+ pinLine: "Linea dell'inchiodatura",
732
+ skewerLine: "Linea dell'infilata",
733
+ drawnBy: (name) => `disegnato da ${name}`,
734
+ drawnByCoach: "disegnato dall'allenatore",
735
+ control: "Quanti pezzi del Bianco e del Nero raggiungono la casa",
736
+ ends: { crown: "Ha vinto", mate: "Scacco matto", flag: "Ha abbandonato", half: "Patta, mezzo punto a testa" },
737
+ },
738
+ guide: {
739
+ title: "Guida",
740
+ contents: "Indice",
741
+ onGitHub: "Chesspal su GitHub",
742
+ onNpm: "Chesspal su npm",
743
+ support: "Sostieni Chesspal",
744
+ credits: "Crediti",
745
+ creditLines: [
746
+ "Le regole: [chess.js](https://github.com/jhlywa/chess.js), con licenza BSD 2-Clause.",
747
+ "I nomi delle aperture: [chess-openings](https://github.com/lichess-org/chess-openings) di Lichess, con licenza CC0.",
748
+ "I pezzi: Nitida, Torretta e Capablanca sono di Chesspal, l'ultimo dai diagrammi di *Chess Fundamentals* di Capablanca (1921), con licenza CC BY 4.0: [i loro crediti](https://github.com/stefanocaronia/chesspal/blob/main/src/page/pieces/CREDITS.md).",
749
+ "I suoni: di Chesspal.",
750
+ ],
751
+ },
650
752
  };
651
753
  // How each piece moves, said when it was taken where it cannot go (Anatolij's words).
652
754
  const englishMoves = {
@@ -33,6 +33,10 @@
33
33
  --help-on: #fff;
34
34
  --help-line: rgba(17, 22, 29, 0.78);
35
35
  --help-halo: rgba(255, 255, 255, 0.85);
36
+ /* The control map's veils (Zoe's variant A): grey over the board, sand where White alone reaches, slate where Black alone does */
37
+ --map-veil: rgba(160, 166, 176, 0.5);
38
+ --map-white: rgba(242, 233, 210, 0.78);
39
+ --map-black: rgba(56, 66, 84, 0.5);
36
40
  --torretta-body: #6d9fd1;
37
41
  --torretta-shade: #4b7cb0;
38
42
  --torretta-screen: #16263a;
@@ -102,6 +106,9 @@
102
106
  --badge-blunder: #d93434;
103
107
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 22px rgba(0, 0, 0, 0.25);
104
108
  --board-shadow: var(--shadow);
109
+ --map-veil: rgba(112, 120, 132, 0.66);
110
+ --map-white: rgba(226, 214, 188, 0.64);
111
+ --map-black: rgba(28, 35, 48, 0.7);
105
112
  color-scheme: dark;
106
113
  }
107
114
 
@@ -1,3 +1,4 @@
1
1
  export { type ImageType, imageTypeOf, imageTypes, ownImage, ownImageStamp, removeOwnImage, saveOwnImage, sentImageLimit } from "./avatar.ts";
2
- export { type Accounts, activateProfile, activeProfile, createProfile, type DrawnAvatar, drawnAvatars, ensureProfile, gameFolder, gameFolders, type Level, levels, listProfiles, nameLimit, type PlayerProfile, type ProfileFields, profileFolder, type Rating, ratingLimits, type RatingSite, ratingSites, updateProfile, } from "./profiles.ts";
2
+ export { type Accounts, activateProfile, activeProfile, createProfile, type DrawnAvatar, drawnAvatars, ensureProfile, fideHighest, fideLowest, gameFolder, gameFolders, type Level, levels, listProfiles, nameLimit, type PlayerProfile, type ProfileFields, profileFolder, updateProfile, } from "./profiles.ts";
3
+ export { type Rating, type RatingSite, ratingSites, saveReadings, type SiteReading, type SiteReadings, type Speed, speeds } from "./ratings.ts";
3
4
  export { helpsIn, languageIn, type PageSettings, profileSettings, saveProfileSettings, settingsOf } from "./settings.ts";
@@ -1,5 +1,6 @@
1
1
  // The player's profiles (D-23): who plays, their games, their picture and their page settings, each in profiles/<id>/ of the data folder (D-71). Below the
2
2
  // commands, beside the games.
3
3
  export { imageTypeOf, imageTypes, ownImage, ownImageStamp, removeOwnImage, saveOwnImage, sentImageLimit } from "./avatar.js";
4
- export { activateProfile, activeProfile, createProfile, drawnAvatars, ensureProfile, gameFolder, gameFolders, levels, listProfiles, nameLimit, profileFolder, ratingLimits, ratingSites, updateProfile, } from "./profiles.js";
4
+ export { activateProfile, activeProfile, createProfile, drawnAvatars, ensureProfile, fideHighest, fideLowest, gameFolder, gameFolders, levels, listProfiles, nameLimit, profileFolder, updateProfile, } from "./profiles.js";
5
+ export { ratingSites, saveReadings, speeds } from "./ratings.js";
5
6
  export { helpsIn, languageIn, profileSettings, saveProfileSettings, settingsOf } from "./settings.js";
@@ -1,30 +1,21 @@
1
+ import { type Rating, type SiteReadings } from "./ratings.ts";
1
2
  /** How the player says they play, from who has just learned to who plays tournaments. */
2
3
  export declare const levels: readonly ["learning", "casual", "regular", "tournament"];
3
4
  export type Level = (typeof levels)[number];
4
- /** Where a rating comes from. */
5
- export declare const ratingSites: readonly ["lichess", "chess.com", "fide"];
6
- export type RatingSite = (typeof ratingSites)[number];
7
- /** The lowest and highest rating a profile takes. */
8
- export declare const ratingLimits: {
9
- readonly lowest: 100;
10
- readonly highest: 3500;
11
- };
12
5
  /** The longest name a profile keeps. */
13
6
  export declare const nameLimit = 40;
14
7
  /** The avatars Zoe drew, for a player without an image of their own (D-45). */
15
8
  export declare const drawnAvatars: readonly ["short-hair", "bun", "glasses", "beanie", "beard", "curls", "moustache", "silver-bob", "cat", "owl"];
16
9
  export type DrawnAvatar = (typeof drawnAvatars)[number];
17
- /** A rating and where it comes from. */
18
- export interface Rating {
19
- readonly value: number;
20
- readonly site: RatingSite;
21
- }
22
- /** What the player tells of themselves: their name, how they play, their rating, the avatar they chose. */
10
+ /** The FIDE ratings Chesspal takes: whole numbers from FIDE's lowest, 1400 since 2024, to 3000. */
11
+ export declare const fideLowest = 1400;
12
+ export declare const fideHighest = 3000;
13
+ /** What the player tells of themselves: their name, how they play, the avatar they chose, their FIDE rating, typed since FIDE has no API (D-106). */
23
14
  export interface ProfileFields {
24
15
  readonly name: string;
25
16
  readonly level?: Level;
26
- readonly rating?: Rating;
27
17
  readonly avatar?: DrawnAvatar;
18
+ readonly fide?: number;
28
19
  }
29
20
  /** The player's names on the sites they play on. */
30
21
  export interface Accounts {
@@ -38,6 +29,10 @@ export interface PlayerProfile extends ProfileFields {
38
29
  readonly new?: true;
39
30
  /** The player's names on Lichess and chess.com, written in profile.md by hand or from the page, by which the games they load are theirs (D-93). */
40
31
  readonly accounts?: Accounts;
32
+ /** What Lichess and chess.com said of those names, the last time Chesspal asked (D-105). */
33
+ readonly sites?: SiteReadings;
34
+ /** The rating that counts, of the names the profile has now: of two, the one the sites are surer of (D-106). */
35
+ readonly rating?: Rating;
41
36
  }
42
37
  /** What may change in a profile: what the player tells of themselves, and their names on the sites, each one written whole. */
43
38
  export type ProfileChanges = Partial<ProfileFields> & {
@@ -4,16 +4,16 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "node:f
4
4
  import { join } from "node:path";
5
5
  import { makeFolder, renameOver, writeAtomically } from "../games/index.js";
6
6
  import { readFrontMatter, writeFrontMatter } from "./front-matter.js";
7
+ import { savedReadings } from "./ratings.js";
7
8
  /** How the player says they play, from who has just learned to who plays tournaments. */
8
9
  export const levels = ["learning", "casual", "regular", "tournament"];
9
- /** Where a rating comes from. */
10
- export const ratingSites = ["lichess", "chess.com", "fide"];
11
- /** The lowest and highest rating a profile takes. */
12
- export const ratingLimits = { lowest: 100, highest: 3500 };
13
10
  /** The longest name a profile keeps. */
14
11
  export const nameLimit = 40;
15
12
  /** The avatars Zoe drew, for a player without an image of their own (D-45). */
16
13
  export const drawnAvatars = ["short-hair", "bun", "glasses", "beanie", "beard", "curls", "moustache", "silver-bob", "cat", "owl"];
14
+ /** The FIDE ratings Chesspal takes: whole numbers from FIDE's lowest, 1400 since 2024, to 3000. */
15
+ export const fideLowest = 1400;
16
+ export const fideHighest = 3000;
17
17
  /** The profiles in the data folder, by their folders' names. */
18
18
  export function listProfiles(root) {
19
19
  return profileIds(root).map((id) => readProfile(root, id));
@@ -47,9 +47,9 @@ export function profileFolder(root, id) {
47
47
  export function createProfile(root, fields, use = true) {
48
48
  const id = activeId(root);
49
49
  if (id !== undefined && readProfile(root, id).new === true) {
50
- const { level, rating, avatar } = fields;
50
+ const { level, avatar, fide } = fields;
51
51
  const named = renamed(root, id, fields.name);
52
- writeProfile(root, named, { name: fields.name, level, rating, avatar, new: false });
52
+ writeProfile(root, named, { name: fields.name, level, avatar, fide, new: false });
53
53
  return readProfile(root, named);
54
54
  }
55
55
  let made = freeId(root, slugOf(fields.name));
@@ -172,22 +172,38 @@ function writeActive(root, id) {
172
172
  }
173
173
  function readProfile(root, id) {
174
174
  const { fields } = readFrontMatter(profileText(root, id));
175
- const value = Number(fields.rating);
176
- const site = oneOf(ratingSites, fields.site);
177
175
  const level = oneOf(levels, fields.level);
178
176
  const avatar = oneOf(drawnAvatars, fields.avatar);
179
- const rated = Number.isInteger(value) && value >= ratingLimits.lowest && value <= ratingLimits.highest && site !== undefined;
177
+ const fide = fideIn(fields.fide);
178
+ const { accounts } = accountsIn(fields);
179
+ const sites = savedReadings(profileFolder(root, id));
180
+ const rating = sites === undefined ? undefined : surest([ofAccount(sites.lichess, accounts?.lichess), ofAccount(sites.chesscom, accounts?.chesscom)]);
180
181
  // A name written by hand is cut as the page and the CLI cut it: the AI reads it.
181
182
  return {
182
183
  id,
183
184
  name: fields.name?.trim().slice(0, nameLimit).trim() || id,
184
185
  ...(level === undefined ? {} : { level }),
185
- ...(rated ? { rating: { value, site } } : {}),
186
186
  ...(avatar === undefined ? {} : { avatar }),
187
+ ...(fide === undefined ? {} : { fide }),
187
188
  ...(fields.new === "true" ? { new: true } : {}),
188
- ...accountsIn(fields),
189
+ ...(accounts === undefined ? {} : { accounts }),
190
+ ...(sites === undefined ? {} : { sites }),
191
+ ...(rating === undefined ? {} : { rating }),
189
192
  };
190
193
  }
194
+ /** A site's rating, when it was read for the name the profile has there now. */
195
+ function ofAccount(reading, account) {
196
+ return account !== undefined && reading?.account.toLowerCase() === account.toLowerCase() ? reading.rating : undefined;
197
+ }
198
+ /** Of the ratings, the one its site is surest of: the lowest RD (D-106). */
199
+ function surest(ratings) {
200
+ return ratings.reduce((best, one) => (one !== undefined && (best === undefined || one.deviation < best.deviation) ? one : best), undefined);
201
+ }
202
+ /** The FIDE rating of profile.md's fide line: none when it is not a whole number in FIDE's range, as one written by hand may not be. */
203
+ function fideIn(text) {
204
+ const fide = Number(text?.trim());
205
+ return Number.isInteger(fide) && fide >= fideLowest && fide <= fideHighest ? fide : undefined;
206
+ }
191
207
  /** The player's names on the sites, from profile.md's lichess and chesscom lines; none without either. */
192
208
  function accountsIn(fields) {
193
209
  const named = (site) => fields[site]?.trim().slice(0, nameLimit).trim() || undefined;
@@ -208,13 +224,12 @@ function writeProfile(root, id, changes) {
208
224
  if ("level" in changes) {
209
225
  written.level = changes.level;
210
226
  }
211
- if ("rating" in changes) {
212
- written.rating = changes.rating?.value;
213
- written.site = changes.rating?.site;
214
- }
215
227
  if ("avatar" in changes) {
216
228
  written.avatar = changes.avatar;
217
229
  }
230
+ if ("fide" in changes) {
231
+ written.fide = changes.fide;
232
+ }
218
233
  if ("accounts" in changes) {
219
234
  written.lichess = changes.accounts?.lichess;
220
235
  written.chesscom = changes.accounts?.chesscom;
@@ -0,0 +1,35 @@
1
+ /** The sites a rating is read from, by the player's names there (D-105). */
2
+ export declare const ratingSites: readonly ["lichess", "chess.com"];
3
+ export type RatingSite = (typeof ratingSites)[number];
4
+ /** The speeds whose rating counts, in the order they count: the games with the AI are like a slow rapid (D-106). */
5
+ export declare const speeds: readonly ["rapid", "classical", "blitz"];
6
+ export type Speed = (typeof speeds)[number];
7
+ /** A rating a site gives: the number as it is, never converted, its speed, how sure the site is of it (its rating deviation, RD), and the day of the last game at that speed. */
8
+ export interface Rating {
9
+ readonly value: number;
10
+ readonly site: RatingSite;
11
+ readonly speed: Speed;
12
+ readonly deviation: number;
13
+ /** YYYY-MM-DD. */
14
+ readonly played?: string;
15
+ }
16
+ /** What a site said of the player's name there, the last time Chesspal asked. */
17
+ export interface SiteReading {
18
+ readonly account: string;
19
+ /** False when the site has no player of that name; unknown while it has never answered. */
20
+ readonly found?: boolean;
21
+ /** The rating that counts there: none when the player has no established rating at a speed that counts. */
22
+ readonly rating?: Rating;
23
+ /** The last try did not reach the site: what it said before stays. */
24
+ readonly unreached?: true;
25
+ }
26
+ /** What the sites said, and when Chesspal last asked them. */
27
+ export interface SiteReadings {
28
+ readonly read: string;
29
+ readonly lichess?: SiteReading;
30
+ readonly chesscom?: SiteReading;
31
+ }
32
+ /** The readings kept in a profile's folder; undefined when it has none, or they cannot be read. */
33
+ export declare function savedReadings(folder: string): SiteReadings | undefined;
34
+ /** Keeps the readings in a profile's folder, over the ones before; none removes them. */
35
+ export declare function saveReadings(folder: string, readings: SiteReadings | undefined): void;
@@ -0,0 +1,61 @@
1
+ // What Lichess and Chess.com said of the player's names there (D-105, D-106): ratings.json in the profile's folder, written by Chesspal each time it reads
2
+ // them, never by hand; the rating that counts is chosen from it as the profile is read.
3
+ import { readFileSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { writeAtomically } from "../games/index.js";
6
+ /** The sites a rating is read from, by the player's names there (D-105). */
7
+ export const ratingSites = ["lichess", "chess.com"];
8
+ /** The speeds whose rating counts, in the order they count: the games with the AI are like a slow rapid (D-106). */
9
+ export const speeds = ["rapid", "classical", "blitz"];
10
+ /** The readings kept in a profile's folder; undefined when it has none, or they cannot be read. */
11
+ export function savedReadings(folder) {
12
+ try {
13
+ return readingsOf(JSON.parse(readFileSync(join(folder, fileName), "utf8")));
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ /** Keeps the readings in a profile's folder, over the ones before; none removes them. */
20
+ export function saveReadings(folder, readings) {
21
+ if (readings === undefined) {
22
+ rmSync(join(folder, fileName), { force: true });
23
+ }
24
+ else {
25
+ writeAtomically(join(folder, fileName), `${JSON.stringify(readings, null, 2)}\n`);
26
+ }
27
+ }
28
+ const fileName = "ratings.json";
29
+ // Readings as the file has them, each part checked: a file edited by hand never gives the AI a rating that is not one.
30
+ function readingsOf(value) {
31
+ const file = value;
32
+ if (typeof file?.read !== "string" || Number.isNaN(Date.parse(file.read))) {
33
+ return undefined;
34
+ }
35
+ const lichess = readingOf(file.lichess, "lichess");
36
+ const chesscom = readingOf(file.chesscom, "chess.com");
37
+ return { read: file.read, ...(lichess === undefined ? {} : { lichess }), ...(chesscom === undefined ? {} : { chesscom }) };
38
+ }
39
+ function readingOf(value, site) {
40
+ const reading = value;
41
+ if (typeof reading?.account !== "string" || reading.account === "") {
42
+ return undefined;
43
+ }
44
+ const rating = ratingOf(reading.rating, site);
45
+ return {
46
+ account: reading.account,
47
+ ...(typeof reading.found === "boolean" ? { found: reading.found } : {}),
48
+ ...(rating === undefined ? {} : { rating }),
49
+ ...(reading.unreached === true ? { unreached: true } : {}),
50
+ };
51
+ }
52
+ function ratingOf(value, site) {
53
+ const rating = value;
54
+ const speed = speeds.find((one) => one === rating?.speed);
55
+ const plausible = (number) => typeof number === "number" && Number.isInteger(number) && number > 0 && number < 4000;
56
+ if (rating?.site !== site || speed === undefined || !plausible(rating.value) || !plausible(rating.deviation)) {
57
+ return undefined;
58
+ }
59
+ const played = typeof rating.played === "string" && /^\d{4}-\d{2}-\d{2}$/.test(rating.played) ? rating.played : undefined;
60
+ return { value: rating.value, site, speed, deviation: rating.deviation, ...(played === undefined ? {} : { played }) };
61
+ }
@@ -29,16 +29,32 @@ export declare class Bell {
29
29
  ring(): void;
30
30
  /** A hook waits for the bell, so the chat's turn has ended; the hook before it, of a turn already ended, is let go. */
31
31
  listen(request: IncomingMessage, response: ServerResponse): void;
32
+ /** The chat left the table (D-104): its process ended, and nobody is in a turn or waits for the bell; a ring no hook heard is no longer news for anyone. */
33
+ chatLeft(): void;
32
34
  private tellIf;
33
35
  }
34
- /** Whose page answers at a port, for the hook to find its own: the chat's session, the player's folder, and whether a browser has the page open. */
35
- export declare function bellProbe(folder: string, open?: boolean): BellProbe;
36
+ /**
37
+ * Whose page answers at a port, for the hook to find its own: the chat's session, the player's folder, whether a browser has the page open, Chesspal's
38
+ * version, and whether the table serves it (D-104), with the session and the folder of the chat attached to it, never its own process's.
39
+ */
40
+ export declare function bellProbe(folder: string, open?: boolean, table?: TableChat): BellProbe;
41
+ /** The chat attached to the table, as its probe says it: its session, null for none or for Codex, and its folder; neither while no chat is attached. */
42
+ export interface TableChat {
43
+ session: string | null;
44
+ chat?: string;
45
+ }
36
46
  /** What a page says of itself at `/api/bell?probe`. */
37
47
  export interface BellProbe {
38
48
  session: string | null;
39
49
  folder: string;
40
50
  open: boolean;
51
+ version?: string;
52
+ table?: true;
53
+ /** The folder of the chat attached to the table. */
54
+ chat?: string;
41
55
  }
56
+ /** What the page at a port of this computer says of itself; undefined when none answers there. */
57
+ export declare function pageAt(port: number): Promise<BellProbe | undefined>;
42
58
  /** The ports a folder's page tries first: one in each of a few ranges, by its path, since Windows may reserve a range. The hook looks for it there. */
43
59
  export declare function pagePorts(folder: string): number[];
44
60
  /** What Codex's Stop hook answers to wake the chat: its turn goes on with the doorbell's line. */
@@ -62,7 +78,12 @@ export type BellHeard = {
62
78
  rang: false;
63
79
  why: string;
64
80
  };
65
- /** Waits at the page of the hook's chat, as waitForBell does, and says what it heard. */
81
+ /**
82
+ * Waits at the page of the hook's chat, as waitForBell does, and says what it heard. The chat may be attached to the table (D-104), beside another chat
83
+ * of its folder that serves its own page: the table is one more page to choose from, unless the ports to try are given (Priya).
84
+ */
66
85
  export declare function bellHeard(input: string, ports?: number[]): Promise<BellHeard>;
67
86
  /** Adds a line to the log of the hooks that heard no bell, keeping its last lines; a log that cannot be written never stops the hook. */
68
87
  export declare function noteSilence(folder: string, why: string, now?: Date): void;
88
+ /** Whether two paths name the same folder: without regard to case on Windows. */
89
+ export declare function samePath(one: string, other: string): boolean;