111 lines
4.0 KiB
Rust
111 lines
4.0 KiB
Rust
// 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) {
|
|
// every master's deeds bring him experience for his talents
|
|
self.add_master_xp(crate::talents::xp_for(k) * n);
|
|
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);
|
|
}
|
|
}
|