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.
- package/LICENSE +674 -0
- package/README.md +145 -0
- package/dist/ChessTactics.d.ts +8 -0
- package/dist/ChessTactics.js +37 -0
- package/dist/TacticFactory.d.ts +4 -0
- package/dist/TacticFactory.js +25 -0
- package/dist/_types.d.ts +15 -0
- package/dist/_types.js +2 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +12 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +23 -0
- package/dist/tactics/BaseTactic.d.ts +13 -0
- package/dist/tactics/BaseTactic.js +68 -0
- package/dist/tactics/Fork.d.ts +9 -0
- package/dist/tactics/Fork.js +32 -0
- package/dist/tactics/HangingPiece.d.ts +7 -0
- package/dist/tactics/HangingPiece.js +34 -0
- package/dist/tactics/Pin.d.ts +10 -0
- package/dist/tactics/Pin.js +97 -0
- package/dist/tactics/Sacrifice.d.ts +9 -0
- package/dist/tactics/Sacrifice.js +41 -0
- package/dist/tactics/Skewer.d.ts +9 -0
- package/dist/tactics/Skewer.js +81 -0
- package/dist/tactics/Trap.d.ts +12 -0
- package/dist/tactics/Trap.js +100 -0
- package/dist/tactics/index.d.ts +7 -0
- package/dist/tactics/index.js +17 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.js +2 -0
- package/dist/utils/SequenceInterpreter.d.ts +13 -0
- package/dist/utils/SequenceInterpreter.js +88 -0
- package/dist/utils/TacticContextParser.d.ts +9 -0
- package/dist/utils/TacticContextParser.js +109 -0
- package/dist/utils/TacticHeuristics.d.ts +9 -0
- package/dist/utils/TacticHeuristics.js +177 -0
- package/dist/utils/Utils.d.ts +18 -0
- package/dist/utils/Utils.js +106 -0
- package/dist/utils/constants.d.ts +5 -0
- package/dist/utils/constants.js +22 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +24 -0
- 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,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;
|
package/dist/_types.d.ts
ADDED
|
@@ -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
package/dist/errors.d.ts
ADDED
|
@@ -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;
|
package/dist/index.d.ts
ADDED
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 };
|