Game engine working, except for teslas and choosing a move
authorJustin Worthe <justin@worthe-it.co.za>
Mon, 2 Jul 2018 19:29:52 +0000 (21:29 +0200)
committerJustin Worthe <justin@worthe-it.co.za>
Mon, 2 Jul 2018 19:29:52 +0000 (21:29 +0200)
src/engine/bitwise_engine.rs
tests/expressive_to_bitwise_comparison.rs

index d8e7868..e3ca4c6 100644 (file)
@@ -3,7 +3,9 @@ use engine::geometry::Point;
 use engine::settings::{GameSettings};
 use engine::{GameStatus, Player, GameState};
 
-const MAP_WIDTH: usize = 16;
+const FULL_MAP_WIDTH: u8 = 16;
+const SINGLE_MAP_WIDTH: u8 = FULL_MAP_WIDTH/2;
+const MAX_CONCURRENT_MISSILES: usize = SINGLE_MAP_WIDTH as usize / 2;
 
 const MISSILE_COOLDOWN: usize = 3;
 
@@ -11,6 +13,9 @@ const DEFENCE_HEALTH: usize = 4; // '20' health is 4 hits
 
 const MAX_TESLAS: usize = 2;
 
+const LEFT_COL_MASK: u64 = 0x0101010101010101;
+const RIGHT_COL_MASK: u64 = 0x8080808080808080;
+
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct BitwiseGameState {
     pub status: GameStatus,
@@ -29,7 +34,7 @@ pub struct PlayerBuildings {
     pub energy_towers: u64,
     pub missile_towers: [u64; MISSILE_COOLDOWN+1],
     
-    pub missiles: [(u64, u64); MAP_WIDTH/4],
+    pub missiles: [(u64, u64); MAX_CONCURRENT_MISSILES],
     pub tesla_cooldowns: [TeslaCooldown; MAX_TESLAS]
 }
 
@@ -61,7 +66,9 @@ impl GameState for BitwiseGameState {
 
         BitwiseGameState::add_left_missiles(&mut self.player_buildings);
         BitwiseGameState::add_right_missiles(&mut self.opponent_buildings);
-        //TODO: Move and collide missiles
+
+        BitwiseGameState::move_left_and_collide_missiles(settings, &mut self.player, &mut self.player_buildings, &mut self.opponent_buildings.missiles);
+        BitwiseGameState::move_right_and_collide_missiles(settings, &mut self.opponent, &mut self.opponent_buildings, &mut self.player_buildings.missiles);
 
         BitwiseGameState::add_energy(settings, &mut self.player, &mut self.player_buildings);
         BitwiseGameState::add_energy(settings, &mut self.opponent, &mut self.opponent_buildings);
@@ -91,12 +98,45 @@ impl BitwiseGameState {
         }
     }
 
+    /**
+     * Like with the expressive, this is to make things more
+     * comparable when writing tests, not for actual use in the
+     * engine.
+     */
+    pub fn sort(&mut self) {
+        for i in 0..MAX_CONCURRENT_MISSILES {
+            for j in i+1..MAX_CONCURRENT_MISSILES {
+                let move_down1 = !self.player_buildings.missiles[i].0 & self.player_buildings.missiles[j].0;
+                self.player_buildings.missiles[i].0 |= move_down1;
+                self.player_buildings.missiles[j].0 &= !move_down1;
+
+                let move_down2 = !self.player_buildings.missiles[i].1 & self.player_buildings.missiles[j].1;
+                self.player_buildings.missiles[i].1 |= move_down2;
+                self.player_buildings.missiles[j].1 &= !move_down2;
+
+                let move_down3 = !self.opponent_buildings.missiles[i].0 & self.opponent_buildings.missiles[j].0;
+                self.opponent_buildings.missiles[i].0 |= move_down3;
+                self.opponent_buildings.missiles[j].0 &= !move_down3;
+
+                let move_down4 = !self.opponent_buildings.missiles[i].1 & self.opponent_buildings.missiles[j].1;
+                self.opponent_buildings.missiles[i].1 |= move_down4;
+                self.opponent_buildings.missiles[j].1 &= !move_down4;
+            }
+        }
+    }
+
+    pub fn sorted(&self) -> BitwiseGameState {
+        let mut res = self.clone();
+        res.sort();
+        res
+    }
+
     fn perform_command(settings: &GameSettings, player: &mut Player, player_buildings: &mut PlayerBuildings, command: Command) {
         match command {
             Command::Nothing => {},
             Command::Build(p, b) => {
                 let blueprint = settings.building_settings(b);
-                let bitfield = p.to_either_bitfield(settings.size.x);
+                let bitfield = p.to_either_bitfield(SINGLE_MAP_WIDTH);
 
                 // This is used internally. I should not be making
                 // invalid moves!
@@ -116,7 +156,7 @@ impl BitwiseGameState {
             },
             Command::Deconstruct(p) => {
                 let unconstructed_to_remove_index = player_buildings.unconstructed.iter().position(|ref b| b.pos == p);
-                let deconstruct_mask = !(p.to_either_bitfield(settings.size.x) & player_buildings.buildings[0]);
+                let deconstruct_mask = !(p.to_either_bitfield(SINGLE_MAP_WIDTH) & player_buildings.buildings[0]);
                 
                 debug_assert!(deconstruct_mask != 0 || unconstructed_to_remove_index.is_some());
                 
@@ -150,7 +190,7 @@ impl BitwiseGameState {
                 let building_type = player_buildings.unconstructed[i].building_type;
                 let blueprint = settings.building_settings(building_type);
                 let pos = player_buildings.unconstructed[i].pos;
-                let bitfield = pos.to_either_bitfield(settings.size.x);
+                let bitfield = pos.to_either_bitfield(SINGLE_MAP_WIDTH);
                 
                 for health_tier in 0..4 {
                     if blueprint.health > health_tier*5 {
@@ -216,20 +256,79 @@ impl BitwiseGameState {
     }
 
 
-    fn move_and_collide_missiles_left(settings: &GameSettings, player_buildings: &mut PlayerBuildings, opponent: &mut Player) {
+    fn move_left_and_collide_missiles(settings: &GameSettings, opponent: &mut Player, opponent_buildings: &mut PlayerBuildings, player_missiles: &mut [(u64, u64); MAX_CONCURRENT_MISSILES]) {
+        for _ in 0..settings.attack.weapon_speed {
+            for i in 0..player_missiles.len() {
+                let about_to_hit_opponent = player_missiles[i].0 & LEFT_COL_MASK;
+                let damage = about_to_hit_opponent.count_ones() as u8 * settings.attack.weapon_damage;
+                opponent.health = opponent.health.saturating_sub(damage);
+                player_missiles[i].0 = (player_missiles[i].0 & !LEFT_COL_MASK) >> 1;
+
+                let swapping_sides = player_missiles[i].1 & LEFT_COL_MASK;
+                player_missiles[i].0 |= swapping_sides << 7;
+                player_missiles[i].1 = (player_missiles[i].1 & !LEFT_COL_MASK) >> 1;
+
+
+                let mut hits = 0;
+                for health_tier in (0..DEFENCE_HEALTH).rev() {
+                    hits = opponent_buildings.buildings[health_tier] & player_missiles[i].0;
+                    player_missiles[i].0 &= !hits;
+                    opponent_buildings.buildings[health_tier] &= !hits;
+                }
+
+                let deconstruct_mask = !hits;
+                opponent_buildings.energy_towers &= deconstruct_mask;
+                for tier in 0..opponent_buildings.missile_towers.len() {
+                    opponent_buildings.missile_towers[tier] &= deconstruct_mask;
+                }
+                for tesla in 0..opponent_buildings.tesla_cooldowns.len() {
+                    if opponent_buildings.tesla_cooldowns[tesla].pos.to_either_bitfield(SINGLE_MAP_WIDTH) & deconstruct_mask == 0 {
+                        opponent_buildings.tesla_cooldowns[tesla].active = false;
+                    }
+                }
+                opponent_buildings.occupied &= deconstruct_mask;
+            }
+        }
+    }
+
+    fn move_right_and_collide_missiles(settings: &GameSettings, opponent: &mut Player, opponent_buildings: &mut PlayerBuildings, player_missiles: &mut [(u64, u64); MAX_CONCURRENT_MISSILES]) {
         for _ in 0..settings.attack.weapon_speed {
-            for i in 0..player_buildings.missiles.len() {
-                //TODO this isn't so simple...
-                //collide some with the player, others jump the boundary
-                player_buildings.missiles[i].0 = player_buildings.missiles[i].0 << 1;
-                //TODO Collide with buildings
+            for i in 0..player_missiles.len() {
+                let about_to_hit_opponent = player_missiles[i].1 & RIGHT_COL_MASK;
+                let damage = about_to_hit_opponent.count_ones() as u8 * settings.attack.weapon_damage;
+                opponent.health = opponent.health.saturating_sub(damage);
+                player_missiles[i].1 = (player_missiles[i].1 & !RIGHT_COL_MASK) << 1;
+
+                let swapping_sides = player_missiles[i].0 & RIGHT_COL_MASK;
+                player_missiles[i].1 |= swapping_sides >> 7;
+                player_missiles[i].0 = (player_missiles[i].0 & !RIGHT_COL_MASK) << 1;
+
+                
+                let mut hits = 0;
+                for health_tier in (0..DEFENCE_HEALTH).rev() {
+                    hits = opponent_buildings.buildings[health_tier] & player_missiles[i].1;
+                    player_missiles[i].1 &= !hits;
+                    opponent_buildings.buildings[health_tier] &= !hits;
+                }
+
+                let deconstruct_mask = !hits;
+                opponent_buildings.energy_towers &= deconstruct_mask;
+                for tier in 0..opponent_buildings.missile_towers.len() {
+                    opponent_buildings.missile_towers[tier] &= deconstruct_mask;
+                }
+                for tesla in 0..opponent_buildings.tesla_cooldowns.len() {
+                    if opponent_buildings.tesla_cooldowns[tesla].pos.to_either_bitfield(SINGLE_MAP_WIDTH) & deconstruct_mask == 0 {
+                        opponent_buildings.tesla_cooldowns[tesla].active = false;
+                    }
+                }
+                opponent_buildings.occupied &= deconstruct_mask;
             }
         }
     }
     
     
     fn add_energy(settings: &GameSettings, player: &mut Player, player_buildings: &mut PlayerBuildings) {
-        player.energy_generated = player_buildings.energy_towers.count_ones() as u16 * settings.energy.energy_generated_per_turn;
+        player.energy_generated = settings.energy_income + player_buildings.energy_towers.count_ones() as u16 * settings.energy.energy_generated_per_turn;
         player.energy += player.energy_generated;
     }
 
index 40181b2..cb57e3b 100644 (file)
@@ -38,7 +38,6 @@ fn test_reading_from_replay(replay_folder: &str, length: usize) {
 
 proptest! {
     #[test]
-    #[ignore]
     fn follows_the_same_random_game_tree(seed in any::<[u32;4]>()) {
         let mut rng = XorShiftRng::from_seed(seed);
         
@@ -49,25 +48,25 @@ proptest! {
         while expected_status == GameStatus::Continue {
             let player_command = random_player_move(&settings, &expressive_state, &mut rng);
             let opponent_command = random_opponent_move(&settings, &expressive_state, &mut rng);
+            println!("Player command: {}", player_command);
+            println!("Opponent command: {}", opponent_command);
 
             expected_status = expressive_state.simulate(&settings, player_command, opponent_command);
             let actual_status = bitwise_state.simulate(&settings, player_command, opponent_command);
 
             assert_eq!(&expected_status, &actual_status);
-            assert_eq!(build_bitwise_from_expressive(&expressive_state), bitwise_state.clone());
+            assert_eq!(build_bitwise_from_expressive(&expressive_state), bitwise_state.sorted());
         }
     }
 }
 
-
-
 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());
+    let all_buildings = sensible_buildings(settings, &state.player(), state.player_has_max_teslas()||true);
     random_move(&state.unoccupied_player_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());
+    let all_buildings = sensible_buildings(settings, &state.opponent(), state.opponent_has_max_teslas()||true);
     random_move(&state.unoccupied_opponent_cells(), &all_buildings, rng)
 }