From b805a49aaa439362fd2182f453ef8840950f384b Mon Sep 17 00:00:00 2001 From: Simonas Kareiva Date: Thu, 21 May 2026 22:12:23 +0300 Subject: [PATCH] Add level 5: Chambers (dictionaries + lists) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Top-level `chambers:` mapping; each chamber name maps to an item list. 2-3 chambers, 2-3 items each, drawn from typed pools. Deterministic per seed via ChaCha8Rng XOR'd with 0x..05. Not wired into levels::registry() yet — integration belongs to a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/levels/l05_dict_list.rs | 99 +++++++++++++++++++++++++++++++++++++ src/levels/mod.rs | 1 + 2 files changed, 100 insertions(+) create mode 100644 src/levels/l05_dict_list.rs diff --git a/src/levels/l05_dict_list.rs b/src/levels/l05_dict_list.rs new file mode 100644 index 0000000..f44f53b --- /dev/null +++ b/src/levels/l05_dict_list.rs @@ -0,0 +1,99 @@ +//! Level 5 — dictionaries AND lists. Each chamber keeps its own inventory. +//! +//! Paired design note: `l05.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 DictList; + +const CHAMBERS: &[&str] = &[ + "armory", "pantry", "library", "vault", "kitchen", "cellar", +]; +const ITEMS: &[&str] = &[ + "sword", "shield", "bread", "water", "tome", "scroll", "gem", "coin", "dagger", "potion", +]; + +#[derive(Serialize)] +struct DescCtx { + chambers: Vec, +} + +#[derive(Serialize)] +struct ChamberDesc { + name: String, + items: Vec, +} + +impl Level for DictList { + fn id(&self) -> u8 { + 5 + } + + fn name(&self) -> &'static str { + "Chambers" + } + + fn generate(&self, seed: u64) -> Generated { + let mut rng = ChaCha8Rng::seed_from_u64(seed ^ 0x0000_0000_0000_0005); + let n = rng.gen_range(2..=3); + let chamber_names: Vec<&'static str> = + CHAMBERS.choose_multiple(&mut rng, n).copied().collect(); + + let mut inner = Mapping::new(); + let mut desc_chambers = Vec::new(); + for name in &chamber_names { + let item_n = rng.gen_range(2..=3); + let items: Vec<&'static str> = + ITEMS.choose_multiple(&mut rng, item_n).copied().collect(); + let seq: Sequence = items + .iter() + .map(|i| Value::String((*i).to_string())) + .collect(); + inner.insert(Value::String((*name).to_string()), Value::Sequence(seq)); + desc_chambers.push(ChamberDesc { + name: (*name).to_string(), + items: items.iter().map(|s| s.to_string()).collect(), + }); + } + + let mut top = Mapping::new(); + top.insert( + Value::String("chambers".to_string()), + Value::Mapping(inner), + ); + + let target_yaml = + serde_yaml::to_string(&Value::Mapping(top)).expect("serialise mapping"); + + let mut d = Describer::new(); + d.register( + "l05", + "Several chambers branch off, each with its own inventory:\n\ + {% for c in chambers %}\n{{ c.name }}:{% for it in c.items %}\n - {{ it }}{% endfor %}\n{% endfor %}\n\ + 💡 Wrap the whole tree under a `chambers:` key — a dict of lists.", + ) + .expect("register template"); + let description = d + .render( + "l05", + &DescCtx { + chambers: desc_chambers, + }, + ) + .expect("render template"); + + Generated { + target_yaml, + description, + flavor: "🏛 You enter a hall. Doors lead to many chambers.".to_string(), + } + } +} diff --git a/src/levels/mod.rs b/src/levels/mod.rs index f3c78cc..b4d3062 100644 --- a/src/levels/mod.rs +++ b/src/levels/mod.rs @@ -13,6 +13,7 @@ pub mod l01_minimum; pub mod l02_kv; pub mod l03_dict; +pub mod l05_dict_list; use serde::{Deserialize, Serialize};