Three keys: - scroll: multi-line string (block scalar `|`) - weight: fractional float (e.g. 12.5) — forces float without needing the `!!float` tag in the target text - title: string of digits (player needs quotes or `!!str` to avoid int) Per-seed deterministic via ChaCha8Rng XOR'd with 0x..08. Not wired into levels::registry() yet — integration belongs to a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
89 lines
2.5 KiB
Rust
89 lines
2.5 KiB
Rust
//! Levels — hand-written Rust generators paired with design notes.
|
|
//!
|
|
//! Each level is implemented in `l<XX>_<name>.rs` and is the authoritative
|
|
//! source of truth for what target YAML the player must reproduce and how
|
|
//! the description is rendered.
|
|
//!
|
|
//! The paired `l<XX>.md` file is a **design note only**: it documents the
|
|
//! intended scene and a minimal example of the target YAML. `.md` files
|
|
//! are *not* loaded at runtime — there is no `include_str!` and no
|
|
//! markdown parser. If a `.md` and its paired `.rs` ever disagree, the
|
|
//! `.rs` wins.
|
|
|
|
pub mod l01_minimum;
|
|
pub mod l02_kv;
|
|
pub mod l03_dict;
|
|
pub mod l08_tags;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Awarded per level based on the grade score. Replaces the old
|
|
/// game-wide difficulty tier. Thresholds:
|
|
/// - Gold ≥ 95 %
|
|
/// - Silver ≥ 80 %
|
|
/// - Bronze ≥ 70 %
|
|
/// - Below 70 % → no nugget (retry).
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum Nugget {
|
|
Bronze,
|
|
Silver,
|
|
Gold,
|
|
}
|
|
|
|
impl Nugget {
|
|
/// Map a grade ratio (0.0..=1.0) to a nugget. `None` means the
|
|
/// player didn't clear the level — retry without advancing.
|
|
pub fn from_score(score: f64) -> Option<Self> {
|
|
if score >= 0.95 {
|
|
Some(Nugget::Gold)
|
|
} else if score >= 0.80 {
|
|
Some(Nugget::Silver)
|
|
} else if score >= 0.70 {
|
|
Some(Nugget::Bronze)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn emoji(self) -> &'static str {
|
|
match self {
|
|
Self::Bronze => "🥉",
|
|
Self::Silver => "🥈",
|
|
Self::Gold => "🥇",
|
|
}
|
|
}
|
|
|
|
pub fn name(self) -> &'static str {
|
|
match self {
|
|
Self::Bronze => "Bronze",
|
|
Self::Silver => "Silver",
|
|
Self::Gold => "Gold",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// What `Level::generate` returns: the canonical target YAML to grade
|
|
/// against, the player-facing description (already rendered), and the
|
|
/// dungeon flavor line.
|
|
pub struct Generated {
|
|
pub target_yaml: String,
|
|
pub description: String,
|
|
pub flavor: String,
|
|
}
|
|
|
|
/// One level's generator. Implementations live in `l<XX>_<name>.rs`.
|
|
pub trait Level {
|
|
fn id(&self) -> u8;
|
|
fn name(&self) -> &'static str;
|
|
fn generate(&self, seed: u64) -> Generated;
|
|
}
|
|
|
|
/// Ordered registry of all levels. `registry()[0]` is level 1.
|
|
pub fn registry() -> Vec<Box<dyn Level>> {
|
|
vec![
|
|
Box::new(l01_minimum::Minimum),
|
|
Box::new(l02_kv::KeyValue),
|
|
Box::new(l03_dict::Dict),
|
|
]
|
|
}
|