//! Level 11 — advanced anchors. Anchor shapes inside a list, alias //! them elsewhere. //! //! Paired design note: `l11.md`. //! //! Like L6, serde_yaml expands aliases on parse, so the emitted target //! is the fully-inlined form. Players who use `&anchor` / `*alias` //! produce the same `Value` and pass via the semantic short-circuit. 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 AdvAnchors; const SHAPES: &[(&str, i64)] = &[ ("triangle", 3), ("square", 4), ("pentagon", 5), ("hexagon", 6), ("heptagon", 7), ("octagon", 8), ]; #[derive(Serialize)] struct DescCtx { shapes: Vec, copies: Vec, } #[derive(Serialize)] struct ShapeDesc { name: String, sides: i64, interior_angle_sum: i64, } fn shape_value(name: &str, sides: i64) -> Value { let mut m = Mapping::new(); m.insert( Value::String("name".to_string()), Value::String(name.to_string()), ); m.insert(Value::String("sides".to_string()), Value::from(sides)); m.insert( Value::String("interior".to_string()), Value::from((sides - 2) * 180), ); Value::Mapping(m) } impl Level for AdvAnchors { fn id(&self) -> u8 { 11 } fn name(&self) -> &'static str { "Advanced Anchors" } fn generate(&self, seed: u64) -> Generated { let mut rng = ChaCha8Rng::seed_from_u64(seed ^ 0x0000_0000_0000_000B); let n = rng.gen_range(2..=3); let picked: Vec<(&'static str, i64)> = SHAPES .choose_multiple(&mut rng, n) .copied() .collect(); // The defining `shapes:` list. let shapes_seq: Sequence = picked .iter() .map(|(name, sides)| shape_value(name, *sides)) .collect(); // `copies:` — random selections with possible repetition. let m = rng.gen_range(3..=4); let mut copies_seq = Sequence::new(); let mut copy_names = Vec::new(); for _ in 0..m { let (name, sides) = picked.choose(&mut rng).unwrap(); copies_seq.push(shape_value(name, *sides)); copy_names.push((*name).to_string()); } let mut top = Mapping::new(); top.insert( Value::String("shapes".to_string()), Value::Sequence(shapes_seq), ); top.insert( Value::String("copies".to_string()), Value::Sequence(copies_seq), ); let target_yaml = serde_yaml::to_string(&Value::Mapping(top)).expect("serialise mapping"); let shape_descs: Vec = picked .iter() .map(|(name, sides)| ShapeDesc { name: (*name).to_string(), sides: *sides, interior_angle_sum: (sides - 2) * 180, }) .collect(); let mut d = Describer::new(); d.register( "l11", "Shapes are defined once and reused.\n\ Definitions:\n\ {% for s in shapes %}- {{ s.name }}: sides={{ s.sides }}, interior={{ s.interior_angle_sum }}\n\ {% endfor %}\n\ Copies, in order: {% for c in copies %}{{ c }}{% if not loop.last %}, {% endif %}{% endfor %}\n\ 💡 Anchor each shape in the list with `- &name`, then alias by `*name` in copies.", ) .expect("register template"); let description = d .render( "l11", &DescCtx { shapes: shape_descs, copies: copy_names, }, ) .expect("render template"); Generated { target_yaml, description, flavor: "🔺 The geometry chamber repeats its forms.".to_string(), } } }