nil_core/continent/
mod.rs1mod 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}