227a7f6b1abcc6405ad6d0bc32ad2ca5bb997ea4
[entelect-challenge-tower-defence.git] / src / engine / mod.rs
1 pub mod command;
2 pub mod geometry;
3 pub mod settings;
4
5 use self::command::{Command, BuildingType};
6 use self::geometry::Point;
7 use self::settings::{GameSettings, BuildingSettings};
8
9 use std::ops::Fn;
10 use std::cmp;
11
12 #[derive(Debug, Clone, PartialEq)]
13 pub struct GameState {
14     pub status: GameStatus,
15     pub player: Player,
16     pub opponent: Player,
17     pub player_unconstructed_buildings: Vec<UnconstructedBuilding>,
18     pub player_buildings: Vec<Building>,
19     pub unoccupied_player_cells: Vec<Point>,
20     pub opponent_unconstructed_buildings: Vec<UnconstructedBuilding>,
21     pub opponent_buildings: Vec<Building>,
22     pub unoccupied_opponent_cells: Vec<Point>,
23     pub player_missiles: Vec<Missile>,
24     pub opponent_missiles: Vec<Missile>
25 }
26
27 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
28 pub enum GameStatus {
29     Continue,
30     PlayerWon,
31     OpponentWon,
32     Draw
33 }
34
35 #[derive(Debug, Clone, PartialEq)]
36 pub struct Player {
37     pub energy: u16,
38     pub health: u8,
39     pub energy_generated: u16,
40 }
41
42 #[derive(Debug, Clone, PartialEq)]
43 pub struct UnconstructedBuilding {
44     pub pos: Point,
45     pub health: u8,
46     pub construction_time_left: u8,
47     pub weapon_damage: u8,
48     pub weapon_speed: u8,
49     pub weapon_cooldown_period: u8,
50     pub energy_generated_per_turn: u16
51 }
52
53 #[derive(Debug, Clone, PartialEq)]
54 pub struct Building {
55     pub pos: Point,
56     pub health: u8,
57     pub weapon_damage: u8,
58     pub weapon_speed: u8,
59     pub weapon_cooldown_time_left: u8,
60     pub weapon_cooldown_period: u8,
61     pub energy_generated_per_turn: u16
62 }
63
64 #[derive(Debug, Clone, PartialEq)]
65 pub struct Missile {
66     pub pos: Point,
67     pub damage: u8,
68     pub speed: u8,
69 }
70
71 impl GameState {
72     pub fn new(
73         player: Player, opponent: Player,
74         player_unconstructed_buildings: Vec<UnconstructedBuilding>, player_buildings: Vec<Building>,
75         opponent_unconstructed_buildings: Vec<UnconstructedBuilding>, opponent_buildings: Vec<Building>,
76         player_missiles: Vec<Missile>, opponent_missiles: Vec<Missile>,
77         settings: &GameSettings) -> GameState {
78         
79         let unoccupied_player_cells = GameState::unoccupied_cells(
80             &player_buildings, &player_unconstructed_buildings, Point::new(0, 0), Point::new(settings.size.x/2, settings.size.y)
81         );
82         let unoccupied_opponent_cells = GameState::unoccupied_cells(
83             &opponent_buildings, &opponent_unconstructed_buildings, Point::new(settings.size.x/2, 0), Point::new(settings.size.x, settings.size.y)
84         );
85         GameState {
86             status: GameStatus::Continue,
87             player: player,
88             opponent: opponent,
89             player_unconstructed_buildings: player_unconstructed_buildings,
90             player_buildings: player_buildings,
91             unoccupied_player_cells: unoccupied_player_cells,
92             opponent_unconstructed_buildings: opponent_unconstructed_buildings,
93             opponent_buildings: opponent_buildings,
94             unoccupied_opponent_cells: unoccupied_opponent_cells,
95             player_missiles: player_missiles,
96             opponent_missiles: opponent_missiles
97         }
98     }
99
100     /**
101      * Sorts the various arrays. Generally not necessary, but useful
102      * for tests that check equality between states.
103      */
104     pub fn sort(&mut self) {
105         self.player_unconstructed_buildings.sort_by_key(|b| b.pos);
106         self.player_buildings.sort_by_key(|b| b.pos);
107         self.unoccupied_player_cells.sort();
108         self.opponent_unconstructed_buildings.sort_by_key(|b| b.pos);
109         self.opponent_buildings.sort_by_key(|b| b.pos);
110         self.unoccupied_opponent_cells.sort();
111         self.player_missiles.sort_by_key(|b| b.pos);
112         self.opponent_missiles.sort_by_key(|b| b.pos);
113     }
114
115     pub fn simulate(&self, settings: &GameSettings, player_command: Command, opponent_command: Command) -> GameState {
116         let mut state = self.clone();
117         state.simulate_mut(settings, player_command, opponent_command);
118         state
119     }
120
121     pub fn simulate_mut(&mut self, settings: &GameSettings, player_command: Command, opponent_command: Command) {
122         if self.status.is_complete() {
123             return;
124         }
125
126         GameState::update_construction(&mut self.player_unconstructed_buildings, &mut self.player_buildings, &mut self.player);
127         GameState::update_construction(&mut self.opponent_unconstructed_buildings, &mut self.opponent_buildings, &mut self.opponent);
128
129         GameState::add_missiles(&mut self.player_buildings, &mut self.player_missiles);
130         GameState::add_missiles(&mut self.opponent_buildings, &mut self.opponent_missiles);
131
132         GameState::move_missiles(&mut self.player_missiles, |p| p.move_right(&settings.size),
133                                  &mut self.opponent_buildings, &mut self.opponent,
134                                  &mut self.unoccupied_opponent_cells);
135         GameState::move_missiles(&mut self.opponent_missiles, |p| p.move_left(),
136                                  &mut self.player_buildings, &mut self.player,
137                                  &mut self.unoccupied_player_cells);
138
139         GameState::add_energy(&mut self.player);
140         GameState::add_energy(&mut self.opponent);
141
142         GameState::perform_command(&mut self.player_unconstructed_buildings, &mut self.player_buildings,  &mut self.player, &mut self.unoccupied_player_cells, settings, player_command, &settings.size);
143         GameState::perform_command(&mut self.opponent_unconstructed_buildings, &mut self.opponent_buildings, &mut self.opponent, &mut self.unoccupied_opponent_cells, settings, opponent_command, &settings.size);
144         
145         GameState::update_status(self);
146     }
147
148     fn perform_command(unconstructed_buildings: &mut Vec<UnconstructedBuilding>, buildings: &mut Vec<Building>, player: &mut Player, unoccupied_cells: &mut Vec<Point>, settings: &GameSettings, command: Command, size: &Point) {
149         match command {
150             Command::Nothing => { },
151             Command::Build(p, b) => {
152                 let blueprint = settings.building_settings(b);
153
154                 // This is used internally. I should not be making
155                 // invalid moves!
156                 debug_assert!(!buildings.iter().any(|b| b.pos == p));
157                 debug_assert!(p.x < size.x && p.y < size.y);
158                 debug_assert!(player.energy >= blueprint.price);
159
160                 player.energy -= blueprint.price;
161                 if blueprint.construction_time > 0 {
162                     unconstructed_buildings.push(UnconstructedBuilding::new(p, blueprint));
163                 } else {
164                     let building = Building::new(p, blueprint);
165                     player.energy_generated += building.energy_generated_per_turn;
166                     buildings.push(building);
167                 }
168                 unoccupied_cells.retain(|&pos| pos != p);
169             },
170         }
171     }
172
173     fn update_construction(unconstructed_buildings: &mut Vec<UnconstructedBuilding>, buildings: &mut Vec<Building>, player: &mut Player) {
174         for building in unconstructed_buildings.iter_mut() {
175             building.construction_time_left -= 1;
176             if building.is_constructed() {
177                 player.energy_generated += building.energy_generated_per_turn;
178                 buildings.push(building.to_building());
179             }
180         }
181         unconstructed_buildings.retain(|b| !b.is_constructed());
182     }
183
184     fn add_missiles(buildings: &mut Vec<Building>, missiles: &mut Vec<Missile>) {
185         for building in buildings.iter_mut().filter(|b| b.is_shooty()) {
186             if building.weapon_cooldown_time_left > 0 {
187                 building.weapon_cooldown_time_left -= 1;
188             } else {
189                 missiles.push(Missile {
190                     pos: building.pos,
191                     speed: building.weapon_speed,
192                     damage: building.weapon_damage,
193                 });
194                 building.weapon_cooldown_time_left = building.weapon_cooldown_period;
195             }
196         }
197     }
198
199     fn move_missiles<F>(missiles: &mut Vec<Missile>, move_fn: F, opponent_buildings: &mut Vec<Building>, opponent: &mut Player, unoccupied_cells: &mut Vec<Point>,)
200     where F: Fn(Point) -> Option<Point> {
201         for missile in missiles.iter_mut() {
202             for _ in 0..missile.speed {
203                 match move_fn(missile.pos) {
204                     None => {
205                         let damage = cmp::min(missile.damage, opponent.health);
206                         opponent.health -= damage;
207                         missile.speed = 0;
208                     },
209                     Some(point) => {
210                         missile.pos = point;
211                         for hit in opponent_buildings.iter_mut().filter(|b| b.pos == point) {
212                             let damage = cmp::min(missile.damage, hit.health);
213                             hit.health -= damage;
214                             missile.speed = 0;
215                         }
216                     }
217                 }
218
219                 /*
220                 check is necessary if speed could be > 1, which isn't the case yet
221                 if missile.speed == 0 {
222                     break;
223                 }
224                  */
225             }
226         }
227         missiles.retain(|m| m.speed > 0);
228
229         for b in opponent_buildings.iter().filter(|b| b.health == 0) {
230             unoccupied_cells.push(b.pos);
231             opponent.energy_generated -= b.energy_generated_per_turn;
232         }
233         opponent_buildings.retain(|b| b.health > 0);
234     }
235
236     fn add_energy(player: &mut Player) {
237         player.energy += player.energy_generated;
238     }
239
240     fn update_status(state: &mut GameState) {
241         let player_dead = state.player.health == 0;
242         let opponent_dead = state.opponent.health == 0;
243         state.status = match (player_dead, opponent_dead) {
244             (true, true) => GameStatus::Draw,
245             (false, true) => GameStatus::PlayerWon,
246             (true, false) => GameStatus::OpponentWon,
247             (false, false) => GameStatus::Continue,
248         };
249     }
250
251     pub fn unoccupied_player_cells_in_row(&self, y: u8) -> Vec<Point> {
252         self.unoccupied_player_cells.iter().filter(|p| p.y == y).cloned().collect()
253     }
254
255     fn unoccupied_cells(buildings: &[Building], unconstructed_buildings: &[UnconstructedBuilding], bl: Point, tr: Point) -> Vec<Point> {
256         let mut result = Vec::with_capacity((tr.y-bl.y) as usize * (tr.x-bl.x) as usize);
257         for y in bl.y..tr.y {
258             for x in bl.x..tr.x {
259                 let pos = Point::new(x, y);
260                 if !buildings.iter().any(|b| b.pos == pos) && !unconstructed_buildings.iter().any(|b| b.pos == pos) {
261                     result.push(pos);
262                 }
263             }
264         }
265         result
266     }
267
268     pub fn player_affordable_buildings(&self, settings: &GameSettings) -> Vec<BuildingType> {
269         GameState::affordable_buildings(self.player.energy, settings)
270     }
271
272     pub fn opponent_affordable_buildings(&self, settings: &GameSettings) -> Vec<BuildingType> {
273         GameState::affordable_buildings(self.opponent.energy, settings)
274     }
275
276     fn affordable_buildings(energy: u16, settings: &GameSettings) -> Vec<BuildingType> {
277         BuildingType::all().iter()
278             .filter(|&b| settings.building_settings(*b).price <= energy)
279             .cloned()
280             .collect()
281     }
282 }
283
284 impl GameStatus {
285     fn is_complete(&self) -> bool {
286         *self != GameStatus::Continue
287     }
288 }
289
290 impl Player {
291     pub fn new(energy: u16, health: u8, settings: &GameSettings, buildings: &[Building]) -> Player {
292         Player {
293             energy: energy,
294             health: health,
295             energy_generated: settings.energy_income + buildings.iter().map(|b| b.energy_generated_per_turn).sum::<u16>()
296         }
297     }
298     
299     pub fn can_afford_all_buildings(&self, settings: &GameSettings) -> bool {
300         self.can_afford_attack_buildings(settings) &&
301             self.can_afford_defence_buildings(settings) &&
302             self.can_afford_energy_buildings(settings)
303     }
304
305     pub fn can_afford_attack_buildings(&self, settings: &GameSettings) -> bool {
306         self.energy >= settings.attack.price
307     }
308     pub fn can_afford_defence_buildings(&self, settings: &GameSettings) -> bool {
309         self.energy >= settings.defence.price
310     }
311     pub fn can_afford_energy_buildings(&self, settings: &GameSettings) -> bool {
312         self.energy >= settings.energy.price
313     }
314
315 }
316
317 impl UnconstructedBuilding {
318     pub fn new(pos: Point, blueprint: &BuildingSettings) -> UnconstructedBuilding {
319         UnconstructedBuilding {
320             pos: pos,
321             health: blueprint.health,
322             construction_time_left: blueprint.construction_time,
323             weapon_damage: blueprint.weapon_damage,
324             weapon_speed: blueprint.weapon_speed,
325             weapon_cooldown_period: blueprint.weapon_cooldown_period,
326             energy_generated_per_turn: blueprint.energy_generated_per_turn
327         }
328     }
329     
330     fn is_constructed(&self) -> bool {
331         self.construction_time_left == 0
332     }
333
334     fn to_building(&self) -> Building {
335         Building {
336             pos: self.pos,
337             health: self.health,
338             weapon_damage: self.weapon_damage,
339             weapon_speed: self.weapon_speed,
340             weapon_cooldown_time_left: 0,
341             weapon_cooldown_period: self.weapon_cooldown_period,
342             energy_generated_per_turn: self.energy_generated_per_turn
343         }
344     }
345 }
346
347 impl Building {
348     pub fn new(pos: Point, blueprint: &BuildingSettings) -> Building {
349         Building {
350             pos: pos,
351             health: blueprint.health,
352             weapon_damage: blueprint.weapon_damage,
353             weapon_speed: blueprint.weapon_speed,
354             weapon_cooldown_time_left: 0,
355             weapon_cooldown_period: blueprint.weapon_cooldown_period,
356             energy_generated_per_turn: blueprint.energy_generated_per_turn
357         }
358     }
359     
360     fn is_shooty(&self) -> bool {
361         self.weapon_damage > 0
362     }
363 }
364
365