1use core::panic;
2use indexmap::IndexMap;
3
4use crate::config::{GalaxyConfig, StructureConfig, SystemConfig};
5use crate::{Details, Resources, StructureInfo, SystemInfo, SystemProduction};
6use std::fmt;
7use std::str::FromStr;
8
9#[derive(Debug, Default)]
11pub struct System {
12 events: Vec<Event>,
14
15 resources: Resources,
17
18 structures: Vec<Structure>,
20}
21
22#[derive(Debug)]
23struct Structure {
24 name: StructureType,
25 level: usize,
26}
27
28#[derive(Clone, Debug)]
29pub struct Event {
30 pub completion: usize,
31 pub action: EventCallback,
32 pub structure: Option<StructureType>,
33}
34
35pub type EventInfo = Event;
36
37#[derive(Clone, Debug)]
38pub enum EventCallback {
39 Metal,
40 Water,
41 Crew,
42 Build,
43}
44
45#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq)]
46pub enum StructureType {
47 Colony,
48 AsteroidMine,
49 WaterHarvester,
50 Hatchery,
51 StorageDepot,
52}
53
54impl fmt::Display for StructureType {
55 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56 write!(f, "{:?}", self)
57 }
58}
59
60impl FromStr for StructureType {
61 type Err = ();
62
63 fn from_str(s: &str) -> Result<Self, Self::Err> {
64 match s.to_lowercase().as_str() {
65 "colony" => Ok(StructureType::Colony),
66 "asteroidmine" => Ok(StructureType::AsteroidMine),
67 "waterharvester" => Ok(StructureType::WaterHarvester),
68 "hatchery" => Ok(StructureType::Hatchery),
69 "storagedepot" => Ok(StructureType::StorageDepot),
70 _ => Err(()),
71 }
72 }
73}
74
75impl System {
76 pub fn new(tick: usize, system_config: &SystemConfig, galaxy_config: &GalaxyConfig) -> Self {
80 let resources = Resources {
81 metal: *system_config.resources.get("metal").unwrap_or(&0),
82 water: *system_config.resources.get("water").unwrap_or(&0),
83 crew: *system_config.resources.get("crew").unwrap_or(&0),
84 };
85 let mut structures = Vec::new();
86 for (name, structure) in system_config.structures.iter() {
87 structures.push(Structure {
88 name: StructureType::from_str(name).unwrap(),
89 level: structure.starting_level.unwrap_or(0),
90 });
91 }
92 let mut new_system = Self {
93 events: Vec::new(),
94 resources,
95 structures,
96 };
97
98 new_system.event_callback(
101 tick,
102 galaxy_config,
103 Event {
104 completion: tick,
105 action: EventCallback::Metal,
106 structure: None,
107 },
108 );
109 new_system.event_callback(
110 tick,
111 galaxy_config,
112 Event {
113 completion: tick,
114 action: EventCallback::Crew,
115 structure: None,
116 },
117 );
118 new_system.event_callback(
119 tick,
120 galaxy_config,
121 Event {
122 completion: tick,
123 action: EventCallback::Water,
124 structure: None,
125 },
126 );
127
128 new_system
129 }
130
131 fn structure(&self, structure: StructureType) -> Option<usize> {
135 self.structures.iter().position(|b| b.name == structure)
136 }
137
138 fn structure_level(&self, structure: StructureType) -> usize {
140 if let Some(index) = self.structure(structure) {
141 self.structures[index].level
142 } else {
143 0
144 }
145 }
146
147 fn get_structure_config(
149 galaxy_config: &GalaxyConfig,
150 structure: StructureType,
151 ) -> &StructureConfig {
152 galaxy_config
153 .systems
154 .structures
155 .get(&structure.to_string().to_lowercase())
156 .unwrap()
157 }
158
159 fn get_production(&mut self, tick: usize, galaxy_config: &GalaxyConfig) -> SystemProduction {
161 self.process_events(tick, galaxy_config);
162 let mut production = SystemProduction {
163 metal: 0,
164 crew: 0,
165 water: 0,
166 };
167 for structure in self.structures.iter() {
168 let production_config = galaxy_config
169 .get_structure_production(&structure.name.to_string(), structure.level);
170 if let Some(metal) = production_config.metal {
171 production.metal += metal;
172 }
173 if let Some(crew) = production_config.crew {
174 production.crew += crew;
175 }
176 if let Some(water) = production_config.water {
177 production.water += water;
178 }
179 }
180 production
181 }
182
183 fn get_storage(&mut self, tick: usize, galaxy_config: &GalaxyConfig) -> SystemProduction {
185 self.process_events(tick, galaxy_config);
186 let mut production = SystemProduction {
187 metal: 0,
188 crew: 0,
189 water: 0,
190 };
191 for structure in self.structures.iter() {
192 let production_config =
193 galaxy_config.get_structure_storage(&structure.name.to_string(), structure.level);
194 if let Some(metal) = production_config.metal {
195 production.metal += metal;
196 }
197 if let Some(crew) = production_config.crew {
198 production.crew += crew;
199 }
200 if let Some(water) = production_config.water {
201 production.water += water;
202 }
203 }
204 production
205 }
206
207 fn event_callback(&mut self, tick: usize, galaxy_config: &GalaxyConfig, event: Event) {
212 if event.completion > tick {
214 return;
215 }
216 match event.action {
217 EventCallback::Metal => {
218 self.resources.metal += 1;
219 let storage = self.get_storage(tick, galaxy_config);
220 if self.resources.metal > storage.metal {
221 self.resources.metal = storage.metal;
222 }
223 let production = self.get_production(tick, galaxy_config);
224 if production.metal > 0 {
225 self.register_event(Event {
227 completion: event.completion + (3600 / production.metal),
228 action: EventCallback::Metal,
229 structure: None,
230 });
231 }
232 }
233 EventCallback::Water => {
234 self.resources.water += 1;
235 let storage = self.get_storage(tick, galaxy_config);
236 if self.resources.water > storage.water {
237 self.resources.water = storage.water;
238 }
239 let production = self.get_production(tick, galaxy_config);
240 if production.water > 0 {
241 self.register_event(Event {
243 completion: event.completion + (3600 / production.water),
244 action: EventCallback::Water,
245 structure: None,
246 });
247 }
248 }
249 EventCallback::Crew => {
250 self.resources.crew += 1;
251 let storage = self.get_storage(tick, galaxy_config);
252 if self.resources.crew > storage.crew {
253 self.resources.crew = storage.crew;
254 }
255 let production = self.get_production(tick, galaxy_config);
256 if production.crew > 0 {
257 self.register_event(Event {
259 completion: event.completion + (3600 / production.crew),
260 action: EventCallback::Crew,
261 structure: None,
262 });
263 }
264 }
265 EventCallback::Build => {
266 if let Some(structure) = event.structure {
268 let index = self.structure(structure).unwrap();
269 self.structures[index].level += 1;
270 } else {
271 panic!("Structure event without StructureType");
272 }
273 }
274 }
275 }
276
277 pub fn metal(&mut self, tick: usize, galaxy_config: &GalaxyConfig) -> usize {
279 self.resources(tick, galaxy_config).water
280 }
281
282 pub fn water(&mut self, tick: usize, galaxy_config: &GalaxyConfig) -> usize {
284 self.resources(tick, galaxy_config).water
285 }
286
287 pub fn crew(&mut self, tick: usize, galaxy_config: &GalaxyConfig) -> usize {
289 self.resources(tick, galaxy_config).crew
290 }
291
292 pub fn resources(&mut self, tick: usize, galaxy_config: &GalaxyConfig) -> Resources {
294 self.process_events(tick, galaxy_config);
295 self.resources.clone()
296 }
297
298 pub fn event_to_process(&mut self, tick: usize) -> bool {
300 if let Some(event) = self.events.first() {
302 event.completion <= tick
303 } else {
304 false
305 }
306 }
307
308 pub fn register_event(&mut self, event: Event) {
311 self.events.push(event);
312 self.events.sort_by_key(|e| e.completion)
313 }
314
315 pub fn process_events(&mut self, tick: usize, galaxy_config: &GalaxyConfig) {
317 let mut dirty = true;
318 while dirty && self.event_to_process(tick) {
319 dirty = false;
320 let events = self.events.clone();
321 self.events.clear();
322 for event in events.iter() {
323 if event.completion <= tick {
324 dirty = true;
325 self.event_callback(tick, galaxy_config, event.clone());
326 } else {
327 self.register_event(event.clone());
328 }
329 }
330 }
331 }
332
333 pub fn score(&mut self, tick: usize, galaxy_config: &GalaxyConfig) -> usize {
338 self.process_events(tick, galaxy_config);
339 self.structures
340 .iter()
341 .map(|b| (1..=b.level).sum::<usize>())
342 .sum()
343 }
344
345 pub fn build(
347 &mut self,
348 tick: usize,
349 galaxy_config: &GalaxyConfig,
350 structure: StructureType,
351 ) -> Result<Event, String> {
352 self.process_events(tick, galaxy_config);
353 if self.events.iter().any(|e| e.structure.is_some()) {
355 return Err("Already building a structure".to_string());
356 }
357 if self.structure(structure).is_some() {
358 let cost = &System::get_structure_config(galaxy_config, structure).cost
360 [self.structure_level(structure)];
361 if self.resources.metal >= *cost.get("metal").unwrap_or(&0)
362 && self.resources.water >= *cost.get("water").unwrap_or(&0)
363 && self.resources.crew >= *cost.get("crew").unwrap_or(&0)
364 {
365 self.resources = self.resources.clone()
367 - Resources {
368 metal: *cost.get("metal").unwrap_or(&0),
369 water: *cost.get("water").unwrap_or(&0),
370 crew: *cost.get("crew").unwrap_or(&0),
371 };
372 let event = Event {
374 completion: tick + cost.get("time").unwrap_or(&1),
375 action: EventCallback::Build,
376 structure: Some(structure),
377 };
378 self.register_event(event.clone());
379 Ok(event)
380 } else {
381 Err("Not enough resources".to_string())
383 }
384 } else {
385 Err("Structure not found".to_string())
386 }
387 }
388
389 pub fn get_details(
391 &mut self,
392 tick: usize,
393 galaxy_config: &GalaxyConfig,
394 structure: Option<StructureType>,
395 ) -> Result<Details, String> {
396 self.process_events(tick, galaxy_config);
397 if let Some(structure) = structure {
398 let production_config = galaxy_config
399 .get_structure_production(&structure.to_string(), self.structure_level(structure));
400 let mut details = StructureInfo {
401 level: self.structure_level(structure),
402 production: Some(Resources {
403 metal: production_config.metal.unwrap_or(0),
404 water: production_config.water.unwrap_or(0),
405 crew: production_config.crew.unwrap_or(0),
406 }),
407 builds: None,
408 };
409 if structure == StructureType::Colony {
410 if details.builds.is_none() {
411 details.builds = Some(IndexMap::new());
412 }
413 let builds = details.builds.as_mut().unwrap();
414 for structure in self.structures.iter() {
415 builds.insert(
416 structure.name,
417 System::get_structure_config(galaxy_config, structure.name)
418 .get_cost(structure.level),
419 );
420 }
421 }
422 Ok(Details::Structure(details))
423 } else {
424 let mut details = SystemInfo {
425 score: self.score(tick, galaxy_config),
426 resources: self.resources.clone(),
427 structures: IndexMap::new(),
428 production: self.get_production(tick, galaxy_config),
429 events: self.events.clone(),
430 };
431 for structure in self.structures.iter() {
432 details.structures.insert(structure.name, structure.level);
433 }
434 Ok(Details::System(details.clone()))
435 }
436 }
437}