Add tactic position generators and tests

This commit is contained in:
stefan-kp
2025-12-06 09:23:04 +01:00
parent 1b2159445d
commit 743bda02bf
12 changed files with 844 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
# Tactical and Gambit Fixture Sets
This directory collects JSON fixtures for automated regression tests and training data generation.
Each file contains narrowly scoped cases for a single tactical motif or gambit family so that Jest
or other tooling can load them directly without hard-coding FEN strings in test suites.
## File naming
- `tactics/<pattern>.json` Examples for one tactical motif. Each file follows the same schema.
- `gambits.json` Openings that should be recognised by move-sequence matching.
## Common schema
Every tactical case shares the following shape:
```json
{
"patternType": "PIN",
"description": "Short explanation of the motif and what the move achieves.",
"cases": [
{
"id": "unique-string",
"initialFen": "...",
"sideToMove": "white",
"bestMove": { "san": "Bb5", "uci": "f1b5" },
"resultingFen": "...",
"expectedPattern": {
"type": "PIN",
"side": "white",
"attackerSquares": ["b5"],
"targetSquares": ["c6"],
"keySquares": ["e8"],
"explanationKey": "pin_to_king"
},
"context": "Optional PGN fragment or free-text rationale.",
"tags": ["opening", "classical"]
}
]
}
```
This layout aligns with the `TacticalPattern` interface defined in the library contract and keeps
enough metadata for round-trip testing: the move that produces the tactic, the resulting position,
and the expected pattern payload.
## Using the fixtures in tests
- Load the JSON files and iterate through `cases` to drive `detectTactics`,
`findTacticalOpportunitiesForSide`, or higher-level exercise generators.
- The `initialFen` represents the position **before** the critical move is played.
- Apply `bestMove` to reach `resultingFen`, then assert that the reported pattern matches
`expectedPattern`.
## Adding more cases
When adding new fixtures, prefer minimal positions that isolate the intended motif so failures
are easy to debug. Include a short `context` note so the scenario can be recreated or visualised
quickly when a test fails.
+32
View File
@@ -0,0 +1,32 @@
{
"gambits": [
{
"id": "kings_gambit",
"name": "King's Gambit",
"ecoCodes": ["C30", "C31", "C32", "C33"],
"side": "white",
"moveSequenceSan": ["e4", "e5", "f4"]
},
{
"id": "evans_gambit",
"name": "Evans Gambit",
"ecoCodes": ["C51"],
"side": "white",
"moveSequenceSan": ["e4", "e5", "Nf3", "Nc6", "Bc4", "Bc5", "b4"]
},
{
"id": "smith_morra_gambit",
"name": "Smith-Morra Gambit",
"ecoCodes": ["B21"],
"side": "white",
"moveSequenceSan": ["e4", "c5", "d4", "cxd4", "c3"]
},
{
"id": "benko_gambit",
"name": "Benko Gambit",
"ecoCodes": ["A57", "A58", "A59"],
"side": "black",
"moveSequenceSan": ["d4", "Nf6", "c4", "c5", "d5", "b5"]
}
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"patternType": "BACK_RANK_WEAKNESS",
"description": "Classic back-rank mate with no escape squares for the king.",
"cases": [
{
"id": "back_rank_rook_mate",
"initialFen": "6k1/5ppp/8/8/8/8/5PPP/R5K1 w - - 0 1",
"sideToMove": "white",
"bestMove": { "san": "Ra8#", "uci": "a1a8" },
"resultingFen": "R5k1/5ppp/8/8/8/8/5PPP/6K1 b - - 1 1",
"expectedPattern": {
"type": "BACK_RANK_WEAKNESS",
"side": "white",
"attackerSquares": ["a8"],
"targetSquares": ["g8"],
"explanationKey": "back_rank_mate"
},
"context": "No luft and the king is boxed in; the rook delivers mate on the back rank.",
"tags": ["mate", "fundamental"]
}
]
}
+23
View File
@@ -0,0 +1,23 @@
{
"patternType": "DISCOVERED_CHECK",
"description": "Bishop steps aside to reveal a rook check along the open file.",
"cases": [
{
"id": "discovered_check_rook_on_open_file",
"initialFen": "4k3/8/8/8/8/8/4B3/4R1K1 w - - 0 1",
"sideToMove": "white",
"bestMove": { "san": "Bb5+", "uci": "e2b5" },
"resultingFen": "4k3/8/8/1B6/8/8/8/4R1K1 b - - 1 1",
"expectedPattern": {
"type": "DISCOVERED_CHECK",
"side": "white",
"attackerSquares": ["e1"],
"targetSquares": ["e8"],
"keySquares": ["b5"],
"explanationKey": "rook_file_discovery"
},
"context": "Moving the bishop from e2 to b5 exposes the rook on e1, delivering check on the e-file.",
"tags": ["discovery", "rook_file"]
}
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"patternType": "DOUBLE_ATTACK",
"description": "Queen move that checks the king while targeting an undefended rook.",
"cases": [
{
"id": "double_attack_queen_check_and_rook",
"initialFen": "r3k3/8/8/8/8/8/8/3Q2K1 w - - 0 1",
"sideToMove": "white",
"bestMove": { "san": "Qa4+", "uci": "d1a4" },
"resultingFen": "r3k3/8/8/8/Q7/8/8/6K1 b - - 1 1",
"expectedPattern": {
"type": "DOUBLE_ATTACK",
"side": "white",
"attackerSquares": ["a4"],
"targetSquares": ["e8", "a8"],
"explanationKey": "queen_check_and_rook"
},
"context": "A single queen move gives check on e8 and simultaneously eyes the rook on a8.",
"tags": ["queen", "double_attack"]
}
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"patternType": "FORK",
"description": "Knight fork hitting king and queen simultaneously.",
"cases": [
{
"id": "fork_knight_checks_king_and_queen",
"initialFen": "4k3/5q2/8/5N2/8/8/8/6K1 w - - 0 1",
"sideToMove": "white",
"bestMove": { "san": "Nd6+", "uci": "f5d6" },
"resultingFen": "4k3/5q2/3N4/8/8/8/8/6K1 b - - 1 1",
"expectedPattern": {
"type": "FORK",
"side": "white",
"attackerSquares": ["d6"],
"targetSquares": ["e8", "f7"],
"explanationKey": "knight_fork"
},
"context": "The knight hop gives check on e8 while simultaneously attacking the queen on f7.",
"tags": ["tactics", "knight"]
}
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"patternType": "OVERLOADING",
"description": "The defending rook on f8 is overloaded, forced to choose between guarding the back rank and protecting f7.",
"cases": [
{
"id": "overloading_rook_on_f8",
"initialFen": "5rk1/5p2/8/3Q4/8/8/8/4R1K1 w - - 0 1",
"sideToMove": "white",
"bestMove": { "san": "Qxf7+", "uci": "d5f7" },
"resultingFen": "5rk1/5Q2/8/8/8/8/8/4R1K1 b - - 0 1",
"expectedPattern": {
"type": "OVERLOADING",
"side": "white",
"attackerSquares": ["f7"],
"targetSquares": ["f8", "f7"],
"explanationKey": "back_rank_vs_defense"
},
"context": "Capturing on f7 lures the only defender; after ...Rxf7 the back rank collapses to Re8#.",
"tags": ["overloading", "back_rank_setup"]
}
]
}
+23
View File
@@ -0,0 +1,23 @@
{
"patternType": "PIN",
"description": "Classical bishop pin on the Ruy Lopez diagonal, tying the c6-knight to the e8-king.",
"cases": [
{
"id": "pin_ruy_lopez_knight_to_king",
"initialFen": "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
"sideToMove": "white",
"bestMove": { "san": "Bb5", "uci": "f1b5" },
"resultingFen": "r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3",
"expectedPattern": {
"type": "PIN",
"side": "white",
"attackerSquares": ["b5"],
"targetSquares": ["c6"],
"keySquares": ["e8"],
"explanationKey": "pin_to_king"
},
"context": "1.e4 e5 2.Nf3 Nc6 3.Bb5 with the knight pinned to the king.",
"tags": ["opening", "ruy_lopez"]
}
]
}
+23
View File
@@ -0,0 +1,23 @@
{
"patternType": "SKEWER",
"description": "Bishop skewer forcing the king off the diagonal so the trailing queen can be captured.",
"cases": [
{
"id": "skewer_bishop_on_long_diagonal",
"initialFen": "7q/6k1/8/8/8/8/1B6/6K1 w - - 0 1",
"sideToMove": "white",
"bestMove": { "san": "Bc3+", "uci": "b2c3" },
"resultingFen": "7q/6k1/8/8/8/2B5/8/6K1 b - - 1 1",
"expectedPattern": {
"type": "SKEWER",
"side": "white",
"attackerSquares": ["c3"],
"targetSquares": ["g7", "h8"],
"keySquares": ["g7"],
"explanationKey": "king_in_front_of_queen"
},
"context": "Minimal material: the move Bc3+ attacks the king on g7; after the king moves the queen on h8 is lost.",
"tags": ["endgame", "long_diagonal"]
}
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"patternType": "TRAPPED_PIECE",
"description": "Illustrates a bishop trapped in the corner with no safe squares.",
"cases": [
{
"id": "trapped_corner_bishop",
"initialFen": "6kb/6P1/5K2/8/8/8/8/8 w - - 0 1",
"sideToMove": "white",
"bestMove": { "san": "gxh8=Q+", "uci": "g7h8q" },
"resultingFen": "6kQ/8/5K2/8/8/8/8/8 b - - 0 1",
"expectedPattern": {
"type": "TRAPPED_PIECE",
"side": "white",
"attackerSquares": ["g7"],
"targetSquares": ["h8"],
"explanationKey": "corner_trap"
},
"context": "The black bishop on h8 cannot escape because of the pawn on g7 and the king on f6.",
"tags": ["endgame", "conversion"]
}
]
}