Skip to content

kirillbobyrev/pabi

Repository files navigation

Pabi

Codecov Dependencies

Build Test Suite Lint

Pabi is a modern chess engine that is currently under development.

Goals

Pabi is inspired by existing Chess and Go engines (mainly Stockfish, lc0, and KataGo). It strives to be a high-quality modern engine.

Modern: Pabi should use up-to-date Rust toolchain, is targeting modern processor architectures, uses latest developments in the domains of programming tooling, Chess Programming and Machine Learning.

High-quality: Pabi will take full advantage of

  • Unit testing and integration testing: the majority of the codebase should be well-tested and validated. Pabi also uses advanced testing techniques such as Fuzzing.
  • Performance testing and benchmarking: Pabi should be fast. Continuous integration runs benchmarks and tests after every change.
  • Continuous quality assurance: GitHub Actions make it possible to lint, test, benchmark and warn on regressions/failures. Dependabot will warn about outdated dependencies and security vulnerabilities.
  • Documentation: leveraging rustdoc to full potential and making the codebase accessible.

Pabi strives to be strong. The ultimate goal is to enter the lists of top chess engines and participate in tournaments, such as Chess.com Computer Chess Championship and TCEC. As such, it should be tested in appropriate time formats (mainly Blitz and Rapid) that are popular in these rating lists and designed to perform well against other engines. Performance and good time-management are crucial for doing well under time pressure.

Project architecture

This document describes high-level architecture of Pabi. If you want to get familiar with the code and understand the project structure, this is a great starting point.

Scope

Implementing a general-purpose chess engine that would truly excel in different domains, such as playing with humans, being able to adjust the strength, providing the best possible analysis for a position when given a lot of time and, finally, playing well against other engines is an enormous task, especially for one person. Therefore, it's crucial to choose the right scope and design the engine with chosen limitations in mind.

Pabi chooses to be the best possible version of an engine that does well against other chess engines in online tournaments. That means that it will prioritize performance under the constraints put by the rules and environments of such tournaments. Most important tournament organizers are TCEC and CCCC, and the most prominent rating to date is CCRL. The first goal is reaching 3000 ELO on the rating lists that are accepted by the organizers of these tournaments.

Most of the competitions are in relatively fast time controls (Blitz, Bullet, Rapid) with some exceptions in relatively short classical formats. In both CCRL rules and [TCEC rules], as well as in CCCC the engines are usually starting from pre-determined positions to make the games interesting and unbalanced.

In all cases, the testing environment's CPU is of x86_64 architecture, which is very important because of PEXT and PDEP instructions that significantly increase the performance of move generators. Also, usually, many CPUs are available (8 cores on CCRL, 52 on TCEC and as many as 256 on CCCC).

Other design choices are deliberate focus on performance over most things (except simplicity and clarity), such as error recovery (there should be minimal one: if the error happened and the engine can reject the input, it will reject the input and continue working) and support for arcane environments.

Recipes

Most commands for development, building the engine, testing, checking for errors and fuzzing it are supported as just recipes. See justfile for a complete list of frequently used commands.

Code map

Rustdoc developer documentation is pushed at each commit to https://kirillbobyrev.github.io/pabi/docs/pabi/.

The source directory contains code for the engine driver implemented in Rust.

Contains implementation of the chess environment: Bitboard-based board representation, move generation and position parsing. This is the core of the engine: a fast move generator and convenient board implementation are crucial for engine's performance.

Contains code that extracts features from a given position and runs "static" position evaluation: a Neural Network considers just a single position and computes the score that determines how good it is for the player that is going to make the next move.

The development of the Neural Network model is in another repo: kirillbobyrev/pabi-brain. The Rust code only runs inference of an already trained model.

Implements Minimax search with a number of extensions for efficiently reducing the search space.

Assembles all pieces together and manages resources to search effieciently under given time constraints. It also communicates back and forth with the tournament manager/server through Universal Chess Interface (UCI) protocol implementation.

Tests the engine through public interfaces. Most tests should go here, unit tests are only valuable for testing private functions that aren't exposed but are still not trivial.

Performance is crucial for a chess engine. This directory contains a number of performance regression tests that should be frequently run to ensure that the engine is not becoming slower. Patches affecting performance should have benchmark result deltas in the description.

Fuzzers complement the existing tests by generating random inputs and trying to increase the coverage. Plenty of bugs can be caught by even relatively simply fuzzers: writing and running them is highly encouraged.

Pre-computed constants (such as Magic Bitboards, Vector Attacks and Zobrist hashing table) speed up move generation and search. This data can be calculated at build time or startup instead but the drawbacks are:

  • Build time (compile-time Rust code can not make use of most built infrastructure) or runtime (warm-up time) overhead
  • Maintenance cost
  • Losing opportunities for the compiler to do better optimizations

Hence, these values are calculated once and checked into the source tree as Rust arrays. These constants shouldn't change over time.