Built bitwise game state from current game state
authorJustin Worthe <justin@worthe-it.co.za>
Sun, 1 Jul 2018 19:07:07 +0000 (21:07 +0200)
committerJustin Worthe <justin@worthe-it.co.za>
Sun, 1 Jul 2018 19:07:07 +0000 (21:07 +0200)
src/engine/bitwise_engine.rs
src/engine/expressive_engine.rs
src/engine/geometry.rs
src/engine/mod.rs
src/input/json.rs
tests/expressive_to_bitwise_comparison.rs

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