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