Skip to main content

subtr_actor/stats/calculators/
backboard.rs

1use 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}