Merge branch 'release/v0.1.0'

This commit is contained in:
landrigun 2022-11-16 13:38:05 +00:00
commit 6f04c87daf

View File

@ -86,14 +86,19 @@ impl Board {
}
fn set_item(&mut self, item: Item, idx: usize) -> Result<(), &str> {
if idx == 0 || idx > 9 {
if idx > 8 {
return Err("invalid input, must be between 1 <= x <= 9");
}
match self.items[idx - 1] {
// player must be forbidden to do this action (only used by minimax: undo action)
if item == Item::Empty {
self.items[idx] = item;
return Ok(());
}
match self.items[idx] {
Item::X => Err("already set, choose another one!"),
Item::O => Err("already set, choose another one!"),
Item::Empty => {
self.items[idx - 1] = item;
self.items[idx] = item;
return Ok(());
}
}
@ -234,6 +239,90 @@ impl From<String> for GameType {
}
}
/// Holds all the mandatory informations to compute the **minimax** algorithm
/// avoiding to collect on each turn both player
struct Minimax {
bot: Player,
human: Player,
}
impl Minimax {
fn new(bot: Player, human: Player) -> Self {
Minimax { bot, human }
}
/// checks if there's a winner and set an associated score for each winner
/// +1 for the bot: **maximizer**
/// -1 for the human: **minimizer**
fn evaluate(&self, board: &Board) -> isize {
if board.check_win(self.bot) {
return 1;
}
if board.check_win(self.human) {
return -1;
}
0
}
/// computes the **minimax** algorithm
/// recursive function, checks all board states (tree) for each turn (human + bot) and returns the best score
fn compute(&self, board: &mut Board, player: Player, depth: usize) -> isize {
let score = self.evaluate(board);
if score != 0 {
return score;
}
// no more moves availables it's a tie
let available_indexes = board.get_available_indexes();
if available_indexes.len() == 0 {
return 0;
}
// maximizer move's
if player.is_bot {
// negative high value, enough to simulate an -INF
let mut best_score = -1000;
for index in available_indexes {
board.set_item(player.item, index).unwrap();
best_score = std::cmp::max(best_score, self.compute(board, self.human, depth + 1));
board.set_item(Item::Empty, index).unwrap();
}
return best_score;
}
// mimimizer move's
let mut best_score = 1000;
for index in available_indexes {
board.set_item(player.item, index).unwrap();
best_score = std::cmp::min(best_score, self.compute(board, self.bot, depth + 1));
board.set_item(Item::Empty, index).unwrap();
}
best_score
}
/// returns the bot best move's (board index) in the current board state
fn get_best_move(&self, board: &mut Board) -> Option<usize> {
let mut best_score = -1000;
let mut best_move = None;
let available_indexes = board.get_available_indexes();
for index in available_indexes {
board.set_item(self.bot.item, index).unwrap();
let score = self.compute(board, self.human, 0);
board.set_item(Item::Empty, index).unwrap();
if score > best_score {
best_score = score;
best_move = Some(index);
}
}
best_move
}
}
#[allow(dead_code)]
struct TicTacToe {
board: Board,
@ -296,7 +385,7 @@ impl TicTacToe {
Ok(self.players[0])
}
/// if `GameType` is againt bot, you can select who starts
/// if `GameType` is against bot, you can select who starts
fn select_first_player(&mut self) {
if self.players.len() != 2 {
fatal("unable to initialize the game, 2 players must be set".to_string());
@ -364,7 +453,7 @@ impl TicTacToe {
self.players = Player::from_game_type(self.game_type);
}
fn select_player_item(&self) -> Result<usize, String> {
fn select_player_item(&mut self) -> Result<usize, String> {
if self.players.len() != 2 {
return Err(
"unable to get player item input, 2 players must be initialized".to_string(),
@ -383,12 +472,21 @@ impl TicTacToe {
}
}
// for now, use a dump bot
let mut rng = rand::thread_rng();
let mut available_indexes = self.board.get_available_indexes();
available_indexes.shuffle(&mut rng);
// TODO: player can select the bot level
// bot uses minimax algorithm
let minimax = Minimax::new(player, self.players[1]);
match minimax.get_best_move(&mut self.board) {
Some(v) => {
return Ok(v + 1);
}
None => {
let mut rng = rand::thread_rng();
let mut available_indexes = self.board.get_available_indexes();
available_indexes.shuffle(&mut rng);
Ok(available_indexes[0] + 1)
return Ok(available_indexes[0] + 1);
}
}
}
fn run(&mut self) {
@ -416,14 +514,12 @@ impl TicTacToe {
loop {
match self.select_player_item() {
Ok(pos) => {
match self.board.set_item(player.item, pos) {
Ok(_) => break,
Err(e) => {
error(e.to_string());
}
Ok(pos) => match self.board.set_item(player.item, pos - 1) {
Ok(_) => break,
Err(e) => {
error(e.to_string());
}
}
},
Err(e) => {
error(e.to_string());
}