RU

Opening Book Tools for STM32

Generator chess_book_builder.py v3.9  |  Editor chess_book_editor.py v4.9

Contents

  1. Purpose
  2. Generator (chess_book_builder.py)
  3. Manual Editor (chess_book_editor.py)
  4. Workflow
  5. Zobrist Compatibility
  6. Troubleshooting

Purpose

Two 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.

The engine plays as Black. A book entry is a position after White's move, when it is Black's turn. The Zobrist key in the book and in the line is the same, so the editor can find a book entry by the hash from the line.

Generator — chess_book_builder.py v3.9

Automatically builds a tree of opening variations using Stockfish, filters out weak moves, and collects up to 1500 positions in the book.

Parameters (top of file)

ParameterValueDescription
BOOK_SIZE1500Maximum number of positions in the book
DEPTH_BLACK14Stockfish search depth for Black's best move (via engine.play())
DEPTH_WHITE12Stockfish search depth for White's candidate moves (via multipv)
MULTIPV5Number of candidate lines requested from Stockfish for White
THRESHOLD_CP40Filter: moves worse than the best by 40+ cp are discarded
Openingse4, d4Root moves for White

Logic

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.

Only Black's moves are stored in the book. White's moves serve solely to expand the tree and create positions for Black's responses.

Generation Stages

StagePlyWhat happens
11Black's best response to e4 and d4
23Filtered White moves → Black's best response
35Same — tree expansion
47Deeper lines (top-3 branching)
59Deeper lines (top-3 branching)
611Deepest lines, fills remaining slots up to 1500

Move Filtering

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.

The 40 cp threshold is a compromise:
• Lower (20–30) — stricter, deeper tree, but may cut reasonable alternatives
• Higher (80–120) — wider, more noise, may not reach stage 6

Timing

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
Total time depends on hardware. With depth 14/12 and multipv 5, expect about 60–80 minutes on a modern CPU.

Output Files

FilePurpose
debut_book.cC array for Flash programming (sorted by key)
book_session.jsonSession file for the manual editor

Format of debut_book.c

#include "polyglot.h"

const polyglot_entry_t debut_book[] = {
\t{0x010C55ABU, 0x1234U, 1},
\t...
};

const int debut_book_size = 1500;

Format of book_session.json

{
  "version": "4.9",
  "book": [[key, "e2e4", "e4", pg], ...],
  "lines": [["1. e4 c5 2. Nf3 [0x010C55AB]", ["e2e4", "c7c5", "g1f3"], key], ...]
}

Dependencies

Running

python chess_book_builder.py

Diagnostics

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
If ply 11 = 0 — the limit was reached earlier. Solution: lower THRESHOLD_CP (to 30) or reduce MULTIPV (from 5 to 3).

Manual Editor — chess_book_editor.py v4.9

A tkinter GUI for manual editing of the opening book. Board with gc2004d font, dark theme, localization (Russian / English).

Features

FunctionDescription
Mouse movesClick a piece → click a square. Legal moves are highlighted.
Auto-addAutomatically adds Black's move to the book when a move is made on the board
+ Add to bookManually add Black's move to the book
Undo moveTake back the last move in the current line
New lineSave the current line, start a new one
Reset boardReset the board to the starting position
Import PGNLoad a PGN file, step-by-step navigation
>>> (PGN)Import all Black moves from PGN into the book
Save debut_book.cSave 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

Key Search

Two independent searches — in Book Entries and in Lines:

ElementBook EntriesLines
What it searchesZobrist key in an entryHash in line name or computed from moves
Input format0x010C55AB or 010c55abSame
NavigationSearch, < Back, Fwd >Search, < Back, Fwd >
Status1/5 — first of fiveSame
You can copy a key from a line name (e.g. e4 c5 Nf3 [0x010C55AB]) and paste it into the book entry search — the corresponding book entry will be found.

PGN Navigation

ButtonAction
<<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

Book Entry

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.

Dependencies

Running

python chess_book_editor.py

Typical Workflow

  1. Generate — run chess_book_builder.py, get book_session.json and debut_book.c
  2. Verify — open book_session.json in the editor, search by keys, make sure hashes in lines and entries match
  3. Edit — add missing lines by mouse or from PGN, remove extras
  4. Export — save debut_book.c from the editor
  5. Flash — compile debut_book.c with the Micro-Max code, flash the STM32

Zobrist Compatibility

Both scripts use the same zobrist_table.h table. Indexing:

The key is masked to 32 bits: key & 0xFFFFFFFF — matching uint32_t on the STM32.

Troubleshooting

ProblemCauseSolution
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