diff options
-rw-r--r-- | src/engine/bitwise_engine.rs | 12 | ||||
-rw-r--r-- | src/engine/expressive_engine.rs | 4 | ||||
-rw-r--r-- | src/engine/geometry.rs | 28 | ||||
-rw-r--r-- | src/engine/mod.rs | 4 | ||||
-rw-r--r-- | src/input/json.rs | 10 | ||||
-rw-r--r-- | tests/expressive_to_bitwise_comparison.rs | 156 |
6 files changed, 167 insertions, 47 deletions
diff --git a/src/engine/bitwise_engine.rs b/src/engine/bitwise_engine.rs index bb1dd76..85c4352 100644 --- a/src/engine/bitwise_engine.rs +++ b/src/engine/bitwise_engine.rs @@ -27,12 +27,10 @@ pub struct PlayerBuildings { pub buildings: [u64; DEFENCE_HEALTH], pub energy_towers: u64, - pub missile_towers: [u64; MISSILE_COOLDOWN], + pub missile_towers: [u64; MISSILE_COOLDOWN+1], pub missiles: [(u64, u64); MAP_WIDTH/4], - pub tesla_cooldowns: [TeslaCooldown; MAX_TESLAS], - - pub unoccupied: Vec<Point> + pub tesla_cooldowns: [TeslaCooldown; MAX_TESLAS] } #[derive(Debug, Clone, PartialEq, Eq)] @@ -50,6 +48,8 @@ pub struct TeslaCooldown { } +const EMPTY: [Point; 0] = []; + impl GameState for BitwiseGameState { fn simulate(&mut self, _settings: &GameSettings, _player_command: Command, _opponent_command: Command) -> GameStatus { //TODO @@ -61,8 +61,8 @@ impl GameState for BitwiseGameState { fn opponent(&self) -> &Player { &self.opponent } fn player_has_max_teslas(&self) -> bool { self.player_buildings.count_teslas() >= MAX_TESLAS } fn opponent_has_max_teslas(&self) -> bool { self.opponent_buildings.count_teslas() >= MAX_TESLAS } - fn unoccupied_player_cells(&self) -> &Vec<Point> { &self.player_buildings.unoccupied } - fn unoccupied_opponent_cells(&self) -> &Vec<Point> { &self.opponent_buildings.unoccupied } + fn unoccupied_player_cells(&self) -> &[Point] { &EMPTY } + fn unoccupied_opponent_cells(&self) -> &[Point] { &EMPTY } } impl PlayerBuildings { diff --git a/src/engine/expressive_engine.rs b/src/engine/expressive_engine.rs index f1255c3..0640d58 100644 --- a/src/engine/expressive_engine.rs +++ b/src/engine/expressive_engine.rs @@ -89,8 +89,8 @@ impl GameState for ExpressiveGameState { fn opponent(&self) -> &Player { &self.opponent } fn player_has_max_teslas(&self) -> bool { self.count_player_teslas() >= 2 } fn opponent_has_max_teslas(&self) -> bool { self.count_opponent_teslas() >= 2 } - fn unoccupied_player_cells(&self) -> &Vec<Point> { &self.unoccupied_player_cells } - fn unoccupied_opponent_cells(&self) -> &Vec<Point> { &self.unoccupied_opponent_cells } + fn unoccupied_player_cells(&self) -> &[Point] { &self.unoccupied_player_cells } + fn unoccupied_opponent_cells(&self) -> &[Point] { &self.unoccupied_opponent_cells } } impl ExpressiveGameState { diff --git a/src/engine/geometry.rs b/src/engine/geometry.rs index 44ce9fe..22b56f0 100644 --- a/src/engine/geometry.rs +++ b/src/engine/geometry.rs @@ -31,6 +31,34 @@ impl Point { pub fn wrapping_move_right(&mut self) { self.x = self.x.wrapping_add(1); } + + pub fn to_bitfield(&self, width: u8) -> (u64, u64) { + if self.x >= width { + let index = self.y * width + self.x - width; + (0, 1 << index) + } else { + let index = self.y * width + self.x; + (1 << index, 0) + } + } + + pub fn to_left_bitfield(&self, width: u8) -> u64 { + if self.x >= width { + 0 + } else { + let index = self.y * width + self.x; + 1 << index + } + } + + pub fn to_right_bitfield(&self, width: u8) -> u64 { + if self.x < width { + 0 + } else { + let index = self.y * width + self.x - width; + 1 << index + } + } } use std::cmp::Ord; diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 6e7c5c9..39a5f26 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -15,8 +15,8 @@ pub trait GameState: Clone + Sync { fn opponent(&self) -> &Player; fn player_has_max_teslas(&self) -> bool; fn opponent_has_max_teslas(&self) -> bool; - fn unoccupied_player_cells(&self) -> &Vec<Point>; - fn unoccupied_opponent_cells(&self) -> &Vec<Point>; + fn unoccupied_player_cells(&self) -> &[Point]; + fn unoccupied_opponent_cells(&self) -> &[Point]; } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/input/json.rs b/src/input/json.rs index fb88bf0..319c77a 100644 --- a/src/input/json.rs +++ b/src/input/json.rs @@ -32,7 +32,7 @@ pub fn read_bitwise_state_from_file(filename: &str) -> Result<bitwise_engine::Bi unconstructed: Vec::new(), buildings: [0,0,0,0], energy_towers: 0, - missile_towers: [0,0,0], + missile_towers: [0,0,0,0], missiles: [(0,0),(0,0),(0,0),(0,0)], tesla_cooldowns: [bitwise_engine::TeslaCooldown { active: false, @@ -42,14 +42,13 @@ pub fn read_bitwise_state_from_file(filename: &str) -> Result<bitwise_engine::Bi active: false, pos: engine::geometry::Point::new(0,0), cooldown: 0 - }], - unoccupied: Vec::new() + }] }, opponent_buildings: bitwise_engine::PlayerBuildings { unconstructed: Vec::new(), buildings: [0,0,0,0], energy_towers: 0, - missile_towers: [0,0,0], + missile_towers: [0,0,0,0], missiles: [(0,0),(0,0),(0,0),(0,0)], tesla_cooldowns: [bitwise_engine::TeslaCooldown { active: false, @@ -59,8 +58,7 @@ pub fn read_bitwise_state_from_file(filename: &str) -> Result<bitwise_engine::Bi active: false, pos: engine::geometry::Point::new(0,0), cooldown: 0 - }], - unoccupied: Vec::new() + }] } }) } diff --git a/tests/expressive_to_bitwise_comparison.rs b/tests/expressive_to_bitwise_comparison.rs index c89c542..1dbbd74 100644 --- a/tests/expressive_to_bitwise_comparison.rs +++ b/tests/expressive_to_bitwise_comparison.rs @@ -19,8 +19,17 @@ use rand::{Rng, XorShiftRng, SeedableRng}; const STATE_PATH: &str = "tests/state0.json"; +#[test] +fn reads_into_bitwise_correctly() { + let (_, expressive_state) = input::json::read_expressive_state_from_file(STATE_PATH).expect("Failed to load expressive state"); + let bitwise_state = input::json::read_bitwise_state_from_file(STATE_PATH).expect("Failed to load bitwise state"); + + assert_eq!(build_bitwise_from_expressive(&expressive_state), bitwise_state.clone()); +} + proptest! { #[test] + #[ignore] fn follows_the_same_random_game_tree(seed in any::<[u32;4]>()) { let mut rng = XorShiftRng::from_seed(seed); @@ -86,44 +95,129 @@ fn sensible_buildings(settings: &GameSettings, player: &Player, has_max_teslas: } fn build_bitwise_from_expressive(expressive: &expressive_engine::ExpressiveGameState) -> bitwise_engine::BitwiseGameState { - //TODO + let player_unconstructed = expressive.player_unconstructed_buildings.iter() + .map(build_bitwise_unconstructed_from_expressive) + .collect(); + let opponent_unconstructed = expressive.opponent_unconstructed_buildings.iter() + .map(build_bitwise_unconstructed_from_expressive) + .collect(); + + let player_energy = expressive.player_buildings.iter() + .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Energy) + .fold(0, |acc, next| acc | next.pos.to_left_bitfield(8)); + let opponent_energy = expressive.opponent_buildings.iter() + .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Energy) + .fold(0, |acc, next| acc | next.pos.to_left_bitfield(8)); + + let mut player_buildings_iter = (0..4) + .map(|i| expressive.player_buildings.iter() + .filter(|b| b.health >= i*5) + .fold(0, |acc, next| acc | next.pos.to_left_bitfield(8)) + ); + let mut opponent_buildings_iter = (0..4) + .map(|i| expressive.opponent_buildings.iter() + .filter(|b| b.health >= i*5) + .fold(0, |acc, next| acc | next.pos.to_left_bitfield(8)) + ); + + let mut player_attack_iter = (0..4) + .map(|i| expressive.player_buildings.iter() + .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Attack) + .filter(|b| b.weapon_cooldown_time_left == i) + .fold(0, |acc, next| acc | next.pos.to_left_bitfield(8)) + ); + let mut opponent_attack_iter = (0..4) + .map(|i| expressive.opponent_buildings.iter() + .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Attack) + .filter(|b| b.weapon_cooldown_time_left == i) + .fold(0, |acc, next| acc | next.pos.to_left_bitfield(8)) + ); + + let empty_missiles: [(u64,u64);4] = [(0,0),(0,0),(0,0),(0,0)]; + let player_missiles = expressive.player_missiles.iter() + .fold(empty_missiles, |acc, m| { + let (mut left, mut right) = m.pos.to_bitfield(8); + let mut res = acc.clone(); + for mut tier in res.iter_mut() { + let setting = (!tier.0 & left, !tier.1 & right); + tier.0 |= setting.0; + tier.1 |= setting.1; + left &= !setting.0; + right &= !setting.1; + } + res + }); + let opponent_missiles = expressive.opponent_missiles.iter() + .fold(empty_missiles, |acc, m| { + let (mut left, mut right) = m.pos.to_bitfield(8); + let mut res = acc.clone(); + for mut tier in res.iter_mut() { + let setting = (!tier.0 & left, !tier.1 & right); + tier.0 |= setting.0; + tier.1 |= setting.1; + left &= !setting.0; + right &= !setting.1; + } + res + }); + + let null_tesla = bitwise_engine::TeslaCooldown { + active: false, + pos: Point::new(0,0), + cooldown: 0 + }; + let mut player_tesla_iter = expressive.player_buildings.iter() + .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Tesla) + .map(|b| bitwise_engine::TeslaCooldown { + active: true, + pos: b.pos, + cooldown: b.weapon_cooldown_time_left + }); + let mut opponent_tesla_iter = expressive.opponent_buildings.iter() + .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Tesla) + .map(|b| bitwise_engine::TeslaCooldown { + active: true, + pos: b.pos, + cooldown: b.weapon_cooldown_time_left + }); bitwise_engine::BitwiseGameState { status: expressive.status, player: expressive.player.clone(), opponent: expressive.opponent.clone(), player_buildings: bitwise_engine::PlayerBuildings { - unconstructed: Vec::new(), - buildings: [0,0,0,0], - energy_towers: 0, - missile_towers: [0,0,0], - missiles: [(0,0),(0,0),(0,0),(0,0)], - tesla_cooldowns: [bitwise_engine::TeslaCooldown { - active: false, - pos: Point::new(0,0), - cooldown: 0 - }, bitwise_engine::TeslaCooldown { - active: false, - pos: Point::new(0,0), - cooldown: 0 - }], - unoccupied: Vec::new() + unconstructed: player_unconstructed, + buildings: [player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap()], + energy_towers: player_energy, + missile_towers: [player_attack_iter.next().unwrap(), player_attack_iter.next().unwrap(), player_attack_iter.next().unwrap(), player_attack_iter.next().unwrap()], + missiles: player_missiles, + tesla_cooldowns: [player_tesla_iter.next().unwrap_or(null_tesla.clone()), + player_tesla_iter.next().unwrap_or(null_tesla.clone())] }, opponent_buildings: bitwise_engine::PlayerBuildings { - unconstructed: Vec::new(), - buildings: [0,0,0,0], - energy_towers: 0, - missile_towers: [0,0,0], - missiles: [(0,0),(0,0),(0,0),(0,0)], - tesla_cooldowns: [bitwise_engine::TeslaCooldown { - active: false, - pos: Point::new(0,0), - cooldown: 0 - }, bitwise_engine::TeslaCooldown { - active: false, - pos: Point::new(0,0), - cooldown: 0 - }], - unoccupied: Vec::new() + unconstructed: opponent_unconstructed, + buildings: [opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap()], + energy_towers: opponent_energy, + missile_towers: [opponent_attack_iter.next().unwrap(), opponent_attack_iter.next().unwrap(), opponent_attack_iter.next().unwrap(), opponent_attack_iter.next().unwrap()], + missiles: opponent_missiles, + tesla_cooldowns: [opponent_tesla_iter.next().unwrap_or(null_tesla.clone()), + opponent_tesla_iter.next().unwrap_or(null_tesla.clone())] } } } + +fn build_bitwise_unconstructed_from_expressive(b: &expressive_engine::UnconstructedBuilding) -> bitwise_engine::UnconstructedBuilding { + bitwise_engine::UnconstructedBuilding { + pos: b.pos, + construction_time_left: b.construction_time_left, + building_type: identify_building_type(b.weapon_damage, b.energy_generated_per_turn) + } +} + +fn identify_building_type(weapon_damage: u8, energy_generated_per_turn: u16) -> BuildingType { + match (weapon_damage, energy_generated_per_turn) { + (5, _) => BuildingType::Attack, + (20, _) => BuildingType::Tesla, + (_, 3) => BuildingType::Energy, + _ => BuildingType::Defence + } +} |