Add cat descriptions
This commit is contained in:
parent
e1b9b5de21
commit
584c0dc383
731
src/description.rs
Normal file
731
src/description.rs
Normal file
|
@ -0,0 +1,731 @@
|
||||||
|
use rand::Rng;
|
||||||
|
use std::{collections::HashSet, fmt};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if selected.saturating_sub(weight) == 0 {
|
||||||
|
return (0, Some(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
(selected.saturating_sub(weight), None)
|
||||||
|
});
|
||||||
|
|
||||||
|
opt
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Descriptor {
|
||||||
|
Sleek,
|
||||||
|
Large,
|
||||||
|
Small,
|
||||||
|
Swift,
|
||||||
|
Skinny,
|
||||||
|
Thin,
|
||||||
|
Young,
|
||||||
|
Old,
|
||||||
|
LongLeg,
|
||||||
|
LongHair,
|
||||||
|
LongTail,
|
||||||
|
Huge,
|
||||||
|
Massive,
|
||||||
|
Scars,
|
||||||
|
Pretty,
|
||||||
|
Handsome,
|
||||||
|
Beautiful,
|
||||||
|
Mottled,
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
.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, Gembder),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pelt {
|
||||||
|
fn gen(rng: &mut impl Rng) -> Pelt {
|
||||||
|
select_weighted(PeltVariant::weights(), rng, 0)
|
||||||
|
.unwrap()
|
||||||
|
.into_pelt(rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, gembder) => write!(f, "{} {}", color, gembder),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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_always(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, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_always(rng: &mut impl Rng) -> Self {
|
||||||
|
select_weighted(Self::weights(), rng, 0).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct EyeColor {
|
||||||
|
eye_color: InnerEyeColor,
|
||||||
|
modifier: Option<ColorModifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EyeColor {
|
||||||
|
fn gen(rng: &mut impl Rng) -> EyeColor {
|
||||||
|
EyeColor {
|
||||||
|
eye_color: InnerEyeColor::gen(rng),
|
||||||
|
modifier: ColorModifier::gen(rng),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "{}", self.pelt)?;
|
||||||
|
|
||||||
|
if !self.post_descriptors.is_empty() {
|
||||||
|
write!(f, " {}", self.post_descriptors)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run(rng: &mut impl Rng) -> String {
|
||||||
|
Builder::gen(rng).to_string()
|
||||||
|
}
|
60
src/main.rs
60
src/main.rs
|
@ -1,10 +1,13 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use reqwest::{Client, header::HeaderMap};
|
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||||
use std::{path::Path, time::Duration,};
|
use reqwest::{header::HeaderMap, Client};
|
||||||
use tokio::{fs::File, time::interval, prelude::*};
|
use std::{path::Path, time::Duration};
|
||||||
|
use tokio::{fs::File, prelude::*, time::interval};
|
||||||
use tokio_compat_02::FutureExt;
|
use tokio_compat_02::FutureExt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
mod description;
|
||||||
|
|
||||||
const DEFAULT_PREFIX: &str = "fire";
|
const DEFAULT_PREFIX: &str = "fire";
|
||||||
const DEFAULT_SUFFIX: &str = "paw";
|
const DEFAULT_SUFFIX: &str = "paw";
|
||||||
const USER_AGENT: &str = "warriors-cats";
|
const USER_AGENT: &str = "warriors-cats";
|
||||||
|
@ -72,7 +75,8 @@ impl Mastodon {
|
||||||
oauth_url.set_path(TOKEN_PATH);
|
oauth_url.set_path(TOKEN_PATH);
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
|
|
||||||
let res = client.post(oauth_url.as_str())
|
let res = client
|
||||||
|
.post(oauth_url.as_str())
|
||||||
.header("User-Agent", USER_AGENT)
|
.header("User-Agent", USER_AGENT)
|
||||||
.form(&OauthParams {
|
.form(&OauthParams {
|
||||||
client_id: self.client_id,
|
client_id: self.client_id,
|
||||||
|
@ -87,7 +91,10 @@ impl Mastodon {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if !res.status().is_success() {
|
if !res.status().is_success() {
|
||||||
return Err(anyhow!("Failed to fetch access token:\n{}", res.text().await?));
|
return Err(anyhow!(
|
||||||
|
"Failed to fetch access token:\n{}",
|
||||||
|
res.text().await?
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let token: Token = res.json().compat().await?;
|
let token: Token = res.json().compat().await?;
|
||||||
|
@ -136,11 +143,14 @@ impl Config {
|
||||||
let token = mastodon.authenticate().await?;
|
let token = mastodon.authenticate().await?;
|
||||||
write_token(token_path.as_ref(), &token).await?;
|
write_token(token_path.as_ref(), &token).await?;
|
||||||
token
|
token
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert("Authorization", format!("Bearer {}", token.access_token).parse()?);
|
headers.insert(
|
||||||
|
"Authorization",
|
||||||
|
format!("Bearer {}", token.access_token).parse()?,
|
||||||
|
);
|
||||||
|
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.user_agent(USER_AGENT)
|
.user_agent(USER_AGENT)
|
||||||
|
@ -157,19 +167,22 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
async fn post(&self) -> Result<()> {
|
async fn post(&self, rng: &mut impl Rng) -> Result<()> {
|
||||||
let name = self.generate();
|
let name = self.generate(rng);
|
||||||
|
let description = description::run(rng);
|
||||||
let mut url = self.server.clone();
|
let mut url = self.server.clone();
|
||||||
url.set_path(STATUS_URL);
|
url.set_path(STATUS_URL);
|
||||||
|
|
||||||
let res = self.client.post(url.as_str())
|
let res = self
|
||||||
|
.client
|
||||||
|
.post(url.as_str())
|
||||||
.form(&Status {
|
.form(&Status {
|
||||||
status: name,
|
status: format!("{} - {}", name, description),
|
||||||
visibility: "unlisted",
|
visibility: "unlisted",
|
||||||
})
|
})
|
||||||
.send()
|
.send()
|
||||||
.compat()
|
.compat()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if !res.status().is_success() {
|
if !res.status().is_success() {
|
||||||
return Err(anyhow!("Failed to post status:\n{}", res.text().await?));
|
return Err(anyhow!("Failed to post status:\n{}", res.text().await?));
|
||||||
|
@ -178,15 +191,20 @@ impl State {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate(&self) -> String {
|
fn generate(&self, rng: &mut impl Rng) -> String {
|
||||||
use rand::{thread_rng, seq::SliceRandom};
|
let prefix = self
|
||||||
let mut rng = thread_rng();
|
.prefix
|
||||||
|
.choose(rng)
|
||||||
let prefix = self.prefix.choose(&mut rng).map(|s| s.as_str()).unwrap_or(DEFAULT_PREFIX);
|
.map(|s| s.as_str())
|
||||||
|
.unwrap_or(DEFAULT_PREFIX);
|
||||||
|
|
||||||
let mut suffix;
|
let mut suffix;
|
||||||
while {
|
while {
|
||||||
suffix = self.suffix.choose(&mut rng).map(|s| s.as_str()).unwrap_or(DEFAULT_SUFFIX);
|
suffix = self
|
||||||
|
.suffix
|
||||||
|
.choose(rng)
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.unwrap_or(DEFAULT_SUFFIX);
|
||||||
suffix == prefix
|
suffix == prefix
|
||||||
} {}
|
} {}
|
||||||
|
|
||||||
|
@ -205,8 +223,10 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let state = config.to_state("Token.toml").await?;
|
let state = config.to_state("Token.toml").await?;
|
||||||
|
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
ticker.tick().await;
|
ticker.tick().await;
|
||||||
state.post().await?;
|
state.post(&mut rng).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue