Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ jobs:
with:
python-version: '3.12'

- name: Generate Stockfish opening book
- name: Generate PickleBook
run: |
python -m pip install --disable-pip-version-check python-chess==1.999
python scripts/generate_opening_book.py --output opening_book_data.inc
! grep -q 'STOCKFISH_BOOK_ENTRY_COUNT = 0' opening_book_data.inc
! grep -q 'PICKLE_BOOK_ENTRY_COUNT = 0' opening_book_data.inc
grep -q 'Lichess-Elite-derived' opening_book_data.inc

- name: Build native Pickle
run: |
Expand All @@ -75,6 +76,7 @@ jobs:
printf 'position startpos moves e2e4\ngo depth 3\nquit\n' | ./build/pickle | tee /tmp/pickle-book-e4.log
grep -q 'info string opening book' /tmp/pickle-book-e4.log
grep -Eq '^bestmove [a-h][1-8][a-h][1-8][qrbn]?$' /tmp/pickle-book-e4.log
! grep -q '^bestmove d7d5$' /tmp/pickle-book-e4.log

- name: Smoke-test UCI search without book
run: |
Expand Down Expand Up @@ -121,16 +123,16 @@ jobs:
npm install
npm run build

- name: Commit generated browser engine and opening book
- name: Commit generated browser engine and PickleBook
if: github.event_name == 'push'
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add opening_book_data.inc web/public/engine/pickle.js web/public/engine/pickle_web.wasm
if git diff --cached --quiet; then
echo 'Generated opening book and WebAssembly are already current.'
echo 'Generated PickleBook and WebAssembly are already current.'
else
git commit -m 'Build Pickle book and WebAssembly [skip ci]'
git commit -m 'Build PickleBook and WebAssembly [skip ci]'
git pull --rebase
git push
fi
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ Pickle currently includes:
- UCI support and configurable hash size
- clock-aware search limits

### Opening book
### PickleBook

Pickle's opening book is generated from `8moves_v3.pgn` in the official [`official-stockfish/books`](https://github.com/official-stockfish/books) repository. That upstream data is released under CC0 1.0.
Pickle's opening repertoire is **PickleBook**, a compact book derived from strong non-bullet games in the Lichess Elite Database rather than from a generic engine-testing book. The current seed tree comes from a pinned Lichess-Elite-derived repertoire in PyCheckmate, whose book builder samples up to 80,000 elite games through the first 16 plies. Pickle pins both the upstream repository revision and the exact source blob used for regeneration.

The generator pins an exact upstream commit and the raw archive digest, parses the PGN into Pickle's own Zobrist-keyed format, merges transpositions, retains weighted candidate moves, and emits `opening_book_data.inc`. The browser therefore does not need to download or parse a PGN at game time.
The upstream human frequencies are only a prior. `scripts/generate_opening_book.py` replays the tree into Pickle's own Zobrist format and reweights each candidate for the limitations of Pickle's shallow browser search. The filter rewards direct development and castling, penalizes repeated early queen moves and exposed kings, penalizes new pawn defects, and downweights positions with unusually high immediate branching or forcing-move density. Low-fit alternatives are removed instead of being kept merely because they are theoretically playable.

The opening book is data only. Pickle does not use Stockfish search code, evaluation code, NNUE weights, or engine binaries.
This is deliberately different from asking a depth-11 engine to understand why a long-term structural concession may pay off dozens of moves later. The book is intended to hand Pickle positions where the important features are visible inside its horizon. The observed `Qxd5` Scandinavian tempo-loss line is part of the generator's regression audit: the development-first `...Nf6` continuation must outrank the early queen capture before a book can be produced.

The generated result is compiled into `opening_book_data.inc`, so native and browser Pickle have no opening-time network dependency. The source repertoire is data only; Pickle does not run PyCheckmate or another chess engine for book moves.

### How Pickle evaluates a position

Expand Down Expand Up @@ -73,7 +75,7 @@ The board uses the MIT-licensed [`react-chessboard`](https://github.com/Clariity

## Build the native engine

Generate the opening book first:
Generate PickleBook first:

```bash
python -m pip install python-chess==1.999
Expand Down Expand Up @@ -107,7 +109,7 @@ setoption name OwnBook value false

## Build the WebAssembly engine

Install the Emscripten SDK, generate the opening book, then run:
Install the Emscripten SDK, generate PickleBook, then run:

```bash
bash scripts/build_wasm.sh
Expand Down Expand Up @@ -145,8 +147,8 @@ Pickle/
├── attacks.* leaper attacks
├── magics.* sliding-piece attack tables
├── evaluate.* static evaluation
├── opening_book.* compiled opening-book lookup
├── opening_book_data.inc generated book data
├── opening_book.* compiled PickleBook lookup
├── opening_book_data.inc generated PickleBook data
├── search.* search and move ordering
├── tt.* transposition table
├── time_manager.* search time allocation
Expand All @@ -163,4 +165,4 @@ Focused contributions are welcome, especially around perft/regression testing, s

## A note on engine code

Pickle is not Stockfish with a different name. It uses established chess-engine techniques and now derives opening-book **data** from the CC0 official Stockfish books repository, but its search, evaluation, move generation, hashing, time management, UCI implementation, and browser engine are Pickle's own codebase.
Pickle is its own chess engine. It uses established chess-engine techniques and a derived strong-human opening dataset, but its search, evaluation, move generation, hashing, time management, UCI implementation, opening-book policy, and browser engine are Pickle's own codebase.
22 changes: 11 additions & 11 deletions opening_book.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,27 @@ std::uint16_t compact_move(Move move) {

const GeneratedBookEntry* find_entry(U64 key) {
std::size_t lo = 0;
std::size_t hi = STOCKFISH_BOOK_ENTRY_COUNT;
std::size_t hi = PICKLE_BOOK_ENTRY_COUNT;
while (lo < hi) {
const std::size_t mid = lo + (hi - lo) / 2;
if (STOCKFISH_BOOK_ENTRIES[mid].key < key) lo = mid + 1;
if (PICKLE_BOOK_ENTRIES[mid].key < key) lo = mid + 1;
else hi = mid;
}
if (lo < STOCKFISH_BOOK_ENTRY_COUNT && STOCKFISH_BOOK_ENTRIES[lo].key == key) {
return &STOCKFISH_BOOK_ENTRIES[lo];
if (lo < PICKLE_BOOK_ENTRY_COUNT && PICKLE_BOOK_ENTRIES[lo].key == key) {
return &PICKLE_BOOK_ENTRIES[lo];
}
return nullptr;
}

} // namespace

void init_opening_book() {
// The book is generated at build time into opening_book_data.inc. There is
// no runtime parsing or network dependency for native or WASM Pickle.
// PickleBook is generated at build time into opening_book_data.inc. There
// is no runtime parsing or network dependency for native or WASM Pickle.
}

Move probe_opening_book(Board& board) {
if (!book_enabled || STOCKFISH_BOOK_ENTRY_COUNT == 0) return 0;
if (!book_enabled || PICKLE_BOOK_ENTRY_COUNT == 0) return 0;

const GeneratedBookEntry* entry = find_entry(board.get_hash_key());
if (!entry || entry->move_count == 0) return 0;
Expand All @@ -65,7 +65,7 @@ Move probe_opening_book(Board& board) {
int total_weight = 0;

for (std::uint32_t i = 0; i < entry->move_count; ++i) {
const GeneratedBookMove& generated = STOCKFISH_BOOK_MOVES[entry->move_offset + i];
const GeneratedBookMove& generated = PICKLE_BOOK_MOVES[entry->move_offset + i];
for (int j = 0; j < legal_moves.count; ++j) {
Move move = legal_moves.moves[j];
if (compact_move(move) != generated.packed) continue;
Expand All @@ -80,8 +80,8 @@ Move probe_opening_book(Board& board) {

if (choices.empty() || total_weight <= 0) return 0;

// Weighted variation prevents Pickle from playing exactly the same opening
// every game while still preferring moves that occur more often upstream.
// Variation remains weighted, but the generated weights already combine
// strong-human frequency with Pickle's shallow-horizon compatibility.
U64 pick = mix64(board.get_hash_key() ^ (++probe_counter * 0x9e3779b97f4a7c15ULL))
% static_cast<U64>(total_weight);
for (const auto& choice : choices) {
Expand All @@ -100,5 +100,5 @@ bool opening_book_enabled() {
}

int opening_book_position_count() {
return static_cast<int>(STOCKFISH_BOOK_ENTRY_COUNT);
return static_cast<int>(PICKLE_BOOK_ENTRY_COUNT);
}
4 changes: 2 additions & 2 deletions opening_book.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
#include "board.h"
#include "move.h"

// Pickle's opening book is generated at build time from pinned
// official-stockfish/books data and compiled into the engine.
// PickleBook is generated at build time from a pinned Lichess-Elite-derived
// repertoire, then reweighted for Pickle's shallow-search horizon.
void init_opening_book();

// Returns a legal weighted book move for the current position, or 0 when out of book.
Expand Down
Loading