From 89f97101af6f05d4c66dfb7171559243d30658d4 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Sat, 5 Apr 2025 19:17:37 -0400 Subject: [PATCH] Various patches and Delta implementations. Cheap summaries in main.rs --- gamelog/src/event.rs | 105 ++++++++++++++++++++++++++++++++++++------- gamelog/src/game.rs | 88 +++++++++++++++++++++++++----------- miller/src/main.rs | 88 +++++++++++++++++++++++++++++++++--- 3 files changed, 232 insertions(+), 49 deletions(-) diff --git a/gamelog/src/event.rs b/gamelog/src/event.rs index 622c59d..caceb1a 100644 --- a/gamelog/src/event.rs +++ b/gamelog/src/event.rs @@ -5,8 +5,8 @@ type Offence = Team; #[derive(Debug, Deserialize, Clone, PartialEq)] pub enum Event { - Play(Play), Kickoff(Offence), + Play(Play), Turnover(Offence), Penalty(TerrainState), Score(ScorePoints), @@ -54,13 +54,13 @@ impl Event { let a = if let TerrainState::Yards(yrds) = preceeding.terrain? { yrds } else { - unreachable!() + 0_u8 }; let b = if let TerrainState::Yards(yrds) = following.terrain? { yrds } else { - unreachable!() + 0_u8 }; Some(a as i8 - b as i8) @@ -159,27 +159,100 @@ mod tests { }); assert!(10_i8 == kickoff.delta(&first_down).unwrap()); - assert!(0_i8 == first_down.delta(&second_down).unwrap()); - assert!(-3_i8 == second_down.delta(&third_down).unwrap()); + assert!(0_i8 == kickoff.delta(&second_down).unwrap()); + assert!(None == kickoff.delta(&penalty)); + assert!(None == kickoff.delta(&score)); + assert!(10_i8 == first_down.delta(&kickoff).unwrap()); + assert!(10_i8 == first_down.delta(&first_down).unwrap()); + assert!(0_i8 == first_down.delta(&second_down).unwrap()); + assert!(None == first_down.delta(&turnover)); + assert!(None == first_down.delta(&penalty)); + assert!(None == first_down.delta(&score)); assert!(10_i8 == first_down.delta(&goal_line).unwrap()); assert!(10_i8 == first_down.delta(&inches).unwrap()); + assert!(None == first_down.delta(&noned_down)); + + assert!(10_i8 == second_down.delta(&kickoff).unwrap()); + assert!(10_i8 == second_down.delta(&first_down).unwrap()); + assert!(-3_i8 == second_down.delta(&third_down).unwrap()); + assert!(None == second_down.delta(&turnover)); + assert!(None == second_down.delta(&penalty)); + assert!(None == second_down.delta(&score)); + assert!(10_i8 == second_down.delta(&goal_line).unwrap()); + assert!(10_i8 == second_down.delta(&inches).unwrap()); + assert!(None == second_down.delta(&noned_down)); + assert!(13_i8 == third_down.delta(&kickoff).unwrap()); + assert!(13_i8 == third_down.delta(&first_down).unwrap()); assert!(8_i8 == third_down.delta(&fourth_down).unwrap()); + assert!(None == third_down.delta(&turnover)); + assert!(None == third_down.delta(&penalty)); + assert!(None == third_down.delta(&score)); assert!(13_i8 == third_down.delta(&goal_line).unwrap()); + assert!(13_i8 == third_down.delta(&inches).unwrap()); + assert!(None == third_down.delta(&noned_down)); + + assert!(5_i8 == fourth_down.delta(&kickoff).unwrap()); + assert!(5_i8 == fourth_down.delta(&first_down).unwrap()); assert!(None == fourth_down.delta(&turnover)); - assert!(None == kickoff.delta(&penalty)); - assert!(None == first_down.delta(&penalty)); - assert!(None == noned_down.delta(&kickoff)); - assert!(None == noned_down.delta(&turnover)); - assert!(None == kickoff.delta(&score)); - assert!(None == first_down.delta(&score)); - assert!(None == turnover.delta(&score)); - assert!(None == goal_line.delta(&first_down)); - assert!(None == inches.delta(&first_down)); - assert!(None == goal_line.delta(&inches)); + assert!(None == fourth_down.delta(&penalty)); + assert!(None == fourth_down.delta(&score)); + assert!(5_i8 == fourth_down.delta(&goal_line).unwrap()); + assert!(5_i8 == fourth_down.delta(&inches).unwrap()); + assert!(None == fourth_down.delta(&noned_down)); + assert!(10_i8 == turnover.delta(&first_down).unwrap()); assert!(0_i8 == turnover.delta(&second_down).unwrap()); - assert!(-3_i8 == turnover.delta(&third_down).unwrap()); + assert!(None == turnover.delta(&turnover)); + assert!(None == turnover.delta(&penalty)); + assert!(None == turnover.delta(&score)); + assert!(10_i8 == turnover.delta(&goal_line).unwrap()); + assert!(10_i8 == turnover.delta(&inches).unwrap()); + assert!(None == turnover.delta(&noned_down)); + + assert!(None == score.delta(&kickoff)); + assert!(None == score.delta(&first_down)); + assert!(None == score.delta(&second_down)); + assert!(None == score.delta(&third_down)); + assert!(None == score.delta(&fourth_down)); + assert!(None == score.delta(&turnover)); + assert!(None == score.delta(&penalty)); + assert!(None == score.delta(&goal_line)); + assert!(None == score.delta(&inches)); + assert!(None == score.delta(&score)); + + assert!(None == goal_line.delta(&kickoff)); + assert!(None == goal_line.delta(&first_down)); + assert!(-10_i8 == goal_line.delta(&second_down).unwrap()); + assert!(-13_i8 == goal_line.delta(&third_down).unwrap()); + assert!(-5_i8 == goal_line.delta(&fourth_down).unwrap()); + assert!(None == goal_line.delta(&turnover)); + assert!(None == goal_line.delta(&penalty)); + assert!(None == goal_line.delta(&goal_line)); + assert!(None == goal_line.delta(&inches)); + assert!(None == goal_line.delta(&score)); + + assert!(None == inches.delta(&kickoff)); + assert!(None == inches.delta(&first_down)); + assert!(-10_i8 == goal_line.delta(&second_down).unwrap()); + assert!(-13_i8 == goal_line.delta(&third_down).unwrap()); + assert!(-5_i8 == goal_line.delta(&fourth_down).unwrap()); + assert!(None == inches.delta(&turnover)); + assert!(None == inches.delta(&penalty)); + assert!(None == inches.delta(&goal_line)); + assert!(None == inches.delta(&inches)); + assert!(None == inches.delta(&score)); + + assert!(None == noned_down.delta(&kickoff)); + assert!(None == noned_down.delta(&first_down)); + assert!(None == noned_down.delta(&second_down)); + assert!(None == noned_down.delta(&third_down)); + assert!(None == noned_down.delta(&fourth_down)); + assert!(None == noned_down.delta(&turnover)); + assert!(None == noned_down.delta(&penalty)); + assert!(None == noned_down.delta(&goal_line)); + assert!(None == noned_down.delta(&inches)); + assert!(None == noned_down.delta(&score)); } } diff --git a/gamelog/src/game.rs b/gamelog/src/game.rs index 2be7546..a1ef14f 100644 --- a/gamelog/src/game.rs +++ b/gamelog/src/game.rs @@ -53,8 +53,6 @@ impl Game { let mut idx: usize = 0; let mut deltas: Vec = vec![]; - dbg!(&events); - while idx < len { if let Some(value) = events[idx].delta(&events[idx + 1]) { deltas.push(value); @@ -66,31 +64,6 @@ impl Game { deltas } - /// The average number of plays in a quarter. - /// Does not include OT plays or quarters where team indeterminate. - pub fn avg_plays_per_quarter(&self, team: Team) -> f32 { - // Handle if teams known at start or not override via index calculation of all game events. - - let quarterly_avgs: Vec = self - .periods - .iter() - .filter_map(|period| { - if !period.is_overtime() { - let plays = period.team_plays(team.to_owned(), None); - Some(plays.unwrap().len() as f32 / period.quarters().len() as f32) - } else { - None - } - }) - .collect::>(); - - let mut summation = 0_f32; - - quarterly_avgs.iter().for_each(|float| summation += float); - - summation / quarterly_avgs.len() as f32 - } - pub fn team_plays(&self, team: Team) -> usize { let quarterly_plays: Vec = self .periods @@ -111,6 +84,67 @@ impl Game { summation } + + /// The average number of plays in a quarter. + /// Does not include OT plays or quarters where team indeterminate. + pub fn avg_plays_per_quarter(&self, team: Team) -> f32 { + // Handle if teams known at start or not override via index calculation of all game events. + + let quarterly_avgs: Vec = self + .periods + .iter() + .filter_map(|period| { + if !period.is_overtime() { + let plays = period.team_plays(team.to_owned(), None); + Some(plays.unwrap().len() as f32 / period.quarters().len() as f32) + } else { + None + } + }) + .collect::>(); + + quarterly_avgs.iter().sum::() / quarterly_avgs.len() as f32 + } + + pub fn avg_delta(&self, team: Team) -> f32 { + let deltas = self.deltas(team); + + // Summation doesn't like directly returning f32 from i8. + deltas.iter().sum::() as f32 / deltas.len() as f32 + } + + pub fn avg_gain(&self, team: Team) -> f32 { + let deltas: Vec = self + .deltas(team) + .iter() + .filter_map(|value| { + if value.is_positive() { + Some(value.to_owned() as u8) + } else { + None + } + }) + .collect(); + + // Summation doesn't like directly returning f32 from u8. + deltas.iter().sum::() as f32 / deltas.len() as f32 + } + + pub fn avg_loss(&self, team: Team) -> f32 { + let deltas: Vec = self + .deltas(team) + .iter() + .filter_map(|value| { + if value.is_negative() { + Some(value.to_owned()) + } else { + None + } + }) + .collect(); + + deltas.iter().sum::() as f32 / deltas.len() as f32 + } } #[derive(Debug, Deserialize, Clone, PartialEq)] diff --git a/miller/src/main.rs b/miller/src/main.rs index 2c55b8e..bc9515c 100644 --- a/miller/src/main.rs +++ b/miller/src/main.rs @@ -1,6 +1,6 @@ use clap::Parser; use core::panic; -use gamelog::{Flags, LogFile}; +use gamelog::{Action, Flags, LogFile, Team}; use std::path::PathBuf; #[derive(Debug, Parser)] @@ -27,17 +27,93 @@ fn main() { Err(err) => panic!("Error: Failed to open logfile: {:?}", err), }; + let mut stats = vec![ + TeamStats::new(Team::ArizonaState), + #[allow(deprecated)] + TeamStats::new(Team::BoiseState), + TeamStats::new(Team::Colorado), + TeamStats::new(Team::Iowa), + TeamStats::new(Team::Nebraska), + TeamStats::new(Team::Syracuse), + TeamStats::new(Team::SouthCarolina), + TeamStats::new(Team::TexasAnM), + ]; + for game in log.0.iter() { if let Ok(teams) = game.teams() { for team in teams { if !game.flags.contains(&Flags::IgnoreTeam(team.to_owned())) { - println!( - "{:?}: {:?}", - &team, - game.avg_plays_per_quarter(team.to_owned()) - ) + // Team is to have their stats recorded this game of file. + let team_idx = stats + .iter() + .position(|stat| { + if stat.team == team.to_owned() { + true + } else { + false + } + }) + .unwrap(); + + stats[team_idx] + .avg_terrain_gain + .push(game.avg_gain(team.to_owned())); + + stats[team_idx] + .avg_terrain_loss + .push(game.avg_loss(team.to_owned())); + + stats[team_idx] + .avg_terrain_delta + .push(game.avg_delta(team.to_owned())); + + stats[team_idx] + .plays_per_quarter + .push(game.avg_plays_per_quarter(team.to_owned())); } } } } + + for team in stats { + dbg!(team); + } +} + +#[derive(Debug)] +struct TeamStats { + team: gamelog::Team, + // Terrain + avg_terrain_gain: Vec, + avg_terrain_loss: Vec, + avg_terrain_delta: Vec, + // Play rate + plays_per_quarter: Vec, + plays_per_game: Vec, + // Penalties + penalties_per_game: Vec, + // Score + points_per_quarter: Vec, + points_per_game: Vec, + // Biases + most_common_play: Option, + least_common_play: Option, +} + +impl TeamStats { + fn new(team: Team) -> Self { + TeamStats { + team, + avg_terrain_gain: vec![], + avg_terrain_loss: vec![], + avg_terrain_delta: vec![], + plays_per_quarter: vec![], + plays_per_game: vec![], + penalties_per_game: vec![], + points_per_quarter: vec![], + points_per_game: vec![], + most_common_play: None, + least_common_play: None, + } + } }