Source Code

https://github.com/youmin1017/minmax-tictactoe-rs.git

Screenshot

Main

fn main() {
    // create game instance
    let mut game = TicTacToe::new();
 
    loop {
        // ai turn
        // find best move index from (0..9)
        let ai_idx = TicTacToe::min_max(game, Player::Ai);
        game.board[ai_idx.index] = Some(Player::Ai);
        game.show();
        // if ai win, breaking the loop
        if game.winning(Player::Ai) {
            println!("AI winnning");
            break;
        }
        // human turn
        print!("Input index: ");
        let x: usize = text_io::read!();
        let y: usize = text_io::read!();
        game.board[x * 3 + y] = Some(Player::Human);
        game.show();
        // if human win, breaking the loop
        if game.winning(Player::Human) {
            println!("Human winnning");
            break;
        }
    }
}

Structs

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Player {
    Ai,
    Human,
}
 
#[derive(Clone, Copy)]
struct TicTacToe {
    /// Human player   -- O
    /// AI    player   -- X
    /// None
    board: [Option<Player>; 9],
}
 
// min_max result
struct Move {
    index: usize,
    score: i32,
}

Game and MinMax Algorithm methods

impl TicTacToe {
    fn new() -> Self {
        Self { board: [None; 9] }
    }
 
	/// MinMax Algorithm
    fn min_max(mut game: TicTacToe, player: Player) -> Move {
        let placable = game.placable();
        // if ai win, get 10 point
        if game.winning(Player::Ai) {
            return Move::from_score(10);
        // if human win, get -10 point
        } else if game.winning(Player::Human) {
            return Move::from_score(-10);
        // if tie, 0 point returned
        } else if placable.is_empty() {
            return Move::from_score(0);
        }
 
        // moves is used to record all possiable move and its points
        let mut moves: Vec<Move> = vec![];
        for idx in placable.into_iter() {
            game.board[idx] = Some(player);
            // human -> ai
            // ai    -> human
            let mut m = Self::min_max(
                game,
                match player {
                    Player::Human => Player::Ai,
                    Player::Ai => Player::Human,
                },
            );
            m.index = idx;
            game.board[idx] = None;
            moves.push(m);
        }
        // ai   : find maxmize point gotten
        // human: find minimize alternatively
        let best_move = match player {
            Player::Ai => moves.into_iter().max_by_key(|x| x.score),
            Player::Human => moves.into_iter().min_by_key(|x| x.score),
        };
        // return best move
        best_move.unwrap_or(Move::from_score(0))
    }
 
    /// Find all placable index
    fn placable(&self) -> Vec<usize> {
        self.board
            .iter()
            .enumerate()
            .filter(|(_, x)| **x == None)
            .map(|(i, _)| i)
            .collect()
    }
 
    /// Check if player is winning
    pub fn winning(&self, player: Player) -> bool {
        let p = Some(player);
        // let x = b[..3];
        self.board[..3] == [p; 3]
            || self.board[3..6] == [p; 3]
            || self.board[6..9] == [p; 3]
            || self.board[0] == p && self.board[3] == p && self.board[6] == p
            || self.board[1] == p && self.board[4] == p && self.board[7] == p
            || self.board[2] == p && self.board[5] == p && self.board[8] == p
            || self.board[0] == p && self.board[4] == p && self.board[8] == p
            || self.board[2] == p && self.board[4] == p && self.board[6] == p
    }
 
    /// Shoe board in terminal
    pub fn show(&self) {
        let transformer = |x: Option<Player>| match x {
            Some(Player::Human) => 'O',
            Some(Player::Ai) => 'X',
            None => ' ',
        };
        self.board.chunks(3).for_each(|x| {
            println!(
                "{} {} {}",
                transformer(x[0]),
                transformer(x[1]),
                transformer(x[2])
            )
        });
        println!("------------------------");
    }
}