/* Hurry Curry! - a game about cooking Copyright 2024 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use anyhow::Result; use hurrycurry_protocol::Score; use serde::{Deserialize, Serialize}; use std::{cmp::Reverse, collections::HashMap}; use tokio::{ fs::{read_to_string, rename, File}, io::AsyncWriteExt, }; #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct ScoreboardStore { maps: HashMap, } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Scoreboard { pub plays: usize, pub best: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ScoreboardEntry { pub players: Vec, pub score: Score, } impl ScoreboardStore { pub async fn load() -> Result { let path = xdg::BaseDirectories::with_prefix("hurrycurry").place_data_file("scoreboards.json")?; // TOCTOU because its easier that way if !path.exists() { ScoreboardStore::default().save().await?; } let s = read_to_string(path).await?; Ok(serde_json::from_str(&s)?) } pub async fn save(&self) -> Result<()> { let path = xdg::BaseDirectories::with_prefix("hurrycurry").place_data_file("scoreboards.json")?; let buffer_path = xdg::BaseDirectories::with_prefix("hurrycurry").place_data_file("scoreboards.json~")?; File::create(&buffer_path) .await? .write_all(serde_json::to_string(self)?.as_bytes()) .await?; rename(buffer_path, path).await?; Ok(()) } pub fn get<'a>(&'a self, map: &str) -> Option<&'a Scoreboard> { self.maps.get(map) } pub fn insert(&mut self, map: &str, players: Vec, score: Score) { let b = self.maps.entry(map.to_owned()).or_default(); b.plays += 1; b.best.push(ScoreboardEntry { players, score }); b.best.sort_by_key(|e| Reverse(e.score.points)); while b.best.len() > 16 { b.best.pop(); } } }