mod coord;
mod field;
mod index;
mod size;
#[cfg(test)]
mod tests;
use crate::city::{City, CitySearch};
use crate::error::{Error, Result};
use crate::ruler::Ruler;
use serde::{Deserialize, Serialize};
pub use coord::{Coord, Distance};
pub use field::{Field, PublicField};
pub use index::{ContinentIndex, ContinentKey};
pub use size::ContinentSize;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct Continent {
fields: Box<[Field]>,
size: ContinentSize,
}
impl Continent {
pub(crate) fn new(size: u8) -> Self {
let size = ContinentSize::new(size);
let capacity = usize::from(size.get()).pow(2);
let mut fields = Vec::with_capacity(capacity);
fields.resize_with(capacity, Field::default);
Self {
fields: fields.into_boxed_slice(),
size,
}
}
#[inline]
pub fn size(&self) -> ContinentSize {
self.size
}
#[inline]
pub fn radius(&self) -> u8 {
self.size.get().div_ceil(2)
}
#[inline]
pub fn center(&self) -> Coord {
Coord::splat(self.radius())
}
pub fn field(&self, key: impl ContinentKey) -> Result<&Field> {
let index = key.into_index(self.size);
self
.fields
.get(usize::from(index))
.ok_or(Error::IndexOutOfBounds(index))
}
pub(crate) fn field_mut(&mut self, key: impl ContinentKey) -> Result<&mut Field> {
let index = key.into_index(self.size);
self
.fields
.get_mut(usize::from(index))
.ok_or(Error::IndexOutOfBounds(index))
}
pub fn fields(&self) -> impl Iterator<Item = &Field> {
self.fields.iter()
}
fn fields_mut(&mut self) -> impl Iterator<Item = &mut Field> {
self.fields.iter_mut()
}
pub fn enumerate_fields(&self) -> impl Iterator<Item = (ContinentIndex, &Field)> {
self
.fields()
.enumerate()
.map(|(idx, field)| (ContinentIndex::new(idx), field))
}
pub fn city(&self, key: impl ContinentKey) -> Result<&City> {
let index = key.into_index(self.size);
if let Some(city) = self.field(index)?.city() {
Ok(city)
} else {
let coord = index.to_coord(self.size)?;
Err(Error::CityNotFound(coord))
}
}
pub fn city_mut(&mut self, key: impl ContinentKey) -> Result<&mut City> {
let size = self.size;
let index = key.into_index(size);
if let Some(city) = self.field_mut(index)?.city_mut() {
Ok(city)
} else {
let coord = index.to_coord(size)?;
Err(Error::CityNotFound(coord))
}
}
pub fn cities(&self) -> impl Iterator<Item = &City> {
self.fields().filter_map(Field::city)
}
pub fn cities_mut(&mut self) -> impl Iterator<Item = &mut City> {
self.fields_mut().filter_map(Field::city_mut)
}
pub fn cities_by<F>(&self, f: F) -> impl Iterator<Item = &City>
where
F: Fn(&City) -> bool,
{
self.cities().filter(move |city| f(city))
}
pub fn cities_of<R>(&self, owner: R) -> impl Iterator<Item = &City>
where
R: Into<Ruler>,
{
let owner: Ruler = owner.into();
self.cities_by(move |city| city.owner() == &owner)
}
pub fn cities_within(&self, origin: Coord, distance: Distance) -> impl Iterator<Item = &City> {
self.cities_by(move |city| origin.is_within_distance(city.coord(), distance))
}
pub fn coords_by<F>(&self, f: F) -> impl Iterator<Item = Coord>
where
F: Fn(&City) -> bool,
{
self.cities_by(f).map(City::coord)
}
pub fn coords_of<R>(&self, owner: R) -> impl Iterator<Item = Coord>
where
R: Into<Ruler>,
{
let owner: Ruler = owner.into();
self.coords_by(move |city| city.owner() == &owner)
}
pub fn city_coords(&self) -> impl Iterator<Item = Coord> {
self.cities().map(City::coord)
}
pub fn owner_of(&self, key: impl ContinentKey) -> Result<&Ruler> {
self.city(key).map(City::owner)
}
pub fn search<S>(&self, search: S) -> Result<Vec<&City>>
where
S: Into<CitySearch>,
{
let mut found = Vec::new();
let mut search: CitySearch = search.into();
search.name = search
.name
.into_iter()
.map(|name| name.trim().to_lowercase().into())
.collect();
'outer: for city in self.cities() {
if search.coord.contains(&city.coord()) {
found.push(city);
continue;
}
if !search.name.is_empty() {
let city_name = city.name().to_lowercase();
for name in &search.name {
if city_name.contains(name.as_str()) {
found.push(city);
continue 'outer;
}
}
}
}
Ok(found)
}
}
impl Default for Continent {
fn default() -> Self {
Self::new(ContinentSize::default().get())
}
}