feat(persistence): add high score saving/loading
Add HighScore resource, persistence module with save/load, and integration tests. Dependencies: serde, serde_json, dirs.
This commit is contained in:
parent
459e8a2353
commit
060a9a2a14
6 changed files with 189 additions and 3 deletions
|
|
@ -86,6 +86,9 @@ pub struct StartButton;
|
|||
#[derive(Component)]
|
||||
pub struct RestartMessage;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct HighScoreText;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Star {
|
||||
pub speed: f32,
|
||||
|
|
|
|||
73
src/persistence.rs
Normal file
73
src/persistence.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::resources::HighScore;
|
||||
|
||||
pub fn save_path() -> PathBuf {
|
||||
if let Some(p) = SAVE_PATH_OVERRIDE.get() {
|
||||
return p.clone();
|
||||
}
|
||||
if let Ok(env_path) = std::env::var("BGLGA_HIGHSCORE_PATH") {
|
||||
return PathBuf::from(env_path);
|
||||
}
|
||||
dirs::data_dir()
|
||||
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default())
|
||||
.join("bglga")
|
||||
.join("highscore.json")
|
||||
}
|
||||
|
||||
pub fn save(score: &HighScore, path: &Path) -> Result<(), std::io::Error> {
|
||||
std::fs::create_dir_all(path.parent().unwrap_or(Path::new(".")))?;
|
||||
let content = serde_json::to_string_pretty(score)?;
|
||||
std::fs::write(path, content)
|
||||
}
|
||||
|
||||
pub fn load(path: &Path) -> HighScore {
|
||||
let content = match std::fs::read_to_string(path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return HighScore { value: 0 },
|
||||
};
|
||||
serde_json::from_str::<HighScore>(&content).unwrap_or(HighScore { value: 0 })
|
||||
}
|
||||
|
||||
static SAVE_PATH_OVERRIDE: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
|
||||
|
||||
pub fn set_save_path_override(path: PathBuf) {
|
||||
SAVE_PATH_OVERRIDE.set(path).ok();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn save_and_load_round_trip() {
|
||||
let temp_path = std::env::temp_dir().join("highscore_roundtrip.json");
|
||||
let score = HighScore { value: 42 };
|
||||
save(&score, &temp_path).expect("save should succeed");
|
||||
let loaded = load(&temp_path);
|
||||
assert_eq!(loaded.value, 42, "loaded score should match saved score");
|
||||
let _ = std::fs::remove_file(&temp_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_missing_file_returns_default() {
|
||||
let missing_path = std::env::temp_dir().join("highscore_nonexistent.json");
|
||||
let loaded = load(&missing_path);
|
||||
assert_eq!(
|
||||
loaded.value, 0,
|
||||
"missing file should return default HighScore"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_malformed_json_returns_default() {
|
||||
let temp_path = std::env::temp_dir().join("highscore_malformed.json");
|
||||
std::fs::write(&temp_path, "{bad json").expect("write temp file should succeed");
|
||||
let loaded = load(&temp_path);
|
||||
assert_eq!(
|
||||
loaded.value, 0,
|
||||
"malformed JSON should return default HighScore"
|
||||
);
|
||||
let _ = std::fs::remove_file(&temp_path);
|
||||
}
|
||||
}
|
||||
|
|
@ -167,3 +167,9 @@ pub struct AttackDiveTimer {
|
|||
pub struct RestartPressed {
|
||||
pub pressed: bool,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct HighScore {
|
||||
#[serde(rename = "high_score")]
|
||||
pub value: u32,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue