Folded duplicate code for player and opponent
authorJustin Worthe <justin@worthe-it.co.za>
Sun, 12 Aug 2018 18:41:31 +0000 (20:41 +0200)
committerJustin Worthe <justin@worthe-it.co.za>
Sun, 12 Aug 2018 18:41:31 +0000 (20:41 +0200)
src/engine/bitwise_engine.rs
src/strategy/monte_carlo.rs

index 24189ee..a4842ec 100644 (file)
@@ -78,31 +78,6 @@ impl BitwiseGameState {
         self.update_status();
         self.status
     }
-
-    pub fn player_has_max_teslas(&self) -> bool { self.player.count_teslas() >= TESLA_MAX }
-    pub fn opponent_has_max_teslas(&self) -> bool { self.opponent.count_teslas() >= TESLA_MAX }
-
-    pub fn player_can_build_iron_curtain(&self) -> bool {
-        self.player.iron_curtain_available && self.player.iron_curtain_remaining == 0 && self.player.energy >= IRON_CURTAIN_PRICE
-    }
-    pub fn opponent_can_build_iron_curtain(&self) -> bool {
-        self.opponent.iron_curtain_available && self.opponent.iron_curtain_remaining == 0 && self.opponent.energy >= IRON_CURTAIN_PRICE
-    }
-
-    pub fn unoccupied_player_cell_count(&self) -> usize { self.player.occupied.count_zeros() as usize }
-    pub fn unoccupied_opponent_cell_count(&self) -> usize { self.opponent.occupied.count_zeros() as usize }
-    pub fn location_of_unoccupied_player_cell(&self, i: usize) -> Point  {
-        let bit = find_bit_index_from_rank(self.player.occupied, i as u64);
-        let point = Point { index: bit };
-        debug_assert!(point.to_either_bitfield() & self.player.occupied == 0);
-        point
-    }
-    pub fn location_of_unoccupied_opponent_cell(&self, i: usize) -> Point {
-        let bit = find_bit_index_from_rank(self.opponent.occupied, i as u64);
-        let point = Point { index: bit };
-        debug_assert!(point.to_either_bitfield() & self.opponent.occupied == 0);
-        point
-    }
 }
 
 fn find_bit_index_from_rank(occupied: u64, i: u64) -> u8 {
@@ -147,9 +122,8 @@ impl BitwiseGameState {
     }
 
     /**
-     * Like with the expressive, this is to make things more
-     * comparable when writing tests, not for actual use in the
-     * engine.
+     * This is to make things more comparable when writing tests, not
+     * for actual use in the engine.
      */
     #[cfg(debug_assertions)]
     pub fn sort(&mut self) {
@@ -463,4 +437,20 @@ impl Player {
     pub fn energy_generated(&self) -> u16 {
         ENERGY_GENERATED_BASE + self.energy_towers.count_ones() as u16 * ENERGY_GENERATED_TOWER
     }
+
+    pub fn has_max_teslas(&self) -> bool {
+        self.count_teslas() >= TESLA_MAX
+    }
+
+    pub fn can_build_iron_curtain(&self) -> bool {
+        self.iron_curtain_available && self.iron_curtain_remaining == 0 && self.energy >= IRON_CURTAIN_PRICE
+    }
+
+    pub fn unoccupied_cell_count(&self) -> usize { self.occupied.count_zeros() as usize }
+    pub fn location_of_unoccupied_cell(&self, i: usize) -> Point  {
+        let bit = find_bit_index_from_rank(self.occupied, i as u64);
+        let point = Point { index: bit };
+        debug_assert!(point.to_either_bitfield() & self.occupied == 0);
+        point
+    }
 }
index 87033cb..3e6cb7a 100644 (file)
@@ -92,7 +92,7 @@ fn simulate_all_options_once(command_scores: &mut[CommandScore], state: &Bitwise
 fn simulate_to_endstate<R: Rng>(command_score: &mut CommandScore, state: &BitwiseGameState, rng: &mut R) {
     let mut state_mut = state.clone();
     
-    let opponent_first = random_opponent_move(&state_mut, rng);
+    let opponent_first = random_move(&state_mut.opponent, rng);
     let mut status = state_mut.simulate(command_score.command, opponent_first);
     
     for _ in 0..MAX_MOVES {
@@ -100,8 +100,8 @@ fn simulate_to_endstate<R: Rng>(command_score: &mut CommandScore, state: &Bitwis
             break;
         }
 
-        let player_command = random_player_move(&state_mut, rng);
-        let opponent_command = random_opponent_move(&state_mut, rng);
+        let player_command = random_move(&state_mut.player, rng);
+        let opponent_command = random_move(&state_mut.opponent, rng);
         status = state_mut.simulate(player_command, opponent_command);
     }
 
@@ -114,30 +114,23 @@ fn simulate_to_endstate<R: Rng>(command_score: &mut CommandScore, state: &Bitwis
     }
 }
 
-fn random_player_move<R: Rng>(state: &BitwiseGameState, rng: &mut R) -> Command {
-    let all_buildings = sensible_buildings(&state.player, state.player_has_max_teslas());
-    random_move(&all_buildings, state.player_can_build_iron_curtain(), rng, state.unoccupied_player_cell_count(), |i| state.location_of_unoccupied_player_cell(i))
-}
-
-fn random_opponent_move<R: Rng>(state: &BitwiseGameState, rng: &mut R) -> Command {
-    let all_buildings = sensible_buildings(&state.opponent, state.opponent_has_max_teslas());
-    random_move(&all_buildings, state.opponent_can_build_iron_curtain(), rng, state.unoccupied_opponent_cell_count(), |i| state.location_of_unoccupied_opponent_cell(i))
-}
-
 // TODO: Given enough energy, most opponents won't do nothing
-fn random_move<R: Rng, F:Fn(usize)->Point>(all_buildings: &[BuildingType], iron_curtain_available: bool, rng: &mut R, free_positions_count: usize, get_point: F) -> Command {
+fn random_move<R: Rng>(player: &Player, rng: &mut R) -> Command {
+    let all_buildings = sensible_buildings(player);
     let nothing_count = 1;
-    let iron_curtain_count = if iron_curtain_available { 1 } else { 0 };
+    let iron_curtain_count = if player.can_build_iron_curtain() { 1 } else { 0 };
+    let free_positions_count = player.unoccupied_cell_count();
+        
     let building_choice_index = rng.gen_range(0, all_buildings.len() + nothing_count + iron_curtain_count);
     
     if building_choice_index == all_buildings.len() {
         Command::Nothing
-    } else if iron_curtain_available && building_choice_index == all_buildings.len() + 1 {
+    } else if iron_curtain_count > 0 && building_choice_index == all_buildings.len() + 1 {
         Command::IronCurtain
     } else if free_positions_count > 0 {
         let position_choice = rng.gen_range(0, free_positions_count);
         Command::Build(
-            get_point(position_choice),
+            player.location_of_unoccupied_cell(position_choice),
             all_buildings[building_choice_index]
         )
     } else {
@@ -199,15 +192,15 @@ impl CommandScore {
 
     //TODO: Devalue nothing so that it doesn't stand and do nothing when it can do things
     fn init_command_scores(state: &BitwiseGameState) -> Vec<CommandScore> {
-        let all_buildings = sensible_buildings(&state.player, state.player_has_max_teslas());
+        let all_buildings = sensible_buildings(&state.player);
 
-        let unoccupied_cells = (0..state.unoccupied_player_cell_count()).map(|i| state.location_of_unoccupied_player_cell(i));
+        let unoccupied_cells = (0..state.player.unoccupied_cell_count()).map(|i| state.player.location_of_unoccupied_cell(i));
 
         let building_command_count = unoccupied_cells.len()*all_buildings.len();
         
         let mut commands = Vec::with_capacity(building_command_count + 2);
         commands.push(CommandScore::new(Command::Nothing));
-        if state.player_can_build_iron_curtain() {
+        if state.player.can_build_iron_curtain() {
             commands.push(CommandScore::new(Command::IronCurtain));
         }
 
@@ -222,7 +215,7 @@ impl CommandScore {
 }
 
 #[cfg(not(feature = "energy-cutoff"))]
-fn sensible_buildings(player: &Player, has_max_teslas: bool) -> Vec<BuildingType> {
+fn sensible_buildings(player: &Player) -> Vec<BuildingType> {
     let mut result = Vec::with_capacity(4);
 
     if DEFENCE_PRICE <= player.energy {
@@ -234,7 +227,7 @@ fn sensible_buildings(player: &Player, has_max_teslas: bool) -> Vec<BuildingType
     if ENERGY_PRICE <= player.energy {
         result.push(BuildingType::Energy);
     }
-    if TESLA_PRICE <= player.energy && !has_max_teslas {
+    if TESLA_PRICE <= player.energy && !player.has_max_teslas() {
         result.push(BuildingType::Tesla);
     }
 
@@ -245,7 +238,7 @@ fn sensible_buildings(player: &Player, has_max_teslas: bool) -> Vec<BuildingType
 //TODO: Heuristic that avoids building the initial energy towers all in the same row? Max energy in a row?
 //TODO: Update cutoff to maybe build iron curtains
 #[cfg(feature = "energy-cutoff")]
-fn sensible_buildings(player: &Player, has_max_teslas: bool) -> Vec<BuildingType> {
+fn sensible_buildings(player: &Player) -> Vec<BuildingType> {
     let mut result = Vec::with_capacity(4);
     let needs_energy = player.energy_generated() <= ENERGY_PRODUCTION_CUTOFF ||
         player.energy <= ENERGY_STORAGE_CUTOFF;
@@ -259,7 +252,7 @@ fn sensible_buildings(player: &Player, has_max_teslas: bool) -> Vec<BuildingType
     if ENERGY_PRICE <= player.energy && needs_energy {
         result.push(BuildingType::Energy);
     }
-    if TESLA_PRICE <= player.energy && !has_max_teslas {
+    if TESLA_PRICE <= player.energy && !player.has_max_teslas() {
         result.push(BuildingType::Tesla);
     }