Add typewriter effect
This commit is contained in:
130
src/config.rs
Normal file
130
src/config.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
//! Game configuration, loaded from `./game.ini`.
|
||||
//!
|
||||
//! Two settings today:
|
||||
//! - `typewriter_effect` (bool, default `true`) — animate level prompts
|
||||
//! character-by-character as they appear in the log.
|
||||
//! - `typewriter_speed_cpm` (u32, default `1000`) — typing speed in
|
||||
//! characters per minute (~60 ms per character).
|
||||
//!
|
||||
//! Missing keys fall back to defaults. Missing file falls back to all
|
||||
//! defaults. Comments (`#` or `;`) and `[sections]` are ignored.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct GameConfig {
|
||||
pub typewriter_enabled: bool,
|
||||
pub typewriter_speed_cpm: u32,
|
||||
}
|
||||
|
||||
impl Default for GameConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
typewriter_enabled: true,
|
||||
typewriter_speed_cpm: 1000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameConfig {
|
||||
/// How long each character takes to appear during the typewriter
|
||||
/// animation. Falls back to the default speed if `speed_cpm` is 0.
|
||||
pub fn char_duration(&self) -> Duration {
|
||||
let cpm = if self.typewriter_speed_cpm == 0 {
|
||||
Self::default().typewriter_speed_cpm
|
||||
} else {
|
||||
self.typewriter_speed_cpm
|
||||
};
|
||||
Duration::from_millis(60_000 / cpm as u64)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_path() -> PathBuf {
|
||||
PathBuf::from("game.ini")
|
||||
}
|
||||
|
||||
pub fn load() -> GameConfig {
|
||||
match std::fs::read_to_string(config_path()) {
|
||||
Ok(content) => parse(&content),
|
||||
Err(_) => GameConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(content: &str) -> GameConfig {
|
||||
let mut cfg = GameConfig::default();
|
||||
for line in content.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty()
|
||||
|| line.starts_with('#')
|
||||
|| line.starts_with(';')
|
||||
|| line.starts_with('[')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some((key, value)) = line.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
let key = key.trim().to_lowercase().replace(' ', "_");
|
||||
let value = value.trim();
|
||||
match key.as_str() {
|
||||
"typewriter_effect" | "typewriter_enabled" => {
|
||||
if let Ok(b) = value.parse::<bool>() {
|
||||
cfg.typewriter_enabled = b;
|
||||
}
|
||||
}
|
||||
"typewriter_speed" | "typewriter_speed_cpm" => {
|
||||
if let Ok(n) = value.parse::<u32>() {
|
||||
cfg.typewriter_speed_cpm = n;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
cfg
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn defaults() {
|
||||
let cfg = GameConfig::default();
|
||||
assert!(cfg.typewriter_enabled);
|
||||
assert_eq!(cfg.typewriter_speed_cpm, 1000);
|
||||
// 1000 cpm = 60 ms per char
|
||||
assert_eq!(cfg.char_duration().as_millis(), 60);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_reads_both_underscored_and_spaced_keys() {
|
||||
let ini = "\
|
||||
# comment\n\
|
||||
; another comment\n\
|
||||
[section]\n\
|
||||
typewriter effect = false\n\
|
||||
typewriter_speed_cpm = 240\n\
|
||||
";
|
||||
let cfg = parse(ini);
|
||||
assert!(!cfg.typewriter_enabled);
|
||||
assert_eq!(cfg.typewriter_speed_cpm, 240);
|
||||
// 240 cpm = 250 ms per char
|
||||
assert_eq!(cfg.char_duration().as_millis(), 250);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_keeps_defaults_on_garbage() {
|
||||
let cfg = parse("typewriter_effect = maybe\ntypewriter_speed_cpm = fast\n");
|
||||
assert_eq!(cfg, GameConfig::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_speed_falls_back_to_default_duration() {
|
||||
let cfg = GameConfig {
|
||||
typewriter_enabled: true,
|
||||
typewriter_speed_cpm: 0,
|
||||
};
|
||||
assert_eq!(cfg.char_duration().as_millis(), 60);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user