524ddf71083022ee71f8d41ca7a9a9748d21c03f
[entelect-challenge-tower-defence.git] / src / engine / bitwise_engine.rs
1 use engine::command::{Command, BuildingType};
2 use engine::geometry::Point;
3 use engine::settings::{GameSettings};
4 use engine::{GameStatus, Player, GameState};
5
6 const FULL_MAP_WIDTH: u8 = 16;
7 const SINGLE_MAP_WIDTH: u8 = FULL_MAP_WIDTH/2;
8 const MAX_CONCURRENT_MISSILES: usize = SINGLE_MAP_WIDTH as usize / 2;
9
10 const MISSILE_COOLDOWN: usize = 3;
11
12 const DEFENCE_HEALTH: usize = 4; // '20' health is 4 hits
13
14 const MAX_TESLAS: usize = 2;
15
16 const LEFT_COL_MASK: u64 = 0x0101010101010101;
17 const RIGHT_COL_MASK: u64 = 0x8080808080808080;
18
19 #[derive(Debug, Clone, PartialEq, Eq)]
20 pub struct BitwiseGameState {
21     pub status: GameStatus,
22     pub player: Player,
23     pub opponent: Player,
24     pub player_buildings: PlayerBuildings,
25     pub opponent_buildings: PlayerBuildings,
26 }
27
28 #[derive(Debug, Clone, PartialEq, Eq)]
29 pub struct PlayerBuildings {
30     pub unconstructed: Vec<UnconstructedBuilding>,
31     pub buildings: [u64; DEFENCE_HEALTH],
32     pub occupied: u64,
33     
34     pub energy_towers: u64,
35     pub missile_towers: [u64; MISSILE_COOLDOWN+1],
36     
37     pub missiles: [(u64, u64); MAX_CONCURRENT_MISSILES],
38     pub tesla_cooldowns: [TeslaCooldown; MAX_TESLAS]
39 }
40
41 #[derive(Debug, Clone, PartialEq, Eq)]
42 pub struct UnconstructedBuilding {
43     pub pos: Point,
44     pub construction_time_left: u8,
45     pub building_type: BuildingType
46 }
47
48 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
49 pub struct TeslaCooldown {
50     pub active: bool,
51     pub pos: Point,
52     pub cooldown: u8
53 }
54
55
56 impl GameState for BitwiseGameState {
57     fn simulate(&mut self, settings: &GameSettings, player_command: Command, opponent_command: Command) -> GameStatus {
58         BitwiseGameState::perform_command(settings, &mut self.player, &mut self.player_buildings, player_command);      BitwiseGameState::perform_command(settings, &mut self.opponent, &mut self.opponent_buildings, opponent_command);
59
60         BitwiseGameState::update_construction(settings, &mut self.player_buildings);
61         BitwiseGameState::update_construction(settings, &mut self.opponent_buildings);
62         
63         //TODO: Fire the TESLAS!
64
65         BitwiseGameState::add_left_missiles(&mut self.player_buildings);
66         BitwiseGameState::add_right_missiles(&mut self.opponent_buildings);
67
68         BitwiseGameState::move_left_and_collide_missiles(settings, &mut self.player, &mut self.player_buildings, &mut self.opponent_buildings.missiles);
69         BitwiseGameState::move_right_and_collide_missiles(settings, &mut self.opponent, &mut self.opponent_buildings, &mut self.player_buildings.missiles);
70
71         BitwiseGameState::add_energy(settings, &mut self.player, &mut self.player_buildings);
72         BitwiseGameState::add_energy(settings, &mut self.opponent, &mut self.opponent_buildings);
73
74         self.update_status();
75         self.status
76     }
77
78
79     fn player(&self) -> &Player { &self.player }
80     fn opponent(&self) -> &Player { &self.opponent }
81     fn player_has_max_teslas(&self) -> bool { self.player_buildings.count_teslas() >= MAX_TESLAS }
82     fn opponent_has_max_teslas(&self) -> bool { self.opponent_buildings.count_teslas() >= MAX_TESLAS }
83
84     fn unoccupied_player_cell_count(&self) -> usize { self.player_buildings.occupied.count_zeros() as usize }
85     fn unoccupied_opponent_cell_count(&self) -> usize { self.opponent_buildings.occupied.count_zeros() as usize }
86     fn location_of_unoccupied_player_cell(&self, i: usize) -> Point  {
87         let mut current = 0;
88         for bit in 0..64 {
89             let is_free = (1 << bit) & self.player_buildings.occupied == 0;
90             if is_free && current == i{
91                 return Point::new(bit%SINGLE_MAP_WIDTH, bit/SINGLE_MAP_WIDTH);
92             } else if is_free {
93                 current += 1;
94             }
95         }
96         panic!("Didn't find indicated free bit for player");
97     }
98     fn location_of_unoccupied_opponent_cell(&self, i: usize) -> Point {
99         let mut current = 0;
100         for bit in 0..64 {
101             let is_free = (1 << bit) & self.opponent_buildings.occupied == 0;
102             if is_free && current == i{
103                 return Point::new((bit%SINGLE_MAP_WIDTH) + SINGLE_MAP_WIDTH, bit/SINGLE_MAP_WIDTH);
104             } else if is_free {
105                 current += 1;
106             }
107         }
108         panic!("Didn't find indicated free bit for opponent");
109     }
110 }
111
112 impl BitwiseGameState {
113     pub fn new(
114         player: Player, opponent: Player,
115         player_buildings: PlayerBuildings, opponent_buildings: PlayerBuildings
116     ) -> BitwiseGameState {
117         BitwiseGameState {
118             status: GameStatus::Continue,
119             player, opponent,
120             player_buildings, opponent_buildings
121         }
122     }
123
124     /**
125      * Like with the expressive, this is to make things more
126      * comparable when writing tests, not for actual use in the
127      * engine.
128      */
129     pub fn sort(&mut self) {
130         for i in 0..MAX_CONCURRENT_MISSILES {
131             for j in i+1..MAX_CONCURRENT_MISSILES {
132                 let move_down1 = !self.player_buildings.missiles[i].0 & self.player_buildings.missiles[j].0;
133                 self.player_buildings.missiles[i].0 |= move_down1;
134                 self.player_buildings.missiles[j].0 &= !move_down1;
135
136                 let move_down2 = !self.player_buildings.missiles[i].1 & self.player_buildings.missiles[j].1;
137                 self.player_buildings.missiles[i].1 |= move_down2;
138                 self.player_buildings.missiles[j].1 &= !move_down2;
139
140                 let move_down3 = !self.opponent_buildings.missiles[i].0 & self.opponent_buildings.missiles[j].0;
141                 self.opponent_buildings.missiles[i].0 |= move_down3;
142                 self.opponent_buildings.missiles[j].0 &= !move_down3;
143
144                 let move_down4 = !self.opponent_buildings.missiles[i].1 & self.opponent_buildings.missiles[j].1;
145                 self.opponent_buildings.missiles[i].1 |= move_down4;
146                 self.opponent_buildings.missiles[j].1 &= !move_down4;
147             }
148         }
149
150         self.player_buildings.unconstructed.sort_by_key(|b| b.pos);
151         self.opponent_buildings.unconstructed.sort_by_key(|b| b.pos);
152     }
153
154     pub fn sorted(&self) -> BitwiseGameState {
155         let mut res = self.clone();
156         res.sort();
157         res
158     }
159
160     fn perform_command(settings: &GameSettings, player: &mut Player, player_buildings: &mut PlayerBuildings, command: Command) {
161         match command {
162             Command::Nothing => {},
163             Command::Build(p, b) => {
164                 let blueprint = settings.building_settings(b);
165                 let bitfield = p.to_either_bitfield(SINGLE_MAP_WIDTH);
166
167                 // This is used internally. I should not be making
168                 // invalid moves!
169                 debug_assert!(player_buildings.buildings[0] & bitfield == 0);
170                 debug_assert!(p.x < settings.size.x && p.y < settings.size.y);
171                 debug_assert!(player.energy >= blueprint.price);
172                 debug_assert!(b != BuildingType::Tesla ||
173                               player_buildings.count_teslas() < MAX_TESLAS);
174
175                 player.energy -= blueprint.price;
176                 player_buildings.unconstructed.push(UnconstructedBuilding {
177                     pos: p,
178                     construction_time_left: blueprint.construction_time,
179                     building_type: b
180                 });
181                 player_buildings.occupied |= bitfield;
182             },
183             Command::Deconstruct(p) => {
184                 let unconstructed_to_remove_index = player_buildings.unconstructed.iter().position(|ref b| b.pos == p);
185                 let deconstruct_mask = !(p.to_either_bitfield(SINGLE_MAP_WIDTH) & player_buildings.buildings[0]);
186                 
187                 debug_assert!(deconstruct_mask != 0 || unconstructed_to_remove_index.is_some());
188                 
189                 if let Some(i) = unconstructed_to_remove_index {
190                     player_buildings.unconstructed.swap_remove(i);
191                 }
192                 
193                 player.energy += 5;
194                 
195                 for tier in 0..player_buildings.buildings.len() {
196                     player_buildings.buildings[tier] &= deconstruct_mask;
197                 }
198                 player_buildings.energy_towers &= deconstruct_mask;
199                 for tier in 0..player_buildings.missile_towers.len() {
200                     player_buildings.missile_towers[tier] &= deconstruct_mask;
201                 }
202                 for tesla in 0..player_buildings.tesla_cooldowns.len() {
203                     if player_buildings.tesla_cooldowns[tesla].pos == p {
204                         player_buildings.tesla_cooldowns[tesla].active = false;
205                     }
206                 }
207                 player_buildings.occupied &= deconstruct_mask;
208             }
209         }
210     }
211
212     fn update_construction(settings: &GameSettings, player_buildings: &mut PlayerBuildings) {
213         let mut buildings_len = player_buildings.unconstructed.len();
214         for i in (0..buildings_len).rev() {
215             if player_buildings.unconstructed[i].construction_time_left == 0 {
216                 let building_type = player_buildings.unconstructed[i].building_type;
217                 let blueprint = settings.building_settings(building_type);
218                 let pos = player_buildings.unconstructed[i].pos;
219                 let bitfield = pos.to_either_bitfield(SINGLE_MAP_WIDTH);
220                 
221                 for health_tier in 0..4 {
222                     if blueprint.health > health_tier*5 {
223                         player_buildings.buildings[health_tier as usize] |= bitfield;
224                     }
225                 }
226                 if building_type == BuildingType::Energy {
227                     player_buildings.energy_towers |= bitfield;
228                 }
229                 if building_type == BuildingType::Attack {
230                     player_buildings.missile_towers[0] |= bitfield;
231                 }
232                 if building_type == BuildingType::Tesla {
233                     let ref mut tesla_cooldown = if player_buildings.tesla_cooldowns[0].active {
234                         player_buildings.tesla_cooldowns[1]
235                     } else {
236                         player_buildings.tesla_cooldowns[0]
237                     };
238                     tesla_cooldown.active = true;
239                     tesla_cooldown.pos = pos;
240                     tesla_cooldown.cooldown = 0;
241                 }
242                 
243                 buildings_len -= 1;
244                 player_buildings.unconstructed.swap(i, buildings_len);
245             } else {
246                 player_buildings.unconstructed[i].construction_time_left -= 1
247             }
248         }
249         player_buildings.unconstructed.truncate(buildings_len);
250     }
251
252
253     fn add_left_missiles(player_buildings: &mut PlayerBuildings) {
254         let mut missiles = player_buildings.missile_towers[0];
255         for mut tier in player_buildings.missiles.iter_mut() {
256             let setting = !tier.0 & missiles;
257             tier.0 |= setting;
258             missiles &= !setting;
259         }
260
261         BitwiseGameState::rotate_missile_towers(player_buildings);
262     }
263
264     fn add_right_missiles(player_buildings: &mut PlayerBuildings) {
265         let mut missiles = player_buildings.missile_towers[0];
266         for mut tier in player_buildings.missiles.iter_mut() {
267             let setting = !tier.1 & missiles;
268             tier.1 |= setting;
269             missiles &= !setting;
270         }
271
272         BitwiseGameState::rotate_missile_towers(player_buildings);
273     }
274
275     fn rotate_missile_towers(player_buildings: &mut PlayerBuildings) {
276         let zero = player_buildings.missile_towers[0];
277         for i in 1..player_buildings.missile_towers.len() {
278             player_buildings.missile_towers[i-1] = player_buildings.missile_towers[i];
279         }
280         let end = player_buildings.missile_towers.len()-1;
281         player_buildings.missile_towers[end] = zero;
282     }
283
284
285     fn move_left_and_collide_missiles(settings: &GameSettings, opponent: &mut Player, opponent_buildings: &mut PlayerBuildings, player_missiles: &mut [(u64, u64); MAX_CONCURRENT_MISSILES]) {
286         for _ in 0..settings.attack.weapon_speed {
287             for i in 0..player_missiles.len() {
288                 let about_to_hit_opponent = player_missiles[i].0 & LEFT_COL_MASK;
289                 let damage = about_to_hit_opponent.count_ones() as u8 * settings.attack.weapon_damage;
290                 opponent.health = opponent.health.saturating_sub(damage);
291                 player_missiles[i].0 = (player_missiles[i].0 & !LEFT_COL_MASK) >> 1;
292
293                 let swapping_sides = player_missiles[i].1 & LEFT_COL_MASK;
294                 player_missiles[i].0 |= swapping_sides << 7;
295                 player_missiles[i].1 = (player_missiles[i].1 & !LEFT_COL_MASK) >> 1;
296
297
298                 let mut hits = 0;
299                 for health_tier in (0..DEFENCE_HEALTH).rev() {
300                     hits = opponent_buildings.buildings[health_tier] & player_missiles[i].0;
301                     player_missiles[i].0 &= !hits;
302                     opponent_buildings.buildings[health_tier] &= !hits;
303                 }
304
305                 let deconstruct_mask = !hits;
306                 opponent_buildings.energy_towers &= deconstruct_mask;
307                 for tier in 0..opponent_buildings.missile_towers.len() {
308                     opponent_buildings.missile_towers[tier] &= deconstruct_mask;
309                 }
310                 for tesla in 0..opponent_buildings.tesla_cooldowns.len() {
311                     if opponent_buildings.tesla_cooldowns[tesla].pos.to_either_bitfield(SINGLE_MAP_WIDTH) & deconstruct_mask == 0 {
312                         opponent_buildings.tesla_cooldowns[tesla].active = false;
313                     }
314                 }
315                 opponent_buildings.occupied &= deconstruct_mask;
316             }
317         }
318     }
319
320     fn move_right_and_collide_missiles(settings: &GameSettings, opponent: &mut Player, opponent_buildings: &mut PlayerBuildings, player_missiles: &mut [(u64, u64); MAX_CONCURRENT_MISSILES]) {
321         for _ in 0..settings.attack.weapon_speed {
322             for i in 0..player_missiles.len() {
323                 let about_to_hit_opponent = player_missiles[i].1 & RIGHT_COL_MASK;
324                 let damage = about_to_hit_opponent.count_ones() as u8 * settings.attack.weapon_damage;
325                 opponent.health = opponent.health.saturating_sub(damage);
326                 player_missiles[i].1 = (player_missiles[i].1 & !RIGHT_COL_MASK) << 1;
327
328                 let swapping_sides = player_missiles[i].0 & RIGHT_COL_MASK;
329                 player_missiles[i].1 |= swapping_sides >> 7;
330                 player_missiles[i].0 = (player_missiles[i].0 & !RIGHT_COL_MASK) << 1;
331
332                 
333                 let mut hits = 0;
334                 for health_tier in (0..DEFENCE_HEALTH).rev() {
335                     hits = opponent_buildings.buildings[health_tier] & player_missiles[i].1;
336                     player_missiles[i].1 &= !hits;
337                     opponent_buildings.buildings[health_tier] &= !hits;
338                 }
339
340                 let deconstruct_mask = !hits;
341                 opponent_buildings.energy_towers &= deconstruct_mask;
342                 for tier in 0..opponent_buildings.missile_towers.len() {
343                     opponent_buildings.missile_towers[tier] &= deconstruct_mask;
344                 }
345                 for tesla in 0..opponent_buildings.tesla_cooldowns.len() {
346                     if opponent_buildings.tesla_cooldowns[tesla].pos.to_either_bitfield(SINGLE_MAP_WIDTH) & deconstruct_mask == 0 {
347                         opponent_buildings.tesla_cooldowns[tesla].active = false;
348                     }
349                 }
350                 opponent_buildings.occupied &= deconstruct_mask;
351             }
352         }
353     }
354     
355     
356     fn add_energy(settings: &GameSettings, player: &mut Player, player_buildings: &mut PlayerBuildings) {
357         player.energy_generated = settings.energy_income + player_buildings.energy_towers.count_ones() as u16 * settings.energy.energy_generated_per_turn;
358         player.energy += player.energy_generated;
359     }
360
361     fn update_status(&mut self) {
362         let player_dead = self.player.health == 0;
363         let opponent_dead = self.opponent.health == 0;
364         self.status = match (player_dead, opponent_dead) {
365             (true, true) => GameStatus::Draw,
366             (false, true) => GameStatus::PlayerWon,
367             (true, false) => GameStatus::OpponentWon,
368             (false, false) => GameStatus::Continue,
369         };
370     }
371
372 }
373
374 impl PlayerBuildings {
375     pub fn count_teslas(&self) -> usize {
376         self.tesla_cooldowns.iter().filter(|t| t.active).count()
377     }
378
379     pub fn empty() -> PlayerBuildings {
380         PlayerBuildings {
381             unconstructed: Vec::with_capacity(4),
382             buildings: [0; 4],
383             occupied: 0,
384             energy_towers: 0,
385             missile_towers: [0; 4],
386             missiles: [(0,0); 4],
387             tesla_cooldowns: [TeslaCooldown::empty(); 2]
388         }
389     }
390 }
391
392 impl TeslaCooldown {
393     pub fn empty() -> TeslaCooldown {
394         TeslaCooldown {
395             active: false,
396             pos: Point::new(0,0),
397             cooldown: 0
398         }
399     }
400 }