Statistics with a chart at the end of a game, 64 achievements kept locally and on the server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 23:36:19 +01:00
1 parent 193b199875
commit 9931ca9b54
37 files changed
+1833 -4

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
// 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<f32>,
#[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);
}
}