Gameplay (legal moves, move application, pressure maps, PGN parsing) now runs entirely in the browser via a chess.js port of engine.py (static/engine.js + vendored chess.min.js). The app no longer calls the server during play, so it stays instant and works even when the backend is scaled to zero or offline. - engine.js: line-for-line port of engine.py; parity-verified identical output (pressure, FEN, status, SAN/UCI) across all 10 built-in games / 829 frames. - app.js: replace all /api fetches with local engine calls; graceful PGN parse errors; asset paths moved to root for static serving. - Save games to localStorage + load from the game dropdown; export any game as a .pgn file. Minimal additive UI in the existing export row. - games.json generated from games.py (scripts/export_games.py). - Production Dockerfile -> static-web-server (~9MB image, was ~150MB python). - make dev -> bun static server; add make games / make parity targets. - app.py kept as optional fallback (serves static app + /api routes). - parity/: automated JS-vs-python engine parity test. git diff stat: 16 files changed, 722 insertions(+), 128 deletions(-)
105 lines
4.3 KiB
Markdown
105 lines
4.3 KiB
Markdown
# Chess Pressure
|
|
|
|
A web application that visualizes **pressure maps** on a chess board -- showing which squares are controlled by white vs. black across every move of a game.
|
|
|
|
## What is "pressure"?
|
|
|
|
For each square on the board, pressure is the net number of pieces attacking it. Positive values mean white controls the square; negative means black does. Two modes are available:
|
|
|
|
- **Unweighted** -- each attacking piece contributes +1 or -1.
|
|
- **Weighted** -- each attacking piece contributes its material value (pawn=1, knight/bishop=3, rook=5, queen=9, king=1).
|
|
|
|
## Features
|
|
|
|
- Step through famous built-in games move by move and watch the pressure heatmap evolve.
|
|
- Upload your own PGN to analyze any game.
|
|
- Make moves from any position and see legal moves + pressure updates in real time.
|
|
- Save games to your browser and re-load them later; export any game as a PGN file.
|
|
- **Fully client-side gameplay** — legal moves, move application, and pressure are all
|
|
computed in the browser (via [chess.js](https://github.com/jhlywa/chess.js)), so the
|
|
app keeps working instantly even when the server is asleep or offline. Production is
|
|
served as plain static files (no backend in the request path).
|
|
|
|
## Architecture
|
|
|
|
The chess logic exists in two equivalent implementations:
|
|
|
|
- `engine.py` (python-chess) — the original engine; also the parity reference.
|
|
- `static/engine.js` (chess.js) — a line-for-line port that runs in the browser.
|
|
|
|
`make parity` verifies the two produce **identical** output (pressure maps, FENs,
|
|
board status, and SAN/UCI) across every built-in game, so the client engine can be
|
|
trusted as a drop-in. Production deploys only the static assets; the FastAPI server
|
|
(`app.py`, with the `/api` routes below) is kept as an optional local fallback.
|
|
|
|
## Built-in Games
|
|
|
|
| Game | Year | White | Black |
|
|
|------|------|-------|-------|
|
|
| The Immortal Game | 1851 | Anderssen | Kieseritzky |
|
|
| The Opera Game | 1858 | Morphy | Duke of Brunswick & Count Isouard |
|
|
| Fischer vs Spassky, Game 6 | 1972 | Fischer | Spassky |
|
|
| Xie Jun vs Chiburdanidze, WCC | 1991 | Xie Jun | Chiburdanidze |
|
|
| Kasparov vs Deep Blue, Game 2 | 1997 | Deep Blue | Kasparov |
|
|
| Polgar vs Anand | 1999 | Polgar | Anand |
|
|
| Polgar vs Kasparov | 2002 | Polgar | Kasparov |
|
|
| Carlsen vs Nakamura | 2010 | Carlsen | Nakamura |
|
|
| Hou Yifan vs Caruana | 2017 | Hou Yifan | Caruana |
|
|
| Ju Wenjun vs Lei Tingjie, WCC G12 | 2023 | Ju Wenjun | Lei Tingjie |
|
|
|
|
## Quickstart
|
|
|
|
```bash
|
|
make dev # static dev server on http://localhost:8888 (bun)
|
|
```
|
|
|
|
Other useful targets:
|
|
|
|
```bash
|
|
make serve # optional Python fallback server (also serves the static app)
|
|
make games # regenerate static/games.json after editing games.py
|
|
make parity # verify the JS engine matches python-chess
|
|
make deploy # deploy the static image to Fly.io
|
|
```
|
|
|
|
## Deployment
|
|
|
|
Production is a ~9 MB image built on
|
|
[static-web-server](https://static-web-server.net/) (see `Dockerfile`) — it serves
|
|
`static/` and nothing else, so it boots fast and scales to zero cleanly. The only
|
|
cold-start cost is the initial page load; all gameplay afterwards is local.
|
|
|
|
## API (optional Python server)
|
|
|
|
The static app does not use these, but `app.py` still exposes them:
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/api/games` | List built-in games |
|
|
| GET | `/api/games/{id}` | Load a built-in game (headers, moves, pressure frames) |
|
|
| POST | `/api/parse` | Parse a PGN string (`{"pgn": "..."}`) |
|
|
| GET | `/api/legal?fen=...` | Get legal moves for a FEN position |
|
|
| POST | `/api/move` | Make a move (`{"fen": "...", "uci": "e2e4"}`) |
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
src/chess_pressure/
|
|
__init__.py
|
|
app.py # Optional FastAPI server (/api routes + static fallback)
|
|
engine.py # Pressure computation, PGN parsing, move logic (parity reference)
|
|
games.py # Built-in famous games (PGN data)
|
|
static/
|
|
engine.js # Browser port of engine.py (chess.js)
|
|
chess.min.js # Vendored chess.js 1.4.0 (bundled global)
|
|
games.json # Built-in games, generated from games.py
|
|
app.js, ... # Frontend (HTML, JS, CSS, piece images)
|
|
scripts/export_games.py # games.py -> static/games.json
|
|
parity/ # JS-vs-python engine parity test
|
|
dev-server.js # bun static dev server
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|