Implement major part of main game

This commit is contained in:
hodasemi 2023-05-11 14:00:24 +02:00
parent 2720750829
commit a83c3a4ab6
4 changed files with 435 additions and 190 deletions

26
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,26 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'mill_game'",
"cargo": {
"args": [
"build",
"--bin=mill_game",
"--package=mill_game"
],
"filter": {
"name": "mill_game",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

View file

@ -14,7 +14,7 @@ pub struct BoardSlot {
pub y: usize,
pub z: usize,
pub state: Mutex<BoardSlotState>,
state: Mutex<BoardSlotState>,
pub position: Vector2<f32>,
pub slot_marker: Option<Entity>,
}
@ -100,10 +100,22 @@ impl BoardSlot {
Ok(mesh)
}
pub fn state(&self) -> BoardSlotState {
*self.state.lock().unwrap()
}
pub fn set_state(&self, state: BoardSlotState) {
*self.state.lock().unwrap() = state;
}
pub fn valid(&self) -> bool {
*self.state.lock().unwrap() != BoardSlotState::Invalid
}
pub fn is_empty(&self) -> bool {
*self.state.lock().unwrap() == BoardSlotState::Empty
}
pub fn white(&self) -> bool {
match *self.state.lock().unwrap() {
BoardSlotState::White(_) => true,
@ -290,6 +302,75 @@ impl Board {
None
}
pub fn get_neighbours<'a>(
slot: &BoardSlot,
board: &'a [[[BoardSlot; 3]; 3]; 3],
) -> Vec<&'a BoardSlot> {
let mut neighbours = Vec::new();
for i in 0..6 {
// find free neighbour field
if let Some(neighbour) = match i {
0 => {
if slot.x > 0 {
Some(&board[slot.x - 1][slot.y][slot.z])
} else {
None
}
}
1 => {
if slot.x < 2 {
Some(&board[slot.x + 1][slot.y][slot.z])
} else {
None
}
}
2 => {
if slot.y > 0 {
Some(&board[slot.x][slot.y - 1][slot.z])
} else {
None
}
}
3 => {
if slot.y < 2 {
Some(&board[slot.x][slot.y + 1][slot.z])
} else {
None
}
}
4 => {
if slot.z > 0 {
Some(&board[slot.x][slot.y][slot.z - 1])
} else {
None
}
}
5 => {
if slot.z < 2 {
Some(&board[slot.x][slot.y][slot.z + 1])
} else {
None
}
}
_ => unreachable!(),
} {
let neighbour_state = neighbour.state.lock().unwrap();
match *neighbour_state {
BoardSlotState::Empty => {
neighbours.push(neighbour);
}
_ => (),
}
}
}
neighbours
}
}
impl Board {

View file

@ -27,6 +27,7 @@ pub struct Stone {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum GameState {
Waiting,
Placing,
Removing,
Main,
@ -60,6 +61,13 @@ impl PlayerColor {
}
}
}
pub fn other(&self) -> Self {
match self {
PlayerColor::White => PlayerColor::Black,
PlayerColor::Black => PlayerColor::White,
}
}
}
pub struct MillGame {
@ -78,6 +86,8 @@ pub struct MillGame {
turn_finished: AtomicBool,
selected_field: Mutex<Option<(usize, usize, usize)>>,
simple_ai: SimpleAI,
white_player_label: Arc<Label>,
@ -185,7 +195,7 @@ impl MillGame {
white_stones: Mutex::new(white_stones.unwrap()),
black_stones: Mutex::new(black_stones.unwrap()),
state: Mutex::new(GameState::Placing),
state: Mutex::new(GameState::Waiting),
current_player: Mutex::new(PlayerColor::White),
scene: Mutex::new(scene),
@ -196,6 +206,8 @@ impl MillGame {
turn_finished: AtomicBool::new(false),
selected_field: Mutex::default(),
simple_ai: SimpleAI::new(PlayerColor::White),
grid: grid.clone(),
@ -233,7 +245,7 @@ impl MillGame {
pub fn finish_turn(&self) {
Self::log(&format!(
"{:?} finised turn",
"{:?} finished turn",
*self.current_player.lock().unwrap()
));
@ -241,12 +253,10 @@ impl MillGame {
}
fn next_game_step(&self) -> Result<()> {
Self::log("next_game_step");
{
let mut state = self.state.lock().unwrap();
Self::log(&format!("\tstate: {:?}", state));
Self::log(&format!(" ===== NEW TURN ({:?}) =====", state));
match *state {
GameState::Placing => {
@ -295,6 +305,7 @@ impl MillGame {
GameState::Main => {
self.current_player.lock().unwrap().swap();
}
GameState::Waiting => unreachable!(),
}
}
@ -318,15 +329,15 @@ impl MillGame {
if player == self.simple_ai.player_color {
Self::log("current player is AI");
self.simple_ai.step(
if !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(),
)?;
&mut *self.state.lock().unwrap(),
)? {
self.finish_turn();
}
}
Self::log("leave next_game_step");
@ -360,7 +371,7 @@ impl MillGame {
)?;
let vertex_buffer = Buffer::builder()
.set_data(&Objects::create_cylinder(1.0, 0.25, 12))
.set_data(&Objects::create_cylinder(1.1, 0.25, 30))
.set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)
.set_memory_usage(MemoryUsage::CpuOnly)
.build(engine.device().clone())?;
@ -394,22 +405,26 @@ impl MillGame {
pub fn place_stone(
stone: &mut Stone,
slot: &mut BoardSlot,
(x, y, z): (usize, usize, usize),
board: &mut [[[BoardSlot; 3]; 3]; 3],
player_color: PlayerColor,
scene: &mut Scene,
) -> Result<()> {
state: &mut GameState,
) -> Result<MillState> {
Self::log("place_stone");
let slot = &board[x][y][z];
match player_color {
PlayerColor::White => {
Self::log(&format!("place white stone"));
*slot.state.lock().unwrap() = BoardSlotState::White(stone.stone);
slot.set_state(BoardSlotState::White(stone.stone));
}
PlayerColor::Black => {
Self::log(&format!("place black stone"));
*slot.state.lock().unwrap() = BoardSlotState::Black(stone.stone);
slot.set_state(BoardSlotState::Black(stone.stone));
}
}
@ -420,13 +435,67 @@ impl MillGame {
let location = entity.get_component_mut::<Location>()?;
location.set_position(slot.position.extend(0.0));
Ok(())
let millstate = Self::check_for_mill(&board[x][y][z], board);
if millstate != MillState::None {
*state = GameState::Removing;
}
pub fn remove_stone(slot: &mut BoardSlot, scene: &mut Scene) -> Result<()> {
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)
}
pub fn remove_stone(stone: &mut Stone, slot: &mut BoardSlot, scene: &mut Scene) -> Result<()> {
Self::log("remove_stone");
let entity = match *slot.state.lock().unwrap() {
let entity = match slot.state() {
BoardSlotState::Black(e) => {
Self::log(&format!("\tremove black stone"));
@ -441,9 +510,11 @@ impl MillGame {
};
scene.remove_entity(entity)?;
Self::log("\t -> TODO: set stone state to Dead");
*slot.state.lock().unwrap() = BoardSlotState::Empty;
assert_ne!(stone.state, StoneState::Dead);
stone.state = StoneState::Dead;
slot.set_state(BoardSlotState::Empty);
Ok(())
}
@ -460,9 +531,14 @@ impl MillGame {
MillState::None
}
pub fn check_for_mill(&self, slot: &BoardSlot, board: &[[[BoardSlot; 3]; 3]; 3]) -> MillState {
pub fn check_for_mill(slot: &BoardSlot, board: &[[[BoardSlot; 3]; 3]; 3]) -> MillState {
Self::log("check for mill");
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],
@ -474,6 +550,7 @@ impl MillGame {
return state;
}
}
let state = Self::check_mill(
&board[slot.x][0][slot.z],
@ -530,6 +607,21 @@ impl MillGame {
})
}
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
}
pub fn log(s: &str) {
println!("{}", s);
}
@ -561,7 +653,8 @@ impl EngineObject for MillGame {
&mut *self.scene.lock().unwrap(),
)?;
}
EngineEvent::MouseButtonDown(button) => match button {
EngineEvent::MouseButtonDown(button) => {
match button {
MouseButton::Left => {
let mut state = self.state.lock().unwrap();
Self::log(&format!("User click at state {:?}", state));
@ -579,20 +672,27 @@ impl EngineObject for MillGame {
.iter_mut()
.find(|stone| stone.state == StoneState::ReadyToBePlaced)
{
Self::place_stone(
if board[x][y][z].is_empty() {
if Self::place_stone(
placable,
&mut board[x][y][z],
(x, y, z),
board,
current_player,
scene,
)?;
if me.check_for_mill(&board[x][y][z], board) != MillState::None
&mut *state,
)? != MillState::None
{
mill_found = true;
*state = GameState::Removing;
}
placed = true;
} else {
Self::log(&format!(
"slot ({:?}), not empty ({:?})",
(x, y, z),
board[x][y][z].state()
));
}
}
Ok(())
@ -610,20 +710,33 @@ impl EngineObject for MillGame {
Self::log(&format!(
"state {:?} - player {:?}",
slot.state,
slot.state(),
*self.current_player.lock().unwrap()
));
if match (
*slot.state.lock().unwrap(),
*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;
Self::remove_stone(slot, scene)?;
}
Ok(())
@ -633,14 +746,48 @@ impl EngineObject for MillGame {
self.finish_turn();
}
}
GameState::Main => todo!(),
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));
}
_ => (),
}
}
}
Ok(())
})?;
}
GameState::Waiting => (),
}
}
MouseButton::Middle => self.camera_controls.lock().unwrap().hold(),
MouseButton::Right => (),
MouseButton::Forward => (),
MouseButton::Backward => (),
},
}
}
EngineEvent::MouseButtonUp(button) => match button {
MouseButton::Left => (),
MouseButton::Middle => self.camera_controls.lock().unwrap().release(),

View file

@ -1,6 +1,6 @@
use crate::{
board::{BoardSlot, BoardSlotState},
game::{GameState, MillGame, PlayerColor, Stone, StoneState},
board::{Board, BoardSlot, BoardSlotState},
game::{GameState, MillGame, MillState, PlayerColor, Stone, StoneState},
};
use anyhow::Result;
@ -20,29 +20,45 @@ impl SimpleAI {
stones: &mut [Stone; 9],
board: &mut [[[BoardSlot; 3]; 3]; 3],
scene: &mut SceneHandle,
state: GameState,
) -> Result<()> {
MillGame::log(&format!("AI at state {:?}", state));
state: &mut GameState,
) -> Result<bool> {
MillGame::log(&format!("AI at state {:?}", *state));
match state {
let mut found_a_mill = false;
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()
let (x, y, z) = {
let free_slots: Vec<&BoardSlot> = board
.iter()
.flatten()
.flatten()
.filter(|slot| *slot.state.lock().unwrap() == BoardSlotState::Empty)
.filter(|slot| slot.state() == BoardSlotState::Empty)
.collect();
let len = free_slots.len();
let slot = &mut free_slots[Random::range(0, len as u32) as usize];
let slot = &free_slots[Random::range(0, len as u32) as usize];
(slot.x, slot.y, slot.z)
};
scene.on_scene(|scene| {
MillGame::place_stone(placable, slot, self.player_color, scene)?;
if MillGame::place_stone(
placable,
(x, y, z),
board,
self.player_color,
scene,
state,
)? != MillState::None
{
found_a_mill = true;
}
Ok(())
})?;
@ -53,115 +69,90 @@ impl SimpleAI {
.iter_mut()
.flatten()
.flatten()
.filter(
|slot| match (*slot.state.lock().unwrap(), self.player_color) {
.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];
let slot: &mut &mut BoardSlot =
&mut other_color[Random::range(0, len as u32) as usize];
let stone = stones
.iter_mut()
.find(|s| {
s.stone
== match slot.state() {
BoardSlotState::Black(e) => e,
BoardSlotState::White(e) => e,
_ => unreachable!(),
}
})
.unwrap();
scene.on_scene(|scene| {
MillGame::remove_stone(slot, scene)?;
MillGame::remove_stone(stone, slot, scene)?;
Ok(())
})?;
}
GameState::Main => {
let mut same_color: Vec<&BoardSlot> = board
let same_color: Vec<&BoardSlot> = board
.iter()
.flatten()
.flatten()
.filter(
|slot| match (*slot.state.lock().unwrap(), self.player_color) {
.filter(|slot| match (slot.state(), self.player_color) {
(BoardSlotState::White(_), PlayerColor::White) => true,
(BoardSlotState::Black(_), PlayerColor::Black) => true,
_ => false,
},
)
})
.collect();
loop {
let len = same_color.len();
let slot = &mut same_color[Random::range(0, len as u32) as usize];
let slot = &same_color[Random::range(0, len as u32) as usize];
let n = Board::get_neighbours(slot, board);
let neighbours: Vec<&&BoardSlot> = n
.iter()
.filter(|n| n.state() == BoardSlotState::Empty)
.collect();
// find free neighbour field
if let Some(neighbour) = match Random::range(0, 6) as usize {
0 => {
if slot.x > 0 {
Some(&board[slot.x - 1][slot.y][slot.z])
} else {
None
}
}
1 => {
if slot.x < 2 {
Some(&board[slot.x + 1][slot.y][slot.z])
} else {
None
}
}
2 => {
if slot.y > 0 {
Some(&board[slot.x][slot.y - 1][slot.z])
} else {
None
}
}
3 => {
if slot.y < 2 {
Some(&board[slot.x][slot.y + 1][slot.z])
} else {
None
}
}
4 => {
if slot.z > 0 {
Some(&board[slot.x][slot.y][slot.z - 1])
} else {
None
}
}
5 => {
if slot.z < 2 {
Some(&board[slot.x][slot.y][slot.z + 1])
} else {
None
}
}
// 6 => ,
// 7 => ,
// 8 => ,
// 9 => ,
_ => unreachable!(),
} {
let mut neighbour_state = neighbour.state.lock().unwrap();
let mut slot_state = slot.state.lock().unwrap();
if !neighbours.is_empty() {
let neighbour =
&neighbours[Random::range(0, neighbours.len() as u32) as usize];
match (*neighbour_state, *slot_state) {
(BoardSlotState::Empty, BoardSlotState::Black(e)) => {
*neighbour_state = BoardSlotState::Black(e);
*slot_state = BoardSlotState::Empty;
}
(BoardSlotState::Empty, BoardSlotState::White(e)) => {
*neighbour_state = BoardSlotState::White(e);
*slot_state = BoardSlotState::Empty;
if neighbour.state() == BoardSlotState::Empty {
let (x, y, z) = (slot.x, slot.y, slot.z);
let (tx, ty, tz) = (neighbour.x, neighbour.y, neighbour.z);
scene.on_scene(|scene| {
if MillGame::move_stone(
(x, y, z),
(tx, ty, tz),
scene,
board,
state,
)? != MillState::None
{
found_a_mill = true;
}
_ => (),
Ok(())
})?;
break;
}
}
}
}
GameState::Waiting => unreachable!(),
}
MillGame::log("finish AI");
Ok(())
Ok(found_a_mill)
}
}