subtr_actor/stats/accumulators/
kickoff.rs1use super::*;
2
3#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
5#[ts(export)]
6pub struct KickoffStats {
7 pub count: u32,
8 pub team_zero_wins: u32,
9 pub team_one_wins: u32,
10 pub neutral_outcomes: u32,
11 pub team_zero_kickoff_possessions: u32,
12 pub team_one_kickoff_possessions: u32,
13 pub team_zero_kickoff_possession_advantages: u32,
14 pub team_one_kickoff_possession_advantages: u32,
15 pub contested_kickoff_possessions: u32,
16 pub kickoff_goal_count: u32,
17 pub team_zero_kickoff_goals: u32,
18 pub team_one_kickoff_goals: u32,
19 pub win_strength_sample_count: u32,
20 pub cumulative_win_strength: f32,
21 pub boost_after_sample_count: u32,
22 pub cumulative_boost_after: f32,
23 pub fake_count: u32,
24 pub missed_count: u32,
25 #[serde(default, skip_serializing_if = "LabeledCounts::is_empty")]
26 pub labeled_event_counts: LabeledCounts,
27 #[serde(default, skip_serializing_if = "LabeledCounts::is_empty")]
28 pub labeled_player_counts: LabeledCounts,
29}
30
31#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
33#[ts(export)]
34pub struct KickoffPlayerStats {
35 pub count: u32,
36 pub touches: u32,
37 pub fakes: u32,
38 pub misses: u32,
39 pub support_go_for_boosts: u32,
40 pub support_cheats: u32,
41 pub support_other: u32,
42 pub support_distance_sample_count: u32,
45 pub cumulative_support_distance_from_center: f32,
47 pub kickoff_goal_count: u32,
48 pub boost_after_sample_count: u32,
49 pub cumulative_boost_after: f32,
50 #[serde(default, skip_serializing_if = "LabeledCounts::is_empty")]
51 pub labeled_event_counts: LabeledCounts,
52}
53
54#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
56#[ts(export)]
57pub struct KickoffTeamStats {
58 pub count: u32,
59 pub wins: u32,
60 pub losses: u32,
61 pub neutral_outcomes: u32,
62 pub kickoff_possessions: u32,
63 pub opponent_kickoff_possessions: u32,
64 pub kickoff_possession_advantages: u32,
65 pub opponent_kickoff_possession_advantages: u32,
66 pub contested_kickoff_possessions: u32,
67 pub kickoff_goal_count: u32,
68 pub kickoff_goals_for: u32,
69 pub kickoff_goals_against: u32,
70 pub win_strength_sample_count: u32,
71 pub cumulative_win_strength: f32,
72 pub boost_after_sample_count: u32,
73 pub cumulative_boost_after: f32,
74 pub fake_count: u32,
75 pub missed_count: u32,
76}
77
78impl KickoffStats {
79 pub fn average_win_strength(&self) -> f32 {
80 if self.win_strength_sample_count == 0 {
81 0.0
82 } else {
83 self.cumulative_win_strength / self.win_strength_sample_count as f32
84 }
85 }
86
87 pub fn average_boost_after(&self) -> f32 {
88 if self.boost_after_sample_count == 0 {
89 0.0
90 } else {
91 self.cumulative_boost_after / self.boost_after_sample_count as f32
92 }
93 }
94
95 pub(crate) fn record_event(&mut self, event: &KickoffEvent) {
96 self.count += 1;
97 self.labeled_event_counts.increment(event.labels());
98 match event.outcome {
99 KickoffOutcome::TeamZeroWin => self.team_zero_wins += 1,
100 KickoffOutcome::TeamOneWin => self.team_one_wins += 1,
101 KickoffOutcome::Neutral => self.neutral_outcomes += 1,
102 KickoffOutcome::Unknown => {}
103 }
104 match event.kickoff_possession_outcome {
105 KickoffPossessionOutcome::TeamZeroPossession => self.team_zero_kickoff_possessions += 1,
106 KickoffPossessionOutcome::TeamOnePossession => self.team_one_kickoff_possessions += 1,
107 KickoffPossessionOutcome::TeamZeroAdvantage => {
108 self.team_zero_kickoff_possession_advantages += 1
109 }
110 KickoffPossessionOutcome::TeamOneAdvantage => {
111 self.team_one_kickoff_possession_advantages += 1
112 }
113 KickoffPossessionOutcome::Contested => self.contested_kickoff_possessions += 1,
114 }
115 if event.kickoff_goal {
116 self.kickoff_goal_count += 1;
117 match event.scoring_team_is_team_0 {
118 Some(true) => self.team_zero_kickoff_goals += 1,
119 Some(false) => self.team_one_kickoff_goals += 1,
120 None => {}
121 }
122 }
123 if let Some(win_strength) = event.win_strength {
124 self.win_strength_sample_count += 1;
125 self.cumulative_win_strength += win_strength;
126 }
127 for player in event.player_events() {
128 self.labeled_player_counts.increment(player.labels());
129 if let Some(taker) = player.as_taker() {
130 if let Some(boost_after) = taker.boost_after {
131 self.boost_after_sample_count += 1;
132 self.cumulative_boost_after += boost_after;
133 }
134 match taker.outcome {
135 KickoffTakerOutcome::Fake => self.fake_count += 1,
136 KickoffTakerOutcome::Missed => self.missed_count += 1,
137 _ => {}
138 }
139 }
140 }
141 }
142
143 pub fn complete_labeled_event_counts(&self) -> LabeledCounts {
144 LabeledCounts::complete_from_label_sets(
145 &[
146 &KICKOFF_OUTCOME_LABELS,
147 &KICKOFF_TYPE_LABELS,
148 &KICKOFF_DIRECTION_LABELS,
149 &KICKOFF_WIN_STRENGTH_LABELS,
150 &KICKOFF_POSSESSION_OUTCOME_LABELS,
151 &KICKOFF_GOAL_LABELS,
152 &KICKOFF_ADVANTAGE_LABELS,
153 ],
154 &self.labeled_event_counts,
155 )
156 }
157
158 pub fn complete_labeled_player_counts(&self) -> LabeledCounts {
159 LabeledCounts::complete_from_label_sets(
160 &[
161 &KICKOFF_SPAWN_LABELS,
162 &KICKOFF_TAKER_OUTCOME_LABELS,
163 &KICKOFF_APPROACH_LABELS,
164 &KICKOFF_FLIP_DIRECTION_LABELS,
165 &KICKOFF_SUPPORT_BEHAVIOR_LABELS,
166 &KICKOFF_BALL_DIRECTION_LABELS,
167 ],
168 &self.labeled_player_counts,
169 )
170 }
171
172 pub fn for_team(&self, is_team_zero: bool) -> KickoffTeamStats {
173 let wins = if is_team_zero {
174 self.team_zero_wins
175 } else {
176 self.team_one_wins
177 };
178 let losses = if is_team_zero {
179 self.team_one_wins
180 } else {
181 self.team_zero_wins
182 };
183 let kickoff_possessions = if is_team_zero {
184 self.team_zero_kickoff_possessions
185 } else {
186 self.team_one_kickoff_possessions
187 };
188 let opponent_kickoff_possessions = if is_team_zero {
189 self.team_one_kickoff_possessions
190 } else {
191 self.team_zero_kickoff_possessions
192 };
193 let kickoff_possession_advantages = if is_team_zero {
194 self.team_zero_kickoff_possession_advantages
195 } else {
196 self.team_one_kickoff_possession_advantages
197 };
198 let opponent_kickoff_possession_advantages = if is_team_zero {
199 self.team_one_kickoff_possession_advantages
200 } else {
201 self.team_zero_kickoff_possession_advantages
202 };
203 let kickoff_goals_for = if is_team_zero {
204 self.team_zero_kickoff_goals
205 } else {
206 self.team_one_kickoff_goals
207 };
208 let kickoff_goals_against = if is_team_zero {
209 self.team_one_kickoff_goals
210 } else {
211 self.team_zero_kickoff_goals
212 };
213 KickoffTeamStats {
214 count: self.count,
215 wins,
216 losses,
217 neutral_outcomes: self.neutral_outcomes,
218 kickoff_possessions,
219 opponent_kickoff_possessions,
220 kickoff_possession_advantages,
221 opponent_kickoff_possession_advantages,
222 contested_kickoff_possessions: self.contested_kickoff_possessions,
223 kickoff_goal_count: self.kickoff_goal_count,
224 kickoff_goals_for,
225 kickoff_goals_against,
226 win_strength_sample_count: self.win_strength_sample_count,
227 cumulative_win_strength: self.cumulative_win_strength,
228 boost_after_sample_count: self.boost_after_sample_count,
229 cumulative_boost_after: self.cumulative_boost_after,
230 fake_count: self.fake_count,
231 missed_count: self.missed_count,
232 }
233 }
234}
235
236impl KickoffPlayerStats {
237 pub(crate) fn record_event(&mut self, event: &KickoffEvent, player: KickoffPlayerEventRef<'_>) {
238 self.count += 1;
239 self.labeled_event_counts.increment(player.labels());
240 if let Some(taker) = player.as_taker() {
241 match taker.outcome {
242 KickoffTakerOutcome::Touched => self.touches += 1,
243 KickoffTakerOutcome::Fake => self.fakes += 1,
244 KickoffTakerOutcome::Missed => self.misses += 1,
245 KickoffTakerOutcome::Unknown => {}
246 }
247 }
248 if let Some(support) = player.as_support() {
249 if support.first_touch_time.is_some() {
250 self.touches += 1;
251 }
252 self.support_distance_sample_count += 1;
253 self.cumulative_support_distance_from_center += support.start_distance_from_center;
254 match support.support_behavior {
255 KickoffSupportBehavior::GoForBoost => self.support_go_for_boosts += 1,
256 KickoffSupportBehavior::Cheat => self.support_cheats += 1,
257 KickoffSupportBehavior::Other => self.support_other += 1,
258 KickoffSupportBehavior::Unknown => {}
259 }
260 }
261 if event.kickoff_goal && event.scoring_team_is_team_0 == Some(player.is_team_0()) {
262 self.kickoff_goal_count += 1;
263 }
264 if let Some(boost_after) = player.boost_after() {
265 self.boost_after_sample_count += 1;
266 self.cumulative_boost_after += boost_after;
267 }
268 }
269
270 pub fn average_boost_after(&self) -> f32 {
271 if self.boost_after_sample_count == 0 {
272 0.0
273 } else {
274 self.cumulative_boost_after / self.boost_after_sample_count as f32
275 }
276 }
277
278 pub fn average_support_distance_from_center(&self) -> f32 {
281 if self.support_distance_sample_count == 0 {
282 0.0
283 } else {
284 self.cumulative_support_distance_from_center / self.support_distance_sample_count as f32
285 }
286 }
287}
288
289impl KickoffTeamStats {
290 pub fn average_win_strength(&self) -> f32 {
291 if self.win_strength_sample_count == 0 {
292 0.0
293 } else {
294 self.cumulative_win_strength / self.win_strength_sample_count as f32
295 }
296 }
297
298 pub fn average_boost_after(&self) -> f32 {
299 if self.boost_after_sample_count == 0 {
300 0.0
301 } else {
302 self.cumulative_boost_after / self.boost_after_sample_count as f32
303 }
304 }
305}
306
307#[derive(Debug, Clone, Default, PartialEq)]
309pub struct KickoffStatsAccumulator {
310 stats: KickoffStats,
311 player_stats: HashMap<PlayerId, KickoffPlayerStats>,
312}
313
314impl KickoffStatsAccumulator {
315 pub fn new() -> Self {
316 Self::default()
317 }
318
319 pub fn stats(&self) -> &KickoffStats {
320 &self.stats
321 }
322
323 pub fn player_stats(&self) -> &HashMap<PlayerId, KickoffPlayerStats> {
324 &self.player_stats
325 }
326
327 pub fn apply_event(&mut self, event: &KickoffEvent) {
328 self.stats.record_event(event);
329 for player in event.player_events() {
330 self.player_stats
331 .entry(player.player().clone())
332 .or_default()
333 .record_event(event, player);
334 }
335 }
336}