//! Level 7 — complex data structures. The full map of a single floor. //! //! Paired design note: `l07.md`. use rand::seq::SliceRandom; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; use serde::Serialize; use serde_yaml::{Mapping, Sequence, Value}; use crate::describe::Describer; use super::{Generated, Level}; pub struct Complex; const EXITS: &[&str] = &["north", "south", "east", "west", "up", "down"]; const CONTENTS: &[&str] = &[ "torch", "bench", "gold", "ruby", "scroll", "tome", "dagger", "shield", ]; const ROOMS: &[(&str, &str)] = &[ ("entrance", "hall"), ("treasury", "vault"), ("library", "study"), ("kitchen", "scullery"), ]; #[derive(Serialize)] struct DescCtx { floor: i64, rooms: Vec, } #[derive(Serialize)] struct RoomDesc { name: String, kind: String, locked: bool, exits: Vec, contents: Vec, } impl Level for Complex { fn id(&self) -> u8 { 7 } fn name(&self) -> &'static str { "The Floor Map" } fn generate(&self, seed: u64) -> Generated { let mut rng = ChaCha8Rng::seed_from_u64(seed ^ 0x0000_0000_0000_0007); let floor = rng.gen_range(1..=5i64); let chosen: Vec<&(&'static str, &'static str)> = ROOMS.choose_multiple(&mut rng, 2).collect(); let mut rooms_map = Mapping::new(); let mut desc_rooms = Vec::new(); for (name, kind) in &chosen { let exits_n = rng.gen_range(1..=3); let exits: Vec<&'static str> = EXITS.choose_multiple(&mut rng, exits_n).copied().collect(); let contents_n = rng.gen_range(1..=3); let contents: Vec<&'static str> = CONTENTS .choose_multiple(&mut rng, contents_n) .copied() .collect(); let locked = rng.gen_bool(0.5); let mut room = Mapping::new(); room.insert( Value::String("type".to_string()), Value::String((*kind).to_string()), ); room.insert(Value::String("locked".to_string()), Value::Bool(locked)); let exits_seq: Sequence = exits .iter() .map(|e| Value::String((*e).to_string())) .collect(); let contents_seq: Sequence = contents .iter() .map(|c| Value::String((*c).to_string())) .collect(); room.insert( Value::String("exits".to_string()), Value::Sequence(exits_seq), ); room.insert( Value::String("contents".to_string()), Value::Sequence(contents_seq), ); rooms_map.insert(Value::String((*name).to_string()), Value::Mapping(room)); desc_rooms.push(RoomDesc { name: (*name).to_string(), kind: (*kind).to_string(), locked, exits: exits.iter().map(|s| s.to_string()).collect(), contents: contents.iter().map(|s| s.to_string()).collect(), }); } let mut top = Mapping::new(); top.insert(Value::String("floor".to_string()), Value::from(floor)); 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( "l07", "Floor {{ floor }}.\n\ {% for r in rooms %}\n\ {{ r.name }} — a {{ r.kind }} (locked: {{ r.locked }})\n\ \texits: {% for e in r.exits %}{{ e }}{% if not loop.last %}, {% endif %}{% endfor %}\n\ \tcontents: {% for c in r.contents %}{{ c }}{% if not loop.last %}, {% endif %}{% endfor %}\n\ {% endfor %}\n\ 💡 Combine maps, lists, and scalars — `floor:` is an int, each room is a dict with two lists.", ) .expect("register template"); let description = d .render( "l07", &DescCtx { floor, rooms: desc_rooms, }, ) .expect("render template"); Generated { target_yaml, description, flavor: "🗺 The map of this floor is rich with detail.".to_string(), } } }