From 65e202d78e7b301eb6d4d03957fa58ed74b748a9 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Sun, 30 Mar 2025 22:16:10 -0400 Subject: [PATCH 01/17] v1? --- README.adoc | 31 +++++++++ gamelog/Cargo.lock | 35 +++++++++++ gamelog/Cargo.toml | 4 ++ gamelog/src/action.rs | 138 +++++++++++++++++++++++++++++++++++++++++ gamelog/src/error.rs | 18 ++---- gamelog/src/event.rs | 14 +++++ gamelog/src/file.rs | 50 ++++++--------- gamelog/src/game.rs | 44 +++++++++++++ gamelog/src/lib.rs | 10 +++ gamelog/src/period.rs | 33 ++++++---- gamelog/src/play.rs | 58 ++++++----------- gamelog/src/score.rs | 8 +++ gamelog/src/terrain.rs | 3 +- miller/src/main.rs | 5 -- 14 files changed, 349 insertions(+), 102 deletions(-) create mode 100644 gamelog/src/action.rs create mode 100644 gamelog/src/event.rs create mode 100644 gamelog/src/game.rs create mode 100644 gamelog/src/score.rs diff --git a/README.adoc b/README.adoc index 11d4b87..2338ac5 100644 --- a/README.adoc +++ b/README.adoc @@ -14,7 +14,38 @@ I figured, that since I already had to digitize every note, that I was required == Goals +=== Gamelog * [*] Data Format +** [*] Support recording multiple games +** [*] Periods +*** Quarters +*** Overtime +** [*] Teams +*** Iowa +*** Syracuse +*** Texas A&M +*** Colorado +*** Nebraska +*** Boise State [deprecated] +*** Arizona State +*** South Carolina +** [*] Downs +*** First - Fourth +*** Kickoff +*** PAT (Point After Touchdown) +**** One +**** Two +**** Failed +** [*] Score +** [*] Terrain Position +*** [*] Yards +*** [*] In (Inches) +*** [*] GL (Goal Line) +** [*] Penalty +** Out? +** [*] Plays + +=== Miller: * [ ] Mathematics ** [ ] Avg. Terrain Gain ** [ ] Avg. Terrain Loss diff --git a/gamelog/Cargo.lock b/gamelog/Cargo.lock index a94f63e..93a3f7b 100644 --- a/gamelog/Cargo.lock +++ b/gamelog/Cargo.lock @@ -24,8 +24,15 @@ dependencies = [ "ron", "semver", "serde", + "strum", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "proc-macro2" version = "1.0.94" @@ -57,6 +64,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "semver" version = "1.0.26" @@ -86,6 +99,28 @@ dependencies = [ "syn", ] +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.100" diff --git a/gamelog/Cargo.toml b/gamelog/Cargo.toml index e80c272..ac307c4 100644 --- a/gamelog/Cargo.toml +++ b/gamelog/Cargo.toml @@ -6,6 +6,10 @@ edition = "2024" [dependencies] ron = "0.9" +[dependencies.strum] +version = "0.27" +features = ["derive"] + [dependencies.semver] version = "1.0" features = ["serde"] diff --git a/gamelog/src/action.rs b/gamelog/src/action.rs new file mode 100644 index 0000000..179e1d8 --- /dev/null +++ b/gamelog/src/action.rs @@ -0,0 +1,138 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize, Clone, Default, PartialEq)] +pub enum Action { + CrackStudentBodyRightTackle, + Curls, + FleaFlicker, + HalfbackSlam, + HalfbackSlipScreen, + HalfbackSweep, + Mesh, + PlayActionBoot, + PlayActionComebacks, + PlayActionPowerZero, + PowerZero, + SlantBubble, + SlotOut, + SpeedOption, + StrongFlood, + #[default] + Unknown, +} + +impl Action { + // I'd love a better way of doing these + // Attributes are probably the way to go, + // but I'm not about to write procedural macros for this project. + + /// Returns `true` if `self` is a play action. + pub fn is_play_action(&self) -> bool { + if let Self::PlayActionBoot | Self::PlayActionComebacks | Self::PlayActionPowerZero = self { + true + } else { + false + } + } + + /// Returns `true` if `self` is a halfback. + pub fn is_halfback(&self) -> bool { + if let Self::HalfbackSlam | Self::HalfbackSlipScreen | Self::HalfbackSweep = self { + true + } else { + true + } + } + + /// Returns `true` if `self` is a running play. + pub fn is_run(&self) -> bool { + if let Self::HalfbackSlam + | Self::SpeedOption + | Self::HalfbackSweep + | Self::PowerZero + | Self::CrackStudentBodyRightTackle = self + { + true + } else { + false + } + } + + /// Returns `true` if `self` is a passing play. + pub fn is_pass(&self) -> bool { + !self.is_run() + } + + /// Returns `true` if `self` is `Event::Unknown`. + pub fn is_unknown(&self) -> bool { + if let Self::Unknown = self { + true + } else { + false + } + } + + /// Returns the `Playset` that this action belongs to. + /// Returns `None` if `Event::Unknown` + pub fn playset(&self) -> Option { + if self.is_unknown() { + return None; + } + + Some(match self { + Self::SlantBubble | Self::HalfbackSlam | Self::PlayActionBoot => Playset::PistolSpread, + Self::StrongFlood | Self::SpeedOption | Self::HalfbackSlipScreen => { + Playset::ShotgunTripleWingsOffset + } + Self::SlotOut | Self::HalfbackSweep | Self::PlayActionComebacks => { + Playset::ShotgunDoubleFlex + } + Self::Curls | Self::PowerZero | Self::PlayActionPowerZero => Playset::IFormNormal, + Self::Mesh | Self::CrackStudentBodyRightTackle | Self::FleaFlicker => { + Playset::IFormTight + } + _ => unreachable!(), + }) + } + + /// Returns the `Key` that this action belongs to. + /// Returns `None` if `Event::Unknown` + pub fn key(&self) -> Option { + if self.is_unknown() { + return None; + } + + // All running plays are on `Key::X` + if self.is_run() { + return Some(Key::X); + } + + Some(match self { + Self::SlantBubble | Self::StrongFlood | Self::SlotOut | Self::Curls | Self::Mesh => { + Key::Square + } + Self::PlayActionBoot + | Self::HalfbackSlipScreen + | Self::PlayActionComebacks + | Self::PlayActionPowerZero + | Self::FleaFlicker => Key::Triangle, + _ => unreachable!(), + }) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Playset { + PistolSpread, + ShotgunTripleWingsOffset, + ShotgunDoubleFlex, + IFormNormal, + IFormTight, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Key { + Square, + X, + Triangle, +} diff --git a/gamelog/src/error.rs b/gamelog/src/error.rs index 7816ff0..c3e135f 100644 --- a/gamelog/src/error.rs +++ b/gamelog/src/error.rs @@ -4,32 +4,26 @@ use std::{fmt, io}; pub enum LogFileError { FailedToOpen(io::Error), RonSpannedError(ron::error::SpannedError), - CompatibilityCheck(semver::Version), } impl fmt::Display for LogFileError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::FailedToOpen(err) => write!(f, "{}", err), - Self::CompatibilityCheck(ver) => write!( - f, - "GameLogs cannot be older than {}, but {} was found in logfile.", - crate::MIN_VER.to_string(), - ver.to_string() - ), Self::RonSpannedError(err) => write!(f, "{}", err), } } } -pub enum DownError { - NotKickoff, +#[derive(Debug)] +pub enum TeamsError { + NumberFound(usize), } -impl fmt::Display for DownError { +impl fmt::Display for TeamsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Self::NotKickoff => write!(f, "Variant was not Down::Kickoff."), + match self { + Self::NumberFound(err) => write!(f, "Expected two, found: {:?}", err), } } } diff --git a/gamelog/src/event.rs b/gamelog/src/event.rs new file mode 100644 index 0000000..bc385b8 --- /dev/null +++ b/gamelog/src/event.rs @@ -0,0 +1,14 @@ +use crate::{Play, ScoreChange, Team, TerrainState}; + +use serde::Deserialize; + +type Offence = Team; + +#[derive(Debug, Deserialize, Clone)] +pub enum Event { + Play(Play), + Kickoff(Offence), + Turnover(Offence), + Penalty(TerrainState), + ScoreChange(ScoreChange), +} diff --git a/gamelog/src/file.rs b/gamelog/src/file.rs index 3bda9da..71d4334 100644 --- a/gamelog/src/file.rs +++ b/gamelog/src/file.rs @@ -5,6 +5,25 @@ use std::{fs::File, path::PathBuf}; #[derive(Debug, Deserialize, Clone)] pub struct LogFile(Vec); +impl LogFile { + pub fn min_ver(&self) -> semver::Version { + let mut lowest = semver::Version::new(u64::MAX, u64::MAX, u64::MAX); + + self.0.iter().for_each(|x| { + if x.version.cmp_precedence(&lowest).is_lt() { + lowest = x.version.clone() + } + }); + + lowest + } + + /// Returns if the LogFile min version is compatible. + pub fn is_compatible(&self) -> bool { + self.min_ver().cmp_precedence(&super::MIN_VER).is_lt() + } +} + impl TryFrom for LogFile { type Error = ron::error::SpannedError; @@ -31,34 +50,3 @@ impl TryFrom for LogFile { } } } - -impl LogFile { - pub fn get_min_ver(self) -> semver::Version { - let mut lowest = semver::Version::new(u64::MAX, u64::MAX, u64::MAX); - - self.0.iter().for_each(|x| { - if x.version.cmp_precedence(&lowest).is_lt() { - lowest = x.version.clone() - } - }); - - lowest - } - - /// Returns if the LogFile min version is compatible. - fn is_compatible(&self) -> bool { - self.clone() - .get_min_ver() - .cmp_precedence(&super::MIN_VER) - .is_lt() - } - - /// Ensures that the returned gamefile is compatible, else returns Error. - pub fn ensure_compatible(self) -> Result { - if self.is_compatible() { - Ok(self) - } else { - Err(error::LogFileError::CompatibilityCheck(self.get_min_ver())) - } - } -} diff --git a/gamelog/src/game.rs b/gamelog/src/game.rs new file mode 100644 index 0000000..2c99e3f --- /dev/null +++ b/gamelog/src/game.rs @@ -0,0 +1,44 @@ +use crate::{Event, Period, Play, PlayHandle, Team, error}; +use serde::Deserialize; + +#[deprecated(since = "0.2.0", note = "Migrated to Game")] +pub type GameRecord = Game; + +#[derive(Debug, Deserialize, Clone)] +pub struct Game { + pub version: semver::Version, + pub periods: Vec, +} + +impl Game { + /// Returns the teams of this game. + pub fn teams(&self) -> Result, error::TeamsError> { + let mut teams = vec![]; + + self.periods.iter().for_each(|period| { + period.events.iter().for_each(|event| { + if let Event::Kickoff(t) | Event::Turnover(t) = event { + if teams.contains(t) { + teams.push(t.to_owned()) + } + } + }) + }); + + if teams.len() == 2 { + Ok(teams) + } else { + Err(error::TeamsError::NumberFound(teams.len())) + } + } +} + +impl PlayHandle for Game { + fn plays(&self) -> Vec { + self.periods + .iter() + .map(|period| period.plays()) + .collect::>>() // Make compiler happy with turbofish. + .concat() + } +} diff --git a/gamelog/src/lib.rs b/gamelog/src/lib.rs index 8d1304f..9dd1de2 100644 --- a/gamelog/src/lib.rs +++ b/gamelog/src/lib.rs @@ -1,13 +1,23 @@ +#![allow(deprecated)] + +mod action; mod error; +mod event; mod file; +mod game; mod period; mod play; +mod score; mod terrain; #[allow(unused)] pub const MIN_VER: semver::Version = semver::Version::new(0, 3, 0); +pub use action::*; +pub use event::Event; pub use file::LogFile; +pub use game::{Game, GameRecord}; pub use period::*; pub use play::*; +pub use score::ScoreChange; pub use terrain::TerrainState; diff --git a/gamelog/src/period.rs b/gamelog/src/period.rs index b062687..f06e4bd 100644 --- a/gamelog/src/period.rs +++ b/gamelog/src/period.rs @@ -1,22 +1,29 @@ +use crate::{Event, PlayHandle}; use serde::Deserialize; -#[deprecated(since = "0.2.0", note = "Migrated to Game")] -type GameRecord = Game; - -#[derive(Debug, Deserialize, Clone)] -pub struct Game { - pub version: semver::Version, - periods: Vec>, -} - #[derive(Debug, Deserialize, Clone)] pub struct Period { - start: Quarter, - end: Option, - plays: Vec, + pub start: Quarter, + pub end: Option, + pub events: Vec, } -#[derive(Debug, Deserialize, Clone)] +impl PlayHandle for Period { + fn plays(&self) -> Vec { + self.events + .iter() + .filter_map(|event| { + if let Event::Play(play) = event { + Some(play.to_owned()) + } else { + None + } + }) + .collect() + } +} + +#[derive(Debug, Deserialize, Clone, PartialEq)] pub enum Quarter { First, Second, diff --git a/gamelog/src/play.rs b/gamelog/src/play.rs index 0ab629d..f1a4eee 100644 --- a/gamelog/src/play.rs +++ b/gamelog/src/play.rs @@ -1,57 +1,35 @@ -use crate::{TerrainState, error}; +use crate::{Action, TerrainState}; use serde::Deserialize; -#[derive(Debug, Deserialize, Clone)] +pub trait PlayHandle { + /// Returns all plays within object's scope. + fn plays(&self) -> Vec; +} + +#[derive(Debug, Deserialize, Clone, Default, PartialEq)] pub struct Play { - down: Down, - terrain: TerrainState, + pub action: Action, + pub down: Down, + pub terrain: TerrainState, } -type Offence = Team; -impl Offence {} - -#[derive(Debug, Deserialize, Clone)] -pub enum Event { - CrackStudentBodyRightTackle(Play), - Curls(Play), - FleaFlicker(Play), - HalfbackSlam(Play), - HalfbackSlipScreen(Play), - HalfbackSweep(Play), - Mesh(Play), - PlayActionBoot(Play), - PlayActionComebacks(Play), - PlayActionPowerZero(Play), - PowerZero(Play), - SlantBubble(Play), - SlotOut(Play), - SpeedOption(Play), - StrongFlood(Play), - Unknown(Play), - Kickoff { offence: Team }, - Turnover { offence: Team }, - Penalty { terrain: TerrainState }, +impl PlayHandle for Play { + fn plays(&self) -> Vec { + vec![self.to_owned()] + } } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, Default, PartialEq)] pub enum Down { + #[default] First, Second, Third, Fourth, - PointAfterTouchdown, + PointAfterTouchdown(Option), } -impl Down { - fn get_offence(&self) -> Result<&Team, error::DownError> { - match self { - Self::Kickoff { offence } => Ok(offence), - _ => Err(error::DownError::NotKickoff), - } - } -} - -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, PartialEq)] pub enum Team { ArizonaState, #[deprecated(since = "0.2.0", note = "Team left the project.")] diff --git a/gamelog/src/score.rs b/gamelog/src/score.rs new file mode 100644 index 0000000..51d2d69 --- /dev/null +++ b/gamelog/src/score.rs @@ -0,0 +1,8 @@ +use crate::Team; +use serde::Deserialize; + +#[derive(Debug, Deserialize, Clone, PartialEq)] +pub struct ScoreChange { + pub team: Team, + pub score: u8, +} diff --git a/gamelog/src/terrain.rs b/gamelog/src/terrain.rs index dcfe9ab..1fc8176 100644 --- a/gamelog/src/terrain.rs +++ b/gamelog/src/terrain.rs @@ -1,11 +1,12 @@ use serde::Deserialize; -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, Default, PartialEq)] pub enum TerrainState { #[deprecated(since = "0.2.0", note = "Replaced in favour of TerrainState::Yards")] Distance(u8), Yards(u8), GoalLine, Inches, + #[default] Unknown, } diff --git a/miller/src/main.rs b/miller/src/main.rs index e69f17b..dfba545 100644 --- a/miller/src/main.rs +++ b/miller/src/main.rs @@ -29,10 +29,5 @@ fn main() { Ok(f) => f, Err(err) => panic!("Error: Failed to open logfile: {:?}", err), }; - - match file.ensure_compatible() { - Ok(f) => f, - Err(err) => panic!("Error: Failed to ensure logfile compatibility: {:?}", err), - } }; } -- 2.49.1 From 25a300009b8ccc46b83ec6549ac0a8dda44f0ad1 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Mon, 31 Mar 2025 08:14:42 -0400 Subject: [PATCH 02/17] Fix .gitignore --- .gitignore => miller/.gitignore | 0 miller/Cargo.lock | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) rename .gitignore => miller/.gitignore (100%) diff --git a/.gitignore b/miller/.gitignore similarity index 100% rename from .gitignore rename to miller/.gitignore diff --git a/miller/Cargo.lock b/miller/Cargo.lock index 1916530..a6f648d 100644 --- a/miller/Cargo.lock +++ b/miller/Cargo.lock @@ -69,6 +69,7 @@ dependencies = [ "ron", "semver", "serde", + "strum", ] [[package]] @@ -116,6 +117,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "semver" version = "1.0.26" @@ -151,6 +158,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.100" -- 2.49.1 From 46c806e53727662faf2627dafd2f30b2abcc3851 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Mon, 31 Mar 2025 18:35:11 -0400 Subject: [PATCH 03/17] Update logfile.ron --- templates/logfile.ron | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/templates/logfile.ron b/templates/logfile.ron index ffc050e..a12964a 100644 --- a/templates/logfile.ron +++ b/templates/logfile.ron @@ -3,20 +3,21 @@ #![enable(unwrap_variant_newtypes)] [ - GameRecord( + Game( version: "0.3.0", periods: [ Period( start: First, end: Fourth, plays: [ + Kickoff(Nebraska), Play( - action: None, - down: Kickoff( - offence: Nebraska - ), + action: Unknown, + down: First, terrain: Yards(10) ), + Score(3), + Pat(Fail) ] ) ] -- 2.49.1 From f2a41be8d17d19f10aab3727a02ae6d32b225ae8 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Mon, 31 Mar 2025 18:39:05 -0400 Subject: [PATCH 04/17] Fix PAT notation and Touchdown notations. Backwards compatible w/ v3.0.0 if `implicit_some` enabled in RON log and no PAT specified within. --- gamelog/Cargo.lock | 2 +- gamelog/Cargo.toml | 2 +- gamelog/src/event.rs | 12 ++++++++++-- gamelog/src/file.rs | 6 ++++-- gamelog/src/lib.rs | 2 -- gamelog/src/play.rs | 17 ++++++++++++++--- gamelog/src/score.rs | 8 -------- miller/Cargo.lock | 2 +- 8 files changed, 31 insertions(+), 20 deletions(-) delete mode 100644 gamelog/src/score.rs diff --git a/gamelog/Cargo.lock b/gamelog/Cargo.lock index 93a3f7b..b7fd1f5 100644 --- a/gamelog/Cargo.lock +++ b/gamelog/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "gamelog" -version = "0.3.0" +version = "0.3.1" dependencies = [ "ron", "semver", diff --git a/gamelog/Cargo.toml b/gamelog/Cargo.toml index ac307c4..4b28b0b 100644 --- a/gamelog/Cargo.toml +++ b/gamelog/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamelog" -version = "0.3.0" +version = "0.3.1" edition = "2024" [dependencies] diff --git a/gamelog/src/event.rs b/gamelog/src/event.rs index bc385b8..8f0b1c5 100644 --- a/gamelog/src/event.rs +++ b/gamelog/src/event.rs @@ -1,4 +1,4 @@ -use crate::{Play, ScoreChange, Team, TerrainState}; +use crate::{Play, Team, TerrainState}; use serde::Deserialize; @@ -10,5 +10,13 @@ pub enum Event { Kickoff(Offence), Turnover(Offence), Penalty(TerrainState), - ScoreChange(ScoreChange), + Score(u8), + Pat(PatPoints), +} + +#[derive(Debug, Deserialize, Clone, PartialEq)] +pub enum PatPoints { + Fail, + One, + Two, } diff --git a/gamelog/src/file.rs b/gamelog/src/file.rs index 71d4334..48f5413 100644 --- a/gamelog/src/file.rs +++ b/gamelog/src/file.rs @@ -20,7 +20,7 @@ impl LogFile { /// Returns if the LogFile min version is compatible. pub fn is_compatible(&self) -> bool { - self.min_ver().cmp_precedence(&super::MIN_VER).is_lt() + self.min_ver().cmp_precedence(&crate::MIN_VER).is_lt() } } @@ -28,7 +28,9 @@ impl TryFrom for LogFile { type Error = ron::error::SpannedError; fn try_from(file: File) -> Result { - ron::de::from_reader(file) + ron::Options::default() + .with_default_extension(ron::extensions::Extensions::EXPLICIT_STRUCT_NAMES) + .from_reader(file) } } diff --git a/gamelog/src/lib.rs b/gamelog/src/lib.rs index 9dd1de2..1722a2c 100644 --- a/gamelog/src/lib.rs +++ b/gamelog/src/lib.rs @@ -7,7 +7,6 @@ mod file; mod game; mod period; mod play; -mod score; mod terrain; #[allow(unused)] @@ -19,5 +18,4 @@ pub use file::LogFile; pub use game::{Game, GameRecord}; pub use period::*; pub use play::*; -pub use score::ScoreChange; pub use terrain::TerrainState; diff --git a/gamelog/src/play.rs b/gamelog/src/play.rs index f1a4eee..c159a14 100644 --- a/gamelog/src/play.rs +++ b/gamelog/src/play.rs @@ -6,11 +6,17 @@ pub trait PlayHandle { fn plays(&self) -> Vec; } +pub trait Distance { + fn distance(&self) -> u8; + + fn delta(&self, d: D); +} + #[derive(Debug, Deserialize, Clone, Default, PartialEq)] pub struct Play { pub action: Action, - pub down: Down, - pub terrain: TerrainState, + pub down: Option, + pub terrain: Option, } impl PlayHandle for Play { @@ -19,6 +25,12 @@ impl PlayHandle for Play { } } +impl Distance for Play { + fn distance(&self) -> u8 {} + + fn delta(&self, d: D) {} +} + #[derive(Debug, Deserialize, Clone, Default, PartialEq)] pub enum Down { #[default] @@ -26,7 +38,6 @@ pub enum Down { Second, Third, Fourth, - PointAfterTouchdown(Option), } #[derive(Debug, Deserialize, Clone, PartialEq)] diff --git a/gamelog/src/score.rs b/gamelog/src/score.rs deleted file mode 100644 index 51d2d69..0000000 --- a/gamelog/src/score.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::Team; -use serde::Deserialize; - -#[derive(Debug, Deserialize, Clone, PartialEq)] -pub struct ScoreChange { - pub team: Team, - pub score: u8, -} diff --git a/miller/Cargo.lock b/miller/Cargo.lock index a6f648d..8b6e181 100644 --- a/miller/Cargo.lock +++ b/miller/Cargo.lock @@ -64,7 +64,7 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "gamelog" -version = "0.3.0" +version = "0.3.1" dependencies = [ "ron", "semver", -- 2.49.1 From 36d21440d8e8e1374eb8a0402d42698aa452f45e Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Mon, 31 Mar 2025 18:40:36 -0400 Subject: [PATCH 05/17] Update gamelog.ron --- gamelog.ron | 325 ++++++++++++++++++++++++---------------------------- 1 file changed, 150 insertions(+), 175 deletions(-) diff --git a/gamelog.ron b/gamelog.ron index f92dc86..54136b9 100644 --- a/gamelog.ron +++ b/gamelog.ron @@ -1,243 +1,224 @@ -#![enable(implicit_some)] -#![enable(unwrap_newtypes)] -#![enable(unwrap_variant_newtypes)] +#![enable(implicit_some),] +#![enable(unwrap_newtypes),] +#![enable(unwrap_variant_newtypes),] [ - GameRecord( - version: "0.2.0", + Game( + version: "0.3.0", periods: [ Period( start: First, end: Third, plays: [ + Kickoff(ArizonaState), Play( - action: None, - down: Kickoff( - offence: ArizonaState - ), - terrain: Distance(10) - ), - Play( - action: None, + action: Unknown, down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: None, + action: Unknown, down: Second, - terrain: 2 + terrain: Yards(2), ), Play( - action: ThrowLeft, + action: Unknown, // Throw Left down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: ThrowRight, + action: Unknown, // Throw Right down: Second, - terrain: 10 + terrain: Yards(10), ), Play( - action: None, + action: Unknown, down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: PlayAction ThrowRight, + action: Unknown, // PA Throw Right down: Second, - terrain: 2 + terrain: Yards(2), ), Play( - action: PlayAction RunRight, + action: Unknown, // PA Run Right down: Third, - terrain: 7 + terrain: Yards(7), ), Play( - action: HandoffRushLeft, + action: HalfbackSweep down: Fourth, - terrain: 11 + terrain: Yards(11), ), Play( - action: None, + action: Unknown, down: First, - terrain: 10 + terrain: Yards(10), ), - TURNOVER, + Turnover(TexasAnM), Play( - action: PlayAction ThrowRight, + action: Unknown, // PA Throw Right down: Second, - terrain: 11 + terrain: Yards(11), ), Play( - action: None, + action: Unknown, down: Third, - terrain: 9 + terrain: Yards(9), ), Play( - action: PassCentre, + action: Unknown, // Throw Centre down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: TossRunRight, + action: Unknown, // Throw Run Right down: Second, - terrain: 3 + terrain: Yards(3), ), Play( - action: FailedThrow, + action: Unknown, // Throw that failed miserably. down: First, - terrain: 10 + terrain: Yards(10), ), + Kickoff(TexasAnM), Play( - action: Kickoff( offence: TexasAnM ), - down: First, - terrain: 10 - ), - Play( - action: SpikeCentre, + action: Unknown, // PA Comebacks or Curls? Original note: Spike Centre down: Second, - terrain: 6 + terrain: Yards(6), ), Play( - action: None, + action: Unknown, // Halfback? Original note: None down: Third, - terrain: 13 + terrain: Yards(13), ), Play( - action: ThrowRight, - down: Fourth, - terrain: 13 - ), - TURNOVER - Play( - action: ShotgunDoubleFlex Throw, - down: Second, - terrain: 10 - ), - Play( - action: SpikeCentre, + action: Unknown, // HB Slip Screen? Original note: Throw Right down: First, - terrain: 10 + terrain: Yards(10), ), + Turnover(ArizonaState), Play( - action: Dupe SpikeCentre, - down: First, - terrain: 10 - ), - Play( - action: FailedThrow, + action: SlotOut, // Slot Out or PA Comebacks. Original note: ShotgunDoubleFlex Throw down: Second, - terrain: 10 + terrain: Yards(10), ), Play( - action: TransferRushRight, + action: PlayActionComebacks, // Curls or PA Comebacks. Original note: Spike Centre + down: First, + terrain: Yards(10), + ), + Turnover(TexasAnM), + Play( + action: PlayActionComebacks, // Original note: Dupe Spike Centre + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Failed Throw + down: Second, + terrain: Yards(10), + ), + Play( + action: PowerZero, // Power 0 or Crack Student Body RT. Original note: Transfer Rush Right down: Third, - terrain: 15 + terrain: Yards(15), ), ] ), - //Texas offence Period( start: Fourth, end: None, plays: [ Play( - action: None, + action: PlayActionComebacks, // Original note: Dupe Spike Centre down: Fourth, - terrain 17 + terrain: Yards(17), ), Play( - action: Punt, + action: Unknown, // Punt down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: ThrowLeft, + action: Unknown, // Original note: Throw Left down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: None, + action: Unknown, down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: ThrowCentre, + action: Unknown, // Original note: Throw Centre down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: Transfer RunLeft, + action: HalfbackSweep, // Original note: Transfer Run Left down: Second, - terrain: 12 + terrain: Yards(12), ), Play( - action: ThrowCentre, + action: Unknown, // Original note: Throw Centre down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: TransferRushRight, + action: PowerZero, // Original note: Transfer Run Right down: Second, - terrain: 8 + terrain: Yards(8), ), Play( - action: , + action: Unknown, down: First, - terrain: 10 - ), - Play( - action: Kickoff(offence: ArizonaState), - down: First, - terrain: 10 + terrain: Yards(10), ), + Kickoff(ArizonaState), ] - ) + ), ] ), - GameRecord( - version: "0.2.0", + Game( + version: "0.3.1", periods: [ Period( start: First, end: None, plays: [ + Kickoff(Syracuse), Play( - action: None - down: Kickoff(offence: Syracuse), - terrain: Distance(10) - - ), - Play( - action: None, + action: Unknown, down: Second, - terrain: 3 + terrain: Yards(3), ), Play( - action: SpikeCentre, + action: Unknown, // Original note: Spike Centre down: Third, - terrain: 3, + terrain: Yards(3), ), Play( action: Curls, down: Fourth, - terrain: 3 + terrain: Yards(3), ), Play( - action: Mesh || PassCentre, + action: Mesh, down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: None, + action: Unknown, down: Second, - terrain: 15 + terrain: Yards(15), ), Play( - action: PlayAction ThrowLeft, + action: Unknown, // Original note: PA Throw Left down: First, - terrain: 10 - ) + terrain: Yards(10), + ), ] ), Period( @@ -245,83 +226,83 @@ end: None, plays: [ Play( - action: PA ThrowCentre, + action: Unknown, // Original note: PA Throw Centre down: Second, - terrain: 10 + terrain: Yards(10), ), Play( - action: Transfer Rush Centre/Left, + action: HalfbackSlam, // Original note: Transfer Rush Centre/Left down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: Shotgun ThrowRight, + action: Unknown, // Original note: Shotgun Throw Right down: Second, - terrain: 8 + terrain: Yards(8), ), Play( - action: PlayAction RushRight, + action: PlayActionPowerZero, // Original note: PA Rush Right down: Third, - terrain: 1 + terrain: Yards(1), ), Play( - action: HB? Rush Centre/Right, + action: Unknown, // PowerZero or CSB RT? Original note: "HB? Rush Centre/Right" down: Fourth, - terrain: 6 + terrain: Yards(6), ), Play( - action: PlayAction RushCentre, - ), - //Turnover, Colorado Offence - Play( - action: ShotgunDoubleFlex PlayActionComebacks, //Big Throw Right - down: First, - terrain: 10 - ), - Play( - action: Transfer HB? RushCentre, - down: Second, - terrain: 9 - ), - Play( - action: FailedThrow, - down: Third, - terrain: 17 - ), - Play( - action: Same FailedThrow, + action: Unknown, // HalfbackSlam? Original note: PA Rush Centre + down: None, + terrain: None, ) + Turnover(Colorado), + Play( + action: PlayActionComebacks, + down: First, + terrain: Yards(10), + ), + Play( + action: HalfbackSlam, // Original note: "Transfer HB? Rush Centre" + down: Second, + terrain: Yards(9), + ), + Play( + action: Unknown, // Original note: Failed Throw + down: Third, + terrain: Yards(17), + ), + Play( + action: Unknown, // Original note: Same Failed Throw + down: Fourth, + terrain: Unknown, + ), ] ), Period( start: Third, end: None, plays: [ + Kickoff(Colorado), Play( - action: Kickoff(offence:Colorado), + action: Unknown, // Original note: Throw Right down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: ThrowRight, + action: Unknown, // Original note: ShotgunDoubleFlex Throw Right down: First, - terrain: 10 + terrain: Yards(10), ), Play( - action: ShotgunDoubleFlex ThrowRight, + action: Unknown, // Original note: Pass Run Centre down: First, - terrain: 10 - ), - Play( - action: Pass RushCentre, - down: First, - terrain: 10 + terrain: Yards(10), ), Play( action: PowerZero, down: Second, - terrain: 6 - ) + terrain: Yards(6), + ), ] ), Period( @@ -331,31 +312,25 @@ Play( action: CrackStudentBodyRightTackle, down: Third, - terrain: 11 + terrain: Yards(11), ), Play( - action: SpikeCentre, + action: Unknown, // Original note: Spike Centre down: Fourth, - terrain: 3 + terrain: Yards(3), ), + Score(0), + Kickoff(Syracuse), Play( - action: SpecialTeams... PuntIntoNet, - ), - Play( - action: Kickoff(offence: Syracuse), + action: Unknown, // Original note: PA Throw Centre down: First, - terrain: 10 - ), - Play( - action: PlayAction ThrowCentre, - down: First, - terrain: 10 + terrain: Yards(10), ), Play( action: SpeedOption, down: Second, - terrain: 3 - ) + terrain: Yards(3), + ), ] ) ] -- 2.49.1 From 79e309eb707fb906f8cefa6ea8c6f25f33e8a892 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Tue, 1 Apr 2025 12:46:39 -0400 Subject: [PATCH 06/17] Update gamelog.ron --- gamelog.ron | 428 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 425 insertions(+), 3 deletions(-) diff --git a/gamelog.ron b/gamelog.ron index 54136b9..433584f 100644 --- a/gamelog.ron +++ b/gamelog.ron @@ -1,6 +1,6 @@ -#![enable(implicit_some),] -#![enable(unwrap_newtypes),] -#![enable(unwrap_variant_newtypes),] +#![enable(implicit_some)] +#![enable(unwrap_newtypes)] +#![enable(unwrap_variant_newtypes)] [ Game( @@ -332,6 +332,428 @@ terrain: Yards(3), ), ] + ), + ] + ), + Game( + version: "0.3.1", + periods: [ + Period( + start: First, + end: None, + plays: [ + Kickoff(Nebraska), + Play( + action: Curls, + down: First, + terrain: Yards(10) + ) + Play( + action: Unknown, + down: Second, + terrain: Yards(7), + ), + Play( + action: Curls, + down: Third, + terrain: Yards(9), + ), + Play( + action: CrackStudentBodyRightTackle, + down: Fourth, + terrain: Yards(9), + ), + Play( + action: FleaFlicker, + down: None, + terrain: None, + ), + Turnover(SouthCarolina), + Play( + action: Unknown, // Original note: Throw Right Dash + down: First, + terrain: Yards(10), + ), + ] + ), + Period( + start: Second, + end: None, + plays: [ + Play( + action: Unknown, // Original note: Throw Centre + down: First, + terrain: GoalLine, + ), + Pat(Fail), + Score(6), + Kickoff(Nebraska), + Play( + action: StrongFlood, + down: First, + terrain: Yards(10), + ), + Play( + action: HalfbackSweep, + down: Second, + terrain: Yards(15), + ), + Play( + action: SlantBubble, + down: Third, + terrain: Yards(4), + ), + Play( + action: Unknown, // Original note: Halfback Slant Dummy + down: Fourth, + terrain: Yards(6), + ), + Play( + action: SlantBubble, + down: None, + terrain: None, + ), + Turnover(SouthCarolina), + Play( + action: Unknown, + down: First, + terrain: Yards(10) + ), + Play( + action: Unknown, + down: Second, + terrain: Yards(10) + ), + ] + ), + Period( + start: Third, + end: None, + plays: [ + Kickoff(SouthCarolina), + Play( + action: Unknown, + down: Second, + terrain: Yards(18), + ), + Play( + action: Unknown, + down: Third, + terrain: Yards(14), + ), + Play( + action: Unknown, // Original note: Spike Centre + down: Fourth, + terrain: Yards(16), + ), + Turnover(Nebraska), + Play( + action: Unknown, // Original note: Fake Field Goal 44yrds + down: None, + terrain: None, + ), + Score(3), + Kickoff(SouthCarolina), + Play( + action: Unknown, + down: Second, + terrain: Yards(3), + ), + Play( + action: Unknown, + down: Third, + terrain: Yards(3), + ), + ] + ), + Period( + start: Fourth, + end: None, + plays: [ + Play( + action: Unknown, // Original note: Throw Centre + down: Fourth, + terrain: Yards(3), + ), + Play( + action: Unknown, // Original note: Throw, Run Left + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Throw Left + down: Second, + terrain: Yards(10), + ), + Play( + action: Unknown, + down: Third, + terrain: Yards(5), + ), + Pat(), // Original note: Dupe, Throw Centre + Score(12), + Kickoff(Nebraska), + Play( + action: PowerZero, + down: First, + terrain: Yards(10), + ), + Play( + action: StrongFlood, + down: First, + terrain: Yards(10), + ), + Play( + action: PlayActionComebacks, + down: Second, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Punt + down: First, + terrain: Yards(10), + ), + Turnover(SouthCarolina), + Play( + action: Unknown, + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, + down: Second, + terrain: Yards(10), + ), + Play( + action: Unknown, + down: Third, + terrain: Yards(10), + ), + Play( + action: Unknown, + down: Fourth, + terrain: Yards(6), + ), + Turnover(Nebraska), + // Field Goal 41 yrds + Score(6), + Kickoff(SouthCarolina), + Play( + action: Unknown, + down: Second, + terrain: Yards(18), + ) + ] + ), + ] + ), + Game( + version: "0.4.0", + periods: [ + Period( + start: First, + end: None, + events: [ + Kickoff(Colorado), + Play( + action: Unknown, + down: Second, + terrain: Yards(3), + ), + Play( + action: Unknown, // Original note: Pass, Dash Centre + down: Third, + terrain: Yards(3), + ), + Play( + action: Unknown, // Original note: Throw, Dash Right + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Dash Centre + down: Second, + terrain: Yards(12), + ), + Play( + action: Unknown, + down: First, + terrain: Yards(10), + ), + ] + ), + Period( + start: Second, + end: None, + events: [ + Play( + action: Unknown, // Original note: Pass Back, Dash + down: Second, + terrain: Yards(15), + ), + Play( // Golden Play + action: Unknown, // Original note: Throw, Dash Right + down: Third, + terrain: Yards(2), + ), + Play( + action: Unknown, // Original note: Same as last. + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Pass Back, Dash Right + down: Second, + terrain: Yards(7), + ), + Play( + action: Unknown, // Original note: Throw Right; Went Out + down: Third, + terrain: Yards(7), + ), + Play( + action: Unknown, // HB? Original note: Walk Back Throw Centre, + down: First, + terrain: GoalLine, + ), + // Touchdown + Pat(One), // Throw + Score(7), + Kickoff(Iowa), + Penalty(Yards(15)), + Play( + action: Unknown, // Original note: PA Throw Right and Dash + down: Second, + terrain: Yards(6), + ), + Play( + action: Unknown, // Original note: Throw Right + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, + down: Second, + terrain: Yards(1), + ), + Play( + action: Unknown, // Original note: Spike Left + down: Third, + terrain: Yards(1), + ), + Play( + action: Unknown, // Original note: PA Throw Right and Dash + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Throw Right + down: First, + terrain: GoalLine, + ), + Score(3), + ] + ), + Period( + start: Third, + end: None, + events: [ + Kickoff(Iowa), + Play( + action: Unknown, // HB? Original note: Run Back, Throw Centre, Run Centre + down: Second, + terrain: Yards(4), + ), + Play( + action: Unknown, // PA? Original note: Dupe, Throw Right + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, // Orignal note: Run Left + down: Second, + terrain: Yards(13), + ), + Play( + action: Unknown, // PA? Original note: Dupe, Throw Centre, Run Centre + down: First, + terrain: Yards(10), + ), + Play( + action: Unknown, // Orignal note: Throw Right + down: Second, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Throw Left + down: Third, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Dupe, Throw Centre, Run Centre + down: First, + terrain: Yards(10), + ), + ] + ), + Period( + start: Fourth, + end: None, + events: [ + Play( + action: Unknown, // Original note: Throw Left, Run Left + down: Second, + terrain: Yards(8), + ), + Play( + action: Unknown, // Original note: Dupe, Throw Centre, Run Centre + down: Third, + terrain: Yards(4), + ), + // Touchdown + Pat(One) + Score(7), + Kickoff(Colorado), + Play( + action: Unknown, // Original note: Dupe, Throw Left + down: Second, + terrain: Yards(10), + ), + Play( + action: Unknown, // Original note: Throw Centre, Run Centre + down: Third, + terrain: Yards(1), + ), + Play( + action: Unknown, // Original note: Run Left + down: Fourth, + terrain: Yards(4) + ), + Play( + action: Unknown, // Original note: Dupe, Run Right, + down: None, + terrain: None, + ), + Turnover(Iowa), + Penalty(Yards(15)), + Play( + action: Unknown, // Original note: Run Left + down: Second, + terrain: Yards(11), + ), + Play( + action: Unknown, // Original note: Pass Back, Run Left + down: Third, + terrain: Yards(15), + ), + //Field Goal + Score(10) + ] ) ] ) -- 2.49.1 From 6942453f4fba7d7bcc0d9e18acc38e73839be9dd Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Tue, 1 Apr 2025 14:29:26 -0400 Subject: [PATCH 07/17] Correct minimum version req. --- gamelog/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamelog/src/lib.rs b/gamelog/src/lib.rs index 1722a2c..6e88c0f 100644 --- a/gamelog/src/lib.rs +++ b/gamelog/src/lib.rs @@ -10,7 +10,7 @@ mod play; mod terrain; #[allow(unused)] -pub const MIN_VER: semver::Version = semver::Version::new(0, 3, 0); +pub const MIN_VER: semver::Version = semver::Version::new(0, 4, 0); pub use action::*; pub use event::Event; -- 2.49.1 From cb5fc6117a4a86c8a16d19e6e757082b3cbebc31 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Tue, 1 Apr 2025 14:37:54 -0400 Subject: [PATCH 08/17] Update to gamelog v0.5.0 --- gamelog/Cargo.lock | 2 +- gamelog/Cargo.toml | 2 +- gamelog/src/event.rs | 29 +++++++++++++++++++++++------ gamelog/src/lib.rs | 2 +- gamelog/src/period.rs | 4 ++-- gamelog/src/play.rs | 8 ++++++-- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/gamelog/Cargo.lock b/gamelog/Cargo.lock index b7fd1f5..b1bfb5c 100644 --- a/gamelog/Cargo.lock +++ b/gamelog/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "gamelog" -version = "0.3.1" +version = "0.5.0" dependencies = [ "ron", "semver", diff --git a/gamelog/Cargo.toml b/gamelog/Cargo.toml index 4b28b0b..9bc0a05 100644 --- a/gamelog/Cargo.toml +++ b/gamelog/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gamelog" -version = "0.3.1" +version = "0.5.0" edition = "2024" [dependencies] diff --git a/gamelog/src/event.rs b/gamelog/src/event.rs index 8f0b1c5..1fa3628 100644 --- a/gamelog/src/event.rs +++ b/gamelog/src/event.rs @@ -10,13 +10,30 @@ pub enum Event { Kickoff(Offence), Turnover(Offence), Penalty(TerrainState), - Score(u8), - Pat(PatPoints), + Score(ScorePoints), } #[derive(Debug, Deserialize, Clone, PartialEq)] -pub enum PatPoints { - Fail, - One, - Two, +pub enum ScorePoints { + Touchdown, + FieldGoal, + Safety, + PatFail, + PatTouchdown, + PatFieldGoal, + PatSafety, +} + +impl ScorePoints { + pub fn to_points(&self) -> u8 { + match &self { + Self::Touchdown => 6, + Self::FieldGoal => 3, + Self::Safety => 2, + Self::PatFail => 0, + Self::PatTouchdown => 2, + Self::PatFieldGoal => 1, + Self::PatSafety => 1, + } + } } diff --git a/gamelog/src/lib.rs b/gamelog/src/lib.rs index 6e88c0f..007edd5 100644 --- a/gamelog/src/lib.rs +++ b/gamelog/src/lib.rs @@ -10,7 +10,7 @@ mod play; mod terrain; #[allow(unused)] -pub const MIN_VER: semver::Version = semver::Version::new(0, 4, 0); +pub const MIN_VER: semver::Version = semver::Version::new(0, 5, 0); pub use action::*; pub use event::Event; diff --git a/gamelog/src/period.rs b/gamelog/src/period.rs index f06e4bd..3099b84 100644 --- a/gamelog/src/period.rs +++ b/gamelog/src/period.rs @@ -1,4 +1,4 @@ -use crate::{Event, PlayHandle}; +use crate::{Event, Play, PlayHandle}; use serde::Deserialize; #[derive(Debug, Deserialize, Clone)] @@ -9,7 +9,7 @@ pub struct Period { } impl PlayHandle for Period { - fn plays(&self) -> Vec { + fn plays(&self) -> Vec { self.events .iter() .filter_map(|event| { diff --git a/gamelog/src/play.rs b/gamelog/src/play.rs index c159a14..0a4d8ec 100644 --- a/gamelog/src/play.rs +++ b/gamelog/src/play.rs @@ -26,9 +26,13 @@ impl PlayHandle for Play { } impl Distance for Play { - fn distance(&self) -> u8 {} + fn distance(&self) -> u8 { + todo!() + } - fn delta(&self, d: D) {} + fn delta(&self, d: D) { + todo!() + } } #[derive(Debug, Deserialize, Clone, Default, PartialEq)] -- 2.49.1 From bf5af561f0665edbd8d1358a5f3fcd75c8423406 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Wed, 2 Apr 2025 11:17:22 -0400 Subject: [PATCH 09/17] Deprecate TerrainState::Distance --- gamelog/src/terrain.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/gamelog/src/terrain.rs b/gamelog/src/terrain.rs index 1fc8176..fd1ded6 100644 --- a/gamelog/src/terrain.rs +++ b/gamelog/src/terrain.rs @@ -2,8 +2,6 @@ use serde::Deserialize; #[derive(Debug, Deserialize, Clone, Default, PartialEq)] pub enum TerrainState { - #[deprecated(since = "0.2.0", note = "Replaced in favour of TerrainState::Yards")] - Distance(u8), Yards(u8), GoalLine, Inches, -- 2.49.1 From a9bc830e2151f9760f43e7eebf71cb23314c6484 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Wed, 2 Apr 2025 19:27:27 -0400 Subject: [PATCH 10/17] Add delta fetching method to events. --- gamelog/src/event.rs | 53 ++++++++++++++++++++++++++++++++++++++++++-- gamelog/src/play.rs | 22 +++++++----------- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/gamelog/src/event.rs b/gamelog/src/event.rs index 1fa3628..ffc0058 100644 --- a/gamelog/src/event.rs +++ b/gamelog/src/event.rs @@ -1,5 +1,4 @@ -use crate::{Play, Team, TerrainState}; - +use crate::{Down, Play, Team, TerrainState, error}; use serde::Deserialize; type Offence = Team; @@ -13,6 +12,56 @@ pub enum Event { Score(ScorePoints), } +impl Event { + pub fn delta(&self, following: &Self) -> Option { + // Clean this trash spaghetti code up. + + fn make_play(event: &Event) -> Option { + match event { + Event::Kickoff(_) => Some(Play::default()), + Event::Play(play) => { + let p = play.to_owned(); + + if p.down.is_none() + || p.terrain.is_none() + || p.terrain.as_ref()? == &TerrainState::Unknown + { + None + } else { + Some(p) + } + } + _ => None, + } + } + + let preceeding = make_play(self)?; + let following = make_play(following)?; + + if following.down? == Down::First { + if let TerrainState::Yards(yrds) = preceeding.terrain? { + Some(yrds as i8) + } else { + None + } + } else { + let a = if let TerrainState::Yards(yrds) = preceeding.terrain? { + yrds + } else { + unreachable!() + }; + + let b = if let TerrainState::Yards(yrds) = following.terrain? { + yrds + } else { + unreachable!() + }; + + Some((a - b) as i8) + } + } +} + #[derive(Debug, Deserialize, Clone, PartialEq)] pub enum ScorePoints { Touchdown, diff --git a/gamelog/src/play.rs b/gamelog/src/play.rs index 0a4d8ec..0f1610d 100644 --- a/gamelog/src/play.rs +++ b/gamelog/src/play.rs @@ -6,13 +6,7 @@ pub trait PlayHandle { fn plays(&self) -> Vec; } -pub trait Distance { - fn distance(&self) -> u8; - - fn delta(&self, d: D); -} - -#[derive(Debug, Deserialize, Clone, Default, PartialEq)] +#[derive(Debug, Deserialize, Clone, PartialEq)] pub struct Play { pub action: Action, pub down: Option, @@ -25,13 +19,13 @@ impl PlayHandle for Play { } } -impl Distance for Play { - fn distance(&self) -> u8 { - todo!() - } - - fn delta(&self, d: D) { - todo!() +impl Default for Play { + fn default() -> Self { + Self { + action: Action::default(), + down: Some(Down::First), + terrain: Some(TerrainState::Yards(10)), + } } } -- 2.49.1 From 145cb37f9667222439d13e6f07879406c0f0884e Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Fri, 4 Apr 2025 09:46:25 -0400 Subject: [PATCH 11/17] Update gamelog.ron --- gamelog.ron | 339 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) diff --git a/gamelog.ron b/gamelog.ron index 433584f..7197cb0 100644 --- a/gamelog.ron +++ b/gamelog.ron @@ -756,5 +756,344 @@ ] ) ] + ), + Game( + version: "0.5.0", + periods: [ + Period( + start: First, + end: None, + events: [ + Kickoff(Nebraska), + Play( + action: CrackStudentBodyRightTackle, + down:Second, + terrain:Yards(13) + ), + Play( + action:Mesh, + down:Third, + terrain:Yards(14) + ), + Play( + action:StrongFlood, + down:Fourth, + terrain:Yards(14) + ), + Play( + action: PlayActionComebacks, + down:First, + terrain:Yards(10) + ), + Play( + action:SlotOut, + down:Second, + terrain:Yards(1) + ) + ] + ), + Period( + start:Second, + end:None, + events:[ + Play( + action:PlayActionComebacks, + down: First, + terrain:Yards(10) + ), + Play( + action:SpeedOption, + down:Second, + terrain:Yards(7) + ), + Play( + action:SlotOut, + down:First, + terrain:Yards(10) + ), + Play( + action:PlayActionComebacks, + down:First, + terrain:Yards(10) + ), + Play( + action:StrongFlood, + down:First, + terrain:GoalLine + ), + Play( + action:StrongFlood, + down:Second, + terrain:GoalLine + ), + Play( + action:HalfbackSlipScreen, + down:Third, + terrain: GoalLine + ), + Play( + action:PlayActionComebacks, + down:None, + terrain:None, + ), + Score(Touchdown), + Score(PatFail), + Kickoff(Iowa), + Play( + action: Unknown, + down:Second, + terrain:Yards(10) + ), + Play( + action: Unknown, + down:Third, + terrain:Yards(10) + ), + Play( + action: Unknown, //Shotgun double flex Shoot Centre + down:Fourth, + terrain:Yards(10) + ), + Turnover(Nebraska), + Play( + action:SlantBubble, + down: First, + terrain: GoalLine, + ) + ] + ), + Period( + start:Third, + end: Fourth, + events: [ + Kickoff(Iowa), + Play( + action:Unknown, + down: Second, + terrain:Yards(4) + ), + Play( + action:Unknown, //PA? + down: First, + terrain:Yards(10), + ), + Play( + action: //IForm Normal, Thrown + down: None, + terrain: None + ), + Score(Touchdown), + Score(PatSafety), + Kickoff(Nebraska), + Play( + action: PlayActionBoot, + down: First, + terrain: Yards(10) + ), + Play( + action: SlantBubble, + down: First, + terrain: Yards(10) + ), + Play( + action: FleaFlicker, + down: Second, + terrain: Yards(10) + ), + Play( + action:PlayActionComebacks, + down: Third, + terrain: Yards(10) + ), + Play( + action: SlotOut, + down: First, + terrain: GoalLine + ), + Play( + action: HalfbackSlipScreen, + down: Second, + terrain: GoalLine, + ), + Play( + action: SpeedOption, + down: Third, + terrain: GoalLine, + ), + Play( + action: StrongFlood, + down: Fourth, + terrain: GoalLine + ), + Play( + action: Curls, + down: None, + terrain: None + ), + Turnover(Iowa), + Play( + action: Unknown, + down: Second, + terrain: Yards(10) + ), + Play( + action: Unknown, + down: Third, + terrain: Yards(10) + ), + Play( + action: Unknown,// Run + down: Fourth, + terrain: Yards(7) + ), + Play( + action:Unknown, + down: First, + terrain: Yards(10) + ), + Turnover(Nebraska), + Play( + action:SlantBubble, + down: Second, + terrain: Yards(2) + ), + Play( + action: PlayActionPowerZero, + down: Third, + terrain: Yards(2) + ), + Play( + action: CrackStudentBodyRightTackle, + down: First, + terrain: Yards(10) + ), + Play( + action: StrongFlood, + down: Second, + terrain: Yards(10) + ), + Play( + action: PlayActionComebacks, + down: None, + terrain: None + ) + ] + ) + ] + ), + Game( + // TexasAnM were opponents, but not recorded as + // they were not present; Miller played in place. + version: "0.5.0", + periods: [ + Period( + start: First, + end: None, + events: [ + Turnover(SouthCarolina), + Play( + action: FleaFlicker, + down: None, + terrain: None + ), + Turnover(TexasAnM) + ] + ), + Period( + start: Second, + end: None, + events:[ + Turnover(SouthCarolina), + Play( + action: SpeedOption, + down: Second, + terrain: Yards(9) + ), + Play( + action: Unknown, + down: None, + terrain: None, + ), + Turnover(TexasAnM), + Score(FieldGoal) + ] + ), + Period( + start: Third, + end: Fourth, + events: [ + Kickoff(SouthCarolina), + Play( + action: Unknown, // Rush + down: Second, + terrain: Yards(7) + ), + Play( + action: Unknown, + down: Third, + terrain: Yards(16) + ), + Play( + action: HalfbackSweep, + down: Fourth, + terrain: Yards(17) + ), + Play( + action: Unknown, + down: None, + terrain: None, + ), + Turnover(TexasAnM), + Score(Touchdown), + Score(PatSafety), + Kickoff(SouthCarolina), + Play( + action: HalfbackSweep, + down: Second, + terrain: Yards(13) + ), + Play( + action: First, + down: First, + terrain: Yards(10) + ), + Play( + action: Unknown,//DoubleFlex Throw + down: Second, + terrain: Yards(10) + ), + Play( + action: Unknown, + down: Third, + terrain: Yards(10) + ), + Play( + action: Unknown, + down: Fourth, + terrain: Yards(10) + ), + Play( + action: Unknown, // Rush + down: None, + terrain: None + ), + Turnover(TexasAnM), + Score(Touchdown), + Score(PatSafety), + Kickoff(SouthCarolina), + Play( + action: Unknown, + down: Second, + terrain: Yards(13) + ), + Play( + action: HalfbackSweep, + down: None, + terrain: None, + ) + //Texas 17, SouthCarolina 0 + ] + ) + ] ) ] -- 2.49.1 From a780d20ee991c78d46e2de56e689359435b4ca91 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Fri, 4 Apr 2025 16:46:30 -0400 Subject: [PATCH 12/17] Progress push. --- gamelog/src/event.rs | 2 +- gamelog/src/game.rs | 6 ++++++ miller/Cargo.lock | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/gamelog/src/event.rs b/gamelog/src/event.rs index ffc0058..706399a 100644 --- a/gamelog/src/event.rs +++ b/gamelog/src/event.rs @@ -1,4 +1,4 @@ -use crate::{Down, Play, Team, TerrainState, error}; +use crate::{Down, Play, Team, TerrainState}; use serde::Deserialize; type Offence = Team; diff --git a/gamelog/src/game.rs b/gamelog/src/game.rs index 2c99e3f..be625a5 100644 --- a/gamelog/src/game.rs +++ b/gamelog/src/game.rs @@ -7,6 +7,7 @@ pub type GameRecord = Game; #[derive(Debug, Deserialize, Clone)] pub struct Game { pub version: semver::Version, + pub flags: Vec, pub periods: Vec, } @@ -42,3 +43,8 @@ impl PlayHandle for Game { .concat() } } + +#[derive(Debug, Deserialize, Clone)] +pub enum FeatureFlags { + Ignore(Team), +} diff --git a/miller/Cargo.lock b/miller/Cargo.lock index 8b6e181..3d598a4 100644 --- a/miller/Cargo.lock +++ b/miller/Cargo.lock @@ -64,7 +64,7 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "gamelog" -version = "0.3.1" +version = "0.5.0" dependencies = [ "ron", "semver", -- 2.49.1 From 589dbd55d008d0de26b19505986f34cfa01ec9d2 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Fri, 4 Apr 2025 18:50:57 -0400 Subject: [PATCH 13/17] Implemented delta-ing methods w/ unit tests. --- gamelog/src/event.rs | 75 +++++++++++++++++++++++++++++++++++++++++-- gamelog/src/game.rs | 8 +++++ gamelog/src/period.rs | 65 +++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 2 deletions(-) diff --git a/gamelog/src/event.rs b/gamelog/src/event.rs index 706399a..e2d8fa0 100644 --- a/gamelog/src/event.rs +++ b/gamelog/src/event.rs @@ -57,13 +57,14 @@ impl Event { unreachable!() }; - Some((a - b) as i8) + Some(a as i8 - b as i8) } } } -#[derive(Debug, Deserialize, Clone, PartialEq)] +#[derive(Debug, Deserialize, Clone, PartialEq, Default)] pub enum ScorePoints { + #[default] Touchdown, FieldGoal, Safety, @@ -86,3 +87,73 @@ impl ScorePoints { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Action, Down, Team, TerrainState}; + + #[test] + fn delta() { + let kickoff = Event::Kickoff(Team::Nebraska); + let first_down = Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::Yards(10)), + }); + let second_down = Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Second), + terrain: Some(TerrainState::Yards(10)), + }); + let third_down = Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Third), + terrain: Some(TerrainState::Yards(13)), + }); + let fourth_down = Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Fourth), + terrain: Some(TerrainState::Yards(5)), + }); + let penalty = Event::Penalty(TerrainState::Yards(15)); + let turnover = Event::Turnover(Team::Nebraska); + let noned_down = Event::Play(Play { + action: Action::Unknown, + down: None, + terrain: None, + }); + let score = Event::Score(ScorePoints::default()); + let goal_line = Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::GoalLine), + }); + let inches = Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::Inches), + }); + + 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!(10_i8 == first_down.delta(&kickoff).unwrap()); + assert!(10_i8 == first_down.delta(&goal_line).unwrap()); + assert!(10_i8 == first_down.delta(&inches).unwrap()); + assert!(13_i8 == third_down.delta(&kickoff).unwrap()); + assert!(8_i8 == third_down.delta(&fourth_down).unwrap()); + assert!(13_i8 == third_down.delta(&goal_line).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)); + } +} diff --git a/gamelog/src/game.rs b/gamelog/src/game.rs index be625a5..d99ccd8 100644 --- a/gamelog/src/game.rs +++ b/gamelog/src/game.rs @@ -32,6 +32,14 @@ impl Game { Err(error::TeamsError::NumberFound(teams.len())) } } + + pub fn deltas(&self) -> Vec { + self.periods + .iter() + .map(|period| period.deltas()) + .collect::>>() + .concat() + } } impl PlayHandle for Game { diff --git a/gamelog/src/period.rs b/gamelog/src/period.rs index 3099b84..5412afd 100644 --- a/gamelog/src/period.rs +++ b/gamelog/src/period.rs @@ -23,6 +23,24 @@ impl PlayHandle for Period { } } +impl Period { + pub fn deltas(&self) -> Vec { + let len = self.events.len() - 1; + let mut idx: usize = 0; + let mut deltas: Vec = vec![]; + + while idx < len { + if let Some(value) = self.events[idx].delta(&self.events[idx + 1]) { + deltas.push(value); + } + + idx += 1 + } + + deltas + } +} + #[derive(Debug, Deserialize, Clone, PartialEq)] pub enum Quarter { First, @@ -31,3 +49,50 @@ pub enum Quarter { Fourth, Overtime(u8), } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Action, Down, Team, TerrainState}; + + #[test] + fn deltas() { + let period = Period { + start: Quarter::First, + end: None, + events: vec![ + Event::Kickoff(Team::Nebraska), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::Yards(10)), + }), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Second), + terrain: Some(TerrainState::Yards(13)), + }), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Third), + terrain: Some(TerrainState::Yards(8)), + }), + Event::Turnover(Team::Nebraska), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::Yards(10)), + }), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::Yards(10)), + }), + ], + }; + + let expected: Vec = vec![10, -3, 5, 10]; + + assert!(period.deltas() == expected) + } +} -- 2.49.1 From e72cdbf4b79d9cc07e526a6100c69114ef9ede3d Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Sat, 5 Apr 2025 16:21:31 -0400 Subject: [PATCH 14/17] Implement PPQ Calculation [per team per game] and housekeeping. --- gamelog.ron | 69 ++++---- gamelog/src/error.rs | 18 ++ gamelog/src/event.rs | 38 ++++- gamelog/src/file.rs | 2 +- gamelog/src/game.rs | 352 ++++++++++++++++++++++++++++++++++++--- gamelog/src/lib.rs | 12 +- gamelog/src/period.rs | 286 ++++++++++++++++++++++++++----- gamelog/src/play.rs | 11 -- miller/Cargo.toml | 1 - miller/src/calculator.rs | 0 miller/src/main.rs | 28 +++- templates/logfile.ron | 8 +- 12 files changed, 688 insertions(+), 137 deletions(-) delete mode 100644 miller/src/calculator.rs diff --git a/gamelog.ron b/gamelog.ron index 7197cb0..9d8e492 100644 --- a/gamelog.ron +++ b/gamelog.ron @@ -4,12 +4,13 @@ [ Game( - version: "0.3.0", + version: "0.5.0", + flags: [IgnoreScore], periods: [ Period( start: First, end: Third, - plays: [ + events: [ Kickoff(ArizonaState), Play( action: Unknown, @@ -47,7 +48,7 @@ terrain: Yards(7), ), Play( - action: HalfbackSweep + action: HalfbackSweep, down: Fourth, terrain: Yards(11), ), @@ -130,7 +131,7 @@ Period( start: Fourth, end: None, - plays: [ + events: [ Play( action: PlayActionComebacks, // Original note: Dupe Spike Centre down: Fourth, @@ -182,12 +183,13 @@ ] ), Game( - version: "0.3.1", + version: "0.5.0", + flags: [IgnoreScore], periods: [ Period( start: First, end: None, - plays: [ + events: [ Kickoff(Syracuse), Play( action: Unknown, @@ -224,7 +226,7 @@ Period( start: Second, end: None, - plays: [ + events: [ Play( action: Unknown, // Original note: PA Throw Centre down: Second, @@ -254,7 +256,7 @@ action: Unknown, // HalfbackSlam? Original note: PA Rush Centre down: None, terrain: None, - ) + ), Turnover(Colorado), Play( action: PlayActionComebacks, @@ -281,7 +283,7 @@ Period( start: Third, end: None, - plays: [ + events: [ Kickoff(Colorado), Play( action: Unknown, // Original note: Throw Right @@ -308,7 +310,7 @@ Period( start: Fourth, end: None, - plays: [ + events: [ Play( action: CrackStudentBodyRightTackle, down: Third, @@ -319,7 +321,6 @@ down: Fourth, terrain: Yards(3), ), - Score(0), Kickoff(Syracuse), Play( action: Unknown, // Original note: PA Throw Centre @@ -336,18 +337,19 @@ ] ), Game( - version: "0.3.1", + version: "0.5.0", + flags: [], periods: [ Period( start: First, end: None, - plays: [ + events: [ Kickoff(Nebraska), Play( action: Curls, down: First, terrain: Yards(10) - ) + ), Play( action: Unknown, down: Second, @@ -379,14 +381,14 @@ Period( start: Second, end: None, - plays: [ + events: [ Play( action: Unknown, // Original note: Throw Centre down: First, terrain: GoalLine, ), - Pat(Fail), - Score(6), + Score(Touchdown), + Score(PatFail), Kickoff(Nebraska), Play( action: StrongFlood, @@ -429,7 +431,7 @@ Period( start: Third, end: None, - plays: [ + events: [ Kickoff(SouthCarolina), Play( action: Unknown, @@ -452,7 +454,7 @@ down: None, terrain: None, ), - Score(3), + Score(FieldGoal), Kickoff(SouthCarolina), Play( action: Unknown, @@ -469,7 +471,7 @@ Period( start: Fourth, end: None, - plays: [ + events: [ Play( action: Unknown, // Original note: Throw Centre down: Fourth, @@ -490,8 +492,8 @@ down: Third, terrain: Yards(5), ), - Pat(), // Original note: Dupe, Throw Centre - Score(12), + Score(Touchdown), + Score(PatFail), Kickoff(Nebraska), Play( action: PowerZero, @@ -536,7 +538,7 @@ ), Turnover(Nebraska), // Field Goal 41 yrds - Score(6), + Score(FieldGoal), Kickoff(SouthCarolina), Play( action: Unknown, @@ -548,7 +550,8 @@ ] ), Game( - version: "0.4.0", + version: "0.5.0", + flags: [], periods: [ Period( start: First, @@ -617,8 +620,8 @@ terrain: GoalLine, ), // Touchdown - Pat(One), // Throw - Score(7), + Score(Touchdown), + Score(PatSafety), Kickoff(Iowa), Penalty(Yards(15)), Play( @@ -656,7 +659,7 @@ down: First, terrain: GoalLine, ), - Score(3), + Score(FieldGoal), ] ), Period( @@ -716,8 +719,8 @@ terrain: Yards(4), ), // Touchdown - Pat(One) - Score(7), + Score(Touchdown), + Score(PatSafety), Kickoff(Colorado), Play( action: Unknown, // Original note: Dupe, Throw Left @@ -752,13 +755,14 @@ terrain: Yards(15), ), //Field Goal - Score(10) + Score(FieldGoal) ] ) ] ), Game( version: "0.5.0", + flags: [], periods: [ Period( start: First, @@ -878,7 +882,7 @@ terrain:Yards(10), ), Play( - action: //IForm Normal, Thrown + action: Unknown,//IForm Normal, Thrown down: None, terrain: None ), @@ -985,6 +989,7 @@ // TexasAnM were opponents, but not recorded as // they were not present; Miller played in place. version: "0.5.0", + flags: [IgnoreTeam(TexasAnM)], periods: [ Period( start: First, @@ -1053,7 +1058,7 @@ terrain: Yards(13) ), Play( - action: First, + action: Unknown, down: First, terrain: Yards(10) ), diff --git a/gamelog/src/error.rs b/gamelog/src/error.rs index c3e135f..709783d 100644 --- a/gamelog/src/error.rs +++ b/gamelog/src/error.rs @@ -27,3 +27,21 @@ impl fmt::Display for TeamsError { } } } + +#[derive(Debug)] +pub struct NoTeamAttribute; + +impl fmt::Display for NoTeamAttribute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Object has no team definition.") + } +} + +#[derive(Debug)] +pub struct CannotDetermineTeams; + +impl fmt::Display for CannotDetermineTeams { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Cannot determine teams present.") + } +} diff --git a/gamelog/src/event.rs b/gamelog/src/event.rs index e2d8fa0..622c59d 100644 --- a/gamelog/src/event.rs +++ b/gamelog/src/event.rs @@ -1,9 +1,9 @@ -use crate::{Down, Play, Team, TerrainState}; +use crate::{Down, Play, Team, TerrainState, error}; use serde::Deserialize; type Offence = Team; -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, PartialEq)] pub enum Event { Play(Play), Kickoff(Offence), @@ -18,7 +18,7 @@ impl Event { fn make_play(event: &Event) -> Option { match event { - Event::Kickoff(_) => Some(Play::default()), + Event::Kickoff(_) | Event::Turnover(_) => Some(Play::default()), Event::Play(play) => { let p = play.to_owned(); @@ -36,7 +36,13 @@ impl Event { } let preceeding = make_play(self)?; - let following = make_play(following)?; + let following = if let Event::Turnover(_) = following { + // I should really just early return + // but this is too funny to look at. + None? + } else { + make_play(following)? + }; if following.down? == Down::First { if let TerrainState::Yards(yrds) = preceeding.terrain? { @@ -60,6 +66,14 @@ impl Event { Some(a as i8 - b as i8) } } + + pub fn team(&self) -> Result { + match self { + Self::Kickoff(team) => Ok(team.to_owned()), + Self::Turnover(team) => Ok(team.to_owned()), + _ => Err(error::NoTeamAttribute), + } + } } #[derive(Debug, Deserialize, Clone, PartialEq, Default)] @@ -90,45 +104,54 @@ impl ScorePoints { #[cfg(test)] mod tests { - use super::*; - use crate::{Action, Down, Team, TerrainState}; + use crate::*; #[test] fn delta() { let kickoff = Event::Kickoff(Team::Nebraska); + let first_down = Event::Play(Play { action: Action::Unknown, down: Some(Down::First), terrain: Some(TerrainState::Yards(10)), }); + let second_down = Event::Play(Play { action: Action::Unknown, down: Some(Down::Second), terrain: Some(TerrainState::Yards(10)), }); + let third_down = Event::Play(Play { action: Action::Unknown, down: Some(Down::Third), terrain: Some(TerrainState::Yards(13)), }); + let fourth_down = Event::Play(Play { action: Action::Unknown, down: Some(Down::Fourth), terrain: Some(TerrainState::Yards(5)), }); + let penalty = Event::Penalty(TerrainState::Yards(15)); + let turnover = Event::Turnover(Team::Nebraska); + let noned_down = Event::Play(Play { action: Action::Unknown, down: None, terrain: None, }); + let score = Event::Score(ScorePoints::default()); + let goal_line = Event::Play(Play { action: Action::Unknown, down: Some(Down::First), terrain: Some(TerrainState::GoalLine), }); + let inches = Event::Play(Play { action: Action::Unknown, down: Some(Down::First), @@ -155,5 +178,8 @@ mod tests { assert!(None == goal_line.delta(&first_down)); assert!(None == inches.delta(&first_down)); assert!(None == goal_line.delta(&inches)); + assert!(10_i8 == turnover.delta(&first_down).unwrap()); + assert!(0_i8 == turnover.delta(&second_down).unwrap()); + assert!(-3_i8 == turnover.delta(&third_down).unwrap()); } } diff --git a/gamelog/src/file.rs b/gamelog/src/file.rs index 48f5413..b21a035 100644 --- a/gamelog/src/file.rs +++ b/gamelog/src/file.rs @@ -3,7 +3,7 @@ use serde::Deserialize; use std::{fs::File, path::PathBuf}; #[derive(Debug, Deserialize, Clone)] -pub struct LogFile(Vec); +pub struct LogFile(pub Vec); impl LogFile { pub fn min_ver(&self) -> semver::Version { diff --git a/gamelog/src/game.rs b/gamelog/src/game.rs index d99ccd8..2be7546 100644 --- a/gamelog/src/game.rs +++ b/gamelog/src/game.rs @@ -1,58 +1,356 @@ -use crate::{Event, Period, Play, PlayHandle, Team, error}; +use crate::{Event, Period, Team, error}; use serde::Deserialize; -#[deprecated(since = "0.2.0", note = "Migrated to Game")] -pub type GameRecord = Game; - #[derive(Debug, Deserialize, Clone)] pub struct Game { pub version: semver::Version, - pub flags: Vec, + pub flags: Vec, pub periods: Vec, } impl Game { /// Returns the teams of this game. pub fn teams(&self) -> Result, error::TeamsError> { + let ignore: Vec = self + .flags + .iter() + .filter_map(|flag| { + if let Flags::IgnoreTeam(team) = flag { + Some(team.to_owned()) + } else { + None + } + }) + .collect(); + let mut teams = vec![]; self.periods.iter().for_each(|period| { - period.events.iter().for_each(|event| { - if let Event::Kickoff(t) | Event::Turnover(t) = event { - if teams.contains(t) { - teams.push(t.to_owned()) + for event in period.events.iter() { + if let Ok(team) = event.team() { + if !ignore.contains(&team) && !teams.contains(&team) { + teams.push(team) } } - }) + } }); - if teams.len() == 2 { + if teams.len() == 2 || ignore.len() != 0 { Ok(teams) } else { Err(error::TeamsError::NumberFound(teams.len())) } } - pub fn deltas(&self) -> Vec { - self.periods + pub fn deltas(&self, team: Team) -> Vec { + let events = self + .periods .iter() - .map(|period| period.deltas()) - .collect::>>() - .concat() + .filter_map(|period| Some(period.team_events(team.to_owned(), None).ok().unwrap())) + .collect::>>() + .concat(); + let len = events.len() - 1; + 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); + } + + idx += 1 + } + + 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 + .iter() + .filter_map(|period| { + if !period.is_overtime() { + let plays = period.team_plays(team.to_owned(), None); + Some(plays.unwrap().len()) + } else { + None + } + }) + .collect::>(); + + let mut summation = 0_usize; + + quarterly_plays.iter().for_each(|value| summation += value); + + summation } } -impl PlayHandle for Game { - fn plays(&self) -> Vec { - self.periods - .iter() - .map(|period| period.plays()) - .collect::>>() // Make compiler happy with turbofish. - .concat() - } +#[derive(Debug, Deserialize, Clone, PartialEq)] +pub enum Flags { + IgnoreTeam(Team), + IgnoreScore, } -#[derive(Debug, Deserialize, Clone)] -pub enum FeatureFlags { - Ignore(Team), +#[cfg(test)] +mod tests { + use crate::*; + + #[test] + fn avg_plays_per_quarter() { + let a = Game { + version: crate::MIN_VER, + flags: vec![], + periods: vec![ + Period { + start: Quarter::First, + end: None, + events: vec![ + Event::Kickoff(Team::Nebraska), + Event::Play(Play::default()), + Event::Turnover(Team::ArizonaState), + ], + }, + Period { + start: Quarter::Second, + end: Some(Quarter::Fourth), + events: vec![ + Event::Turnover(Team::Nebraska), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Turnover(Team::ArizonaState), + ], + }, + ], + }; + + let b = Game { + version: crate::MIN_VER, + flags: vec![], + periods: vec![Period { + start: Quarter::Second, + end: Some(Quarter::Fourth), + events: vec![ + Event::Turnover(Team::Nebraska), + Event::Play(Play::default()), + Event::Turnover(Team::ArizonaState), + ], + }], + }; + + assert!(a.avg_plays_per_quarter(Team::Nebraska) == ((1_f32 + 2_f32) / 2_f32)); + assert!(b.avg_plays_per_quarter(Team::Nebraska) == (1_f32 / 3_f32)) + } + + #[test] + fn team_plays() { + let a = Game { + version: crate::MIN_VER, + flags: vec![], + periods: vec![ + Period { + start: Quarter::First, + end: None, + events: vec![ + Event::Kickoff(Team::Nebraska), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + ], + }, + Period { + start: Quarter::Second, + end: Some(Quarter::Fourth), + events: vec![ + Event::Turnover(Team::Nebraska), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Play(Play::default()), + ], + }, + ], + }; + + assert!(a.team_plays(Team::Nebraska) == 12_usize) + } + + #[test] + #[allow(deprecated)] + fn teams() { + let a = Game { + version: crate::MIN_VER, + flags: vec![], + periods: vec![ + Period { + start: Quarter::First, + end: None, + events: vec![Event::Kickoff(Team::Nebraska)], + }, + Period { + start: Quarter::Second, + end: Some(Quarter::Fourth), + events: vec![ + Event::Turnover(Team::ArizonaState), + Event::Kickoff(Team::Nebraska), + ], + }, + ], + }; + + let b = Game { + version: crate::MIN_VER, + flags: vec![], + periods: vec![ + Period { + start: Quarter::First, + end: None, + events: vec![Event::Kickoff(Team::Nebraska)], + }, + Period { + start: Quarter::Second, + end: Some(Quarter::Fourth), + events: vec![ + Event::Turnover(Team::ArizonaState), + Event::Kickoff(Team::BoiseState), + ], + }, + ], + }; + + let c = Game { + version: crate::MIN_VER, + flags: vec![Flags::IgnoreTeam(Team::Nebraska)], + periods: vec![ + Period { + start: Quarter::First, + end: None, + events: vec![Event::Kickoff(Team::Nebraska)], + }, + Period { + start: Quarter::Second, + end: Some(Quarter::Fourth), + events: vec![ + Event::Turnover(Team::ArizonaState), + Event::Kickoff(Team::Nebraska), + ], + }, + ], + }; + + let d = Game { + version: crate::MIN_VER, + flags: vec![Flags::IgnoreTeam(Team::Nebraska)], + periods: vec![Period { + start: Quarter::First, + end: None, + events: vec![Event::Kickoff(Team::Nebraska)], + }], + }; + + assert!(a.teams().unwrap() == vec![Team::Nebraska, Team::ArizonaState]); + assert!(b.teams().is_err() == true); + assert!(c.teams().unwrap() == vec![Team::ArizonaState]); + assert!(d.teams().unwrap() == vec![]); + } + + #[test] + fn deltas() { + let game = Game { + version: crate::MIN_VER, + flags: vec![], + periods: vec![ + Period { + start: Quarter::First, + end: None, + events: vec![ + Event::Kickoff(Team::Nebraska), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::Yards(10)), + }), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Second), + terrain: Some(TerrainState::Yards(13)), + }), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Third), + terrain: Some(TerrainState::Yards(8)), + }), + Event::Turnover(Team::ArizonaState), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::Yards(10)), + }), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Second), + terrain: Some(TerrainState::Yards(10)), + }), + Event::Turnover(Team::Nebraska), + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::Second), + terrain: Some(TerrainState::Yards(12)), + }), + ], + }, + Period { + start: Quarter::Second, + end: None, + events: vec![ + Event::Play(Play { + action: Action::Unknown, + down: Some(Down::First), + terrain: Some(TerrainState::Yards(10)), + }), + Event::Turnover(Team::ArizonaState), + ], + }, + ], + }; + + assert!(dbg!(game.deltas(Team::Nebraska)) == vec![10_i8, -3_i8, 5_i8, -2_i8, 12_i8]); + assert!(dbg!(game.deltas(Team::ArizonaState)) == vec![10_i8, 0_i8]); + } } diff --git a/gamelog/src/lib.rs b/gamelog/src/lib.rs index 007edd5..81f722f 100644 --- a/gamelog/src/lib.rs +++ b/gamelog/src/lib.rs @@ -1,21 +1,21 @@ -#![allow(deprecated)] - mod action; mod error; mod event; mod file; mod game; mod period; +#[allow(deprecated)] mod play; mod terrain; #[allow(unused)] pub const MIN_VER: semver::Version = semver::Version::new(0, 5, 0); +// I'm lazy. pub use action::*; -pub use event::Event; -pub use file::LogFile; -pub use game::{Game, GameRecord}; +pub use event::*; +pub use file::*; +pub use game::*; pub use period::*; pub use play::*; -pub use terrain::TerrainState; +pub use terrain::*; diff --git a/gamelog/src/period.rs b/gamelog/src/period.rs index 5412afd..927045f 100644 --- a/gamelog/src/period.rs +++ b/gamelog/src/period.rs @@ -1,4 +1,4 @@ -use crate::{Event, Play, PlayHandle}; +use crate::{Event, Play, PlayHandle, Team, error}; use serde::Deserialize; #[derive(Debug, Deserialize, Clone)] @@ -24,20 +24,117 @@ impl PlayHandle for Period { } impl Period { - pub fn deltas(&self) -> Vec { - let len = self.events.len() - 1; - let mut idx: usize = 0; - let mut deltas: Vec = vec![]; + pub fn team_events( + &self, + team: Team, + assume_team_known: Option, + ) -> Result, error::CannotDetermineTeams> { + let mut events: Vec = vec![]; + let mut first = true; + let mut record: bool = true; + let assume_team_known = assume_team_known.unwrap_or(false); - while idx < len { - if let Some(value) = self.events[idx].delta(&self.events[idx + 1]) { - deltas.push(value); + for event in self.events.iter() { + if let Event::Kickoff(_) | Event::Turnover(_) = event { + record = { + if team == event.team().unwrap() { + // Wipe events vec if the start of quarter was opposition + // on offence. + if first { + events = vec![]; + } + + true + } else { + events.push(event.to_owned()); + false + } + }; + + first = false; } - idx += 1 + if record { + events.push(event.to_owned()); + } } - deltas + // If already handled or assumption override applicable + if !first || (first && assume_team_known) { + Ok(events) + } else { + Err(error::CannotDetermineTeams) + } + } + + pub fn team_plays( + &self, + team: Team, + assume_team_known: Option, + ) -> Result, error::CannotDetermineTeams> { + Ok(self + .team_events(team, assume_team_known)? + .iter() + .filter_map(|event| { + if let Event::Play(play) = event { + Some(play.to_owned()) + } else { + None + } + }) + .collect()) + } + + pub fn quarters(&self) -> Vec { + let mut quarters: Vec = vec![self.start.to_owned()]; + + if self.end.is_none() { + return quarters; + } + + let order = vec![ + Quarter::First, + Quarter::Second, + Quarter::Third, + Quarter::Fourth, + ]; + + let start = if let Quarter::Overtime(x) = self.start { + (3 + x) as usize + } else { + order.iter().position(|q| q == &self.start).unwrap() + }; + + let end = if let Quarter::Overtime(x) = self.end.as_ref().unwrap() { + (3 + x) as usize + } else { + order + .iter() + .position(|q| q == self.end.as_ref().unwrap()) + .unwrap() + }; + + let range: Vec = ((start + 1)..=end).collect(); + + for i in range { + quarters.push(match i { + 0 => Quarter::First, + 1 => Quarter::Second, + 2 => Quarter::Third, + 3 => Quarter::Fourth, + _ => Quarter::Overtime((i - 3) as u8), + }); + } + + quarters + } + + pub fn is_overtime(&self) -> bool { + if self.start.is_overtime() || self.end.as_ref().is_some_and(|some| some.is_overtime()) { + true + } else { + false + } } } @@ -50,49 +147,158 @@ pub enum Quarter { Overtime(u8), } +impl Quarter { + pub fn is_overtime(&self) -> bool { + if let Self::Overtime(_) = self { + true + } else { + false + } + } +} + #[cfg(test)] mod tests { - use super::*; - use crate::{Action, Down, Team, TerrainState}; + use crate::*; #[test] - fn deltas() { + fn team_events() { + let a = Period { + start: Quarter::First, + end: None, + events: vec![ + Event::Kickoff(Team::Nebraska), + Event::Play(Play::default()), + Event::Turnover(Team::ArizonaState), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Kickoff(Team::Nebraska), + Event::Score(ScorePoints::Touchdown), + Event::Kickoff(Team::SouthCarolina), + ], + }; + + let b = Period { + start: Quarter::Second, + end: None, + events: vec![ + Event::Play(Play::default()), + Event::Turnover(Team::SouthCarolina), + ], + }; + + let c = Period { + start: Quarter::Second, + end: None, + events: vec![ + Event::Play(Play::default()), + Event::Turnover(Team::Nebraska), + ], + }; + + let d = Period { + start: Quarter::Second, + end: None, + events: vec![Event::Play(Play::default())], + }; + + assert!( + a.team_events(Team::Nebraska, None).unwrap() + == vec![ + Event::Kickoff(Team::Nebraska), + Event::Play(Play::default()), + Event::Turnover(Team::ArizonaState), + Event::Kickoff(Team::Nebraska), + Event::Score(ScorePoints::Touchdown), + Event::Kickoff(Team::SouthCarolina), + ] + ); + assert!( + b.team_events(Team::Nebraska, None).unwrap() + == vec![ + Event::Play(Play::default()), + Event::Turnover(Team::SouthCarolina) + ] + ); + assert!( + c.team_events(Team::Nebraska, None).unwrap() == vec![Event::Turnover(Team::Nebraska)] + ); + assert!(true == d.team_events(Team::Nebraska, None).is_err()); + assert!(false == d.team_events(Team::Nebraska, Some(true)).is_err()) + } + + #[test] + fn team_plays() { let period = Period { start: Quarter::First, end: None, events: vec![ Event::Kickoff(Team::Nebraska), - Event::Play(Play { - action: Action::Unknown, - down: Some(Down::First), - terrain: Some(TerrainState::Yards(10)), - }), - Event::Play(Play { - action: Action::Unknown, - down: Some(Down::Second), - terrain: Some(TerrainState::Yards(13)), - }), - Event::Play(Play { - action: Action::Unknown, - down: Some(Down::Third), - terrain: Some(TerrainState::Yards(8)), - }), + Event::Play(Play::default()), + Event::Turnover(Team::ArizonaState), + Event::Play(Play::default()), + Event::Play(Play::default()), + Event::Kickoff(Team::Nebraska), + Event::Play(Play::default()), + Event::Score(ScorePoints::default()), + Event::Kickoff(Team::SouthCarolina), + Event::Play(Play::default()), Event::Turnover(Team::Nebraska), - Event::Play(Play { - action: Action::Unknown, - down: Some(Down::First), - terrain: Some(TerrainState::Yards(10)), - }), - Event::Play(Play { - action: Action::Unknown, - down: Some(Down::First), - terrain: Some(TerrainState::Yards(10)), - }), + Event::Play(Play::default()), ], }; - let expected: Vec = vec![10, -3, 5, 10]; + assert!( + period.team_plays(Team::Nebraska, None).unwrap() + == vec![Play::default(), Play::default(), Play::default()] + ); + } - assert!(period.deltas() == expected) + #[test] + fn quarters() { + let first = Period { + start: Quarter::First, + end: None, + events: vec![], + }; + + let second_fourth = Period { + start: Quarter::Second, + end: Some(Quarter::Fourth), + events: vec![], + }; + + let third_ot_three = Period { + start: Quarter::Third, + end: Some(Quarter::Overtime(3)), + events: vec![], + }; + + let ot_one_three = Period { + start: Quarter::Overtime(1), + end: Some(Quarter::Overtime(3)), + events: vec![], + }; + + assert!(first.quarters() == vec![Quarter::First]); + assert!(second_fourth.quarters() == vec![Quarter::Second, Quarter::Third, Quarter::Fourth]); + assert!( + third_ot_three.quarters() + == vec![ + Quarter::Third, + Quarter::Fourth, + Quarter::Overtime(1), + Quarter::Overtime(2), + Quarter::Overtime(3) + ] + ); + assert!( + ot_one_three.quarters() + == vec![ + Quarter::Overtime(1), + Quarter::Overtime(2), + Quarter::Overtime(3) + ] + ) } } diff --git a/gamelog/src/play.rs b/gamelog/src/play.rs index 0f1610d..3c683a4 100644 --- a/gamelog/src/play.rs +++ b/gamelog/src/play.rs @@ -1,11 +1,6 @@ use crate::{Action, TerrainState}; use serde::Deserialize; -pub trait PlayHandle { - /// Returns all plays within object's scope. - fn plays(&self) -> Vec; -} - #[derive(Debug, Deserialize, Clone, PartialEq)] pub struct Play { pub action: Action, @@ -13,12 +8,6 @@ pub struct Play { pub terrain: Option, } -impl PlayHandle for Play { - fn plays(&self) -> Vec { - vec![self.to_owned()] - } -} - impl Default for Play { fn default() -> Self { Self { diff --git a/miller/Cargo.toml b/miller/Cargo.toml index 1d5f2b4..8805ca2 100644 --- a/miller/Cargo.toml +++ b/miller/Cargo.toml @@ -3,7 +3,6 @@ name = "miller" version = "0.1.0" edition = "2024" license = "MIT" -license-file = "LICENSE" [dependencies.clap] version = "4.5" diff --git a/miller/src/calculator.rs b/miller/src/calculator.rs deleted file mode 100644 index e69de29..0000000 diff --git a/miller/src/main.rs b/miller/src/main.rs index dfba545..2c55b8e 100644 --- a/miller/src/main.rs +++ b/miller/src/main.rs @@ -1,8 +1,6 @@ -mod calculator; - use clap::Parser; use core::panic; -use gamelog::LogFile; +use gamelog::{Flags, LogFile}; use std::path::PathBuf; #[derive(Debug, Parser)] @@ -12,7 +10,7 @@ struct Args { short, long, value_hint = clap::ValueHint::DirPath, - default_value = format!("{}/templates/logfile.ron", std::env::current_dir() + default_value = format!("{}/../templates/logfile.ron", std::env::current_dir() .expect("Failed to get current working dir.") .into_os_string() .to_str() @@ -24,10 +22,22 @@ struct Args { fn main() { let config = Args::parse(); - let log: LogFile = { - let file = match LogFile::try_from(config.logfile_path) { - Ok(f) => f, - Err(err) => panic!("Error: Failed to open logfile: {:?}", err), - }; + let log: LogFile = match LogFile::try_from(config.logfile_path) { + Ok(f) => f, + Err(err) => panic!("Error: Failed to open logfile: {:?}", err), }; + + 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()) + ) + } + } + } + } } diff --git a/templates/logfile.ron b/templates/logfile.ron index a12964a..bd43e30 100644 --- a/templates/logfile.ron +++ b/templates/logfile.ron @@ -4,20 +4,20 @@ [ Game( - version: "0.3.0", + version: "0.5.0", + flags: [], periods: [ Period( start: First, end: Fourth, - plays: [ + events: [ Kickoff(Nebraska), Play( action: Unknown, down: First, terrain: Yards(10) ), - Score(3), - Pat(Fail) + Score(FieldGoal), ] ) ] -- 2.49.1 From 594d53e08048fffedf6ed342f9ed7632fdbf3623 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Sat, 5 Apr 2025 16:22:59 -0400 Subject: [PATCH 15/17] Clean out last of PlayHandle. --- gamelog/src/period.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/gamelog/src/period.rs b/gamelog/src/period.rs index 927045f..654d878 100644 --- a/gamelog/src/period.rs +++ b/gamelog/src/period.rs @@ -1,4 +1,4 @@ -use crate::{Event, Play, PlayHandle, Team, error}; +use crate::{Event, Play, Team, error}; use serde::Deserialize; #[derive(Debug, Deserialize, Clone)] @@ -8,21 +8,6 @@ pub struct Period { pub events: Vec, } -impl PlayHandle for Period { - fn plays(&self) -> Vec { - self.events - .iter() - .filter_map(|event| { - if let Event::Play(play) = event { - Some(play.to_owned()) - } else { - None - } - }) - .collect() - } -} - impl Period { pub fn team_events( &self, -- 2.49.1 From 89f97101af6f05d4c66dfb7171559243d30658d4 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Sat, 5 Apr 2025 19:17:37 -0400 Subject: [PATCH 16/17] 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, + } + } } -- 2.49.1 From a7f8cb04f7caaf9b7aca2fc3c9adff3a0f415386 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Sun, 6 Apr 2025 12:19:42 -0400 Subject: [PATCH 17/17] Finish tasks to bring calculation capabilities in line with last interval. --- README.adoc | 8 ++++---- gamelog.ron | 9 +++++++-- gamelog/src/game.rs | 37 ++++++++++++++++++++++++++++--------- miller/src/main.rs | 31 ++++++++++++++++++++++++++----- 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/README.adoc b/README.adoc index 2338ac5..295222c 100644 --- a/README.adoc +++ b/README.adoc @@ -47,10 +47,10 @@ I figured, that since I already had to digitize every note, that I was required === Miller: * [ ] Mathematics -** [ ] Avg. Terrain Gain -** [ ] Avg. Terrain Loss -** [ ] Avg. Terrain Delta -** [ ] Avg. Offence Plays per quarter +** [*] Avg. Terrain Gain +** [*] Avg. Terrain Loss +** [*] Avg. Terrain Delta +** [*] Avg. Offence Plays per quarter ** [ ] Avg. Offence Plays per game ** [ ] Avg. Penalties per game * [ ] Play Trend Analysis diff --git a/gamelog.ron b/gamelog.ron index 9d8e492..517a1dd 100644 --- a/gamelog.ron +++ b/gamelog.ron @@ -9,7 +9,7 @@ periods: [ Period( start: First, - end: Third, + end: Second, events: [ Kickoff(ArizonaState), Play( @@ -83,6 +83,12 @@ down: First, terrain: Yards(10), ), + ] + ), + Period( + start: Third, + end: None, + events: [ Kickoff(TexasAnM), Play( action: Unknown, // PA Comebacks or Curls? Original note: Spike Centre @@ -619,7 +625,6 @@ down: First, terrain: GoalLine, ), - // Touchdown Score(Touchdown), Score(PatSafety), Kickoff(Iowa), diff --git a/gamelog/src/game.rs b/gamelog/src/game.rs index a1ef14f..efea30a 100644 --- a/gamelog/src/game.rs +++ b/gamelog/src/game.rs @@ -65,8 +65,7 @@ impl Game { } pub fn team_plays(&self, team: Team) -> usize { - let quarterly_plays: Vec = self - .periods + self.periods .iter() .filter_map(|period| { if !period.is_overtime() { @@ -76,13 +75,9 @@ impl Game { None } }) - .collect::>(); - - let mut summation = 0_usize; - - quarterly_plays.iter().for_each(|value| summation += value); - - summation + .collect::>() + .iter() + .sum::() } /// The average number of plays in a quarter. @@ -145,6 +140,30 @@ impl Game { deltas.iter().sum::() as f32 / deltas.len() as f32 } + + pub fn penalties(&self, team: Team) -> usize { + self.periods + .iter() + .filter_map(|period| { + Some( + period + .team_events(team.to_owned(), None) + .ok()? + .iter() + .filter_map(|event| { + if let Event::Penalty(_) = event { + Some(event.to_owned()) + } else { + None + } + }) + .collect::>(), + ) + }) + .collect::>>() + .concat() + .len() + } } #[derive(Debug, Deserialize, Clone, PartialEq)] diff --git a/miller/src/main.rs b/miller/src/main.rs index bc9515c..58667aa 100644 --- a/miller/src/main.rs +++ b/miller/src/main.rs @@ -1,9 +1,10 @@ -use clap::Parser; +use clap::{ArgAction, Parser}; use core::panic; -use gamelog::{Action, Flags, LogFile, Team}; +use gamelog::{Action, Flags, Key, LogFile, Team}; use std::path::PathBuf; #[derive(Debug, Parser)] +#[clap(author, version, about)] struct Args { /// Path to source file or block device #[arg( @@ -17,6 +18,12 @@ struct Args { .unwrap()) )] logfile_path: PathBuf, + + // Behaviour is backwards. + // ArgAction::SetFalse by default evaluates to true, + // ArgAction::SetTrue by default evaluates to false. + #[arg(short, long, action=ArgAction::SetFalse)] + display_results: bool, } fn main() { @@ -39,6 +46,7 @@ fn main() { TeamStats::new(Team::TexasAnM), ]; + // Work on knocking down the nesting here? for game in log.0.iter() { if let Ok(teams) = game.teams() { for team in teams { @@ -70,13 +78,22 @@ fn main() { stats[team_idx] .plays_per_quarter .push(game.avg_plays_per_quarter(team.to_owned())); + + stats[team_idx] + .plays_per_game + .push(game.team_plays(team.to_owned())); + + stats[team_idx] + .penalties_per_game + .push(game.penalties(team.to_owned())); } } } } - for team in stats { - dbg!(team); + if dbg!(config.display_results) { + // :#? for pretty-printing. + stats.iter().for_each(|team| println!("{:#?}", team)); } } @@ -91,13 +108,15 @@ struct TeamStats { plays_per_quarter: Vec, plays_per_game: Vec, // Penalties - penalties_per_game: Vec, + penalties_per_game: Vec, // Score points_per_quarter: Vec, points_per_game: Vec, // Biases most_common_play: Option, least_common_play: Option, + most_common_key: Option, + least_common_key: Option, } impl TeamStats { @@ -114,6 +133,8 @@ impl TeamStats { points_per_game: vec![], most_common_play: None, least_common_play: None, + most_common_key: None, + least_common_key: None, } } } -- 2.49.1