diff --git a/Cargo.lock b/Cargo.lock index 61bf458..20f21d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1078,6 +1078,7 @@ dependencies = [ "rand", "reqwest", "serde", + "serde_json", "tokio 0.3.2", "tokio-compat-02", "toml", diff --git a/Cargo.toml b/Cargo.toml index 46f7bb5..08b4cbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1.0" rand = "0.7" reqwest = { version = "0.10", default-features = false, features = ["json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" toml = "0.5" tokio = { version = "0.3", features = ["full"] } tokio-compat-02 = "0.1" diff --git a/Config.example.toml b/Config.example.toml index 58d59e4..4779d9b 100644 --- a/Config.example.toml +++ b/Config.example.toml @@ -1,154 +1,3 @@ -[names] -prefix = [ - "feather", - "crow", - "bramble", - "hawk", - "fire", - "red", - "lion", - "misty", - "tawny", - "gray", - "running", - "golden", - "owl", - "rabbit", - "spider", - "leaf", - "squirrel", - "oak", - "storm", - "stone", - "sand", - "dark", - "long", - "one", - "half", - "holly", - "black", - "brindle", - "leopard", - "tiger", - "moth", - "spotted", - "raven", - "sorrel", - "cloud", - "bright", - "lost", - "cedar", - "ginger", - "blue", - "white", - "willow", - "mouse", - "dust", - "frost", - "speckle", - "small", - "patch", - "dapple", - "broken", - "stumpy", - "brown", - "wet", - "claw", - "little", - "night", - "dawn", - "ash", - "tall", - "crooked", - "yellow", - "cinder", - "bracken", - "dead", - "bark", - "torn", - "morning", - "mud", - "shade", - "loud", - "silver", - "swift", - "thorn", - "heavy", - "fern", - "rowan", - "gorse", - "moss", - "russet", - "jagged", - "rain", - "soot", - "shrew", - "talon", - "weasel", - "robin", - "thistle", - "vole", - "swallow", - "splash", - "smoke", - "birch", - "larch", - "berry", - "hazel", - "web", - "rush", - "beech", - "reed", - "mole", - "honey", - "poppy", - "ivy", - "snow", - "kestrel", - "hare", - "breeze", - "minnow", - "pounce", - "pebble", - "ripple", -] -suffix = [ - "kit", - "paw", - "star", - "leaf", - "tail", - "heart", - "claw", - "storm", - "stripe", - "wind", - "pelt", - "fur", - "face", - "flower", - "ear", - "eye", - "foot", - "nose", - "cloud", - "fang", - "poppy", - "whisker", - "belly", - "stream", - "pool", - "throat", - "brook", - "tooth", - "step", - "frost", - "wing", - "fall", - "bird", - "feather", -] - [timing] duration = 360 diff --git a/Description.json b/Description.json new file mode 100644 index 0000000..ad9293b --- /dev/null +++ b/Description.json @@ -0,0 +1,297 @@ +{ + "root": "pelt", + "entries": [{ + "name": "descriptor", + "variants": [ + "sleek", + "large", + "small", + "swift", + "skinny", + "thin", + "young", + "old", + "long-legged", + "long-haired", + "long-tailed", + "huge", + "massive", + "battle-scarred", + "pretty", + "handsome", + "beautiful", + "mottled" + ], + "next": [{ + "from": [ + "massive", + "huge", + "large" + ], + "to": [ + "long-legged", + "long-haired", + "old", + "young", + "long-tailed", + "battle-scarred", + "handsome", + "pretty", + "beautiful", + "mottled" + ], + "chance": 0.5, + "join": ", " + }, { + "from": [ + "small" + ], + "to": [ + "sleek", + "swift", + "skinny", + "thin", + "old", + "young", + "long-haired", + "battle-scarred", + "handsome", + "pretty", + "beautiful", + "mottled" + ], + "chance": 0.5, + "join": ", " + }, { + "from": [ + "handsome", + "pretty", + "beautiful" + ], + "to": [ + "old", + "young" + ], + "chance": 0.5, + "join": ", " + }] + }, { + "name": "eye-color", + "variants": [ + "blue", + "amber", + "green", + "brown" + ], + "depends": [{ + "variants": "any", + "depends": "color-modifier", + "chance": 0.3, + "order": "{color-modifier} {base}" + }] + }, { + "name": "post-descriptor", + "variants": [ + "long claws", + "a twisted paw", + "eyes", + "paws", + "stripes", + "spots", + "splotches" + ], + "depends": [{ + "variants": ["eyes"], + "depends": "eye-color", + "chance": 1, + "order": "{eye-color} {base}" + }, { + "variants": ["paws", "stripes", "spots", "splotches"], + "depends": "pelt-color", + "chance": 1, + "order": "{pelt-color} {base}" + }], + "next": [{ + "from": "any", + "to": "any", + "chance": 0.4, + "join": " and " + }] + }, { + "name": "solid", + "variants": ["solid"] + }, { + "name": "very", + "variants": ["very"] + }, { + "name": "color-modifier", + "variants": [ + "dark", + "light", + "pale", + "bright" + ], + "depends": [{ + "variants": "any", + "depends": "very", + "chance": 0.2, + "order": "{very} {base}" + }] + }, { + "name": "pelt-color", + "variants": [ + "black", + "black-and-white", + "black-and-gray", + "gray", + "gray-and-white", + "silver", + "blue-gray", + "ginger", + "brown", + "reddish-brown", + "golden-brown", + "dusky-brown", + "white" + ], + "depends": [{ + "variants": [ + "black", + "gray", + "silver", + "blue-gray", + "ginger", + "brown", + "reddish-brown", + "golden-brown", + "dusky-brown", + "white" + ], + "depends": "solid", + "chance": 0.2, + "order": "{solid} {base}" + }, { + "variants": ["black"], + "depends": "color-modifier", + "chance": 0.3, + "order": "{color-modifier} {base}", + "weights": [ + [1, "dark"] + ] + }, { + "variants": [ + "gray", + "silver", + "blue-gray", + "ginger", + "brown", + "reddish-brown", + "golden-brown" + ], + "depends": "color-modifier", + "chance": 0.3, + "order": "{color-modifier} {base}", + "weights": [ + [1, "light"], + [1, "pale"], + [1, "bright"] + ] + }, { + "variants": ["white"], + "depends": "color-modifier", + "chance": 0.3, + "order": "{color-modifier} {base}", + "weights": [ + [1, "bright"] + ] + }] + }, { + "name": "gembder", + "variants": [ + "she-cat", + "queen", + "tom" + ] + }, { + "name": "pelt", + "variants": [ + "tortoiseshell", + "tabby", + "cat" + ], + "silent": ["cat"], + "depends": [{ + "variants": ["tortoiseshell", "tabby"], + "depends": "pelt-color", + "chance": 0.5, + "order": "{pelt-color} {base}" + }, { + "variants": ["cat"], + "depends": "pelt-color", + "chance": 1, + "order": "{pelt-color} {base}" + }, { + "variants": ["tortoiseshell"], + "depends": "gembder", + "chance": 0.5, + "order": "{base} {gembder}", + "weights": [ + [80, "she-cat"], + [20, "queen"], + [1, "tom"] + ] + }, { + "variants": ["tabby"], + "depends": "gembder", + "chance": 0.5, + "order": "{base} {gembder}" + }, { + "variants": ["cat"], + "depends": "gembder", + "chance": 0.5, + "order": "{base} {gembder}", + "default": "cat" + }, { + "variants": "any", + "depends": "descriptor", + "chance": 0.5, + "order": "{descriptor} {base}" + }, { + "variants": ["tabby"], + "depends": "post-descriptor", + "chance": 0.5, + "order": "{base} with {post-descriptor}", + "weights": [ + [2, "long claws"], + [1, "a twisted paw"], + [4, "eyes"], + [3, "paws"], + [2, "stripes"] + ] + }, { + "variants": ["tortoiseshell"], + "depends": "post-descriptor", + "chance": 0.5, + "order": "{base} with {post-descriptor}", + "weights": [ + [2, "long claws"], + [1, "a twisted paw"], + [4, "eyes"], + [3, "paws"], + [2, "splotches"] + ] + }, { + "variants": ["cat"], + "depends": "post-descriptor", + "chance": 0.5, + "order": "{base} with {post-descriptor}", + "weights": [ + [2, "long claws"], + [1, "a twisted paw"], + [4, "eyes"], + [3, "paws"], + [2, "spots"] + ] + }] + }] +} diff --git a/Name.json b/Name.json new file mode 100644 index 0000000..dc60d3c --- /dev/null +++ b/Name.json @@ -0,0 +1,152 @@ +{ + "prefix": [ + "feather", + "crow", + "bramble", + "hawk", + "fire", + "red", + "lion", + "misty", + "tawny", + "gray", + "running", + "golden", + "owl", + "rabbit", + "spider", + "leaf", + "squirrel", + "oak", + "storm", + "stone", + "sand", + "dark", + "long", + "one", + "half", + "holly", + "black", + "brindle", + "leopard", + "tiger", + "moth", + "spotted", + "raven", + "sorrel", + "cloud", + "bright", + "lost", + "cedar", + "ginger", + "blue", + "white", + "willow", + "mouse", + "dust", + "frost", + "speckle", + "small", + "patch", + "dapple", + "broken", + "stumpy", + "brown", + "wet", + "claw", + "little", + "night", + "dawn", + "ash", + "tall", + "crooked", + "yellow", + "cinder", + "bracken", + "dead", + "bark", + "torn", + "morning", + "mud", + "shade", + "loud", + "silver", + "swift", + "thorn", + "heavy", + "fern", + "rowan", + "gorse", + "moss", + "russet", + "jagged", + "rain", + "soot", + "shrew", + "talon", + "weasel", + "robin", + "thistle", + "vole", + "swallow", + "splash", + "smoke", + "birch", + "larch", + "berry", + "hazel", + "web", + "rush", + "beech", + "reed", + "mole", + "honey", + "poppy", + "ivy", + "snow", + "kestrel", + "hare", + "breeze", + "minnow", + "pounce", + "pebble", + "ripple" + ], + "suffix": [ + "kit", + "paw", + "star", + "leaf", + "tail", + "heart", + "claw", + "storm", + "stripe", + "wind", + "pelt", + "fur", + "face", + "flower", + "ear", + "eye", + "foot", + "nose", + "cloud", + "fang", + "poppy", + "whisker", + "belly", + "stream", + "pool", + "throat", + "brook", + "tooth", + "step", + "frost", + "wing", + "fall", + "bird", + "feather" + ] +} + diff --git a/src/description.rs b/src/description.rs index b739181..5032437 100644 --- a/src/description.rs +++ b/src/description.rs @@ -1,728 +1,272 @@ -use rand::Rng; -use std::{collections::HashSet, fmt}; +use anyhow::Result; +use rand::{seq::SliceRandom, Rng}; +use std::{collections::HashSet, path::Path}; -fn select_weighted(weights: Vec<(u8, T)>, rng: &mut impl Rng, nothing_weight: u8) -> Option { - let total = weights.iter().fold(0, |acc, (i, _)| acc + i) + nothing_weight; - - if total == 0 { - return None; - } +fn select(variants: &HashSet, weights: &[(u64, String)], rng: &mut impl Rng) -> String { + let filtered = weights + .iter() + .filter(|(_, name)| variants.contains(name)) + .collect::>(); + let total = filtered.iter().fold(0, |acc, (weight, _)| acc + weight); let selected = rng.gen_range(0, total); - let (_, opt) = weights - .into_iter() - .fold((selected, None), |(selected, acc), (weight, item)| { - if acc.is_some() { - return (selected, acc); + let (s, _) = filtered + .iter() + .fold((None, selected), |(opt, count), (weight, item)| { + if opt.is_some() { + return (opt, 0); } - if selected.saturating_sub(weight) == 0 { - return (0, Some(item)); + let new_count = count.saturating_sub(*weight); + + if new_count == 0 { + return (Some(item), 0); } - (selected.saturating_sub(weight), None) + (None, new_count) }); - opt + s.unwrap().to_owned() } -#[derive(Debug)] -enum Descriptor { - Sleek, - Large, - Small, - Swift, - Skinny, - Thin, - Young, - Old, - LongLeg, - LongHair, - LongTail, - Huge, - Massive, - Scars, - Pretty, - Handsome, - Beautiful, - Mottled, +#[derive(serde::Deserialize)] +struct Entry { + name: String, + variants: HashSet, + + #[serde(default)] + silent: HashSet, + + #[serde(default)] + next: Vec, + + #[serde(default)] + depends: Vec, } -impl Descriptor { - fn weights(variant: PeltVariant) -> Vec<(u8, Descriptor)> { - if PeltVariant::Base == variant { - vec![ - (1, Descriptor::Sleek), - (1, Descriptor::Large), - (1, Descriptor::Small), - (1, Descriptor::Swift), - (1, Descriptor::Skinny), - (1, Descriptor::Thin), - (1, Descriptor::Young), - (1, Descriptor::Old), - (1, Descriptor::LongLeg), - (1, Descriptor::LongHair), - (1, Descriptor::LongTail), - (1, Descriptor::Huge), - (1, Descriptor::Massive), - (1, Descriptor::Scars), - (1, Descriptor::Handsome), - (1, Descriptor::Pretty), - (1, Descriptor::Beautiful), - (1, Descriptor::Mottled), - ] - } else { - vec![ - (1, Descriptor::Sleek), - (1, Descriptor::Large), - (1, Descriptor::Small), - (1, Descriptor::Swift), - (1, Descriptor::Skinny), - (1, Descriptor::Thin), - (1, Descriptor::Young), - (1, Descriptor::Old), - (1, Descriptor::LongLeg), - (1, Descriptor::LongHair), - (1, Descriptor::LongTail), - (1, Descriptor::Huge), - (1, Descriptor::Massive), - (1, Descriptor::Scars), - (1, Descriptor::Handsome), - (1, Descriptor::Pretty), - (1, Descriptor::Beautiful), - ] +impl Entry { + fn gen( + &self, + weights: Option<&[(u64, String)]>, + entries: &[Entry], + rng: &mut impl Rng, + ) -> String { + let variant = self.gen_variant(weights, rng); + + let building = self.gen_depends(&variant, entries, rng); + + let mut seen = HashSet::new(); + seen.insert(variant.clone()); + self.gen_next(building, &variant, weights, entries, &mut seen, rng) + } + + fn gen_variant(&self, weights: Option<&[(u64, String)]>, rng: &mut impl Rng) -> String { + if let Some(weights) = weights { + return select(&self.variants, &weights, rng); } - } - fn next_weights(&self, variant: PeltVariant) -> Vec<(u8, Descriptor)> { - match self { - Descriptor::Sleek => vec![], - Descriptor::Massive | Descriptor::Huge | Descriptor::Large - if variant == PeltVariant::Base => - { - vec![ - (1, Descriptor::LongLeg), - (1, Descriptor::LongHair), - (1, Descriptor::Old), - (1, Descriptor::Young), - (1, Descriptor::LongTail), - (1, Descriptor::Scars), - (1, Descriptor::Handsome), - (1, Descriptor::Pretty), - (1, Descriptor::Beautiful), - (1, Descriptor::Mottled), - ] - } - Descriptor::Massive | Descriptor::Huge | Descriptor::Large => vec![ - (1, Descriptor::LongLeg), - (1, Descriptor::LongHair), - (1, Descriptor::Old), - (1, Descriptor::Young), - (1, Descriptor::LongTail), - (1, Descriptor::Scars), - (1, Descriptor::Handsome), - (1, Descriptor::Pretty), - (1, Descriptor::Beautiful), - ], - Descriptor::Small if variant == PeltVariant::Base => vec![ - (1, Descriptor::Sleek), - (1, Descriptor::Swift), - (1, Descriptor::Skinny), - (1, Descriptor::Thin), - (1, Descriptor::Old), - (1, Descriptor::Young), - (1, Descriptor::LongHair), - (1, Descriptor::Scars), - (1, Descriptor::Handsome), - (1, Descriptor::Pretty), - (1, Descriptor::Beautiful), - (1, Descriptor::Mottled), - ], - Descriptor::Small => vec![ - (1, Descriptor::Sleek), - (1, Descriptor::Swift), - (1, Descriptor::Skinny), - (1, Descriptor::Thin), - (1, Descriptor::Old), - (1, Descriptor::Young), - (1, Descriptor::LongHair), - (1, Descriptor::Scars), - (1, Descriptor::Handsome), - (1, Descriptor::Pretty), - (1, Descriptor::Beautiful), - ], - Descriptor::Swift => vec![], - Descriptor::Skinny => vec![], - Descriptor::Thin => vec![], - Descriptor::Young => vec![], - Descriptor::Old => vec![], - Descriptor::LongLeg => vec![], - Descriptor::LongHair => vec![], - Descriptor::LongTail => vec![], - Descriptor::Scars => vec![], - Descriptor::Handsome => vec![(1, Descriptor::Old), (1, Descriptor::Young)], - Descriptor::Pretty => vec![(1, Descriptor::Old), (1, Descriptor::Young)], - Descriptor::Beautiful => vec![(1, Descriptor::Old), (1, Descriptor::Young)], - Descriptor::Mottled => vec![], - } - } -} - -impl fmt::Display for Descriptor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - Descriptor::Sleek => "sleek", - Descriptor::Large => "large", - Descriptor::Small => "small", - Descriptor::Swift => "swift", - Descriptor::Skinny => "skinny", - Descriptor::Thin => "thin", - Descriptor::Young => "young", - Descriptor::Old => "old", - Descriptor::LongLeg => "long-legged", - Descriptor::LongHair => "long-haired", - Descriptor::LongTail => "long-tailed", - Descriptor::Huge => "huge", - Descriptor::Massive => "massive", - Descriptor::Scars => "battle-scarred", - Descriptor::Handsome => "handsome", - Descriptor::Pretty => "pretty", - Descriptor::Beautiful => "beautiful", - Descriptor::Mottled => "mottled", - }; - - write!(f, "{}", s) - } -} - -#[derive(Debug)] -struct Descriptors { - inner: Vec, -} - -impl Descriptors { - fn gen(rng: &mut impl Rng, variant: PeltVariant) -> Self { - let mut v = vec![]; - if let Some(first) = select_weighted(Descriptor::weights(variant), rng, 10) { - let mut first = first; - let mut next_weights; - loop { - next_weights = first.next_weights(variant); - v.push(first); - if let Some(next) = select_weighted(next_weights, rng, 10) { - first = next; - } else { - break; - } - } - } - Descriptors { inner: v } - } - - fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl fmt::Display for Descriptors { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = self - .inner + self.variants .iter() - .map(|d| d.to_string()) .collect::>() - .join(", "); - - write!(f, "{}", s) - } -} - -#[derive(Debug)] -enum PostDescriptor { - LongClaw, - TwistedPaw, - Eyes(EyeColor), - Paw(PeltColor), - Stripe(PeltColor), - Spot(PeltColor), - Splotch(PeltColor), -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -enum PostDescriptorVariant { - LongClaw, - TwistedPaw, - Eyes, - Paw, - Stripe, - Spot, - Splotch, -} - -impl PostDescriptorVariant { - fn weights(pelt_variant: PeltVariant) -> Vec<(u8, PostDescriptorVariant)> { - match pelt_variant { - PeltVariant::Tabby => vec![ - (2, PostDescriptorVariant::LongClaw), - (1, PostDescriptorVariant::TwistedPaw), - (4, PostDescriptorVariant::Eyes), - (3, PostDescriptorVariant::Paw), - (3, PostDescriptorVariant::Stripe), - ], - PeltVariant::Tortoiseshell => vec![ - (2, PostDescriptorVariant::LongClaw), - (1, PostDescriptorVariant::TwistedPaw), - (4, PostDescriptorVariant::Eyes), - (3, PostDescriptorVariant::Paw), - (2, PostDescriptorVariant::Splotch), - ], - PeltVariant::Base => vec![ - (2, PostDescriptorVariant::LongClaw), - (1, PostDescriptorVariant::TwistedPaw), - (4, PostDescriptorVariant::Eyes), - (3, PostDescriptorVariant::Paw), - (2, PostDescriptorVariant::Spot), - ], - } - } - - fn into_post_descriptor(self, rng: &mut impl Rng) -> PostDescriptor { - match self { - PostDescriptorVariant::LongClaw => PostDescriptor::LongClaw, - PostDescriptorVariant::TwistedPaw => PostDescriptor::TwistedPaw, - PostDescriptorVariant::Eyes => PostDescriptor::Eyes(EyeColor::gen(rng)), - PostDescriptorVariant::Paw => PostDescriptor::Paw(PeltColor::gen(rng, true)), - PostDescriptorVariant::Stripe => PostDescriptor::Stripe(PeltColor::gen(rng, true)), - PostDescriptorVariant::Spot => PostDescriptor::Spot(PeltColor::gen(rng, true)), - PostDescriptorVariant::Splotch => PostDescriptor::Splotch(PeltColor::gen(rng, true)), - } - } -} - -impl fmt::Display for PostDescriptor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - PostDescriptor::LongClaw => write!(f, "long claws"), - PostDescriptor::TwistedPaw => write!(f, "a twisted paw"), - PostDescriptor::Eyes(color) => write!(f, "{} eyes", color), - PostDescriptor::Paw(color) => write!(f, "{} paws", color), - PostDescriptor::Stripe(color) => write!(f, "{} stripes", color), - PostDescriptor::Spot(color) => write!(f, "{} spots", color), - PostDescriptor::Splotch(color) => write!(f, "{} splotches", color), - } - } -} - -#[derive(Debug)] -struct PostDescriptors { - inner: Vec, -} - -impl PostDescriptors { - fn gen(rng: &mut impl Rng, pelt_variant: PeltVariant) -> Self { - let mut v = vec![]; - let mut hs = HashSet::new(); - while let Some(variant) = - select_weighted(PostDescriptorVariant::weights(pelt_variant), rng, 10) - { - let new = hs.insert(variant); - if !new { - break; - } - v.push(variant.into_post_descriptor(rng)); - } - PostDescriptors { inner: v } - } - - fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl fmt::Display for PostDescriptors { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.inner.len() == 0 { - return write!(f, ""); - } - - let joined = self - .inner - .iter() - .map(|pd| pd.to_string()) - .collect::>() - .join(" and "); - - write!(f, "with {}", joined) - } -} - -#[derive(Debug)] -enum ColorModifier { - Dark, - Light, - Pale, -} - -impl ColorModifier { - fn gen(rng: &mut impl Rng) -> Option { - select_weighted( - vec![ - (1, ColorModifier::Dark), - (1, ColorModifier::Light), - (1, ColorModifier::Pale), - ], - rng, - 9, - ) - } -} - -impl fmt::Display for ColorModifier { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - ColorModifier::Dark => "dark", - ColorModifier::Light => "light", - ColorModifier::Pale => "pale", - }; - - write!(f, "{}", s) - } -} - -#[derive(Debug)] -struct PeltColor { - pelt_color: InnerPeltColor, - modifier: Option<(bool, ColorModifier)>, - solid: bool, -} - -impl PeltColor { - fn gen(rng: &mut impl Rng, can_be_solid: bool) -> Self { - let pelt_color = InnerPeltColor::gen(rng); - let solid = can_be_solid && pelt_color.is_solid() && rng.gen_bool(0.2); - PeltColor { - pelt_color, - modifier: ColorModifier::gen(rng).map(|modifier| (rng.gen_bool(0.2), modifier)), - solid, - } - } - - fn is_solid(&self) -> bool { - self.solid - } -} - -impl fmt::Display for PeltColor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match &self.modifier { - Some((true, modifier)) => write!(f, "very {} {}", modifier, self.pelt_color), - Some((false, modifier)) => write!(f, "{} {}", modifier, self.pelt_color), - None if self.is_solid() => write!(f, "solid {}", self.pelt_color), - None => write!(f, "{}", self.pelt_color), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -enum InnerPeltColor { - Black(Option), - Gray(Option), - Silver, - BlueGray, - Ginger, - Brown, - ReddishBrown, - GoldenBrown, - DuskyBrown, - White, -} - -impl InnerPeltColor { - fn gen(rng: &mut impl Rng) -> Self { - select_weighted( - vec![ - (1, InnerPeltColor::Black(Some(BlackSecondColor::Gray))), - (1, InnerPeltColor::Black(Some(BlackSecondColor::White))), - (2, InnerPeltColor::Black(None)), - (1, InnerPeltColor::Gray(Some(GraySecondColor::White))), - (2, InnerPeltColor::Gray(None)), - (2, InnerPeltColor::Silver), - (2, InnerPeltColor::BlueGray), - (2, InnerPeltColor::Ginger), - (2, InnerPeltColor::Brown), - (2, InnerPeltColor::ReddishBrown), - (2, InnerPeltColor::GoldenBrown), - (2, InnerPeltColor::DuskyBrown), - (2, InnerPeltColor::White), - ], - rng, - 0, - ) - .unwrap() - } - - fn is_solid(&self) -> bool { - match self { - InnerPeltColor::Black(Some(_)) | InnerPeltColor::Gray(Some(_)) => false, - _ => true, - } - } -} - -impl fmt::Display for InnerPeltColor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - InnerPeltColor::Black(Some(BlackSecondColor::Gray)) => "black-and-gray", - InnerPeltColor::Black(Some(BlackSecondColor::White)) => "black-and-white", - InnerPeltColor::Black(None) => "black", - InnerPeltColor::Gray(Some(GraySecondColor::White)) => "gray-and-white", - InnerPeltColor::Gray(None) => "gray", - InnerPeltColor::Silver => "silver", - InnerPeltColor::BlueGray => "bluegray", - InnerPeltColor::Ginger => "ginger", - InnerPeltColor::Brown => "brown", - InnerPeltColor::ReddishBrown => "reddish-brown", - InnerPeltColor::GoldenBrown => "golden-brown", - InnerPeltColor::DuskyBrown => "dusky-brown", - InnerPeltColor::White => "white", - }; - - write!(f, "{}", s) - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -enum BlackSecondColor { - Gray, - White, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -enum GraySecondColor { - White, -} - -#[derive(Debug)] -enum Pelt { - Tortoiseshell(Option, Option), - Tabby(Option, Option), - Base(PeltColor, Option), -} - -impl Pelt { - fn gen(rng: &mut impl Rng) -> Pelt { - select_weighted(PeltVariant::weights(), rng, 0) + .choose(rng) .unwrap() - .into_pelt(rng) + .to_string() } - fn as_variant(&self) -> PeltVariant { - match self { - Pelt::Tortoiseshell(_, _) => PeltVariant::Tortoiseshell, - Pelt::Tabby(_, _) => PeltVariant::Tabby, - Pelt::Base(_, _) => PeltVariant::Base, - } - } -} - -impl fmt::Display for Pelt { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Pelt::Tortoiseshell(modifier, gembder) => { - if let Some(m) = modifier { - write!(f, "{} ", m)?; - } - write!(f, "tortoiseshell")?; - if let Some(g) = gembder { - write!(f, " {}", g)?; - } - Ok(()) - } - Pelt::Tabby(color, gembder) => { - if let Some(c) = color { - write!(f, "{} ", c)?; - } - write!(f, "tabby")?; - if let Some(g) = gembder { - write!(f, " {}", g)?; - } - Ok(()) - } - Pelt::Base(color, Some(gembder)) => write!(f, "{} {}", color, gembder), - Pelt::Base(color, None) => write!(f, "{} cat", color), - } - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -enum PeltVariant { - Tortoiseshell, - Tabby, - Base, -} - -impl PeltVariant { - fn weights() -> Vec<(u8, PeltVariant)> { - vec![ - (1, PeltVariant::Tortoiseshell), - (3, PeltVariant::Tabby), - (5, PeltVariant::Base), - ] - } - - fn into_pelt(self, rng: &mut impl Rng) -> Pelt { - let has_color = rng.gen_bool(0.4); - - match self { - PeltVariant::Tortoiseshell => { - Pelt::Tortoiseshell(ColorModifier::gen(rng), Gembder::gen_skewed(rng)) - } - PeltVariant::Tabby if has_color => { - Pelt::Tabby(Some(PeltColor::gen(rng, false)), Gembder::gen(rng)) - } - PeltVariant::Tabby => Pelt::Tabby(None, Gembder::gen(rng)), - PeltVariant::Base => Pelt::Base(PeltColor::gen(rng, true), Gembder::gen(rng)), - } - } -} - -#[derive(Debug)] -enum Gembder { - Queen, - SheCat, - Tom, -} - -impl Gembder { - fn weights_skewed() -> Vec<(u8, Gembder)> { - vec![ - (1, Gembder::Tom), - (80, Gembder::SheCat), - (20, Gembder::Queen), - ] - } - - fn weights() -> Vec<(u8, Gembder)> { - vec![(5, Gembder::Tom), (3, Gembder::SheCat), (2, Gembder::Queen)] - } - - fn gen(rng: &mut impl Rng) -> Option { - select_weighted(Self::weights(), rng, 10) - } - - fn gen_skewed(rng: &mut impl Rng) -> Option { - select_weighted(Self::weights_skewed(), rng, 101) - } -} - -impl fmt::Display for Gembder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - Gembder::Queen => "queen", - Gembder::SheCat => "she-cat", - Gembder::Tom => "tom", + fn gen_depends(&self, variant: &str, entries: &[Entry], rng: &mut impl Rng) -> String { + let base = if self.silent.contains(variant) { + String::new() + } else { + variant.to_string() }; - write!(f, "{}", s) + self.depends + .iter() + .fold(base, |acc, depend| depend.gen(acc, variant, entries, rng)) + } + + fn gen_next( + &self, + building: String, + variant: &str, + weights: Option<&[(u64, String)]>, + entries: &[Entry], + seen: &mut HashSet, + rng: &mut impl Rng, + ) -> String { + let (s, _, _) = self.next.iter().fold( + (building, variant.to_string(), seen), + |(acc, v, seen), next| { + if let Some(variant) = next.gen(&v, &self.variants, weights, rng) { + if seen.contains(&variant) { + return (acc, variant, seen); + } + seen.insert(variant.clone()); + + let depends = self.gen_depends(&variant, entries, rng); + let building = next.join(acc, &depends); + + let building = self.gen_next(building, &variant, weights, entries, seen, rng); + + return (building, variant, seen); + } + + (acc, v, seen) + }, + ); + + s } } -#[derive(Debug)] -struct EyeColor { - eye_color: InnerEyeColor, - modifier: Option, +#[derive(serde::Deserialize)] +struct Next { + from: Anyable, + to: Anyable, + chance: f64, + join: String, } -impl EyeColor { - fn gen(rng: &mut impl Rng) -> EyeColor { - EyeColor { - eye_color: InnerEyeColor::gen(rng), - modifier: ColorModifier::gen(rng), +impl Next { + fn join(&self, building: String, new: &str) -> String { + building + &self.join + new + } + + fn gen( + &self, + variant: &str, + variants: &HashSet, + weights: Option<&[(u64, String)]>, + rng: &mut impl Rng, + ) -> Option { + if !self.from.contains(variant) { + return None; + } + + if !rng.gen_bool(self.chance) { + return None; + } + + if let Some(weights) = weights { + let hs: HashSet<_> = weights + .iter() + .filter_map(|(_, s)| { + if self.to.contains(s) { + Some(s.to_string()) + } else { + None + } + }) + .collect(); + + return Some(select(&hs, weights, rng)); + } + + Some( + variants + .iter() + .filter(|s| self.to.contains(s)) + .collect::>() + .choose(rng) + .unwrap() + .to_string(), + ) + } +} + +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum Anyable { + Specified(HashSet), + + Any(Any), +} + +#[derive(serde::Deserialize)] +enum Any { + #[serde(rename = "any")] + Any, +} + +impl Anyable { + fn contains(&self, variant: &str) -> bool { + match self { + Anyable::Specified(ref hs) => hs.contains(variant), + Anyable::Any(_) => true, } } } -impl fmt::Display for EyeColor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match &self.modifier { - Some(modifier) => write!(f, "{} {}", modifier, self.eye_color), - None => write!(f, "{}", self.eye_color), - } - } +#[derive(serde::Deserialize)] +struct Depend { + variants: Anyable, + depends: String, + chance: f64, + order: String, + weights: Option>, + default: Option, } -#[derive(Debug)] -enum InnerEyeColor { - Blue, - Amber, - Green, - Brown, -} - -impl InnerEyeColor { - fn weights() -> Vec<(u8, InnerEyeColor)> { - vec![ - (1, InnerEyeColor::Blue), - (1, InnerEyeColor::Amber), - (1, InnerEyeColor::Green), - (3, InnerEyeColor::Brown), - ] - } - - fn gen(rng: &mut impl Rng) -> InnerEyeColor { - select_weighted(Self::weights(), rng, 0).unwrap() - } -} - -impl fmt::Display for InnerEyeColor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - InnerEyeColor::Blue => "blue", - InnerEyeColor::Amber => "amber", - InnerEyeColor::Green => "green", - InnerEyeColor::Brown => "brown", - }; - - write!(f, "{}", s) - } -} - -struct Builder { - descriptors: Descriptors, - pelt: Pelt, - post_descriptors: PostDescriptors, -} - -impl Builder { - fn gen(rng: &mut impl Rng) -> Self { - let pelt = Pelt::gen(rng); - let variant = pelt.as_variant(); - - Builder { - descriptors: Descriptors::gen(rng, variant), - pelt, - post_descriptors: PostDescriptors::gen(rng, variant), - } - } -} - -impl fmt::Display for Builder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if !self.descriptors.is_empty() { - write!(f, "{} ", self.descriptors)?; +impl Depend { + fn gen(&self, acc: String, variant: &str, entries: &[Entry], rng: &mut impl Rng) -> String { + if !self.variants.contains(variant) { + return acc; } - write!(f, "{}", self.pelt)?; + if !rng.gen_bool(self.chance) { + if let Some(default) = self.default.as_ref() { + if acc.is_empty() { + return default.to_string(); + } - if !self.post_descriptors.is_empty() { - write!(f, " {}", self.post_descriptors)?; + return self.join(acc, default); + } + return acc; } - Ok(()) + let entry = entries + .into_iter() + .find(|entry| entry.name == self.depends) + .expect(&format!("Missing entry for {}", self.depends)); + + let value = entry.gen(self.weights.as_ref().map(Vec::as_slice), entries, rng); + + if acc.is_empty() { + return value; + } + + self.join(acc, &value) + } + + fn join(&self, acc: String, value: &str) -> String { + self.order + .replace("{base}", &acc) + .replace(&format!("{{{}}}", self.depends), value) } } -pub(crate) fn run(rng: &mut impl Rng) -> String { - Builder::gen(rng).to_string() +#[derive(serde::Deserialize)] +pub(crate) struct Config { + root: String, + entries: Vec, +} + +impl Config { + pub(crate) fn gen(&self, rng: &mut impl Rng) -> String { + let root = self + .entries + .iter() + .find(|entry| entry.name == self.root) + .expect(&format!("Invalid config: no entry called {}", self.root)); + + root.gen(None, &self.entries, rng) + } +} + +pub(crate) async fn config(path: impl AsRef) -> Result { + let bytes = tokio::fs::read(path).await?; + let config: Config = serde_json::from_slice(&bytes)?; + + Ok(config) } diff --git a/src/main.rs b/src/main.rs index 67f5cd2..6d17b95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use rand::{seq::SliceRandom, thread_rng, Rng}; +use rand::{thread_rng, Rng}; use reqwest::{header::HeaderMap, Client}; use std::{path::Path, time::Duration}; use tokio::{fs::File, prelude::*, time::interval}; @@ -7,16 +7,14 @@ use tokio_compat_02::FutureExt; use url::Url; mod description; +mod name; -const DEFAULT_PREFIX: &str = "fire"; -const DEFAULT_SUFFIX: &str = "paw"; const USER_AGENT: &str = "warriors-cats"; const TOKEN_PATH: &str = "/oauth/token"; const STATUS_URL: &str = "/api/v1/statuses"; #[derive(serde::Deserialize)] struct Config { - names: Names, timing: Timing, mastodon: Mastodon, } @@ -34,17 +32,11 @@ struct Timing { duration: u32, } -#[derive(serde::Deserialize)] -struct Names { - prefix: Vec, - suffix: Vec, -} - struct State { server: Url, client: Client, - prefix: Vec, - suffix: Vec, + name: name::Config, + description: description::Config, } #[derive(serde::Serialize)] @@ -128,9 +120,13 @@ impl Config { Ok(config) } - async fn to_state(self, token_path: impl AsRef) -> Result { + async fn to_state( + self, + token_path: impl AsRef, + name: name::Config, + description: description::Config, + ) -> Result { let Config { - names, timing: _, mastodon, } = self; @@ -160,16 +156,14 @@ impl Config { Ok(State { server, client, - prefix: names.prefix, - suffix: names.suffix, + name, + description, }) } } impl State { async fn post(&self, rng: &mut impl Rng) -> Result<()> { - let name = self.generate(rng); - let description = description::run(rng); let mut url = self.server.clone(); url.set_path(STATUS_URL); @@ -177,7 +171,7 @@ impl State { .client .post(url.as_str()) .form(&Status { - status: format!("{} - {}", name, description), + status: self.gen(rng), visibility: "unlisted", }) .send() @@ -191,40 +185,26 @@ impl State { Ok(()) } - fn generate(&self, rng: &mut impl Rng) -> String { - let prefix = self - .prefix - .choose(rng) - .map(|s| s.as_str()) - .unwrap_or(DEFAULT_PREFIX); + fn gen(&self, rng: &mut impl Rng) -> String { + let name = self.name.gen(rng); + let description = self.description.gen(rng); - let mut suffix; - while { - suffix = self - .suffix - .choose(rng) - .map(|s| s.as_str()) - .unwrap_or(DEFAULT_SUFFIX); - suffix == prefix - } {} - - let (first, rest) = prefix.split_at(1); - - first.to_uppercase() + rest + &suffix + format!("{} - {}", name, description) } } #[tokio::main] async fn main() -> Result<()> { let config = Config::open("Config.toml").await?; + let name = name::config("Name.json").await?; + let description = description::config("Description.json").await?; let duration = Duration::from_secs(60) * config.timing.duration; let mut ticker = interval(duration); - let state = config.to_state("Token.toml").await?; + let state = config.to_state("Token.toml", name, description).await?; let mut rng = thread_rng(); - loop { ticker.tick().await; state.post(&mut rng).await?; diff --git a/src/name.rs b/src/name.rs new file mode 100644 index 0000000..2fd241d --- /dev/null +++ b/src/name.rs @@ -0,0 +1,34 @@ +use anyhow::Result; +use rand::{seq::SliceRandom, Rng}; +use std::path::Path; + +#[derive(serde::Deserialize)] +pub(crate) struct Config { + prefix: Vec, + suffix: Vec, +} + +impl Config { + pub(crate) fn gen(&self, rng: &mut impl Rng) -> String { + let prefix = self.prefix.choose(rng).map(|s| s.as_str()).unwrap(); + + let suffixes = self + .suffix + .iter() + .filter(|s| *s != prefix) + .collect::>(); + + let suffix = suffixes.choose(rng).unwrap().to_string(); + + let (first, rest) = prefix.split_at(1); + + first.to_uppercase() + rest + &suffix + } +} + +pub(crate) async fn config(path: impl AsRef) -> Result { + let bytes = tokio::fs::read(path).await?; + let config: Config = serde_json::from_slice(&bytes)?; + + Ok(config) +}