Merge branch 'release/v0.1.0'
This commit is contained in:
		
						commit
						6f04c87daf
					
				
							
								
								
									
										118
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -86,14 +86,19 @@ impl Board { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn set_item(&mut self, item: Item, idx: usize) -> Result<(), &str> { |     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"); |             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::X => Err("already set, choose another one!"), | ||||||
|             Item::O => Err("already set, choose another one!"), |             Item::O => Err("already set, choose another one!"), | ||||||
|             Item::Empty => { |             Item::Empty => { | ||||||
|                 self.items[idx - 1] = item; |                 self.items[idx] = item; | ||||||
|                 return Ok(()); |                 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)] | #[allow(dead_code)] | ||||||
| struct TicTacToe { | struct TicTacToe { | ||||||
|     board: Board, |     board: Board, | ||||||
| @ -296,7 +385,7 @@ impl TicTacToe { | |||||||
|         Ok(self.players[0]) |         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) { |     fn select_first_player(&mut self) { | ||||||
|         if self.players.len() != 2 { |         if self.players.len() != 2 { | ||||||
|             fatal("unable to initialize the game, 2 players must be set".to_string()); |             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); |         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 { |         if self.players.len() != 2 { | ||||||
|             return Err( |             return Err( | ||||||
|                 "unable to get player item input, 2 players must be initialized".to_string(), |                 "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
 |         // 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 rng = rand::thread_rng(); | ||||||
|                 let mut available_indexes = self.board.get_available_indexes(); |                 let mut available_indexes = self.board.get_available_indexes(); | ||||||
|                 available_indexes.shuffle(&mut rng); |                 available_indexes.shuffle(&mut rng); | ||||||
| 
 | 
 | ||||||
|         Ok(available_indexes[0] + 1) |                 return Ok(available_indexes[0] + 1); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn run(&mut self) { |     fn run(&mut self) { | ||||||
| @ -416,14 +514,12 @@ impl TicTacToe { | |||||||
| 
 | 
 | ||||||
|             loop { |             loop { | ||||||
|                 match self.select_player_item() { |                 match self.select_player_item() { | ||||||
|                     Ok(pos) => { |                     Ok(pos) => match self.board.set_item(player.item, pos - 1) { | ||||||
|                         match self.board.set_item(player.item, pos) { |  | ||||||
|                         Ok(_) => break, |                         Ok(_) => break, | ||||||
|                         Err(e) => { |                         Err(e) => { | ||||||
|                             error(e.to_string()); |                             error(e.to_string()); | ||||||
|                         } |                         } | ||||||
|                         } |                     }, | ||||||
|                     } |  | ||||||
|                     Err(e) => { |                     Err(e) => { | ||||||
|                         error(e.to_string()); |                         error(e.to_string()); | ||||||
|                     } |                     } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user