Skip to main content

galactic_war/
lib.rs

1use indexmap::IndexMap;
2use rand::Rng;
3use std::collections::HashMap;
4use system::EventInfo;
5
6pub mod config;
7mod system;
8use crate::config::GalaxyConfig;
9use crate::system::System;
10
11pub use crate::system::{Event, EventCallback, StructureType};
12
13#[derive(Debug)]
14pub struct Galaxy {
15    /// Cached configuration for the galaxy
16    config: GalaxyConfig,
17
18    /// All the systems in the galaxy
19    systems: HashMap<Coords, System>,
20
21    /// The most recent tick
22    ///
23    /// This is used to ensure that events can only arrive in order
24    tick: usize,
25}
26
27/// Production of a system.
28///
29/// Each value is the amount of resources produced per 3600 ticks (hour).
30pub type SystemProduction = Resources;
31
32/// Resources in a system.
33#[derive(Clone, Debug, Default)]
34pub struct Resources {
35    pub metal: usize,
36    pub crew: usize,
37    pub water: usize,
38}
39
40impl std::ops::Add for Resources {
41    type Output = Resources;
42
43    fn add(self, other: Resources) -> Resources {
44        Resources {
45            metal: self.metal + other.metal,
46            crew: self.crew + other.crew,
47            water: self.water + other.water,
48        }
49    }
50}
51
52impl std::ops::Sub for Resources {
53    type Output = Resources;
54
55    fn sub(self, other: Resources) -> Resources {
56        Resources {
57            metal: self.metal - other.metal,
58            crew: self.crew - other.crew,
59            water: self.water - other.water,
60        }
61    }
62}
63
64impl std::ops::Mul<usize> for Resources {
65    type Output = Resources;
66
67    fn mul(self, other: usize) -> Resources {
68        Resources {
69            metal: self.metal * other,
70            crew: self.crew * other,
71            water: self.water * other,
72        }
73    }
74}
75
76#[derive(Clone, Debug, Default)]
77pub struct SystemInfo {
78    /// Computed score of the system
79    pub score: usize,
80
81    /// Resources in the system.
82    pub resources: Resources,
83
84    /// Production of the system.
85    ///
86    /// Given in units per hour (3600 ticks).
87    pub production: SystemProduction,
88
89    /// Structure levels
90    pub structures: IndexMap<StructureType, usize>,
91
92    /// Events in flight
93    ///
94    /// Next resource, unit builds, incoming attacks, etc.
95    pub events: Vec<EventInfo>,
96}
97
98/// Struct to hold the cost for a build
99#[derive(Clone, Debug, Default)]
100pub struct Cost {
101    pub metal: usize,
102    pub water: usize,
103    pub crew: usize,
104    pub ticks: usize,
105}
106
107impl Cost {
108    /// Create a Cost from a HashMap
109    ///
110    /// This converts the format seen in the config files to an actual Cost struct
111    pub fn from_map(cost: &HashMap<String, usize>) -> Cost {
112        Cost {
113            metal: *cost.get("metal").unwrap_or(&0),
114            water: *cost.get("water").unwrap_or(&0),
115            crew: *cost.get("crew").unwrap_or(&0),
116            ticks: *cost.get("ticks").unwrap_or(&0),
117        }
118    }
119}
120
121/// Info for a specific structure
122///
123/// Lots of details are optional, as they don't all apply to all structures
124#[derive(Clone, Debug, Default)]
125pub struct StructureInfo {
126    /// Level of the structure.
127    pub level: usize,
128    /// Production of the structure, if any.
129    pub production: Option<SystemProduction>,
130    /// Things that this structure can build, if any.
131    pub builds: Option<IndexMap<StructureType, Cost>>,
132}
133
134/// Info to use in return values
135///
136/// Stores SystemInfo and StructureInfo
137/// TODO: Is this really the best approach? If it's just two types, may want to just split the API.
138#[derive(Clone, Debug)]
139pub enum Details {
140    System(SystemInfo),
141    Structure(StructureInfo),
142}
143
144impl Galaxy {
145    /// Create a new Galaxy
146    ///
147    /// Uses a custom configuration struct to set up all the details
148    pub fn new(config: GalaxyConfig, initial_tick: usize) -> Self {
149        let mut systems = HashMap::new();
150        let mut rng = rand::thread_rng();
151        for _ in 0..config.system_count {
152            // Create a new island at a random location in the 2d space
153            let x: usize = rng.gen_range(0..=config.size.x);
154            let y: usize = rng.gen_range(0..=config.size.y);
155            if systems.contains_key(&(x, y).into()) {
156                // Already have a system here, try again
157                continue;
158            }
159            let system = System::new(initial_tick, &config.systems, &config);
160            systems.insert((x, y).into(), system);
161        }
162        Self {
163            config,
164            systems,
165            tick: initial_tick,
166        }
167    }
168
169    /// Retrieve the details of a system, possibly scoped to a specific structure
170    pub fn get_details(
171        &mut self,
172        tick: usize,
173        coords: Coords,
174        structure: Option<StructureType>,
175    ) -> Result<Details, String> {
176        self.update_tick(tick)?;
177        let system = self.systems.get_mut(&coords).unwrap();
178        system.get_details(tick, &self.config, structure)
179    }
180
181    /// Get a pointer to the Config
182    pub fn get_config(&self) -> &GalaxyConfig {
183        &self.config
184    }
185
186    /// Return basic stats about the Galaxy
187    pub fn stats(&mut self, tick: usize) -> Result<String, String> {
188        self.update_tick(tick)?;
189        let mut stats = format!("System count: {}\n", self.config.system_count);
190        for (coords, system) in self.systems.iter_mut() {
191            stats.push_str(&format!(
192                "System at {:?} has score {} and metal {}\n",
193                coords,
194                system.score(tick, &self.config),
195                system.metal(tick, &self.config),
196            ));
197        }
198        Ok(stats)
199    }
200
201    /// Retrieve the full list of systems
202    pub fn systems(&self) -> &HashMap<Coords, System> {
203        &self.systems
204    }
205
206    /// Build a structure in a system
207    pub fn build(
208        &mut self,
209        tick: usize,
210        coords: Coords,
211        structure: StructureType,
212    ) -> Result<Event, String> {
213        self.update_tick(tick)?;
214        let system = self.systems.get_mut(&coords).unwrap();
215        system.build(tick, &self.config, structure)
216    }
217
218    /// Update the current tick, and verify we are not going back in time
219    fn update_tick(&mut self, tick: usize) -> Result<(), String> {
220        if tick < self.tick {
221            return Err("Tick is out of order".to_string());
222        }
223        self.tick = tick;
224        Ok(())
225    }
226}
227
228/// Coords for systems
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
230pub struct Coords {
231    pub x: usize,
232    pub y: usize,
233}
234
235impl From<(usize, usize)> for Coords {
236    fn from(coords: (usize, usize)) -> Self {
237        Coords {
238            x: coords.0,
239            y: coords.1,
240        }
241    }
242}