chesspal 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +11 -12
  2. package/dist/cli/cli.js +83 -3
  3. package/dist/cli/command-line.js +26 -9
  4. package/dist/cli/icon.d.ts +25 -0
  5. package/dist/cli/icon.js +80 -0
  6. package/dist/commands/agents.d.ts +19 -2
  7. package/dist/commands/agents.js +41 -14
  8. package/dist/commands/ai-say.d.ts +2 -1
  9. package/dist/commands/ai-say.js +3 -4
  10. package/dist/commands/analyse-game.d.ts +6 -0
  11. package/dist/commands/analyse-game.js +51 -15
  12. package/dist/commands/annotate-game.d.ts +4 -0
  13. package/dist/commands/annotate-game.js +60 -20
  14. package/dist/commands/answer-lines.d.ts +10 -4
  15. package/dist/commands/answer-lines.js +30 -4
  16. package/dist/commands/coach.js +6 -4
  17. package/dist/commands/context.d.ts +11 -1
  18. package/dist/commands/context.js +5 -0
  19. package/dist/commands/delete-review.js +9 -4
  20. package/dist/commands/game-summary.d.ts +9 -2
  21. package/dist/commands/game-summary.js +11 -0
  22. package/dist/commands/index.d.ts +4 -3
  23. package/dist/commands/index.js +8 -5
  24. package/dist/commands/init.js +2 -4
  25. package/dist/commands/instructions.d.ts +9 -0
  26. package/dist/commands/instructions.js +30 -0
  27. package/dist/commands/lead-input.d.ts +1 -1
  28. package/dist/commands/lead-input.js +7 -4
  29. package/dist/commands/look.d.ts +9 -5
  30. package/dist/commands/look.js +19 -9
  31. package/dist/commands/page-link.js +6 -5
  32. package/dist/commands/past-games.d.ts +1 -1
  33. package/dist/commands/past-games.js +12 -5
  34. package/dist/commands/player-move.js +2 -3
  35. package/dist/commands/ponder.js +1 -1
  36. package/dist/commands/position.js +2 -2
  37. package/dist/commands/profiles.d.ts +23 -9
  38. package/dist/commands/profiles.js +76 -35
  39. package/dist/commands/ratings.d.ts +8 -0
  40. package/dist/commands/ratings.js +45 -0
  41. package/dist/commands/registry.d.ts +1 -1
  42. package/dist/commands/registry.js +3 -2
  43. package/dist/commands/say.d.ts +11 -6
  44. package/dist/commands/say.js +21 -13
  45. package/dist/commands/status.d.ts +6 -2
  46. package/dist/commands/status.js +70 -26
  47. package/dist/commands/study.d.ts +11 -1
  48. package/dist/commands/study.js +39 -7
  49. package/dist/commands/view.d.ts +17 -5
  50. package/dist/commands/view.js +67 -10
  51. package/dist/core/facts.d.ts +14 -2
  52. package/dist/core/facts.js +32 -7
  53. package/dist/core/game.d.ts +5 -0
  54. package/dist/core/game.js +9 -0
  55. package/dist/core/index.d.ts +2 -1
  56. package/dist/core/index.js +1 -1
  57. package/dist/core/placement.d.ts +63 -0
  58. package/dist/core/placement.js +259 -0
  59. package/dist/core/possibilities.d.ts +38 -0
  60. package/dist/core/possibilities.js +254 -0
  61. package/dist/core/review.d.ts +6 -1
  62. package/dist/core/review.js +21 -6
  63. package/dist/games/ai-model.d.ts +4 -2
  64. package/dist/games/ai-model.js +10 -1
  65. package/dist/games/analysis.d.ts +31 -4
  66. package/dist/games/analysis.js +44 -5
  67. package/dist/games/archive.d.ts +6 -3
  68. package/dist/games/archive.js +13 -5
  69. package/dist/games/current-game.d.ts +26 -2
  70. package/dist/games/current-game.js +57 -21
  71. package/dist/games/files.d.ts +7 -0
  72. package/dist/games/files.js +48 -1
  73. package/dist/games/index.d.ts +4 -4
  74. package/dist/games/index.js +4 -4
  75. package/dist/games/table-talk.d.ts +2 -0
  76. package/dist/games/table-talk.js +5 -1
  77. package/dist/games/talk.d.ts +4 -3
  78. package/dist/games/talk.js +3 -3
  79. package/dist/home/data-folder.d.ts +2 -0
  80. package/dist/home/data-folder.js +5 -1
  81. package/dist/home/index.d.ts +1 -1
  82. package/dist/home/index.js +1 -1
  83. package/dist/host-files/agents.d.ts +2 -2
  84. package/dist/host-files/agents.js +12 -6
  85. package/dist/host-files/host-files.d.ts +5 -3
  86. package/dist/host-files/host-files.js +15 -8
  87. package/dist/host-files/index.d.ts +2 -2
  88. package/dist/host-files/index.js +2 -2
  89. package/dist/page/app/app.js +59 -22
  90. package/dist/page/app/keys.js +16 -2
  91. package/dist/page/app/served.js +2 -2
  92. package/dist/page/app/title.js +17 -0
  93. package/dist/page/avatars/torretta.js +4 -4
  94. package/dist/page/board/board.js +2 -1
  95. package/dist/page/board/marks.js +4 -24
  96. package/dist/page/board/signs.js +23 -0
  97. package/dist/page/dialogs/analyses.js +4 -4
  98. package/dist/page/dialogs/dialog.js +4 -1
  99. package/dist/page/dialogs/dialogs.css +84 -10
  100. package/dist/page/dialogs/profiles.js +95 -42
  101. package/dist/page/game/column.js +6 -2
  102. package/dist/page/game/game.css +12 -0
  103. package/dist/page/game/move-list.js +2 -2
  104. package/dist/page/game/plies.js +11 -0
  105. package/dist/page/guide/blocks.js +67 -0
  106. package/dist/page/guide/en.md +66 -0
  107. package/dist/page/guide/foot.js +18 -0
  108. package/dist/page/guide/guide.css +200 -0
  109. package/dist/page/guide/guide.js +89 -0
  110. package/dist/page/guide/it.md +66 -0
  111. package/dist/page/guide/markdown.js +67 -0
  112. package/dist/page/helps/helps.css +10 -11
  113. package/dist/page/helps/helps.js +15 -15
  114. package/dist/page/layout/favicon.svg +13 -0
  115. package/dist/page/layout/header.js +3 -3
  116. package/dist/page/layout/icon.icns +0 -0
  117. package/dist/page/layout/icon.ico +0 -0
  118. package/dist/page/layout/icon.png +0 -0
  119. package/dist/page/layout/layout.css +140 -9
  120. package/dist/page/layout/window.js +45 -0
  121. package/dist/page/legend/legend.css +203 -0
  122. package/dist/page/legend/legend.js +169 -0
  123. package/dist/page/legend/view.js +54 -0
  124. package/dist/page/main.js +4 -1
  125. package/dist/page/players/card.js +1 -1
  126. package/dist/page/profile/profile.js +14 -9
  127. package/dist/page/profile/settings.js +9 -0
  128. package/dist/page/style.css +4 -0
  129. package/dist/page/table/opener.css +65 -0
  130. package/dist/page/table/opener.js +59 -0
  131. package/dist/page/talk/chat.css +97 -0
  132. package/dist/page/talk/talk.css +38 -26
  133. package/dist/page/talk/talk.js +31 -7
  134. package/dist/page/text.js +120 -18
  135. package/dist/page/theme.css +7 -0
  136. package/dist/profiles/index.d.ts +2 -1
  137. package/dist/profiles/index.js +2 -1
  138. package/dist/profiles/profiles.d.ts +10 -15
  139. package/dist/profiles/profiles.js +30 -15
  140. package/dist/profiles/ratings.d.ts +35 -0
  141. package/dist/profiles/ratings.js +61 -0
  142. package/dist/server/bell.d.ts +24 -3
  143. package/dist/server/bell.js +84 -10
  144. package/dist/server/caller.d.ts +2 -1
  145. package/dist/server/caller.js +3 -2
  146. package/dist/server/mcp.d.ts +7 -3
  147. package/dist/server/mcp.js +106 -56
  148. package/dist/server/page/analyses.d.ts +2 -0
  149. package/dist/server/page/analyses.js +20 -5
  150. package/dist/server/page/attach.d.ts +4 -0
  151. package/dist/server/page/attach.js +112 -0
  152. package/dist/server/page/chat.d.ts +60 -0
  153. package/dist/server/page/chat.js +79 -0
  154. package/dist/server/page/events.d.ts +7 -0
  155. package/dist/server/page/events.js +20 -0
  156. package/dist/server/page/files.js +10 -1
  157. package/dist/server/page/game.js +62 -29
  158. package/dist/server/page/page.d.ts +47 -7
  159. package/dist/server/page/page.js +74 -51
  160. package/dist/server/page/profiles.d.ts +5 -0
  161. package/dist/server/page/profiles.js +26 -10
  162. package/dist/server/page/table.d.ts +39 -0
  163. package/dist/server/page/table.js +102 -0
  164. package/dist/server/table/browser.d.ts +6 -0
  165. package/dist/server/table/browser.js +48 -0
  166. package/dist/server/table/hosts/chats.d.ts +7 -0
  167. package/dist/server/table/hosts/chats.js +31 -0
  168. package/dist/server/table/hosts/find.d.ts +2 -0
  169. package/dist/server/table/hosts/find.js +20 -0
  170. package/dist/server/table/hosts/hosts.d.ts +34 -0
  171. package/dist/server/table/hosts/hosts.js +49 -0
  172. package/dist/server/table/hosts/index.d.ts +3 -0
  173. package/dist/server/table/hosts/index.js +4 -0
  174. package/dist/server/table/hosts/play.d.ts +25 -0
  175. package/dist/server/table/hosts/play.js +152 -0
  176. package/dist/server/table/hosts/seat.d.ts +25 -0
  177. package/dist/server/table/hosts/seat.js +74 -0
  178. package/dist/server/table/hosts/terminal.d.ts +6 -0
  179. package/dist/server/table/hosts/terminal.js +63 -0
  180. package/dist/server/table/icon/index.d.ts +32 -0
  181. package/dist/server/table/icon/index.js +83 -0
  182. package/dist/server/table/icon/linux.d.ts +5 -0
  183. package/dist/server/table/icon/linux.js +47 -0
  184. package/dist/server/table/icon/macos.d.ts +3 -0
  185. package/dist/server/table/icon/macos.js +38 -0
  186. package/dist/server/table/icon/windows.d.ts +3 -0
  187. package/dist/server/table/icon/windows.js +48 -0
  188. package/dist/server/table/index.d.ts +6 -0
  189. package/dist/server/table/index.js +7 -0
  190. package/dist/server/table/launch.d.ts +19 -0
  191. package/dist/server/table/launch.js +78 -0
  192. package/dist/server/table/link.d.ts +10 -0
  193. package/dist/server/table/link.js +230 -0
  194. package/dist/server/table/table.d.ts +35 -0
  195. package/dist/server/table/table.js +94 -0
  196. package/dist/sites/chesscom.d.ts +4 -0
  197. package/dist/sites/chesscom.js +25 -0
  198. package/dist/sites/choice.d.ts +17 -0
  199. package/dist/sites/choice.js +13 -0
  200. package/dist/sites/index.d.ts +2 -0
  201. package/dist/sites/index.js +4 -0
  202. package/dist/sites/lichess.d.ts +4 -0
  203. package/dist/sites/lichess.js +32 -0
  204. package/dist/sites/request.d.ts +11 -0
  205. package/dist/sites/request.js +15 -0
  206. package/dist/sites/sites.d.ts +6 -0
  207. package/dist/sites/sites.js +33 -0
  208. package/instructions/analysis.md +1 -1
  209. package/instructions/analyst-agent.md +1 -1
  210. package/instructions/coach.md +2 -1
  211. package/instructions/opponent.md +6 -6
  212. package/instructions/ponder.md +1 -0
  213. package/instructions/start.md +1 -0
  214. package/package.json +1 -1
@@ -1,23 +1,37 @@
1
- /** The view after the player's move: the check, the pieces attacked, then what is at risk, the pins, the checks and the moves of each piece. */
2
- export function compactView(facts, turn) {
1
+ /**
2
+ * The view after the player's move, in the masters' order (D-100): the check, the checks, the captures, the threats, what the moves would do, the pins,
3
+ * skewers and loose pieces, the pieces attacked, the moves of each piece.
4
+ */
5
+ export function compactView(facts, turn, possible) {
3
6
  const names = namesOf(facts);
4
7
  const attacked = officersOf(facts).filter((piece) => piece.attackers.length > 0);
5
8
  const pieces = attacked.length === 0 ? ["Attacked pieces: none"] : ["Attacked pieces (attackers / defenders):", ...attacked.map((piece) => pieceLine(piece, names, true))];
6
- return [...checkLines(facts, turn, names), ...pieces, ...tacticLines(facts, turn, names)];
9
+ return [...checkLines(facts, turn, names), ...tacticLines(facts, turn, names, possible, pieces)];
7
10
  }
8
11
  /** The view of a level without the tactical facts (D-60): the check and the legal moves by piece, which are the rules (D-17). */
9
12
  export function boardView(facts, turn) {
10
13
  const names = namesOf(facts);
11
14
  return [...checkLines(facts, turn, names), ...legalLines(facts, names)];
12
15
  }
13
- /** The whole view, for look: every piece with its attackers and defenders, then the same lines as the compact view. */
14
- export function fullView(facts, turn) {
16
+ /** The whole view, for look: the same lines as the compact view, with every piece's attackers and defenders. */
17
+ export function fullView(facts, turn, possible) {
15
18
  const names = namesOf(facts);
16
19
  const sides = sidesInOrder.flatMap((side) => [
17
20
  `${colourName(side)} pieces (attackers / defenders):`,
18
21
  ...officersOf(facts).filter((piece) => piece.side === side).map((piece) => pieceLine(piece, names, false)),
19
22
  ]);
20
- return [...checkLines(facts, turn, names), ...sides, ...tacticLines(facts, turn, names)];
23
+ return [...checkLines(facts, turn, names), ...tacticLines(facts, turn, names, possible, sides)];
24
+ }
25
+ /** What the moves of the side to move would do, in one line for a move of the analysis: fork Nc7+ (Ke8 Ra8); stalemate Qf7; none when nothing. */
26
+ export function tacticsText(facts, possibilities, withTraps) {
27
+ const names = namesOf(facts);
28
+ const traps = withTraps ? possibilities.stalemateTraps.map(({ san, replies }) => `${san} (if ${replies === "all" ? "any reply" : replies.join(" or ")})`) : [];
29
+ const parts = [
30
+ ...chanceParts(possibilities, names),
31
+ kindPart("stalemate", possibilities.stalemates),
32
+ kindPart("stalemate trap", traps),
33
+ ].filter((part) => part !== undefined);
34
+ return parts.length === 0 ? undefined : parts.join("; ");
21
35
  }
22
36
  /** What is at risk, in one line for a move of the analysis (D-68): undefended Black Nf6; by a cheaper piece White Qd1; outnumbered Black Pe5; none when nothing is. */
23
37
  export function riskLine(facts) {
@@ -54,22 +68,65 @@ const sidesInOrder = ["white", "black"];
54
68
  function checkLines(facts, turn, names) {
55
69
  return facts.checkedBy === undefined ? [] : [`${colourName(turn)} is in check from ${facts.checkedBy.map(names.piece).join(" and ")}.`];
56
70
  }
57
- /** What is at risk, loose and pinned, the checks of both sides, and the legal moves of the side to move by piece. */
58
- function tacticLines(facts, turn, names) {
71
+ /**
72
+ * In the masters' order (D-100): the side to move's checks and captures, the threats (what is at risk, the other side's checks and chances), what its
73
+ * moves would do, the pins, skewers and loose pieces, the pieces in detail, its legal moves by piece.
74
+ */
75
+ function tacticLines(facts, turn, names, possible, detail) {
59
76
  const list = (squares) => bySide(facts, squares, names) || "none";
60
77
  const atRisk = facts.undefended.length === 0 && facts.attackedByCheaper.length === 0 && facts.outnumbered.length === 0
61
78
  ? "At risk: none"
62
79
  : `Attacked and undefended: ${list(facts.undefended)}; defended, but attacked by a cheaper piece: ${list(facts.attackedByCheaper)}; `
63
80
  + `attacked more times than defended: ${list(facts.outnumbered)}`;
64
81
  const pins = facts.pins.map((pin) => `${names.sided(pin.square)} to ${names.piece(pin.to)} by ${names.piece(pin.by)}`);
82
+ const skewers = facts.skewers.map((skewer) => `${names.sided(skewer.square)} with ${names.piece(skewer.to)} behind, by ${names.piece(skewer.by)}`);
83
+ const [ownChecks, ...otherChecks] = checksLines(facts, turn);
84
+ // A game over has no moves to tell of.
85
+ const moves = possible !== undefined && facts.pieces.some((piece) => (piece.moves?.length ?? 0) > 0) ? possible : undefined;
65
86
  return [
87
+ ownChecks,
88
+ ...(moves === undefined ? [] : [captureLine(moves.possibilities)]),
66
89
  atRisk,
67
- `Loose: ${list(facts.loose)}`,
90
+ ...otherChecks,
91
+ ...(moves === undefined ? [] : threatLines(moves, turn, names)),
92
+ ...(moves === undefined ? [] : [`${colourName(turn)} (to move) tactics: ${tacticsText(facts, moves.possibilities, moves.strong) ?? "none"}`]),
68
93
  `Pinned: ${pins.join(", ") || "none"}`,
69
- ...checksLines(facts, turn),
94
+ ...(skewers.length === 0 ? [] : [`Skewered: ${skewers.join(", ")}`]),
95
+ `Loose: ${list(facts.loose)}`,
96
+ ...detail,
70
97
  ...legalLines(facts, names),
71
98
  ];
72
99
  }
100
+ /** The captures of the side to move, each with what the piece taken is to the piece taking it. */
101
+ function captureLine({ captures }) {
102
+ return `Captures: ${captures.map((capture) => (capture.fact === undefined ? capture.san : `${capture.san} (${capture.fact})`)).join(", ") || "none"}`;
103
+ }
104
+ /** What the other side's moves would do if it were to move: its promotions, and as the level sees them its double attacks and discovered checks. */
105
+ function threatLines({ threats, strong }, turn, names) {
106
+ if (threats === undefined) {
107
+ return [];
108
+ }
109
+ const parts = [
110
+ kindPart("promote", threats.promotions),
111
+ ...(strong ? chanceParts({ ...threats, promotions: [] }, names) : []),
112
+ ].filter((part) => part !== undefined);
113
+ return parts.length === 0 ? [] : [`${colourName(turn === "white" ? "black" : "white")} threats, if it were to move: ${parts.join("; ")}`];
114
+ }
115
+ /** A side's forks, double attacks, double and discovered checks and promotions, each kind with its moves. */
116
+ function chanceParts(chances, names) {
117
+ const attack = ({ san, targets }) => `${san} (${targets.map(names.piece).join(" ")})`;
118
+ return [
119
+ kindPart("fork", chances.doubleAttacks.filter((each) => each.fork).map(attack)),
120
+ kindPart("double attack", chances.doubleAttacks.filter((each) => !each.fork).map(attack)),
121
+ kindPart("double check", chances.doubleChecks),
122
+ kindPart("discovered check", chances.discoveredChecks),
123
+ kindPart("promote", chances.promotions),
124
+ ];
125
+ }
126
+ /** A kind with its moves, comma-separated; none without moves. */
127
+ function kindPart(kind, moves) {
128
+ return moves.length === 0 ? undefined : `${kind} ${moves.join(", ")}`;
129
+ }
73
130
  /** The checks the side to move can give, and those the other side could give if it were to move; one line when neither has any. */
74
131
  function checksLines(facts, turn) {
75
132
  if (facts.checks.length === 0 && facts.otherChecks?.length === 0) {
@@ -1,4 +1,4 @@
1
- import { Chess, type Color, type Square } from "chess.js";
1
+ import { Chess, type Color, type PieceSymbol, type Square } from "chess.js";
2
2
  import type { Move, Side } from "./game.ts";
3
3
  /** A piece's letter, as SAN writes it, with P for a pawn. */
4
4
  export type PieceLetter = "K" | "Q" | "R" | "B" | "N" | "P";
@@ -60,8 +60,20 @@ export interface Facts {
60
60
  export declare function factsOf(chess: Chess, legal: readonly Move[]): Facts;
61
61
  /** 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. */
62
62
  export type Reach = (square: Square, color: Color) => Square[];
63
+ /** What the attacks read of a board: its pieces, where a side's pieces of a type stand, and which pieces of a side attack a square, as chess.js counts them. */
64
+ export interface Board {
65
+ get(square: Square): {
66
+ type: PieceSymbol;
67
+ color: Color;
68
+ } | undefined;
69
+ findPiece(piece: {
70
+ type: PieceSymbol;
71
+ color: Color;
72
+ }): Square[];
73
+ attackers(square: Square, color: Color): Square[];
74
+ }
63
75
  /** 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. */
64
- export declare function attacksOf(chess: Chess): Reach;
76
+ export declare function attacksOf(board: Board): Reach;
65
77
  /** The squares strictly between two squares on one line. */
66
78
  export declare function squaresBetween(from: Square, to: Square): Square[];
67
79
  export declare function fileOf(square: Square): number;
@@ -1,5 +1,6 @@
1
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 and skewers, the checks, the moves of each piece.
2
2
  import { Chess } from "chess.js";
3
+ import { mayCastle, Placement, turnPassed } from "./placement.js";
3
4
  /** The facts of a chess.js board, whose legal moves are given: computed once for each position, by its key, and frozen. */
4
5
  export function factsOf(chess, legal) {
5
6
  const key = chess.hash();
@@ -24,8 +25,9 @@ const order = { k: 0, q: 1, r: 2, b: 3, n: 4, p: 5 };
24
25
  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
26
  const files = "abcdefgh";
26
27
  /** 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)));
28
+ export function attacksOf(board) {
29
+ const kings = ["w", "b"].flatMap((color) => board.findPiece({ type: "k", color }).map((square) => ({ square, type: "k", color })));
30
+ return alongPins(board, kings.flatMap((king) => pinsTo(board, king)));
29
31
  }
30
32
  /** The squares strictly between two squares on one line. */
31
33
  export function squaresBetween(from, to) {
@@ -164,17 +166,40 @@ function enPassantVictim(chess) {
164
166
  }
165
167
  /** 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. */
166
168
  function otherChecks(chess) {
167
- const fields = chess.fen().split(" ");
168
- fields[1] = chess.turn() === "w" ? "b" : "w";
169
- fields[3] = "-";
170
- return new Chess(fields.join(" ")).moves().filter((san) => /[+#]$/.test(san));
169
+ const other = chess.turn() === "w" ? "b" : "w";
170
+ // Castling may check, and the board of pieces alone does not try it: where the other side may castle, chess.js lists all its moves.
171
+ if (mayCastle(chess, other)) {
172
+ return turnPassed(chess).moves().filter((san) => /[+#]$/.test(san));
173
+ }
174
+ // Else its moves are tried and written on the board of pieces alone, in chess.js's order (D-34); chess.js writes the rare one it cannot.
175
+ const board = new Placement(chess);
176
+ const moves = board.movesOf(other);
177
+ const checks = moves.filter((move) => {
178
+ board.play(move);
179
+ const check = board.inCheck(chess.turn());
180
+ board.undo();
181
+ return check;
182
+ }).map((move) => board.sanOf(move, moves));
183
+ return checks.includes(undefined) ? turnPassed(chess).moves().filter((san) => /[+#]$/.test(san)) : checks;
171
184
  }
172
185
  /** The pieces on the board, White's first, each side's from the king down (K Q R B N P), then by square. */
173
186
  function piecesOf(chess) {
174
187
  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));
175
188
  }
176
- /** The squares from a square to the edge of the board, one step at a time. */
189
+ /** The squares from a square to the edge of the board, one step at a time; worked out once for each square and step. */
177
190
  function rayFrom(from, fileStep, rankStep) {
191
+ const step = (fileStep + 1) * 3 + rankStep + 1;
192
+ let known = rays.get(from);
193
+ if (known === undefined) {
194
+ known = [];
195
+ rays.set(from, known);
196
+ }
197
+ known[step] ??= Object.freeze(walked(from, fileStep, rankStep));
198
+ return known[step];
199
+ }
200
+ // The rays from each square already walked, by their step.
201
+ const rays = new Map();
202
+ function walked(from, fileStep, rankStep) {
178
203
  const ray = [];
179
204
  let file = fileOf(from) + fileStep;
180
205
  let rank = rankOf(from) + rankStep;
@@ -1,6 +1,7 @@
1
1
  import { type Square } from "chess.js";
2
2
  import { type Facts } from "./facts.ts";
3
3
  import { type Illegal } from "./illegal.ts";
4
+ import { type Chances, type Possibilities } from "./possibilities.ts";
4
5
  import { type Profile } from "./profile.ts";
5
6
  import { type Annotation, type GameRecord } from "./record.ts";
6
7
  export type { Square } from "chess.js";
@@ -73,6 +74,10 @@ export declare class Game {
73
74
  get lastMove(): Move | undefined;
74
75
  /** 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
76
  get facts(): Facts;
77
+ /** What the moves would do (D-100): captures, double attacks, discovered checks, promotions, stalemates; once for each position. */
78
+ get possibilities(): Possibilities;
79
+ /** What the other side's moves would do if it were to move (D-100), none in check; once for each position, only where it is shown. */
80
+ get threats(): Chances | undefined;
76
81
  /** The positional profile (D-19): pawns, files, outposts, space, mobility, development, king safety, control, phase, draws; computed once for each position. */
77
82
  get profile(): Profile;
78
83
  /** The legal moves, or those of the piece on a square. */
package/dist/core/game.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { Chess, validateFen } from "chess.js";
3
3
  import { factsOf } from "./facts.js";
4
4
  import { whyIllegal } from "./illegal.js";
5
+ import { possibilitiesOf, threatsOf } from "./possibilities.js";
5
6
  import { profileOf } from "./profile.js";
6
7
  import { cleanComment, recordedMove } from "./record.js";
7
8
  /**
@@ -65,6 +66,14 @@ export class Game {
65
66
  get facts() {
66
67
  return factsOf(this.chess, legalMovesOf(this.chess));
67
68
  }
69
+ /** What the moves would do (D-100): captures, double attacks, discovered checks, promotions, stalemates; once for each position. */
70
+ get possibilities() {
71
+ return possibilitiesOf(this.chess, legalMovesOf(this.chess));
72
+ }
73
+ /** What the other side's moves would do if it were to move (D-100), none in check; once for each position, only where it is shown. */
74
+ get threats() {
75
+ return threatsOf(this.chess);
76
+ }
68
77
  /** The positional profile (D-19): pawns, files, outposts, space, mobility, development, king safety, control, phase, draws; computed once for each position. */
69
78
  get profile() {
70
79
  const key = this.chess.hash();
@@ -14,7 +14,8 @@ export { endWithMood, type Mood, moodAt, type MoodNow, moodOf, moods, moodTag, w
14
14
  export { bookMoves, indexBook, indexOpenings, inOpenings, openingOf, outOfBookMove } from "./openings.ts";
15
15
  export type { Opening, OpeningBook, OpeningIndex } from "./openings.ts";
16
16
  export { pgnDate, readPgn, writePgn } from "./pgn.ts";
17
+ export type { Capture, Chances, DoubleAttack, Possibilities, StalemateTrap } from "./possibilities.ts";
17
18
  export type { BishopFacts, CentreSquare, KingFacts, Outpost, PawnFacts, PawnSquare, Phase, Profile, SideProfile } from "./profile.ts";
18
19
  export { movesOnly } from "./record.ts";
19
20
  export type { Annotation, GameRecord, RecordedMove } from "./record.ts";
20
- export { aiHostIn, type AiModel, aiModelIn, aiNameOf, annotatorOf, arrowOf, type Badge, badges, closingOf, type DrawColour, drawingsIn, drawingsText, type DrawnArrow, type DrawnSquare, gameReviewOf, isReviewed, type MoveReview, reviewed, reviewOf, squareOf, unreviewed, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.ts";
21
+ export { aiHostIn, type AiModel, aiModelIn, aiNameOf, annotatorOf, arrowOf, type Badge, badges, closingOf, type DrawColour, drawingsIn, drawingsText, type DrawnArrow, type DrawnSquare, gameReviewOf, isReviewed, type MoveReview, reviewed, reviewOf, squareOf, unreviewed, unsigned, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.ts";
@@ -9,4 +9,4 @@ export { endWithMood, moodAt, moodOf, moods, moodTag, withoutMoods } from "./moo
9
9
  export { bookMoves, indexBook, indexOpenings, inOpenings, openingOf, outOfBookMove } from "./openings.js";
10
10
  export { pgnDate, readPgn, writePgn } from "./pgn.js";
11
11
  export { movesOnly } from "./record.js";
12
- export { aiHostIn, aiModelIn, aiNameOf, annotatorOf, arrowOf, badges, closingOf, drawingsIn, drawingsText, gameReviewOf, isReviewed, reviewed, reviewOf, squareOf, unreviewed, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.js";
12
+ export { aiHostIn, aiModelIn, aiNameOf, annotatorOf, arrowOf, badges, closingOf, drawingsIn, drawingsText, gameReviewOf, isReviewed, reviewed, reviewOf, squareOf, unreviewed, unsigned, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.js";
@@ -0,0 +1,63 @@
1
+ import { Chess, type Color, type PieceSymbol, type Square } from "chess.js";
2
+ /** A piece, by its type and side. */
3
+ export interface Piece {
4
+ readonly type: PieceSymbol;
5
+ readonly color: Color;
6
+ }
7
+ /** A piece on its square. */
8
+ export interface Placed extends Piece {
9
+ readonly square: Square;
10
+ }
11
+ /** A move to try: its squares, and the piece a pawn becomes. */
12
+ export interface Tried {
13
+ readonly from: Square;
14
+ readonly to: Square;
15
+ readonly promotion?: PieceSymbol;
16
+ }
17
+ /** The pieces of a position, moved without the rules' checks and taken back in the reverse order. */
18
+ export declare class Placement {
19
+ #private;
20
+ constructor(chess: Chess);
21
+ get(square: Square): Piece | undefined;
22
+ /** The squares of a side's pieces of one type. */
23
+ findPiece({ type, color }: Piece): Square[];
24
+ /** A side's pieces. */
25
+ piecesOf(color: Color): Placed[];
26
+ /** The pieces of a side that attack a square, pinned or not: pawns, knights and kings by their steps, the others along their lines. */
27
+ attackers(square: Square, color: Color): Square[];
28
+ /** The squares the piece on a square attacks: by its steps, or along its lines up to the first piece. */
29
+ attackedFrom(square: Square): Square[];
30
+ /** Whether a square lies between a rook, bishop or queen of a side and a piece of the other side besides a pawn, on their line with nothing else between. */
31
+ isBetween(square: Square, color: Color): boolean;
32
+ /** Whether a piece of a side stands between its king and a rook, bishop or queen of the other side on their line. */
33
+ isPinned(color: Color): boolean;
34
+ /** Plays a move: the piece taken goes, en passant too, and a pawn becomes the piece it promotes to. It returns the piece taken. */
35
+ play(move: Tried): Piece | undefined;
36
+ /** Takes back the last move played. */
37
+ undo(): void;
38
+ /**
39
+ * A side's moves that leave its king safe, in the order chess.js lists them: the pawns' pushes, captures and promotions, the pieces' steps and lines;
40
+ * never castling or en passant.
41
+ */
42
+ movesOf(color: Color): Tried[];
43
+ /** Whether a side has a move of those, stopping at the first. */
44
+ hasMove(color: Color): boolean;
45
+ /**
46
+ * A move of those in SAN, as chess.js writes it, with the side's moves for the disambiguation; undefined for a pawn pushed two squares that would
47
+ * mate, which an en passant capture may answer: chess.js tells.
48
+ */
49
+ sanOf(move: Tried, moves: readonly Tried[]): string | undefined;
50
+ /** Whether a side's king is attacked. */
51
+ inCheck(color: Color): boolean;
52
+ }
53
+ /** A move's SAN, and where chess.js lists it among the side's moves: the lists keep that order. */
54
+ export interface Named {
55
+ readonly san: string;
56
+ readonly order: number;
57
+ }
58
+ /** The SAN of moves of the side to move of a chess.js board, and their order as chess.js lists them; read square by square, as moves need them. */
59
+ export declare function namesOn(boardOf: () => Chess): (move: Tried) => Named | undefined;
60
+ /** Whether a side may castle on a chess.js board, as far as the board of pieces alone would miss it: a right it keeps, with the squares to its rook empty. */
61
+ export declare function mayCastle(chess: Chess, color: Color): boolean;
62
+ /** The same board with the other side to move, without en passant, which belonged to the side to move. */
63
+ export declare function turnPassed(chess: Chess): Chess;
@@ -0,0 +1,259 @@
1
+ // A board of pieces alone, to try many moves in a row fast (D-34): no turn, castling rights or history, which the possibilities do not read. Which pieces
2
+ // attack a square is read along the board's lines and steps, as chess.js counts it.
3
+ import { Chess } from "chess.js";
4
+ /** The pieces of a position, moved without the rules' checks and taken back in the reverse order. */
5
+ export class Placement {
6
+ #pieces = new Map();
7
+ #played = [];
8
+ constructor(chess) {
9
+ for (const cell of chess.board().flat()) {
10
+ if (cell !== null) {
11
+ this.#pieces.set(cell.square, { type: cell.type, color: cell.color });
12
+ }
13
+ }
14
+ }
15
+ get(square) {
16
+ return this.#pieces.get(square);
17
+ }
18
+ /** The squares of a side's pieces of one type. */
19
+ findPiece({ type, color }) {
20
+ const found = [];
21
+ for (const [square, piece] of this.#pieces) {
22
+ if (piece.type === type && piece.color === color) {
23
+ found.push(square);
24
+ }
25
+ }
26
+ return found;
27
+ }
28
+ /** A side's pieces. */
29
+ piecesOf(color) {
30
+ const found = [];
31
+ for (const [square, piece] of this.#pieces) {
32
+ if (piece.color === color) {
33
+ found.push({ square, type: piece.type, color });
34
+ }
35
+ }
36
+ return found;
37
+ }
38
+ /** The pieces of a side that attack a square, pinned or not: pawns, knights and kings by their steps, the others along their lines. */
39
+ attackers(square, color) {
40
+ const of = (from, types) => {
41
+ const piece = this.#pieces.get(from);
42
+ return piece !== undefined && piece.color === color && types.includes(piece.type);
43
+ };
44
+ const found = [
45
+ ...pawnsTaking[color].get(square).filter((from) => of(from, "p")),
46
+ ...knightSteps.get(square).filter((from) => of(from, "n")),
47
+ ...kingSteps.get(square).filter((from) => of(from, "k")),
48
+ ];
49
+ for (const { squares, sliders } of linesFrom.get(square)) {
50
+ const first = squares.find((each) => this.#pieces.has(each));
51
+ if (first !== undefined && of(first, sliders)) {
52
+ found.push(first);
53
+ }
54
+ }
55
+ return found;
56
+ }
57
+ /** The squares the piece on a square attacks: by its steps, or along its lines up to the first piece. */
58
+ attackedFrom(square) {
59
+ const { type, color } = this.#pieces.get(square);
60
+ if (type === "p" || type === "n" || type === "k") {
61
+ return (type === "p" ? pawnCaptures[color] : type === "n" ? knightSteps : kingSteps).get(square);
62
+ }
63
+ return linesFrom.get(square).filter((line) => line.sliders.includes(type)).flatMap(({ squares }) => {
64
+ const stop = squares.findIndex((each) => this.#pieces.has(each));
65
+ return stop === -1 ? squares : squares.slice(0, stop + 1);
66
+ });
67
+ }
68
+ /** Whether a square lies between a rook, bishop or queen of a side and a piece of the other side besides a pawn, on their line with nothing else between. */
69
+ isBetween(square, color) {
70
+ const lines = linesFrom.get(square);
71
+ const first = lines.map(({ squares }) => squares.find((each) => this.#pieces.has(each)));
72
+ const slider = (index) => {
73
+ const piece = first[index] === undefined ? undefined : this.#pieces.get(first[index]);
74
+ return piece !== undefined && piece.color === color && lines[index].sliders.includes(piece.type);
75
+ };
76
+ const target = (index) => {
77
+ const piece = first[index] === undefined ? undefined : this.#pieces.get(first[index]);
78
+ return piece !== undefined && piece.color !== color && piece.type !== "p";
79
+ };
80
+ return opposite.some(([one, two]) => (slider(one) && target(two)) || (slider(two) && target(one)));
81
+ }
82
+ /** Whether a piece of a side stands between its king and a rook, bishop or queen of the other side on their line. */
83
+ isPinned(color) {
84
+ const king = this.findPiece({ type: "k", color })[0];
85
+ return king !== undefined && linesFrom.get(king).some(({ squares, sliders }) => {
86
+ const [first, second] = squares.filter((each) => this.#pieces.has(each)).map((each) => this.#pieces.get(each));
87
+ return first?.color === color && second !== undefined && second.color !== color && sliders.includes(second.type);
88
+ });
89
+ }
90
+ /** Plays a move: the piece taken goes, en passant too, and a pawn becomes the piece it promotes to. It returns the piece taken. */
91
+ play(move) {
92
+ const piece = this.#pieces.get(move.from);
93
+ const passant = piece.type === "p" && move.from[0] !== move.to[0] && !this.#pieces.has(move.to);
94
+ const takenOn = passant ? `${move.to[0]}${move.from[1]}` : move.to;
95
+ const taken = this.#pieces.get(takenOn);
96
+ this.#played.push({ move, piece, taken, takenOn });
97
+ this.#pieces.delete(move.from);
98
+ this.#pieces.delete(takenOn);
99
+ this.#pieces.set(move.to, move.promotion === undefined ? piece : { type: move.promotion, color: piece.color });
100
+ return taken;
101
+ }
102
+ /** Takes back the last move played. */
103
+ undo() {
104
+ const { move, piece, taken, takenOn } = this.#played.pop();
105
+ this.#pieces.delete(move.to);
106
+ this.#pieces.set(move.from, piece);
107
+ if (taken !== undefined) {
108
+ this.#pieces.set(takenOn, taken);
109
+ }
110
+ }
111
+ /**
112
+ * A side's moves that leave its king safe, in the order chess.js lists them: the pawns' pushes, captures and promotions, the pieces' steps and lines;
113
+ * never castling or en passant.
114
+ */
115
+ movesOf(color) {
116
+ return this.#placedOf(color).flatMap((piece) => this.#reachable(piece)).filter((move) => this.#leavesKingSafe(move, color));
117
+ }
118
+ /** Whether a side has a move of those, stopping at the first. */
119
+ hasMove(color) {
120
+ return this.#placedOf(color).some((piece) => this.#reachable(piece).some((move) => this.#leavesKingSafe(move, color)));
121
+ }
122
+ /**
123
+ * A move of those in SAN, as chess.js writes it, with the side's moves for the disambiguation; undefined for a pawn pushed two squares that would
124
+ * mate, which an en passant capture may answer: chess.js tells.
125
+ */
126
+ sanOf(move, moves) {
127
+ const { type, color } = this.#pieces.get(move.from);
128
+ const other = color === "w" ? "b" : "w";
129
+ const takes = this.#pieces.has(move.to) || (type === "p" && move.from[0] !== move.to[0]);
130
+ const head = type === "p" ? (takes ? `${move.from[0]}x` : "") : `${type.toUpperCase()}${this.#disambiguation(move, type, moves)}${takes ? "x" : ""}`;
131
+ this.play(move);
132
+ const check = this.inCheck(other);
133
+ const mate = check && !this.hasMove(other);
134
+ this.undo();
135
+ if (mate && type === "p" && Math.abs(Number(move.to[1]) - Number(move.from[1])) === 2) {
136
+ return undefined;
137
+ }
138
+ return `${head}${move.to}${move.promotion === undefined ? "" : `=${move.promotion.toUpperCase()}`}${mate ? "#" : check ? "+" : ""}`;
139
+ }
140
+ /** Whether a side's king is attacked. */
141
+ inCheck(color) {
142
+ const king = this.findPiece({ type: "k", color })[0];
143
+ return king !== undefined && this.attackers(king, color === "w" ? "b" : "w").length > 0;
144
+ }
145
+ #leavesKingSafe(move, color) {
146
+ this.play(move);
147
+ const safe = !this.inCheck(color);
148
+ this.undo();
149
+ return safe;
150
+ }
151
+ // A side's pieces square by square from a8 to h1, as chess.js goes over them.
152
+ #placedOf(color) {
153
+ return boardOrder.flatMap((square) => {
154
+ const piece = this.#pieces.get(square);
155
+ return piece?.color === color ? [{ square, type: piece.type, color }] : [];
156
+ });
157
+ }
158
+ // The squares a move's piece starts from, when another of its type reaches the same square: its file, else its rank, else both (chess.js).
159
+ #disambiguation(move, type, moves) {
160
+ const rivals = moves.filter((each) => each.to === move.to && each.from !== move.from && this.#pieces.get(each.from)?.type === type);
161
+ if (rivals.length === 0) {
162
+ return "";
163
+ }
164
+ const sameRank = rivals.some((each) => each.from[1] === move.from[1]);
165
+ const sameFile = rivals.some((each) => each.from[0] === move.from[0]);
166
+ return sameRank && sameFile ? move.from : sameFile ? move.from[1] : move.from[0];
167
+ }
168
+ // A piece's moves before its king's safety is looked at, in chess.js's order.
169
+ #reachable({ square, type, color }) {
170
+ const free = (to) => !this.#pieces.has(to);
171
+ const takes = (to) => this.#pieces.get(to)?.color === (color === "w" ? "b" : "w");
172
+ const open = (to) => free(to) || takes(to);
173
+ if (type === "p") {
174
+ const forward = pawnPushes[color].get(square);
175
+ const pushes = free(forward[0]) ? (forward.length === 2 && free(forward[1]) ? forward : forward.slice(0, 1)) : [];
176
+ const captures = pawnCaptures[color].get(square).filter(takes);
177
+ return [...pushes, ...captures].flatMap((to) => (to[1] === "8" || to[1] === "1" ? promotions.map((promotion) => ({ from: square, to, promotion })) : [{ from: square, to }]));
178
+ }
179
+ if (type === "n" || type === "k") {
180
+ return (type === "n" ? knightSteps : kingSteps).get(square).filter(open).map((to) => ({ from: square, to }));
181
+ }
182
+ return sliderRays[type].get(square).flatMap((ray) => {
183
+ const stop = ray.findIndex((each) => !free(each));
184
+ const reached = stop === -1 ? ray : ray.slice(0, takes(ray[stop]) ? stop + 1 : stop);
185
+ return reached.map((to) => ({ from: square, to }));
186
+ });
187
+ }
188
+ }
189
+ /** The SAN of moves of the side to move of a chess.js board, and their order as chess.js lists them; read square by square, as moves need them. */
190
+ export function namesOn(boardOf) {
191
+ const bySquare = new Map();
192
+ return (move) => {
193
+ let moves = bySquare.get(move.from);
194
+ if (moves === undefined) {
195
+ moves = boardOf().moves({ square: move.from, verbose: true });
196
+ bySquare.set(move.from, moves);
197
+ }
198
+ const index = moves.findIndex((each) => each.to === move.to && each.promotion === move.promotion);
199
+ // chess.js lists the moves square by square from a8 to h1.
200
+ const square = (8 - Number(move.from[1])) * 8 + files.indexOf(move.from[0]);
201
+ return index === -1 ? undefined : { san: moves[index].san, order: square * 256 + index };
202
+ };
203
+ }
204
+ /** Whether a side may castle on a chess.js board, as far as the board of pieces alone would miss it: a right it keeps, with the squares to its rook empty. */
205
+ export function mayCastle(chess, color) {
206
+ const rank = color === "w" ? "1" : "8";
207
+ const rights = chess.getCastlingRights(color);
208
+ const empty = (between) => [...between].every((file) => chess.get(`${file}${rank}`) === undefined);
209
+ return (rights.k && empty("fg")) || (rights.q && empty("bcd"));
210
+ }
211
+ /** The same board with the other side to move, without en passant, which belonged to the side to move. */
212
+ export function turnPassed(chess) {
213
+ const fields = chess.fen().split(" ");
214
+ fields[1] = chess.turn() === "w" ? "b" : "w";
215
+ fields[3] = "-";
216
+ return new Chess(fields.join(" "));
217
+ }
218
+ const files = "abcdefgh";
219
+ const promotions = ["n", "b", "r", "q"];
220
+ const squares = [...files].flatMap((file) => [1, 2, 3, 4, 5, 6, 7, 8].map((rank) => `${file}${rank}`));
221
+ const boardOrder = [8, 7, 6, 5, 4, 3, 2, 1].flatMap((rank) => [...files].map((file) => `${file}${rank}`));
222
+ // The steps and lines from each square, worked out once, in the order chess.js tries them.
223
+ const knightSteps = stepsFrom([[-2, 1], [-1, 2], [1, 2], [2, 1], [2, -1], [1, -2], [-1, -2], [-2, -1]]);
224
+ const kingSteps = stepsFrom([[-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0]]);
225
+ // The squares a pawn of a side takes a square from, those it takes on, and those it pushes to: one, or two from its first rank.
226
+ const pawnsTaking = { w: stepsFrom([[-1, -1], [1, -1]]), b: stepsFrom([[-1, 1], [1, 1]]) };
227
+ const pawnCaptures = { w: stepsFrom([[-1, 1], [1, 1]]), b: stepsFrom([[1, -1], [-1, -1]]) };
228
+ const pawnPushes = { w: pushesFrom(1, "2"), b: pushesFrom(-1, "7") };
229
+ const sliderRays = {
230
+ b: raysFrom([[-1, 1], [1, 1], [1, -1], [-1, -1]]),
231
+ r: raysFrom([[0, 1], [1, 0], [0, -1], [-1, 0]]),
232
+ q: raysFrom([[-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0]]),
233
+ };
234
+ // Each line from each square, with the pieces that move along it.
235
+ const lineSteps = [[0, 1, "rq"], [0, -1, "rq"], [1, 0, "rq"], [-1, 0, "rq"], [1, 1, "bq"], [1, -1, "bq"], [-1, 1, "bq"], [-1, -1, "bq"]];
236
+ const linesFrom = new Map(squares.map((square) => [square, lineSteps.map(([file, rank, sliders]) => ({ squares: rayFrom(square, file, rank), sliders }))]));
237
+ // The lines of lineSteps that run opposite ways from a square.
238
+ const opposite = [[0, 1], [2, 3], [4, 7], [5, 6]];
239
+ function stepsFrom(steps) {
240
+ return new Map(squares.map((square) => [square, steps.map(([file, rank]) => squareAt(square, file, rank)).filter((each) => each !== undefined)]));
241
+ }
242
+ function raysFrom(steps) {
243
+ return new Map(squares.map((square) => [square, steps.map(([file, rank]) => rayFrom(square, file, rank))]));
244
+ }
245
+ function pushesFrom(step, firstRank) {
246
+ return new Map(squares.map((square) => [square, (square[1] === firstRank ? [step, 2 * step] : [step]).map((rank) => squareAt(square, 0, rank)).filter((each) => each !== undefined)]));
247
+ }
248
+ function rayFrom(from, fileStep, rankStep) {
249
+ const ray = [];
250
+ for (let next = squareAt(from, fileStep, rankStep); next !== undefined; next = squareAt(next, fileStep, rankStep)) {
251
+ ray.push(next);
252
+ }
253
+ return ray;
254
+ }
255
+ function squareAt(from, fileStep, rankStep) {
256
+ const file = files.indexOf(from[0]) + fileStep;
257
+ const rank = Number(from[1]) + rankStep;
258
+ return file >= 0 && file < 8 && rank >= 1 && rank <= 8 ? `${files[file]}${rank}` : undefined;
259
+ }
@@ -0,0 +1,38 @@
1
+ import { Chess, type Square } from "chess.js";
2
+ import type { Move } from "./game.ts";
3
+ /** A capture of the side to move, with what the piece taken is to the piece taking it: none when it is defended and worth less. */
4
+ export interface Capture {
5
+ readonly san: string;
6
+ readonly fact?: "undefended" | "worth more" | "same value" | "outnumbered";
7
+ }
8
+ /** A move that attacks two targets or more: a fork when the piece moved attacks them all, else a double attack with a piece it uncovers. */
9
+ export interface DoubleAttack {
10
+ readonly san: string;
11
+ readonly targets: readonly Square[];
12
+ readonly fork: boolean;
13
+ }
14
+ /** A move after which a reply of the other side stalemates the side that moved: those replies, or all when every one does. */
15
+ export interface StalemateTrap {
16
+ readonly san: string;
17
+ readonly replies: readonly string[] | "all";
18
+ }
19
+ /** What a side's moves would do to the other side. */
20
+ export interface Chances {
21
+ readonly doubleAttacks: readonly DoubleAttack[];
22
+ readonly discoveredChecks: readonly string[];
23
+ readonly doubleChecks: readonly string[];
24
+ readonly promotions: readonly string[];
25
+ }
26
+ /** What the moves of the side to move would do. */
27
+ export interface Possibilities extends Chances {
28
+ readonly captures: readonly Capture[];
29
+ readonly stalemates: readonly string[];
30
+ readonly stalemateTraps: readonly StalemateTrap[];
31
+ }
32
+ /** The possibilities of a chess.js board, whose legal moves are given: computed once for each position, by its key, and frozen. */
33
+ export declare function possibilitiesOf(chess: Chess, legal: readonly Move[]): Possibilities;
34
+ /**
35
+ * What the other side's moves would do if it were to move, as threats: none when the side to move is in check, since the other side could not be to move.
36
+ * Computed apart from the possibilities, only where they are shown: once for each position, by its key, and frozen.
37
+ */
38
+ export declare function threatsOf(chess: Chess): Chances | undefined;