subtr_actor/stats/calculators/
backboard.rs1use super::*;
2
3#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
4#[ts(export)]
5pub struct BackboardPlayerStats {
6 pub count: u32,
7 pub is_last_backboard: bool,
8 pub last_backboard_time: Option<f32>,
9 pub last_backboard_frame: Option<usize>,
10 pub time_since_last_backboard: Option<f32>,
11 pub frames_since_last_backboard: Option<usize>,
12}
13
14#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
15#[ts(export)]
16pub struct BackboardTeamStats {
17 pub count: u32,
18}
19
20#[derive(Debug, Clone, Default)]
21pub struct BackboardCalculator {
22 player_stats: HashMap<PlayerId, BackboardPlayerStats>,
23 team_zero_stats: BackboardTeamStats,
24 team_one_stats: BackboardTeamStats,
25 events: Vec<BackboardBounceEvent>,
26 current_last_backboard_player: Option<PlayerId>,
27}
28
29impl BackboardCalculator {
30 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn player_stats(&self) -> &HashMap<PlayerId, BackboardPlayerStats> {
35 &self.player_stats
36 }
37
38 pub fn team_zero_stats(&self) -> &BackboardTeamStats {
39 &self.team_zero_stats
40 }
41
42 pub fn team_one_stats(&self) -> &BackboardTeamStats {
43 &self.team_one_stats
44 }
45
46 pub fn events(&self) -> &[BackboardBounceEvent] {
47 &self.events
48 }
49
50 fn begin_sample(&mut self, frame: &FrameInfo) {
51 for stats in self.player_stats.values_mut() {
52 stats.is_last_backboard = false;
53 stats.time_since_last_backboard = stats
54 .last_backboard_time
55 .map(|time| (frame.time - time).max(0.0));
56 stats.frames_since_last_backboard = stats
57 .last_backboard_frame
58 .map(|last_frame| frame.frame_number.saturating_sub(last_frame));
59 }
60 }
61
62 fn apply_events(&mut self, frame: &FrameInfo, events: &[BackboardBounceEvent]) {
63 for event in events {
64 let stats = self.player_stats.entry(event.player.clone()).or_default();
65 stats.count += 1;
66 stats.last_backboard_time = Some(event.time);
67 stats.last_backboard_frame = Some(event.frame);
68 stats.time_since_last_backboard = Some((frame.time - event.time).max(0.0));
69 stats.frames_since_last_backboard =
70 Some(frame.frame_number.saturating_sub(event.frame));
71
72 let team_stats = if event.is_team_0 {
73 &mut self.team_zero_stats
74 } else {
75 &mut self.team_one_stats
76 };
77 team_stats.count += 1;
78 self.events.push(event.clone());
79 }
80
81 if let Some(last_event) = events.last() {
82 self.current_last_backboard_player = Some(last_event.player.clone());
83 }
84
85 if let Some(player_id) = self.current_last_backboard_player.as_ref() {
86 if let Some(stats) = self.player_stats.get_mut(player_id) {
87 stats.is_last_backboard = true;
88 }
89 }
90 }
91
92 pub fn update(
93 &mut self,
94 frame: &FrameInfo,
95 backboard_bounce_state: &BackboardBounceState,
96 ) -> SubtrActorResult<()> {
97 self.begin_sample(frame);
98 self.apply_events(frame, &backboard_bounce_state.bounce_events);
99 Ok(())
100 }
101}