diff --git a/04_Awari/rust/Cargo.toml b/04_Awari/rust/Cargo.toml new file mode 100644 index 00000000..1c2935d4 --- /dev/null +++ b/04_Awari/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" \ No newline at end of file diff --git a/04_Awari/rust/src/main.rs b/04_Awari/rust/src/main.rs new file mode 100644 index 00000000..7aa00831 --- /dev/null +++ b/04_Awari/rust/src/main.rs @@ -0,0 +1,214 @@ +use std::{thread, time::Duration}; + +use rand::Rng; + +// AI "learning" is not implemented. Don't have the time. - Ugur + +fn main() { + loop { + let mut game = Game::default(); + + loop { + game.draw(); + if game.play_turn(false) { + break; + } + } + } +} + +enum DistributeResult { + Normal, + // Leftover beans + EndOnHomePit(bool), + // "true" if ended on Player Home Pit + EndOnEmptyPit(usize), + // "index" of the empty pit within the Row + ChosenEmpty, +} + +struct Game { + pits: [u8; 14], + player_turn: bool, +} + +impl Default for Game { + fn default() -> Self { + println!("\n\n\t\t AWARI"); + println!("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + + Self { + pits: [3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 0], + player_turn: true, + } + } +} + +impl Game { + fn step_through(&mut self, mut index: usize) -> usize { + let mut bean_amount = self.pits[index]; + self.pits[index] = 0; + + loop { + index += 1; + + if index > self.pits.len() - 1 { + index = 0; + } + + self.pits[index] += 1; + + bean_amount -= 1; + if bean_amount == 0 { + return index; + } + } + } + + fn play_turn(&mut self, is_repeat: bool) -> bool { + use DistributeResult::*; + + if self.is_game_over() { + println!("\nGame Over!"); + let (player_beans, ai_beans) = (self.pits[6], self.pits[13]); + if player_beans == ai_beans { + println!("It's a draw") + } else if player_beans > ai_beans { + println!("You win by {}", player_beans - ai_beans); + } else { + println!("I win by {}", ai_beans - player_beans); + } + return true; + } + + let chosen_index = if self.player_turn { + player_prompt(if is_repeat { "Again?" } else { "Your move?" }) - 1 + } else { + println!("========================"); + + thread::sleep(Duration::from_secs(1)); + + let non_empty_pits: Vec = self + .pits + .iter() + .enumerate() + .filter(|&(i, p)| (7..13).contains(&i) && *p > 0) + .map(|(i, _)| i) + .collect(); + let random_index = rand::thread_rng().gen_range(0..non_empty_pits.len()); + let ai_move = non_empty_pits[random_index]; + + println!("My move is {}", ai_move - 6); + + println!("========================"); + ai_move + }; + + match self.process_choice(chosen_index) { + Normal => (), + EndOnHomePit(player) => { + self.draw(); + + if player == self.player_turn && !is_repeat { + self.play_turn(true); + } + } + EndOnEmptyPit(last_index) => { + let opposite_index = 12 - last_index; + let home_index = if self.player_turn { 6 } else { 13 }; + let won_beans = 1 + self.pits[opposite_index]; + + self.pits[last_index] = 0; + self.pits[opposite_index] = 0; + self.pits[home_index] += won_beans; + } + ChosenEmpty => { + println!("Chosen pit is empty"); + return self.play_turn(is_repeat); + } + } + + if !is_repeat { + self.player_turn = !self.player_turn; + } + + false + } + + pub fn process_choice(&mut self, index: usize) -> DistributeResult { + use DistributeResult::*; + + if self.pits[index] == 0 { + return ChosenEmpty; + } + + let last_index = self.step_through(index); + + if last_index == 6 && self.player_turn { + return EndOnHomePit(true); + } else if last_index == 13 && !self.player_turn { + return EndOnHomePit(false); + } else if self.pits[last_index] == 1 { + return EndOnEmptyPit(last_index); + } + + Normal + } + + fn is_game_over(&self) -> bool { + let player_empty = !(0..6).any(|i| self.pits[i] > 0); + let ai_empty = !(7..13).any(|i| self.pits[i] > 0); + player_empty || ai_empty + } + + fn draw(&self) { + let row_as_string = |player: bool| -> String { + let mut row_as_string = String::new(); + + let range = if player { 0..6 } else { 7..13 }; + + range.for_each(|i| { + let mut bean_amount_as_string = self.pits[i].to_string(); + bean_amount_as_string.push_str(" "); + + if player { + row_as_string.push_str(&bean_amount_as_string); + } else { + row_as_string.insert_str(0, &bean_amount_as_string); + } + }); + + row_as_string + }; + + println!( + "\n {}\n{} {}\n {}\n", + row_as_string(false), + self.pits[13].to_string(), + self.pits[6].to_string(), + row_as_string(true) + ); + } +} + +pub fn player_prompt(message: &str) -> usize { + loop { + let mut input = String::new(); + println!("{}", message); + + if let Ok(_) = std::io::stdin().read_line(&mut input) { + match input.trim().parse::() { + Ok(n) => { + if (1..=6).contains(&n) { + return n; + } else { + println!("Enter a number between 1 and 6") + } + } + Err(e) => { + println!("{}", e); + } + } + } + } +}