chess-tactics 0.0.12 → 0.0.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # chess-tactics
2
2
 
3
- chess-tactics is a tactic detection library that returns verbose tactic descriptions given a position and an engine move continuation.
3
+ chess-tactics is an opinionated tactic detection library that finds a tactic, the pieces involved, and the tactical sequence given a position and its engine evaluation.
4
4
 
5
5
  Supported Tactics:
6
6
 
@@ -37,7 +37,6 @@ const tactics = chessTactics.classify(context); // [{type:"fork", ... }]
37
37
  Creates a new instance of the tactics classifier.
38
38
 
39
39
  - **`tacticKeys`** (optional): An array of which tactics to include. Defaults to all available tactics
40
- - **Returns**: An instance of `ChessTactics`.
41
40
 
42
41
  ---
43
42
 
@@ -46,8 +45,8 @@ Creates a new instance of the tactics classifier.
46
45
  Analyzes a board position and the sequence of moves to determine if a specific tactic has occurred.
47
46
 
48
47
  - **`context`** (TacticContext): An object containing the position & evaluation
49
- - **`options`** (TacticOptions): An object to set class behavior
50
- - **Returns**: [`Tactic[]`](#tactic).
48
+ - **`options`** [`TacticOptions`](#tactic-options): An object to modify class behavior
49
+ - **Returns**: [`Tactic[]`](#tactic). An array of tactics found in the position
51
50
 
52
51
  ---
53
52
 
@@ -61,7 +60,7 @@ Supported tactical patterns:
61
60
 
62
61
  ### `TacticContext`
63
62
 
64
- Context required for the tactic algorithms. Provide the type that supports all patterns passed to constructor
63
+ Context required for the tactic algorithms
65
64
 
66
65
  | Type | Supported Tactic Keys | Description |
67
66
  | :-------------------------------- | :------------------------------------------------------ | :-------------------------------------------------------------------- |
@@ -85,6 +84,17 @@ Context required for the tactic algorithms. Provide the type that supports all p
85
84
 
86
85
  ---
87
86
 
87
+ ### `TacticOptions`
88
+
89
+ Options to modify behavior
90
+
91
+ <a id="tactic-options"></a>
92
+
93
+ | Type | Type | Description |
94
+ | :------------------ | :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | `trimEndSequence` | `boolean` (default:`True`) | Preprocessing step that if true, trims the end of the evaluation sequence while it contains captures or checks. If you are providing a raw engine string, this should be True to avoid miscalculation of sequence material change. (Ex. Set to false if you provide puzzle evaluations that end at the puzzle's completion). |
96
+ | `maxLookaheadMoves` | `number` (default: 5) | Specifies the maximum number of half-moves (ply) that can contain checks or captures before the tactical move is played. (Ex. If set to zero, a trade preceeding a fork will not be found because the first move is not a forking move) |
97
+
88
98
  ### `Evaluation`
89
99
 
90
100
  <a id="evaluation"></a>
@@ -19,6 +19,8 @@ class BaseTactic {
19
19
  this.sequenceInterpreter.setContext(newContext);
20
20
  const tactic = this.isTactic(newContext);
21
21
  if (tactic) {
22
+ // TODO
23
+ // Why the hardcode? Could we find even some subset of behavior that allows for "two attackers vs one defender" free pieces?
22
24
  if (tactic.type === "hanging" && i > 0) {
23
25
  return null;
24
26
  }
@@ -10,7 +10,10 @@ class ForkTactics extends _tactics_1.BaseTactic {
10
10
  const chess = new chess_js_1.Chess(position);
11
11
  const currentMove = chess.move(evaluation.sequence[0]);
12
12
  const cosmeticForks = this.getCosmeticForks(position, currentMove);
13
- const attackedSquares = cosmeticForks.map((m) => m.to);
13
+ let attackedSquares = cosmeticForks.map((m) => m.to);
14
+ attackedSquares = (0, _utils_1.filterOutInitiallyAttackedSquares)(position, currentMove, attackedSquares);
15
+ if (attackedSquares.length < 2)
16
+ return null;
14
17
  const tacticalSequence = this.sequenceInterpreter.identifyWinningSequence([currentMove.to], attackedSquares);
15
18
  if (tacticalSequence) {
16
19
  return {
@@ -9,8 +9,14 @@ class PinTactics extends _tactics_1.BaseTactic {
9
9
  const { position, evaluation } = context;
10
10
  const chess = new chess_js_1.Chess(position);
11
11
  const currentMove = evaluation.sequence[0];
12
- const cosmeticPins = this.getCosmeticPins(position, currentMove);
12
+ let cosmeticPins = this.getCosmeticPins(position, currentMove);
13
13
  for (const [nextMoveWithPiece, nextMoveWithoutPiece] of cosmeticPins) {
14
+ if ((0, _utils_1.filterOutInitiallyAttackedSquares)(position, currentMove, [
15
+ nextMoveWithPiece.to,
16
+ nextMoveWithoutPiece.to,
17
+ ]).length < 2) {
18
+ continue;
19
+ }
14
20
  const tacticalSequence = this.sequenceInterpreter.identifyWinningSequence([currentMove.to], [nextMoveWithPiece.to, nextMoveWithoutPiece.to]);
15
21
  if (tacticalSequence) {
16
22
  return {
@@ -11,6 +11,12 @@ class SkewerTactics extends _tactics_1.BaseTactic {
11
11
  const currentMove = chess.move(evaluation.sequence[0]);
12
12
  const cosmeticSkewers = this.getCosmeticSkewers(context);
13
13
  for (const [nextMoveWithPiece, nextMoveWithoutPiece] of cosmeticSkewers) {
14
+ if ((0, _utils_1.filterOutInitiallyAttackedSquares)(position, currentMove, [
15
+ nextMoveWithPiece.to,
16
+ nextMoveWithoutPiece.to,
17
+ ]).length < 2) {
18
+ continue;
19
+ }
14
20
  const tacticalSequence = this.sequenceInterpreter.identifyWinningSequence([currentMove.to], [nextMoveWithPiece.to, nextMoveWithoutPiece.to]);
15
21
  if (tacticalSequence) {
16
22
  return {
@@ -36,7 +36,10 @@ class TrapTactics extends _tactics_1.BaseTactic {
36
36
  chess.move(currentMove);
37
37
  const m = capturingMoves[i];
38
38
  const fen = chess.fen();
39
+ // There are cases where previously trapped pieces are classified on the next move.
40
+ // The current move should reveal or directly attack the piece of interest
39
41
  // TODO
42
+ // Add testcases where piece is trapped, and moving away from an opponent's one check threat triggers the 'trap' on the unrelated piece
40
43
  if (this.pieceIsTrapped(fen, m)) {
41
44
  if (m.captured && _utils_1.PIECE_VALUES[m.piece] < _utils_1.PIECE_VALUES[m.captured]) {
42
45
  return {
@@ -26,11 +26,10 @@ class SequenceInterpreter {
26
26
  }
27
27
  return matches;
28
28
  }
29
- // either the pieces on attackedSquares are captured for material gain
30
- // or defender desparados on attackerSquare for a loss of material
31
- // the separation of tacticalSequence and remainingSequence
32
- // is to ensure that non-capturing engine intermezzos
33
- // still allow captures on attackedSquares to be found
29
+ // Tactic is returned if the pieces on attackerSquares capture any of the pieces on attackedSquares
30
+ // AND the resulting position after checks and captures won material
31
+ // TODO
32
+ // The isDesparado case handles 6 of 170 tests. It's probably too broad, try only desparados
34
33
  identifyWinningSequence(attackerSquares, attackedSquares) {
35
34
  if (attackedSquares.length === 0 || attackerSquares.length === 0)
36
35
  return null;
@@ -60,6 +59,11 @@ class SequenceInterpreter {
60
59
  return null;
61
60
  }
62
61
  }
62
+ // Track the attackers movements through the sequence
63
+ const attackerIdx = attackerSquares.indexOf(move.from);
64
+ if (attackerIdx !== -1) {
65
+ attackerSquares[attackerIdx] = move.to;
66
+ }
63
67
  }
64
68
  return null;
65
69
  }
@@ -7,3 +7,4 @@ export declare function getEscapeSquares(fen: Fen, square: Square): any[];
7
7
  export declare function getBlockingMoves(fen: Fen, attackingSquare: Square, threatenedSquare: Square): any[];
8
8
  export declare function getMovesToSquare(fen: Fen, square: Square): Move[];
9
9
  export declare function getThreateningMoves(position: Fen, currentMove: Move): Move[];
10
+ export declare function filterOutInitiallyAttackedSquares(position: Fen, currentMove: Move, attackedSquares: Square[]): Square[];
@@ -7,6 +7,7 @@ exports.getEscapeSquares = getEscapeSquares;
7
7
  exports.getBlockingMoves = getBlockingMoves;
8
8
  exports.getMovesToSquare = getMovesToSquare;
9
9
  exports.getThreateningMoves = getThreateningMoves;
10
+ exports.filterOutInitiallyAttackedSquares = filterOutInitiallyAttackedSquares;
10
11
  const chess_js_1 = require("chess.js");
11
12
  const _utils_1 = require("./index");
12
13
  function attackingSquareIsGood(fen, square, startingMove = null) {
@@ -163,7 +164,8 @@ function getThreateningMoves(position, currentMove) {
163
164
  const threateningMoves = [];
164
165
  for (const m of possibleMoves) {
165
166
  for (const n of possibleMoves) {
166
- if (n.captured !== "k" && n.to !== m.to)
167
+ // we remove the pieces to avoid the scenario where one of the forked pieces 'defends' the other through the attacking piece and nullifies the tactic
168
+ if (n.captured !== "k" && n.captured !== "p" && n.to !== m.to)
167
169
  chess.remove(n.to);
168
170
  }
169
171
  if (m.captured && attackingSquareIsGood(chess.fen(), m.to, m) && m.captured !== m.piece) {
@@ -175,3 +177,10 @@ function getThreateningMoves(position, currentMove) {
175
177
  }
176
178
  return threateningMoves;
177
179
  }
180
+ function filterOutInitiallyAttackedSquares(position, currentMove, attackedSquares) {
181
+ const chess = new chess_js_1.Chess(position);
182
+ const attackingPieceInitialMoves = chess
183
+ .moves({ square: currentMove.from, verbose: true })
184
+ .map((m) => m.to);
185
+ return attackedSquares.filter((s) => !attackingPieceInitialMoves.includes(s));
186
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chess-tactics",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/antonryoung02/chess-tactics.git"