summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/engine/expressive_engine.rs397
-rw-r--r--src/engine/mod.rs436
-rw-r--r--src/input/json.rs29
-rw-r--r--src/main.rs2
-rw-r--r--src/strategy/monte_carlo.rs77
5 files changed, 485 insertions, 456 deletions
diff --git a/src/engine/expressive_engine.rs b/src/engine/expressive_engine.rs
new file mode 100644
index 0000000..f1255c3
--- /dev/null
+++ b/src/engine/expressive_engine.rs
@@ -0,0 +1,397 @@
+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<UnconstructedBuilding>,
+ pub player_buildings: Vec<Building>,
+ pub unoccupied_player_cells: Vec<Point>,
+ pub opponent_unconstructed_buildings: Vec<UnconstructedBuilding>,
+ pub opponent_buildings: Vec<Building>,
+ pub unoccupied_opponent_cells: Vec<Point>,
+ pub player_missiles: Vec<Missile>,
+ pub opponent_missiles: Vec<Missile>
+}
+
+#[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
+}
+
+#[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_cells(&self) -> &Vec<Point> { &self.unoccupied_player_cells }
+ fn unoccupied_opponent_cells(&self) -> &Vec<Point> { &self.unoccupied_opponent_cells }
+}
+
+impl ExpressiveGameState {
+ pub fn new(
+ player: Player, opponent: Player,
+ player_unconstructed_buildings: Vec<UnconstructedBuilding>, player_buildings: Vec<Building>,
+ opponent_unconstructed_buildings: Vec<UnconstructedBuilding>, opponent_buildings: Vec<Building>,
+ player_missiles: Vec<Missile>, opponent_missiles: Vec<Missile>,
+ 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.
+ */
+ 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<UnconstructedBuilding>, buildings: &mut Vec<Building>, player: &mut Player, unoccupied_cells: &mut Vec<Point>, 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<UnconstructedBuilding>, buildings: &mut Vec<Building>, player: &mut Player, unoccupied_cells: &mut Vec<Point>, 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<UnconstructedBuilding>, buildings: &mut Vec<Building>, 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<Building>, player_unoccupied_cells: &mut Vec<Point>, opponent: &mut Player, opponent_buildings: &mut Vec<Building>, opponent_unoccupied_cells: &mut Vec<Point>,settings: &GameSettings) {
+ for tesla in player_buildings.iter_mut().filter(|b| b.weapon_damage == 20) {
+ 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) {
+ 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<Building>, missiles: &mut Vec<Missile>) {
+ 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<F>(missiles: &mut Vec<Missile>, mut wrapping_move_fn: F, opponent_buildings: &mut Vec<Building>, opponent: &mut Player, unoccupied_cells: &mut Vec<Point>, 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<Point> {
+ 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
+ }
+ }
+}
+
+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
+ }
+ }
+
+ 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 588724a..65d6e24 100644
--- a/src/engine/mod.rs
+++ b/src/engine/mod.rs
@@ -1,29 +1,22 @@
pub mod command;
pub mod geometry;
pub mod settings;
+pub mod expressive_engine;
-use self::command::{Command, BuildingType};
-use self::geometry::Point;
-use self::settings::{GameSettings, BuildingSettings};
-
-use std::ops::FnMut;
-#[cfg(feature = "energy-cutoff")] pub const ENERGY_PRODUCTION_CUTOFF: f32 = 1.2;
-#[cfg(feature = "energy-cutoff")] pub const ENERGY_STORAGE_CUTOFF: f32 = 1.5;
+use self::command::{Command};
+use self::geometry::Point;
+use self::settings::{GameSettings};
-#[derive(Debug, Clone, PartialEq)]
-pub struct GameState {
- pub status: GameStatus,
- pub player: Player,
- pub opponent: Player,
- pub player_unconstructed_buildings: Vec<UnconstructedBuilding>,
- pub player_buildings: Vec<Building>,
- pub unoccupied_player_cells: Vec<Point>,
- pub opponent_unconstructed_buildings: Vec<UnconstructedBuilding>,
- pub opponent_buildings: Vec<Building>,
- pub unoccupied_opponent_cells: Vec<Point>,
- pub player_missiles: Vec<Missile>,
- pub opponent_missiles: Vec<Missile>
+pub trait GameState: Clone + Sync {
+ fn simulate(&mut self, settings: &GameSettings, player_command: Command, opponent_command: Command) -> GameStatus;
+
+ fn player(&self) -> &Player;
+ 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>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -41,413 +34,12 @@ pub struct Player {
pub energy_generated: u16,
}
-#[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
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct Missile {
- pub pos: Point,
- pub damage: u8,
- pub speed: u8,
-}
-
-impl GameState {
- pub fn new(
- player: Player, opponent: Player,
- player_unconstructed_buildings: Vec<UnconstructedBuilding>, player_buildings: Vec<Building>,
- opponent_unconstructed_buildings: Vec<UnconstructedBuilding>, opponent_buildings: Vec<Building>,
- player_missiles: Vec<Missile>, opponent_missiles: Vec<Missile>,
- settings: &GameSettings) -> GameState {
-
- let unoccupied_player_cells = GameState::unoccupied_cells(
- &player_buildings, &player_unconstructed_buildings, Point::new(0, 0), Point::new(settings.size.x/2, settings.size.y)
- );
- let unoccupied_opponent_cells = GameState::unoccupied_cells(
- &opponent_buildings, &opponent_unconstructed_buildings, Point::new(settings.size.x/2, 0), Point::new(settings.size.x, settings.size.y)
- );
- GameState {
- 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.
- */
- 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);
- }
-
- pub fn simulate(&self, settings: &GameSettings, player_command: Command, opponent_command: Command) -> GameState {
- let mut state = self.clone();
- state.simulate_mut(settings, player_command, opponent_command);
- state
- }
-
- pub fn simulate_mut(&mut self, settings: &GameSettings, player_command: Command, opponent_command: Command) {
- if self.status.is_complete() {
- return;
- }
-
- GameState::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);
- GameState::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);
- GameState::perform_deconstruct_command(&mut self.player_unconstructed_buildings, &mut self.player_buildings, &mut self.player, &mut self.unoccupied_player_cells, player_command);
- GameState::perform_deconstruct_command(&mut self.opponent_unconstructed_buildings, &mut self.opponent_buildings, &mut self.opponent, &mut self.unoccupied_opponent_cells, opponent_command);
-
- GameState::update_construction(&mut self.player_unconstructed_buildings, &mut self.player_buildings, &mut self.player);
- GameState::update_construction(&mut self.opponent_unconstructed_buildings, &mut self.opponent_buildings, &mut self.opponent);
-
- GameState::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);
-
- GameState::add_missiles(&mut self.player_buildings, &mut self.player_missiles);
- GameState::add_missiles(&mut self.opponent_buildings, &mut self.opponent_missiles);
-
- GameState::move_missiles(&mut self.player_missiles, |p| p.wrapping_move_right(),
- &mut self.opponent_buildings, &mut self.opponent,
- &mut self.unoccupied_opponent_cells,
- &settings);
- GameState::move_missiles(&mut self.opponent_missiles, |p| p.wrapping_move_left(),
- &mut self.player_buildings, &mut self.player,
- &mut self.unoccupied_player_cells,
- &settings);
-
- GameState::add_energy(&mut self.player);
- GameState::add_energy(&mut self.opponent);
-
- GameState::update_status(self);
- }
-
- fn perform_construct_command(unconstructed_buildings: &mut Vec<UnconstructedBuilding>, buildings: &mut Vec<Building>, player: &mut Player, unoccupied_cells: &mut Vec<Point>, 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<UnconstructedBuilding>, buildings: &mut Vec<Building>, player: &mut Player, unoccupied_cells: &mut Vec<Point>, 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<UnconstructedBuilding>, buildings: &mut Vec<Building>, 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<Building>, player_unoccupied_cells: &mut Vec<Point>, opponent: &mut Player, opponent_buildings: &mut Vec<Building>, opponent_unoccupied_cells: &mut Vec<Point>,settings: &GameSettings) {
- for tesla in player_buildings.iter_mut().filter(|b| b.weapon_damage == 20) {
- 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) {
- 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<Building>, missiles: &mut Vec<Missile>) {
- 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<F>(missiles: &mut Vec<Missile>, mut wrapping_move_fn: F, opponent_buildings: &mut Vec<Building>, opponent: &mut Player, unoccupied_cells: &mut Vec<Point>, 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 GameState) {
- 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<Point> {
- 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 Player {
- pub fn new(energy: u16, health: u8, settings: &GameSettings, buildings: &[Building]) -> Player {
+ pub fn new(energy: u16, health: u8, settings: &GameSettings, buildings: &[expressive_engine::Building]) -> Player {
Player {
energy,
health,
energy_generated: settings.energy_income + buildings.iter().map(|b| b.energy_generated_per_turn).sum::<u16>()
}
}
-
- #[cfg(not(feature = "energy-cutoff"))]
- pub fn sensible_buildings(&self, tesla_allowed: bool, settings: &GameSettings) -> Vec<BuildingType> {
- let mut result = Vec::with_capacity(3);
- for b in BuildingType::all().iter() {
- let building_setting = settings.building_settings(*b);
- let affordable = building_setting.price <= self.energy;
- let is_tesla = building_setting.weapon_damage == 20;
- if affordable && (!is_tesla || tesla_allowed) {
- result.push(*b);
- }
- }
- result
- }
-
- #[cfg(feature = "energy-cutoff")]
- pub fn sensible_buildings(&self, tesla_allowed: bool, settings: &GameSettings) -> Vec<BuildingType> {
- let mut result = Vec::with_capacity(3);
- let needs_energy = self.energy_generated as f32 <= ENERGY_PRODUCTION_CUTOFF * settings.max_building_price as f32 &&
- self.energy as f32 <= ENERGY_STORAGE_CUTOFF * settings.max_building_price as f32;
-
- for b in BuildingType::all().iter() {
- let building_setting = settings.building_settings(*b);
- let affordable = building_setting.price <= self.energy;
- let energy_producing = building_setting.energy_generated_per_turn > 0;
- let is_tesla = building_setting.weapon_damage == 20;
- if affordable && (!energy_producing || needs_energy) && (!is_tesla || tesla_allowed) {
- result.push(*b);
- }
- }
- result
- }
}
-
-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
- }
- }
-}
-
-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
- }
- }
-
- fn is_shooty(&self) -> bool {
- self.weapon_damage > 0 && self.weapon_damage < 20
- }
-}
-
diff --git a/src/input/json.rs b/src/input/json.rs
index a2f6d8c..3968afd 100644
--- a/src/input/json.rs
+++ b/src/input/json.rs
@@ -3,10 +3,11 @@ use std::io::prelude::*;
use serde_json;
use std::error::Error;
-use ::engine;
+use engine;
+use engine::expressive_engine;
-pub fn read_state_from_file(filename: &str) -> Result<(engine::settings::GameSettings, engine::GameState), Box<Error>> {
+pub fn read_state_from_file(filename: &str) -> Result<(engine::settings::GameSettings, expressive_engine::ExpressiveGameState), Box<Error>> {
let mut file = File::open(filename)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
@@ -121,10 +122,10 @@ impl State {
)
}
- fn to_engine(&self, settings: &engine::settings::GameSettings) -> engine::GameState {
+ fn to_engine(&self, settings: &engine::settings::GameSettings) -> expressive_engine::ExpressiveGameState {
let player_buildings = self.buildings_to_engine('A');
let opponent_buildings = self.buildings_to_engine('B');
- engine::GameState::new(
+ expressive_engine::ExpressiveGameState::new(
self.player().to_engine(settings, &player_buildings),
self.opponent().to_engine(settings, &opponent_buildings),
self.unconstructed_buildings_to_engine('A'),
@@ -149,7 +150,7 @@ impl State {
.expect("Opponent character did not appear in state.json")
}
- fn unconstructed_buildings_to_engine(&self, player_type: char) -> Vec<engine::UnconstructedBuilding> {
+ fn unconstructed_buildings_to_engine(&self, player_type: char) -> Vec<expressive_engine::UnconstructedBuilding> {
self.game_map.iter()
.flat_map(|row| row.iter()
.flat_map(|cell| cell.buildings.iter()
@@ -160,7 +161,7 @@ impl State {
.collect()
}
- fn buildings_to_engine(&self, player_type: char) -> Vec<engine::Building> {
+ fn buildings_to_engine(&self, player_type: char) -> Vec<expressive_engine::Building> {
self.game_map.iter()
.flat_map(|row| row.iter()
.flat_map(|cell| cell.buildings.iter()
@@ -171,7 +172,7 @@ impl State {
.collect()
}
- fn missiles_to_engine(&self, player_type: char) -> Vec<engine::Missile> {
+ fn missiles_to_engine(&self, player_type: char) -> Vec<expressive_engine::Missile> {
self.game_map.iter()
.flat_map(|row| row.iter()
.flat_map(|cell| cell.missiles.iter()
@@ -198,14 +199,14 @@ impl BuildingBlueprint {
}
impl Player {
- fn to_engine(&self, settings: &engine::settings::GameSettings, buildings: &[engine::Building]) -> engine::Player {
+ fn to_engine(&self, settings: &engine::settings::GameSettings, buildings: &[expressive_engine::Building]) -> engine::Player {
engine::Player::new(self.energy, self.health, settings, buildings)
}
}
impl BuildingState {
- fn to_engine(&self) -> engine::Building {
- engine::Building {
+ fn to_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,
@@ -216,8 +217,8 @@ impl BuildingState {
}
}
- fn to_engine_unconstructed(&self) -> engine::UnconstructedBuilding {
- engine::UnconstructedBuilding {
+ fn to_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
@@ -230,8 +231,8 @@ impl BuildingState {
}
impl MissileState {
- fn to_engine(&self) -> engine::Missile {
- engine::Missile {
+ fn to_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/src/main.rs b/src/main.rs
index f3d9373..61e2e55 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,7 +15,7 @@ use std::fs::File;
use std::io::prelude::*;
use std::process;
-fn choose_move(settings: &engine::settings::GameSettings, state: &engine::GameState, start_time: &PreciseTime) -> Command {
+fn choose_move<GS:engine::GameState>(settings: &engine::settings::GameSettings, state: &GS, start_time: &PreciseTime) -> Command {
#[cfg(not(feature = "reduced-time"))]
#[cfg(not(feature = "extended-time"))]
let max_time = Duration::milliseconds(1950);
diff --git a/src/strategy/monte_carlo.rs b/src/strategy/monte_carlo.rs
index ba4310d..dae74bc 100644
--- a/src/strategy/monte_carlo.rs
+++ b/src/strategy/monte_carlo.rs
@@ -1,10 +1,10 @@
use engine::settings::GameSettings;
use engine::command::*;
use engine::geometry::*;
-use engine::{GameState, GameStatus};
+use engine::{GameState, GameStatus, Player};
use rand::{Rng, XorShiftRng, SeedableRng};
-
+
const MAX_MOVES: u16 = 400;
use time::{Duration, PreciseTime};
@@ -12,7 +12,10 @@ use time::{Duration, PreciseTime};
#[cfg(not(feature = "single-threaded"))]
use rayon::prelude::*;
-pub fn choose_move(settings: &GameSettings, state: &GameState, start_time: &PreciseTime, max_time: Duration) -> Command {
+#[cfg(feature = "energy-cutoff")] pub const ENERGY_PRODUCTION_CUTOFF: u16 = 30;
+#[cfg(feature = "energy-cutoff")] pub const ENERGY_STORAGE_CUTOFF: u16 = 45;
+
+pub fn choose_move<GS: GameState>(settings: &GameSettings, state: &GS, start_time: &PreciseTime, max_time: Duration) -> Command {
let mut command_scores = CommandScore::init_command_scores(settings, state);
loop {
@@ -51,22 +54,24 @@ pub fn choose_move(settings: &GameSettings, state: &GameState, start_time: &Prec
}
}
-fn simulate_to_endstate<R: Rng>(command_score: &mut CommandScore, settings: &GameSettings, state: &GameState, rng: &mut R) {
- let opponent_first = random_opponent_move(settings, state, rng);
- let mut state_mut = state.simulate(settings, command_score.command, opponent_first);
+fn simulate_to_endstate<R: Rng, GS: GameState>(command_score: &mut CommandScore, settings: &GameSettings, state: &GS, rng: &mut R) {
+ let mut state_mut = state.clone();
+
+ let opponent_first = random_opponent_move(settings, &state_mut, rng);
+ let mut status = state_mut.simulate(settings, command_score.command, opponent_first);
for _ in 0..MAX_MOVES {
- if state_mut.status != GameStatus::Continue {
+ if status != GameStatus::Continue {
break;
}
let player_command = random_player_move(settings, &state_mut, rng);
let opponent_command = random_opponent_move(settings, &state_mut, rng);
- state_mut.simulate_mut(settings, player_command, opponent_command);
+ status = state_mut.simulate(settings, player_command, opponent_command);
}
let next_seed = [rng.next_u32(), rng.next_u32(), rng.next_u32(), rng.next_u32()];
- match state_mut.status {
+ match status {
GameStatus::PlayerWon => command_score.add_victory(next_seed),
GameStatus::OpponentWon => command_score.add_defeat(next_seed),
GameStatus::Continue => command_score.add_stalemate(next_seed),
@@ -74,14 +79,14 @@ fn simulate_to_endstate<R: Rng>(command_score: &mut CommandScore, settings: &Gam
}
}
-fn random_player_move<R: Rng>(settings: &GameSettings, state: &GameState, rng: &mut R) -> Command {
- let all_buildings = state.player.sensible_buildings(state.count_player_teslas() < 2, settings);
- random_move(&state.unoccupied_player_cells, &all_buildings, rng)
+fn random_player_move<R: Rng, GS: GameState>(settings: &GameSettings, state: &GS, rng: &mut R) -> Command {
+ let all_buildings = sensible_buildings(settings, &state.player(), state.player_has_max_teslas());
+ random_move(&state.unoccupied_player_cells(), &all_buildings, rng)
}
-fn random_opponent_move<R: Rng>(settings: &GameSettings, state: &GameState, rng: &mut R) -> Command {
- let all_buildings = state.opponent.sensible_buildings(state.count_opponent_teslas() < 2, settings);
- random_move(&state.unoccupied_opponent_cells, &all_buildings, rng)
+fn random_opponent_move<R: Rng, GS: GameState>(settings: &GameSettings, state: &GS, rng: &mut R) -> Command {
+ let all_buildings = sensible_buildings(settings, &state.opponent(), state.opponent_has_max_teslas());
+ random_move(&state.unoccupied_opponent_cells(), &all_buildings, rng)
}
fn random_move<R: Rng>(free_positions: &[Point], all_buildings: &[BuildingType], rng: &mut R) -> Command {
@@ -155,16 +160,16 @@ impl CommandScore {
(self.victories as i32 - self.defeats as i32) * 10000 / (self.attempts as i32)
}
- fn init_command_scores(settings: &GameSettings, state: &GameState) -> Vec<CommandScore> {
- let all_buildings = state.player.sensible_buildings(state.count_player_teslas() < 2, settings);
+ fn init_command_scores<GS: GameState>(settings: &GameSettings, state: &GS) -> Vec<CommandScore> {
+ let all_buildings = sensible_buildings(settings, &state.player(), state.player_has_max_teslas());
- let building_command_count = state.unoccupied_player_cells.len()*all_buildings.len();
+ let building_command_count = state.unoccupied_player_cells().len()*all_buildings.len();
let nothing_count = 1;
let mut commands = Vec::with_capacity(building_command_count + nothing_count);
commands.push(CommandScore::new(Command::Nothing));
- for &position in &state.unoccupied_player_cells {
+ for &position in state.unoccupied_player_cells() {
for &building in &all_buildings {
commands.push(CommandScore::new(Command::Build(position, building)));
}
@@ -173,3 +178,37 @@ impl CommandScore {
commands
}
}
+
+#[cfg(not(feature = "energy-cutoff"))]
+fn sensible_buildings(settings: &GameSettings, player: &Player, has_max_teslas: bool) -> Vec<BuildingType> {
+ 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
+}
+
+
+#[cfg(feature = "energy-cutoff")]
+fn sensible_buildings(settings: &GameSettings, player: &Player, has_max_teslas: bool) -> Vec<BuildingType> {
+ let mut result = Vec::with_capacity(4);
+ let needs_energy = player.energy_generated <= ENERGY_PRODUCTION_CUTOFF ||
+ player.energy <= ENERGY_STORAGE_CUTOFF;
+
+ for b in BuildingType::all().iter() {
+ let building_setting = settings.building_settings(*b);
+ let affordable = building_setting.price <= player.energy;
+ let energy_producing = building_setting.energy_generated_per_turn > 0;
+ let is_tesla = *b == BuildingType::Tesla;
+ if affordable && (!energy_producing || needs_energy) && (!is_tesla || !has_max_teslas) {
+ result.push(*b);
+ }
+ }
+ result
+}
+