131 lines
3.7 KiB
Rust
131 lines
3.7 KiB
Rust
//! 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);
|
|
}
|
|
}
|