2 unstable releases

0.2.0 Jun 17, 2022
0.1.0 Mar 20, 2021

#875 in Game dev

MIT license

19KB
328 lines

arbor

This crate provides a generic interface to the Monte Carlo Tree Search algorithm. It allows a developer to implement an AI agent for a two player game without the need to describe heuristics or strategies specific to the game.

Examples using arbor are provided on GitHub including:

  • Reversi
  • Connect 4
  • Mancala
  • Tic-Tac-Toe

These examples are demonstrated graphically with Yew on the Arbor GitHub.io.

Documentation

Documentation is provided on Docs.rs.

Example

Add arbor to the dependencies of your Cargo.toml:

[dependencies]
arbor = "0.2.0"

And then, in your rust file:

use arbor::*;

#[derive(Copy,Clone,Debug,PartialEq)]
enum Grid {
    TL,TM,TR,
    ML,MM,MR,
    BL,BM,BR
}

#[derive(Copy,Clone,Debug)]
struct TicTacToe {
    space: [Mark;9],
    turn: usize,
    side: Mark,
}

impl Action for Grid {}
impl Player for Mark {}

impl GameState<Mark,Grid> for TicTacToe {

    fn actions<F>(&self,f: &mut F) where F: FnMut(Grid){
        debug_assert!(self.gameover().is_none());
        
        for mark in ALLMOVES.iter() {
            let i = *mark as usize;
            if self.space[i] == Mark::N {
                f(ALLMOVES[i])
            }
        }
    }
    
    fn make(&self, action: Grid) -> Self {
        debug_assert!(self.gameover().is_none(),"Make called while gameover\n{}",self);
        debug_assert!(self.space[action as usize] == Mark::N,"Make called on invalid space {:?}\n{}",action,self);

        let mut next = TicTacToe {
            space: self.space,
            turn: self.turn + 1,
            side: if self.side == Mark::X {Mark::O} else {Mark::X},
        };

        next.space[action as usize] = self.side;

        next
    }
    
    fn gameover(&self) -> Option<GameResult> {
        let winner = self.winner();
        if (self.turn == 9) || (winner != Mark::N) {
            return match winner {
                Mark::N => Some(GameResult::Draw),

                // Side to play last always wins
                _ => Some(GameResult::Lose),
            }
        } else {
            None
        }
    }

    fn player(&self) -> Mark {
        self.side
    }
}

Dependencies

~3MB
~60K SLoC