Custom chess firmware for the Xteink X4 e-ink ebook reader (ESP32-C3). Started as a chess engine experiment — can I still write a program from scratch using only documentation? — then became a full playable game on the device's e-paper display.
Captured straight from the device framebuffer — see Screenshots.
Chess engine
- Minimax + alpha-beta pruning
- Iterative deepening with time limit
- Transposition table (Zobrist hashing)
- Move ordering (MVV-LVA)
- Piece-square table evaluation
Game rules
- Full move generation: pawns, en passant, castling, promotion
- Checkmate and stalemate detection
- 50-move rule draw
- Insufficient material draw (K vs K, K+minor vs K, same-color bishops)
UI
- Difficulty: Easy (depth 3 / 400 ms), Medium (depth 5 / 800 ms), Hard (depth 7 / 1500 ms)
- Play as White, Black, or Random
- 48×48 bitmap pieces rasterized from the Cburnett SVG set — outline for White, filled silhouette with white detailing for Black
- Gray dark squares (50% checkerboard dither) with white halo masks so pieces stay crisp on any square
- Legal move dots on valid destination squares
- Captured pieces listed above and below the board with material advantage (
PPNB +2) - Move history in long algebraic notation (e.g.
Nb1xc3+), last moves visible during play - Game over screen with full move log
- Auto-sleep after 10 min idle; wake on power button
| Component | Detail |
|---|---|
| Device | Xteink X4 |
| MCU | ESP32-C3 |
| Display | 1-bit e-ink, portrait orientation |
| Flash | 16 MB |
| Input | D-pad + confirm/back/power buttons |
| Button | Action |
|---|---|
| LEFT / RIGHT | Switch move direction (ray cycling) |
| UP / DOWN | Walk distance along selected ray |
| CONFIRM | Select piece / confirm move / confirm promotion |
| BACK | Return to menu |
| POWER | Sleep |
Requires PlatformIO and the open-x4-sdk submodule.
# First time — pull the SDK
git submodule update --init
# Build + flash
pio run -t upload
# Serial monitor (115200 baud)
pio device monitor
# Build only (no device needed)
pio runWith the device connected over USB, send S on the serial port and the firmware dumps its framebuffer as hex. tools/screenshot.py automates it:
pip install pyserial pillow
python3 tools/screenshot.py /dev/ttyACM0 screenshot.png(Close pio device monitor first — only one program can hold the port.)
src/test.c compiles with plain gcc and runs three suites: perft (move generation correctness), draw detection, and search benchmarks.
make test
./testThe chess engine (chess.c / chess.h) is pure C with no Arduino dependencies, so it builds and tests natively. Everything else is C++ / Arduino.
| File | Responsibility |
|---|---|
chess.c |
Board state, move generation, apply/unmake, minimax search, Zobrist hashing, transposition table |
render.cpp |
Wraps EInkDisplay + Adafruit_GFX; InkGFX subclass applies display rotation in drawPixel |
piece_bitmaps.h |
48×48 1-bit piece bitmaps (filled / outline / interior masks) generated from Cburnett SVGs |
game.cpp |
Game loop: human vs. engine turns, move input, promotion picker, captured pieces, move history |
menu.cpp |
Difficulty and side selection screen |
ui.cpp |
Shared helpers: idle timer, footer hints, promotion picker, captured row renderer |
main.cpp |
Top-level APP_MENU / APP_GAME state machine; InputManager polling; GPIO wakeup |
SDK: open-x4-sdk/ is a git submodule providing EInkDisplay and InputManager, linked into the build via platformio.ini.
Board coordinates: index 0 = a1 (White's back-left corner). rank = index / 8, file = index % 8. White starts at indices 0–15, Black at 48–63.
See LICENSE.
