Metadata-Version: 2.4
Name: chess-detect
Version: 0.2.1
Summary: Automatic detection of chess tactical themes in PGN games
Project-URL: Homepage, https://github.com/aslyamov/chess_detect
Project-URL: Repository, https://github.com/aslyamov/chess_detect
Project-URL: Issues, https://github.com/aslyamov/chess_detect/issues
Author: aslyamov
License-Expression: MIT
License-File: LICENSE
Keywords: analysis,chess,pgn,python-chess,tactics
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Games/Entertainment :: Board Games
Requires-Python: >=3.10
Requires-Dist: python-chess>=1.0
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Description-Content-Type: text/markdown

# chess_detect

Automatic detection of chess tactical and strategic themes in PGN games using [python-chess](https://python-chess.readthedocs.io/).

Analyzes PGN files and annotates moves with tactical motifs (forks, pins, skewers, discovered checks) and strategic patterns (open files, doubled/isolated pawns). Supports Russian and English output with visual PGN arrow annotations.

[Документация на русском](README_RU.md)

## Installation

```bash
pip install chess-detect
```

For development:

```bash
git clone https://github.com/aslyamov/chess_detect.git
cd chess_detect
pip install -e ".[dev]"
```

## Quick Start

```python
from chess_detect import ChessDetector

detector = ChessDetector(lang="en")  # or lang="ru"

pgn = """[FEN "q3k3/8/8/1N6/8/8/8/4K3 w - - 0 1"]

1. Nc7+"""

result = detector.analyze(pgn)
print(result)
# 1. Nc7+ {Fork (Nc7 -> Qa8, Ke8)} *
```

## Configuration

```python
ChessDetector(
    lang="en",        # "ru" or "en" (default: "ru")
    tactics=True,     # tactical themes (forks, pins, skewers...)
    strategy=True,    # strategic motifs (open files, doubled pawns...)
    arrows=True,      # arrows and square highlights in PGN
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `lang` | `str` | `"ru"` | Output language |
| `tactics` | `bool` | `True` | Enable tactical themes |
| `strategy` | `bool` | `True` | Enable strategic motifs |
| `arrows` | `bool` | `True` | Enable PGN arrows (`[%cal]`) and highlights (`[%csl]`) |

Examples:

```python
# Tactics only, no arrows
detector = ChessDetector(lang="en", strategy=False, arrows=False)

# Strategy only
detector = ChessDetector(lang="ru", tactics=False)
```

## Tactical Themes

### 1. Double Check

Two pieces check the king simultaneously. The king must move.

```
FEN: 3k4/8/8/8/3N4/8/8/3RK3 w - - 0 1
Move: Nc6+ — knight gives check and discovers check from rook d1.
```

### 2. Fork

One piece attacks two or more valuable enemy pieces.

```
FEN: q3k3/8/8/1N6/8/8/8/4K3 w - - 0 1
Move: Nc7+ — knight attacks both king e8 and queen a8.
```

### 3. Discovered Check

A piece moves away, uncovering check from another piece, while making a useful attack.

```
FEN: 3k4/8/4p3/8/3N4/8/8/3RK3 w - - 0 1
Move: Nxe6 — knight captures pawn and discovers check from rook d1.
```

### 4. Pin

A ray piece attacks through an enemy piece to a more valuable one behind it. The front piece cannot move.

```
FEN: 6k1/8/4n3/8/8/1B6/8/4K3 w - - 0 1
Move: Bc4 — bishop pins knight e6 to king g8 along the diagonal.
```

### 5. Skewer

Reverse pin: the more valuable piece is in front and must move, exposing the piece behind.

```
FEN: 4q3/8/8/4k3/8/8/8/R5K1 w - - 0 1
Move: Re1+ — rook checks king e5, queen e8 is behind.
```

### 6. Trapped Piece

After a move, an enemy piece has no safe squares.

```
FEN: 4k2n/4P3/5R2/5BP1/8/8/8/4K2Q w - - 0 1
Move: g6 — knight h8 is trapped, all escape squares are under attack.
```

### 7. Hanging Capture

Capturing a piece that is undefended or can be taken by a cheaper piece.

```
FEN: 4k3/8/8/R3n3/8/8/8/4K3 w - - 0 1
Move: Rxe5 — rook captures undefended knight.
```

### 8. Removing Defender (material)

Capturing a piece that defended another. The defended piece is now hanging.

```
FEN: 4k3/8/8/8/4b3/Q1n5/8/1B2K3 w - - 0 1
Move: Qxc3 — queen takes knight that defended bishop e4.
```

### 9. Removing Defender (mate)

Capturing a piece that defended a key square. A mate threat appears.

```
FEN: 7k/6p1/6r1/8/4B3/2Q5/8/4K3 w - - 0 1
Move: Bxg6 — bishop takes rook, creating mate threat Qc8#.
```

### 10. Exploiting Pin

Attacking a pinned piece (it cannot escape) or capturing through a pinned defender.

```
FEN: 4k3/8/8/5p2/4n3/8/3P4/4R1K1 w - - 0 1
Move: d3 — pawn attacks knight e4 pinned to king by rook e1.
```

## Strategic Motifs

Strategic themes are annotated with blue arrows/highlights (tactical themes use green).
Each motif is detected once when it first appears, then ignored.

### 11. Open File

A file with no pawns of either color.

```
FEN: 4k3/8/8/3p4/8/8/8/3RK3 w - - 0 1
Move: Rxd5 — last pawn removed from d-file, the file is now open.
```

### 12. Doubled Pawns

Two or more pawns of the same color on the same file.

```
FEN: 4k3/8/8/8/8/3n4/2PP4/4K3 w - - 0 1
Move: cxd3 — pawn captures, creating doubled pawns on d2 and d3.
```

### 13. Isolated Pawn

A pawn with no friendly pawns on adjacent files.

```
FEN: 4k3/8/8/8/r1PP4/8/8/4K3 b - - 0 1
Move: Rxc4 — rook captures c4 pawn, leaving d4 pawn isolated.
```

## Status

| Theme | Russian | English | Status |
|-------|---------|---------|--------|
| Double Check | Двойной шах | Double Check | :white_check_mark: |
| Fork | Вилка | Fork | :white_check_mark: |
| Discovered Check | Вскрытый шах | Discovered Check | :white_check_mark: |
| Pin | Связка | Pin | :white_check_mark: |
| Skewer | Копьё | Skewer | :white_check_mark: |
| Trapped Piece | Пойманная фигура | Trapped Piece | :white_check_mark: |
| Hanging Capture | Взятие висящей фигуры | Hanging Capture | :white_check_mark: |
| Removing Defender (material) | Уничтожение защитника (выигрыш фигуры) | Removing Defender (winning material) | :white_check_mark: |
| Removing Defender (mate) | Уничтожение защитника (угроза мата) | Removing Defender (mate threat) | :white_check_mark: |
| Exploiting Pin | Использование связки | Exploiting Pin | :white_check_mark: |
| Open File | Открытая линия | Open File | :white_check_mark: |
| Doubled Pawns | Сдвоенные пешки | Doubled Pawns | :white_check_mark: |
| Isolated Pawn | Изолированная пешка | Isolated Pawn | :white_check_mark: |

### Planned

- Deflection
- Decoy
- Zwischenzug
- Discovered Attack
- Back Rank Mate
- Smothered Mate

## PGN Annotations

Detected themes are visualized with arrows (`[%cal]`) and square highlights (`[%csl]`) in the PGN output. These annotations are displayed by Lichess, chess.com, and other viewers.

- **Green** arrows/highlights — tactical themes
- **Blue** arrows/highlights — strategic motifs

```
1. Nc7+ { [%csl Ga8,Ge8][%cal Gc7a8,Gc7e8] Fork (Nc7 -> Qa8, Ke8) }
1. Rxd5 { [%cal Bd1d8] Open File (d) }
```

## Architecture

```
chess_detect/
├── __init__.py          # Exports ChessDetector
├── detector.py          # Main class
├── context.py           # MoveContext — lazy evaluation
├── utils.py             # Utilities (piece values, ray casting)
├── i18n/                # Translations
│   ├── ru.py
│   └── en.py
└── detectors/
    ├── base.py          # BaseDetector (arrow_color, reset)
    ├── tactics/         # 10 tactical detectors
    │   ├── double_check.py
    │   ├── fork.py
    │   ├── discovered_check.py
    │   ├── pin.py
    │   ├── skewer.py
    │   ├── trapped.py
    │   ├── hanging_capture.py
    │   ├── removing_defender.py
    │   └── exploiting_pin.py
    └── strategic/       # 3 strategic detectors
        ├── open_file.py
        ├── doubled_pawns.py
        └── isolated_pawn.py
```

## Adding a New Detector

```python
from ..base import BaseDetector
from ...context import MoveContext

class MyDetector(BaseDetector):
    key = "my_theme"       # i18n key
    arrow_color = "green"  # "green" for tactics, "blue" for strategy

    def detect(self, ctx: MoveContext) -> bool:
        # ctx.board_before, ctx.board_after, ctx.move
        # ctx.is_check, ctx.is_capture, ctx.captured_piece
        return False

    def format_details(self, ctx: MoveContext, lang: str) -> str:
        return ""  # e.g. "Nc7 -> Qa8, Ke8"

    def get_arrows(self, ctx: MoveContext) -> list[tuple[int, int]]:
        return []  # (from_sq, to_sq) or (sq, sq) for highlights
```

1. Create a file in `detectors/tactics/` or `detectors/strategic/`
2. Add translations to `i18n/ru.py` and `i18n/en.py`
3. Register in `detector.py`

## Tests

```bash
python -m pytest tests/ -v
```

**110 tests** — all passing.

## License

[MIT](LICENSE)
