warriors-names/src/description.rs

273 lines
6.6 KiB
Rust
Raw Normal View History

use anyhow::Result;
use rand::{seq::SliceRandom, Rng};
use std::{collections::HashSet, path::Path};
2020-10-23 20:56:20 +00:00
fn select(variants: &HashSet<String>, weights: &[(u64, String)], rng: &mut impl Rng) -> String {
let filtered = weights
.iter()
.filter(|(_, name)| variants.contains(name))
.collect::<Vec<_>>();
2020-10-23 20:56:20 +00:00
let total = filtered.iter().fold(0, |acc, (weight, _)| acc + weight);
2020-10-23 20:56:20 +00:00
let selected = rng.gen_range(0, total);
let (s, _) = filtered
.iter()
.fold((None, selected), |(opt, count), (weight, item)| {
if opt.is_some() {
return (opt, 0);
2020-10-23 20:56:20 +00:00
}
let new_count = count.saturating_sub(*weight);
2020-10-23 20:56:20 +00:00
if new_count == 0 {
return (Some(item), 0);
2020-10-23 20:56:20 +00:00
}
(None, new_count)
});
2020-10-23 20:56:20 +00:00
s.unwrap().to_owned()
2020-10-23 20:56:20 +00:00
}
#[derive(serde::Deserialize)]
struct Entry {
name: String,
variants: HashSet<String>,
2020-10-23 20:56:20 +00:00
#[serde(default)]
silent: HashSet<String>,
2020-10-23 20:56:20 +00:00
#[serde(default)]
next: Vec<Next>,
2020-10-23 20:56:20 +00:00
#[serde(default)]
depends: Vec<Depend>,
2020-10-23 20:56:20 +00:00
}
impl Entry {
fn gen(
&self,
weights: Option<&[(u64, String)]>,
entries: &[Entry],
rng: &mut impl Rng,
) -> String {
let variant = self.gen_variant(weights, rng);
2020-10-23 20:56:20 +00:00
let building = self.gen_depends(&variant, entries, rng);
2020-10-23 20:56:20 +00:00
let mut seen = HashSet::new();
seen.insert(variant.clone());
self.gen_next(building, &variant, weights, entries, &mut seen, rng)
2020-10-23 20:56:20 +00:00
}
fn gen_variant(&self, weights: Option<&[(u64, String)]>, rng: &mut impl Rng) -> String {
if let Some(weights) = weights {
return select(&self.variants, &weights, rng);
2020-10-23 20:56:20 +00:00
}
self.variants
2020-10-23 20:56:20 +00:00
.iter()
.collect::<Vec<_>>()
.choose(rng)
.unwrap()
.to_string()
2020-10-23 20:56:20 +00:00
}
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()
};
2020-10-23 20:56:20 +00:00
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);
}
2020-10-23 20:56:20 +00:00
(acc, v, seen)
},
);
2020-10-23 20:56:20 +00:00
s
2020-10-23 20:56:20 +00:00
}
}
#[derive(serde::Deserialize)]
struct Next {
from: Anyable,
to: Anyable,
chance: f64,
join: String,
2020-10-23 20:56:20 +00:00
}
impl Next {
fn join(&self, building: String, new: &str) -> String {
building + &self.join + new
2020-10-23 20:56:20 +00:00
}
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;
2020-10-23 20:56:20 +00:00
}
if !rng.gen_bool(self.chance) {
return None;
2020-10-23 20:56:20 +00:00
}
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));
}
2020-10-23 20:56:20 +00:00
Some(
variants
.iter()
.filter(|s| self.to.contains(s))
.collect::<Vec<_>>()
.choose(rng)
.unwrap()
.to_string(),
)
2020-10-23 20:56:20 +00:00
}
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum Anyable {
Specified(HashSet<String>),
2020-10-23 20:56:20 +00:00
Any(Any),
2020-10-23 20:56:20 +00:00
}
#[derive(serde::Deserialize)]
enum Any {
#[serde(rename = "any")]
Any,
2020-10-23 20:56:20 +00:00
}
impl Anyable {
fn contains(&self, variant: &str) -> bool {
2020-10-23 20:56:20 +00:00
match self {
Anyable::Specified(ref hs) => hs.contains(variant),
Anyable::Any(_) => true,
2020-10-23 20:56:20 +00:00
}
}
}
#[derive(serde::Deserialize)]
struct Depend {
variants: Anyable,
depends: String,
chance: f64,
order: String,
weights: Option<Vec<(u64, String)>>,
default: Option<String>,
2020-10-23 20:56:20 +00:00
}
impl Depend {
fn gen(&self, acc: String, variant: &str, entries: &[Entry], rng: &mut impl Rng) -> String {
if !self.variants.contains(variant) {
return acc;
}
2020-10-23 20:56:20 +00:00
if !rng.gen_bool(self.chance) {
if let Some(default) = self.default.as_ref() {
if acc.is_empty() {
return default.to_string();
}
2020-10-23 20:56:20 +00:00
return self.join(acc, default);
2020-10-23 20:56:20 +00:00
}
return acc;
2020-10-23 20:56:20 +00:00
}
let entry = entries
.into_iter()
.find(|entry| entry.name == self.depends)
.expect(&format!("Missing entry for {}", self.depends));
2020-10-23 20:56:20 +00:00
let value = entry.gen(self.weights.as_ref().map(Vec::as_slice), entries, rng);
2020-10-23 20:56:20 +00:00
if acc.is_empty() {
return value;
2020-10-23 20:56:20 +00:00
}
self.join(acc, &value)
2020-10-23 20:56:20 +00:00
}
fn join(&self, acc: String, value: &str) -> String {
self.order
.replace("{base}", &acc)
.replace(&format!("{{{}}}", self.depends), value)
2020-10-23 20:56:20 +00:00
}
}
#[derive(serde::Deserialize)]
pub(crate) struct Config {
root: String,
entries: Vec<Entry>,
2020-10-23 20:56:20 +00:00
}
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));
2020-10-23 20:56:20 +00:00
root.gen(None, &self.entries, rng)
2020-10-23 20:56:20 +00:00
}
}
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)?;
2020-10-23 20:56:20 +00:00
Ok(config)
2020-10-23 20:56:20 +00:00
}