Implement PPQ Calculation [per team per game] and housekeeping.

This commit is contained in:
Cutieguwu
2025-04-05 16:21:31 -04:00
parent 589dbd55d0
commit e72cdbf4b7
12 changed files with 688 additions and 137 deletions

View File

@@ -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)
),

View File

@@ -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.")
}
}

View File

@@ -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<Play> {
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<Team, error::NoTeamAttribute> {
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());
}
}

View File

@@ -3,7 +3,7 @@ use serde::Deserialize;
use std::{fs::File, path::PathBuf};
#[derive(Debug, Deserialize, Clone)]
pub struct LogFile(Vec<super::Game>);
pub struct LogFile(pub Vec<super::Game>);
impl LogFile {
pub fn min_ver(&self) -> semver::Version {

View File

@@ -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<FeatureFlags>,
pub flags: Vec<Flags>,
pub periods: Vec<Period>,
}
impl Game {
/// Returns the teams of this game.
pub fn teams(&self) -> Result<Vec<Team>, error::TeamsError> {
let ignore: Vec<Team> = 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<i8> {
self.periods
pub fn deltas(&self, team: Team) -> Vec<i8> {
let events = self
.periods
.iter()
.map(|period| period.deltas())
.collect::<Vec<Vec<i8>>>()
.concat()
.filter_map(|period| Some(period.team_events(team.to_owned(), None).ok().unwrap()))
.collect::<Vec<Vec<Event>>>()
.concat();
let len = events.len() - 1;
let mut idx: usize = 0;
let mut deltas: Vec<i8> = 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<f32> = 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::<Vec<f32>>();
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<usize> = 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::<Vec<usize>>();
let mut summation = 0_usize;
quarterly_plays.iter().for_each(|value| summation += value);
summation
}
}
impl PlayHandle for Game {
fn plays(&self) -> Vec<Play> {
self.periods
.iter()
.map(|period| period.plays())
.collect::<Vec<Vec<Play>>>() // 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]);
}
}

View File

@@ -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::*;

View File

@@ -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<i8> {
let len = self.events.len() - 1;
let mut idx: usize = 0;
let mut deltas: Vec<i8> = vec![];
pub fn team_events(
&self,
team: Team,
assume_team_known: Option<bool>,
) -> Result<Vec<Event>, error::CannotDetermineTeams> {
let mut events: Vec<Event> = 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<bool>,
) -> Result<Vec<Play>, 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<Quarter> {
let mut quarters: Vec<Quarter> = 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<usize> = ((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<i8> = 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)
]
)
}
}

View File

@@ -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<Play>;
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
pub struct Play {
pub action: Action,
@@ -13,12 +8,6 @@ pub struct Play {
pub terrain: Option<TerrainState>,
}
impl PlayHandle for Play {
fn plays(&self) -> Vec<Play> {
vec![self.to_owned()]
}
}
impl Default for Play {
fn default() -> Self {
Self {

View File

@@ -3,7 +3,6 @@ name = "miller"
version = "0.1.0"
edition = "2024"
license = "MIT"
license-file = "LICENSE"
[dependencies.clap]
version = "4.5"

View File

@@ -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())
)
}
}
}
}
}

View File

@@ -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),
]
)
]