chesspal 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli/cli.js +17 -4
- package/dist/cli/command-line.js +22 -12
- package/dist/cli/icon.d.ts +2 -0
- package/dist/cli/icon.js +11 -1
- package/dist/cli/prompt-hook.d.ts +17 -0
- package/dist/cli/prompt-hook.js +100 -0
- package/dist/commands/analysis/analyse.d.ts +2 -2
- package/dist/commands/analysis/analyse.js +10 -16
- package/dist/commands/analysis/annotate.d.ts +8 -0
- package/dist/commands/analysis/annotate.js +19 -3
- package/dist/commands/answers/agents.d.ts +17 -7
- package/dist/commands/answers/agents.js +46 -16
- package/dist/commands/answers/clock.js +2 -1
- package/dist/commands/answers/drawings.js +6 -2
- package/dist/commands/answers/index.d.ts +4 -3
- package/dist/commands/answers/index.js +4 -3
- package/dist/commands/answers/lines.d.ts +5 -3
- package/dist/commands/answers/lines.js +17 -9
- package/dist/commands/answers/opening-book.d.ts +13 -7
- package/dist/commands/answers/opening-book.js +34 -38
- package/dist/commands/answers/opening-tags.d.ts +4 -0
- package/dist/commands/answers/opening-tags.js +11 -0
- package/dist/commands/answers/play-move.d.ts +3 -1
- package/dist/commands/answers/play-move.js +9 -6
- package/dist/commands/answers/say.js +2 -1
- package/dist/commands/answers/summary.d.ts +7 -3
- package/dist/commands/answers/summary.js +16 -5
- package/dist/commands/answers/view.d.ts +1 -1
- package/dist/commands/answers/view.js +2 -2
- package/dist/commands/game/ai-move.js +4 -4
- package/dist/commands/game/coach.js +7 -6
- package/dist/commands/game/end.js +2 -2
- package/dist/commands/game/export.d.ts +1 -1
- package/dist/commands/game/index.d.ts +1 -1
- package/dist/commands/game/index.js +1 -1
- package/dist/commands/game/look.d.ts +7 -3
- package/dist/commands/game/look.js +9 -4
- package/dist/commands/game/new.js +42 -12
- package/dist/commands/game/player-move.d.ts +16 -0
- package/dist/commands/game/player-move.js +69 -19
- package/dist/commands/game/ponder.js +7 -4
- package/dist/commands/game/position.d.ts +2 -1
- package/dist/commands/game/position.js +9 -4
- package/dist/commands/game/status.d.ts +4 -2
- package/dist/commands/game/status.js +24 -14
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +3 -3
- package/dist/commands/profiles/past-games.d.ts +7 -2
- package/dist/commands/profiles/past-games.js +45 -18
- package/dist/commands/registry/context.d.ts +2 -0
- package/dist/commands/registry/context.js +5 -0
- package/dist/commands/registry/index.d.ts +1 -1
- package/dist/commands/registry/index.js +1 -1
- package/dist/commands/talk/ai-say.d.ts +1 -1
- package/dist/commands/talk/ai-say.js +7 -6
- package/dist/core/game.d.ts +13 -0
- package/dist/core/game.js +29 -3
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/core/openings.d.ts +20 -6
- package/dist/core/openings.js +45 -22
- package/dist/core/pgn.d.ts +4 -0
- package/dist/core/pgn.js +35 -6
- package/dist/games/analysis.d.ts +7 -4
- package/dist/games/analysis.js +5 -3
- package/dist/games/archive.d.ts +25 -5
- package/dist/games/archive.js +52 -19
- package/dist/games/chat-start.d.ts +2 -2
- package/dist/games/chat-start.js +6 -1
- package/dist/games/current-game.d.ts +2 -2
- package/dist/games/current-game.js +3 -2
- package/dist/games/index.d.ts +3 -2
- package/dist/games/index.js +3 -2
- package/dist/games/position-shown.d.ts +24 -0
- package/dist/games/position-shown.js +51 -0
- package/dist/games/thoughts.d.ts +12 -4
- package/dist/games/thoughts.js +27 -10
- package/dist/home/index.d.ts +1 -0
- package/dist/home/index.js +1 -0
- package/dist/home/same-path.d.ts +7 -0
- package/dist/home/same-path.js +24 -0
- package/dist/host-files/host-files.d.ts +5 -0
- package/dist/host-files/host-files.js +21 -7
- package/dist/host-files/index.d.ts +1 -1
- package/dist/host-files/index.js +1 -1
- package/dist/page/app/anchor.js +15 -11
- package/dist/page/app/app.js +49 -14
- package/dist/page/app/served.js +30 -38
- package/dist/page/app/watch.js +97 -0
- package/dist/page/avatars/torretta.css +108 -0
- package/dist/page/base.css +1 -1
- package/dist/page/board/board.css +11 -8
- package/dist/page/board/marks.js +2 -2
- package/dist/page/board/pointer.js +2 -2
- package/dist/page/dialogs/analyses.js +5 -3
- package/dist/page/dialogs/dialogs.css +3 -1
- package/dist/page/dialogs/rows.js +72 -0
- package/dist/page/dialogs/site-games.js +3 -1
- package/dist/page/game/column.js +10 -8
- package/dist/page/game/game.css +137 -16
- package/dist/page/game/move-list.js +7 -5
- package/dist/page/game/plies.js +4 -0
- package/dist/page/game/review-progress.js +11 -0
- package/dist/page/guide/drawings/drawings.css +0 -1
- package/dist/page/guide/en.md +2 -2
- package/dist/page/guide/it.md +2 -2
- package/dist/page/helps/helps.css +5 -5
- package/dist/page/helps/helps.js +3 -181
- package/dist/page/helps/rows.js +16 -0
- package/dist/page/helps/strategy.js +105 -0
- package/dist/page/helps/tactics.js +69 -0
- package/dist/page/layout/header.js +49 -6
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/layout.css +49 -17
- package/dist/page/layout/window.js +45 -6
- package/dist/page/lead/lead.js +41 -4
- package/dist/page/legend/legend.css +4 -16
- package/dist/page/legend/legend.js +28 -15
- package/dist/page/profile/settings.js +1 -0
- package/dist/page/sounds/sounds.js +1 -1
- package/dist/page/style.css +1 -0
- package/dist/page/table/action.js +42 -0
- package/dist/page/table/table.css +57 -39
- package/dist/page/table/window.js +67 -67
- package/dist/page/talk/talk.css +52 -118
- package/dist/page/talk/talk.js +65 -20
- package/dist/page/talk/voice.js +92 -21
- package/dist/page/text.js +34 -8
- package/dist/page/theme.css +13 -0
- package/dist/profiles/profiles.js +5 -1
- package/dist/server/bell/bell.d.ts +24 -29
- package/dist/server/bell/bell.js +79 -133
- package/dist/server/bell/index.d.ts +1 -1
- package/dist/server/bell/index.js +2 -2
- package/dist/server/mcp/mcp.js +49 -7
- package/dist/server/page/analyses.js +15 -4
- package/dist/server/page/chat.d.ts +5 -1
- package/dist/server/page/chat.js +5 -1
- package/dist/server/page/files.js +3 -1
- package/dist/server/page/game.d.ts +5 -0
- package/dist/server/page/game.js +95 -15
- package/dist/server/page/http.d.ts +1 -1
- package/dist/server/page/page.d.ts +14 -9
- package/dist/server/page/page.js +111 -26
- package/dist/server/page/table.d.ts +40 -5
- package/dist/server/page/table.js +65 -25
- package/dist/server/page/update.d.ts +1 -1
- package/dist/server/page/update.js +4 -4
- package/dist/server/page/windows.d.ts +22 -0
- package/dist/server/page/windows.js +45 -0
- package/dist/server/ports/index.d.ts +1 -0
- package/dist/server/ports/index.js +2 -0
- package/dist/server/ports/ports.d.ts +78 -0
- package/dist/server/ports/ports.js +222 -0
- package/dist/server/table/browser.d.ts +22 -6
- package/dist/server/table/browser.js +98 -20
- package/dist/server/table/hosts/chats.d.ts +5 -0
- package/dist/server/table/hosts/chats.js +11 -4
- package/dist/server/table/hosts/hooks.d.ts +6 -0
- package/dist/server/table/hosts/hooks.js +38 -0
- package/dist/server/table/hosts/hosts.d.ts +13 -5
- package/dist/server/table/hosts/hosts.js +16 -10
- package/dist/server/table/hosts/index.d.ts +1 -0
- package/dist/server/table/hosts/index.js +1 -0
- package/dist/server/table/hosts/play.d.ts +7 -3
- package/dist/server/table/hosts/play.js +63 -9
- package/dist/server/table/icon/index.d.ts +2 -0
- package/dist/server/table/icon/index.js +16 -2
- package/dist/server/table/index.d.ts +2 -2
- package/dist/server/table/index.js +1 -1
- package/dist/server/table/launch.d.ts +5 -4
- package/dist/server/table/launch.js +18 -5
- package/dist/server/table/link.js +4 -1
- package/dist/server/table/table.d.ts +7 -0
- package/dist/server/table/table.js +40 -12
- package/dist/sites/index.d.ts +1 -1
- package/dist/sites/index.js +1 -1
- package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
- package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
- package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
- package/dist/sites/{choice.js → ratings/choice.js} +1 -1
- package/dist/sites/ratings/index.d.ts +1 -0
- package/dist/sites/ratings/index.js +2 -0
- package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
- package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
- package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
- package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
- package/instructions/opponent.md +1 -1
- package/instructions/start.md +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The clock in the commands (D-13): a flag that falls ends the game as soon as a command reads it, and the clock's line for the AI.
|
|
2
2
|
import { canMate, clockText, Game, timeControlText, timeLeft } from "../../core/index.js";
|
|
3
3
|
import { archiveEnded, endOf, loadGame, saveGame } from "../../games/index.js";
|
|
4
|
+
import { openingTags } from "./opening-tags.js";
|
|
4
5
|
import { colourName } from "./view.js";
|
|
5
6
|
/** The current game of a folder `now`, or undefined: a game whose side to move has run out of time is over, saved and archived first. */
|
|
6
7
|
export function gameAt(folder, now) {
|
|
@@ -15,7 +16,7 @@ export function gameAt(folder, now) {
|
|
|
15
16
|
const ended = outOfTime(current, turn, now);
|
|
16
17
|
saveGame(folder, ended);
|
|
17
18
|
// An archive that cannot be written leaves the game in game.json, where status shows it, until a new game archives it.
|
|
18
|
-
archiveEnded(folder, ended);
|
|
19
|
+
archiveEnded(folder, ended, openingTags(ended.game.record));
|
|
19
20
|
return ended;
|
|
20
21
|
}
|
|
21
22
|
/** The clock of a game `now`, as the AI reads it; none in a game without one. */
|
|
@@ -12,10 +12,10 @@ export const noteInput = z.string().trim().min(1).max(noteLimit).regex(/^[^[\]{}
|
|
|
12
12
|
const drawing = z.string().overwrite((text) => text.replace(/[\s-]+/g, ""));
|
|
13
13
|
/** The arrows the AI draws, as their input reads them. */
|
|
14
14
|
export const arrowsInput = z.array(drawing.regex(/^[GRYB][a-h][1-8][a-h][1-8]$/, "an arrow is a colour's letter and two squares: Gf6e4; a single square goes in squares")
|
|
15
|
-
.refine((arrow) => arrow.slice(1, 3) !== arrow.slice(3, 5), "an arrow goes from a square to another: a single square goes in squares")).max(drawLimit).optional()
|
|
15
|
+
.refine((arrow) => arrow.slice(1, 3) !== arrow.slice(3, 5), "an arrow goes from a square to another: a single square goes in squares")).max(drawLimit).optional().overwrite(noneIfEmpty)
|
|
16
16
|
.describe("arrows on the board, as PGN's %cal writes them: Rf2e1. G green: the better move or the good idea; R red: a threat or the mistake; Y yellow: another way; B blue: a plan.");
|
|
17
17
|
/** The squares the AI marks, as their input reads them. */
|
|
18
|
-
export const squaresInput = z.array(drawing.regex(/^[GRYB][a-h][1-8]$/, "a square is a colour's letter and the square: Rf2")).max(drawLimit).optional()
|
|
18
|
+
export const squaresInput = z.array(drawing.regex(/^[GRYB][a-h][1-8]$/, "a square is a colour's letter and the square: Rf2")).max(drawLimit).optional().overwrite(noneIfEmpty)
|
|
19
19
|
.describe("squares to mark, as PGN's %csl writes them: Rf2; colours as for arrows");
|
|
20
20
|
/** The arrows and squares drawn, those that read; none of either left out. */
|
|
21
21
|
export function drawingsOf(arrows = [], squares = []) {
|
|
@@ -23,3 +23,7 @@ export function drawingsOf(arrows = [], squares = []) {
|
|
|
23
23
|
const drawnSquares = squares.map(squareOf).filter((square) => square !== undefined);
|
|
24
24
|
return { ...(drawnArrows.length === 0 ? {} : { arrows: drawnArrows }), ...(drawnSquares.length === 0 ? {} : { squares: drawnSquares }) };
|
|
25
25
|
}
|
|
26
|
+
// An empty list draws nothing, as one left out: a host may send every parameter of the schema (Priya).
|
|
27
|
+
function noneIfEmpty(list) {
|
|
28
|
+
return list?.length === 0 ? undefined : list;
|
|
29
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export { agentRuns, agentsLine, analystLine, analystOf, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.ts";
|
|
1
|
+
export { agentRuns, agentsLine, analystLine, analystOf, analystOnView, analystQuietAt, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.ts";
|
|
2
2
|
export { clockLine, clockSummaryOf, gameAt } from "./clock.ts";
|
|
3
3
|
export { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.ts";
|
|
4
4
|
export { aheadWords, leadFrom, leadInput } from "./lead-input.ts";
|
|
5
5
|
export { closingLines, headLines, lastMoveText, moveLines, newGameStep, nextStep, positionLines, shownMove, shownMoves, sidesLine, wordLines } from "./lines.ts";
|
|
6
|
-
export { bookOpening,
|
|
6
|
+
export { bookOpening, bookPlace, bookPlies, knownMoves, openingLine, openingName } from "./opening-book.ts";
|
|
7
|
+
export { openingTags } from "./opening-tags.ts";
|
|
7
8
|
export { playMove, PositionChanged, positionIn, refuseOtherGame, refuseUnseenPosition, samePosition } from "./play-move.ts";
|
|
8
9
|
export { give, newWords, say, unansweredWords, waitingWords, type Written } from "./say.ts";
|
|
9
10
|
export { type Speech, speechLine, speechOf } from "./speech.ts";
|
|
10
11
|
export { gameShown, gameToStudy, knownTag, moveIndex, moveNumbers, namedGame, noGameGoingOn, positionAfter, positionLine, shownPosition, type Studied, studyInput } from "./study.ts";
|
|
11
|
-
export { gameGoingOn, type GameSummary, lastMoves, type MovePlayed, summaryOf, withThought } from "./summary.ts";
|
|
12
|
+
export { endText, gameGoingOn, type GameSummary, lastMoves, type MovePlayed, summaryOf, withThought } from "./summary.ts";
|
|
12
13
|
export { balanceText, boardView, colourName, compactView, fullView, materialText, riskLine, tacticsText } from "./view.ts";
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
// What the commands share to answer the AI: the lines it reads, the game's summary and view, the clock, the talk, the moves played, the games to study.
|
|
2
2
|
// Above the registry only, below every other command folder.
|
|
3
|
-
export { agentRuns, agentsLine, analystLine, analystOf, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.js";
|
|
3
|
+
export { agentRuns, agentsLine, analystLine, analystOf, analystOnView, analystQuietAt, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.js";
|
|
4
4
|
export { clockLine, clockSummaryOf, gameAt } from "./clock.js";
|
|
5
5
|
export { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.js";
|
|
6
6
|
export { aheadWords, leadFrom, leadInput } from "./lead-input.js";
|
|
7
7
|
export { closingLines, headLines, lastMoveText, moveLines, newGameStep, nextStep, positionLines, shownMove, shownMoves, sidesLine, wordLines } from "./lines.js";
|
|
8
|
-
export { bookOpening,
|
|
8
|
+
export { bookOpening, bookPlace, bookPlies, knownMoves, openingLine, openingName } from "./opening-book.js";
|
|
9
|
+
export { openingTags } from "./opening-tags.js";
|
|
9
10
|
export { playMove, PositionChanged, positionIn, refuseOtherGame, refuseUnseenPosition, samePosition } from "./play-move.js";
|
|
10
11
|
export { give, newWords, say, unansweredWords, waitingWords } from "./say.js";
|
|
11
12
|
export { speechLine, speechOf } from "./speech.js";
|
|
12
13
|
export { gameShown, gameToStudy, knownTag, moveIndex, moveNumbers, namedGame, noGameGoingOn, positionAfter, positionLine, shownPosition, studyInput } from "./study.js";
|
|
13
|
-
export { gameGoingOn, lastMoves, summaryOf, withThought } from "./summary.js";
|
|
14
|
+
export { endText, gameGoingOn, lastMoves, summaryOf, withThought } from "./summary.js";
|
|
14
15
|
export { balanceText, boardView, colourName, compactView, fullView, materialText, riskLine, tacticsText } from "./view.js";
|
|
@@ -17,7 +17,7 @@ export declare function shownMoves(moves: string): string;
|
|
|
17
17
|
/** The lines after a move, which the line before named: its note, where the game stands, a warning. closingLines end them. */
|
|
18
18
|
export declare function moveLines({ note, game, warning }: MovePlayed): string[];
|
|
19
19
|
/** The last lines of a tool's answer: the player's words from the board page, then the next step, which stays the last line. */
|
|
20
|
-
export declare function closingLines(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean): string[];
|
|
20
|
+
export declare function closingLines(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean, doorbell?: boolean): string[];
|
|
21
21
|
/**
|
|
22
22
|
* The player's words from the board page, one line each, in analysis with the move the page showed (D-101): lines they broke are joined, so no line of
|
|
23
23
|
* theirs reads as one of Chesspal's.
|
|
@@ -25,9 +25,11 @@ export declare function closingLines(game: GameSummary | undefined, words?: read
|
|
|
25
25
|
export declare function wordLines(words: readonly Written[]): string[];
|
|
26
26
|
/**
|
|
27
27
|
* What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
|
|
28
|
-
* board page open in no browser, a new game starts there, never in the chat.
|
|
28
|
+
* board page open in no browser, a new game starts there, never in the chat. In a host with no `doorbell`, the player tells the chat what they did on the page.
|
|
29
29
|
*/
|
|
30
|
-
export declare function nextStep(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean): string;
|
|
30
|
+
export declare function nextStep(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean, doorbell?: boolean): string;
|
|
31
|
+
/** What the AI does with the player's move in a host with no doorbell: one played on the board page they tell it of, one written in the chat it plays. */
|
|
32
|
+
export declare const toldMove = "when they tell you they moved on the board page, call status; when they write it here, call player_move with it.";
|
|
31
33
|
/** How a new game starts: the player chooses it in the board page's window, which new_game without choices opens (D-85). */
|
|
32
34
|
export declare const newGameStep = "call new_game without choices: it opens the board page's New game window, where the player chooses the colour, the clock and the level.";
|
|
33
35
|
/** Who plays what, from the AI's side. */
|
|
@@ -72,8 +72,8 @@ export function moveLines({ note, game, warning }) {
|
|
|
72
72
|
return [...(note === undefined ? [] : [`Note: ${note}`]), ...positionLines(game, false), ...(warning === undefined ? [] : [warning])];
|
|
73
73
|
}
|
|
74
74
|
/** The last lines of a tool's answer: the player's words from the board page, then the next step, which stays the last line. */
|
|
75
|
-
export function closingLines(game, words = [], pageClosed = false) {
|
|
76
|
-
return [...wordLines(words), nextStep(game, words, pageClosed)];
|
|
75
|
+
export function closingLines(game, words = [], pageClosed = false, doorbell = true) {
|
|
76
|
+
return [...wordLines(words), nextStep(game, words, pageClosed, doorbell)];
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
79
|
* The player's words from the board page, one line each, in analysis with the move the page showed (D-101): lines they broke are joined, so no line of
|
|
@@ -84,9 +84,9 @@ export function wordLines(words) {
|
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
|
|
87
|
-
* board page open in no browser, a new game starts there, never in the chat.
|
|
87
|
+
* board page open in no browser, a new game starts there, never in the chat. In a host with no `doorbell`, the player tells the chat what they did on the page.
|
|
88
88
|
*/
|
|
89
|
-
export function nextStep(game, words = [], pageClosed = false) {
|
|
89
|
+
export function nextStep(game, words = [], pageClosed = false, doorbell = true) {
|
|
90
90
|
const step = pageClosed ? closedPageGameStep : newGameStep;
|
|
91
91
|
// Words written on a game in analysis: the chat answers them there, and corrects the review where they show it wrong (D-101).
|
|
92
92
|
if (words.some((word) => word.place !== undefined) && (game === undefined || game.end !== undefined)) {
|
|
@@ -104,7 +104,7 @@ export function nextStep(game, words = [], pageClosed = false) {
|
|
|
104
104
|
}
|
|
105
105
|
if (game.end !== undefined) {
|
|
106
106
|
const analysis = game.end.analyst === "offer"
|
|
107
|
-
? ` To go over it, on their yes, ${analystLine(game.end.archive)}`
|
|
107
|
+
? ` To go over it, on their yes, ${analystLine(game.end.archive, game.end.codex === true)}`
|
|
108
108
|
: game.end.analyst === "done"
|
|
109
109
|
? ` ${reviewedLine(game.end.archive)}`
|
|
110
110
|
: "";
|
|
@@ -114,7 +114,9 @@ export function nextStep(game, words = [], pageClosed = false) {
|
|
|
114
114
|
: `Next: answer the player with ai_say.${analysis} When they want another game, ${step}`;
|
|
115
115
|
}
|
|
116
116
|
if (game.turn === game.ai && game.clock?.waiting === true) {
|
|
117
|
-
return
|
|
117
|
+
return doorbell
|
|
118
|
+
? "Next: don't move yet: the player starts the game with Start on the board page, and the doorbell wakes you then. End your turn."
|
|
119
|
+
: "Next: don't move yet: the player starts the game with Start on the board page. End your turn, and call status when they tell you they did.";
|
|
118
120
|
}
|
|
119
121
|
if (game.turn === game.ai) {
|
|
120
122
|
return words.length === 0
|
|
@@ -124,8 +126,13 @@ export function nextStep(game, words = [], pageClosed = false) {
|
|
|
124
126
|
if (game.drawOffer === "player") {
|
|
125
127
|
return "Next: accept the draw with end_game draw_agreed, or answer with ai_say and wait for the player's move.";
|
|
126
128
|
}
|
|
127
|
-
|
|
129
|
+
if (words.length > 0) {
|
|
130
|
+
return "Next: answer the player with ai_say, then wait for their move.";
|
|
131
|
+
}
|
|
132
|
+
return doorbell ? "Next: wait for the player's move, then call player_move with it." : `Next: wait for the player's move: ${toldMove}`;
|
|
128
133
|
}
|
|
134
|
+
/** What the AI does with the player's move in a host with no doorbell: one played on the board page they tell it of, one written in the chat it plays. */
|
|
135
|
+
export const toldMove = "when they tell you they moved on the board page, call status; when they write it here, call player_move with it.";
|
|
129
136
|
/** How a new game starts: the player chooses it in the board page's window, which new_game without choices opens (D-85). */
|
|
130
137
|
export const newGameStep = "call new_game without choices: it opens the board page's New game window, where the player chooses the colour, the clock and the level.";
|
|
131
138
|
// With the page open in no browser the window cannot open: the player opens the page and presses New game.
|
|
@@ -163,8 +170,9 @@ function clockLines(game) {
|
|
|
163
170
|
}
|
|
164
171
|
function whereLine(game) {
|
|
165
172
|
if (game.end !== undefined) {
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
// Saved where the player's games are: the AI says so, never the file, which past_games names when it is needed.
|
|
174
|
+
const archived = game.end.archive === undefined ? "" : " Saved with the player's games.";
|
|
175
|
+
return `Game over: ${endText(game.end, game.ai)}.${archived}`;
|
|
168
176
|
}
|
|
169
177
|
return `${game.turn === game.ai ? "You" : "Player"} (${colourName(game.turn)}) to move, move ${game.moveNumber}.`;
|
|
170
178
|
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { type GameRecord, type Opening } from "../../core/index.ts";
|
|
2
|
-
/**
|
|
3
|
-
|
|
1
|
+
import { type BookPlace, Game, type GameRecord, type Opening } from "../../core/index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The game's opening as the AI reads it: ECO and name, and the move it left the book at (D-68); none outside the book, or where the build made no index.
|
|
4
|
+
* A live game gives the positions it went through; a record alone is replayed.
|
|
5
|
+
*/
|
|
6
|
+
export declare function openingLine(game: Game | GameRecord): string | undefined;
|
|
4
7
|
/** The moves the book knows from a position (D-15), for the AI at the master level; none out of the book, or where the build made no book. */
|
|
5
8
|
export declare function knownMoves(fen: string): readonly string[];
|
|
6
|
-
/** How many plies from the start the book knows, to the last before the game left it (D-69); none from another position. */
|
|
7
|
-
export declare function bookPlies(
|
|
8
|
-
/** Whether a line of Lichess's openings reaches a position, by any order of moves (D-68); never where the build made no book. */
|
|
9
|
-
export declare function bookReaches(fen: string): boolean;
|
|
9
|
+
/** How many plies from the start the book knows, to the last before the game left it (D-68, D-69); none from another position. */
|
|
10
|
+
export declare function bookPlies(game: Game | GameRecord): number;
|
|
10
11
|
/** The opening a game reached, ECO and name, without where it left the book: for the games in the archive. */
|
|
11
12
|
export declare function openingName(record: GameRecord): string | undefined;
|
|
12
13
|
/** The last named opening a game reached, and at which ply; none outside the book, or where the build made no index. */
|
|
13
14
|
export declare function bookOpening(record: GameRecord): Opening | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Where a game stands in the book, its opening and its plies the book knows, once per record: from the positions a live game went through, or a record
|
|
17
|
+
* replayed; none from another position, or where the build made no index.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bookPlace(game: Game | GameRecord): BookPlace | undefined;
|
|
@@ -1,45 +1,31 @@
|
|
|
1
|
-
// The book of the openings the build indexed (D-16), read once at the first lookup
|
|
1
|
+
// The book of the openings the build indexed (D-16), read once at the first lookup: where a game is in it (D-68) at every level, its moves for the master (D-60).
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import { bookMoves,
|
|
3
|
+
import { bookMoves, bookPlaceOf, Game, outOfBookMove, positionKeysOf } from "../../core/index.js";
|
|
4
4
|
// The index of the named openings and the book of their moves, each read at its first lookup; null where the build made none.
|
|
5
5
|
let index;
|
|
6
6
|
let book;
|
|
7
7
|
// Each record is looked up once: the live game keeps its record until a move changes it.
|
|
8
|
-
const
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
const places = new WeakMap();
|
|
9
|
+
/**
|
|
10
|
+
* The game's opening as the AI reads it: ECO and name, and the move it left the book at (D-68); none outside the book, or where the build made no index.
|
|
11
|
+
* A live game gives the positions it went through; a record alone is replayed.
|
|
12
|
+
*/
|
|
13
|
+
export function openingLine(game) {
|
|
14
|
+
const place = bookPlace(game);
|
|
15
|
+
if (place?.opening === undefined) {
|
|
16
|
+
return undefined;
|
|
15
17
|
}
|
|
16
|
-
|
|
18
|
+
const left = outOfBookMove(recordOf(game).moves.length, place.plies);
|
|
19
|
+
return `${place.opening.eco} ${place.opening.name}${left === undefined ? "" : ` (out of book since move ${left})`}`;
|
|
17
20
|
}
|
|
18
21
|
/** The moves the book knows from a position (D-15), for the AI at the master level; none out of the book, or where the build made no book. */
|
|
19
22
|
export function knownMoves(fen) {
|
|
20
23
|
const moves = openingBook();
|
|
21
24
|
return moves === undefined ? [] : bookMoves(fen, moves);
|
|
22
25
|
}
|
|
23
|
-
/** How many plies from the start the book knows, to the last before the game left it (D-69); none from another position. */
|
|
24
|
-
export function bookPlies(
|
|
25
|
-
|
|
26
|
-
return 0;
|
|
27
|
-
}
|
|
28
|
-
const game = new Game({ tags: record.tags, moves: [] });
|
|
29
|
-
let plies = 0;
|
|
30
|
-
for (const { san } of record.moves) {
|
|
31
|
-
if (!game.playRecorded(san).played || !bookReaches(game.position.fen)) {
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
plies++;
|
|
35
|
-
}
|
|
36
|
-
return plies;
|
|
37
|
-
}
|
|
38
|
-
/** Whether a line of Lichess's openings reaches a position, by any order of moves (D-68); never where the build made no book. */
|
|
39
|
-
export function bookReaches(fen) {
|
|
40
|
-
const named = openingIndex();
|
|
41
|
-
const moves = openingBook();
|
|
42
|
-
return named !== undefined && moves !== undefined && inOpenings(fen, named, moves);
|
|
26
|
+
/** How many plies from the start the book knows, to the last before the game left it (D-68, D-69); none from another position. */
|
|
27
|
+
export function bookPlies(game) {
|
|
28
|
+
return bookPlace(game)?.plies ?? 0;
|
|
43
29
|
}
|
|
44
30
|
/** The opening a game reached, ECO and name, without where it left the book: for the games in the archive. */
|
|
45
31
|
export function openingName(record) {
|
|
@@ -48,15 +34,25 @@ export function openingName(record) {
|
|
|
48
34
|
}
|
|
49
35
|
/** The last named opening a game reached, and at which ply; none outside the book, or where the build made no index. */
|
|
50
36
|
export function bookOpening(record) {
|
|
51
|
-
|
|
52
|
-
return named === undefined ? undefined : openingOf(record, named);
|
|
37
|
+
return bookPlace(record)?.opening;
|
|
53
38
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Where a game stands in the book, its opening and its plies the book knows, once per record: from the positions a live game went through, or a record
|
|
41
|
+
* replayed; none from another position, or where the build made no index.
|
|
42
|
+
*/
|
|
43
|
+
export function bookPlace(game) {
|
|
44
|
+
const record = recordOf(game);
|
|
45
|
+
let place = places.get(record);
|
|
46
|
+
if (place === undefined) {
|
|
47
|
+
const named = openingIndex();
|
|
48
|
+
const keys = game instanceof Game ? game.positionKeys : positionKeysOf(record);
|
|
49
|
+
place = named === undefined ? null : bookPlaceOf(keys, named, openingBook()) ?? null;
|
|
50
|
+
places.set(record, place);
|
|
58
51
|
}
|
|
59
|
-
return
|
|
52
|
+
return place ?? undefined;
|
|
53
|
+
}
|
|
54
|
+
function recordOf(game) {
|
|
55
|
+
return game instanceof Game ? game.record : game;
|
|
60
56
|
}
|
|
61
57
|
function openingBook() {
|
|
62
58
|
if (book === undefined) {
|
|
@@ -64,7 +60,7 @@ function openingBook() {
|
|
|
64
60
|
book = JSON.parse(readFileSync(new URL("../../../dist/core/book.json", import.meta.url), "utf8"));
|
|
65
61
|
}
|
|
66
62
|
catch {
|
|
67
|
-
// Not built yet: the master goes without
|
|
63
|
+
// Not built yet: no game is in the book, and the master goes without its moves.
|
|
68
64
|
book = null;
|
|
69
65
|
}
|
|
70
66
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GameRecord } from "../../core/index.ts";
|
|
2
|
+
import type { OpeningTags } from "../../games/index.ts";
|
|
3
|
+
/** The opening a game reached, in the ECO and Opening tags the archive writes: none from another position or outside the book. */
|
|
4
|
+
export declare function openingTags(record: GameRecord): OpeningTags;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { openingName } from "./opening-book.js";
|
|
2
|
+
/** The opening a game reached, in the ECO and Opening tags the archive writes: none from another position or outside the book. */
|
|
3
|
+
export function openingTags(record) {
|
|
4
|
+
const opening = openingName(record);
|
|
5
|
+
if (opening === undefined) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
// The ECO code, then the name: B50 Sicilian Defense.
|
|
9
|
+
const space = opening.indexOf(" ");
|
|
10
|
+
return { ECO: opening.slice(0, space), Opening: opening.slice(space + 1) };
|
|
11
|
+
}
|
|
@@ -14,12 +14,14 @@ export interface WithMove {
|
|
|
14
14
|
allowance?: number;
|
|
15
15
|
/** A coach judges the player's moves with the helps on (D-88): the AI reads it after the player's move. */
|
|
16
16
|
coached?: boolean;
|
|
17
|
+
/** The chat's host runs the doorbell: the AI refused a move hears the page will wake it, else that the player tells it. */
|
|
18
|
+
doorbell?: boolean;
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Plays a move for one side `now` and saves the game, with the time it took and the clock after it in its comment ([%clk]). A refused move, or words refused
|
|
20
22
|
* with it, change nothing: it throws the message `refused` writes from the core's.
|
|
21
23
|
*/
|
|
22
|
-
export declare function playMove(folder: string, now: Date, mover: Mover, text: string, refused: (message: string, legal: readonly string[]) => string, { comment, said, offerDraw, allowance, coached }?: WithMove): MovePlayed;
|
|
24
|
+
export declare function playMove(folder: string, now: Date, mover: Mover, text: string, refused: (message: string, legal: readonly string[]) => string, { comment, said, offerDraw, allowance, coached, doorbell }?: WithMove): MovePlayed;
|
|
23
25
|
/** A chat's move, resignation or game replaced on a position it has not seen (D-71): it was decided on another. */
|
|
24
26
|
export declare class PositionChanged extends Error {
|
|
25
27
|
constructor();
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
// A move of the AI or of the player on the current game: played, timed by the clock, saved, and archived when it ends the game.
|
|
2
2
|
import { clockAfterMove, clockTag } from "../../core/index.js";
|
|
3
3
|
import { archiveEnded, endOf, loadGame, saveGame, talkLine } from "../../games/index.js";
|
|
4
|
+
import { toldMove } from "./lines.js";
|
|
5
|
+
import { openingTags } from "./opening-tags.js";
|
|
4
6
|
import { gameGoingOn, summaryOf, withThought } from "./summary.js";
|
|
5
7
|
import { colourName } from "./view.js";
|
|
6
8
|
/**
|
|
7
9
|
* Plays a move for one side `now` and saves the game, with the time it took and the clock after it in its comment ([%clk]). A refused move, or words refused
|
|
8
10
|
* with it, change nothing: it throws the message `refused` writes from the core's.
|
|
9
11
|
*/
|
|
10
|
-
export function playMove(folder, now, mover, text, refused, { comment, said, offerDraw = false, allowance = 0, coached = false } = {}) {
|
|
12
|
+
export function playMove(folder, now, mover, text, refused, { comment, said, offerDraw = false, allowance = 0, coached = false, doorbell = true } = {}) {
|
|
11
13
|
// A flag falls only past the allowance.
|
|
12
|
-
const before = gameToMove(folder, mover, new Date(now.getTime() - allowance));
|
|
14
|
+
const before = gameToMove(folder, mover, new Date(now.getTime() - allowance), doorbell);
|
|
13
15
|
const { game } = before;
|
|
14
16
|
const position = game.position;
|
|
15
17
|
// The words are checked before the move, which changes the live game; they count as said after it.
|
|
16
18
|
const line = said === undefined ? undefined : talkLine(before, mover, said.text, said.at, game.record.moves.length + 1);
|
|
17
19
|
if (mover === "ai" && before.clock?.waiting === true) {
|
|
18
|
-
|
|
20
|
+
const then = doorbell ? "the doorbell wakes you then" : "call status when they tell you they did";
|
|
21
|
+
throw new Error(`The clocks wait for the player to start the game with Start on the board page: don't move yet, ${then}. Nothing changed.`);
|
|
19
22
|
}
|
|
20
23
|
if (offerDraw && game.record.moves.length === 0) {
|
|
21
24
|
throw new Error("A draw can be offered once each side has made a move: play the move without offer_draw. Nothing changed.");
|
|
@@ -37,7 +40,7 @@ export function playMove(folder, now, mover, text, refused, { comment, said, off
|
|
|
37
40
|
current.talk.push(line);
|
|
38
41
|
}
|
|
39
42
|
saveGame(folder, current);
|
|
40
|
-
const { archive, warning } = endOf(game) === undefined ? {} : archiveEnded(folder, current);
|
|
43
|
+
const { archive, warning } = endOf(game) === undefined ? {} : archiveEnded(folder, current, openingTags(game.record));
|
|
41
44
|
const result = { move: `${position.moveNumber}${position.turn === "white" ? "." : "..."}${played.move.san}`, game: withThought(summaryOf(current, now, archive), folder, current, coached) };
|
|
42
45
|
if (played.note !== undefined) {
|
|
43
46
|
result.note = played.note;
|
|
@@ -80,7 +83,7 @@ export function refuseOtherGame(context, folder) {
|
|
|
80
83
|
export function samePosition(one, other) {
|
|
81
84
|
return one.started === other.started && one.plies === other.plies;
|
|
82
85
|
}
|
|
83
|
-
function gameToMove(folder, mover, now) {
|
|
86
|
+
function gameToMove(folder, mover, now, doorbell) {
|
|
84
87
|
const current = gameGoingOn(folder, now);
|
|
85
88
|
const { turn } = current.game.position;
|
|
86
89
|
const aiToMove = turn === current.ai;
|
|
@@ -88,7 +91,7 @@ function gameToMove(folder, mover, now) {
|
|
|
88
91
|
throw new Error(`It is your move (${colourName(turn)}): call ai_move. Nothing changed.`);
|
|
89
92
|
}
|
|
90
93
|
if (mover === "ai" && !aiToMove) {
|
|
91
|
-
throw new Error(`It is the player's move (${colourName(turn)}): wait for it, then call player_move. Nothing changed.`);
|
|
94
|
+
throw new Error(`It is the player's move (${colourName(turn)}): ${doorbell ? "wait for it, then call player_move." : toldMove} Nothing changed.`);
|
|
92
95
|
}
|
|
93
96
|
return current;
|
|
94
97
|
}
|
|
@@ -4,6 +4,7 @@ import { archiveEnded, endOf, gameInAnalysis, loadGame, loadTableTalk, saveGame,
|
|
|
4
4
|
import { gameFolder, helpsIn } from "../../profiles/index.js";
|
|
5
5
|
import { gameAt } from "./clock.js";
|
|
6
6
|
import { shownMove } from "./lines.js";
|
|
7
|
+
import { openingTags } from "./opening-tags.js";
|
|
7
8
|
/**
|
|
8
9
|
* Adds a line to the talk of the current game, going on or just over, and saves it; the AI's mood once the game is over, which the archive keeps too (D-70).
|
|
9
10
|
* With no game the line goes in the talk before it (D-77). A line refused changes nothing: it throws, in words for whoever spoke.
|
|
@@ -24,7 +25,7 @@ export function say(folder, who, text, at, mood, shown) {
|
|
|
24
25
|
}
|
|
25
26
|
saveGame(folder, current);
|
|
26
27
|
if (mood !== undefined) {
|
|
27
|
-
archiveEnded(folder, current);
|
|
28
|
+
archiveEnded(folder, current, openingTags(current.game.record));
|
|
28
29
|
}
|
|
29
30
|
return current;
|
|
30
31
|
}
|
|
@@ -38,13 +38,14 @@ export interface GameSummary {
|
|
|
38
38
|
mood?: MoodNow;
|
|
39
39
|
/**
|
|
40
40
|
* When the game is over: how it ended, its name in the archive once it is there, and, where an analyst agent runs (D-89), whether the chat offers it or
|
|
41
|
-
* goes over the review it did.
|
|
41
|
+
* goes over the review it did, and whether the host is Codex, which starts it with spawn_agent.
|
|
42
42
|
*/
|
|
43
43
|
end?: {
|
|
44
44
|
result: string;
|
|
45
45
|
termination?: string;
|
|
46
46
|
archive?: string;
|
|
47
47
|
analyst?: "offer" | "done";
|
|
48
|
+
codex?: true;
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
/** A move numbered as in the game (4.Ng5, 4...d5), with the squares it went from and to. */
|
|
@@ -76,10 +77,13 @@ export declare function withThought(summary: GameSummary, folder: string, curren
|
|
|
76
77
|
export declare function gameGoingOn(folder: string, now: Date): CurrentGame;
|
|
77
78
|
/** Whether a game has no move yet, or was closed before any: nothing to go over. */
|
|
78
79
|
export declare function unplayed(summary: GameSummary): boolean;
|
|
79
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* How a game ended, in words: its result, and how it came when known, each colour named with who played it, the AI as `aiName`:
|
|
82
|
+
* "0-1, White (the player) resigns", "1-0, White (you) checkmates Black (the player)".
|
|
83
|
+
*/
|
|
80
84
|
export declare function endText({ result, termination }: {
|
|
81
85
|
result: string;
|
|
82
86
|
termination?: string;
|
|
83
|
-
}): string;
|
|
87
|
+
}, ai: Side, aiName?: string): string;
|
|
84
88
|
/** The last plies of a record, numbered as a player writes them, from the move number and side of its FEN. */
|
|
85
89
|
export declare function lastMoves(record: GameRecord, count: number): string;
|
|
@@ -14,7 +14,7 @@ export function summaryOf(current, now, archive) {
|
|
|
14
14
|
if (lastMove !== undefined) {
|
|
15
15
|
summary.lastMove = { move: lastMoves(record, 1), from: lastMove.from, to: lastMove.to };
|
|
16
16
|
}
|
|
17
|
-
const opening = openingLine(
|
|
17
|
+
const opening = openingLine(game);
|
|
18
18
|
const clock = clockSummaryOf(current, now);
|
|
19
19
|
const drawOffer = drawOfferOf(current);
|
|
20
20
|
if (drawOffer !== undefined) {
|
|
@@ -58,7 +58,7 @@ export function gameGoingOn(folder, now) {
|
|
|
58
58
|
}
|
|
59
59
|
const end = endOf(current.game);
|
|
60
60
|
if (end !== undefined) {
|
|
61
|
-
throw new Error(`The game is over (${endText(end)}): new_game starts another. Nothing changed.`);
|
|
61
|
+
throw new Error(`The game is over (${endText(end, current.ai)}): new_game starts another. Nothing changed.`);
|
|
62
62
|
}
|
|
63
63
|
return current;
|
|
64
64
|
}
|
|
@@ -66,9 +66,20 @@ export function gameGoingOn(folder, now) {
|
|
|
66
66
|
export function unplayed(summary) {
|
|
67
67
|
return summary.lastMoves === "";
|
|
68
68
|
}
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
/**
|
|
70
|
+
* How a game ended, in words: its result, and how it came when known, each colour named with who played it, the AI as `aiName`:
|
|
71
|
+
* "0-1, White (the player) resigns", "1-0, White (you) checkmates Black (the player)".
|
|
72
|
+
*/
|
|
73
|
+
export function endText({ result, termination }, ai, aiName = "you") {
|
|
74
|
+
if (termination === undefined) {
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
const named = (colour) => `${colour} (${colour.toLowerCase() === ai ? aiName : "the player"})`;
|
|
78
|
+
if (termination === "checkmate" && result !== "1/2-1/2") {
|
|
79
|
+
const winner = result === "1-0" ? "White" : "Black";
|
|
80
|
+
return `${result}, ${named(winner)} checkmates ${named(winner === "White" ? "Black" : "White")}`;
|
|
81
|
+
}
|
|
82
|
+
return `${result}, ${termination.replace(/\b(White|Black)\b/g, named)}`;
|
|
72
83
|
}
|
|
73
84
|
/** The last plies of a record, numbered as a player writes them, from the move number and side of its FEN. */
|
|
74
85
|
export function lastMoves(record, count) {
|
|
@@ -22,5 +22,5 @@ export declare function riskLine(facts: Facts): string | undefined;
|
|
|
22
22
|
export declare function colourName(side: Side): string;
|
|
23
23
|
/** The material as the player sees it beside the board (D-59): who is ahead and by how much, and the pieces each side took. */
|
|
24
24
|
export declare function materialText(material: Material): string;
|
|
25
|
-
/** Who is ahead in material, and by how many pawns: White +2, or even. */
|
|
25
|
+
/** Who is ahead in material, and by how many pawns, with the unit, so that no AI reads it as pieces: White +2 in pawns, or even. */
|
|
26
26
|
export declare function balanceText(balance: number): string;
|
|
@@ -51,9 +51,9 @@ export function materialText(material) {
|
|
|
51
51
|
}
|
|
52
52
|
return `${ahead} (White took ${takenText(material.taken.white)}, Black took ${takenText(material.taken.black)})`;
|
|
53
53
|
}
|
|
54
|
-
/** Who is ahead in material, and by how many pawns: White +2, or even. */
|
|
54
|
+
/** Who is ahead in material, and by how many pawns, with the unit, so that no AI reads it as pieces: White +2 in pawns, or even. */
|
|
55
55
|
export function balanceText(balance) {
|
|
56
|
-
return balance === 0 ? "even" : `${balance > 0 ? "White" : "Black"} +${Math.abs(balance)}`;
|
|
56
|
+
return balance === 0 ? "even" : `${balance > 0 ? "White" : "Black"} +${Math.abs(balance)} in pawns`;
|
|
57
57
|
}
|
|
58
58
|
/** The pieces taken, counted by kind: Q 2R 7P. */
|
|
59
59
|
function takenText(taken) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { boardText, drawingsText, moods, moodTag } from "../../core/index.js";
|
|
3
3
|
import { commentWithPlan, talkLimits } from "../../games/index.js";
|
|
4
|
-
import { coachAgent, ponderAgentFor } from "../../host-files/index.js";
|
|
4
|
+
import { coachAgent, hasDoorbell, ponderAgentFor } from "../../host-files/index.js";
|
|
5
5
|
import { gameFolder, helpsIn } from "../../profiles/index.js";
|
|
6
6
|
import { agentRuns, agentsLine, arrowsInput, closingLines, coachRuns, drawingsOf, give, moveLines, playMove, refuseUnseenPosition, shownMove, squaresInput, waitingWords, withAnalyst } from "../answers/index.js";
|
|
7
7
|
import { defineCommand, sentence } from "../registry/index.js";
|
|
@@ -36,12 +36,12 @@ export const aiMove = defineCommand({
|
|
|
36
36
|
// The player's words are read before the move, which would count as the answer to them; a move refused leaves them for the next call.
|
|
37
37
|
const waiting = waitingWords(context);
|
|
38
38
|
const words = [commentWithPlan(comment, plan), drawingsText(drawings), mood === undefined ? "" : moodTag(mood)].filter((part) => part !== "").join(" ");
|
|
39
|
-
const played = playMove(folder, context.now(), "ai", move, (message, legal) => `${message} Legal: ${legal.join(" ")}. Nothing changed.`, { comment: words, offerDraw });
|
|
39
|
+
const played = playMove(folder, context.now(), "ai", move, (message, legal) => `${message} Legal: ${legal.join(" ")}. Nothing changed.`, { comment: words, offerDraw, doorbell: hasDoorbell(context.host?.()) });
|
|
40
40
|
const agents = agentsAfter(context, folder, played);
|
|
41
41
|
const board = context.pageOpen?.() === true ? {} : { board: true };
|
|
42
42
|
return { ...played, game: withAnalyst(played.game, context), words: give(context, waiting), ...(agents.length === 0 ? {} : { agents }), ...board };
|
|
43
43
|
},
|
|
44
|
-
format: (result) => {
|
|
44
|
+
format: (result, context) => {
|
|
45
45
|
// The mood the AI has just given is not told back: player_move and status tell it while it lasts (Priya).
|
|
46
46
|
const game = { ...result.game };
|
|
47
47
|
delete game.mood;
|
|
@@ -51,7 +51,7 @@ export const aiMove = defineCommand({
|
|
|
51
51
|
...moveLines({ ...result, game }),
|
|
52
52
|
...(result.board === true ? ["No board page is open: copy this board into your message.", boardText(result.game.fen)] : []),
|
|
53
53
|
...(result.agents === undefined ? [] : [agentsLine(result.agents)]),
|
|
54
|
-
...closingLines(result.game, result.words),
|
|
54
|
+
...closingLines(result.game, result.words, false, hasDoorbell(context?.host?.())),
|
|
55
55
|
].join("\n");
|
|
56
56
|
},
|
|
57
57
|
});
|
|
@@ -3,7 +3,7 @@ import { badges, Game, leadText, levelOf, materialOf, withSanLetters } from "../
|
|
|
3
3
|
import { archiveCoached, coachWaitingSince, coachWords, endOf, keepHint, keepJudgment, keepLead, loadGame, startCoach } from "../../games/index.js";
|
|
4
4
|
import { agentHosts, coachAgent } from "../../host-files/index.js";
|
|
5
5
|
import { activeProfile, gameFolder, helpsIn } from "../../profiles/index.js";
|
|
6
|
-
import { aheadWords, arrowsInput, clockLine, clockSummaryOf, colourName, compactView, drawingsOf, knownMoves, lastMoves, leadFrom, leadInput, materialText, noteInput, openingLine, riskLine, speechLine, speechOf, squaresInput, } from "../answers/index.js";
|
|
6
|
+
import { aheadWords, arrowsInput, clockLine, clockSummaryOf, colourName, compactView, drawingsOf, endText, knownMoves, lastMoves, leadFrom, leadInput, materialText, noteInput, openingLine, openingTags, riskLine, speechLine, speechOf, squaresInput, } from "../answers/index.js";
|
|
7
7
|
import { ratingsText } from "../profiles/index.js";
|
|
8
8
|
import { defineCommand } from "../registry/index.js";
|
|
9
9
|
import { waitForPlayer, wordsOf } from "./ponder.js";
|
|
@@ -149,7 +149,7 @@ function startOf(current, folder, context) {
|
|
|
149
149
|
const head = over === undefined
|
|
150
150
|
? `Game "${place.game}", position ${plies}: the player (${colourName(player)}) to move, move ${game.position.moveNumber}; the opponent plays ${colourName(ai)} at the `
|
|
151
151
|
+ `${levelOf(record)} level.`
|
|
152
|
-
: `Game "${place.game}", position ${plies}: the game is over, ${over
|
|
152
|
+
: `Game "${place.game}", position ${plies}: the game is over, ${endText(over, ai, "the opponent")}, with ${ender} move.`;
|
|
153
153
|
const speech = speechOf(folder, context.language);
|
|
154
154
|
const clock = clockSummaryOf(current, context.now());
|
|
155
155
|
const keepLine = over === undefined
|
|
@@ -160,7 +160,7 @@ function startOf(current, folder, context) {
|
|
|
160
160
|
head,
|
|
161
161
|
speech === undefined ? "The player speaks English." : speechLine(speech),
|
|
162
162
|
...playerLines(context),
|
|
163
|
-
...movesLines(
|
|
163
|
+
...movesLines(game),
|
|
164
164
|
...(clock === undefined ? [] : [clockLine(clock, ai)]),
|
|
165
165
|
...(judged === undefined ? [] : judgedLines(current, judged, player)),
|
|
166
166
|
...(over === undefined ? nowLines(current, folder, player) : []),
|
|
@@ -168,8 +168,9 @@ function startOf(current, folder, context) {
|
|
|
168
168
|
];
|
|
169
169
|
}
|
|
170
170
|
/** The game's moves as the coach reads them, with the position a game set up began in, the last ones of a long game, and its opening by the book (Anatolij). */
|
|
171
|
-
function movesLines(
|
|
172
|
-
const
|
|
171
|
+
function movesLines(game) {
|
|
172
|
+
const { record } = game;
|
|
173
|
+
const opening = openingLine(game);
|
|
173
174
|
return [
|
|
174
175
|
...(record.tags.FEN === undefined ? [] : [`The game began from a set position: ${record.tags.FEN}`]),
|
|
175
176
|
`${record.moves.length > pliesShown ? `Moves (the last ${pliesShown} plies)` : "Moves"}: ${lastMoves(record, pliesShown)}`,
|
|
@@ -291,7 +292,7 @@ function keep(folder, current, place, { ahead, badge, note, hint, arrows, square
|
|
|
291
292
|
const hintLeft = hint !== undefined && !toMove;
|
|
292
293
|
if (over) {
|
|
293
294
|
// The game went to the archive with the move that ended it: the judgment goes there too.
|
|
294
|
-
archiveCoached(folder, current);
|
|
295
|
+
archiveCoached(folder, current, openingTags(current.game.record));
|
|
295
296
|
return { kept: place, then: "over", saved, hintLeft };
|
|
296
297
|
}
|
|
297
298
|
if (!toMove) {
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { canMate, endWithMood, Game, moods, timeLeft } from "../../core/index.js";
|
|
3
3
|
import { archiveEnded, bothSidesMoved, drawOfferOf, saveGame } from "../../games/index.js";
|
|
4
4
|
import { gameFolder } from "../../profiles/index.js";
|
|
5
|
-
import { colourName, gameGoingOn, nextStep, positionLines, refuseUnseenPosition, summaryOf, withAnalyst } from "../answers/index.js";
|
|
5
|
+
import { colourName, gameGoingOn, nextStep, openingTags, positionLines, refuseUnseenPosition, summaryOf, withAnalyst } from "../answers/index.js";
|
|
6
6
|
import { defineCommand } from "../registry/index.js";
|
|
7
7
|
/** The endings the board cannot see: a resignation, a draw agreed on an offer (D-65). Checkmate, stalemate and the draws by rule end the game by themselves (D-37). */
|
|
8
8
|
export const endGame = defineCommand({
|
|
@@ -35,7 +35,7 @@ export const endGame = defineCommand({
|
|
|
35
35
|
? { ...current, game }
|
|
36
36
|
: { ...current, game, clock: { ...current.clock, left: timeLeft(current.clock, current.game.position.turn, now), since: now.toISOString() } };
|
|
37
37
|
saveGame(folder, ended);
|
|
38
|
-
const { archive, warning } = archiveEnded(folder, ended);
|
|
38
|
+
const { archive, warning } = archiveEnded(folder, ended, openingTags(ended.game.record));
|
|
39
39
|
const summary = withAnalyst(summaryOf(ended, now, archive), context);
|
|
40
40
|
return warning === undefined ? { game: summary } : { game: summary, warning };
|
|
41
41
|
},
|