diff --git a/src/levels/l10_dynamic.rs b/src/levels/l10_dynamic.rs new file mode 100644 index 0000000..a46c1a6 --- /dev/null +++ b/src/levels/l10_dynamic.rs @@ -0,0 +1,101 @@ +//! Level 10 — dynamic values. The vault ledger accepts numbers and +//! dates in many forms. +//! +//! Paired design note: `l10.md`. +//! +//! The target is canonical (decimal ints, decimal float, ISO date +//! string). The lesson is that the player can write equivalent values +//! in hex / octal / exponent forms — all parse to the same number. + +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 Dynamic; + +#[derive(Serialize)] +struct DescCtx { + gold_dec: i64, + gold_hex: String, + silver_dec: i64, + silver_oct: String, + experience: f64, + date: String, +} + +impl Level for Dynamic { + fn id(&self) -> u8 { + 10 + } + + fn name(&self) -> &'static str { + "Vault Ledger" + } + + fn generate(&self, seed: u64) -> Generated { + let mut rng = ChaCha8Rng::seed_from_u64(seed ^ 0x0000_0000_0000_000A); + + let gold = rng.gen_range(0x100..=0xFFFi64); + let silver = rng.gen_range(0o100..=0o777i64); + // Fractional float so the canonical serialisation keeps the `.5`. + let experience = rng.gen_range(10..=99) as f64 + 0.5; + let year = rng.gen_range(1100..=2050i64); + let month = rng.gen_range(1..=12i64); + let day = rng.gen_range(1..=28i64); + let date = format!("{year:04}-{month:02}-{day:02}"); + + let mut vault = Mapping::new(); + vault.insert(Value::String("gold".to_string()), Value::from(gold)); + vault.insert(Value::String("silver".to_string()), Value::from(silver)); + vault.insert( + Value::String("experience".to_string()), + Value::from(experience), + ); + vault.insert( + Value::String("date".to_string()), + Value::String(date.clone()), + ); + + let mut top = Mapping::new(); + top.insert(Value::String("vault".to_string()), Value::Mapping(vault)); + + let target_yaml = + serde_yaml::to_string(&Value::Mapping(top)).expect("serialise mapping"); + + let mut d = Describer::new(); + d.register( + "l10", + "The vault ledger demands its values:\n\ + gold: {{ gold_dec }} (try hex: 0x{{ gold_hex }})\n\ + silver: {{ silver_dec }} (try octal: 0o{{ silver_oct }})\n\ + experience: {{ experience }} (any equivalent float form passes)\n\ + date: {{ date }} (an ISO-8601 date string)\n\ + 💡 Any equivalent numeric form passes — what matters is the parsed value.", + ) + .expect("register template"); + let description = d + .render( + "l10", + &DescCtx { + gold_dec: gold, + gold_hex: format!("{:X}", gold), + silver_dec: silver, + silver_oct: format!("{:o}", silver), + experience, + date, + }, + ) + .expect("render template"); + + Generated { + target_yaml, + description, + flavor: "🪙 A vault ledger awaits in many ciphers.".to_string(), + } + } +} diff --git a/src/levels/mod.rs b/src/levels/mod.rs index f3c78cc..edf390a 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 l10_dynamic; use serde::{Deserialize, Serialize};