Initial stab at putting game engine behind a trait
authorJustin Worthe <justin@worthe-it.co.za>
Sat, 30 Jun 2018 13:09:25 +0000 (15:09 +0200)
committerJustin Worthe <justin@worthe-it.co.za>
Sat, 30 Jun 2018 13:09:25 +0000 (15:09 +0200)
src/engine/expressive_engine.rs [new file with mode: 0644]
src/engine/mod.rs
src/input/json.rs
src/main.rs
src/strategy/monte_carlo.rs
tests/live-comparison.rs

diff --git a/src/engine/expressive_engine.rs b/src/engine/expressive_engine.rs
new file mode 100644 (file)
index 0000000..f1255c3
--- /dev/null
@@ -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
+    }
+}
+
index 588724a..65d6e24 100644 (file)
@@ -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
-    }
-}
-
index a2f6d8c..3968afd 100644 (file)
@@ -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,
index f3d9373..61e2e55 100644 (file)
@@ -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);
index ba4310d..dae74bc 100644 (file)
@@ -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
+}
+
index 5f87be3..20dbb2f 100644 (file)
@@ -4,6 +4,7 @@ use zombot::input::json;
 use zombot::engine::command::{Command, BuildingType};
 use zombot::engine::geometry::Point;
 use zombot::engine::settings::GameSettings;
+use zombot::engine::GameState;
 
 use std::fs::File;
 use std::io::prelude::*;
@@ -26,7 +27,7 @@ fn test_from_replay(replay_folder: &str, length: usize) {
         let opponent = read_opponent_command(&format!("{}/Round {:03}/OpponentCommand.txt", replay_folder, i), &settings);
         let (_, mut expected_state) = json::read_state_from_file(&format!("{}/Round {:03}/state.json", replay_folder, i+1)).unwrap();
         
-        state.simulate_mut(&settings, player, opponent);
+        state.simulate(&settings, player, opponent);
         state.sort();
         expected_state.sort();
         assert_eq!(state, expected_state, "\nFailed on state {}\n", i+1);