//! Level 6 — anchors. Two rooms share the same trap — define it once. //! //! Paired design note: `l06.md`. //! //! Note: serde_yaml resolves aliases at parse time, so the target is //! emitted **expanded** (the trap dict appears in each room). Players //! who use anchors/aliases will produce the same parsed `Value` and //! pass via the semantic short-circuit. Players who paste the dict //! verbatim also pass. 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 Anchors; const ROOM_NAMES: &[&str] = &["north", "south", "east", "west"]; const TRAP_TYPES: &[&str] = &["pit", "snare", "dart", "rune"]; #[derive(Serialize)] struct DescCtx { trap_type: String, trap_depth: i64, trap_spikes: bool, rooms: Vec, } impl Level for Anchors { fn id(&self) -> u8 { 6 } fn name(&self) -> &'static str { "Anchors" } fn generate(&self, seed: u64) -> Generated { let mut rng = ChaCha8Rng::seed_from_u64(seed ^ 0x0000_0000_0000_0006); let trap_type = *TRAP_TYPES.choose(&mut rng).expect("non-empty"); let trap_depth = rng.gen_range(10..=30i64); let trap_spikes = rng.gen_bool(0.5); let n_rooms = rng.gen_range(2..=3); let rooms: Vec<&'static str> = ROOM_NAMES .choose_multiple(&mut rng, n_rooms) .copied() .collect(); let mut trap = Mapping::new(); trap.insert( Value::String("type".to_string()), Value::String(trap_type.to_string()), ); trap.insert(Value::String("depth".to_string()), Value::from(trap_depth)); trap.insert( Value::String("spikes".to_string()), Value::Bool(trap_spikes), ); let mut rooms_map = Mapping::new(); for r in &rooms { rooms_map.insert( Value::String((*r).to_string()), Value::Mapping(trap.clone()), ); } let mut top = Mapping::new(); top.insert(Value::String("trap".to_string()), Value::Mapping(trap)); top.insert( Value::String("rooms".to_string()), Value::Mapping(rooms_map), ); let target_yaml = serde_yaml::to_string(&Value::Mapping(top)).expect("serialise mapping"); let mut d = Describer::new(); d.register( "l06", "A single trap recurs through these halls:\n\ - type: {{ trap_type }}\n\ - depth: {{ trap_depth }}\n\ - spikes: {{ trap_spikes }}\n\ \n\ Reuse it for these rooms: {% for r in rooms %}{{ r }}{% if not loop.last %}, {% endif %}{% endfor %}.\n\ 💡 Define `trap: &name` once and reference it as `*name` in every room.", ) .expect("register template"); let description = d .render( "l06", &DescCtx { trap_type: trap_type.to_string(), trap_depth, trap_spikes, rooms: rooms.iter().map(|s| s.to_string()).collect(), }, ) .expect("render template"); Generated { target_yaml, description, flavor: "🪤 A trap recurs through these halls.".to_string(), } } }