//! Level 3 — dictionaries. Each direction leads to a feature with its //! own type + one characteristic property. //! //! Paired design note: `l03.md`. use rand::seq::SliceRandom; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; use serde::Serialize; use serde_yaml::{Mapping, Value}; use crate::describe::Describer; use super::{Generated, Level}; pub struct Dict; const DIRECTIONS: &[&str] = &["left", "right", "straight", "back", "up", "down"]; enum Feature { Door, Tunnel, Pit, Stairs, Wall, Altar, } impl Feature { fn name(&self) -> &'static str { match self { Feature::Door => "door", Feature::Tunnel => "tunnel", Feature::Pit => "pit", Feature::Stairs => "stairs", Feature::Wall => "wall", Feature::Altar => "altar", } } fn random(rng: &mut ChaCha8Rng) -> Self { match rng.gen_range(0..6) { 0 => Feature::Door, 1 => Feature::Tunnel, 2 => Feature::Pit, 3 => Feature::Stairs, 4 => Feature::Wall, _ => Feature::Altar, } } /// One characteristic property: (key, value). fn property(&self, rng: &mut ChaCha8Rng) -> (&'static str, Value) { match self { Feature::Door => ("locked", Value::Bool(rng.gen_bool(0.5))), Feature::Tunnel => ("depth", Value::from(rng.gen_range(5..=30i64))), Feature::Pit => ("depth", Value::from(rng.gen_range(5..=30i64))), Feature::Stairs => { let going = if rng.gen_bool(0.5) { "up" } else { "down" }; ("going", Value::String(going.to_string())) } Feature::Wall => ("cracked", Value::Bool(rng.gen_bool(0.5))), Feature::Altar => ("blessed", Value::Bool(rng.gen_bool(0.5))), } } } #[derive(Serialize)] struct DescCtx { entries: Vec, } #[derive(Serialize)] struct DescEntry { direction: String, feature: String, prop_name: String, prop_value: String, } impl Level for Dict { fn id(&self) -> u8 { 3 } fn name(&self) -> &'static str { "Dictionaries" } fn generate(&self, seed: u64) -> Generated { // Per-level constant so the same `current_seed` produces different // content per level. let mut rng = ChaCha8Rng::seed_from_u64(seed ^ 0x0000_0000_0000_0003); let n = rng.gen_range(2..=3); let directions: Vec<&'static str> = DIRECTIONS.choose_multiple(&mut rng, n).copied().collect(); let mut top = Mapping::new(); let mut entries = Vec::with_capacity(directions.len()); for d in &directions { let feature = Feature::random(&mut rng); let (prop_name, prop_value) = feature.property(&mut rng); let mut inner = Mapping::new(); inner.insert( Value::String("type".to_string()), Value::String(feature.name().to_string()), ); inner.insert( Value::String(prop_name.to_string()), prop_value.clone(), ); top.insert(Value::String((*d).to_string()), Value::Mapping(inner)); let prop_value_str = match &prop_value { Value::Bool(b) => b.to_string(), Value::Number(n) => n.to_string(), Value::String(s) => s.clone(), _ => String::new(), }; entries.push(DescEntry { direction: (*d).to_string(), feature: feature.name().to_string(), prop_name: prop_name.to_string(), prop_value: prop_value_str, }); } let target_yaml = serde_yaml::to_string(&Value::Mapping(top)).expect("serialise mapping"); let mut d = Describer::new(); d.register( "l03", "{% for e in entries %}- {{ e.direction }} → {{ e.feature }} ({{ e.prop_name }}: {{ e.prop_value }})\n{% endfor %}\nšŸ’” Each feature is a dictionary — give it a `type:` key plus its property.", ) .expect("register template"); let description = d .render("l03", &DescCtx { entries }) .expect("render template"); Generated { target_yaml, description, flavor: "🧭 You stand at a junction. Each path reveals its own detail.".to_string(), } } }