diff --git a/src/board.rs b/src/board.rs index fa92d8b..56dd2d2 100644 --- a/src/board.rs +++ b/src/board.rs @@ -81,6 +81,24 @@ impl BoardSlot { Ok(mesh) } + + pub fn valid(&self) -> bool { + self.state != BoardSlotState::Invalid + } + + pub fn white(&self) -> bool { + match self.state { + BoardSlotState::White(_) => true, + _ => false, + } + } + + pub fn black(&self) -> bool { + match self.state { + BoardSlotState::Black(_) => true, + _ => false, + } + } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] diff --git a/src/game.rs b/src/game.rs index b5a4e34..b4b307f 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,6 +1,6 @@ use std::sync::{ atomic::{AtomicU32, Ordering::SeqCst}, - Arc, Mutex, + Arc, Mutex, MutexGuard, }; use anyhow::Result; @@ -28,6 +28,7 @@ pub struct Stone { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] pub enum GameState { Placing, + Removing, Main, } @@ -37,11 +38,26 @@ pub enum PlayerColor { Black, } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum MillState { + White, + Black, + None, +} + impl PlayerColor { pub fn swap(&mut self) { *self = match *self { - PlayerColor::White => PlayerColor::Black, - PlayerColor::Black => PlayerColor::White, + PlayerColor::White => { + MillGame::log("swapped to black player"); + + PlayerColor::Black + } + PlayerColor::Black => { + MillGame::log("swapped to white player"); + + PlayerColor::White + } } } } @@ -196,8 +212,10 @@ impl MillGame { } if let Some(me) = weak_self.upgrade() { + Self::log("start game"); + *me.state.lock().unwrap() = GameState::Placing; - *me.current_player.lock().unwrap() = PlayerColor::White; + *me.current_player.lock().unwrap() = PlayerColor::Black; me.next_game_step()?; } @@ -210,22 +228,13 @@ impl MillGame { } fn next_game_step(&self) -> Result<()> { - match *self.current_player.lock().unwrap() { - PlayerColor::White => { - self.black_player_label - .set_background(Color::try_from("#9b9292")?)?; - self.white_player_label.set_background(Color::Yellow)?; - } - PlayerColor::Black => { - self.white_player_label - .set_background(Color::try_from("#9b9292")?)?; - self.black_player_label.set_background(Color::Yellow)?; - } - } + Self::log("next_game_step"); { let mut state = self.state.lock().unwrap(); + Self::log(&format!("\tstate: {:?}", state)); + match *state { GameState::Placing => { if !self @@ -241,34 +250,91 @@ impl MillGame { .iter() .any(|stones| stones.state == StoneState::ReadyToBePlaced) { + Self::log("change state to Main"); *state = GameState::Main; } + + if self.check_for_mill() != MillState::None { + Self::log("change state to Removing"); + *state = GameState::Removing; + } else { + self.current_player.lock().unwrap().swap(); + } + } + GameState::Removing => { + if !self + .black_stones + .lock() + .unwrap() + .iter() + .any(|stones| stones.state == StoneState::ReadyToBePlaced) + && !self + .white_stones + .lock() + .unwrap() + .iter() + .any(|stones| stones.state == StoneState::ReadyToBePlaced) + { + Self::log("change state to Main"); + *state = GameState::Main; + } else { + Self::log("change state to Placing"); + *state = GameState::Placing; + } + + self.current_player.lock().unwrap().swap(); + } + GameState::Main => { + if self.check_for_mill() != MillState::None { + Self::log("change state to Removing"); + *state = GameState::Removing; + } else { + self.current_player.lock().unwrap().swap(); + } } - GameState::Main => {} } } - if *self.current_player.lock().unwrap() == self.simple_ai.player_color { - match self.simple_ai.player_color { - PlayerColor::White => self.simple_ai.step( - &mut *self.white_stones.lock().unwrap(), - self.board.lock().unwrap().slots(), - &mut *self.scene.lock().unwrap(), - )?, - PlayerColor::Black => self.simple_ai.step( - &mut *self.black_stones.lock().unwrap(), - self.board.lock().unwrap().slots(), - &mut *self.scene.lock().unwrap(), - )?, - } + let player = *self.current_player.lock().unwrap(); + + Self::log(&format!("current player {:?}", player)); + + match player { + PlayerColor::White => { + self.black_player_label + .set_background(Color::try_from("#9b9292")?)?; + self.white_player_label.set_background(Color::Yellow)?; + } + PlayerColor::Black => { + self.white_player_label + .set_background(Color::try_from("#9b9292")?)?; + self.black_player_label.set_background(Color::Yellow)?; + } + } + + if player == self.simple_ai.player_color { + Self::log("current player is AI"); + + self.simple_ai.step( + &mut self.stones(self.simple_ai.player_color), + self.board.lock().unwrap().slots(), + &mut *self.scene.lock().unwrap(), + *self.state.lock().unwrap(), + )?; - self.current_player.lock().unwrap().swap(); self.next_game_step()?; } Ok(()) } + fn stones(&self, player_color: PlayerColor) -> MutexGuard<'_, [Stone; 9]> { + match player_color { + PlayerColor::White => self.white_stones.lock().unwrap(), + PlayerColor::Black => self.black_stones.lock().unwrap(), + } + } + fn init_nine_stones(engine: &Arc, color: Color) -> Result<[EntityObject; 9]> { Ok((0..9) .map(|_| Self::init_stone(engine, color)) @@ -327,9 +393,19 @@ impl MillGame { player_color: PlayerColor, scene: &mut Scene, ) -> Result<()> { + Self::log("place_stone"); + match player_color { - PlayerColor::White => slot.state = BoardSlotState::White(stone.stone), - PlayerColor::Black => slot.state = BoardSlotState::Black(stone.stone), + PlayerColor::White => { + Self::log(&format!("place white stone")); + + slot.state = BoardSlotState::White(stone.stone); + } + PlayerColor::Black => { + Self::log(&format!("place black stone")); + + slot.state = BoardSlotState::Black(stone.stone); + } } stone.state = StoneState::Placed; @@ -342,16 +418,115 @@ impl MillGame { Ok(()) } - pub fn check_for_mill(&self) -> Result { - let slots = self.board.lock().unwrap().slots(); + pub fn remove_stone(slot: &mut BoardSlot, scene: &mut Scene) -> Result<()> { + Self::log("remove_stone"); + + let entity = match slot.state { + BoardSlotState::Black(e) => { + Self::log(&format!("\tremove black stone")); + + e + } + BoardSlotState::White(e) => { + Self::log(&format!("\tremove white stone")); + + e + } + _ => unreachable!(), + }; + + scene.remove_entity(entity)?; + + slot.state = BoardSlotState::Empty; + + Ok(()) + } + + fn check_mill(s1: &BoardSlot, s2: &BoardSlot, s3: &BoardSlot) -> MillState { + if s1.valid() && s2.valid() && s3.valid() { + if s1.white() && s2.white() && s3.white() { + return MillState::White; + } else if s1.black() && s2.black() && s3.black() { + return MillState::Black; + } + } + + MillState::None + } + + pub fn check_for_mill(&self) -> MillState { + Self::log("check for mill"); + + let mut board = self.board.lock().unwrap(); + let slots = board.slots(); for x in 0..3 { for y in 0..3 { - for z in 0..3 { - // + let state = Self::check_mill(&slots[x][y][0], &slots[x][y][1], &slots[x][y][2]); + + if state != MillState::None { + Self::log(&format!("mill found {:?}", state)); + + return state; } } } + + for x in 0..3 { + for z in 0..3 { + let state = Self::check_mill(&slots[x][0][z], &slots[x][1][z], &slots[x][2][z]); + + if state != MillState::None { + Self::log(&format!("mill found {:?}", state)); + + return state; + } + } + } + + for y in 0..3 { + for z in 0..3 { + let state = Self::check_mill(&slots[0][y][z], &slots[1][y][z], &slots[2][y][z]); + + if state != MillState::None { + Self::log(&format!("mill found {:?}", state)); + + return state; + } + } + } + + MillState::None + } + + fn check_mouse_click(&self, f: F) -> Result<()> + where + F: FnOnce(&Self, &mut BoardSlot, &mut Scene) -> Result<()>, + { + Self::log("check_mouse_click"); + + self.scene.lock().unwrap().on_scene(|scene| { + if let Some(world_space) = + scene.screen_space_to_world(self.mouse_x.load(SeqCst), self.mouse_y.load(SeqCst))? + { + if let Some(slot) = self + .board + .lock() + .unwrap() + .close_to_marker(world_space.truncate()) + { + Self::log("click is close to marker"); + + f(self, slot, scene)?; + } + } + + Ok(()) + }) + } + + pub fn log(s: &str) { + println!("{}", s); } } @@ -378,39 +553,60 @@ impl EngineObject for MillGame { } EngineEvent::MouseButtonDown(button) => match button { MouseButton::Left => { - let mut placed = false; + let state = *self.state.lock().unwrap(); + Self::log(&format!("User click at state {:?}", state)); + + match state { + GameState::Placing => { + let mut placed = false; + + self.check_mouse_click(|me, slot, scene| { + let current_player = *self.current_player.lock().unwrap(); - self.scene.lock().unwrap().on_scene(|scene| { - if let Some(world_space) = scene.screen_space_to_world( - self.mouse_x.load(SeqCst), - self.mouse_y.load(SeqCst), - )? { - if let Some(slot) = self - .board - .lock() - .unwrap() - .close_to_marker(world_space.truncate()) - { if let Some(placable) = self - .black_stones - .lock() - .unwrap() + .stones(current_player) .iter_mut() .find(|stone| stone.state == StoneState::ReadyToBePlaced) { - Self::place_stone(placable, slot, PlayerColor::Black, scene)?; + Self::place_stone(placable, slot, current_player, scene)?; placed = true; } + + Ok(()) + })?; + + if placed { + self.next_game_step()?; } } + GameState::Removing => { + let mut removed = false; - Ok(()) - })?; + self.check_mouse_click(|me, slot, scene| { + Self::log(&format!( + "state {:?} - player {:?}", + slot.state, + *self.current_player.lock().unwrap() + )); - if placed { - self.current_player.lock().unwrap().swap(); - self.next_game_step()?; + if match (slot.state, *self.current_player.lock().unwrap()) { + (BoardSlotState::Black(_), PlayerColor::White) => true, + (BoardSlotState::White(_), PlayerColor::Black) => true, + _ => false, + } { + removed = true; + Self::remove_stone(slot, scene)?; + } + + Ok(()) + })?; + + if removed { + self.next_game_step()?; + } + } + GameState::Main => todo!(), } } MouseButton::Middle => self.camera_controls.lock().unwrap().hold(), diff --git a/src/simple_ai.rs b/src/simple_ai.rs index 9508ff7..b2e9935 100644 --- a/src/simple_ai.rs +++ b/src/simple_ai.rs @@ -1,6 +1,6 @@ use crate::{ board::{BoardSlot, BoardSlotState}, - game::{MillGame, PlayerColor, Stone, StoneState}, + game::{GameState, MillGame, PlayerColor, Stone, StoneState}, }; use anyhow::Result; @@ -20,29 +20,61 @@ impl SimpleAI { stones: &mut [Stone; 9], board: &mut [[[BoardSlot; 3]; 3]; 3], scene: &mut SceneHandle, + state: GameState, ) -> Result<()> { - if let Some(placable) = stones - .iter_mut() - .find(|stone| stone.state == StoneState::ReadyToBePlaced) - { - let mut free_slots: Vec<&mut BoardSlot> = board - .iter_mut() - .flatten() - .flatten() - .filter(|slot| slot.state == BoardSlotState::Empty) - .collect(); + MillGame::log(&format!("AI at state {:?}", state)); - let len = free_slots.len(); + match state { + GameState::Placing => { + if let Some(placable) = stones + .iter_mut() + .find(|stone| stone.state == StoneState::ReadyToBePlaced) + { + let mut free_slots: Vec<&mut BoardSlot> = board + .iter_mut() + .flatten() + .flatten() + .filter(|slot| slot.state == BoardSlotState::Empty) + .collect(); - let slot = &mut free_slots[Random::range(0, len as u32) as usize]; + let len = free_slots.len(); - scene.on_scene(|scene| { - MillGame::place_stone(placable, slot, self.player_color, scene)?; + let slot = &mut free_slots[Random::range(0, len as u32) as usize]; - Ok(()) - })?; + scene.on_scene(|scene| { + MillGame::place_stone(placable, slot, self.player_color, scene)?; + + Ok(()) + })?; + } + } + GameState::Removing => { + let mut other_color: Vec<&mut BoardSlot> = board + .iter_mut() + .flatten() + .flatten() + .filter(|slot| match (slot.state, self.player_color) { + (BoardSlotState::Black(_), PlayerColor::White) => true, + (BoardSlotState::White(_), PlayerColor::Black) => true, + _ => false, + }) + .collect(); + + let len = other_color.len(); + + let slot = &mut other_color[Random::range(0, len as u32) as usize]; + + scene.on_scene(|scene| { + MillGame::remove_stone(slot, scene)?; + + Ok(()) + })?; + } + GameState::Main => todo!(), } + MillGame::log("finish AI"); + Ok(()) } }