Source Code
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!("------------------------");
}
}