mctser 0.1.2

An incridiblely easy-to-use library for Monte Carlo Tree Search
Documentation
use std::rc::Rc;

use game::{Action, EndStatus, Player, TictactoeGame};

impl mctser::EndStatus for EndStatus {}
impl mctser::Action for Action {}

impl mctser::Player<EndStatus> for Player {
    fn reward_when_outcome_is(&self, outcome: &EndStatus) -> f32 {
        match outcome {
            EndStatus::Win(winner) => {
                if self == winner {
                    1.
                } else {
                    0.
                }
            }
            EndStatus::Tie => 0.5,
        }
    }
}

impl mctser::GameState<Player, EndStatus, Action> for TictactoeGame {
    fn end_status(&self) -> Option<EndStatus> {
        self.end_status
    }

    fn player(&self) -> Player {
        self.player
    }

    fn possible_actions(&self) -> Vec<Action> {
        let mut possible_actions = Vec::new();
        for row in 0..3 {
            for col in 0..3 {
                if !self.occupied(Action(row, col)) {
                    possible_actions.push(Action(row, col));
                }
            }
        }
        possible_actions
    }

    fn act(&self, selection: &Action) -> Self {
        self.place(selection).unwrap()
    }
}

fn main() {
    let mut game = Rc::new(TictactoeGame::new());
    let mut search_tree = mctser::SearchTree::new(game.clone());

    while game.end_status.is_none() {
        let selected = search_tree.search(1000).unwrap();
        search_tree.renew(&selected).unwrap();
        game = search_tree.get_game_state();
        game.draw_board();
    }
}

mod game {
    #[derive(Clone, Copy)]
    pub enum EndStatus {
        Win(Player),
        Tie,
    }

    #[derive(PartialEq, Eq, Clone, Copy)]
    pub enum Player {
        Player0,
        Player1,
    }

    impl Player {
        pub fn next(&self) -> Player {
            match self {
                Player::Player0 => Player::Player1,
                Player::Player1 => Player::Player0,
            }
        }
    }

    #[derive(PartialEq, Eq, Clone, Copy)]
    pub struct Action(pub usize, pub usize);

    pub struct TictactoeGame {
        pub board: [[Option<Player>; 3]; 3],
        pub player: Player,
        pub end_status: Option<EndStatus>,
    }

    impl TictactoeGame {
        pub fn new() -> Self {
            Self {
                board: [[None; 3]; 3],
                player: Player::Player0,
                end_status: None,
            }
        }

        fn check_end_status(&mut self) {
            for row in 0..3 {
                if self.board[row][0].is_some()
                    && self.board[row][0] == self.board[row][1]
                    && self.board[row][1] == self.board[row][2]
                {
                    let winner = self.board[row][0].unwrap();
                    self.end_status = Some(EndStatus::Win(winner));
                    return;
                }
            }

            for col in 0..3 {
                if self.board[0][col].is_some()
                    && self.board[0][col] == self.board[1][col]
                    && self.board[1][col] == self.board[2][col]
                {
                    let winner = self.board[0][col].unwrap();
                    self.end_status = Some(EndStatus::Win(winner));
                    return;
                }
            }

            if self.board[1][1].is_some()
                && ((self.board[0][0] == self.board[1][1] && self.board[1][1] == self.board[2][2])
                    || (self.board[0][2] == self.board[1][1]
                        && self.board[1][1] == self.board[2][0]))
            {
                let winner = self.board[1][1].unwrap();
                self.end_status = Some(EndStatus::Win(winner));
                return;
            }

            if self.board.iter().flatten().all(|p| p.is_some()) {
                self.end_status = Some(EndStatus::Tie);
            } else {
                self.end_status = None;
            }
        }

        pub fn occupied(&self, selection: Action) -> bool {
            let Action(row, col) = selection;
            self.board[row][col].is_some()
        }

        pub fn place(&self, selection: &Action) -> Result<TictactoeGame, &str> {
            let Action(row, col) = *selection;
            match self.board[row][col] {
                Some(_) => Err("invalid place"),
                None => {
                    let mut board = self.board;
                    board[row][col] = Some(self.player);
                    let mut game = TictactoeGame {
                        board,
                        player: self.player.next(),
                        end_status: None,
                    };
                    game.check_end_status();
                    Ok(game)
                }
            }
        }

        pub fn draw_board(&self) {
            println!("\u{250C}\u{2500}\u{2500}\u{2500}\u{2510}");
            for row in 0..3 {
                print!("\u{2502}");
                for col in 0..3 {
                    match self.board[row][col] {
                        Some(Player::Player0) => print!("O"),
                        Some(Player::Player1) => print!("X"),
                        None => print!("-"),
                    }
                }
                println!("\u{2502}");
            }
            println!("\u{2514}\u{2500}\u{2500}\u{2500}\u{2518}");
        }
    }
}