chessgen 0.9.9

Chess moves generator
Documentation

chessgen

Build License Cargo Documentation

A simple and fast chess moves generator.

This is my next and probably last rewrite of a chess moves generator into a new programming language.

Since 2014, I have implemented the same engine in:

Rust implementation is the newest and most advanced in terms of code quality and features.

Performance comparison between different language versions.

PerfT computes number of all possible moves for a given depth and is a common way how to test performance and result of a chess generator.

Rust implementation is very comparable in performance to C++ and it surprisingly behaves faster for bigger depth of moves.

Benchmark: AMD Ryzen 7 5800H, Ubuntu 23.04

Multithreaded, with cache:

Multithreaded, no cache:

Benchmark: Apple M4, macOS

Multithreaded, with cache (Rust v0.9.5):

v0.9.9 Performance Optimizations

The following optimizations were applied by Claude (Anthropic) to significantly reduce per-move overhead:

  • ChessBoard struct size reduced from ~1680 to ~160 bytes - Removed the piece_cache: [Option<(Color, Piece)>; 64] field (1536 bytes) which was copied on every apply_move call. Replaced with a lightweight color_pieces: [BitBoard; 2] cache (16 bytes) that is maintained incrementally.
  • O(1) piece accessors - pieces(), all_pieces(), my_pieces(), and opponent_pieces() now return cached bitboards instead of folding 6 piece bitboards per call.
  • Optimized apply_move() - Piece type lookup from bitboards ordered by move frequency (Pawn first, King last). Capture detection uses the cached color bitboard directly.
  • Stack-allocated Zobrist tables - Replaced heap-allocated Vec<Vec<Vec<u64>>> with fixed-size arrays [[[u64; 64]; 6]; 2], eliminating pointer indirection in the hot hashing loop.
  • Direct bitboard checks in PerfT - Replaced piece_at() calls in the performance-critical perft1 loop with direct bitboard membership tests.

Examples

Initializing ChessBoard

use chessgen::ChessBoard;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // standard chess board
    let mut board = ChessBoard::STANDARD;

    // creating board from FEN
    board = ChessBoard::from_fen(ChessBoard::STANDARD_BOARD_FEN)?;
    board = ChessBoard::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")?;

    // creating board from String representation
    board = ChessBoard::from_string(
        "
      a b c d e f g h
    8 r n b q k b n r 8
    7 p p p p p p p p 7
    6 - - - - - - - - 6
    5 - - - - - - - - 5
    4 - - - - - - - - 4
    3 - - - - - - - - 3
    2 P P P P P P P P 2
    1 R N B Q K B N R 1
      a b c d e f g h
    ",
    )?;

    // creating board from String representation without decoration
    board = ChessBoard::from_string(
        "
     r n b q k b n r 
     p p p p p p p p 
     - - - - - - - - 
     - - - - - - - - 
     - - - - - - - - 
     - - - - - - - - 
     P P P P P P P P 
     R N B Q K B N R 
    ",
    )?;

    println!("{}", board);

    Ok(())
}

Output:

Generating attacks and moves

See: ChessProgramming Pseudo Legal Move

use chessgen::{ChessBoard, Color};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let board = ChessBoard::from_string(
        "
          - - - - q - - k
          - - - - - - - -
          - - - - - - - -
          - - - - - - - -
          - - - - - - - -
          - - - - - - - -
          - - - - B - - -
          - - - Q K - - -
     ",
    )?;

    // attacks
    println!("Attacks:\n{}", board.attacks(Color::Black));

    // legal moves
    print!("Legal moves: ");
    for m in board.legal_moves() {
        print!("{} ", m)
    }
    println!("\n");

    // pseudo legal moves
    print!("Pseudo legal moves: ");
    board.moves(&mut |m| print!("{} ", m));
    println!("\n");

    Ok(())
}

Output:

Applying a move

use chessgen::{ChessBoard, Move};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut board = ChessBoard::from_string(
        "
          - - - - q - - k
          - - - - - - - -
          - - - - - - - -
          - - - - - - - -
          - - - - - - - -
          - - - - - - - -
          - - - - B - - -
          - - - Q K - - -
     ",
    )?;

    board = board.validate_and_apply_move(&Move::from_string("d1d8")?)?;

    println!("{}", board);

    Ok(())
}

Output:

Running PerfT

use chessgen::{ChessBoard, PerfT};
use std::time::Instant;

const CACHE_SIZE: usize = 64 * 1024 * 1024;

/// Run PerfT at specific board and depth.
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let board = ChessBoard::STANDARD;
    let depth = 5;

    let start = Instant::now();
    let count = PerfT::new(CACHE_SIZE).perft_n(&board, depth);
    let duration = start.elapsed();

    println!("perfT finished:");
    println!("   FEN:   {}", board.to_fen());
    println!("   depth: {}", depth);
    println!("   count: {}", count);
    println!("   time:  {:?}", duration);

    Ok(())
}

Output:

Displaying chess board

You may implement custom display of the chessboard using

    ChessBoard::piece_at()
use chessgen::{ChessBoard, Index};

/// Run PerfT at specific board and depth.
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let board = ChessBoard::STANDARD;

    for i in Index::ALL_FIELDS {
        if i.index > 0 && i.rank() != Index::new(i.index - 1).rank() {
            println!();
        }

        let file = i.file();
        let rank = i.rank();

        // translate ranks for a display as rank 0 is the most bottom rank
        let translated_i = Index::from_rank_and_file(7 - rank, file);

        let output = match board.piece_at(translated_i) {
            Some((color, piece)) => piece.to_char(color),
            None => '-',
        };

        print!(" {} ", output);
    }
    println!();

    Ok(())
}

Output: