pub use view::{DescriptionView, DetailsView, NameView, SummaryView};
mod building;
mod location;
mod region;
mod view;
use super::{Demographics, Field, Generate};
use initiative_macros::WordList;
use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Place {
pub uuid: Uuid,
#[serde(flatten)]
pub data: PlaceData,
}
#[derive(Clone, Debug, Deserialize, Default, Eq, PartialEq, Serialize)]
pub struct PlaceData {
pub location_uuid: Field<Uuid>,
pub subtype: Field<PlaceType>,
pub name: Field<String>,
pub description: Field<String>,
}
#[derive(Debug, Default)]
pub struct PlaceRelations {
pub location: Option<(Place, Option<Place>)>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, WordList)]
#[serde(into = "&'static str", try_from = "&str")]
pub enum PlaceType {
#[term = "place"]
Any,
Building(building::BuildingType),
Location(location::LocationType),
Region(region::RegionType),
}
impl Place {
pub fn display_name(&self) -> NameView {
self.data.display_name()
}
pub fn display_summary(&self) -> SummaryView {
self.data.display_summary()
}
pub fn display_description(&self) -> DescriptionView {
self.data.display_description()
}
pub fn display_details(&self, relations: PlaceRelations) -> DetailsView {
self.data.display_details(self.uuid, relations)
}
pub fn get_words() -> &'static [&'static str] {
&["place"][..]
}
pub fn lock_all(&mut self) {
self.data.lock_all()
}
pub fn apply_diff(&mut self, diff: &mut PlaceData) {
self.data.apply_diff(diff)
}
}
impl PlaceData {
pub fn display_name(&self) -> NameView {
NameView::new(self)
}
pub fn display_summary(&self) -> SummaryView {
SummaryView::new(self)
}
pub fn display_description(&self) -> DescriptionView {
DescriptionView::new(self)
}
pub fn display_details(&self, uuid: Uuid, relations: PlaceRelations) -> DetailsView {
DetailsView::new(self, uuid, relations)
}
pub fn lock_all(&mut self) {
let Self {
location_uuid,
subtype,
name,
description,
} = self;
location_uuid.lock();
subtype.lock();
name.lock();
description.lock();
}
pub fn apply_diff(&mut self, diff: &mut Self) {
let Self {
location_uuid,
subtype,
name,
description,
} = self;
location_uuid.apply_diff(&mut diff.location_uuid);
subtype.apply_diff(&mut diff.subtype);
name.apply_diff(&mut diff.name);
description.apply_diff(&mut diff.description);
}
}
impl Generate for PlaceData {
fn regenerate(&mut self, rng: &mut impl Rng, demographics: &Demographics) {
if !self.name.is_locked() || self.subtype.is_none() {
self.subtype
.replace_with(|_| PlaceType::generate(rng, demographics));
}
if let Some(value) = self.subtype.value() {
match value {
PlaceType::Building(_) => building::generate(self, rng, demographics),
PlaceType::Location(_) => location::generate(self, rng, demographics),
_ => {}
}
}
}
}
impl PlaceType {
pub const fn get_emoji(&self) -> &'static str {
if let Some(emoji) = match self {
Self::Any => None,
Self::Building(subtype) => subtype.get_emoji(),
Self::Location(subtype) => subtype.get_emoji(),
Self::Region(subtype) => subtype.get_emoji(),
} {
emoji
} else {
"ð"
}
}
}
impl Default for PlaceType {
fn default() -> Self {
Self::Any
}
}
impl Generate for PlaceType {
fn regenerate(&mut self, rng: &mut impl Rng, _demographics: &Demographics) {
*self = Self::get_words()
.nth(rng.gen_range(0..Self::word_count()))
.unwrap()
.parse()
.unwrap();
}
}
impl fmt::Display for PlaceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn generate_test() {
let demographics = Demographics::default();
let mut rng = SmallRng::seed_from_u64(1);
assert_ne!(
PlaceData::generate(&mut rng, &demographics).subtype,
PlaceData::generate(&mut rng, &demographics).subtype,
);
let mut rng1 = SmallRng::seed_from_u64(0);
let mut rng2 = SmallRng::seed_from_u64(0);
assert_eq!(
PlaceData::generate(&mut rng1, &demographics).subtype,
PlaceData::generate(&mut rng2, &demographics).subtype,
);
}
#[test]
fn place_type_default_test() {
assert_eq!(PlaceType::Any, PlaceType::default());
}
#[test]
fn place_type_serialize_deserialize_test() {
{
let inn: PlaceType = "inn".parse().unwrap();
assert_eq!(r#""inn""#, serde_json::to_string(&inn).unwrap());
assert_eq!(inn, serde_json::from_str::<PlaceType>(r#""inn""#).unwrap());
}
{
let business: PlaceType = "business".parse().unwrap();
assert_eq!(r#""business""#, serde_json::to_string(&business).unwrap());
assert_eq!(
business,
serde_json::from_str::<PlaceType>(r#""business""#).unwrap()
);
}
{
let building: PlaceType = "building".parse().unwrap();
assert_eq!(r#""building""#, serde_json::to_string(&building).unwrap());
assert_eq!(
building,
serde_json::from_str::<PlaceType>(r#""building""#).unwrap(),
);
}
{
let place: PlaceType = "place".parse().unwrap();
assert_eq!(r#""place""#, serde_json::to_string(&place).unwrap());
assert_eq!(
place,
serde_json::from_str::<PlaceType>(r#""place""#).unwrap(),
);
}
}
#[test]
fn place_serialize_deserialize_test() {
let place = oaken_mermaid_inn();
assert_eq!(
r#"{"uuid":"00000000-0000-0000-0000-000000000000","location_uuid":"00000000-0000-0000-0000-000000000000","subtype":"inn","name":"Oaken Mermaid Inn","description":"I am Mordenkainen"}"#,
serde_json::to_string(&place).unwrap(),
);
let value: Place = serde_json::from_str(r#"{"uuid":"00000000-0000-0000-0000-000000000000","location_uuid":"00000000-0000-0000-0000-000000000000","subtype":"inn","name":"Oaken Mermaid Inn","description":"I am Mordenkainen"}"#).unwrap();
assert_eq!(place, value);
}
#[test]
fn apply_diff_test_no_change() {
let mut place = oaken_mermaid_inn();
let mut diff = PlaceData::default();
place.data.apply_diff(&mut diff);
assert_eq!(oaken_mermaid_inn(), place);
assert_eq!(PlaceData::default(), diff);
}
#[test]
fn apply_diff_test_from_empty() {
let oaken_mermaid_inn = oaken_mermaid_inn();
let mut place = PlaceData::default();
let mut diff = oaken_mermaid_inn.data.clone();
place.apply_diff(&mut diff);
assert_eq!(oaken_mermaid_inn.data, place);
let mut empty_locked = PlaceData::default();
empty_locked.lock_all();
assert_eq!(empty_locked, diff);
}
#[test]
fn lock_all_test() {
let mut place = PlaceData::default();
place.lock_all();
assert_eq!(
PlaceData {
location_uuid: Field::Locked(None),
subtype: Field::Locked(None),
name: Field::Locked(None),
description: Field::Locked(None),
},
place,
);
}
#[test]
fn get_emoji_test() {
let mut words_emoji: Vec<(String, String)> = PlaceType::get_words()
.map(|word| {
(
word.to_string(),
PlaceType::parse_cs(word).unwrap().get_emoji().to_string(),
)
})
.collect();
words_emoji.sort();
let expect_words_emoji: Vec<(String, String)> = [
("abbey", "ð"),
("academy", "ð"),
("archipelago", "ð"),
("arena", "ð"),
("armorer", "ðĄ"),
("bakery", "ð"),
("bank", "ðĶ"),
("bar", "ðŧ"),
("barony", "ð"),
("barracks", "â"),
("barrens", "ð"),
("base", "â"),
("bathhouse", "ð"),
("beach", "ð"),
("blacksmith", "ðĄ"),
("brewery", "ðŧ"),
("bridge", "ð"),
("building", "ð"),
("business", "ðŠ"),
("camp", "ð"),
("campsite", "ð"),
("canyon", "ð"),
("capital", "ð"),
("caravansary", "ðĻ"),
("casino", "ð"),
("castle", "ð°"),
("cave", "ð"),
("cavern", "ð"),
("cemetery", "ðŠĶ"),
("chasm", "ð"),
("church", "ð"),
("citadel", "ð°"),
("city", "ð"),
("city-state", "ð"),
("club", ""),
("coastline", "ð"),
("college", "ð"),
("confederation", "ð"),
("continent", "ð"),
("country", "ð"),
("county", "ð"),
("court", "ð°"),
("crypt", "ðŠĶ"),
("desert", "ð"),
("distillery", "ðĨ"),
("district", "ð"),
("domain", "ð"),
("duchy", "ð"),
("duty-house", "ðŠ"),
("embassy", "ðĐ"),
("empire", "ð"),
("farm", "ðą"),
("ferry", "âī"),
("fighting-pit", "â"),
("food-counter", "ðē"),
("forest", "ðģ"),
("forge", "ðĨ"),
("fort", "ð°"),
("fortress", "ð°"),
("fountain", "âē"),
("furniture-shop", "ðŠ"),
("furrier", "ðĶ"),
("gambling-hall", "ð"),
("garden", "ðą"),
("gate", "ðŠ"),
("general-store", "ðŠ"),
("glacier", "ð"),
("gorge", "ð"),
("graveyard", "ðŠĶ"),
("grove", "ðģ"),
("guardhouse", "ðĄ"),
("guild-hall", "ðŠ"),
("hamlet", "ð"),
("harbor", "âĩ"),
("hermitage", "ð"),
("hill", "â°"),
("hotel", "ðĻ"),
("house", "ð "),
("imports-shop", "ðŠ"),
("inn", "ðĻ"),
("island", "ð"),
("jail", "ðĄ"),
("jeweller", "ð"),
("jungle", "ðģ"),
("keep", "ð°"),
("kingdom", "ð"),
("lake", "ð"),
("library", "ð"),
("lighthouse", "âĩ"),
("location", "ð"),
("lodge", "ðĻ"),
("lumberyard", "ðŠĩ"),
("magic-shop", "ðŠ"),
("manor", "ð "),
("mansion", "ð "),
("market", "ðŠ"),
("marsh", "ð"),
("mausoleum", "ðŠĶ"),
("mesa", "ð"),
("metropolis", "ð"),
("mill", "ðū"),
("mine", "â"),
("monastery", "ð"),
("monolith", "ðŋ"),
("monument", "ð―"),
("moor", "ð"),
("mosque", "ð"),
("mountain", "â°"),
("nation", "ð"),
("necropolis", "ðŠĶ"),
("neighborhood", "ð"),
("nightclub", "ðŧ"),
("nunnery", "ð"),
("oasis", "ðī"),
("ocean", "ð"),
("outpost", "ðĐ"),
("palace", "ð°"),
("parish", "ð"),
("pass", "â°"),
("peninsula", "ð"),
("pet-store", "ðķ"),
("pier", "âĩ"),
("place", "ð"),
("plain", "ð"),
("plateau", "ð"),
("portal", "ð"),
("principality", "ð"),
("prison", "ðĄ"),
("province", "ð"),
("pub", "ðŧ"),
("quarter", "ð"),
("realm", "ð"),
("reef", "ð"),
("region", "ð"),
("region", "ð"),
("residence", "ð "),
("restaurant", "ð―"),
("ridge", "â°"),
("rift", "ð"),
("river", "ð"),
("ruin", "ð"),
("school", "ð"),
("sea", "ð"),
("shipyard", "âĩ"),
("shop", "ðŠ"),
("shrine", "ð"),
("smithy", "ðĄ"),
("specialty-shop", "ðŠ"),
("spirits-shop", "ðĨ"),
("stable", "ð"),
("statue", "ð―"),
("store", "ðŠ"),
("street", "ð"),
("stronghold", "ð°"),
("swamp", "ð"),
("synagogue", "ð"),
("tavern", "ðĻ"),
("temple", "ð"),
("territory", "ð"),
("textiles-shop", "ðŠ"),
("theater", "ð"),
("tomb", "ðŠĶ"),
("tower", "ð°"),
("town", "ð"),
("trading-post", "ðŠ"),
("tree", "ðģ"),
("tundra", "â"),
("university", "ð"),
("vale", "ð"),
("valley", "ð"),
("vault", "ðĶ"),
("village", "ð"),
("wainwright", "ðŠ"),
("wall", "ð§ą"),
("ward", "ð"),
("warehouse", "ðĶ"),
("wasteland", "ð"),
("watch-house", "ðĄ"),
("weaponsmith", "ðĄ"),
("woodshop", "ðŠ"),
("world", "ð"),
]
.iter()
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect();
assert_eq!(expect_words_emoji, words_emoji);
}
fn oaken_mermaid_inn() -> Place {
Place {
uuid: uuid::Uuid::nil(),
data: PlaceData {
location_uuid: uuid::Uuid::nil().into(),
subtype: "inn".parse::<PlaceType>().ok().into(),
name: "Oaken Mermaid Inn".into(),
description: "I am Mordenkainen".into(),
},
}
}
}