Implement PPQ Calculation [per team per game] and housekeeping.
This commit is contained in:
69
gamelog.ron
69
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)
|
||||
),
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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![];
|
||||
}
|
||||
|
||||
idx += 1
|
||||
true
|
||||
} else {
|
||||
events.push(event.to_owned());
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
deltas
|
||||
if record {
|
||||
events.push(event.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -3,7 +3,6 @@ name = "miller"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
license-file = "LICENSE"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "4.5"
|
||||
|
||||
@@ -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) {
|
||||
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())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user