// Statistics of a game for the master at this computer: counters (heroes slain, tiles dug, wood // felled, ...) and a sample of gold, creatures and army strength every few seconds for the charts // at the end. They are local (not part of the shared match state) and feed the achievements. use crate::game::*; use serde::{Deserialize, Serialize}; // counters (index into GameStats::c) pub const HEROES: usize = 0; // heroes slain or captured pub const BOSSES: usize = 1; // champions of the Order brought down pub const LOST: usize = 2; // own creatures lost pub const DUG: usize = 3; // tiles dug out pub const WOOD: usize = 4; // wood felled pub const WALLS: usize = 5; // walls raised pub const ROOMS: usize = 6; // room tiles built pub const SPELLS: usize = 7; // spells cast (incl. lightning and healing) pub const SLAPS: usize = 8; // creatures slapped pub const TRAPS: usize = 9; // traps and doors ordered pub const TOWERS: usize = 10; // towers built pub const DROWNED: usize = 11; // heroes who fell into water or lava pub const BURNT: usize = 12; // bridge tiles that burnt down pub const SIEGE: usize = 13; // siege engines built pub const CAPTURED: usize = 14; // heroes knocked out and carried off pub const GOLD: usize = 15; // gold earned (treasure delivered, bounties, loot) pub const DRAWBRIDGE: usize = 16; // drawbridges raised pub const COUNTERS: usize = 17; // one sample every SAMPLE_EVERY seconds: time, gold, creatures, army pub const SAMPLE_EVERY: f32 = 10.0; const MAX_SAMPLES: usize = 720; #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GameStats { #[serde(default)] pub c: Vec, #[serde(default)] pub samples: Vec<(f32, f32, u16, f32)>, #[serde(default)] pub sample_t: f32, } impl GameStats { pub fn get(&self, k: usize) -> f32 { self.c.get(k).copied().unwrap_or(0.0) } } impl Game { // counts for the master at this computer (another master's deeds count on his computer) pub(crate) fn count(&mut self, k: usize, n: f32) { if self.me_side != self.local_side || self.demo.is_some() { return; } if self.stats.c.len() < COUNTERS { self.stats.c.resize(COUNTERS, 0.0); } self.stats.c[k] += n; } // a hero fell or was captured: the master at this computer counts it in single player and // co-op (the heroes are everybody's enemy there) pub(crate) fn count_local(&mut self, k: usize) { let me = self.me_side; let local = self.local_side; self.switch_to(local); self.count(k, 1.0); self.switch_to(me); } // every few seconds: how the local master stands pub(crate) fn sample_stats(&mut self, dt: f32) { if self.demo.is_some() || self.state != 0 { return; } self.stats.sample_t -= dt; if self.stats.sample_t > 0.0 { return; } self.stats.sample_t = SAMPLE_EVERY; let s = (self.time, self.gold, self.creature_count() as u16, self.army_value()); self.stats.samples.push(s); if self.stats.samples.len() > MAX_SAMPLES { // keep the whole game in view: every second sample goes let v: Vec<_> = self.stats.samples.iter().copied().step_by(2).collect(); self.stats.samples = v; } } } #[cfg(test)] mod tests { use super::*; #[test] fn a_game_keeps_its_statistics() { let mut g = Game::new(3, 64, 64); for _ in 0..(35 * 20) { g.update(0.05); } assert!(g.stats.samples.len() >= 3, "{} samples", g.stats.samples.len()); g.count(DUG, 2.0); assert_eq!(g.stats.get(DUG), 2.0); // saved with the game let json = serde_json::to_string(&g.stats).unwrap(); let back: GameStats = serde_json::from_str(&json).unwrap(); assert_eq!(back.get(DUG), 2.0); } }