From f41255a8dda9e2c6a18c32564a30e63eed58f6b3 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Thu, 9 Aug 2018 13:10:14 +0200 Subject: Removed expressive engine Refocus on the bitwise idea. It's faster, and has more potential for speed. Also, it works well as a way of thinking on the puzzle as a whole. --- src/bin/perf-test.rs | 14 - src/engine/expressive_engine.rs | 412 ------------------------------ src/engine/mod.rs | 1 - src/input/json.rs | 117 +-------- tests/expressive_to_bitwise_comparison.rs | 256 ------------------- 5 files changed, 7 insertions(+), 793 deletions(-) delete mode 100644 src/engine/expressive_engine.rs delete mode 100644 tests/expressive_to_bitwise_comparison.rs diff --git a/src/bin/perf-test.rs b/src/bin/perf-test.rs index 8d6a490..8c93f5a 100644 --- a/src/bin/perf-test.rs +++ b/src/bin/perf-test.rs @@ -13,20 +13,6 @@ fn main() { bitwise(); } -fn _expressive() { - println!("Running expressive engine"); - let start_time = PreciseTime::now(); - let (settings, state) = match input::json::read_expressive_state_from_file(STATE_PATH) { - Ok(ok) => ok, - Err(error) => { - println!("Error while parsing JSON file: {}", error); - process::exit(1); - } - }; - let max_time = Duration::milliseconds(MAX_TIME_MILLIS); - strategy::monte_carlo::choose_move(&settings, &state, &start_time, max_time); -} - fn bitwise() { println!("Running bitwise engine"); let start_time = PreciseTime::now(); diff --git a/src/engine/expressive_engine.rs b/src/engine/expressive_engine.rs deleted file mode 100644 index 557e0fa..0000000 --- a/src/engine/expressive_engine.rs +++ /dev/null @@ -1,412 +0,0 @@ -use std::ops::FnMut; -use engine::command::{Command, BuildingType}; -use engine::geometry::Point; -use engine::settings::{GameSettings, BuildingSettings}; -use engine::{GameStatus, Player, GameState}; - -#[derive(Debug, Clone, PartialEq)] -pub struct ExpressiveGameState { - pub status: GameStatus, - pub player: Player, - pub opponent: Player, - pub player_unconstructed_buildings: Vec, - pub player_buildings: Vec, - pub unoccupied_player_cells: Vec, - pub opponent_unconstructed_buildings: Vec, - pub opponent_buildings: Vec, - pub unoccupied_opponent_cells: Vec, - pub player_missiles: Vec, - pub opponent_missiles: Vec -} - -#[derive(Debug, Clone, PartialEq)] -pub struct UnconstructedBuilding { - pub pos: Point, - pub health: u8, - pub construction_time_left: u8, - pub weapon_damage: u8, - pub weapon_speed: u8, - pub weapon_cooldown_period: u8, - pub energy_generated_per_turn: u16 -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Building { - pub pos: Point, - pub health: u8, - pub weapon_damage: u8, - pub weapon_speed: u8, - pub weapon_cooldown_time_left: u8, - pub weapon_cooldown_period: u8, - pub energy_generated_per_turn: u16, - pub age: u16 -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Missile { - pub pos: Point, - pub damage: u8, - pub speed: u8, -} - -impl GameState for ExpressiveGameState { - fn simulate(&mut self, settings: &GameSettings, player_command: Command, opponent_command: Command) -> GameStatus { - if self.status.is_complete() { - return self.status; - } - - ExpressiveGameState::perform_construct_command(&mut self.player_unconstructed_buildings, &mut self.player_buildings, &mut self.player, &mut self.unoccupied_player_cells, settings, player_command, &settings.size); - ExpressiveGameState::perform_construct_command(&mut self.opponent_unconstructed_buildings, &mut self.opponent_buildings, &mut self.opponent, &mut self.unoccupied_opponent_cells, settings, opponent_command, &settings.size); - ExpressiveGameState::perform_deconstruct_command(&mut self.player_unconstructed_buildings, &mut self.player_buildings, &mut self.player, &mut self.unoccupied_player_cells, player_command); - ExpressiveGameState::perform_deconstruct_command(&mut self.opponent_unconstructed_buildings, &mut self.opponent_buildings, &mut self.opponent, &mut self.unoccupied_opponent_cells, opponent_command); - - ExpressiveGameState::update_construction(&mut self.player_unconstructed_buildings, &mut self.player_buildings, &mut self.player); - ExpressiveGameState::update_construction(&mut self.opponent_unconstructed_buildings, &mut self.opponent_buildings, &mut self.opponent); - - ExpressiveGameState::fire_teslas(&mut self.player, &mut self.player_buildings, &mut self.unoccupied_player_cells, &mut self.opponent, &mut self.opponent_buildings, &mut self.unoccupied_opponent_cells, &settings); - - ExpressiveGameState::add_missiles(&mut self.player_buildings, &mut self.player_missiles); - ExpressiveGameState::add_missiles(&mut self.opponent_buildings, &mut self.opponent_missiles); - - ExpressiveGameState::move_missiles(&mut self.player_missiles, |p| p.wrapping_move_right(), - &mut self.opponent_buildings, &mut self.opponent, - &mut self.unoccupied_opponent_cells, - &settings); - ExpressiveGameState::move_missiles(&mut self.opponent_missiles, |p| p.wrapping_move_left(), - &mut self.player_buildings, &mut self.player, - &mut self.unoccupied_player_cells, - &settings); - - ExpressiveGameState::add_energy(&mut self.player); - ExpressiveGameState::add_energy(&mut self.opponent); - - ExpressiveGameState::update_status(self); - - self.status - } - - - fn player(&self) -> &Player { &self.player } - 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_cell_count(&self) -> usize { self.unoccupied_player_cells.len() } - fn unoccupied_opponent_cell_count(&self) -> usize { self.unoccupied_opponent_cells.len() } - fn location_of_unoccupied_player_cell(&self, i: usize) -> Point { self.unoccupied_player_cells[i] } - fn location_of_unoccupied_opponent_cell(&self, i: usize) -> Point { self.unoccupied_opponent_cells[i] } -} - -impl ExpressiveGameState { - pub fn new( - player: Player, opponent: Player, - player_unconstructed_buildings: Vec, player_buildings: Vec, - opponent_unconstructed_buildings: Vec, opponent_buildings: Vec, - player_missiles: Vec, opponent_missiles: Vec, - settings: &GameSettings) -> ExpressiveGameState { - - let unoccupied_player_cells = ExpressiveGameState::unoccupied_cells( - &player_buildings, &player_unconstructed_buildings, Point::new(0, 0), Point::new(settings.size.x/2, settings.size.y) - ); - let unoccupied_opponent_cells = ExpressiveGameState::unoccupied_cells( - &opponent_buildings, &opponent_unconstructed_buildings, Point::new(settings.size.x/2, 0), Point::new(settings.size.x, settings.size.y) - ); - ExpressiveGameState { - status: GameStatus::Continue, - player, opponent, - player_unconstructed_buildings, player_buildings, unoccupied_player_cells, - opponent_unconstructed_buildings, opponent_buildings, unoccupied_opponent_cells, - player_missiles, opponent_missiles - } - } - - /** - * Sorts the various arrays. Generally not necessary, but useful - * for tests that check equality between states. - */ - #[cfg(debug_assertions)] - pub fn sort(&mut self) { - self.player_unconstructed_buildings.sort_by_key(|b| b.pos); - self.player_buildings.sort_by_key(|b| b.pos); - self.unoccupied_player_cells.sort(); - self.opponent_unconstructed_buildings.sort_by_key(|b| b.pos); - self.opponent_buildings.sort_by_key(|b| b.pos); - self.unoccupied_opponent_cells.sort(); - self.player_missiles.sort_by_key(|b| b.pos); - self.opponent_missiles.sort_by_key(|b| b.pos); - } - - fn perform_construct_command(unconstructed_buildings: &mut Vec, buildings: &mut Vec, player: &mut Player, unoccupied_cells: &mut Vec, settings: &GameSettings, command: Command, size: &Point) { - if let Command::Build(p, b) = command { - let blueprint = settings.building_settings(b); - - // This is used internally. I should not be making - // invalid moves! - debug_assert!(!buildings.iter().any(|b| b.pos == p)); - debug_assert!(p.x < size.x && p.y < size.y); - debug_assert!(player.energy >= blueprint.price); - debug_assert!(b != BuildingType::Tesla || - (unconstructed_buildings.iter().filter(|b| b.weapon_damage == 20).count() + - buildings.iter().filter(|b| b.weapon_damage == 20).count() < 2)); - - player.energy -= blueprint.price; - unconstructed_buildings.push(UnconstructedBuilding::new(p, blueprint)); - - let to_remove_index = unoccupied_cells.iter().position(|&pos| pos == p).unwrap(); - unoccupied_cells.swap_remove(to_remove_index); - } - } - fn perform_deconstruct_command(unconstructed_buildings: &mut Vec, buildings: &mut Vec, player: &mut Player, unoccupied_cells: &mut Vec, command: Command) { - if let Command::Deconstruct(p) = command { - let to_remove_index = buildings.iter().position(|ref b| b.pos == p); - let unconstructed_to_remove_index = unconstructed_buildings.iter().position(|ref b| b.pos == p); - debug_assert!(to_remove_index.is_some() || unconstructed_to_remove_index.is_some()); - - if let Some(i) = to_remove_index { - player.energy_generated -= buildings[i].energy_generated_per_turn; - buildings.swap_remove(i); - } - if let Some(i) = unconstructed_to_remove_index { - unconstructed_buildings.swap_remove(i); - } - - player.energy += 5; - - unoccupied_cells.push(p); - } - } - - fn update_construction(unconstructed_buildings: &mut Vec, buildings: &mut Vec, player: &mut Player) { - let mut buildings_len = unconstructed_buildings.len(); - for i in (0..buildings_len).rev() { - if unconstructed_buildings[i].is_constructed() { - player.energy_generated += unconstructed_buildings[i].energy_generated_per_turn; - buildings.push(unconstructed_buildings[i].to_building()); - buildings_len -= 1; - unconstructed_buildings.swap(i, buildings_len); - } else { - unconstructed_buildings[i].construction_time_left -= 1 - } - } - unconstructed_buildings.truncate(buildings_len); - } - - fn fire_teslas(player: &mut Player, player_buildings: &mut Vec, player_unoccupied_cells: &mut Vec, opponent: &mut Player, opponent_buildings: &mut Vec, opponent_unoccupied_cells: &mut Vec,settings: &GameSettings) { - #[cfg(debug_assertions)] - { - player_buildings.sort_by(|a, b| b.age.cmp(&a.age).then(a.pos.cmp(&b.pos))); - opponent_buildings.sort_by(|a, b| b.age.cmp(&a.age).then(a.pos.cmp(&b.pos))); - } - - for tesla in player_buildings.iter_mut().filter(|b| b.weapon_damage == 20) { - tesla.age += 1; - if tesla.weapon_cooldown_time_left > 0 { - tesla.weapon_cooldown_time_left -= 1; - } else if player.energy >= 100 { - player.energy -= 100; - tesla.weapon_cooldown_time_left = tesla.weapon_cooldown_period; - - if tesla.pos.x + 1 >= settings.size.x/2 { - opponent.health = opponent.health.saturating_sub(settings.tesla.weapon_damage); - } - 'player_col_loop: for x in tesla.pos.x+1..tesla.pos.x+(settings.size.x/2)+2 { - for &y in [tesla.pos.y.saturating_sub(1), tesla.pos.y, tesla.pos.y.saturating_add(1)].iter() { - let target_point = Point::new(x, y); - for b in 0..opponent_buildings.len() { - if opponent_buildings[b].pos == target_point && opponent_buildings[b].health > 0 { - opponent_buildings[b].health = opponent_buildings[b].health.saturating_sub(settings.tesla.weapon_damage); - continue 'player_col_loop; - } - } - } - } - } - } - - for tesla in opponent_buildings.iter_mut().filter(|b| b.weapon_damage == 20) { - tesla.age += 1; - if tesla.weapon_cooldown_time_left > 0 { - tesla.weapon_cooldown_time_left -= 1; - } else if opponent.energy >= 100 { - opponent.energy -= 100; - tesla.weapon_cooldown_time_left = tesla.weapon_cooldown_period; - - if tesla.pos.x <= settings.size.x/2 { - player.health = player.health.saturating_sub(settings.tesla.weapon_damage); - } - 'opponent_col_loop: for x in tesla.pos.x.saturating_sub((settings.size.x/2)+1)..tesla.pos.x { - for &y in [tesla.pos.y.saturating_sub(1), tesla.pos.y, tesla.pos.y.saturating_add(1)].iter() { - let target_point = Point::new(x, y); - for b in 0..player_buildings.len() { - if player_buildings[b].pos == target_point && player_buildings[b].health > 0 { - player_buildings[b].health = player_buildings[b].health.saturating_sub(settings.tesla.weapon_damage); - continue 'opponent_col_loop; - } - } - } - } - } - } - - for building in player_buildings.iter().filter(|b| b.health == 0) { - player_unoccupied_cells.push(building.pos); - player.energy_generated -= building.energy_generated_per_turn; - } - player_buildings.retain(|b| b.health > 0); - - for building in opponent_buildings.iter().filter(|b| b.health == 0) { - opponent_unoccupied_cells.push(building.pos); - opponent.energy_generated -= building.energy_generated_per_turn; - } - opponent_buildings.retain(|b| b.health > 0); - } - - fn add_missiles(buildings: &mut Vec, missiles: &mut Vec) { - for building in buildings.iter_mut().filter(|b| b.is_shooty()) { - if building.weapon_cooldown_time_left > 0 { - building.weapon_cooldown_time_left -= 1; - } else { - missiles.push(Missile { - pos: building.pos, - speed: building.weapon_speed, - damage: building.weapon_damage, - }); - building.weapon_cooldown_time_left = building.weapon_cooldown_period; - } - } - } - - fn move_missiles(missiles: &mut Vec, mut wrapping_move_fn: F, opponent_buildings: &mut Vec, opponent: &mut Player, unoccupied_cells: &mut Vec, settings: &GameSettings) - where F: FnMut(&mut Point) { - let mut missiles_len = missiles.len(); - 'speed_loop: for _ in 0..settings.attack.weapon_speed { - 'missile_loop: for m in (0..missiles.len()).rev() { - wrapping_move_fn(&mut missiles[m].pos); - if missiles[m].pos.x >= settings.size.x { - opponent.health = opponent.health.saturating_sub(missiles[m].damage); - - missiles_len -= 1; - missiles.swap(m, missiles_len); - - continue 'missile_loop; - } - else { - for b in 0..opponent_buildings.len() { - if opponent_buildings[b].pos == missiles[m].pos { - opponent_buildings[b].health = opponent_buildings[b].health.saturating_sub(missiles[m].damage); - - missiles_len -= 1; - missiles.swap(m, missiles_len); - - if opponent_buildings[b].health == 0 { - unoccupied_cells.push(opponent_buildings[b].pos); - opponent.energy_generated -= opponent_buildings[b].energy_generated_per_turn; - opponent_buildings.swap_remove(b); - } - //after game engine bug fix, this should go back to missile_loop - continue 'missile_loop; - } - } - } - } - missiles.truncate(missiles_len); - } - } - - fn add_energy(player: &mut Player) { - player.energy += player.energy_generated; - } - - fn update_status(state: &mut ExpressiveGameState) { - let player_dead = state.player.health == 0; - let opponent_dead = state.opponent.health == 0; - state.status = match (player_dead, opponent_dead) { - (true, true) => GameStatus::Draw, - (false, true) => GameStatus::PlayerWon, - (true, false) => GameStatus::OpponentWon, - (false, false) => GameStatus::Continue, - }; - } - - fn unoccupied_cells(buildings: &[Building], unconstructed_buildings: &[UnconstructedBuilding], bl: Point, tr: Point) -> Vec { - let mut result = Vec::with_capacity((tr.y-bl.y) as usize * (tr.x-bl.x) as usize); - for y in bl.y..tr.y { - for x in bl.x..tr.x { - let pos = Point::new(x, y); - if !buildings.iter().any(|b| b.pos == pos) && !unconstructed_buildings.iter().any(|b| b.pos == pos) { - result.push(pos); - } - } - } - result - } - - pub fn count_player_teslas(&self) -> usize { - self.player_unconstructed_buildings.iter().filter(|b| b.weapon_damage == 20).count() + - self.player_buildings.iter().filter(|b| b.weapon_damage == 20).count() - } - - pub fn count_opponent_teslas(&self) -> usize { - self.opponent_unconstructed_buildings.iter().filter(|b| b.weapon_damage == 20).count() + - self.opponent_buildings.iter().filter(|b| b.weapon_damage == 20).count() - } -} - -impl GameStatus { - fn is_complete(&self) -> bool { - *self != GameStatus::Continue - } -} - - - -impl UnconstructedBuilding { - pub fn new(pos: Point, blueprint: &BuildingSettings) -> UnconstructedBuilding { - UnconstructedBuilding { - pos, - health: blueprint.health, - construction_time_left: blueprint.construction_time, - weapon_damage: blueprint.weapon_damage, - weapon_speed: blueprint.weapon_speed, - weapon_cooldown_period: blueprint.weapon_cooldown_period, - energy_generated_per_turn: blueprint.energy_generated_per_turn - } - } - - fn is_constructed(&self) -> bool { - self.construction_time_left == 0 - } - - fn to_building(&self) -> Building { - Building { - pos: self.pos, - health: self.health, - weapon_damage: self.weapon_damage, - weapon_speed: self.weapon_speed, - weapon_cooldown_time_left: 0, - weapon_cooldown_period: self.weapon_cooldown_period, - energy_generated_per_turn: self.energy_generated_per_turn, - age: 0 - } - } -} - -impl Building { - pub fn new(pos: Point, blueprint: &BuildingSettings) -> Building { - Building { - pos, - health: blueprint.health, - weapon_damage: blueprint.weapon_damage, - weapon_speed: blueprint.weapon_speed, - weapon_cooldown_time_left: 0, - weapon_cooldown_period: blueprint.weapon_cooldown_period, - energy_generated_per_turn: blueprint.energy_generated_per_turn, - age: 0 - } - } - - fn is_shooty(&self) -> bool { - self.weapon_damage > 0 && self.weapon_damage < 20 - } -} - diff --git a/src/engine/mod.rs b/src/engine/mod.rs index a444059..c205d72 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -1,7 +1,6 @@ pub mod command; pub mod geometry; pub mod settings; -pub mod expressive_engine; pub mod bitwise_engine; pub mod constants; diff --git a/src/input/json.rs b/src/input/json.rs index 000c355..200252a 100644 --- a/src/input/json.rs +++ b/src/input/json.rs @@ -5,21 +5,9 @@ use std::error::Error; use engine; use engine::command; -use engine::expressive_engine; use engine::bitwise_engine; use engine::constants::*; -pub fn read_expressive_state_from_file(filename: &str) -> Result<(engine::settings::GameSettings, expressive_engine::ExpressiveGameState), Box> { - let mut file = File::open(filename)?; - let mut content = String::new(); - file.read_to_string(&mut content)?; - let state: State = serde_json::from_str(content.as_ref())?; - - let engine_settings = state.to_engine_settings(); - let engine_state = state.to_expressive_engine(&engine_settings); - Ok((engine_settings, engine_state)) -} - pub fn read_bitwise_state_from_file(filename: &str) -> Result<(engine::settings::GameSettings, bitwise_engine::BitwiseGameState), Box> { let mut file = File::open(filename)?; let mut content = String::new(); @@ -99,10 +87,10 @@ struct BuildingState { health: u8, construction_time_left: i16, //price: u16, - weapon_damage: u8, - weapon_speed: u8, + //weapon_damage: u8, + //weapon_speed: u8, weapon_cooldown_time_left: u8, - weapon_cooldown_period: u8, + //weapon_cooldown_period: u8, //destroy_multiplier: u32, //construction_score: u32, energy_generated_per_turn: u16, @@ -115,10 +103,10 @@ struct BuildingState { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct MissileState { - damage: u8, - speed: u8, - x: u8, - y: u8, + //damage: u8, + //speed: u8, + //x: u8, + //y: u8, player_type: char } @@ -135,22 +123,6 @@ impl State { ) } - fn to_expressive_engine(&self, settings: &engine::settings::GameSettings) -> expressive_engine::ExpressiveGameState { - let player_buildings = self.buildings_to_expressive_engine('A'); - let opponent_buildings = self.buildings_to_expressive_engine('B'); - expressive_engine::ExpressiveGameState::new( - self.player().to_engine(settings, &player_buildings), - self.opponent().to_engine(settings, &opponent_buildings), - self.unconstructed_buildings_to_expressive_engine('A'), - player_buildings, - self.unconstructed_buildings_to_expressive_engine('B'), - opponent_buildings, - self.missiles_to_expressive_engine('A'), - self.missiles_to_expressive_engine('B'), - settings - ) - } - fn to_bitwise_engine(&self) -> bitwise_engine::BitwiseGameState { let mut player = self.player().to_bitwise_engine(); let mut opponent = self.opponent().to_bitwise_engine(); @@ -237,39 +209,6 @@ impl State { .find(|p| p.player_type == 'B') .expect("Opponent character did not appear in state.json") } - - fn unconstructed_buildings_to_expressive_engine(&self, player_type: char) -> Vec { - self.game_map.iter() - .flat_map(|row| row.iter() - .flat_map(|cell| cell.buildings.iter() - .filter(|b| b.player_type == player_type && b.construction_time_left >= 0) - .map(|b| b.to_expressive_engine_unconstructed()) - ) - ) - .collect() - } - - fn buildings_to_expressive_engine(&self, player_type: char) -> Vec { - self.game_map.iter() - .flat_map(|row| row.iter() - .flat_map(|cell| cell.buildings.iter() - .filter(|b| b.player_type == player_type && b.construction_time_left < 0) - .map(|b| b.to_expressive_engine()) - ) - ) - .collect() - } - - fn missiles_to_expressive_engine(&self, player_type: char) -> Vec { - self.game_map.iter() - .flat_map(|row| row.iter() - .flat_map(|cell| cell.missiles.iter() - .filter(|b| b.player_type == player_type) - .map(|b| b.to_expressive_engine()) - ) - ) - .collect() - } } impl BuildingBlueprint { @@ -287,13 +226,6 @@ impl BuildingBlueprint { } impl Player { - fn to_engine(&self, settings: &engine::settings::GameSettings, buildings: &[expressive_engine::Building]) -> engine::Player { - engine::Player { - energy: self.energy, - health: self.health, - energy_generated: settings.energy_income + buildings.iter().map(|b| b.energy_generated_per_turn).sum::() - } - } fn to_bitwise_engine(&self) -> engine::Player { engine::Player { energy: self.energy, @@ -304,31 +236,6 @@ impl Player { } impl BuildingState { - fn to_expressive_engine(&self) -> expressive_engine::Building { - expressive_engine::Building { - pos: engine::geometry::Point::new(self.x, self.y), - health: self.health, - weapon_damage: self.weapon_damage, - weapon_speed: self.weapon_speed, - weapon_cooldown_time_left: self.weapon_cooldown_time_left, - weapon_cooldown_period: self.weapon_cooldown_period, - energy_generated_per_turn: self.energy_generated_per_turn, - age: self.construction_time_left.abs() as u16 - } - } - - fn to_expressive_engine_unconstructed(&self) -> expressive_engine::UnconstructedBuilding { - expressive_engine::UnconstructedBuilding { - pos: engine::geometry::Point::new(self.x, self.y), - health: self.health, - construction_time_left: self.construction_time_left as u8, // > 0 check already happened - weapon_damage: self.weapon_damage, - weapon_speed: self.weapon_speed, - weapon_cooldown_period: self.weapon_cooldown_period, - energy_generated_per_turn: self.energy_generated_per_turn, - } - } - fn to_bitwise_engine_unconstructed(&self) -> bitwise_engine::UnconstructedBuilding { bitwise_engine::UnconstructedBuilding { pos: engine::geometry::Point::new(self.x, self.y), @@ -346,13 +253,3 @@ impl BuildingState { } } } - -impl MissileState { - fn to_expressive_engine(&self) -> expressive_engine::Missile { - expressive_engine::Missile { - pos: engine::geometry::Point::new(self.x, self.y), - damage: self.damage, - speed: self.speed, - } - } -} diff --git a/tests/expressive_to_bitwise_comparison.rs b/tests/expressive_to_bitwise_comparison.rs deleted file mode 100644 index 72b5731..0000000 --- a/tests/expressive_to_bitwise_comparison.rs +++ /dev/null @@ -1,256 +0,0 @@ -extern crate zombot; - -#[macro_use] extern crate proptest; -extern crate rand; - -use zombot::input; -use zombot::engine::command::{Command, BuildingType}; -use zombot::engine::geometry::Point; -use zombot::engine::settings::GameSettings; -use zombot::engine::{GameState, GameStatus, Player}; - -use zombot::engine::expressive_engine; -use zombot::engine::bitwise_engine; -use zombot::engine::constants::*; - -use proptest::prelude::*; - -use rand::{Rng, XorShiftRng, SeedableRng}; - - -const STATE_PATH: &str = "tests/state0.json"; - -#[test] -fn reads_into_bitwise_correctly() { - test_reading_from_replay("tests/after_200", 64); -} - -fn test_reading_from_replay(replay_folder: &str, length: usize) { - for i in 0..length { - let state_file = format!("{}/Round {:03}/state.json", replay_folder, i); - - let (_, expressive_state) = input::json::read_expressive_state_from_file(&state_file).expect("Failed to load expressive state"); - let (_, bitwise_state) = input::json::read_bitwise_state_from_file(&state_file).expect("Failed to load bitwise state"); - - assert_eq!(build_bitwise_from_expressive(&expressive_state), bitwise_state.clone(), "\nFailed on state {}\n", i); - } -} - - -proptest! { - #[test] - fn follows_the_same_random_game_tree(seed in any::<[u32;4]>()) { - let mut rng = XorShiftRng::from_seed(seed); - - let (settings, mut expressive_state) = input::json::read_expressive_state_from_file(STATE_PATH).expect("Failed to load expressive state"); - let (_, mut bitwise_state) = input::json::read_bitwise_state_from_file(STATE_PATH).expect("Failed to load bitwise state"); - - expressive_state.sort(); - - let mut expected_status = GameStatus::Continue; - while expected_status == GameStatus::Continue { - let player_command = random_player_move(&settings, &expressive_state, &bitwise_state, &mut rng); - let opponent_command = random_opponent_move(&settings, &expressive_state, &bitwise_state, &mut rng); - println!("Player command: {}", player_command); - println!("Opponent command: {}", opponent_command); - - expected_status = expressive_state.simulate(&settings, player_command, opponent_command); - let actual_status = bitwise_state.simulate(&settings, player_command, opponent_command); - - expressive_state.sort(); - - assert_eq!(&expected_status, &actual_status); - assert_eq!(build_bitwise_from_expressive(&expressive_state), bitwise_state.sorted()); - } - } -} - -fn random_player_move(settings: &GameSettings, expressive_state: &GSE, bitwise_state: &GSB, rng: &mut R) -> Command { - assert_eq!(expressive_state.player_has_max_teslas(), bitwise_state.player_has_max_teslas()); - let all_buildings = sensible_buildings(settings, &expressive_state.player(), expressive_state.player_has_max_teslas()); - random_move(&all_buildings, rng, expressive_state.unoccupied_player_cell_count(), |i| expressive_state.location_of_unoccupied_player_cell(i), |i| bitwise_state.location_of_unoccupied_player_cell(i)) -} - -fn random_opponent_move(settings: &GameSettings, expressive_state: &GSE, bitwise_state: &GSB, rng: &mut R) -> Command { - assert_eq!(expressive_state.player_has_max_teslas(), bitwise_state.player_has_max_teslas()); - let all_buildings = sensible_buildings(settings, &expressive_state.opponent(), expressive_state.opponent_has_max_teslas()); - random_move(&all_buildings, rng, expressive_state.unoccupied_opponent_cell_count(), |i| expressive_state.location_of_unoccupied_opponent_cell(i), |i| bitwise_state.location_of_unoccupied_opponent_cell(i)) -} - -fn random_movePoint, FB:Fn(usize)->Point>(all_buildings: &[BuildingType], rng: &mut R, free_positions_count: usize, get_point_expressive: FE, get_point_bitwise: FB) -> Command { - let building_command_count = free_positions_count*all_buildings.len(); - let nothing_count = 1; - - let number_of_commands = building_command_count + nothing_count; - - let choice_index = rng.gen_range(0, number_of_commands); - - if choice_index == number_of_commands - 1 { - Command::Nothing - } else { - let expressive_point = get_point_expressive(choice_index/all_buildings.len()); - let bitwise_point = get_point_bitwise(choice_index/all_buildings.len()); - assert_eq!(expressive_point, bitwise_point); - Command::Build( - expressive_point, - all_buildings[choice_index%all_buildings.len()] - ) - } -} - -fn sensible_buildings(settings: &GameSettings, player: &Player, has_max_teslas: bool) -> Vec { - let mut result = Vec::with_capacity(4); - for b in BuildingType::all().iter() { - let building_setting = settings.building_settings(*b); - let affordable = building_setting.price <= player.energy; - let is_tesla = *b == BuildingType::Tesla; - if affordable && (!is_tesla || !has_max_teslas) { - result.push(*b); - } - } - result -} - -fn build_bitwise_from_expressive(expressive: &expressive_engine::ExpressiveGameState) -> bitwise_engine::BitwiseGameState { - 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()); - 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_right_bitfield()); - - let mut player_buildings_iter = (0..DEFENCE_HEALTH as u8) - .map(|i| expressive.player_buildings.iter() - .filter(|b| b.health > i*MISSILE_DAMAGE) - .fold(0, |acc, next| acc | next.pos.to_left_bitfield()) - ); - let mut opponent_buildings_iter = (0..DEFENCE_HEALTH as u8) - .map(|i| expressive.opponent_buildings.iter() - .filter(|b| b.health > i*MISSILE_DAMAGE) - .fold(0, |acc, next| acc | next.pos.to_right_bitfield()) - ); - - let player_occupied = expressive.player_buildings.iter() - .fold(0, |acc, next| acc | next.pos.to_left_bitfield()) | - expressive.player_unconstructed_buildings.iter() - .fold(0, |acc, next| acc | next.pos.to_left_bitfield()); - let opponent_occupied = expressive.opponent_buildings.iter() - .fold(0, |acc, next| acc | next.pos.to_right_bitfield()) | - expressive.opponent_unconstructed_buildings.iter() - .fold(0, |acc, next| acc | next.pos.to_right_bitfield()); - - let mut player_attack_iter = (0..MISSILE_COOLDOWN_STATES as u8) - .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()) - ); - let mut opponent_attack_iter = (0..MISSILE_COOLDOWN_STATES as u8) - .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_right_bitfield()) - ); - - let empty_missiles: [(u64,u64);MISSILE_COOLDOWN_STATES] = [(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(); - 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(); - let mut res = acc.clone(); - for mut tier in res.iter_mut() { - let setting = (!tier.0 & right, !tier.1 & left); - tier.0 |= setting.0; - tier.1 |= setting.1; - right &= !setting.0; - left &= !setting.1; - } - res - }); - - let null_tesla = bitwise_engine::TeslaCooldown { - active: false, - pos: Point::new(0,0), - cooldown: 0, - age: 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, - age: b.age, - }); - 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, - age: b.age, - }); - bitwise_engine::BitwiseGameState { - status: expressive.status, - player: expressive.player.clone(), - opponent: expressive.opponent.clone(), - player_buildings: bitwise_engine::PlayerBuildings { - unconstructed: player_unconstructed, - buildings: [player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap()], - occupied: player_occupied, - 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()], - firing_tower: 0, - 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: opponent_unconstructed, - buildings: [opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap()], - occupied: opponent_occupied, - 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()], - firing_tower: 0, - 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) { - (MISSILE_DAMAGE, _) => BuildingType::Attack, - (TESLA_DAMAGE, _) => BuildingType::Tesla, - (_, ENERGY_GENERATED_TOWER) => BuildingType::Energy, - _ => BuildingType::Defence - } -} -- cgit v1.2.3