DEV Community

Cover image for Building a Chess Engine in Python - Part 2: Achieving 100% Correctness & Setting Up Multicore Self-Play
Nicholas
Nicholas

Posted on

Building a Chess Engine in Python - Part 2: Achieving 100% Correctness & Setting Up Multicore Self-Play

It’s PEAK.

Hey guys ;)

Since my last post, I’ve done a massive amount of under-the-hood work. Honestly, writing the code this time felt surprisingly smooth, but the results are a huge milestone for the project: I built a performance/correctness tester, implemented a parallelized self-play system using multiple CPU cores, and hooked it up to a database.

We are officially at the doorstep of Reinforcement Learning. Let’s break down how it all came together.


The Heartbreak (and Triumph) of Perft Testing

In my previous update, I mentioned that I was moving toward Perft (Performance Test) to verify the engine's correctness. A Perft test recursively walks the move tree to a specific depth, counts all total leaf nodes, and compares that number to standard chess data.

When I ran my first test to depth 3 and 4, my numbers were lower than the official benchmarks on chessprogramming.org. I'm not going to lie - I was really sad. After days of wrestling with bitwise masks, realizing your engine is missing thousands of moves feels brutal.

But instead of giving up, I used the test to isolate the discrepancies. It turned out the missing paths weren't deep architectural flaws, but rather sneaky edge cases in pawn logic:

  • The En Passant Ghost: A bug where specific en passant capture rights weren't being cleared or generated correctly.
  • Out of Index Crash: A rare boundary issue when pawns reached the absolute edges of the board during specific masking checks.

Once those pawn bugs were squashed, I ran the numbers again. Success! The engine officially proves its 100% mathematical correctness up to depth 4 across multiple complex custom test positions.


Multicore Self-Play & Epsilon-Greedy Exploration

With a bulletproof bitboard engine, the next challenge was generating data for our future Reinforcement Learning (RL) agent. To train a model, you need games—thousands of them.

To achieve this, I implemented a Self-Play system. Here is how the architecture works right now:

  1. Epsilon-Greedy Minimax: I modified my classic Minimax algorithm into an epsilon-greedy version.
  2. Opening Imitation: To prevent the engine from playing the exact same game over and over, the first 4 moves of every match are completely random. This effectively imitates traditional chess openings and forces the engine to explore a massive variety of board states.
  3. Multicore Scaling: Running games sequentially takes ages. I utilized Python's multiprocessing features to distribute the self-play simulation across multiple CPU cores simultaneously.
  4. Database Storage: Every single generated game, position, evaluated score, and move state is now being piped and safely recorded into a local database.

Next Stop: Reinforcement Learning

The infrastructure is ready. The engine is fast, verified as correct, scales across my CPU, and logs clean training data directly to a DB.

The next station is implementing and training the actual RL models. This is where the real experiment begins.


#python #chess #performance #multiprocessing #showdev

Top comments (0)