#board-game #traits #abstract #representation #moves #deterministic #tic-tac-toe

board-game-traits

Traits for abstract game position representations

5 releases (3 breaking)

0.4.0 Jan 14, 2024
0.3.0 Mar 7, 2022
0.2.1 Jul 11, 2021
0.2.0 Feb 23, 2021
0.1.0 Jun 27, 2019

#2 in #moves

Download history 18/week @ 2023-12-24 7/week @ 2024-01-14 5/week @ 2024-02-18 36/week @ 2024-02-25 7/week @ 2024-03-03 10/week @ 2024-03-10 4/week @ 2024-03-17

57 downloads per month
Used in pgn-traits

MIT license

10KB
96 lines

Overview

A collection of minimal traits for abstractly representing 2-player board games. The traits can be implemented for any sequential, deterministic, perfect-information game. This includes many popular games such as chess, go, xiangqi, othello, connect four and tic-tac-toe.

No implementations are provided in this crate.

Usage examples

Count the total number of moves possible at n moves deep. Commonly called a perf test and used to check the correctness of move generation:

pub fn perft<B: Board>(board: &mut B, depth: u16) -> u64 {
    if depth == 0 {
        1
    } else {
        let mut moves = vec![];
        board.generate_moves(&mut moves);
        moves
            .into_iter()
            .map(|mv| {
                let reverse_move = board.do_move(mv);
                let num_moves = perft(board, depth - 1);
                board.reverse_move(reverse_move);
                num_moves
            })
            .sum()
    }
}

The Minimax algorithm, running n plies ahead:

fn minimax<B: EvalBoard>(board: &mut B, depth: u16) -> f32 {
    match board.game_result() {
        Some(GameResult::WhiteWin) => return 100.0,
        Some(GameResult::BlackWin) => return -100.0,
        Some(GameResult::Draw) => return 0.0,
        None => (),
    }
    if depth == 0 {
        board.static_eval()
    } else {
        let side_to_move = board.side_to_move();
        let mut moves = vec![];
        board.generate_moves(&mut moves);
        let child_evaluations = moves.into_iter().map(|mv| {
            let reverse_move = board.do_move(mv);
            let eval = minimax(board, depth - 1);
            board.reverse_move(reverse_move);
            eval
        });
        match side_to_move {
            Color::White => child_evaluations
                .max_by(|a, b| a.partial_cmp(b).unwrap())
                .unwrap(),
            Color::Black => child_evaluations
                .min_by(|a, b| a.partial_cmp(b).unwrap())
                .unwrap(),
        }
    }
}

No runtime deps