Skip to main content

nil_core/continent/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4mod coord;
5mod field;
6mod index;
7mod size;
8
9#[cfg(test)]
10mod tests;
11
12use crate::city::{City, CitySearch};
13use crate::error::{Error, Result};
14use crate::ruler::Ruler;
15use serde::{Deserialize, Serialize};
16
17pub use coord::{Coord, Distance};
18pub use field::{Field, PublicField};
19pub use index::{ContinentIndex, ContinentKey};
20pub use size::ContinentSize;
21
22#[derive(Clone, Debug, Deserialize, Serialize)]
23#[serde(rename_all = "camelCase")]
24#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
25pub struct Continent {
26  fields: Box<[Field]>,
27  size: ContinentSize,
28}
29
30impl Continent {
31  pub(crate) fn new(size: u8) -> Self {
32    let size = ContinentSize::new(size);
33    let capacity = usize::from(size.get()).pow(2);
34    let mut fields = Vec::with_capacity(capacity);
35    fields.resize_with(capacity, Field::default);
36
37    Self {
38      fields: fields.into_boxed_slice(),
39      size,
40    }
41  }
42
43  #[inline]
44  pub fn size(&self) -> ContinentSize {
45    self.size
46  }
47
48  #[inline]
49  pub fn radius(&self) -> u8 {
50    self.size.get().div_ceil(2)
51  }
52
53  #[inline]
54  pub fn center(&self) -> Coord {
55    Coord::splat(self.radius())
56  }
57
58  pub fn field(&self, key: impl ContinentKey) -> Result<&Field> {
59    let index = key.into_index(self.size);
60    self
61      .fields
62      .get(usize::from(index))
63      .ok_or(Error::IndexOutOfBounds(index))
64  }
65
66  pub(crate) fn field_mut(&mut self, key: impl ContinentKey) -> Result<&mut Field> {
67    let index = key.into_index(self.size);
68    self
69      .fields
70      .get_mut(usize::from(index))
71      .ok_or(Error::IndexOutOfBounds(index))
72  }
73
74  pub fn fields(&self) -> impl Iterator<Item = &Field> {
75    self.fields.iter()
76  }
77
78  fn fields_mut(&mut self) -> impl Iterator<Item = &mut Field> {
79    self.fields.iter_mut()
80  }
81
82  pub fn enumerate_fields(&self) -> impl Iterator<Item = (ContinentIndex, &Field)> {
83    self
84      .fields()
85      .enumerate()
86      .map(|(idx, field)| (ContinentIndex::new(idx), field))
87  }
88
89  pub fn city(&self, key: impl ContinentKey) -> Result<&City> {
90    let index = key.into_index(self.size);
91    if let Some(city) = self.field(index)?.city() {
92      Ok(city)
93    } else {
94      let coord = index.to_coord(self.size)?;
95      Err(Error::CityNotFound(coord))
96    }
97  }
98
99  pub fn city_mut(&mut self, key: impl ContinentKey) -> Result<&mut City> {
100    let size = self.size;
101    let index = key.into_index(size);
102    if let Some(city) = self.field_mut(index)?.city_mut() {
103      Ok(city)
104    } else {
105      let coord = index.to_coord(size)?;
106      Err(Error::CityNotFound(coord))
107    }
108  }
109
110  pub fn cities(&self) -> impl Iterator<Item = &City> {
111    self.fields().filter_map(Field::city)
112  }
113
114  pub fn cities_mut(&mut self) -> impl Iterator<Item = &mut City> {
115    self.fields_mut().filter_map(Field::city_mut)
116  }
117
118  pub fn cities_by<F>(&self, f: F) -> impl Iterator<Item = &City>
119  where
120    F: Fn(&City) -> bool,
121  {
122    self.cities().filter(move |city| f(city))
123  }
124
125  pub fn cities_of<R>(&self, owner: R) -> impl Iterator<Item = &City>
126  where
127    R: Into<Ruler>,
128  {
129    let owner: Ruler = owner.into();
130    self.cities_by(move |city| city.owner() == &owner)
131  }
132
133  pub fn cities_within(&self, origin: Coord, distance: Distance) -> impl Iterator<Item = &City> {
134    self.cities_by(move |city| origin.is_within_distance(city.coord(), distance))
135  }
136
137  pub fn coords_by<F>(&self, f: F) -> impl Iterator<Item = Coord>
138  where
139    F: Fn(&City) -> bool,
140  {
141    self.cities_by(f).map(City::coord)
142  }
143
144  pub fn coords_of<R>(&self, owner: R) -> impl Iterator<Item = Coord>
145  where
146    R: Into<Ruler>,
147  {
148    let owner: Ruler = owner.into();
149    self.coords_by(move |city| city.owner() == &owner)
150  }
151
152  pub fn city_coords(&self) -> impl Iterator<Item = Coord> {
153    self.cities().map(City::coord)
154  }
155
156  pub fn owner_of(&self, key: impl ContinentKey) -> Result<&Ruler> {
157    self.city(key).map(City::owner)
158  }
159
160  pub fn search<S>(&self, search: S) -> Result<Vec<&City>>
161  where
162    S: Into<CitySearch>,
163  {
164    let mut found = Vec::new();
165    let mut search: CitySearch = search.into();
166    search.name = search
167      .name
168      .into_iter()
169      .map(|name| name.trim().to_lowercase().into())
170      .collect();
171
172    'outer: for city in self.cities() {
173      if search.coord.contains(&city.coord()) {
174        found.push(city);
175        continue;
176      }
177
178      if !search.name.is_empty() {
179        let city_name = city.name().to_lowercase();
180        for name in &search.name {
181          if city_name.contains(name.as_str()) {
182            found.push(city);
183            continue 'outer;
184          }
185        }
186      }
187    }
188
189    Ok(found)
190  }
191}
192
193impl Default for Continent {
194  fn default() -> Self {
195    Self::new(ContinentSize::default().get())
196  }
197}