Split to library. Reimplemented sample strategy in new state.
[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::{BuildingType, Command};
6 use self::geometry::Point;
7 use self::settings::GameSettings;
8
9 use std::ops::Fn;
10 use std::cmp;
11
12 #[derive(Debug, Clone)]
13 pub struct GameState {
14     pub status: GameStatus,
15     pub player: Player,
16     pub opponent: Player,
17     pub player_buildings: Vec<Building>,
18     pub opponent_buildings: Vec<Building>,
19     pub player_missiles: Vec<Missile>,
20     pub opponent_missiles: Vec<Missile>
21 }
22
23 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
24 pub enum GameStatus {
25     Continue,
26     PlayerWon,
27     OpponentWon,
28     Draw,
29     InvalidMove
30 }
31
32 #[derive(Debug, Clone)]
33 pub struct Player {
34     pub energy: u16,
35     pub health: u16
36 }
37
38 #[derive(Debug, Clone)]
39 pub struct Building {
40     pub pos: Point,
41     pub health: u16,
42     pub construction_time_left: u8,
43     pub weapon_damage: u16,
44     pub weapon_speed: u8,
45     pub weapon_cooldown_time_left: u8,
46     pub weapon_cooldown_period: u8,
47     pub energy_generated_per_turn: u16
48 }
49
50 #[derive(Debug, Clone)]
51 pub struct Missile {
52     pub pos: Point,
53     pub damage: u16,
54     pub speed: u8,
55 }
56
57 impl GameState {
58     pub fn simulate(&self, settings: &GameSettings, player_command: Command, opponent_command: Command) -> GameState {
59         if self.status.is_complete() {
60             return self.clone();
61         }
62         
63         let mut state = self.clone();
64         let player_valid = GameState::perform_command(&mut state.player_buildings, player_command, &settings.size);
65         let opponent_valid = GameState::perform_command(&mut state.opponent_buildings, opponent_command, &settings.size);
66
67         if !player_valid || !opponent_valid {
68             state.status = GameStatus::InvalidMove;
69             return state;
70         }
71
72         GameState::update_construction(&mut state.player_buildings);
73         GameState::update_construction(&mut state.opponent_buildings);
74
75         GameState::add_missiles(&mut state.player_buildings, &mut state.player_missiles);
76         GameState::add_missiles(&mut state.opponent_buildings, &mut state.opponent_missiles);
77
78         GameState::move_missiles(&mut state.player_missiles, |p| p.move_right(&settings.size),
79                                  &mut state.opponent_buildings, &mut state.opponent);
80         GameState::move_missiles(&mut state.opponent_missiles, |p| p.move_left(),
81                                  &mut state.player_buildings, &mut state.player);
82
83         GameState::add_energy(&mut state.player, settings, &state.player_buildings);
84         GameState::add_energy(&mut state.opponent, settings, &state.opponent_buildings);
85
86         GameState::update_status(&mut state);
87         state
88     }
89
90     fn perform_command(buildings: &mut Vec<Building>, command: Command, size: &Point) -> bool {
91         match command {
92             Command::Nothing => { true },
93             Command::Build(p, b) => {
94                 let occupied = buildings.iter().any(|b| b.pos == p);
95                 let in_range = p.x < size.x && p.y < size.y;
96                 buildings.push(Building::new(p, b));
97                 !occupied && in_range
98             },
99         }
100     }
101
102     fn update_construction(buildings: &mut Vec<Building>) {
103         for building in buildings.iter_mut().filter(|b| !b.is_constructed()) {
104             building.construction_time_left -= 1;
105         }
106     }
107
108     fn add_missiles(buildings: &mut Vec<Building>, missiles: &mut Vec<Missile>) {
109         for building in buildings.iter_mut().filter(|b| b.is_shooty()) {
110             if building.weapon_cooldown_time_left > 0 {
111                 building.weapon_cooldown_time_left -= 1;
112             } else {
113                 missiles.push(Missile {
114                     pos: building.pos,
115                     speed: building.weapon_speed,
116                     damage: building.weapon_damage,
117                 });
118                 building.weapon_cooldown_time_left = building.weapon_cooldown_period;
119             }
120         }
121     }
122
123     fn move_missiles<F>(missiles: &mut Vec<Missile>, move_fn: F, opponent_buildings: &mut Vec<Building>, opponent: &mut Player)
124     where F: Fn(Point) -> Option<Point> {
125         for missile in missiles.iter_mut() {
126             for _ in 0..missile.speed {
127                 match move_fn(missile.pos) {
128                     None => {
129                         let damage = cmp::min(missile.damage, opponent.health);
130                         opponent.health -= damage;
131                         missile.speed = 0;
132                     },
133                     Some(point) => {
134                         missile.pos = point;
135                         for hit in opponent_buildings.iter_mut().filter(|b| b.is_constructed() && b.pos == point && b.health > 0) {
136                             let damage = cmp::min(missile.damage, hit.health);
137                             hit.health -= damage;
138                             missile.speed = 0;                    
139                         }
140                     }
141                 }
142                 
143                 if missile.speed == 0 {
144                     break;
145                 }
146             }
147         }
148         missiles.retain(|m| m.speed > 0);
149         opponent_buildings.retain(|b| b.health > 0);
150     }
151
152     fn add_energy(player: &mut Player, settings: &GameSettings, buildings: &Vec<Building>) {
153         player.energy += settings.energy_income;
154         player.energy += buildings.iter().map(|b| b.energy_generated_per_turn).sum::<u16>();
155     }
156
157     fn update_status(state: &mut GameState) {
158         let player_dead = state.player.health == 0;
159         let opponent_dead = state.player.health == 0;
160         state.status = match (player_dead, opponent_dead) {
161             (true, true) => GameStatus::Draw,
162             (true, false) => GameStatus::PlayerWon,
163             (false, true) => GameStatus::OpponentWon,
164             (false, false) => GameStatus::Continue,
165         };
166     }
167
168     pub fn unoccupied_player_cells_in_row(&self, settings: &GameSettings, y: u8) -> Vec<Point> {
169         (0..settings.size.x/2)
170             .map(|x| Point::new(x, y))
171             .filter(|&p| !self.player_buildings.iter().any(|b| b.pos == p))
172             .collect()
173     }
174
175     pub fn unoccupied_player_cells(&self, settings: &GameSettings) -> Vec<Point> {
176         (0..settings.size.y)
177             .flat_map(|y| (0..settings.size.x/2).map(|x| Point::new(x, y)).collect::<Vec<_>>())
178             .filter(|&p| !self.player_buildings.iter().any(|b| b.pos == p))
179             .collect()
180     }
181 }
182
183 impl GameStatus {
184     fn is_complete(&self) -> bool {
185         *self != GameStatus::Continue
186     }
187 }
188
189 impl Player {
190     pub fn can_afford_all_buildings(&self, settings: &GameSettings) -> bool {
191         self.can_afford_attack_buildings(settings) &&
192             self.can_afford_defence_buildings(settings) &&
193             self.can_afford_energy_buildings(settings)
194     }
195
196     pub fn can_afford_attack_buildings(&self, settings: &GameSettings) -> bool {
197         self.energy >= settings.attack_price
198     }
199     pub fn can_afford_defence_buildings(&self, settings: &GameSettings) -> bool {
200         self.energy >= settings.defence_price
201     }
202     pub fn can_afford_energy_buildings(&self, settings: &GameSettings) -> bool {
203         self.energy >= settings.energy_price
204     }
205
206 }
207
208 impl Building {
209     fn new(pos: Point, building: BuildingType) -> Building {
210         match building {
211             BuildingType::Defense => Building {
212                 pos: pos,
213                 health: 20,
214                 construction_time_left: 3,
215                 weapon_damage: 0,
216                 weapon_speed: 0,
217                 weapon_cooldown_time_left: 0,
218                 weapon_cooldown_period: 0,
219                 energy_generated_per_turn: 0
220             },
221             BuildingType::Attack => Building {
222                 pos: pos,
223                 health: 5,
224                 construction_time_left: 1,
225                 weapon_damage: 5,
226                 weapon_speed: 1,
227                 weapon_cooldown_time_left: 0,
228                 weapon_cooldown_period: 3,
229                 energy_generated_per_turn: 0
230             },
231             BuildingType::Energy => Building {
232                 pos: pos,
233                 health: 5,
234                 construction_time_left: 1,
235                 weapon_damage: 0,
236                 weapon_speed: 0,
237                 weapon_cooldown_time_left: 0,
238                 weapon_cooldown_period: 0,
239                 energy_generated_per_turn: 3
240             }
241         }
242     }
243
244     fn is_constructed(&self) -> bool {
245         self.construction_time_left == 0
246     }
247
248     fn is_shooty(&self) -> bool {
249         self.is_constructed() && self.weapon_damage > 0
250     }
251 }
252
253 #[test]
254 fn how_big() {
255     use std::mem;
256     assert_eq!(4, mem::size_of::<Player>());
257     assert_eq!(12, mem::size_of::<Building>());
258     assert_eq!(6, mem::size_of::<Missile>());
259     assert_eq!(112, mem::size_of::<GameState>());
260     assert_eq!(24, mem::size_of::<Vec<Building>>());
261     assert_eq!(24, mem::size_of::<Vec<Missile>>());
262     
263 }