chesspal 0.0.1 → 0.2.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 +99 -2
- package/dist/cli/chat-hook.d.ts +2 -0
- package/dist/cli/chat-hook.js +33 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +28 -0
- package/dist/cli/command-line.d.ts +7 -0
- package/dist/cli/command-line.js +177 -0
- package/dist/commands/agents.d.ts +26 -0
- package/dist/commands/agents.js +72 -0
- package/dist/commands/ai-move.d.ts +28 -0
- package/dist/commands/ai-move.js +85 -0
- package/dist/commands/ai-say.d.ts +22 -0
- package/dist/commands/ai-say.js +58 -0
- package/dist/commands/analyse-game.d.ts +65 -0
- package/dist/commands/analyse-game.js +265 -0
- package/dist/commands/annotate-game.d.ts +39 -0
- package/dist/commands/annotate-game.js +131 -0
- package/dist/commands/answer-lines.d.ts +28 -0
- package/dist/commands/answer-lines.js +144 -0
- package/dist/commands/clock.d.ts +15 -0
- package/dist/commands/clock.js +45 -0
- package/dist/commands/coach.d.ts +45 -0
- package/dist/commands/coach.js +344 -0
- package/dist/commands/context.d.ts +67 -0
- package/dist/commands/context.js +37 -0
- package/dist/commands/delete-game.d.ts +7 -0
- package/dist/commands/delete-game.js +30 -0
- package/dist/commands/delete-review.d.ts +8 -0
- package/dist/commands/delete-review.js +34 -0
- package/dist/commands/draw-offer.d.ts +10 -0
- package/dist/commands/draw-offer.js +43 -0
- package/dist/commands/drawings.d.ts +13 -0
- package/dist/commands/drawings.js +25 -0
- package/dist/commands/end-game.d.ts +23 -0
- package/dist/commands/end-game.js +67 -0
- package/dist/commands/export-game.d.ts +14 -0
- package/dist/commands/export-game.js +27 -0
- package/dist/commands/game-summary.d.ts +78 -0
- package/dist/commands/game-summary.js +102 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.js +64 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +73 -0
- package/dist/commands/lead-input.d.ts +8 -0
- package/dist/commands/lead-input.js +21 -0
- package/dist/commands/levels.d.ts +9 -0
- package/dist/commands/levels.js +25 -0
- package/dist/commands/look.d.ts +31 -0
- package/dist/commands/look.js +64 -0
- package/dist/commands/new-game.d.ts +68 -0
- package/dist/commands/new-game.js +218 -0
- package/dist/commands/opening-book.d.ts +13 -0
- package/dist/commands/opening-book.js +84 -0
- package/dist/commands/page-link.d.ts +11 -0
- package/dist/commands/page-link.js +28 -0
- package/dist/commands/past-games.d.ts +11 -0
- package/dist/commands/past-games.js +47 -0
- package/dist/commands/play-move.d.ts +37 -0
- package/dist/commands/play-move.js +94 -0
- package/dist/commands/player-move.d.ts +13 -0
- package/dist/commands/player-move.js +51 -0
- package/dist/commands/player-say.d.ts +5 -0
- package/dist/commands/player-say.js +16 -0
- package/dist/commands/ponder.d.ts +52 -0
- package/dist/commands/ponder.js +179 -0
- package/dist/commands/position.d.ts +14 -0
- package/dist/commands/position.js +130 -0
- package/dist/commands/profiles.d.ts +69 -0
- package/dist/commands/profiles.js +163 -0
- package/dist/commands/registry.d.ts +59 -0
- package/dist/commands/registry.js +62 -0
- package/dist/commands/say.d.ts +16 -0
- package/dist/commands/say.js +84 -0
- package/dist/commands/speech.d.ts +8 -0
- package/dist/commands/speech.js +20 -0
- package/dist/commands/status.d.ts +35 -0
- package/dist/commands/status.js +128 -0
- package/dist/commands/study-games.d.ts +5 -0
- package/dist/commands/study-games.js +40 -0
- package/dist/commands/study.d.ts +38 -0
- package/dist/commands/study.js +106 -0
- package/dist/commands/view.d.ts +14 -0
- package/dist/commands/view.js +125 -0
- package/dist/core/board-text.d.ts +7 -0
- package/dist/core/board-text.js +18 -0
- package/dist/core/book.json +1 -0
- package/dist/core/clock.d.ts +45 -0
- package/dist/core/clock.js +109 -0
- package/dist/core/facts.d.ts +55 -0
- package/dist/core/facts.js +175 -0
- package/dist/core/game.d.ts +95 -0
- package/dist/core/game.js +352 -0
- package/dist/core/illegal.d.ts +19 -0
- package/dist/core/illegal.js +141 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +12 -0
- package/dist/core/lead.d.ts +15 -0
- package/dist/core/lead.js +40 -0
- package/dist/core/levels.d.ts +20 -0
- package/dist/core/levels.js +33 -0
- package/dist/core/material.d.ts +9 -0
- package/dist/core/material.js +12 -0
- package/dist/core/moods.d.ts +21 -0
- package/dist/core/moods.js +45 -0
- package/dist/core/openings.d.ts +35 -0
- package/dist/core/openings.js +98 -0
- package/dist/core/openings.json +1 -0
- package/dist/core/pgn.d.ts +10 -0
- package/dist/core/pgn.js +213 -0
- package/dist/core/profile.d.ts +117 -0
- package/dist/core/profile.js +232 -0
- package/dist/core/record.d.ts +21 -0
- package/dist/core/record.js +25 -0
- package/dist/core/review.d.ts +80 -0
- package/dist/core/review.js +146 -0
- package/dist/games/ai-model.d.ts +5 -0
- package/dist/games/ai-model.js +15 -0
- package/dist/games/analysis.d.ts +12 -0
- package/dist/games/analysis.js +26 -0
- package/dist/games/archive.d.ts +63 -0
- package/dist/games/archive.js +242 -0
- package/dist/games/chat-start.d.ts +16 -0
- package/dist/games/chat-start.js +26 -0
- package/dist/games/coach.d.ts +57 -0
- package/dist/games/coach.js +144 -0
- package/dist/games/current-game.d.ts +56 -0
- package/dist/games/current-game.js +134 -0
- package/dist/games/files.d.ts +9 -0
- package/dist/games/files.js +53 -0
- package/dist/games/index.d.ts +11 -0
- package/dist/games/index.js +12 -0
- package/dist/games/plans.d.ts +9 -0
- package/dist/games/plans.js +23 -0
- package/dist/games/table-talk.d.ts +12 -0
- package/dist/games/table-talk.js +25 -0
- package/dist/games/talk.d.ts +36 -0
- package/dist/games/talk.js +131 -0
- package/dist/games/thoughts.d.ts +15 -0
- package/dist/games/thoughts.js +60 -0
- package/dist/home/data-folder.d.ts +12 -0
- package/dist/home/data-folder.js +55 -0
- package/dist/home/index.d.ts +2 -0
- package/dist/home/index.js +3 -0
- package/dist/home/open-folder.d.ts +2 -0
- package/dist/home/open-folder.js +14 -0
- package/dist/host-files/agents.d.ts +20 -0
- package/dist/host-files/agents.js +53 -0
- package/dist/host-files/host-files.d.ts +15 -0
- package/dist/host-files/host-files.js +182 -0
- package/dist/host-files/index.d.ts +2 -0
- package/dist/host-files/index.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/page/app/anchor.js +88 -0
- package/dist/page/app/app.js +458 -0
- package/dist/page/app/keys.js +94 -0
- package/dist/page/app/phase.js +30 -0
- package/dist/page/app/served.js +73 -0
- package/dist/page/avatars/avatars.js +19 -0
- package/dist/page/avatars/beanie.webp +0 -0
- package/dist/page/avatars/beard.webp +0 -0
- package/dist/page/avatars/bun.webp +0 -0
- package/dist/page/avatars/cat.webp +0 -0
- package/dist/page/avatars/curls.webp +0 -0
- package/dist/page/avatars/glasses.webp +0 -0
- package/dist/page/avatars/moustache.webp +0 -0
- package/dist/page/avatars/owl.webp +0 -0
- package/dist/page/avatars/short-hair.webp +0 -0
- package/dist/page/avatars/silver-bob.webp +0 -0
- package/dist/page/avatars/thought.js +10 -0
- package/dist/page/avatars/torretta.js +38 -0
- package/dist/page/base.css +160 -0
- package/dist/page/board/board.css +959 -0
- package/dist/page/board/board.js +125 -0
- package/dist/page/board/explain.js +58 -0
- package/dist/page/board/marks.js +160 -0
- package/dist/page/board/motion.js +71 -0
- package/dist/page/board/pointer.js +239 -0
- package/dist/page/board/squares.js +26 -0
- package/dist/page/dialogs/analyses.js +195 -0
- package/dist/page/dialogs/dialog.js +26 -0
- package/dist/page/dialogs/dialogs.css +1228 -0
- package/dist/page/dialogs/new-game.js +42 -0
- package/dist/page/dialogs/profiles.js +152 -0
- package/dist/page/dialogs/search.js +31 -0
- package/dist/page/dialogs/settings.js +42 -0
- package/dist/page/dialogs/studied.js +39 -0
- package/dist/page/dialogs/study.js +51 -0
- package/dist/page/game/badges.js +50 -0
- package/dist/page/game/browse.js +17 -0
- package/dist/page/game/card-fold.js +40 -0
- package/dist/page/game/column.js +51 -0
- package/dist/page/game/ending.js +73 -0
- package/dist/page/game/game.css +577 -0
- package/dist/page/game/move-list.js +72 -0
- package/dist/page/game/opening-book.js +14 -0
- package/dist/page/game/plies.js +82 -0
- package/dist/page/game/result-tile.js +14 -0
- package/dist/page/helps/helps.css +689 -0
- package/dist/page/helps/helps.js +220 -0
- package/dist/page/helps/view.js +127 -0
- package/dist/page/layout/fold-tab.js +11 -0
- package/dist/page/layout/folds.js +43 -0
- package/dist/page/layout/header.js +28 -0
- package/dist/page/layout/layout.css +834 -0
- package/dist/page/lead/lead.js +82 -0
- package/dist/page/lead/view.js +38 -0
- package/dist/page/main.js +8 -0
- package/dist/page/pieces/CREDITS.md +19 -0
- package/dist/page/pieces/capablanca-small.svg +20 -0
- package/dist/page/pieces/capablanca.svg +22 -0
- package/dist/page/pieces/nitida-small.svg +20 -0
- package/dist/page/pieces/nitida.svg +21 -0
- package/dist/page/pieces/pieces.js +28 -0
- package/dist/page/pieces/torretta-small.svg +20 -0
- package/dist/page/pieces/torretta.svg +21 -0
- package/dist/page/players/card.js +56 -0
- package/dist/page/players/clock-time.js +12 -0
- package/dist/page/players/model-name.js +20 -0
- package/dist/page/profile/profile.js +99 -0
- package/dist/page/profile/settings.js +77 -0
- package/dist/page/sounds/capture.ogg +0 -0
- package/dist/page/sounds/check.ogg +0 -0
- package/dist/page/sounds/draw.ogg +0 -0
- package/dist/page/sounds/flag.ogg +0 -0
- package/dist/page/sounds/mate.ogg +0 -0
- package/dist/page/sounds/move.ogg +0 -0
- package/dist/page/sounds/offer.ogg +0 -0
- package/dist/page/sounds/resign.ogg +0 -0
- package/dist/page/sounds/sounds.js +105 -0
- package/dist/page/style.css +9 -0
- package/dist/page/talk/talk.css +958 -0
- package/dist/page/talk/talk.js +165 -0
- package/dist/page/talk/voice.js +38 -0
- package/dist/page/text.js +711 -0
- package/dist/page/theme.css +292 -0
- package/dist/profiles/avatar.d.ts +23 -0
- package/dist/profiles/avatar.js +84 -0
- package/dist/profiles/front-matter.d.ts +9 -0
- package/dist/profiles/front-matter.js +33 -0
- package/dist/profiles/index.d.ts +3 -0
- package/dist/profiles/index.js +5 -0
- package/dist/profiles/profiles.d.ts +69 -0
- package/dist/profiles/profiles.js +254 -0
- package/dist/profiles/settings.d.ts +12 -0
- package/dist/profiles/settings.js +46 -0
- package/dist/server/bell.d.ts +68 -0
- package/dist/server/bell.js +225 -0
- package/dist/server/caller.d.ts +21 -0
- package/dist/server/caller.js +132 -0
- package/dist/server/mcp.d.ts +13 -0
- package/dist/server/mcp.js +207 -0
- package/dist/server/page/analyses.d.ts +23 -0
- package/dist/server/page/analyses.js +65 -0
- package/dist/server/page/files.d.ts +5 -0
- package/dist/server/page/files.js +92 -0
- package/dist/server/page/game.d.ts +6 -0
- package/dist/server/page/game.js +381 -0
- package/dist/server/page/http.d.ts +29 -0
- package/dist/server/page/http.js +68 -0
- package/dist/server/page/page.d.ts +41 -0
- package/dist/server/page/page.js +229 -0
- package/dist/server/page/profiles.d.ts +19 -0
- package/dist/server/page/profiles.js +215 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/instructions/analysis.md +4 -0
- package/instructions/analyst-agent.md +8 -0
- package/instructions/analyst.md +4 -0
- package/instructions/coach.md +11 -0
- package/instructions/opponent.md +15 -0
- package/instructions/ponder.md +9 -0
- package/package.json +66 -5
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Side } from "./game.ts";
|
|
2
|
+
/** A time control: the time each side starts with and the time added after each of its moves, in milliseconds. */
|
|
3
|
+
export interface TimeControl {
|
|
4
|
+
readonly base: number;
|
|
5
|
+
readonly increment: number;
|
|
6
|
+
}
|
|
7
|
+
/** The time each side has left, in milliseconds, at the start of the turn of the side to move, `since` (an ISO date). */
|
|
8
|
+
export interface Clock {
|
|
9
|
+
readonly control: TimeControl;
|
|
10
|
+
readonly left: Readonly<Record<Side, number>>;
|
|
11
|
+
readonly since: string;
|
|
12
|
+
/** The clock waits for the player to start it, on the board page or with their first move: no side's time runs until then. */
|
|
13
|
+
readonly waiting?: true;
|
|
14
|
+
}
|
|
15
|
+
/** A time control written as minutes and seconds added, as Lichess and chess.com write it: 10+5, 3+0, 0.5+0; the minutes make whole seconds. */
|
|
16
|
+
export declare function timeControlOf(text: string): TimeControl;
|
|
17
|
+
/** A time control as minutes and seconds added: 10+5. */
|
|
18
|
+
export declare function timeControlText({ base, increment }: TimeControl): string;
|
|
19
|
+
/** A time control as PGN's TimeControl tag writes it, in seconds: 600+5. */
|
|
20
|
+
export declare function timeControlTag({ base, increment }: TimeControl): string;
|
|
21
|
+
/** A time control from PGN's TimeControl tag, in seconds: 600+5, or 600 with nothing added; none for a tag of another form, such as - or ?. */
|
|
22
|
+
export declare function timeControlOfTag(tag: string): TimeControl | undefined;
|
|
23
|
+
/** The clock at the start of a game: each side with the whole time, `waiting` for the player to start it or running at once. */
|
|
24
|
+
export declare function startClock(control: TimeControl, now: Date, waiting: boolean): Clock;
|
|
25
|
+
/** The clock started `now`, the turn of the side to move beginning then; a clock already running stays as it is. */
|
|
26
|
+
export declare function clockStarted(clock: Clock, now: Date): Clock;
|
|
27
|
+
/** The time each side has left `now`: the side to move, `turn`, has spent the time since its turn began; never below nothing, and nothing while it waits. */
|
|
28
|
+
export declare function timeLeft(clock: Clock, turn: Side, now: Date): Record<Side, number>;
|
|
29
|
+
/**
|
|
30
|
+
* The clock after `mover`'s move `now`: the time spent taken off, less an `allowance` for time that was not theirs, the increment added, and the other side's
|
|
31
|
+
* turn beginning. A clock that waits takes nothing off and stays waiting, unless the move `starts` it: the player's first.
|
|
32
|
+
*/
|
|
33
|
+
export declare function clockAfterMove(clock: Clock, mover: Side, now: Date, allowance?: number, starts?: boolean): Clock;
|
|
34
|
+
/** A time as a clock shows it, cut to the second: 4:32, 1:02:03. */
|
|
35
|
+
export declare function clockText(milliseconds: number): string;
|
|
36
|
+
/** The time left after a move as PGN's [%clk] writes it, in a move's comment: [%clk 0:04:32]. */
|
|
37
|
+
export declare function clockTag(milliseconds: number): string;
|
|
38
|
+
/** The time left a move's comment gives as [%clk 0:04:32], in milliseconds; none without one. */
|
|
39
|
+
export declare function clockOfComment(comment: string): number | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Whether `side` could still mate by some series of legal moves, as a fallen flag asks (FIDE 6.9), by Lichess's rule: not with the king alone; not with
|
|
42
|
+
* king and one knight unless the other side has a piece other than queens to block its king; not with bishops alone, all on squares of one colour, when the
|
|
43
|
+
* other side has no knight or pawn; with anything else it could, if the other side helped.
|
|
44
|
+
*/
|
|
45
|
+
export declare function canMate(fen: string, side: Side): boolean;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** A time control written as minutes and seconds added, as Lichess and chess.com write it: 10+5, 3+0, 0.5+0; the minutes make whole seconds. */
|
|
2
|
+
export function timeControlOf(text) {
|
|
3
|
+
const match = /^\s*(\d+(?:\.\d+)?)\s*\+\s*(\d+)\s*$/.exec(text);
|
|
4
|
+
const seconds = Number(match?.[1]) * 60;
|
|
5
|
+
const base = Math.round(seconds) * second;
|
|
6
|
+
const increment = Number(match?.[2]) * second;
|
|
7
|
+
if (match === null || Math.abs(seconds - Math.round(seconds)) > 1e-9 || base <= 0 || base > 180 * minute || increment > 180 * second) {
|
|
8
|
+
throw new Error(`"${text}" is not a time control: write minutes, in whole seconds, and seconds added, such as 10+5, 3+0 or 0.5+0, up to 180+180.`);
|
|
9
|
+
}
|
|
10
|
+
return { base, increment };
|
|
11
|
+
}
|
|
12
|
+
/** A time control as minutes and seconds added: 10+5. */
|
|
13
|
+
export function timeControlText({ base, increment }) {
|
|
14
|
+
return `${base / minute}+${increment / second}`;
|
|
15
|
+
}
|
|
16
|
+
/** A time control as PGN's TimeControl tag writes it, in seconds: 600+5. */
|
|
17
|
+
export function timeControlTag({ base, increment }) {
|
|
18
|
+
return `${Math.round(base / second)}+${Math.round(increment / second)}`;
|
|
19
|
+
}
|
|
20
|
+
/** A time control from PGN's TimeControl tag, in seconds: 600+5, or 600 with nothing added; none for a tag of another form, such as - or ?. */
|
|
21
|
+
export function timeControlOfTag(tag) {
|
|
22
|
+
const [, base, increment] = /^\s*(\d+)(?:\+(\d+))?\s*$/.exec(tag) ?? [];
|
|
23
|
+
return base === undefined || Number(base) === 0 ? undefined : { base: Number(base) * second, increment: Number(increment ?? 0) * second };
|
|
24
|
+
}
|
|
25
|
+
/** The clock at the start of a game: each side with the whole time, `waiting` for the player to start it or running at once. */
|
|
26
|
+
export function startClock(control, now, waiting) {
|
|
27
|
+
const clock = { control, left: { white: control.base, black: control.base }, since: now.toISOString() };
|
|
28
|
+
return waiting ? { ...clock, waiting: true } : clock;
|
|
29
|
+
}
|
|
30
|
+
/** The clock started `now`, the turn of the side to move beginning then; a clock already running stays as it is. */
|
|
31
|
+
export function clockStarted(clock, now) {
|
|
32
|
+
return clock.waiting === true ? { control: clock.control, left: clock.left, since: now.toISOString() } : clock;
|
|
33
|
+
}
|
|
34
|
+
/** The time each side has left `now`: the side to move, `turn`, has spent the time since its turn began; never below nothing, and nothing while it waits. */
|
|
35
|
+
export function timeLeft(clock, turn, now) {
|
|
36
|
+
const spent = clock.waiting === true ? 0 : Math.max(0, now.getTime() - Date.parse(clock.since));
|
|
37
|
+
return { ...clock.left, [turn]: Math.max(0, clock.left[turn] - spent) };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The clock after `mover`'s move `now`: the time spent taken off, less an `allowance` for time that was not theirs, the increment added, and the other side's
|
|
41
|
+
* turn beginning. A clock that waits takes nothing off and stays waiting, unless the move `starts` it: the player's first.
|
|
42
|
+
*/
|
|
43
|
+
export function clockAfterMove(clock, mover, now, allowance = 0, starts = false) {
|
|
44
|
+
if (clock.waiting === true) {
|
|
45
|
+
return starts ? clockStarted(clock, now) : { ...clock, since: now.toISOString() };
|
|
46
|
+
}
|
|
47
|
+
const left = timeLeft(clock, mover, new Date(now.getTime() - allowance));
|
|
48
|
+
return { ...clock, left: { ...left, [mover]: left[mover] + clock.control.increment }, since: now.toISOString() };
|
|
49
|
+
}
|
|
50
|
+
/** A time as a clock shows it, cut to the second: 4:32, 1:02:03. */
|
|
51
|
+
export function clockText(milliseconds) {
|
|
52
|
+
const seconds = Math.floor(milliseconds / second);
|
|
53
|
+
const [hours, minutes] = [Math.floor(seconds / 3600), Math.floor(seconds / 60) % 60];
|
|
54
|
+
const rest = String(seconds % 60).padStart(2, "0");
|
|
55
|
+
return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${rest}` : `${minutes}:${rest}`;
|
|
56
|
+
}
|
|
57
|
+
/** The time left after a move as PGN's [%clk] writes it, in a move's comment: [%clk 0:04:32]. */
|
|
58
|
+
export function clockTag(milliseconds) {
|
|
59
|
+
const text = clockText(milliseconds);
|
|
60
|
+
return `[%clk ${text.split(":").length === 2 ? `0:${text.padStart(5, "0")}` : text}]`;
|
|
61
|
+
}
|
|
62
|
+
/** The time left a move's comment gives as [%clk 0:04:32], in milliseconds; none without one. */
|
|
63
|
+
export function clockOfComment(comment) {
|
|
64
|
+
const [, hours, minutes, seconds] = /\[%clk (\d+):(\d{1,2}):(\d{1,2}(?:\.\d+)?)\]/.exec(comment) ?? [];
|
|
65
|
+
return hours === undefined ? undefined : Math.round(((Number(hours) * 60 + Number(minutes)) * 60 + Number(seconds)) * second);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether `side` could still mate by some series of legal moves, as a fallen flag asks (FIDE 6.9), by Lichess's rule: not with the king alone; not with
|
|
69
|
+
* king and one knight unless the other side has a piece other than queens to block its king; not with bishops alone, all on squares of one colour, when the
|
|
70
|
+
* other side has no knight or pawn; with anything else it could, if the other side helped.
|
|
71
|
+
*/
|
|
72
|
+
export function canMate(fen, side) {
|
|
73
|
+
const squares = boardSquares(fen);
|
|
74
|
+
const own = squares.filter(({ letter }) => (letter === letter.toUpperCase()) === (side === "white")).map(({ letter }) => letter.toLowerCase());
|
|
75
|
+
const other = squares.filter(({ letter }) => (letter === letter.toUpperCase()) !== (side === "white")).map(({ letter }) => letter.toLowerCase());
|
|
76
|
+
if (own.length === 0) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (own.length === 1 && own[0] === "n") {
|
|
80
|
+
return other.some((letter) => letter !== "q");
|
|
81
|
+
}
|
|
82
|
+
if (own.every((letter) => letter === "b")) {
|
|
83
|
+
const colours = new Set(squares.filter(({ letter }) => letter.toLowerCase() === "b").map(({ dark }) => dark));
|
|
84
|
+
return colours.size > 1 || other.some((letter) => letter === "n" || letter === "p");
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
/** The pieces of a FEN but the kings, each with whether its square is dark. */
|
|
89
|
+
function boardSquares(fen) {
|
|
90
|
+
const rows = (fen.split(" ")[0] ?? "").split("/");
|
|
91
|
+
return rows.flatMap((row, index) => {
|
|
92
|
+
let file = 0;
|
|
93
|
+
const found = [];
|
|
94
|
+
for (const letter of row) {
|
|
95
|
+
if (/\d/.test(letter)) {
|
|
96
|
+
file += Number(letter);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
if (!/k/i.test(letter)) {
|
|
100
|
+
found.push({ letter, dark: (file + (8 - index)) % 2 === 1 });
|
|
101
|
+
}
|
|
102
|
+
file += 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return found;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const second = 1000;
|
|
109
|
+
const minute = 60 * second;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Chess, type Color, type Square } from "chess.js";
|
|
2
|
+
import type { Move, Side } from "./game.ts";
|
|
3
|
+
/** A piece's letter, as SAN writes it, with P for a pawn. */
|
|
4
|
+
export type PieceLetter = "K" | "Q" | "R" | "B" | "N" | "P";
|
|
5
|
+
/**
|
|
6
|
+
* A piece on the board with the pieces that could take it and those of its side that could take back: direct attacks, en passant included; a piece pinned
|
|
7
|
+
* to its king only along the pin's line, though it still gives check. A king has no defenders.
|
|
8
|
+
*/
|
|
9
|
+
export interface PieceFacts {
|
|
10
|
+
readonly square: Square;
|
|
11
|
+
readonly piece: PieceLetter;
|
|
12
|
+
readonly side: Side;
|
|
13
|
+
readonly attackers: readonly Square[];
|
|
14
|
+
readonly defenders: readonly Square[];
|
|
15
|
+
/** The rooks, bishops and queens lined up behind an attacker or a defender, by its square: a battery. */
|
|
16
|
+
readonly behind?: Readonly<Partial<Record<Square, readonly Square[]>>>;
|
|
17
|
+
/** Its legal moves in SAN, for the pieces of the side to move only. */
|
|
18
|
+
readonly moves?: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/** A piece pinned to its king, or to a queen or rook of its side worth more than itself when the pinner is worth less or that piece is undefended. */
|
|
21
|
+
export interface Pin {
|
|
22
|
+
readonly square: Square;
|
|
23
|
+
readonly by: Square;
|
|
24
|
+
readonly to: Square;
|
|
25
|
+
}
|
|
26
|
+
/** The facts of a position: data for the AI and the page, never a judgment (D-19). */
|
|
27
|
+
export interface Facts {
|
|
28
|
+
/** Every piece, White's first, each side's from the king down: K Q R B N P. */
|
|
29
|
+
readonly pieces: readonly PieceFacts[];
|
|
30
|
+
/** The pieces giving check to the side to move, when it is in check. */
|
|
31
|
+
readonly checkedBy?: readonly Square[];
|
|
32
|
+
/** Pieces attacked and not defended, kings left out. */
|
|
33
|
+
readonly undefended: readonly Square[];
|
|
34
|
+
/** Pieces defended, but attacked by a piece worth less. */
|
|
35
|
+
readonly attackedByCheaper: readonly Square[];
|
|
36
|
+
/** Pieces defended, but by fewer pieces than attack them, counting only the direct ones on both sides; not those attacked by a cheaper piece. */
|
|
37
|
+
readonly outnumbered: readonly Square[];
|
|
38
|
+
/** Pieces neither attacked nor defended, kings left out: the first targets of a tactic. */
|
|
39
|
+
readonly loose: readonly Square[];
|
|
40
|
+
readonly pins: readonly Pin[];
|
|
41
|
+
/** The checks of the side to move, in SAN. */
|
|
42
|
+
readonly checks: readonly string[];
|
|
43
|
+
/** The checks the other side would have if it were to move; left out when the side to move is in check, since it could not be. */
|
|
44
|
+
readonly otherChecks?: readonly string[];
|
|
45
|
+
}
|
|
46
|
+
/** The facts of a chess.js board, whose legal moves are given: computed once for each position, by its key, and frozen. */
|
|
47
|
+
export declare function factsOf(chess: Chess, legal: readonly Move[]): Facts;
|
|
48
|
+
/** Which pieces attack a square: chess.js's direct attacks, a piece pinned to its king only along the pin's line, and the pawns that take en passant. */
|
|
49
|
+
export type Reach = (square: Square, color: Color) => Square[];
|
|
50
|
+
/** Which pieces attack a square as the facts count them: a piece pinned to its king only along the pin's line, though it still gives check; no en passant. */
|
|
51
|
+
export declare function attacksOf(chess: Chess): Reach;
|
|
52
|
+
/** The squares strictly between two squares on one line. */
|
|
53
|
+
export declare function squaresBetween(from: Square, to: Square): Square[];
|
|
54
|
+
export declare function fileOf(square: Square): number;
|
|
55
|
+
export declare function rankOf(square: Square): number;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// What a position shows, computed without judging it (D-19, D-26): who attacks and defends each piece, what is at risk, the pins, the checks, the moves of each piece.
|
|
2
|
+
import { Chess } from "chess.js";
|
|
3
|
+
/** The facts of a chess.js board, whose legal moves are given: computed once for each position, by its key, and frozen. */
|
|
4
|
+
export function factsOf(chess, legal) {
|
|
5
|
+
const key = chess.hash();
|
|
6
|
+
let facts = factsByPosition.get(key);
|
|
7
|
+
if (facts === undefined) {
|
|
8
|
+
facts = deepFrozen(computed(chess, legal));
|
|
9
|
+
if (factsByPosition.size >= positionsKept) {
|
|
10
|
+
factsByPosition.delete(factsByPosition.keys().next().value);
|
|
11
|
+
}
|
|
12
|
+
factsByPosition.set(key, facts);
|
|
13
|
+
}
|
|
14
|
+
return facts;
|
|
15
|
+
}
|
|
16
|
+
// The facts of the positions seen last, by their key.
|
|
17
|
+
const factsByPosition = new Map();
|
|
18
|
+
const positionsKept = 256;
|
|
19
|
+
// The usual values, only to compare two pieces: never summed (D-19). A king is never the cheaper one.
|
|
20
|
+
const values = { p: 1, n: 3, b: 3, r: 5, q: 9, k: 100 };
|
|
21
|
+
// The order SAN lists pieces in.
|
|
22
|
+
const order = { k: 0, q: 1, r: 2, b: 3, n: 4, p: 5 };
|
|
23
|
+
// The lines from a square: a step on the file and the rank, and the slider that moves along it besides the queen.
|
|
24
|
+
const lines = [[0, 1, "r"], [0, -1, "r"], [1, 0, "r"], [-1, 0, "r"], [1, 1, "b"], [1, -1, "b"], [-1, 1, "b"], [-1, -1, "b"]];
|
|
25
|
+
const files = "abcdefgh";
|
|
26
|
+
/** Which pieces attack a square as the facts count them: a piece pinned to its king only along the pin's line, though it still gives check; no en passant. */
|
|
27
|
+
export function attacksOf(chess) {
|
|
28
|
+
return alongPins(chess, piecesOf(chess).filter((piece) => piece.type === "k").flatMap((king) => pinsTo(chess, king)));
|
|
29
|
+
}
|
|
30
|
+
/** The squares strictly between two squares on one line. */
|
|
31
|
+
export function squaresBetween(from, to) {
|
|
32
|
+
const ray = rayFrom(from, Math.sign(fileOf(to) - fileOf(from)), Math.sign(rankOf(to) - rankOf(from)));
|
|
33
|
+
return ray.slice(0, ray.indexOf(to));
|
|
34
|
+
}
|
|
35
|
+
export function fileOf(square) {
|
|
36
|
+
return files.indexOf(square[0]);
|
|
37
|
+
}
|
|
38
|
+
export function rankOf(square) {
|
|
39
|
+
return Number(square[1]);
|
|
40
|
+
}
|
|
41
|
+
function computed(chess, legal) {
|
|
42
|
+
const placed = piecesOf(chess);
|
|
43
|
+
const turn = chess.turn();
|
|
44
|
+
const kingPins = placed.filter((piece) => piece.type === "k").flatMap((king) => pinsTo(chess, king));
|
|
45
|
+
const reach = reachOf(chess, kingPins, legal);
|
|
46
|
+
const pieces = placed.map((piece) => pieceFacts(chess, piece, reach, turn === piece.color ? legal.filter((move) => move.from === piece.square) : undefined));
|
|
47
|
+
const factsAt = new Map(pieces.map((piece) => [piece.square, piece]));
|
|
48
|
+
const officers = pieces.filter((piece) => piece.piece !== "K");
|
|
49
|
+
const atRisk = officers.filter((piece) => piece.attackers.length > 0);
|
|
50
|
+
const valueAt = (square) => values[chess.get(square).type];
|
|
51
|
+
const defended = atRisk.filter((piece) => piece.defenders.length > 0);
|
|
52
|
+
const byCheaper = defended.filter((piece) => Math.min(...piece.attackers.map(valueAt)) < valueAt(piece.square));
|
|
53
|
+
const relativePins = placed.filter((piece) => piece.type === "q" || piece.type === "r").flatMap((target) => pinsTo(chess, target).filter((pin) => valueAt(pin.by) < values[target.type] || factsAt.get(target.square)?.defenders.length === 0));
|
|
54
|
+
const king = pieces.find((piece) => piece.piece === "K" && piece.side === sideOf(turn));
|
|
55
|
+
const facts = {
|
|
56
|
+
pieces,
|
|
57
|
+
...(chess.inCheck() && king !== undefined ? { checkedBy: king.attackers } : {}),
|
|
58
|
+
undefended: atRisk.filter((piece) => piece.defenders.length === 0).map((piece) => piece.square),
|
|
59
|
+
attackedByCheaper: byCheaper.map((piece) => piece.square),
|
|
60
|
+
outnumbered: defended.filter((piece) => !byCheaper.includes(piece) && piece.attackers.length > piece.defenders.length).map((piece) => piece.square),
|
|
61
|
+
loose: officers.filter((piece) => piece.attackers.length === 0 && piece.defenders.length === 0).map((piece) => piece.square),
|
|
62
|
+
pins: [...kingPins, ...relativePins].sort((left, right) => compare(left.square, right.square)),
|
|
63
|
+
checks: legal.filter((move) => /[+#]$/.test(move.san)).map((move) => move.san),
|
|
64
|
+
};
|
|
65
|
+
return chess.inCheck() ? facts : { ...facts, otherChecks: otherChecks(chess) };
|
|
66
|
+
}
|
|
67
|
+
function reachOf(chess, kingPins, legal) {
|
|
68
|
+
const direct = alongPins(chess, kingPins);
|
|
69
|
+
const enPassant = enPassantVictim(chess);
|
|
70
|
+
const takers = enPassant === undefined ? [] : legal.filter((move) => move.to === enPassant.to && chess.get(move.from)?.type === "p").map((move) => move.from);
|
|
71
|
+
return (square, color) => (square === enPassant?.square && color === chess.turn() ? [...direct(square, color), ...takers].sort(compare) : direct(square, color));
|
|
72
|
+
}
|
|
73
|
+
function alongPins(chess, kingPins) {
|
|
74
|
+
const pinLines = new Map(kingPins.map((pin) => [pin.square, new Set(squaresBetween(pin.to, pin.by).concat(pin.by))]));
|
|
75
|
+
return (square, color) => {
|
|
76
|
+
// A pinned piece still gives check: the king is never taken.
|
|
77
|
+
const isKing = chess.get(square)?.type === "k";
|
|
78
|
+
return chess.attackers(square, color).filter((from) => isKing || (pinLines.get(from)?.has(square) ?? true)).sort(compare);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function pieceFacts(chess, { square, type, color }, reach, moves) {
|
|
82
|
+
const attackers = reach(square, color === "w" ? "b" : "w");
|
|
83
|
+
const defenders = type === "k" ? [] : reach(square, color);
|
|
84
|
+
const behind = batteries(chess, square, [...attackers, ...defenders]);
|
|
85
|
+
const facts = { square, piece: type.toUpperCase(), side: sideOf(color), attackers, defenders };
|
|
86
|
+
const withBehind = Object.keys(behind).length === 0 ? facts : { ...facts, behind };
|
|
87
|
+
return moves === undefined ? withBehind : { ...withBehind, moves: moves.map((move) => move.san) };
|
|
88
|
+
}
|
|
89
|
+
/** The rooks, bishops and queens of each piece's side lined up behind it, away from the square it attacks or defends. */
|
|
90
|
+
function batteries(chess, target, fronts) {
|
|
91
|
+
const found = {};
|
|
92
|
+
for (const front of fronts) {
|
|
93
|
+
const fileStep = Math.sign(fileOf(front) - fileOf(target));
|
|
94
|
+
const rankStep = Math.sign(rankOf(front) - rankOf(target));
|
|
95
|
+
const straight = fileStep === 0 || rankStep === 0;
|
|
96
|
+
const slider = straight ? "r" : "b";
|
|
97
|
+
const { color, type } = chess.get(front);
|
|
98
|
+
// A pawn beside its target takes en passant and leaves the rank: nothing lines up behind it.
|
|
99
|
+
const onLine = straight ? type !== "p" : Math.abs(fileOf(front) - fileOf(target)) === Math.abs(rankOf(front) - rankOf(target));
|
|
100
|
+
const lined = [];
|
|
101
|
+
for (const square of onLine ? rayFrom(front, fileStep, rankStep) : []) {
|
|
102
|
+
const piece = chess.get(square);
|
|
103
|
+
if (piece === undefined) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (piece.color !== color || (piece.type !== slider && piece.type !== "q")) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
lined.push(square);
|
|
110
|
+
}
|
|
111
|
+
if (lined.length > 0) {
|
|
112
|
+
found[front] = lined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return found;
|
|
116
|
+
}
|
|
117
|
+
/** The pins to a piece: by a rook, bishop or queen on its line, with a piece of its side between them. */
|
|
118
|
+
function pinsTo(chess, target) {
|
|
119
|
+
const pins = [];
|
|
120
|
+
for (const [fileStep, rankStep, slider] of lines) {
|
|
121
|
+
const [first, second] = rayFrom(target.square, fileStep, rankStep).filter((square) => chess.get(square) !== undefined);
|
|
122
|
+
const pinned = first === undefined ? undefined : chess.get(first);
|
|
123
|
+
const pinner = second === undefined ? undefined : chess.get(second);
|
|
124
|
+
if (pinned?.color === target.color && pinner !== undefined && pinner.color !== target.color && (pinner.type === slider || pinner.type === "q")
|
|
125
|
+
&& (target.type === "k" || values[pinned.type] < values[target.type])) {
|
|
126
|
+
pins.push({ square: first, by: second, to: target.square });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return pins;
|
|
130
|
+
}
|
|
131
|
+
/** The pawn that can be taken en passant, and the square a pawn takes it on; none without an en passant square. */
|
|
132
|
+
function enPassantVictim(chess) {
|
|
133
|
+
const to = chess.fen().split(" ")[3];
|
|
134
|
+
if (to === undefined || to === "-") {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return { square: `${to[0]}${chess.turn() === "w" ? 5 : 4}`, to: to };
|
|
138
|
+
}
|
|
139
|
+
/** The checks of the side not to move, on the same board with the turn passed: no en passant, which belonged to the side to move. */
|
|
140
|
+
function otherChecks(chess) {
|
|
141
|
+
const fields = chess.fen().split(" ");
|
|
142
|
+
fields[1] = chess.turn() === "w" ? "b" : "w";
|
|
143
|
+
fields[3] = "-";
|
|
144
|
+
return new Chess(fields.join(" ")).moves().filter((san) => /[+#]$/.test(san));
|
|
145
|
+
}
|
|
146
|
+
/** The pieces on the board, White's first, each side's from the king down (K Q R B N P), then by square. */
|
|
147
|
+
function piecesOf(chess) {
|
|
148
|
+
return chess.board().flat().filter((cell) => cell !== null).map(({ square, type, color }) => ({ square, type, color })).sort((left, right) => compare(right.color, left.color) || order[left.type] - order[right.type] || compare(left.square, right.square));
|
|
149
|
+
}
|
|
150
|
+
/** The squares from a square to the edge of the board, one step at a time. */
|
|
151
|
+
function rayFrom(from, fileStep, rankStep) {
|
|
152
|
+
const ray = [];
|
|
153
|
+
let file = fileOf(from) + fileStep;
|
|
154
|
+
let rank = rankOf(from) + rankStep;
|
|
155
|
+
while (file >= 0 && file < 8 && rank >= 1 && rank <= 8) {
|
|
156
|
+
ray.push(`${files[file]}${rank}`);
|
|
157
|
+
file += fileStep;
|
|
158
|
+
rank += rankStep;
|
|
159
|
+
}
|
|
160
|
+
return ray;
|
|
161
|
+
}
|
|
162
|
+
/** Compares two names of squares or colours: a fixed alphabet, no locale. */
|
|
163
|
+
function compare(left, right) {
|
|
164
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
165
|
+
}
|
|
166
|
+
function sideOf(color) {
|
|
167
|
+
return color === "w" ? "white" : "black";
|
|
168
|
+
}
|
|
169
|
+
function deepFrozen(value) {
|
|
170
|
+
if (typeof value === "object" && value !== null) {
|
|
171
|
+
Object.values(value).forEach(deepFrozen);
|
|
172
|
+
Object.freeze(value);
|
|
173
|
+
}
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type Square } from "chess.js";
|
|
2
|
+
import { type Facts } from "./facts.ts";
|
|
3
|
+
import { type Illegal } from "./illegal.ts";
|
|
4
|
+
import { type Profile } from "./profile.ts";
|
|
5
|
+
import { type Annotation, type GameRecord } from "./record.ts";
|
|
6
|
+
export type { Square } from "chess.js";
|
|
7
|
+
/** A side of the board. */
|
|
8
|
+
export type Side = "white" | "black";
|
|
9
|
+
/** The piece a pawn becomes. */
|
|
10
|
+
export type Promotion = "q" | "r" | "b" | "n";
|
|
11
|
+
/** A legal move: its SAN, and its squares for the page. */
|
|
12
|
+
export interface Move {
|
|
13
|
+
readonly san: string;
|
|
14
|
+
readonly from: Square;
|
|
15
|
+
readonly to: Square;
|
|
16
|
+
readonly promotion?: Promotion;
|
|
17
|
+
}
|
|
18
|
+
/** Where the game stands. The key is the same for the same position, whatever the moves that led to it: chess.js's hash, never to store, it changes with the library. */
|
|
19
|
+
export interface Position {
|
|
20
|
+
readonly fen: string;
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly turn: Side;
|
|
23
|
+
readonly moveNumber: number;
|
|
24
|
+
readonly check: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** How the board ends a game, the draws too (D-37). Resignation, agreement and time are not the board's: the commands record them. */
|
|
27
|
+
export type Ending = "checkmate" | "stalemate" | "insufficient material" | "threefold repetition" | "fifty moves";
|
|
28
|
+
/** Whether the board ends the game, and how. */
|
|
29
|
+
export type Outcome = {
|
|
30
|
+
readonly over: false;
|
|
31
|
+
} | {
|
|
32
|
+
readonly over: true;
|
|
33
|
+
readonly result: "1-0" | "0-1" | "1/2-1/2";
|
|
34
|
+
readonly ending: Ending;
|
|
35
|
+
};
|
|
36
|
+
/** Why a move was refused. */
|
|
37
|
+
export type Refusal = "unreadable" | "illegal" | "ambiguous" | "over";
|
|
38
|
+
/** A move played, with a note when it was written with a check or a mate it does not give; or why it was refused, with the legal moves in SAN. */
|
|
39
|
+
export type Played = {
|
|
40
|
+
readonly played: true;
|
|
41
|
+
readonly move: Move;
|
|
42
|
+
readonly note?: string;
|
|
43
|
+
} | {
|
|
44
|
+
readonly played: false;
|
|
45
|
+
readonly refusal: Refusal;
|
|
46
|
+
readonly message: string;
|
|
47
|
+
readonly legal: readonly string[];
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* A live game: it plays moves written in SAN or as squares, and keeps the record. Checkmate, stalemate and the draws by insufficient material,
|
|
51
|
+
* threefold repetition and fifty moves end it (D-37); a game recorded elsewhere replays past the draws it played on from.
|
|
52
|
+
*/
|
|
53
|
+
export declare class Game {
|
|
54
|
+
private readonly chess;
|
|
55
|
+
private readonly tags;
|
|
56
|
+
private readonly comment;
|
|
57
|
+
private readonly moves;
|
|
58
|
+
private last;
|
|
59
|
+
private readonly keys;
|
|
60
|
+
private readonly castled;
|
|
61
|
+
private keptPosition;
|
|
62
|
+
private keptOutcome;
|
|
63
|
+
private keptRecord;
|
|
64
|
+
/** The game of a record, replayed move by move; without one, a new game from the standard position. */
|
|
65
|
+
constructor(record?: GameRecord);
|
|
66
|
+
/** Where the game stands now. */
|
|
67
|
+
get position(): Position;
|
|
68
|
+
/** Whether the board ends the game, and how. */
|
|
69
|
+
get outcome(): Outcome;
|
|
70
|
+
/** The game as plain data, frozen; when the board ends the game, its Result tag is the board's, unless the record gave one. */
|
|
71
|
+
get record(): GameRecord;
|
|
72
|
+
/** The last move played, with its squares; none before the first. */
|
|
73
|
+
get lastMove(): Move | undefined;
|
|
74
|
+
/** What the position shows (D-26): attackers and defenders, pieces at risk, pins, checks, the moves of each piece; computed once for each position. */
|
|
75
|
+
get facts(): Facts;
|
|
76
|
+
/** The positional profile (D-19): pawns, files, outposts, space, mobility, development, king safety, control, phase, draws; computed once for each position. */
|
|
77
|
+
get profile(): Profile;
|
|
78
|
+
/** The legal moves, or those of the piece on a square. */
|
|
79
|
+
legalMoves(square?: Square): readonly Move[];
|
|
80
|
+
/** Why the piece of the side to move on a square cannot go to another (D-18); undefined when it can, or when no rule says why. */
|
|
81
|
+
whyNot(from: Square, to: Square): Illegal | undefined;
|
|
82
|
+
/** Plays a move written in SAN (Nf3, exd5, O-O, e8=Q), with the pieces' figurines too (♘f3), or as squares (g1f3, e7e8q); a refused move changes nothing. */
|
|
83
|
+
play(text: string): Played;
|
|
84
|
+
/** Plays a move of a game recorded elsewhere, as play does, but also past a draw that game played on from: insufficient material, repetition or fifty moves. */
|
|
85
|
+
playRecorded(text: string): Played;
|
|
86
|
+
/** Sets the judgment of a move by its ply, 1 for the first move recorded: the NAGs and the comment given replace its own, an empty one removes them. */
|
|
87
|
+
annotate(ply: number, annotation: Annotation): void;
|
|
88
|
+
private apply;
|
|
89
|
+
/** A move written loosely (as squares, with a letter too many or too few) matched against the legal moves. */
|
|
90
|
+
private resolve;
|
|
91
|
+
private illegal;
|
|
92
|
+
private snapshot;
|
|
93
|
+
}
|
|
94
|
+
/** A text with SAN's letters and signs for the figurines and signs others write: ♘f3 as Nf3, × as x; a pawn's figurine says nothing. */
|
|
95
|
+
export declare function withSanLetters(text: string): string;
|