bdc4a193c87091a3169febbe896fac0d71a941b2
[entelect-challenge-tower-defence.git] / tests / expressive_to_bitwise_comparison.rs
1 extern crate zombot;
2
3 #[macro_use] extern crate proptest;
4 extern crate rand;
5
6 use zombot::input;
7 use zombot::engine::command::{Command, BuildingType};
8 use zombot::engine::geometry::Point;
9 use zombot::engine::settings::GameSettings;
10 use zombot::engine::{GameState, GameStatus, Player};
11
12 use zombot::engine::expressive_engine;
13 use zombot::engine::bitwise_engine;
14
15 use proptest::prelude::*;
16
17 use rand::{Rng, XorShiftRng, SeedableRng};
18
19
20 const STATE_PATH: &str = "tests/state0.json";
21
22 #[test]
23 fn reads_into_bitwise_correctly() {
24     test_reading_from_replay("tests/after_200", 64);
25 }
26
27 fn test_reading_from_replay(replay_folder: &str, length: usize) {
28     for i in 0..length {
29         let state_file = format!("{}/Round {:03}/state.json", replay_folder, i);
30
31         let (_, expressive_state) = input::json::read_expressive_state_from_file(&state_file).expect("Failed to load expressive state");
32         let (_, bitwise_state) = input::json::read_bitwise_state_from_file(&state_file).expect("Failed to load bitwise state");
33
34         assert_eq!(build_bitwise_from_expressive(&expressive_state), bitwise_state.clone(), "\nFailed on state {}\n", i);
35     }
36 }
37
38
39 proptest! {
40     #[test]
41     fn follows_the_same_random_game_tree(seed in any::<[u32;4]>()) {
42         let mut rng = XorShiftRng::from_seed(seed);
43         
44         let (settings, mut expressive_state) = input::json::read_expressive_state_from_file(STATE_PATH).expect("Failed to load expressive state");
45         let (_, mut bitwise_state) = input::json::read_bitwise_state_from_file(STATE_PATH).expect("Failed to load bitwise state");
46
47         let mut expected_status = GameStatus::Continue;
48         while expected_status == GameStatus::Continue {
49             let player_command = random_player_move(&settings, &expressive_state, &bitwise_state, &mut rng);
50             let opponent_command = random_opponent_move(&settings, &expressive_state, &bitwise_state, &mut rng);
51             println!("Player command: {}", player_command);
52             println!("Opponent command: {}", opponent_command);
53
54             expected_status = expressive_state.simulate(&settings, player_command, opponent_command);
55             let actual_status = bitwise_state.simulate(&settings, player_command, opponent_command);
56
57             expressive_state.sort();
58             
59             assert_eq!(&expected_status, &actual_status);
60             assert_eq!(build_bitwise_from_expressive(&expressive_state), bitwise_state.sorted());
61         }
62     }
63 }
64
65 fn random_player_move<R: Rng, GSE: GameState, GSB: GameState>(settings: &GameSettings, expressive_state: &GSE, bitwise_state: &GSB, rng: &mut R) -> Command {
66     assert_eq!(expressive_state.player_has_max_teslas(), bitwise_state.player_has_max_teslas());
67     let all_buildings = sensible_buildings(settings, &expressive_state.player(), expressive_state.player_has_max_teslas());
68     random_move(&all_buildings, rng, expressive_state.unoccupied_player_cell_count(), |i| expressive_state.location_of_unoccupied_player_cell(i), |i| bitwise_state.location_of_unoccupied_player_cell(i))
69 }
70
71 fn random_opponent_move<R: Rng, GSE: GameState, GSB: GameState>(settings: &GameSettings, expressive_state: &GSE, bitwise_state: &GSB, rng: &mut R) -> Command {
72     assert_eq!(expressive_state.player_has_max_teslas(), bitwise_state.player_has_max_teslas());
73     let all_buildings = sensible_buildings(settings, &expressive_state.opponent(), expressive_state.opponent_has_max_teslas());
74     random_move(&all_buildings, rng, expressive_state.unoccupied_opponent_cell_count(), |i| expressive_state.location_of_unoccupied_opponent_cell(i), |i| bitwise_state.location_of_unoccupied_opponent_cell(i))
75 }
76
77 fn random_move<R: Rng, FE:Fn(usize)->Point, FB:Fn(usize)->Point>(all_buildings: &[BuildingType], rng: &mut R, free_positions_count: usize, get_point_expressive: FE, get_point_bitwise: FB) -> Command {
78     let building_command_count = free_positions_count*all_buildings.len();
79     let nothing_count = 1;
80
81     let number_of_commands = building_command_count + nothing_count;
82     
83     let choice_index = rng.gen_range(0, number_of_commands);
84
85     if choice_index == number_of_commands - 1 {
86         Command::Nothing
87     } else {
88         let expressive_point = get_point_expressive(choice_index/all_buildings.len());
89         let bitwise_point = get_point_bitwise(choice_index/all_buildings.len());
90         assert_eq!(expressive_point, bitwise_point);
91         Command::Build(
92             expressive_point,
93             all_buildings[choice_index%all_buildings.len()]
94         )
95     }
96 }
97
98 fn sensible_buildings(settings: &GameSettings, player: &Player, has_max_teslas: bool) -> Vec<BuildingType> {
99     let mut result = Vec::with_capacity(4);
100     for b in BuildingType::all().iter() {
101         let building_setting = settings.building_settings(*b);
102         let affordable = building_setting.price <= player.energy;
103         let is_tesla = *b == BuildingType::Tesla;
104         if affordable && (!is_tesla || !has_max_teslas) {
105             result.push(*b);
106         }
107     }
108     result
109 }
110
111 fn build_bitwise_from_expressive(expressive: &expressive_engine::ExpressiveGameState) -> bitwise_engine::BitwiseGameState {
112     let player_unconstructed = expressive.player_unconstructed_buildings.iter()
113         .map(build_bitwise_unconstructed_from_expressive)
114         .collect();
115     let opponent_unconstructed = expressive.opponent_unconstructed_buildings.iter()
116         .map(build_bitwise_unconstructed_from_expressive)
117         .collect();
118     
119     let player_energy = expressive.player_buildings.iter()
120         .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Energy)
121         .fold(0, |acc, next| acc | next.pos.to_left_bitfield());
122     let opponent_energy = expressive.opponent_buildings.iter()
123         .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Energy)
124         .fold(0, |acc, next| acc | next.pos.to_right_bitfield());
125
126     let mut player_buildings_iter = (0..4)
127         .map(|i| expressive.player_buildings.iter()
128              .filter(|b| b.health > i*5)
129              .fold(0, |acc, next| acc | next.pos.to_left_bitfield())
130         );
131     let mut opponent_buildings_iter = (0..4)
132         .map(|i| expressive.opponent_buildings.iter()
133              .filter(|b| b.health > i*5)
134              .fold(0, |acc, next| acc | next.pos.to_right_bitfield())
135         );
136
137     let player_occupied = expressive.player_buildings.iter()
138         .fold(0, |acc, next| acc | next.pos.to_left_bitfield()) |
139     expressive.player_unconstructed_buildings.iter()
140         .fold(0, |acc, next| acc | next.pos.to_left_bitfield());
141     let opponent_occupied = expressive.opponent_buildings.iter()
142         .fold(0, |acc, next| acc | next.pos.to_right_bitfield()) |
143     expressive.opponent_unconstructed_buildings.iter()
144         .fold(0, |acc, next| acc | next.pos.to_right_bitfield());
145
146     let mut player_attack_iter = (0..4)
147         .map(|i| expressive.player_buildings.iter()
148              .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Attack)
149              .filter(|b| b.weapon_cooldown_time_left == i)
150              .fold(0, |acc, next| acc | next.pos.to_left_bitfield())
151         );
152     let mut opponent_attack_iter = (0..4)
153         .map(|i| expressive.opponent_buildings.iter()
154              .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Attack)
155              .filter(|b| b.weapon_cooldown_time_left == i)
156              .fold(0, |acc, next| acc | next.pos.to_right_bitfield())
157         );
158
159     let empty_missiles: [(u64,u64);4] = [(0,0),(0,0),(0,0),(0,0)];
160     let player_missiles = expressive.player_missiles.iter()
161         .fold(empty_missiles, |acc, m| {
162             let (mut left, mut right) = m.pos.to_bitfield();
163             let mut res = acc.clone();
164             for mut tier in res.iter_mut() {
165                 let setting = (!tier.0 & left, !tier.1 & right);
166                 tier.0 |= setting.0;
167                 tier.1 |= setting.1;
168                 left &= !setting.0;
169                 right &= !setting.1;
170             }
171             res
172         });
173     let opponent_missiles = expressive.opponent_missiles.iter()
174         .fold(empty_missiles, |acc, m| {
175             let (mut left, mut right) = m.pos.to_bitfield();
176             let mut res = acc.clone();
177             for mut tier in res.iter_mut() {
178                 let setting = (!tier.0 & left, !tier.1 & right);
179                 tier.0 |= setting.0;
180                 tier.1 |= setting.1;
181                 left &= !setting.0;
182                 right &= !setting.1;
183             }
184             res
185         });
186
187     let null_tesla = bitwise_engine::TeslaCooldown {
188         active: false,
189         pos: Point::new(0,0),
190         cooldown: 0
191     };
192     let mut player_tesla_iter = expressive.player_buildings.iter()
193         .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Tesla)
194         .map(|b| bitwise_engine::TeslaCooldown {
195             active: true,
196             pos: b.pos,
197             cooldown: b.weapon_cooldown_time_left
198         });
199     let mut opponent_tesla_iter = expressive.opponent_buildings.iter()
200         .filter(|b| identify_building_type(b.weapon_damage, b.energy_generated_per_turn) == BuildingType::Tesla)
201         .map(|b| bitwise_engine::TeslaCooldown {
202             active: true,
203             pos: b.pos,
204             cooldown: b.weapon_cooldown_time_left
205         });
206     bitwise_engine::BitwiseGameState {
207         status: expressive.status,
208         player: expressive.player.clone(),
209         opponent: expressive.opponent.clone(),
210         player_buildings: bitwise_engine::PlayerBuildings {
211             unconstructed: player_unconstructed,
212             buildings: [player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap(), player_buildings_iter.next().unwrap()],
213             occupied: player_occupied,
214             energy_towers: player_energy,
215             missile_towers: [player_attack_iter.next().unwrap(), player_attack_iter.next().unwrap(), player_attack_iter.next().unwrap(), player_attack_iter.next().unwrap()],
216             missiles: player_missiles,
217             tesla_cooldowns: [player_tesla_iter.next().unwrap_or(null_tesla.clone()),
218                               player_tesla_iter.next().unwrap_or(null_tesla.clone())]
219         },
220         opponent_buildings: bitwise_engine::PlayerBuildings {
221             unconstructed: opponent_unconstructed,
222             buildings: [opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap(), opponent_buildings_iter.next().unwrap()],
223             occupied: opponent_occupied,
224             energy_towers: opponent_energy,
225             missile_towers: [opponent_attack_iter.next().unwrap(), opponent_attack_iter.next().unwrap(), opponent_attack_iter.next().unwrap(), opponent_attack_iter.next().unwrap()],
226             missiles: opponent_missiles,
227             tesla_cooldowns: [opponent_tesla_iter.next().unwrap_or(null_tesla.clone()),
228                               opponent_tesla_iter.next().unwrap_or(null_tesla.clone())]
229         }
230     }
231 }
232
233 fn build_bitwise_unconstructed_from_expressive(b: &expressive_engine::UnconstructedBuilding) -> bitwise_engine::UnconstructedBuilding {
234     bitwise_engine::UnconstructedBuilding {
235         pos: b.pos,
236         construction_time_left: b.construction_time_left,
237         building_type: identify_building_type(b.weapon_damage, b.energy_generated_per_turn)
238     }
239 }
240
241 fn identify_building_type(weapon_damage: u8, energy_generated_per_turn: u16) -> BuildingType {
242     match (weapon_damage, energy_generated_per_turn) {
243         (5, _) => BuildingType::Attack,
244         (20, _) => BuildingType::Tesla,
245         (_, 3) => BuildingType::Energy,
246         _ => BuildingType::Defence
247     }
248 }