Collapsed player info into the rest of the 'buildings' object
[entelect-challenge-tower-defence.git] / src / input / json.rs
1 use std::fs::File;
2 use std::io::prelude::*;
3 use serde_json;
4 use std::error::Error;
5
6 use engine;
7 use engine::command;
8 use engine::bitwise_engine;
9 use engine::constants::*;
10
11 pub fn read_bitwise_state_from_file(filename: &str) -> Result<bitwise_engine::BitwiseGameState, Box<Error>> {
12     let mut file = File::open(filename)?;
13     let mut content = String::new();
14     file.read_to_string(&mut content)?;
15     let state: State = serde_json::from_str(content.as_ref())?;
16
17     let engine_state = state.to_bitwise_engine();
18     Ok(engine_state)
19 }
20
21 #[derive(Deserialize)]
22 #[serde(rename_all = "camelCase")]
23 struct State {
24     game_details: GameDetails,
25     players: Vec<Player>,
26     game_map: Vec<Vec<GameCell>>,
27 }
28
29 #[derive(Deserialize)]
30 #[serde(rename_all = "camelCase")]
31 struct GameDetails {
32     round: u16
33 }
34
35 #[derive(Deserialize)]
36 #[serde(rename_all = "camelCase")]
37 struct Player {
38     player_type: char,
39     energy: u16,
40     health: u8,
41     iron_curtain_available: bool,
42     active_iron_curtain_lifetime: i16
43 }
44
45 #[derive(Deserialize)]
46 #[serde(rename_all = "camelCase")]
47 struct GameCell {
48     x: u8,
49     y: u8,
50     buildings: Vec<BuildingState>,
51     missiles: Vec<MissileState>,
52 }
53
54 #[derive(Deserialize)]
55 #[serde(rename_all = "camelCase")]
56 struct BuildingState {
57     health: u8,
58     construction_time_left: i16,
59     weapon_cooldown_time_left: u8,
60     building_type: String,
61     x: u8,
62     y: u8,
63     player_type: char
64 }
65
66 #[derive(Deserialize)]
67 #[serde(rename_all = "camelCase")]
68 struct MissileState {
69     player_type: char
70 }
71
72
73 impl State {
74     fn to_bitwise_engine(&self) -> bitwise_engine::BitwiseGameState {
75         let json_player = self.player();
76         let json_opponent = self.opponent();
77         let mut player = bitwise_engine::Player::empty();
78         let mut opponent = bitwise_engine::Player::empty();
79
80         // TODO roll the player into the playerbuildings and remove this duplication
81         player.health = json_player.health;
82         player.energy = json_player.energy;
83         player.iron_curtain_available = json_player.iron_curtain_available;
84         player.iron_curtain_remaining = if json_player.active_iron_curtain_lifetime < 0 {
85             0
86         } else {
87             json_player.active_iron_curtain_lifetime as u8
88         };
89         opponent.health = json_opponent.health;
90         opponent.energy = json_opponent.energy;
91         opponent.iron_curtain_available = json_opponent.iron_curtain_available;
92         opponent.iron_curtain_remaining = if json_opponent.active_iron_curtain_lifetime < 0 {
93             0
94         } else {
95             json_opponent.active_iron_curtain_lifetime as u8
96         };
97         
98         for row in &self.game_map {
99             for cell in row {
100                 let point = engine::geometry::Point::new(cell.x, cell.y);
101                 for building in &cell.buildings {
102                     let building_type = building.convert_building_type();
103                     
104                     let mut bitwise_buildings = if building.player_type == 'A' {
105                         &mut player
106                     } else {
107                         &mut opponent
108                     };
109                     let bitfield = point.to_either_bitfield();
110
111                     bitwise_buildings.occupied |= bitfield;
112                     if building.construction_time_left >= 0 {
113                         bitwise_buildings.unconstructed.push(building.to_bitwise_engine_unconstructed());
114                     } else {
115                         for health_tier in 0..DEFENCE_HEALTH {
116                             if building.health > health_tier as u8 * MISSILE_DAMAGE {
117                                 bitwise_buildings.buildings[health_tier] |= bitfield;
118                             }
119                         }
120                         if building_type == command::BuildingType::Energy {
121                             bitwise_buildings.energy_towers |= bitfield;
122                         }
123                         else if building_type == command::BuildingType::Attack {
124                             for cooldown_tier in 0..MISSILE_COOLDOWN + 1 {
125                                 if building.weapon_cooldown_time_left == cooldown_tier as u8 {
126                                     bitwise_buildings.missile_towers[cooldown_tier] |= bitfield;
127                                 }
128                             }
129                         }
130                         else if building_type == command::BuildingType::Tesla {
131                             bitwise_buildings.tesla_cooldowns.push(bitwise_engine::TeslaCooldown { 
132                                 pos: point,
133                                 cooldown: building.weapon_cooldown_time_left,
134                                 age: building.construction_time_left.abs() as u16
135                             });
136                         }
137                     }
138                 }
139                 for missile in &cell.missiles {
140                     let (mut left, mut right) = engine::geometry::Point::new_double_bitfield(cell.x, cell.y, missile.player_type == 'A');
141                     let mut bitwise_buildings = if missile.player_type == 'A' {
142                         &mut player
143                     } else {
144                         &mut opponent
145                     };
146
147                     for mut tier in bitwise_buildings.missiles.iter_mut() {
148                         let setting = (!tier.0 & left, !tier.1 & right);
149                         tier.0 |= setting.0;
150                         tier.1 |= setting.1;
151                         left &= !setting.0;
152                         right &= !setting.1;
153                     }
154                 }
155             }
156         }
157             
158         bitwise_engine::BitwiseGameState::new(
159             player, opponent,
160             self.game_details.round
161         )
162     }
163
164     fn player(&self) -> &Player {
165         self.players.iter()
166             .find(|p| p.player_type == 'A')
167             .expect("Player character did not appear in state.json")
168     }
169
170     fn opponent(&self) -> &Player {
171         self.players.iter()
172             .find(|p| p.player_type == 'B')
173             .expect("Opponent character did not appear in state.json")
174     }
175 }
176
177 impl BuildingState {
178     fn to_bitwise_engine_unconstructed(&self) -> bitwise_engine::UnconstructedBuilding {
179         bitwise_engine::UnconstructedBuilding {
180             pos: engine::geometry::Point::new(self.x, self.y),
181             construction_time_left: self.construction_time_left as u8, // > 0 check already happened
182             building_type: self.convert_building_type()
183         }
184     }
185
186     fn convert_building_type(&self) -> command::BuildingType {
187         match self.building_type.as_ref() {
188             "ATTACK" => command::BuildingType::Attack,
189             "ENERGY" => command::BuildingType::Energy,
190             "TESLA" => command::BuildingType::Tesla,
191             _ => command::BuildingType::Defence,
192         }
193     }
194 }