chess_book_builder.py v3.9 | Editor chess_book_editor.py v4.9Two scripts build an opening book for the Micro-Max chess engine running on an STM32 microcontroller. The book is stored in Flash as an array of polyglot_entry_t structures (Zobrist key + Polyglot move), sorted by key for binary search.
Automatically builds a tree of opening variations using Stockfish, filters out weak moves, and collects up to 1500 positions in the book.
| Parameter | Value | Description |
|---|---|---|
BOOK_SIZE | 1500 | Maximum number of positions in the book |
DEPTH_BLACK | 14 | Stockfish search depth for Black's best move (via engine.play()) |
DEPTH_WHITE | 12 | Stockfish search depth for White's candidate moves (via multipv) |
MULTIPV | 5 | Number of candidate lines requested from Stockfish for White |
THRESHOLD_CP | 40 | Filter: moves worse than the best by 40+ cp are discarded |
| Openings | e4, d4 | Root moves for White |
The tree is expanded by White's moves only. For each position where it is White's turn, filtered_moves() asks Stockfish for the top MULTIPV lines and keeps those within THRESHOLD_CP of the best. For each resulting position (after White's move), Stockfish returns one best move for Black via engine.play(). That move is what gets written to the book.
| Stage | Ply | What happens |
|---|---|---|
| 1 | 1 | Black's best response to e4 and d4 |
| 2 | 3 | Filtered White moves → Black's best response |
| 3 | 5 | Same — tree expansion |
| 4 | 7 | Deeper lines (top-3 branching) |
| 5 | 9 | Deeper lines (top-3 branching) |
| 6 | 11 | Deepest lines, fills remaining slots up to 1500 |
The filtered_moves() function asks Stockfish for the top MULTIPV candidates and keeps only those within 40 cp of the best evaluation. Weak moves (e.g., Ke2 on move 2) are discarded, freeing space for deeper lines.
After each stage, the script prints elapsed time and an ETA based on seconds-per-position:
Stage 1 (ply 1): 2 pos. | 14.0s | total 14.0s | ETA: ~65min remaining
Stage 2 (ply 3): 20 pos. | 56s | total 70s | ETA: ~55min remaining
| File | Purpose |
|---|---|
debut_book.c | C array for Flash programming (sorted by key) |
book_session.json | Session file for the manual editor |
#include "polyglot.h"
const polyglot_entry_t debut_book[] = {
\t{0x010C55ABU, 0x1234U, 1},
\t...
};
const int debut_book_size = 1500;
{
"version": "4.9",
"book": [[key, "e2e4", "e4", pg], ...],
"lines": [["1. e4 c5 2. Nf3 [0x010C55AB]", ["e2e4", "c7c5", "g1f3"], key], ...]
}
zobrist_table.h — Zobrist number table (781 values), searched in the script directorystockfish-windows-*.exe — engine, searched in the script directorypython-chess — chess library for Pythonpython chess_book_builder.py
After completion, stage statistics are printed:
Statistics by stage:
ply 1 (stage 1): 2 positions
ply 3 (stage 2): 20 positions
ply 5 (stage 3): 100 positions
ply 7 (stage 4): 300 positions
ply 9 (stage 5): 500 positions
ply 11 (stage 6): 578 positions
ply 11 = 0 — the limit was reached earlier. Solution: lower THRESHOLD_CP (to 30) or reduce MULTIPV (from 5 to 3).
A tkinter GUI for manual editing of the opening book. Board with gc2004d font, dark theme, localization (Russian / English).
| Function | Description |
|---|---|
| Mouse moves | Click a piece → click a square. Legal moves are highlighted. |
| Auto-add | Automatically adds Black's move to the book when a move is made on the board |
| + Add to book | Manually add Black's move to the book |
| Undo move | Take back the last move in the current line |
| New line | Save the current line, start a new one |
| Reset board | Reset the board to the starting position |
| Import PGN | Load a PGN file, step-by-step navigation |
| >>> (PGN) | Import all Black moves from PGN into the book |
| Save debut_book.c | Save the book to a C file (sorted by key) |
| Save session (JSON) | Save the book + lines to JSON |
| Load session (JSON) | Load a previously saved session |
Two independent searches — in Book Entries and in Lines:
| Element | Book Entries | Lines |
|---|---|---|
| What it searches | Zobrist key in an entry | Hash in line name or computed from moves |
| Input format | 0x010C55AB or 010c55ab | Same |
| Navigation | Search, < Back, Fwd > | Search, < Back, Fwd > |
| Status | 1/5 — first of five | Same |
e4 c5 Nf3 [0x010C55AB]) and paste it into the book entry search — the corresponding book entry will be found.
| Button | Action |
|---|---|
<< | Go to start |
< | Move backward |
> | Move forward (with auto-add if enabled) |
>> | Go to end (with auto-add for all moves) |
>>> | Import all Black moves into the book |
Each entry: key (Zobrist, uint32_t) + move (UCI, 4 characters) + SAN + Polyglot (uint16_t).
The key is computed by compute_zobrist(board) before Black's move — i.e., for the position in which the Micro-Max engine will look up a response.
zobrist_table.h — same table as the generatorchess_book_editor_lang.py — localization file (50 keys, ru/en)python-chess, tkinterpython chess_book_editor.py
chess_book_builder.py, get book_session.json and debut_book.cbook_session.json in the editor, search by keys, make sure hashes in lines and entries matchdebut_book.c from the editordebut_book.c with the Micro-Max code, flash the STM32Both scripts use the same zobrist_table.h table. Indexing:
(piece_type - 1) * 2 + (0 for White, 1 for Black) × 64 + squareZOBRIST_NUMBERS[-1]The key is masked to 32 bits: key & 0xFFFFFFFF — matching uint32_t on the STM32.
| Problem | Cause | Solution |
|---|---|---|
TypeError: Move has no len() |
build_board receives a chess.Move instead of a string |
Use v3.7+ — build_board accepts both types |
score() got unexpected keyword 'margin_score' |
Old version of python-chess | Use v3.7+ — .score() call without parameters |
| Key search does not work | Keys from JSON are strings, not int |
Use editor v4.7+ — _safe_key() converts automatically |
| Entry table is empty after loading JSON | pg from JSON is a string, f"{pg:04X}" fails |
Use editor v4.7+ — _safe_int(pg) |
| Hash in line does not match hash in book | Key was computed for different positions | Use generator v3.6+ — the key is the same in both |
| Generator does not reach stage 6 | Limit of 1500 reached earlier | Lower THRESHOLD_CP to 30 or reduce MULTIPV to 3 |
| Generation takes too long | Depth too high or multipv too wide | Use v3.9: depth 14/12, multipv 5. Reduce DEPTH_BLACK to 12 for a quick test run |