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