Merge branch 'release/v0.1.0'
This commit is contained in:
commit
6f04c87daf
130
src/main.rs
130
src/main.rs
@ -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());
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user