Rewrite description generator to use json config

This commit is contained in:
asonix 2020-10-31 15:33:07 -05:00
parent e9510092c5
commit 50d41873cd
8 changed files with 734 additions and 876 deletions

1
Cargo.lock generated
View File

@ -1078,6 +1078,7 @@ dependencies = [
"rand",
"reqwest",
"serde",
"serde_json",
"tokio 0.3.2",
"tokio-compat-02",
"toml",

View File

@ -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"

View File

@ -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

297
Description.json Normal file
View File

@ -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"]
]
}]
}]
}

152
Name.json Normal file
View File

@ -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"
]
}

View File

@ -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<T>(weights: Vec<(u8, T)>, rng: &mut impl Rng, nothing_weight: u8) -> Option<T> {
let total = weights.iter().fold(0, |acc, (i, _)| acc + i) + nothing_weight;
if total == 0 {
return None;
}
fn select(variants: &HashSet<String>, weights: &[(u64, String)], rng: &mut impl Rng) -> String {
let filtered = weights
.iter()
.filter(|(_, name)| variants.contains(name))
.collect::<Vec<_>>();
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<String>,
#[serde(default)]
silent: HashSet<String>,
#[serde(default)]
next: Vec<Next>,
#[serde(default)]
depends: Vec<Depend>,
}
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<Descriptor>,
}
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::<Vec<_>>()
.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<PostDescriptor>,
}
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::<Vec<_>>()
.join(" and ");
write!(f, "with {}", joined)
}
}
#[derive(Debug)]
enum ColorModifier {
Dark,
Light,
Pale,
}
impl ColorModifier {
fn gen(rng: &mut impl Rng) -> Option<Self> {
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<BlackSecondColor>),
Gray(Option<GraySecondColor>),
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<ColorModifier>, Option<Gembder>),
Tabby(Option<PeltColor>, Option<Gembder>),
Base(PeltColor, Option<Gembder>),
}
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<Self> {
select_weighted(Self::weights(), rng, 10)
}
fn gen_skewed(rng: &mut impl Rng) -> Option<Self> {
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<String>,
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<ColorModifier>,
#[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<String>,
weights: Option<&[(u64, String)]>,
rng: &mut impl Rng,
) -> Option<String> {
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::<Vec<_>>()
.choose(rng)
.unwrap()
.to_string(),
)
}
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum Anyable {
Specified(HashSet<String>),
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<Vec<(u64, String)>>,
default: Option<String>,
}
#[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<Entry>,
}
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<Path>) -> Result<Config> {
let bytes = tokio::fs::read(path).await?;
let config: Config = serde_json::from_slice(&bytes)?;
Ok(config)
}

View File

@ -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<String>,
suffix: Vec<String>,
}
struct State {
server: Url,
client: Client,
prefix: Vec<String>,
suffix: Vec<String>,
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<Path>) -> Result<State> {
async fn to_state(
self,
token_path: impl AsRef<Path>,
name: name::Config,
description: description::Config,
) -> Result<State> {
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?;

34
src/name.rs Normal file
View File

@ -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<String>,
suffix: Vec<String>,
}
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::<Vec<_>>();
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<Path>) -> Result<Config> {
let bytes = tokio::fs::read(path).await?;
let config: Config = serde_json::from_slice(&bytes)?;
Ok(config)
}