chesspal 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -10
- package/dist/commands/analysis/analyse.d.ts +4 -1
- package/dist/commands/analysis/analyse.js +46 -12
- package/dist/commands/analysis/index.d.ts +1 -0
- package/dist/commands/analysis/index.js +2 -1
- package/dist/commands/analysis/site-games.d.ts +27 -0
- package/dist/commands/analysis/site-games.js +84 -0
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +3 -2
- package/dist/commands/profiles/index.d.ts +1 -1
- package/dist/commands/profiles/index.js +1 -1
- package/dist/commands/profiles/profiles.d.ts +3 -1
- package/dist/commands/profiles/profiles.js +2 -1
- package/dist/commands/registry/context.d.ts +4 -1
- package/dist/commands/registry/registry.d.ts +2 -0
- package/dist/core/accounts.d.ts +11 -0
- package/dist/core/accounts.js +14 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/page/app/app.js +10 -9
- package/dist/page/dialogs/analyses.js +7 -17
- package/dist/page/dialogs/dialogs.css +102 -0
- package/dist/page/dialogs/outcome.js +13 -0
- package/dist/page/dialogs/site-games.js +71 -0
- package/dist/page/dialogs/studied.js +13 -19
- package/dist/page/dialogs/study.js +30 -13
- package/dist/page/guide/en.md +1 -0
- package/dist/page/guide/it.md +1 -0
- package/dist/page/helps/helps.css +26 -18
- package/dist/page/helps/helps.js +2 -2
- package/dist/page/text.js +32 -2
- package/dist/server/mcp/mcp.js +2 -2
- package/dist/server/page/analyses.d.ts +5 -0
- package/dist/server/page/analyses.js +18 -1
- package/dist/server/page/game.js +3 -3
- package/dist/server/page/page.js +4 -1
- package/dist/sites/chesscom.d.ts +1 -1
- package/dist/sites/chesscom.js +1 -1
- package/dist/sites/games/chesscom.d.ts +4 -0
- package/dist/sites/games/chesscom.js +54 -0
- package/dist/sites/games/game.d.ts +34 -0
- package/dist/sites/games/game.js +64 -0
- package/dist/sites/games/games.d.ts +14 -0
- package/dist/sites/games/games.js +22 -0
- package/dist/sites/games/index.d.ts +2 -0
- package/dist/sites/games/index.js +3 -0
- package/dist/sites/games/lichess.d.ts +4 -0
- package/dist/sites/games/lichess.js +45 -0
- package/dist/sites/index.d.ts +2 -1
- package/dist/sites/index.js +5 -3
- package/dist/sites/lichess.d.ts +1 -1
- package/dist/sites/lichess.js +1 -1
- package/dist/sites/request/index.d.ts +1 -0
- package/dist/sites/request/index.js +2 -0
- package/dist/sites/{request.d.ts → request/request.d.ts} +7 -0
- package/dist/sites/request/request.js +37 -0
- package/dist/sites/sites.d.ts +1 -1
- package/dist/sites/sites.js +1 -1
- package/instructions/opponent.md +1 -1
- package/package.json +1 -1
- package/dist/sites/request.js +0 -15
package/README.md
CHANGED
|
@@ -6,21 +6,60 @@ Play chess against an AI on a real board, or learn with one.
|
|
|
6
6
|
|
|
7
7
|
## Get started
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Five steps, done once, in about 15 minutes. You need a paid Claude plan or a ChatGPT account. After these steps you open the board with a double click.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**1. Install Node.js.** Download it from [nodejs.org](https://nodejs.org): take the version marked **LTS**, the steady one (22.13 or newer). Install it like any program, keeping every choice as it is; on Windows, leave the box "Automatically install the necessary tools" unticked.
|
|
12
|
+
|
|
13
|
+
**2. Open a terminal**, the window where you type commands. Open it after installing Node.js: one opened before does not find it.
|
|
14
|
+
|
|
15
|
+
- **Windows:** press Start, type `cmd` and press Enter. This opens Command Prompt. Use it rather than PowerShell: on a new computer PowerShell refuses these commands with "running scripts is disabled on this system".
|
|
16
|
+
- **macOS:** press ⌘ and Space, type `Terminal` and press Enter.
|
|
17
|
+
|
|
18
|
+
In the steps below, copy each command, paste it in the terminal (Ctrl+V or right-click; on macOS ⌘V) and press Enter. Wait for the terminal to finish before the next one. Lines with "WARN" are normal: go on.
|
|
19
|
+
|
|
20
|
+
**3. Install the AI and sign in.** Choose one of the two. You need your own subscription: Chesspal never uses an API key, which is paid by the call.
|
|
21
|
+
|
|
22
|
+
- **Claude Code**, with a paid Claude plan (Pro or higher; the free plan does not include it):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @anthropic-ai/claude-code
|
|
26
|
+
claude
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`claude` asks a few questions first, such as the colours you prefer: press Enter to keep the first choice. Then it opens your browser: sign in with your Claude account and go back to the terminal.
|
|
30
|
+
- **Codex**, with a ChatGPT account:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g @openai/codex
|
|
34
|
+
codex
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Choose **Sign in with ChatGPT** and sign in in your browser.
|
|
38
|
+
|
|
39
|
+
Once you are signed in, close that terminal window with its × and open a new one, as in step 2.
|
|
40
|
+
|
|
41
|
+
**4. Install Chesspal and open it.**
|
|
12
42
|
|
|
13
43
|
```bash
|
|
14
44
|
npm install -g chesspal
|
|
15
45
|
chesspal
|
|
16
46
|
```
|
|
17
47
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
48
|
+
The board page opens in your browser, and you can close this terminal. The first time, Chesspal offers an icon on your desktop and in your menu of programs: accept it, and from then on you open the board with a double click on it, with no terminal.
|
|
49
|
+
|
|
50
|
+
**5. Start the AI.** The first time, the board page asks who you are: fill it in and press **Start playing**. Then press **Claude Code** or **Codex**: the AI opens in a terminal of its own. Keep that window open while you play. The first time it asks some questions:
|
|
51
|
+
|
|
52
|
+
- whether you trust the folder: choose **Yes** with the arrow keys and press Enter;
|
|
53
|
+
- with Codex, about Chesspal's two hooks, the small programs that tell the AI when you move on the board: choose **Trust all and continue**;
|
|
54
|
+
- with Codex on Windows, whether to set up its sandbox, a safe space where it works: choose the first option, and answer yes when Windows asks for permission.
|
|
55
|
+
|
|
56
|
+
Now press **New game**: choose your colour, the clock and the AI's level, and play on the board. Talk with the AI on the board page or in its terminal.
|
|
57
|
+
|
|
58
|
+
**If a command fails:**
|
|
22
59
|
|
|
23
|
-
|
|
60
|
+
- "npm is not recognized" or "command not found": close the terminal and open a new one; if it still fails, install Node.js again.
|
|
61
|
+
- On macOS, "EACCES" or "permission denied": npm cannot write where Node.js put it. Follow [npm's guide](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally), not `sudo`: with `sudo`, Chesspal and Claude Code cannot update themselves.
|
|
62
|
+
- Anything else in red, or with "ERR!": copy the last lines and [tell us](#try-it-and-tell-us).
|
|
24
63
|
|
|
25
64
|
## What it does
|
|
26
65
|
|
|
@@ -36,11 +75,11 @@ Coming later: an AI coach that helps you grow, exercises chosen on your weak poi
|
|
|
36
75
|
|
|
37
76
|
## What you need
|
|
38
77
|
|
|
39
|
-
- **Node.js 22.13** or newer
|
|
40
|
-
- **An AI agent** with your own subscription: Claude Code, with a paid Claude plan, or Codex, with a ChatGPT account.
|
|
78
|
+
- **Node.js 22.13** or newer.
|
|
79
|
+
- **An AI agent** with your own subscription: [Claude Code](https://code.claude.com/docs/en/setup), with a paid Claude plan, or [Codex](https://developers.openai.com/codex), with a ChatGPT account.
|
|
41
80
|
- **A web browser**, for the board page.
|
|
42
81
|
|
|
43
|
-
Chesspal works with any agent that
|
|
82
|
+
Chesspal works with any agent that can use Chesspal's tools (MCP) and the files on your computer: we develop it with Claude Code, and Codex is connected too.
|
|
44
83
|
|
|
45
84
|
## Good to know
|
|
46
85
|
|
|
@@ -63,7 +63,10 @@ interface Analysis {
|
|
|
63
63
|
/** The AI's word on the whole game, from annotate_game. */
|
|
64
64
|
review?: string;
|
|
65
65
|
}
|
|
66
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* A game over move by move, for the analyst (D-68): the current one, one of the archive or kept to study, one the player brings as a PGN, or one of their latest
|
|
68
|
+
* on the sites (D-110), kept to study.
|
|
69
|
+
*/
|
|
67
70
|
export declare const analyseGame: import("../registry/registry.ts").Command<z.ZodObject<{
|
|
68
71
|
game: z.ZodOptional<z.ZodString>;
|
|
69
72
|
pgn: z.ZodOptional<z.ZodString>;
|
|
@@ -1,37 +1,53 @@
|
|
|
1
1
|
// The analyst's tool (D-21, D-68): a game over, move by move in one call, facts only and never a judgment, with the analyst's instructions once in a chat.
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { clockOfComment, clockText, closingOf, Game, gameReviewOf, isReviewed, leadOf, leadText, levelOf, materialOf, readPgn, reviewed, reviewOf, timeControlOfTag, timeControlText, withGameReview, withLead, } from "../../core/index.js";
|
|
3
|
+
import { accountSide, clockOfComment, clockText, closingOf, Game, gameReviewOf, isReviewed, leadOf, leadText, levelOf, materialOf, readPgn, reviewed, reviewOf, timeControlOfTag, timeControlText, withGameReview, withLead, } from "../../core/index.js";
|
|
4
4
|
import { gameInAnalysis, isGameName, keepToStudy, lastChatStart, planOf, rewriteGame, startAnalysis } from "../../games/index.js";
|
|
5
5
|
import { analystAgent, instructionsFile } from "../../host-files/index.js";
|
|
6
6
|
import { activeProfile, gameFolder } from "../../profiles/index.js";
|
|
7
|
+
import { pointsAtSite, siteGameName, siteOfGameName } from "../../sites/index.js";
|
|
7
8
|
import { analystOf, anotherAnalyst, balanceText, bookOpening, bookReaches, colourName, gameToStudy, knownTag, moveNumbers, namedGame, noGameGoingOn, riskLine, speechLine, speechOf, tacticsText } from "../answers/index.js";
|
|
8
|
-
import { playerLine } from "../profiles/index.js";
|
|
9
|
+
import { playerLine, siteText } from "../profiles/index.js";
|
|
9
10
|
import { defineCommand } from "../registry/index.js";
|
|
10
|
-
|
|
11
|
+
import { profileSiteGames } from "./site-games.js";
|
|
12
|
+
/**
|
|
13
|
+
* A game over move by move, for the analyst (D-68): the current one, one of the archive or kept to study, one the player brings as a PGN, or one of their latest
|
|
14
|
+
* on the sites (D-110), kept to study.
|
|
15
|
+
*/
|
|
11
16
|
export const analyseGame = defineCommand({
|
|
12
17
|
name: "analyse_game",
|
|
13
18
|
description: "Gives a game that is over move by move, to comment it with the player: each move with what it took and the time it took, the material "
|
|
14
19
|
+ "when it changes, the pieces at risk, the only legal move when there is one alone, the mates in one and what the next moves would do after it (forks, double attacks, discovered checks, "
|
|
15
20
|
+ "promotions, stalemates and stalemate traps), where the game left the opening book, and your comments and plans. Facts "
|
|
16
|
-
+ "only: the judgment is yours. Without game or pgn, the game just over, or else the last one played; game: a name as past_games or
|
|
17
|
-
+ "writes it; pgn: a game the player brings from elsewhere, kept to study in the profile. Never while a game goes on. The first time in a chat "
|
|
21
|
+
+ "only: the judgment is yours. Without game or pgn, the game just over, or else the last one played; game: a name as past_games, study_games or "
|
|
22
|
+
+ "site_games writes it; pgn: a game the player brings from elsewhere, kept to study in the profile. Never while a game goes on. The first time in a chat "
|
|
18
23
|
+ "it also says how to analyse. annotate_game then keeps your badges and notes for the board page. Example: analyse_game {}.",
|
|
19
24
|
input: z.object({
|
|
20
|
-
game: z.string().optional().describe("a game's name, as past_games, study_games or analyse_game writes it"),
|
|
25
|
+
game: z.string().optional().describe("a game's name, as past_games, study_games, site_games or analyse_game writes it"),
|
|
21
26
|
pgn: z.string().optional().describe("a game played elsewhere, in PGN: its tags and its moves, with their [%clk] and a review exported from the board page if it has them; other comments and evals can go"),
|
|
22
27
|
}),
|
|
23
28
|
sharedWith: [analystAgent],
|
|
24
|
-
|
|
29
|
+
openWorld: true,
|
|
30
|
+
run: async ({ game, pgn }, context) => {
|
|
25
31
|
if (game !== undefined && pgn !== undefined) {
|
|
26
32
|
throw new Error("Give game or pgn, not both: game names a game of the archive, pgn brings one played elsewhere.");
|
|
27
33
|
}
|
|
34
|
+
const given = game?.trim();
|
|
35
|
+
// A link, or a name of a site's game that site_games does not write, is no game Chesspal can find (D-110).
|
|
36
|
+
if ([given, pgn].some((text) => text !== undefined && pointsAtSite(text))) {
|
|
37
|
+
throw new Error("Chesspal takes no link: site_games lists the player's latest games on Lichess and Chess.com; give the name it writes, as game.");
|
|
38
|
+
}
|
|
28
39
|
// A game's name given as pgn, as an AI often gives it, is that game (Priya).
|
|
29
|
-
const
|
|
30
|
-
const
|
|
40
|
+
const fromSite = siteGameName(given ?? pgn ?? "");
|
|
41
|
+
const named = pgn !== undefined && isGameName(pgn) ? pgn.trim() : given;
|
|
42
|
+
const studied = fromSite !== undefined
|
|
43
|
+
? importedGame(context, await siteGamePgn(context, fromSite))
|
|
44
|
+
: pgn === undefined || named !== given
|
|
45
|
+
? gameToStudy(context, named)
|
|
46
|
+
: importedGame(context, pgn);
|
|
31
47
|
if (context.agent === analystAgent) {
|
|
32
48
|
oneAnalyst(context, studied);
|
|
33
49
|
}
|
|
34
|
-
const analysis = analysisOf(studied, activeProfile(context.home())
|
|
50
|
+
const analysis = analysisOf(studied, activeProfile(context.home()));
|
|
35
51
|
if (studied.name !== undefined) {
|
|
36
52
|
markBook(context, { record: studied.record, name: studied.name }, analysis.moves);
|
|
37
53
|
startAnalysis(gameFolder(context.home()), studied.name, context.now());
|
|
@@ -111,6 +127,23 @@ function importedGame(context, pgn) {
|
|
|
111
127
|
// A game brought again is the one already kept, with the review written on it since.
|
|
112
128
|
return namedGame(context, keepToStudy(gameFolder(context.home()), record, context.now()));
|
|
113
129
|
}
|
|
130
|
+
/** The PGN of one of the player's latest games on the sites, asked of them again by its name as site_games writes it (D-110). */
|
|
131
|
+
async function siteGamePgn(context, name) {
|
|
132
|
+
noGameGoingOn(context);
|
|
133
|
+
const site = siteOfGameName(name);
|
|
134
|
+
const profile = activeProfile(context.home()).id;
|
|
135
|
+
const { games, unreached } = await profileSiteGames(context, site);
|
|
136
|
+
if (activeProfile(context.home()).id !== profile) {
|
|
137
|
+
throw new Error("The profile in use changed while the site answered: call site_games again for the player's games now. Nothing changed.");
|
|
138
|
+
}
|
|
139
|
+
const found = games.find((one) => one.name === name);
|
|
140
|
+
if (found !== undefined) {
|
|
141
|
+
return found.pgn;
|
|
142
|
+
}
|
|
143
|
+
throw new Error(unreached.includes(site)
|
|
144
|
+
? `${siteText[site]} could not be reached: tell the player, and ask again when they ask. Nothing changed.`
|
|
145
|
+
: `${name} is not among the player's latest games on ${siteText[site]} any more: call site_games again and give a name it writes. Nothing changed.`);
|
|
146
|
+
}
|
|
114
147
|
/**
|
|
115
148
|
* The opening book's moves marked as such in the game kept (D-69): a fact, not a judgment, so Chesspal puts the badge itself on each move from the start to the
|
|
116
149
|
* last one the book knows, where the AI gave none; a game from another position has none.
|
|
@@ -132,7 +165,7 @@ function bookPliesOfFacts(record, moves) {
|
|
|
132
165
|
const firstOut = moves.findIndex((move) => move.outOfBook);
|
|
133
166
|
return record.tags.FEN !== undefined ? 0 : firstOut >= 0 ? firstOut : moves.length;
|
|
134
167
|
}
|
|
135
|
-
function analysisOf({ record, name },
|
|
168
|
+
function analysisOf({ record, name }, profile) {
|
|
136
169
|
const { tags } = record;
|
|
137
170
|
const ai = sideNamed(record, "AI");
|
|
138
171
|
const control = timeControlOfTag(tags.TimeControl ?? "");
|
|
@@ -145,7 +178,8 @@ function analysisOf({ record, name }, playerName) {
|
|
|
145
178
|
black: tags.Black ?? "?",
|
|
146
179
|
ai,
|
|
147
180
|
level: ai === undefined ? undefined : levelOf(record),
|
|
148
|
-
player
|
|
181
|
+
// The player by their name, or by their names on the sites (D-93).
|
|
182
|
+
player: ai === undefined ? sideNamed(record, profile.name) ?? accountSide(record.tags, profile.accounts) : undefined,
|
|
149
183
|
date: knownTag(tags.Date),
|
|
150
184
|
timeControl: control === undefined ? knownTag(tags.TimeControl) : timeControlText(control),
|
|
151
185
|
result: ended?.result,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { analyseGame } from "./analyse.ts";
|
|
2
2
|
export { annotateGame } from "./annotate.ts";
|
|
3
3
|
export { deleteReview } from "./delete-review.ts";
|
|
4
|
+
export { type ListedGame, type ListedGames, siteGames, SiteGamesRefused } from "./site-games.ts";
|
|
4
5
|
export { studyGames } from "./study-games.ts";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// The commands on a game over or kept to study: the analyst's analysis and review, the games kept and the reviews deleted.
|
|
1
|
+
// The commands on a game over or kept to study: the analyst's analysis and review, the games kept, the latest on the sites, and the reviews deleted.
|
|
2
2
|
export { analyseGame } from "./analyse.js";
|
|
3
3
|
export { annotateGame } from "./annotate.js";
|
|
4
4
|
export { deleteReview } from "./delete-review.js";
|
|
5
|
+
export { siteGames, SiteGamesRefused } from "./site-games.js";
|
|
5
6
|
export { studyGames } from "./study-games.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type Side } from "../../core/index.ts";
|
|
3
|
+
import { type RatingSite } from "../../profiles/index.ts";
|
|
4
|
+
import { type SiteGame, type SiteGames } from "../../sites/index.ts";
|
|
5
|
+
import { type Context } from "../registry/index.ts";
|
|
6
|
+
/** A game of the list, with the side the player had when one of their names on the sites is on one side alone (D-93). */
|
|
7
|
+
export interface ListedGame extends SiteGame {
|
|
8
|
+
readonly player?: Side;
|
|
9
|
+
}
|
|
10
|
+
/** The latest games on the sites, with the player's side in each. */
|
|
11
|
+
export interface ListedGames extends Omit<SiteGames, "games"> {
|
|
12
|
+
readonly games: readonly ListedGame[];
|
|
13
|
+
}
|
|
14
|
+
/** Why the list is refused, for the page to say it in the player's words: a game going on, no name on the sites, or no network to ask them. */
|
|
15
|
+
export declare class SiteGamesRefused extends Error {
|
|
16
|
+
readonly refusal: "game-going" | "no-accounts" | "no-network";
|
|
17
|
+
constructor(refusal: SiteGamesRefused["refusal"], message: string);
|
|
18
|
+
}
|
|
19
|
+
/** The latest games on the sites, one line each without their PGN, the latest first; the page gets the PGNs too. */
|
|
20
|
+
export declare const siteGames: import("../registry/registry.ts").Command<z.ZodObject<{}, z.core.$strip>, ListedGames>;
|
|
21
|
+
/**
|
|
22
|
+
* The latest games on the sites of the profile in use, or on one of them alone, each with the player's side; refused when the profile names no account
|
|
23
|
+
* there, or when Chesspal cannot ask the network.
|
|
24
|
+
*/
|
|
25
|
+
export declare function profileSiteGames(context: Context, only?: RatingSite): Promise<ListedGames>;
|
|
26
|
+
/** Refused while a game goes on, as every analysis is, with the page's code. */
|
|
27
|
+
export declare function refusedWhilePlaying(context: Context): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// The player's latest games on Lichess and Chess.com (D-110), from the names in the profile in use, to study one: asked anew each time, never kept.
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { accountSide } from "../../core/index.js";
|
|
4
|
+
import { activeProfile } from "../../profiles/index.js";
|
|
5
|
+
import { readSiteGames } from "../../sites/index.js";
|
|
6
|
+
import { noGameGoingOn } from "../answers/index.js";
|
|
7
|
+
import { siteText } from "../profiles/index.js";
|
|
8
|
+
import { defineCommand } from "../registry/index.js";
|
|
9
|
+
/** Why the list is refused, for the page to say it in the player's words: a game going on, no name on the sites, or no network to ask them. */
|
|
10
|
+
export class SiteGamesRefused extends Error {
|
|
11
|
+
refusal;
|
|
12
|
+
constructor(refusal, message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.refusal = refusal;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** The latest games on the sites, one line each without their PGN, the latest first; the page gets the PGNs too. */
|
|
18
|
+
export const siteGames = defineCommand({
|
|
19
|
+
name: "site_games",
|
|
20
|
+
description: "Lists the player's latest finished games on Lichess and Chess.com, from their names there in the profile, the latest first: the side "
|
|
21
|
+
+ "they had and the opponent, with the ratings, how it went, the speed, the day, and the game's name. analyse_game takes that name as game, and "
|
|
22
|
+
+ "keeps the game to study. Never while a game goes on. Example: site_games {}.",
|
|
23
|
+
input: z.object({}),
|
|
24
|
+
readOnly: true,
|
|
25
|
+
openWorld: true,
|
|
26
|
+
run: async (input, context) => {
|
|
27
|
+
refusedWhilePlaying(context);
|
|
28
|
+
return profileSiteGames(context);
|
|
29
|
+
},
|
|
30
|
+
format: ({ games, answered, unreached, unknown }) => {
|
|
31
|
+
const none = games.length === 0 && answered.length > 0 ? ["No finished game of standard chess on the sites."] : [];
|
|
32
|
+
const missed = [
|
|
33
|
+
...(unreached.length === 0 ? [] : [`${sitesText(unreached)} could not be reached: tell the player, and ask again when they ask.`]),
|
|
34
|
+
...(unknown.length === 0 ? [] : [`${sitesText(unknown)} ${unknown.length === 1 ? "has" : "have"} no player by the profile's name there: the player can correct it in the profile.`]),
|
|
35
|
+
];
|
|
36
|
+
const next = games.length === 0 ? [] : [`To go over one, analyse_game with game set to its name, such as {"game": "${games[0].name}"}.`];
|
|
37
|
+
return [...games.map(gameLine), ...none, ...missed, ...next].join("\n");
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* The latest games on the sites of the profile in use, or on one of them alone, each with the player's side; refused when the profile names no account
|
|
42
|
+
* there, or when Chesspal cannot ask the network.
|
|
43
|
+
*/
|
|
44
|
+
export async function profileSiteGames(context, only) {
|
|
45
|
+
const { accounts } = activeProfile(context.home());
|
|
46
|
+
const asked = only === undefined ? accounts : only === "lichess" ? { lichess: accounts?.lichess } : { chesscom: accounts?.chesscom };
|
|
47
|
+
if (asked?.lichess === undefined && asked?.chesscom === undefined) {
|
|
48
|
+
throw new SiteGamesRefused("no-accounts", `Tell the player: put your name on ${only === undefined ? "Lichess or Chess.com" : siteText[only]} in your profile, with the pencil beside your name in Who's playing? on the board page. Nothing changed.`);
|
|
49
|
+
}
|
|
50
|
+
if (context.fetch === undefined) {
|
|
51
|
+
throw new SiteGamesRefused("no-network", "Chesspal cannot reach the sites from here. Nothing changed.");
|
|
52
|
+
}
|
|
53
|
+
const read = await readSiteGames(asked, context.fetch);
|
|
54
|
+
return { ...read, games: read.games.map((game) => withPlayer(game, accounts)) };
|
|
55
|
+
}
|
|
56
|
+
/** Refused while a game goes on, as every analysis is, with the page's code. */
|
|
57
|
+
export function refusedWhilePlaying(context) {
|
|
58
|
+
try {
|
|
59
|
+
noGameGoingOn(context);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new SiteGamesRefused("game-going", error.message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function withPlayer(game, accounts) {
|
|
66
|
+
const player = accountSide({ White: game.white, Black: game.black, Site: game.site }, accounts);
|
|
67
|
+
return player === undefined ? game : { ...game, player };
|
|
68
|
+
}
|
|
69
|
+
/** chess.com/live/186997906366: White (3476) vs BATEK_HA_TPAKTOPE (3102), won, blitz, 2026-11-11; from both sides when the player's is not known. */
|
|
70
|
+
function gameLine(game) {
|
|
71
|
+
const rated = (name, rating) => rating === undefined ? name : `${name} (${rating})`;
|
|
72
|
+
const when = `${game.speed}, ${game.played.slice(0, 10)}.`;
|
|
73
|
+
if (game.player === undefined) {
|
|
74
|
+
return `${game.name}: ${rated(game.white, game.whiteRating)} vs ${rated(game.black, game.blackRating)}, ${game.result}, ${when}`;
|
|
75
|
+
}
|
|
76
|
+
const white = game.player === "white";
|
|
77
|
+
const you = rated(white ? "White" : "Black", white ? game.whiteRating : game.blackRating);
|
|
78
|
+
const opponent = white ? rated(game.black, game.blackRating) : rated(game.white, game.whiteRating);
|
|
79
|
+
const went = game.result === "1/2-1/2" ? "drew" : (game.result === "1-0") === white ? "won" : "lost";
|
|
80
|
+
return `${game.name}: ${you} vs ${opponent}, ${went}, ${when}`;
|
|
81
|
+
}
|
|
82
|
+
function sitesText(sites) {
|
|
83
|
+
return sites.map((site) => siteText[site]).join(" and ");
|
|
84
|
+
}
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyCommand } from "./registry/index.ts";
|
|
2
|
-
export { analyseGame, deleteReview } from "./analysis/index.ts";
|
|
2
|
+
export { analyseGame, deleteReview, siteGames, SiteGamesRefused } from "./analysis/index.ts";
|
|
3
3
|
export { analystOf, bookOpening, bookPlies, gameAt, gameToStudy, namedGame, playMove, PositionChanged, positionIn, samePosition, say, shownPosition } from "./answers/index.ts";
|
|
4
4
|
export { aiMove, answerDraw, ChatNotClear, DrawRefused, endGame, newGame, offerDraw, startNewGame } from "./game/index.ts";
|
|
5
5
|
export { chooseDrawnAvatar, deleteGame, editProfile, GameGoingOn, newProfile, ratingsAge, refreshRatings, useProfile } from "./profiles/index.ts";
|
package/dist/commands/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// The commands: the only way the AI, the command line and the page change a game (D-5, D-27), grouped by area, each defined once.
|
|
2
|
-
import { analyseGame, annotateGame, deleteReview, studyGames } from "./analysis/index.js";
|
|
2
|
+
import { analyseGame, annotateGame, deleteReview, siteGames, studyGames } from "./analysis/index.js";
|
|
3
3
|
import { aiMove, coach, endGame, exportGame, look, newGame, playerMove, ponder, position, status } from "./game/index.js";
|
|
4
4
|
import { deleteGame, editProfile, newProfile, pastGames, profiles, setAvatar, useProfile } from "./profiles/index.js";
|
|
5
5
|
import { init, instructions } from "./setup/index.js";
|
|
6
6
|
import { aiSay, playerSay } from "./talk/index.js";
|
|
7
7
|
// What the server and the command line use besides the list: the context, the reading of an input, and the operations the page shares with the tools.
|
|
8
|
-
export { analyseGame, deleteReview } from "./analysis/index.js";
|
|
8
|
+
export { analyseGame, deleteReview, siteGames, SiteGamesRefused } from "./analysis/index.js";
|
|
9
9
|
export { analystOf, bookOpening, bookPlies, gameAt, gameToStudy, namedGame, playMove, PositionChanged, positionIn, samePosition, say, shownPosition } from "./answers/index.js";
|
|
10
10
|
export { aiMove, answerDraw, ChatNotClear, DrawRefused, endGame, newGame, offerDraw, startNewGame } from "./game/index.js";
|
|
11
11
|
export { chooseDrawnAvatar, deleteGame, editProfile, GameGoingOn, newProfile, ratingsAge, refreshRatings, useProfile } from "./profiles/index.js";
|
|
@@ -30,6 +30,7 @@ export const commands = [
|
|
|
30
30
|
analyseGame,
|
|
31
31
|
annotateGame,
|
|
32
32
|
studyGames,
|
|
33
|
+
siteGames,
|
|
33
34
|
deleteReview,
|
|
34
35
|
deleteGame,
|
|
35
36
|
profiles,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { deleteGame } from "./delete-game.ts";
|
|
2
2
|
export { pastGameLines, pastGames } from "./past-games.ts";
|
|
3
|
-
export { chooseDrawnAvatar, editProfile, GameGoingOn, newProfile, playerLine, profiles, ratingsText, setAvatar, useProfile } from "./profiles.ts";
|
|
3
|
+
export { chooseDrawnAvatar, editProfile, GameGoingOn, newProfile, playerLine, profiles, ratingsText, setAvatar, siteText, useProfile } from "./profiles.ts";
|
|
4
4
|
export { ratingsAge, refreshRatings } from "./ratings.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The commands on the players: their profiles, their ratings read from the sites, their past games and the deletion of one.
|
|
2
2
|
export { deleteGame } from "./delete-game.js";
|
|
3
3
|
export { pastGameLines, pastGames } from "./past-games.js";
|
|
4
|
-
export { chooseDrawnAvatar, editProfile, GameGoingOn, newProfile, playerLine, profiles, ratingsText, setAvatar, useProfile } from "./profiles.js";
|
|
4
|
+
export { chooseDrawnAvatar, editProfile, GameGoingOn, newProfile, playerLine, profiles, ratingsText, setAvatar, siteText, useProfile } from "./profiles.js";
|
|
5
5
|
export { ratingsAge, refreshRatings } from "./ratings.js";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { type DrawnAvatar, type PlayerProfile } from "../../profiles/index.ts";
|
|
2
|
+
import { type DrawnAvatar, type PlayerProfile, type RatingSite } from "../../profiles/index.ts";
|
|
3
3
|
import { type Context } from "../registry/index.ts";
|
|
4
4
|
/** A change of profile refused while a game goes on: the page says it in the player's language. */
|
|
5
5
|
export declare class GameGoingOn extends Error {
|
|
6
6
|
constructor();
|
|
7
7
|
}
|
|
8
|
+
/** A site's name as the AI's lines write it. */
|
|
9
|
+
export declare const siteText: Readonly<Record<RatingSite, string>>;
|
|
8
10
|
/** The profiles, the one in use first. */
|
|
9
11
|
export declare const profiles: import("../registry/registry.ts").Command<z.ZodObject<{}, z.core.$strip>, {
|
|
10
12
|
profiles: PlayerProfile[];
|
|
@@ -13,6 +13,8 @@ export class GameGoingOn extends Error {
|
|
|
13
13
|
super("A game is going on in this profile: finish it, or resign, before changing profile. Nothing changed.");
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
+
/** A site's name as the AI's lines write it. */
|
|
17
|
+
export const siteText = { "lichess": "Lichess", "chess.com": "Chess.com" };
|
|
16
18
|
/** The profiles, the one in use first. */
|
|
17
19
|
export const profiles = defineCommand({
|
|
18
20
|
name: "profiles",
|
|
@@ -160,7 +162,6 @@ function isPlaying(context) {
|
|
|
160
162
|
return current !== undefined && endOf(current.game) === undefined;
|
|
161
163
|
}
|
|
162
164
|
const levelText = { learning: "has just learned", casual: "plays now and then", regular: "plays often", tournament: "plays tournaments" };
|
|
163
|
-
const siteText = { "lichess": "Lichess", "chess.com": "Chess.com" };
|
|
164
165
|
function playerText(profile) {
|
|
165
166
|
const { name, level } = profile;
|
|
166
167
|
const ratings = ratingsText(profile);
|
|
@@ -20,7 +20,10 @@ export interface Context {
|
|
|
20
20
|
pageOpen?: () => boolean;
|
|
21
21
|
/** Opens a folder in the system's file manager: the data folder, from the board page; whether it could; none in the tests. */
|
|
22
22
|
openFolder?: (folder: string) => Promise<boolean>;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Asks the sites the player plays on: their ratings (D-105), only for the page and the command line, never a tool of the AI's (D-106); their latest
|
|
25
|
+
* games, which the tool site_games asks too (D-110); none in the tests.
|
|
26
|
+
*/
|
|
24
27
|
fetch?: typeof globalThis.fetch;
|
|
25
28
|
/** Opens the New game window on the board pages open now, where the player chooses the game: whether one was open. */
|
|
26
29
|
askNewGame?: () => boolean;
|
|
@@ -12,6 +12,8 @@ export interface Command<Schema extends z.ZodObject = z.ZodObject, Result = unkn
|
|
|
12
12
|
destructive?: boolean;
|
|
13
13
|
/** Calling the command again with the same input changes nothing more. */
|
|
14
14
|
idempotent?: boolean;
|
|
15
|
+
/** The command may ask a site outside this computer, as site_games asks Lichess and Chess.com (D-110); every other one stays local. */
|
|
16
|
+
openWorld?: true;
|
|
15
17
|
/** Hosts load the tool at once instead of on demand: for the ones the AI calls at the start of every chat. */
|
|
16
18
|
alwaysLoad?: boolean;
|
|
17
19
|
/** Its answer shows the chat the game as it is: the chat may move on it, and on no other (D-71). */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Side } from "./game.ts";
|
|
2
|
+
/** The player's names on the sites they play on, from their profile. */
|
|
3
|
+
export interface SiteNames {
|
|
4
|
+
readonly lichess?: string;
|
|
5
|
+
readonly chesscom?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The side a game loaded from elsewhere names by one of the player's names on the sites (D-93): the one of its site when the Site tag says Lichess or
|
|
9
|
+
* chess.com, either otherwise; none when neither side, or both, has one.
|
|
10
|
+
*/
|
|
11
|
+
export declare function accountSide(tags: Readonly<Record<string, string | undefined>>, names: SiteNames | undefined): Side | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The side a game loaded from elsewhere names by one of the player's names on the sites (D-93): the one of its site when the Site tag says Lichess or
|
|
3
|
+
* chess.com, either otherwise; none when neither side, or both, has one.
|
|
4
|
+
*/
|
|
5
|
+
export function accountSide(tags, names) {
|
|
6
|
+
const site = (tags.Site ?? "").toLowerCase();
|
|
7
|
+
const known = [
|
|
8
|
+
site.includes("chess.com") ? undefined : names?.lichess,
|
|
9
|
+
site.includes("lichess") ? undefined : names?.chesscom,
|
|
10
|
+
].filter((name) => name !== undefined).map((name) => name.trim().toLowerCase());
|
|
11
|
+
const white = known.includes(tags.White?.trim().toLowerCase() ?? "");
|
|
12
|
+
const black = known.includes(tags.Black?.trim().toLowerCase() ?? "");
|
|
13
|
+
return white === black ? undefined : white ? "white" : "black";
|
|
14
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { accountSide, type SiteNames } from "./accounts.ts";
|
|
1
2
|
export { boardText, figurinesOf } from "./board-text.ts";
|
|
2
3
|
export { canMate, clockAfterMove, clockOfComment, clockStarted, clockTag, clockText, startClock, timeControlOf, timeControlOfTag, timeControlTag, timeControlText, timeLeft } from "./clock.ts";
|
|
3
4
|
export type { Clock, TimeControl } from "./clock.ts";
|
package/dist/core/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// The chess core: the game and its rules, the facts of a position, its record, PGN, the openings. It runs in the browser too (D-17, D-18).
|
|
2
|
+
export { accountSide } from "./accounts.js";
|
|
2
3
|
export { boardText, figurinesOf } from "./board-text.js";
|
|
3
4
|
export { canMate, clockAfterMove, clockOfComment, clockStarted, clockTag, clockText, startClock, timeControlOf, timeControlOfTag, timeControlTag, timeControlText, timeLeft } from "./clock.js";
|
|
4
5
|
export { Game, withSanLetters } from "./game.js";
|
package/dist/page/app/app.js
CHANGED
|
@@ -11,7 +11,7 @@ import { piecesOf } from "../board/squares.js";
|
|
|
11
11
|
import { NewGameDialog } from "../dialogs/new-game.js";
|
|
12
12
|
import { NameButton, ProfileDialog, ProfilesDialog, Wizard } from "../dialogs/profiles.js";
|
|
13
13
|
import { SettingsPanel } from "../dialogs/settings.js";
|
|
14
|
-
import { playerIn, studiedSides } from "../dialogs/studied.js";
|
|
14
|
+
import { othersOf, playerIn, studiedSides } from "../dialogs/studied.js";
|
|
15
15
|
import { StudyDialog } from "../dialogs/study.js";
|
|
16
16
|
import { BadgeCounts } from "../game/badges.js";
|
|
17
17
|
import { stepped } from "../game/browse.js";
|
|
@@ -231,7 +231,7 @@ export function App({ start }) {
|
|
|
231
231
|
const playedName = game === undefined || game === null ? undefined : anchorName(game.name);
|
|
232
232
|
useAnchor({ ready: served !== undefined, shown: studied !== undefined ? anchorName(studied.name) : idle ? undefined : playedName, played: playedName, going }, viewing, (plies) => goTo(plies ?? total), async (wanted) => await post("/api/study", { game: wanted, look: true }, text, name) === undefined);
|
|
233
233
|
// The tab names the game and the move shown, as the cards name its players; between games, Chesspal alone.
|
|
234
|
-
const titled = idle || served === undefined ? undefined : studiedSides(live.record.tags, served.profile.name, playedAs, text.sides, served.profile.accounts);
|
|
234
|
+
const titled = idle || served === undefined ? undefined : studiedSides(live.record.tags, served.profile.name, playedAs, text.sides, served.profile.accounts, othersOf(served.profiles, served.profile.id));
|
|
235
235
|
useTabTitle(titled === undefined ? undefined : tabTitle(titled.white.name, titled.black.name, at === 0 ? undefined : plies[at - 1]));
|
|
236
236
|
// The header's theme switch picks the other of the two the page shows, leaving the system's.
|
|
237
237
|
const switchTheme = () => change({ ...settings, theme: document.documentElement.dataset.theme === "dark" ? "light" : "dark" });
|
|
@@ -281,7 +281,7 @@ export function App({ start }) {
|
|
|
281
281
|
const profile = served.profile;
|
|
282
282
|
const picture = _jsx(PlayerPicture, { profile: profile });
|
|
283
283
|
// In analysis the cards name the players of the game, the player's side at the bottom when they played it (D-69).
|
|
284
|
-
const sides = studied === undefined ? undefined : studiedSides(live.record.tags, profile.name, playedAs, text.sides, profile.accounts);
|
|
284
|
+
const sides = studied === undefined ? undefined : studiedSides(live.record.tags, profile.name, playedAs, text.sides, profile.accounts, othersOf(served.profiles, profile.id));
|
|
285
285
|
const player = sides?.bottom ?? (game === null || game.ai === "black" ? "white" : "black");
|
|
286
286
|
const ai = player === "white" ? "black" : "white";
|
|
287
287
|
// The Torretta's face on the move shown: the mood the AI gave, while it lasts (D-70); in analysis, of the side the AI played.
|
|
@@ -414,7 +414,7 @@ export function App({ start }) {
|
|
|
414
414
|
act("/api/new", { ai_colour: colour === "random" ? "random" : colour === "white" ? "black" : "white", level: chosen, ...(control === undefined ? {} : { clock: control }) });
|
|
415
415
|
};
|
|
416
416
|
// In analysis the AI's note goes with the other side's card and the talk with the player's, turned round with the board as in a game (D-101, Zoe).
|
|
417
|
-
const studiedCard = (side, place) => sides === undefined ? null : (_jsx(PlayerCard, { place: place, picture: pictureOf(sides[side], profile, 48, mood), name: sides[side].name, signed: sides[side].who === "ai" && playedBy !== undefined ? signature(playedBy.model, playedBy.effort) : undefined, side: side, taken: material.taken[side], ahead: ahead(side), badges: _jsx(BadgeCounts, { plies: plies, side: side, names: text.badges }), text: text }));
|
|
417
|
+
const studiedCard = (side, place) => sides === undefined ? null : (_jsx(PlayerCard, { place: place, picture: pictureOf(sides[side], profile, served.profiles, 48, mood), name: sides[side].name, signed: sides[side].who === "ai" && playedBy !== undefined ? signature(playedBy.model, playedBy.effort) : undefined, side: side, taken: material.taken[side], ahead: ahead(side), badges: _jsx(BadgeCounts, { plies: plies, side: side, names: text.badges }), text: text }));
|
|
418
418
|
// A game brought to analyse: the page shows it once the server has it in analysis; a refusal stays in the dialog. One the AI has to go over, with no
|
|
419
419
|
// chat at the table, opens the chat's window: the analyst works with the chat's model (D-109).
|
|
420
420
|
const studyGame = async (studied) => {
|
|
@@ -427,7 +427,7 @@ export function App({ start }) {
|
|
|
427
427
|
setDialog(toReview ? "chat" : undefined);
|
|
428
428
|
return undefined;
|
|
429
429
|
};
|
|
430
|
-
return (_jsx(ChosenSet.Provider, { value: settings.set, children: _jsxs(PointSquare.Provider, { value: pointSquare, children: [panel, _jsx(ProfileDialog, { open: dialog === "profile", profile: profile, onClose: () => setDialog(undefined), text: text }), _jsx(ProfilesDialog, { open: dialog === "profiles", profiles: served.profiles, active: profile, locked: going ? text.profile.whilePlaying : undefined, dataFolder: served.dataFolder, onEdit: () => setDialog("profile"), onClose: () => setDialog(undefined), text: text }), table === undefined ? null : _jsx(ChatDialog, { open: dialog === "chat", review: chatToReview, table: table, settings: settings, onChange: change, name: name, onClose: closeChat, text: text }), _jsx(NewGameDialog, { open: dialog === "new-game", choice: choice, going: going, onStart: startNewGame, onClose: () => setDialog(undefined), text: text }), guide, _jsx(StudyDialog, { open: dialog === "study", onStudy: studyGame, onClose: () => setDialog(undefined), you: profile.name, accounts: profile.accounts, ai: name, text: text }), served.update === undefined || served.table === undefined
|
|
430
|
+
return (_jsx(ChosenSet.Provider, { value: settings.set, children: _jsxs(PointSquare.Provider, { value: pointSquare, children: [panel, _jsx(ProfileDialog, { open: dialog === "profile", profile: profile, onClose: () => setDialog(undefined), text: text }), _jsx(ProfilesDialog, { open: dialog === "profiles", profiles: served.profiles, active: profile, locked: going ? text.profile.whilePlaying : undefined, dataFolder: served.dataFolder, onEdit: () => setDialog("profile"), onClose: () => setDialog(undefined), text: text }), table === undefined ? null : _jsx(ChatDialog, { open: dialog === "chat", review: chatToReview, table: table, settings: settings, onChange: change, name: name, onClose: closeChat, text: text }), _jsx(NewGameDialog, { open: dialog === "new-game", choice: choice, going: going, onStart: startNewGame, onClose: () => setDialog(undefined), text: text }), guide, _jsx(StudyDialog, { open: dialog === "study", onStudy: studyGame, onClose: () => setDialog(undefined), onProfile: () => setDialog("profile"), you: profile.name, accounts: profile.accounts, others: othersOf(served.profiles, profile.id), ai: name, text: text }), served.update === undefined || served.table === undefined
|
|
431
431
|
? null
|
|
432
432
|
: (_jsx(UpdateDialog, { open: dialog === "update", version: served.update.version, current: served.version, installing: served.update.installing === true, installed: served.update.installed === true, going: going, chat: served.table.chat ? (served.table.held ? "held" : "byHand") : undefined, name: name, onClose: () => setDialog(undefined), text: text })), offline ? _jsx("p", { class: "offline", role: "alert", children: text.offline(name) }) : null, served.outdated === true
|
|
433
433
|
? _jsx("p", { class: "outdated", role: "status", children: text.outdated(name) })
|
|
@@ -435,7 +435,7 @@ export function App({ start }) {
|
|
|
435
435
|
? _jsx(UpdateNotice, { version: served.update.version, name: name, text: text })
|
|
436
436
|
: null, _jsxs("main", { class: `app${flipped ? " flipped" : ""}${folds.talk === "open" ? "" : ` talk-${folds.talk}`}${folds.game === "folded" ? " folded" : ""}`, inert: offline, children: [header, sides === undefined
|
|
437
437
|
? (_jsx(TalkFaces, { name: name, aiPicture: thinker(40), you: profile.name, picture: picture, ...(idle ? {} : { ai, player }), aiMoves: going && !playerToMove, playerMoves: playerToMove, unread: unread || asleep, text: text }))
|
|
438
|
-
: (_jsx(TalkFaces, { name: sides[ai].name, aiPicture: pictureOf(sides[ai], profile, 40, mood), you: sides[player].name, picture: pictureOf(sides[player], profile, 40, mood), ai: ai, player: player, aiMoves: false, playerMoves: false, unread: asleep, text: text })), _jsx(FoldTab, { column: "talk", folded: folds.talk === "folded", label: thinking === undefined || folds.talk !== "folded" ? text.talk : `${text.talk}: ${text.thinking[thinking]}`, unread: (unread || asleep) && folds.talk === "folded", ...(thinking === undefined || folds.talk !== "folded" ? {} : { mark: _jsx(Thought, { thinking: thinking, words: text.thinking }) }), onFold: fold }), _jsx(FoldTab, { column: "game", folded: folds.game === "folded", label: text.game, onFold: fold }), _jsx("span", { class: "visually-hidden", "aria-live": "polite", children: unread ? text.wrote(name) : "" }), _jsx("div", { class: sides === undefined ? "talk" : "talk studied", id: "talk", children: sides !== undefined && studied !== undefined
|
|
438
|
+
: (_jsx(TalkFaces, { name: sides[ai].name, aiPicture: pictureOf(sides[ai], profile, served.profiles, 40, mood), you: sides[player].name, picture: pictureOf(sides[player], profile, served.profiles, 40, mood), ai: ai, player: player, aiMoves: false, playerMoves: false, unread: asleep, text: text })), _jsx(FoldTab, { column: "talk", folded: folds.talk === "folded", label: thinking === undefined || folds.talk !== "folded" ? text.talk : `${text.talk}: ${text.thinking[thinking]}`, unread: (unread || asleep) && folds.talk === "folded", ...(thinking === undefined || folds.talk !== "folded" ? {} : { mark: _jsx(Thought, { thinking: thinking, words: text.thinking }) }), onFold: fold }), _jsx(FoldTab, { column: "game", folded: folds.game === "folded", label: text.game, onFold: fold }), _jsx("span", { class: "visually-hidden", "aria-live": "polite", children: unread ? text.wrote(name) : "" }), _jsx("div", { class: sides === undefined ? "talk" : "talk studied", id: "talk", children: sides !== undefined && studied !== undefined
|
|
439
439
|
? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[ai].name, children: [studiedCard(ai, "ai"), asleepNote, _jsx(AiNote, { ...(reviewed === undefined ? {} : { ply: reviewed }), ...(shownOpening === undefined ? {} : { opening: shownOpening }), ...(at === total && closing !== undefined ? { closing } : {}), ...(at === 0 && summary !== undefined ? { summary } : {}), ...(reviewing ? { reviewing: true } : {}), ...(completeIt === undefined ? {} : { onComplete: completeIt }), name: analyst, by: annotator, mood: mood, ...(settings.voice ? { voice: text } : {}), text: text })] }), _jsxs("section", { class: "you-block", "aria-label": sides[player].name, children: [_jsx("div", { class: "chat", children: _jsx(Compose, { lines: analysisTalk(talk, studied.name), newestFirst: false, spent: 0, draft: draft, onDraft: (words) => {
|
|
440
440
|
setDraft(words);
|
|
441
441
|
setUnsent(undefined);
|
|
@@ -460,9 +460,10 @@ export function App({ start }) {
|
|
|
460
460
|
},
|
|
461
461
|
}), shownPlies: at, onShow: goTo, onStep: step, ...(studied === undefined ? {} : { onDownload: () => download(studied.pgn, pgnName(live.record.tags, "analysis")) }), ...(total === 0 ? {} : { onSaveMoves: () => download(writePgn(movesOnly(live.record)), pgnName(live.record.tags)) }), ...(between && game !== null && replayed.record.moves.length > 0 ? { onAnalyse: () => void studyGame({ last: true }) } : {}), text: text }), _jsx(PageFoot, { version: served.version, text: text })] })] }) }));
|
|
462
462
|
}
|
|
463
|
-
/** The picture of a player of the game in analysis: the player's own, the AI's Torretta with its mood, or the plain face of someone else. */
|
|
464
|
-
function pictureOf({ who }, profile, size, mood) {
|
|
465
|
-
|
|
463
|
+
/** The picture of a player of the game in analysis: the player's own, the AI's Torretta with its mood, another profile's, or the plain face of someone else. */
|
|
464
|
+
function pictureOf({ who, profile: other }, profile, profiles, size, mood) {
|
|
465
|
+
const shown = who === "player" ? profile : profiles.find(({ id }) => id === other);
|
|
466
|
+
return who === "ai" ? _jsx(Torretta, { size: size, mood: mood, bust: true }) : _jsx(PlayerPicture, { profile: shown, size: size });
|
|
466
467
|
}
|
|
467
468
|
/**
|
|
468
469
|
* Plays the sound of each move the game gains while the page watches it, the AI's and the player's, of its end without a move and of a draw offered:
|