2023-05-09 14:39:53 +00:00
|
|
|
use std::sync::{
|
2023-05-10 08:22:46 +00:00
|
|
|
atomic::{AtomicBool, AtomicU32, Ordering::SeqCst},
|
2023-05-10 05:47:10 +00:00
|
|
|
Arc, Mutex, MutexGuard,
|
2023-05-09 14:39:53 +00:00
|
|
|
};
|
2023-05-08 18:56:50 +00:00
|
|
|
|
|
|
|
use anyhow::Result;
|
2023-05-09 16:58:07 +00:00
|
|
|
use assetpath::AssetPath;
|
2023-05-09 14:39:53 +00:00
|
|
|
use engine::prelude::{cgmath::vec3, *};
|
2023-05-08 18:56:50 +00:00
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
use crate::{
|
|
|
|
board::{Board, BoardSlot, BoardSlotState},
|
|
|
|
objects::Objects,
|
|
|
|
simple_ai::SimpleAI,
|
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
|
|
|
pub enum StoneState {
|
|
|
|
ReadyToBePlaced,
|
|
|
|
Placed,
|
|
|
|
Dead,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Stone {
|
|
|
|
pub stone: Entity,
|
|
|
|
pub state: StoneState,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
|
|
|
pub enum GameState {
|
2023-05-11 12:00:24 +00:00
|
|
|
Waiting,
|
2023-05-09 16:58:07 +00:00
|
|
|
Placing,
|
2023-05-10 05:47:10 +00:00
|
|
|
Removing,
|
2023-05-09 16:58:07 +00:00
|
|
|
Main,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
|
|
|
pub enum PlayerColor {
|
|
|
|
White,
|
|
|
|
Black,
|
|
|
|
}
|
|
|
|
|
2023-05-10 05:47:10 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
|
|
|
pub enum MillState {
|
|
|
|
White,
|
|
|
|
Black,
|
|
|
|
None,
|
|
|
|
}
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
impl PlayerColor {
|
|
|
|
pub fn swap(&mut self) {
|
|
|
|
*self = match *self {
|
2023-05-10 05:47:10 +00:00
|
|
|
PlayerColor::White => {
|
|
|
|
MillGame::log("swapped to black player");
|
|
|
|
|
|
|
|
PlayerColor::Black
|
|
|
|
}
|
|
|
|
PlayerColor::Black => {
|
|
|
|
MillGame::log("swapped to white player");
|
|
|
|
|
|
|
|
PlayerColor::White
|
|
|
|
}
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-11 12:00:24 +00:00
|
|
|
|
|
|
|
pub fn other(&self) -> Self {
|
|
|
|
match self {
|
|
|
|
PlayerColor::White => PlayerColor::Black,
|
|
|
|
PlayerColor::Black => PlayerColor::White,
|
|
|
|
}
|
|
|
|
}
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
2023-05-08 18:56:50 +00:00
|
|
|
|
|
|
|
pub struct MillGame {
|
2023-05-09 16:58:07 +00:00
|
|
|
board: Arc<Mutex<Board>>,
|
|
|
|
white_stones: Mutex<[Stone; 9]>,
|
|
|
|
black_stones: Mutex<[Stone; 9]>,
|
|
|
|
|
|
|
|
state: Mutex<GameState>,
|
|
|
|
current_player: Mutex<PlayerColor>,
|
2023-05-09 14:39:53 +00:00
|
|
|
|
|
|
|
scene: Mutex<SceneHandle>,
|
|
|
|
camera_controls: Mutex<CameraControl>,
|
|
|
|
|
|
|
|
mouse_x: AtomicU32,
|
|
|
|
mouse_y: AtomicU32,
|
2023-05-09 16:58:07 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
turn_finished: AtomicBool,
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
selected_field: Mutex<Option<(usize, usize, usize)>>,
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
simple_ai: SimpleAI,
|
|
|
|
|
|
|
|
white_player_label: Arc<Label>,
|
|
|
|
black_player_label: Arc<Label>,
|
|
|
|
start_button: Arc<Button>,
|
|
|
|
grid: Arc<Grid>,
|
|
|
|
|
|
|
|
gui: Arc<GuiBuilder>,
|
2023-05-08 18:56:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MillGame {
|
2023-05-09 16:58:07 +00:00
|
|
|
pub const OFFSET_TO_BOARD: f32 = 0.02;
|
2023-05-09 14:39:53 +00:00
|
|
|
|
2023-05-08 18:56:50 +00:00
|
|
|
pub fn new(engine: Arc<Engine>) -> Result<Arc<Self>> {
|
|
|
|
let mut scene = SceneHandle::new(&engine)?;
|
2023-05-09 14:39:53 +00:00
|
|
|
let board = Board::new(&engine, &mut scene)?;
|
|
|
|
|
|
|
|
let mut white_stones = None;
|
|
|
|
let mut black_stones = None;
|
|
|
|
|
|
|
|
scene.on_scene(|scene| {
|
|
|
|
let view = scene.view_mut();
|
|
|
|
|
|
|
|
view.camera_mut().set_center(board.center());
|
|
|
|
view.update_buffer()?;
|
|
|
|
|
|
|
|
// add light
|
|
|
|
let mut sun_light = engine.new_directional_light()?;
|
|
|
|
sun_light.set_direction(vec3(10.0, -4.0, -20.0))?;
|
|
|
|
sun_light.set_color(vec3(1.0, 1.0, 1.0))?;
|
|
|
|
sun_light.set_position(vec3(0.0, 0.0, 100.0))?;
|
|
|
|
sun_light.set_power(10000000000.0)?;
|
|
|
|
|
|
|
|
scene.add_light(sun_light)?;
|
|
|
|
|
|
|
|
white_stones = Some(
|
|
|
|
Self::init_nine_stones(&engine, Color::White)?
|
|
|
|
.into_iter()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(index, mut e)| {
|
|
|
|
let location = e.get_component_mut::<Location>()?;
|
|
|
|
location.set_position(board.white_start_slots()[index].extend(0.0));
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
Ok(Stone {
|
|
|
|
stone: scene.add_entity(e)?,
|
|
|
|
state: StoneState::ReadyToBePlaced,
|
|
|
|
})
|
2023-05-09 14:39:53 +00:00
|
|
|
})
|
2023-05-09 16:58:07 +00:00
|
|
|
.collect::<Result<Vec<Stone>>>()?
|
2023-05-09 14:39:53 +00:00
|
|
|
.try_into()
|
2023-05-09 16:58:07 +00:00
|
|
|
.unwrap_or_else(|_: Vec<Stone>| {
|
2023-05-09 14:39:53 +00:00
|
|
|
unreachable!("create array from vec from an array")
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
black_stones = Some(
|
2023-05-09 16:58:07 +00:00
|
|
|
Self::init_nine_stones(&engine, Color::try_from("#2c2c2c")?)?
|
2023-05-09 14:39:53 +00:00
|
|
|
.into_iter()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(index, mut e)| {
|
|
|
|
let location = e.get_component_mut::<Location>()?;
|
|
|
|
location.set_position(board.black_start_slots()[index].extend(0.0));
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
Ok(Stone {
|
|
|
|
stone: scene.add_entity(e)?,
|
|
|
|
state: StoneState::ReadyToBePlaced,
|
|
|
|
})
|
2023-05-09 14:39:53 +00:00
|
|
|
})
|
2023-05-09 16:58:07 +00:00
|
|
|
.collect::<Result<Vec<Stone>>>()?
|
2023-05-09 14:39:53 +00:00
|
|
|
.try_into()
|
2023-05-09 16:58:07 +00:00
|
|
|
.unwrap_or_else(|_: Vec<Stone>| {
|
2023-05-09 14:39:53 +00:00
|
|
|
unreachable!("create array from vec from an array")
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
let mut camera_control = CameraControl::new(&mut scene)?;
|
|
|
|
camera_control.set_zoom_levels(
|
|
|
|
(3..60).into_iter().rev().map(|z| z as f32).collect(),
|
|
|
|
5,
|
|
|
|
&mut scene,
|
|
|
|
)?;
|
2023-05-08 18:56:50 +00:00
|
|
|
|
|
|
|
scene.activate()?;
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
let gui = GuiBuilder::new(
|
|
|
|
engine.gui_handler(),
|
|
|
|
&AssetPath::from((
|
|
|
|
engine.settings().resource_base_path.as_str(),
|
|
|
|
"mainmenu.xml",
|
|
|
|
)),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let grid: Arc<Grid> = gui.element("grid")?;
|
|
|
|
let white_player_label: Arc<Label> = gui.element("white_player_label")?;
|
|
|
|
let black_player_label: Arc<Label> = gui.element("black_player_label")?;
|
|
|
|
let start_button: Arc<Button> = gui.element("start")?;
|
|
|
|
|
|
|
|
gui.enable()?;
|
|
|
|
|
|
|
|
let me = Arc::new(Self {
|
|
|
|
board: Arc::new(Mutex::new(board)),
|
|
|
|
white_stones: Mutex::new(white_stones.unwrap()),
|
|
|
|
black_stones: Mutex::new(black_stones.unwrap()),
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
state: Mutex::new(GameState::Waiting),
|
2023-05-09 16:58:07 +00:00
|
|
|
current_player: Mutex::new(PlayerColor::White),
|
2023-05-09 14:39:53 +00:00
|
|
|
|
|
|
|
scene: Mutex::new(scene),
|
|
|
|
camera_controls: Mutex::new(camera_control),
|
|
|
|
|
|
|
|
mouse_x: AtomicU32::new(0),
|
|
|
|
mouse_y: AtomicU32::new(0),
|
2023-05-09 16:58:07 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
turn_finished: AtomicBool::new(false),
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
selected_field: Mutex::default(),
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
simple_ai: SimpleAI::new(PlayerColor::White),
|
|
|
|
|
|
|
|
grid: grid.clone(),
|
|
|
|
white_player_label,
|
|
|
|
black_player_label,
|
|
|
|
start_button: start_button.clone(),
|
|
|
|
|
|
|
|
gui,
|
|
|
|
});
|
|
|
|
|
|
|
|
start_button.set_callback({
|
|
|
|
let weak_grid = Arc::downgrade(&grid);
|
|
|
|
let weak_self = Arc::downgrade(&me);
|
|
|
|
|
|
|
|
move || {
|
|
|
|
if let Some(grid) = weak_grid.upgrade() {
|
|
|
|
grid.detach(1, 0)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(me) = weak_self.upgrade() {
|
2023-05-10 05:47:10 +00:00
|
|
|
Self::log("start game");
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
*me.state.lock().unwrap() = GameState::Placing;
|
2023-05-10 05:47:10 +00:00
|
|
|
*me.current_player.lock().unwrap() = PlayerColor::Black;
|
2023-05-09 16:58:07 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
me.turn_finished.store(true, SeqCst);
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(me)
|
|
|
|
}
|
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
pub fn finish_turn(&self) {
|
|
|
|
Self::log(&format!(
|
2023-05-11 12:00:24 +00:00
|
|
|
"{:?} finished turn",
|
2023-05-10 08:22:46 +00:00
|
|
|
*self.current_player.lock().unwrap()
|
|
|
|
));
|
|
|
|
|
|
|
|
self.turn_finished.store(true, SeqCst);
|
|
|
|
}
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
fn next_game_step(&self) -> Result<()> {
|
|
|
|
{
|
|
|
|
let mut state = self.state.lock().unwrap();
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
Self::log(&format!(" ===== NEW TURN ({:?}) =====", state));
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
match *state {
|
|
|
|
GameState::Placing => {
|
|
|
|
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)
|
|
|
|
{
|
2023-05-10 05:47:10 +00:00
|
|
|
Self::log("change state to Main");
|
2023-05-09 16:58:07 +00:00
|
|
|
*state = GameState::Main;
|
|
|
|
}
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
self.current_player.lock().unwrap().swap();
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
|
|
|
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 => {
|
2023-05-10 08:22:46 +00:00
|
|
|
self.current_player.lock().unwrap().swap();
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
2023-05-11 12:00:24 +00:00
|
|
|
GameState::Waiting => unreachable!(),
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 05:47:10 +00:00
|
|
|
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)?;
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
2023-05-10 05:47:10 +00:00
|
|
|
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");
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
if !self.simple_ai.step(
|
2023-05-10 05:47:10 +00:00
|
|
|
&mut self.stones(self.simple_ai.player_color),
|
|
|
|
self.board.lock().unwrap().slots(),
|
|
|
|
&mut *self.scene.lock().unwrap(),
|
2023-05-11 12:00:24 +00:00
|
|
|
&mut *self.state.lock().unwrap(),
|
|
|
|
)? {
|
|
|
|
self.finish_turn();
|
|
|
|
}
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
Self::log("leave next_game_step");
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
Ok(())
|
2023-05-08 18:56:50 +00:00
|
|
|
}
|
2023-05-09 14:39:53 +00:00
|
|
|
|
2023-05-10 05:47:10 +00:00
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-09 14:39:53 +00:00
|
|
|
fn init_nine_stones(engine: &Arc<Engine>, color: Color) -> Result<[EntityObject; 9]> {
|
|
|
|
Ok((0..9)
|
|
|
|
.map(|_| Self::init_stone(engine, color))
|
|
|
|
.collect::<Result<Vec<EntityObject>>>()?
|
|
|
|
.try_into()
|
|
|
|
.unwrap_or_else(|_: Vec<EntityObject>| {
|
|
|
|
unreachable!("create array from vec from an array")
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn init_stone(engine: &Arc<Engine>, color: Color) -> Result<EntityObject> {
|
|
|
|
let mut marker = engine.assets().empty_entity();
|
|
|
|
|
|
|
|
let draw = Draw::new(vec![{
|
|
|
|
let mut mesh = AssetMesh::new(
|
|
|
|
engine.device(),
|
|
|
|
engine.settings().graphics_info()?.render_type,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let vertex_buffer = Buffer::builder()
|
2023-05-11 12:00:24 +00:00
|
|
|
.set_data(&Objects::create_cylinder(1.1, 0.25, 30))
|
2023-05-09 14:39:53 +00:00
|
|
|
.set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)
|
|
|
|
.set_memory_usage(MemoryUsage::CpuOnly)
|
|
|
|
.build(engine.device().clone())?;
|
|
|
|
|
|
|
|
let a: [f32; 3] = color.into();
|
|
|
|
|
|
|
|
mesh.add_primitive(
|
|
|
|
vertex_buffer,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
PrimitiveMaterial {
|
|
|
|
color: [a[0], a[1], a[2], 1.0],
|
|
|
|
metallic_factor: 0.2,
|
|
|
|
emissive_factor: [0.2, 0.2, 0.2],
|
|
|
|
roughness_factor: 0.8,
|
|
|
|
alpha_mode: AlphaMode::Opaque,
|
|
|
|
alpha_cut_off: 0.5,
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
mesh
|
|
|
|
}]);
|
|
|
|
marker.insert_component(draw);
|
|
|
|
|
|
|
|
let location = Location::from_entity(&mut marker);
|
|
|
|
marker.insert_component(location);
|
|
|
|
|
|
|
|
Ok(marker)
|
|
|
|
}
|
2023-05-09 16:58:07 +00:00
|
|
|
|
|
|
|
pub fn place_stone(
|
|
|
|
stone: &mut Stone,
|
2023-05-11 12:00:24 +00:00
|
|
|
(x, y, z): (usize, usize, usize),
|
|
|
|
board: &mut [[[BoardSlot; 3]; 3]; 3],
|
2023-05-09 16:58:07 +00:00
|
|
|
player_color: PlayerColor,
|
|
|
|
scene: &mut Scene,
|
2023-05-11 12:00:24 +00:00
|
|
|
state: &mut GameState,
|
|
|
|
) -> Result<MillState> {
|
2023-05-10 05:47:10 +00:00
|
|
|
Self::log("place_stone");
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
let slot = &board[x][y][z];
|
|
|
|
|
2023-05-09 16:58:07 +00:00
|
|
|
match player_color {
|
2023-05-10 05:47:10 +00:00
|
|
|
PlayerColor::White => {
|
|
|
|
Self::log(&format!("place white stone"));
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
slot.set_state(BoardSlotState::White(stone.stone));
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
|
|
|
PlayerColor::Black => {
|
|
|
|
Self::log(&format!("place black stone"));
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
slot.set_state(BoardSlotState::Black(stone.stone));
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
stone.state = StoneState::Placed;
|
|
|
|
|
|
|
|
let entity = scene.entity_mut(stone.stone)?;
|
|
|
|
|
|
|
|
let location = entity.get_component_mut::<Location>()?;
|
|
|
|
location.set_position(slot.position.extend(0.0));
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
let millstate = Self::check_for_mill(&board[x][y][z], board);
|
|
|
|
|
|
|
|
if millstate != MillState::None {
|
|
|
|
*state = GameState::Removing;
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::log(&format!("return place stone with {:?}", millstate));
|
|
|
|
|
|
|
|
Ok(millstate)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn move_stone(
|
|
|
|
(x, y, z): (usize, usize, usize),
|
|
|
|
(tx, ty, tz): (usize, usize, usize),
|
|
|
|
scene: &mut Scene,
|
|
|
|
board: &mut [[[BoardSlot; 3]; 3]; 3],
|
|
|
|
state: &mut GameState,
|
|
|
|
) -> Result<MillState> {
|
|
|
|
Self::log(&format!(
|
|
|
|
"move stone ({:?}) to ({:?})",
|
|
|
|
(x, y, z),
|
|
|
|
(tx, ty, tz)
|
|
|
|
));
|
|
|
|
|
|
|
|
let slot = &board[x][y][z];
|
|
|
|
let neighbour = &board[tx][ty][tz];
|
|
|
|
|
|
|
|
if neighbour.state() != BoardSlotState::Empty {
|
|
|
|
Self::log("neighbour not empty");
|
|
|
|
return Ok(MillState::None);
|
|
|
|
}
|
|
|
|
|
|
|
|
neighbour.set_state(slot.state());
|
|
|
|
slot.set_state(BoardSlotState::Empty);
|
|
|
|
|
|
|
|
scene
|
|
|
|
.entity_mut(match neighbour.state() {
|
|
|
|
BoardSlotState::Black(e) => e,
|
|
|
|
BoardSlotState::White(e) => e,
|
|
|
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
})?
|
|
|
|
.get_component_mut::<Location>()?
|
|
|
|
.set_position(neighbour.position.extend(0.0));
|
|
|
|
|
|
|
|
let millstate = Self::check_for_mill(&board[tx][ty][tz], board);
|
|
|
|
|
|
|
|
if millstate != MillState::None {
|
|
|
|
Self::log("mill found!");
|
|
|
|
*state = GameState::Removing;
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::log(&format!("return move stone with {:?}", millstate));
|
|
|
|
|
|
|
|
Ok(millstate)
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
pub fn remove_stone(stone: &mut Stone, slot: &mut BoardSlot, scene: &mut Scene) -> Result<()> {
|
2023-05-10 05:47:10 +00:00
|
|
|
Self::log("remove_stone");
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
let entity = match slot.state() {
|
2023-05-10 05:47:10 +00:00
|
|
|
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)?;
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
assert_ne!(stone.state, StoneState::Dead);
|
|
|
|
stone.state = StoneState::Dead;
|
|
|
|
|
|
|
|
slot.set_state(BoardSlotState::Empty);
|
2023-05-10 05:47:10 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
pub fn check_for_mill(slot: &BoardSlot, board: &[[[BoardSlot; 3]; 3]; 3]) -> MillState {
|
2023-05-10 05:47:10 +00:00
|
|
|
Self::log("check for mill");
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
if !(slot.x == 0 && slot.y == 0)
|
|
|
|
&& !(slot.x == 2 && slot.y == 0)
|
|
|
|
&& !(slot.x == 0 && slot.y == 2)
|
|
|
|
&& !(slot.x == 2 && slot.y == 2)
|
|
|
|
{
|
|
|
|
let state = Self::check_mill(
|
|
|
|
&board[slot.x][slot.y][0],
|
|
|
|
&board[slot.x][slot.y][1],
|
|
|
|
&board[slot.x][slot.y][2],
|
|
|
|
);
|
2023-05-09 16:58:07 +00:00
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
if state != MillState::None {
|
|
|
|
Self::log(&format!("mill found {:?}", state));
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
return state;
|
|
|
|
}
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
let state = Self::check_mill(
|
|
|
|
&board[slot.x][0][slot.z],
|
|
|
|
&board[slot.x][1][slot.z],
|
|
|
|
&board[slot.x][2][slot.z],
|
|
|
|
);
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
if state != MillState::None {
|
|
|
|
Self::log(&format!("mill found {:?}", state));
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
return state;
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
let state = Self::check_mill(
|
|
|
|
&board[0][slot.y][slot.z],
|
|
|
|
&board[1][slot.y][slot.z],
|
|
|
|
&board[2][slot.y][slot.z],
|
|
|
|
);
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
if state != MillState::None {
|
|
|
|
Self::log(&format!("mill found {:?}", state));
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
return state;
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MillState::None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_mouse_click<F>(&self, f: F) -> Result<()>
|
|
|
|
where
|
2023-05-10 08:22:46 +00:00
|
|
|
F: FnOnce(
|
|
|
|
&Self,
|
|
|
|
(usize, usize, usize),
|
|
|
|
&mut [[[BoardSlot; 3]; 3]; 3],
|
|
|
|
&mut Scene,
|
|
|
|
) -> Result<()>,
|
2023-05-10 05:47:10 +00:00
|
|
|
{
|
|
|
|
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))?
|
|
|
|
{
|
2023-05-10 08:22:46 +00:00
|
|
|
let mut board = self.board.lock().unwrap();
|
|
|
|
|
|
|
|
if let Some(slot) = board.close_to_marker(world_space.truncate()) {
|
2023-05-10 05:47:10 +00:00
|
|
|
Self::log("click is close to marker");
|
|
|
|
|
2023-05-10 08:22:46 +00:00
|
|
|
f(self, (slot.x, slot.y, slot.z), board.slots(), scene)?;
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
fn is_neighbour((x, y, z): (usize, usize, usize), (tx, ty, tz): (usize, usize, usize)) -> bool {
|
|
|
|
if (x == tx && y == ty)
|
|
|
|
&& ((x == 0 && y == 0)
|
|
|
|
|| (x == 2 && y == 0)
|
|
|
|
|| (x == 0 && y == 2)
|
|
|
|
|| (x == 2 && y == 2))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
(x as i32 - tx as i32).abs() == 1
|
|
|
|
|| (y as i32 - ty as i32).abs() == 1
|
|
|
|
|| (z as i32 - tz as i32).abs() == 1
|
|
|
|
}
|
|
|
|
|
2023-05-10 05:47:10 +00:00
|
|
|
pub fn log(s: &str) {
|
|
|
|
println!("{}", s);
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
2023-05-08 18:56:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EngineObject for MillGame {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"MillGame"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update(&self) -> Result<()> {
|
2023-05-10 08:22:46 +00:00
|
|
|
if self.turn_finished.load(SeqCst) {
|
|
|
|
self.turn_finished.store(false, SeqCst);
|
|
|
|
self.next_game_step()?;
|
|
|
|
}
|
|
|
|
|
2023-05-08 18:56:50 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn event(&self, event: EngineEvent) -> Result<()> {
|
|
|
|
match event {
|
2023-05-09 14:39:53 +00:00
|
|
|
EngineEvent::MouseMotion(x, y) => {
|
|
|
|
self.mouse_x.store(x, SeqCst);
|
|
|
|
self.mouse_y.store(y, SeqCst);
|
|
|
|
|
|
|
|
self.camera_controls.lock().unwrap().mouse_move(
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
&mut *self.scene.lock().unwrap(),
|
|
|
|
)?;
|
|
|
|
}
|
2023-05-11 12:00:24 +00:00
|
|
|
EngineEvent::MouseButtonDown(button) => {
|
|
|
|
match button {
|
|
|
|
MouseButton::Left => {
|
|
|
|
let mut state = self.state.lock().unwrap();
|
|
|
|
Self::log(&format!("User click at state {:?}", state));
|
|
|
|
|
|
|
|
match *state {
|
|
|
|
GameState::Placing => {
|
|
|
|
let mut placed = false;
|
|
|
|
let mut mill_found = false;
|
|
|
|
|
|
|
|
self.check_mouse_click(|me, (x, y, z), board, scene| {
|
|
|
|
let current_player = *self.current_player.lock().unwrap();
|
|
|
|
|
|
|
|
if let Some(placable) = self
|
|
|
|
.stones(current_player)
|
|
|
|
.iter_mut()
|
|
|
|
.find(|stone| stone.state == StoneState::ReadyToBePlaced)
|
2023-05-10 08:22:46 +00:00
|
|
|
{
|
2023-05-11 12:00:24 +00:00
|
|
|
if board[x][y][z].is_empty() {
|
|
|
|
if Self::place_stone(
|
|
|
|
placable,
|
|
|
|
(x, y, z),
|
|
|
|
board,
|
|
|
|
current_player,
|
|
|
|
scene,
|
|
|
|
&mut *state,
|
|
|
|
)? != MillState::None
|
|
|
|
{
|
|
|
|
mill_found = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
placed = true;
|
|
|
|
} else {
|
|
|
|
Self::log(&format!(
|
|
|
|
"slot ({:?}), not empty ({:?})",
|
|
|
|
(x, y, z),
|
|
|
|
board[x][y][z].state()
|
|
|
|
));
|
|
|
|
}
|
2023-05-10 08:22:46 +00:00
|
|
|
}
|
2023-05-09 16:58:07 +00:00
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
|
|
|
|
if placed && !mill_found {
|
|
|
|
self.finish_turn();
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
2023-05-11 12:00:24 +00:00
|
|
|
}
|
|
|
|
GameState::Removing => {
|
|
|
|
let mut removed = false;
|
|
|
|
|
|
|
|
self.check_mouse_click(|me, (x, y, z), board, scene| {
|
|
|
|
let slot = &mut board[x][y][z];
|
|
|
|
|
|
|
|
Self::log(&format!(
|
|
|
|
"state {:?} - player {:?}",
|
|
|
|
slot.state(),
|
|
|
|
*self.current_player.lock().unwrap()
|
|
|
|
));
|
|
|
|
|
|
|
|
if match (slot.state(), *self.current_player.lock().unwrap()) {
|
|
|
|
(BoardSlotState::Black(_), PlayerColor::White) => true,
|
|
|
|
(BoardSlotState::White(_), PlayerColor::Black) => true,
|
|
|
|
_ => false,
|
|
|
|
} {
|
|
|
|
let mut stones = self
|
|
|
|
.stones(self.current_player.lock().unwrap().other());
|
|
|
|
|
|
|
|
let stone = stones
|
|
|
|
.iter_mut()
|
|
|
|
.find(|s| {
|
|
|
|
s.stone
|
|
|
|
== match slot.state() {
|
|
|
|
BoardSlotState::Black(e) => e,
|
|
|
|
BoardSlotState::White(e) => e,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
Self::remove_stone(stone, slot, scene)?;
|
|
|
|
|
|
|
|
removed = true;
|
|
|
|
}
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
Ok(())
|
|
|
|
})?;
|
2023-05-10 05:47:10 +00:00
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
if removed {
|
|
|
|
self.finish_turn();
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
2023-05-11 12:00:24 +00:00
|
|
|
}
|
|
|
|
GameState::Main => {
|
|
|
|
self.check_mouse_click(|me, (tx, ty, tz), board, scene| {
|
|
|
|
let mut selected_slot = me.selected_field.lock().unwrap();
|
|
|
|
|
|
|
|
match *selected_slot {
|
|
|
|
Some((x, y, z)) => {
|
|
|
|
if Self::is_neighbour((x, y, z), (tx, ty, tz)) {
|
|
|
|
if Self::move_stone((x,y,z), (tx,ty,tz), scene, board, &mut state)? == MillState::None {
|
|
|
|
*selected_slot = None;
|
|
|
|
self.finish_turn();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
let slot = &board[tx][ty][tz];
|
|
|
|
|
|
|
|
match (
|
|
|
|
slot.state(),
|
|
|
|
*me.current_player.lock().unwrap(),
|
|
|
|
) {
|
|
|
|
(BoardSlotState::Black(_), PlayerColor::Black)
|
|
|
|
| (BoardSlotState::White(_), PlayerColor::White) => {
|
|
|
|
*selected_slot = Some((tx, ty, tz));
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-09 14:39:53 +00:00
|
|
|
|
2023-05-11 12:00:24 +00:00
|
|
|
Ok(())
|
|
|
|
})?;
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
2023-05-11 12:00:24 +00:00
|
|
|
GameState::Waiting => (),
|
2023-05-10 05:47:10 +00:00
|
|
|
}
|
2023-05-09 16:58:07 +00:00
|
|
|
}
|
2023-05-11 12:00:24 +00:00
|
|
|
MouseButton::Middle => self.camera_controls.lock().unwrap().hold(),
|
|
|
|
MouseButton::Right => (),
|
|
|
|
MouseButton::Forward => (),
|
|
|
|
MouseButton::Backward => (),
|
2023-05-09 14:39:53 +00:00
|
|
|
}
|
2023-05-11 12:00:24 +00:00
|
|
|
}
|
2023-05-09 14:39:53 +00:00
|
|
|
EngineEvent::MouseButtonUp(button) => match button {
|
|
|
|
MouseButton::Left => (),
|
|
|
|
MouseButton::Middle => self.camera_controls.lock().unwrap().release(),
|
|
|
|
MouseButton::Right => (),
|
|
|
|
MouseButton::Forward => (),
|
|
|
|
MouseButton::Backward => (),
|
|
|
|
},
|
|
|
|
EngineEvent::MouseWheel(_x, y, direction) => self
|
|
|
|
.camera_controls
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.scroll_wheel(y, direction, &mut *self.scene.lock().unwrap())?,
|
|
|
|
|
2023-05-08 19:24:12 +00:00
|
|
|
EngineEvent::KeyDown(_) => (),
|
|
|
|
EngineEvent::KeyUp(_) => (),
|
|
|
|
EngineEvent::ButtonDown(_) => (),
|
|
|
|
EngineEvent::ButtonUp(_) => (),
|
|
|
|
EngineEvent::ControllerAxis(_) => (),
|
|
|
|
EngineEvent::ControllerAdded(_) => (),
|
|
|
|
EngineEvent::ControllerRemoved(_) => (),
|
|
|
|
EngineEvent::FileDrop(_) => (),
|
2023-05-08 18:56:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|