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(-)
4.3 KiB
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), 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
make dev # static dev server on http://localhost:8888 (bun)
Other useful targets:
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 (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