chess-tactics 0.0.1

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 (43) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +145 -0
  3. package/dist/ChessTactics.d.ts +8 -0
  4. package/dist/ChessTactics.js +37 -0
  5. package/dist/TacticFactory.d.ts +4 -0
  6. package/dist/TacticFactory.js +25 -0
  7. package/dist/_types.d.ts +15 -0
  8. package/dist/_types.js +2 -0
  9. package/dist/errors.d.ts +7 -0
  10. package/dist/errors.js +12 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +23 -0
  13. package/dist/tactics/BaseTactic.d.ts +13 -0
  14. package/dist/tactics/BaseTactic.js +68 -0
  15. package/dist/tactics/Fork.d.ts +9 -0
  16. package/dist/tactics/Fork.js +32 -0
  17. package/dist/tactics/HangingPiece.d.ts +7 -0
  18. package/dist/tactics/HangingPiece.js +34 -0
  19. package/dist/tactics/Pin.d.ts +10 -0
  20. package/dist/tactics/Pin.js +97 -0
  21. package/dist/tactics/Sacrifice.d.ts +9 -0
  22. package/dist/tactics/Sacrifice.js +41 -0
  23. package/dist/tactics/Skewer.d.ts +9 -0
  24. package/dist/tactics/Skewer.js +81 -0
  25. package/dist/tactics/Trap.d.ts +12 -0
  26. package/dist/tactics/Trap.js +100 -0
  27. package/dist/tactics/index.d.ts +7 -0
  28. package/dist/tactics/index.js +17 -0
  29. package/dist/types.d.ts +46 -0
  30. package/dist/types.js +2 -0
  31. package/dist/utils/SequenceInterpreter.d.ts +13 -0
  32. package/dist/utils/SequenceInterpreter.js +88 -0
  33. package/dist/utils/TacticContextParser.d.ts +9 -0
  34. package/dist/utils/TacticContextParser.js +109 -0
  35. package/dist/utils/TacticHeuristics.d.ts +9 -0
  36. package/dist/utils/TacticHeuristics.js +177 -0
  37. package/dist/utils/Utils.d.ts +18 -0
  38. package/dist/utils/Utils.js +106 -0
  39. package/dist/utils/constants.d.ts +5 -0
  40. package/dist/utils/constants.js +22 -0
  41. package/dist/utils/index.d.ts +5 -0
  42. package/dist/utils/index.js +24 -0
  43. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # chess-tactics
2
+
3
+ chess-tactics is a tactic detection library that returns verbose tactic descriptions given a position and an engine move continuation.
4
+
5
+ Supported Tactics:
6
+
7
+ - Fork
8
+ - Pin
9
+ - Skewer
10
+ - Sacrifice
11
+ - Trapped Piece
12
+ - Hanging Piece
13
+
14
+ ## Installation
15
+
16
+ (npm release in progress)
17
+
18
+ ## Example Usage
19
+
20
+ ```typescript
21
+ const chessTactics = new ChessTactics(["fork", "pin", "skewer", "trap"]);
22
+ const context = {
23
+ position: "8/4r1k1/8/8/8/2PPN3/1PK5/8 w - - 0 1",
24
+ evaluation: {
25
+ sequence: "e3f5 g7f6 f5e7 f6e6 c2b3 e6d6 b3c4",
26
+ },
27
+ };
28
+ const tactics = chessTactics.classify(context); // [{type:"fork", ... }]
29
+ ```
30
+
31
+ ## API Reference
32
+
33
+ ### `ChessTactics`
34
+
35
+ #### `constructor(tacticKeys?:` [`TacticKey[]`](#tactickey) `)`
36
+
37
+ Creates a new instance of the tactics classifier.
38
+
39
+ - **`tacticKeys`** (optional): An array of which tactics to include. Defaults to all available tactics
40
+ - **Returns**: An instance of `ChessTactics`.
41
+
42
+ ---
43
+
44
+ #### `.classify(context:TacticContext, options:TacticOptions?)`
45
+
46
+ Analyzes a board position and the sequence of moves to determine if a specific tactic has occurred.
47
+
48
+ - **`context`** (TacticContext): An object containing the position & evaluation
49
+ - **`options`** (TacticOptions): An object to set class behavior
50
+ - **Returns**: [`Tactic[]`](#tactic).
51
+
52
+ ---
53
+
54
+ ## Type Definitions
55
+
56
+ ### `TacticKey`
57
+
58
+ <a id="tactickey"></a>
59
+ Supported tactical patterns:
60
+ `"fork"` | `"pin"` | `"skewer"` | `"sacrifice"` | `"trap"` | `"hanging"`
61
+
62
+ ### `TacticContext`
63
+
64
+ Context required for the tactic algorithms. Provide the type that supports all patterns passed to constructor
65
+
66
+ | Type | Supported Tactic Keys | Description |
67
+ | :-------------------------------- | :------------------------------------------------------ | :-------------------------------------------------------------------- |
68
+ | `DefaultTacticContext` | `fork`, `pin`, `skewer`, `trap`, `sacrifice` | Standard analysis requiring only the current position and evaluation. |
69
+ | `PositionComparisonTacticContext` | `hanging`, `fork`, `pin`, `skewer`, `trap`, `sacrifice` | For tactics that require knowledge of the previous move's state |
70
+
71
+ ---
72
+
73
+ #### `DefaultTacticContext`
74
+
75
+ - **`position`**: `Fen` (String)
76
+ - **`evaluation`**: [`Evaluation`](#evaluation)
77
+
78
+ #### `PositionComparisonTacticContext`
79
+
80
+ - **`position`**: `Fen` (String)
81
+ - **`evaluation`**: [`Evaluation`](#evaluation)
82
+ - **`prevPosition`**: `Fen`
83
+ - **`prevEvaluation`**: [`Evaluation`](#evaluation)
84
+ - **`prevMove`**: `Move`
85
+
86
+ ---
87
+
88
+ ### `Evaluation`
89
+
90
+ <a id="evaluation"></a>
91
+
92
+ The engine evaluation of a position, separated into a type union for convenience
93
+
94
+ | Type | Description |
95
+ | :--------------- | :------------------------------------------------------------------------------------------------------- |
96
+ | `UciEvaluation` | Sequence containing the best line in a position |
97
+ | `MoveEvaluation` | Sequence containing the best line in a position where the first move and followup sequence are separated |
98
+
99
+ ---
100
+
101
+ #### `UciEvaluation`
102
+
103
+ - **`sequence`**: `string` | `string[]` | [`Move[]`](#external-types-chessjs)
104
+ - The evaluation sequence
105
+
106
+ #### `MoveEvaluation`
107
+
108
+ - **`move`**: `string` | `{ from: string; to: string }` | `Move`
109
+ - The first move in the evaluation sequence.
110
+ - **`followup`**: `string` | `string[]` | [`Move[]`](#external-types-chessjs)
111
+ - The expected sequence of moves following the initial move.
112
+
113
+ ---
114
+
115
+ ### `Tactic`
116
+
117
+ <a id="tactic"></a>
118
+ The final object returned by the classification engine. It contains the identified tactical theme along with the board state changes.
119
+
120
+ | Property | Type | Description |
121
+ | :--------------- | :---------------------------------------------------------- | :---------------------------------------------------------------------- |
122
+ | `type` | [`TacticKey`](#tactickey) | The category of tactic (e.g., fork, pin). |
123
+ | `attackedPieces` | [`{piece:Piece, square:Square}[]`](#external-types-chessjs) | The pieces involved on the recieving end of the tactic |
124
+ | `sequence` | [`Move[]`](#external-types-chessjs) | The array of moves consisting of the entire tactical sequence. |
125
+ | `triggerIndex` | `number` | The `sequence` index of the tactical move |
126
+ | `startPosition` | `Fen` | The FEN string of the position before the sequence. |
127
+ | `endPosition` | `Fen` | The FEN string of the position after the sequence. |
128
+ | `materialChange` | `number` | The net material gain for the player of the tactic (e.g., `3` or `-5`). |
129
+
130
+ ---
131
+
132
+ ### External Types (chess.js)
133
+
134
+ <a id="chessjs-types"></a>
135
+ This library accepts and returns types from [chess.js](https://github.com/jhlywa/chess.js) for board representation:
136
+
137
+ **`Square`**: A string representing a square on the board (e.g., `'a1'`, `'h8'`).
138
+
139
+ **`Piece`**: `{ type: 'p' | 'n' | 'b' | 'r' | 'q' | 'k', color: 'w' | 'b' }`.
140
+
141
+ **`Move`**: A move object containing `from`, `to`, `promotion`, and `san`
142
+
143
+ ### Dependencies
144
+
145
+ `chess.js: ^1.4.0`
@@ -0,0 +1,8 @@
1
+ import { Tactic, TacticContext, TacticKey, TacticOptions } from "./types";
2
+ export declare class ChessTactics {
3
+ private tacticClassifiers;
4
+ private selectedTactics;
5
+ constructor(tacticKeys?: TacticKey[]);
6
+ private initializeClassifiers;
7
+ classify(context: TacticContext, options?: TacticOptions): Tactic[];
8
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChessTactics = void 0;
4
+ const TacticFactory_1 = require("./TacticFactory");
5
+ const TacticContextParser_1 = require("./utils/TacticContextParser");
6
+ const _utils_1 = require("./utils/index");
7
+ class ChessTactics {
8
+ tacticClassifiers;
9
+ selectedTactics;
10
+ constructor(tacticKeys = []) {
11
+ this.selectedTactics = tacticKeys;
12
+ this.initializeClassifiers();
13
+ }
14
+ initializeClassifiers() {
15
+ if (this.selectedTactics.length === 0) {
16
+ this.selectedTactics = _utils_1.DEFAULT_TACTIC_CLASSIFIERS;
17
+ }
18
+ else {
19
+ this.tacticClassifiers = [];
20
+ for (const t of this.selectedTactics) {
21
+ this.tacticClassifiers.push(TacticFactory_1.TacticFactory.create(t));
22
+ }
23
+ }
24
+ }
25
+ classify(context, options = _utils_1.DEFAULT_TACTIC_OPTIONS) {
26
+ const tactics = [];
27
+ const mergedOptions = { ..._utils_1.DEFAULT_TACTIC_OPTIONS, ...options };
28
+ const internalContext = TacticContextParser_1.TacticContextParser.parse(context, mergedOptions);
29
+ for (const classifier of this.tacticClassifiers) {
30
+ const t = classifier.findTactic(internalContext);
31
+ if (t)
32
+ tactics.push(t);
33
+ }
34
+ return tactics;
35
+ }
36
+ }
37
+ exports.ChessTactics = ChessTactics;
@@ -0,0 +1,4 @@
1
+ import { TacticClassifier, TacticKey } from "./types";
2
+ export declare class TacticFactory {
3
+ static create(type: TacticKey): TacticClassifier;
4
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TacticFactory = void 0;
4
+ const _tactics_1 = require("./tactics/index");
5
+ const _utils_1 = require("./utils/index");
6
+ class TacticFactory {
7
+ static create(type) {
8
+ const sequenceInterpreter = new _utils_1.SequenceInterpreter();
9
+ switch (type) {
10
+ case "fork":
11
+ return new _tactics_1.ForkTactics(sequenceInterpreter);
12
+ case "pin":
13
+ return new _tactics_1.PinTactics(sequenceInterpreter);
14
+ case "skewer":
15
+ return new _tactics_1.SkewerTactics(sequenceInterpreter);
16
+ case "sacrifice":
17
+ return new _tactics_1.SacrificeTactics(sequenceInterpreter);
18
+ case "trap":
19
+ return new _tactics_1.TrapTactics(sequenceInterpreter);
20
+ case "hanging":
21
+ return new _tactics_1.HangingPieceTactics(sequenceInterpreter);
22
+ }
23
+ }
24
+ }
25
+ exports.TacticFactory = TacticFactory;
@@ -0,0 +1,15 @@
1
+ import { Move } from "chess.js";
2
+ import { Fen } from "./types";
3
+ export type _Evaluation = {
4
+ sequence: Move[];
5
+ };
6
+ export type _DefaultTacticContext = {
7
+ position: Fen;
8
+ evaluation: _Evaluation;
9
+ };
10
+ export type _PositionComparisonTacticContext = _DefaultTacticContext & {
11
+ prevEvaluation: _Evaluation;
12
+ prevPosition: Fen;
13
+ prevMove: Move;
14
+ };
15
+ export type _TacticContext = _DefaultTacticContext | _PositionComparisonTacticContext;
package/dist/_types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ export type ChessTacticParserErrorCode = "INVALID_FEN" | "INVALID_MOVE" | "INVALID_FOLLOWUP" | "INVALID_SEQUENCE";
2
+ export declare class ChessTacticsParserError extends Error {
3
+ code: ChessTacticParserErrorCode;
4
+ constructor(message: string, code: ChessTacticParserErrorCode, options?: {
5
+ cause?: unknown;
6
+ });
7
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChessTacticsParserError = void 0;
4
+ class ChessTacticsParserError extends Error {
5
+ code;
6
+ constructor(message, code, options) {
7
+ super(message, options);
8
+ this.name = "ChessTacticsParserError";
9
+ this.code = code;
10
+ }
11
+ }
12
+ exports.ChessTacticsParserError = ChessTacticsParserError;
@@ -0,0 +1,4 @@
1
+ export * from "./types";
2
+ export { ChessTactics } from "./ChessTactics";
3
+ export { TacticFactory } from "./TacticFactory";
4
+ export * from "./errors";
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.TacticFactory = exports.ChessTactics = void 0;
18
+ __exportStar(require("./types"), exports);
19
+ var ChessTactics_1 = require("./ChessTactics");
20
+ Object.defineProperty(exports, "ChessTactics", { enumerable: true, get: function () { return ChessTactics_1.ChessTactics; } });
21
+ var TacticFactory_1 = require("./TacticFactory");
22
+ Object.defineProperty(exports, "TacticFactory", { enumerable: true, get: function () { return TacticFactory_1.TacticFactory; } });
23
+ __exportStar(require("./errors"), exports);
@@ -0,0 +1,13 @@
1
+ import { Tactic, TacticClassifier } from "../types";
2
+ import { SequenceInterpreter } from "../utils/index";
3
+ import { _TacticContext } from "src/_types";
4
+ import { Move } from "chess.js";
5
+ export declare class BaseTactic implements TacticClassifier {
6
+ protected sequenceInterpreter: SequenceInterpreter;
7
+ constructor(sequenceInterpreter: SequenceInterpreter);
8
+ findTactic(context: _TacticContext): Tactic | null;
9
+ isTactic(context: _TacticContext): Partial<Tactic> | null;
10
+ wrapTactic(tactic: Partial<Tactic>, context: _TacticContext, sequence: Move[], sequenceIndex: number): Tactic;
11
+ private isForcingPosition;
12
+ private contextAtState;
13
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseTactic = void 0;
4
+ const _utils_1 = require("../utils/index");
5
+ const chess_js_1 = require("chess.js");
6
+ class BaseTactic {
7
+ sequenceInterpreter;
8
+ constructor(sequenceInterpreter) {
9
+ this.sequenceInterpreter = sequenceInterpreter;
10
+ }
11
+ // Detects non-immediate tactics by iteratively calling isTactic on the position as long as the previous attacker move was 'forcing'
12
+ findTactic(context) {
13
+ let sequence = context.evaluation.sequence;
14
+ const chess = new chess_js_1.Chess(context.position);
15
+ let isForcing = true;
16
+ let i = 0;
17
+ while (isForcing && i < sequence.length - 2) {
18
+ const newContext = this.contextAtState(context, chess.fen(), sequence, i);
19
+ this.sequenceInterpreter.setContext(newContext);
20
+ const tactic = this.isTactic(newContext);
21
+ if (tactic) {
22
+ return this.wrapTactic(tactic, context, sequence, i);
23
+ }
24
+ // Play your move and opponent's response
25
+ const currMove = chess.move(sequence[i]);
26
+ isForcing = this.isForcingPosition(chess, currMove);
27
+ chess.move(sequence[i + 1]);
28
+ i += 2;
29
+ }
30
+ return null;
31
+ }
32
+ isTactic(context) {
33
+ throw new Error("BaseTactic.isTactic must be overridden in subclass");
34
+ }
35
+ wrapTactic(tactic, context, sequence, sequenceIndex) {
36
+ return {
37
+ type: tactic.type,
38
+ endPosition: tactic.endPosition,
39
+ attackedPieces: tactic.attackedPieces,
40
+ startPosition: context.position,
41
+ materialChange: (0, _utils_1.getMaterialChange)(context.position, tactic.endPosition, (0, _utils_1.colorToPlay)(context.position)),
42
+ triggerIndex: sequenceIndex,
43
+ sequence: sequence.slice(0, sequenceIndex).concat(tactic.sequence),
44
+ };
45
+ }
46
+ // could try adding 'madeThreat' to the isForcing if needed
47
+ // most likely would require a multipv > 1 engine evaluation for anything better than this
48
+ isForcingPosition(chess, move) {
49
+ return move.captured !== undefined || chess.inCheck();
50
+ }
51
+ contextAtState(context, position, sequence, sequenceIndex) {
52
+ let newContext = {
53
+ ...context,
54
+ position: position,
55
+ evaluation: { sequence: sequence.slice(sequenceIndex) },
56
+ };
57
+ if ("prevEvaluation" in context && sequenceIndex > 0) {
58
+ newContext = {
59
+ ...newContext,
60
+ prevPosition: context.position,
61
+ prevMove: sequence[sequenceIndex - 1],
62
+ prevEvaluation: context.evaluation,
63
+ };
64
+ }
65
+ return newContext;
66
+ }
67
+ }
68
+ exports.BaseTactic = BaseTactic;
@@ -0,0 +1,9 @@
1
+ import { Move } from "chess.js";
2
+ import { Fen, Tactic } from "../types";
3
+ import { BaseTactic } from "./index";
4
+ import { _DefaultTacticContext } from "src/_types";
5
+ declare class ForkTactics extends BaseTactic {
6
+ isTactic(context: _DefaultTacticContext): Partial<Tactic> | null;
7
+ getCosmeticForks(position: Fen, currentMove: Move): Move[];
8
+ }
9
+ export { ForkTactics };
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ForkTactics = void 0;
4
+ const chess_js_1 = require("chess.js");
5
+ const _utils_1 = require("../utils/index");
6
+ const _tactics_1 = require("./index");
7
+ class ForkTactics extends _tactics_1.BaseTactic {
8
+ isTactic(context) {
9
+ const { position, evaluation } = context;
10
+ const chess = new chess_js_1.Chess(position);
11
+ const currentMove = chess.move(evaluation.sequence[0]);
12
+ const cosmeticForks = this.getCosmeticForks(position, currentMove);
13
+ const attackedSquares = cosmeticForks.map((m) => m.to);
14
+ const tacticalSequence = this.sequenceInterpreter.identifyWinningSequence([currentMove.to], attackedSquares);
15
+ if (tacticalSequence) {
16
+ return {
17
+ type: "fork",
18
+ attackedPieces: attackedSquares.map((s) => ({ square: s, piece: chess.get(s) })),
19
+ ...tacticalSequence,
20
+ };
21
+ }
22
+ return null;
23
+ }
24
+ getCosmeticForks(position, currentMove) {
25
+ const threateningMoves = (0, _utils_1.getThreateningMoves)(position, currentMove);
26
+ if (threateningMoves.length >= 2) {
27
+ return threateningMoves;
28
+ }
29
+ return [];
30
+ }
31
+ }
32
+ exports.ForkTactics = ForkTactics;
@@ -0,0 +1,7 @@
1
+ import { BaseTactic } from "./index";
2
+ import { _PositionComparisonTacticContext } from "src/_types";
3
+ import { Tactic } from "../types";
4
+ declare class HangingPieceTactics extends BaseTactic {
5
+ isTactic(context: _PositionComparisonTacticContext): Partial<Tactic> | null;
6
+ }
7
+ export { HangingPieceTactics };
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HangingPieceTactics = void 0;
4
+ const chess_js_1 = require("chess.js");
5
+ const _utils_1 = require("../utils/index");
6
+ const _tactics_1 = require("./index");
7
+ class HangingPieceTactics extends _tactics_1.BaseTactic {
8
+ isTactic(context) {
9
+ const { position, evaluation, prevMove } = context;
10
+ const chess = new chess_js_1.Chess(position);
11
+ const currentMove = evaluation.sequence[0];
12
+ if (!currentMove.captured) {
13
+ return null;
14
+ }
15
+ if (prevMove.captured &&
16
+ _utils_1.PIECE_VALUES[prevMove.captured] >= _utils_1.PIECE_VALUES[currentMove.captured]) {
17
+ return null;
18
+ }
19
+ chess.load(position);
20
+ const attackers = chess.attackers(currentMove.to);
21
+ const tacticalSequence = this.sequenceInterpreter.identifyWinningSequence(attackers, [
22
+ currentMove.to,
23
+ ]);
24
+ if (tacticalSequence) {
25
+ return {
26
+ type: "hanging",
27
+ attackedPieces: [{ square: currentMove.to, piece: chess.get(currentMove.to) }],
28
+ ...tacticalSequence,
29
+ };
30
+ }
31
+ return null;
32
+ }
33
+ }
34
+ exports.HangingPieceTactics = HangingPieceTactics;
@@ -0,0 +1,10 @@
1
+ import { Move } from "chess.js";
2
+ import { BaseTactic } from "./index";
3
+ import { _DefaultTacticContext } from "src/_types";
4
+ import { Fen, Tactic } from "../types";
5
+ declare class PinTactics extends BaseTactic {
6
+ isTactic(context: _DefaultTacticContext): Partial<Tactic> | null;
7
+ getCosmeticPins(position: Fen, currentMove: Move): Array<Array<Move>>;
8
+ movePreventsPawnMobility(position: Fen, currentMove: Move, moveCapturingPawn: Move, moveCapturingBehindPiece: Move): boolean;
9
+ }
10
+ export { PinTactics };
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PinTactics = void 0;
4
+ const chess_js_1 = require("chess.js");
5
+ const _utils_1 = require("../utils/index");
6
+ const _tactics_1 = require("./index");
7
+ class PinTactics extends _tactics_1.BaseTactic {
8
+ isTactic(context) {
9
+ const { position, evaluation } = context;
10
+ const chess = new chess_js_1.Chess(position);
11
+ const currentMove = evaluation.sequence[0];
12
+ const cosmeticPins = this.getCosmeticPins(position, currentMove);
13
+ for (const [nextMoveWithPiece, nextMoveWithoutPiece] of cosmeticPins) {
14
+ const tacticalSequence = this.sequenceInterpreter.identifyWinningSequence([currentMove.to], [nextMoveWithPiece.to, nextMoveWithoutPiece.to]);
15
+ if (tacticalSequence) {
16
+ return {
17
+ type: "pin",
18
+ attackedPieces: [
19
+ { square: nextMoveWithPiece.to, piece: chess.get(nextMoveWithPiece.to) },
20
+ {
21
+ square: nextMoveWithoutPiece.to,
22
+ piece: chess.get(nextMoveWithoutPiece.to),
23
+ },
24
+ ],
25
+ ...tacticalSequence,
26
+ };
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ getCosmeticPins(position, currentMove) {
32
+ if (["p", "k", "n"].includes(currentMove.piece)) {
33
+ return [];
34
+ }
35
+ const chess = new chess_js_1.Chess(position);
36
+ chess.move(currentMove);
37
+ // On the next turn where can you move this piece
38
+ (0, _utils_1.invertTurn)(chess);
39
+ const possibleNextMoves = chess.moves({ square: currentMove.to, verbose: true });
40
+ const cosmeticPins = [];
41
+ for (const move of possibleNextMoves) {
42
+ // Can't pin a king or same piece
43
+ if (!move.captured || move.captured === "k" || move.captured === currentMove.piece) {
44
+ continue;
45
+ }
46
+ // Return to on next turn position and remove the piece
47
+ chess.load(position);
48
+ chess.move(currentMove);
49
+ chess.remove(move.to);
50
+ (0, _utils_1.invertTurn)(chess);
51
+ // Is a more valuable piece behind the capturable pieces
52
+ const possibleNextMovesWithoutPiece = chess.moves({
53
+ square: currentMove.to,
54
+ verbose: true,
55
+ });
56
+ const newMoves = (0, _utils_1.getMoveDiff)(possibleNextMoves, possibleNextMovesWithoutPiece);
57
+ // A cosmetic pin is 'did my piece move to a square that pins any lower value piece to a higher value piece'
58
+ for (const m of newMoves) {
59
+ if (m.captured && _utils_1.PIECE_VALUES[move.captured] < _utils_1.PIECE_VALUES[m.captured]) {
60
+ if (move.captured === "p" &&
61
+ !this.movePreventsPawnMobility(position, currentMove, move, m)) {
62
+ continue;
63
+ }
64
+ cosmeticPins.push([move, m]); // [move to capture pinned piece, move to capture piece behind it]
65
+ }
66
+ }
67
+ }
68
+ return cosmeticPins;
69
+ }
70
+ movePreventsPawnMobility(position, currentMove, moveCapturingPawn, moveCapturingBehindPiece) {
71
+ // do any of the possible pawn movements uncover the pin -> losing material?
72
+ if (moveCapturingBehindPiece.captured === "k")
73
+ return true;
74
+ const pawnSquare = moveCapturingPawn.to;
75
+ const chess = new chess_js_1.Chess(position);
76
+ chess.move(currentMove);
77
+ const movesAfter = chess.moves({ square: pawnSquare, verbose: true });
78
+ // this case is mostly for king where the pawn movement is truly restricted
79
+ // this doesn't cover when currentMove blocks the pawn directly. commenting out
80
+ // now to see if the pawn move uncovers the pinned piece
81
+ let doesPrevent = false;
82
+ for (const move of movesAfter) {
83
+ chess.load(position);
84
+ chess.move(currentMove);
85
+ chess.move(move);
86
+ // the pawn can capture your pinning piece
87
+ if (move.to === currentMove.to) {
88
+ return false;
89
+ }
90
+ if (chess.attackers(moveCapturingBehindPiece.to).length > 0) {
91
+ doesPrevent = true;
92
+ }
93
+ }
94
+ return doesPrevent;
95
+ }
96
+ }
97
+ exports.PinTactics = PinTactics;
@@ -0,0 +1,9 @@
1
+ import { Move } from "chess.js";
2
+ import { Fen, Tactic } from "../types";
3
+ import { BaseTactic } from "./index";
4
+ import { _DefaultTacticContext } from "src/_types";
5
+ declare class SacrificeTactics extends BaseTactic {
6
+ isTactic(context: _DefaultTacticContext): Partial<Tactic> | null;
7
+ sacrificedMaterial(position: Fen, currentMove: Move): boolean;
8
+ }
9
+ export { SacrificeTactics };
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SacrificeTactics = void 0;
4
+ const chess_js_1 = require("chess.js");
5
+ const _utils_1 = require("../utils/index");
6
+ const _tactics_1 = require("./index");
7
+ class SacrificeTactics extends _tactics_1.BaseTactic {
8
+ isTactic(context) {
9
+ const { position, evaluation } = context;
10
+ const chess = new chess_js_1.Chess(position);
11
+ const currentMove = evaluation.sequence[0];
12
+ chess.move(currentMove);
13
+ const captureSequence = [currentMove].concat(this.sequenceInterpreter.getCaptureSequence(chess.fen(), evaluation.sequence.slice(1)));
14
+ chess.undo();
15
+ if (this.sacrificedMaterial(position, currentMove) && captureSequence.length > 0) {
16
+ const endPosition = this.sequenceInterpreter.positionAfterSequence(position, captureSequence);
17
+ const materialChange = (0, _utils_1.getMaterialChange)(position, endPosition, (0, _utils_1.colorToPlay)(position));
18
+ return {
19
+ type: "sacrifice",
20
+ attackedPieces: [{ square: currentMove.to, piece: chess.get(currentMove.to) }],
21
+ sequence: captureSequence,
22
+ startPosition: position,
23
+ endPosition: endPosition,
24
+ materialChange: materialChange,
25
+ };
26
+ }
27
+ return null;
28
+ }
29
+ sacrificedMaterial(position, currentMove) {
30
+ if (currentMove.promotion || currentMove.piece === "p") {
31
+ return false;
32
+ }
33
+ if ((0, _utils_1.attackingSquareIsBad)(position, currentMove.to, currentMove)) {
34
+ const materialGained = currentMove.captured ? _utils_1.PIECE_VALUES[currentMove.captured] : 0;
35
+ const materialLost = _utils_1.PIECE_VALUES[currentMove.piece];
36
+ return materialGained - materialLost < 0;
37
+ }
38
+ return false;
39
+ }
40
+ }
41
+ exports.SacrificeTactics = SacrificeTactics;
@@ -0,0 +1,9 @@
1
+ import { Move } from "chess.js";
2
+ import { Tactic } from "../types";
3
+ import { BaseTactic } from "./index";
4
+ import { _DefaultTacticContext, _TacticContext } from "src/_types";
5
+ declare class SkewerTactics extends BaseTactic {
6
+ isTactic(context: _DefaultTacticContext): Partial<Tactic> | null;
7
+ getCosmeticSkewers(context: _TacticContext): Move[][];
8
+ }
9
+ export { SkewerTactics };