Rebase tui branch on gamelog v0.7.1

This commit is contained in:
Olivia Brooks
2025-04-23 12:53:24 -04:00
9 changed files with 1675 additions and 1413 deletions

View File

@@ -12,6 +12,7 @@ I figured, that since I already had to digitize every note, that I was required
== Goals
* [ ] Auto Ranking system?
* [ ] Data Visualizer?
* [ ] Dynamic Web Page?
* [ ] Pattern Analysis / Play Trend Analysis

File diff suppressed because it is too large Load Diff

2
gamelog/Cargo.lock generated
View File

@@ -19,7 +19,7 @@ dependencies = [
[[package]]
name = "gamelog"
version = "0.6.1"
version = "0.7.1"
dependencies = [
"ron",
"semver",

View File

@@ -1,6 +1,6 @@
[package]
name = "gamelog"
version = "0.6.1"
version = "0.7.1"
edition = "2024"
[dependencies]

View File

@@ -1,4 +1,4 @@
use crate::{Down, Play, TerrainState, error};
use crate::{Down, Play, Quarter, TerrainState, error};
use serde::Deserialize;
type Offence = Team;
@@ -10,6 +10,7 @@ pub enum Event {
Turnover(Offence),
Penalty(TerrainState),
Score(ScorePoints),
Quarter(Quarter),
}
impl Event {
@@ -74,6 +75,14 @@ impl Event {
_ => Err(error::NoTeamAttribute),
}
}
pub fn quarter(&self) -> Option<Quarter> {
if let Event::Quarter(quarter) = self {
Some(quarter.to_owned())
} else {
None
}
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
@@ -171,6 +180,20 @@ mod tests {
terrain: Some(TerrainState::Inches),
});
let quarter = Event::Quarter(Quarter::First);
assert!(None == quarter.delta(&kickoff));
assert!(None == quarter.delta(&first_down));
assert!(None == quarter.delta(&second_down));
assert!(None == quarter.delta(&third_down));
assert!(None == quarter.delta(&fourth_down));
assert!(None == quarter.delta(&turnover));
assert!(None == quarter.delta(&penalty));
assert!(None == quarter.delta(&goal_line));
assert!(None == quarter.delta(&inches));
assert!(None == quarter.delta(&score));
assert!(None == quarter.delta(&quarter));
assert!(10_i8 == kickoff.delta(&first_down).unwrap());
assert!(0_i8 == kickoff.delta(&second_down).unwrap());
assert!(None == kickoff.delta(&penalty));
@@ -248,9 +271,9 @@ mod tests {
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!(-10_i8 == inches.delta(&second_down).unwrap());
assert!(-13_i8 == inches.delta(&third_down).unwrap());
assert!(-5_i8 == inches.delta(&fourth_down).unwrap());
assert!(None == inches.delta(&turnover));
assert!(None == inches.delta(&penalty));
assert!(None == inches.delta(&goal_line));

View File

@@ -1,11 +1,12 @@
use crate::{Event, Period, Team, error};
use crate::{Event, Quarter, Team, error};
use serde::Deserialize;
use strum::IntoEnumIterator;
#[derive(Debug, Deserialize, Clone)]
pub struct Game {
pub version: semver::Version,
pub flags: Vec<Flags>,
pub periods: Vec<Period>,
pub events: Vec<Event>,
}
impl Game {
@@ -25,12 +26,10 @@ impl Game {
let mut teams = vec![];
self.periods.iter().for_each(|period| {
for event in period.events.iter() {
if let Ok(team) = event.team() {
if !ignore.contains(&team) && !teams.contains(&team) {
teams.push(team)
}
self.events.iter().for_each(|event| {
if let Ok(team) = event.team() {
if !ignore.contains(&team) && !teams.contains(&team) {
teams.push(team)
}
}
});
@@ -43,14 +42,17 @@ impl Game {
}
pub fn deltas(&self, team: Team) -> Vec<i8> {
let events = self
.periods
let events: Vec<Event> = self
.team_events(team)
.iter()
// TOTALLY BORKED.
// BREAKS IF THE TEAMS ARE UNKNOWN, NEEDS FIXING.
.filter_map(|period| Some(period.team_events(team.to_owned(), None).ok().unwrap()))
.collect::<Vec<Vec<Event>>>()
.concat();
.filter_map(|event| {
if let Event::Quarter(_) = event {
None
} else {
Some(event.to_owned())
}
})
.collect();
let len = events.len() - 1;
let mut idx: usize = 0;
let mut deltas: Vec<i8> = vec![];
@@ -67,33 +69,30 @@ impl Game {
}
pub fn team_plays(&self, team: Team) -> usize {
self.periods
self.team_events(team)
.iter()
.filter_map(|period| {
if !period.is_overtime() {
let plays = period.team_plays(team.to_owned(), None);
Some(plays.unwrap().len())
.filter_map(|event| {
if let Event::Play(_) = event {
Some(event)
} else {
None
}
})
.collect::<Vec<usize>>()
.iter()
.sum::<usize>()
.collect::<Vec<&Event>>()
.len()
}
/// 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 periods: Vec<Period> = Quarter::iter()
.filter_map(|quarter| Some(self.to_owned().get_period(quarter.to_owned())).to_owned())
.collect();
let quarterly_avgs: Vec<f32> = self
.periods
let quarterly_avgs: Vec<f32> = 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)
Some(period.team_plays(team.to_owned()) as f32)
} else {
None
}
@@ -144,29 +143,138 @@ impl Game {
}
pub fn penalties(&self, team: Team) -> usize {
// Knock down nesting?
self.periods
self.team_events(team)
.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::<Vec<Event>>(),
)
.filter_map(|event| {
if let Event::Penalty(_) = event {
Some(event.to_owned())
} else {
None
}
})
.collect::<Vec<Vec<Event>>>()
.concat()
.collect::<Vec<Event>>()
.len()
}
pub fn get_period(&self, quarter: Quarter) -> Period {
let mut record = false;
Period {
period: quarter.to_owned(),
events: self
.events
.iter()
.filter_map(|event| {
if let Event::Quarter(_) = event {
record = Event::Quarter(quarter.to_owned()) == *event;
}
if record {
return Some(event.to_owned());
}
None
})
.collect::<Vec<Event>>(),
}
}
pub fn team_events(&self, team: Team) -> Vec<Event> {
let mut events: Vec<Event> = vec![];
let mut first = true;
let mut record: bool = true;
self.events.iter().for_each(|event| {
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;
}
if record {
events.push(event.to_owned());
}
});
// If already handled or assumption override applicable
events
}
}
#[derive(Debug, Clone)]
pub struct Period {
period: Quarter,
events: Vec<Event>,
}
impl Period {
pub fn team_events(&self, team: Team) -> Vec<Event> {
let mut events: Vec<Event> = vec![];
let mut first = true;
let mut record: bool = true;
self.events.iter().for_each(|event| {
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;
}
if record {
events.push(event.to_owned());
}
});
events
}
pub fn team_plays(&self, team: Team) -> usize {
self.team_events(team)
.iter()
.filter_map(|event| {
if let Event::Play(_) = event {
Some(event)
} else {
None
}
})
.collect::<Vec<&Event>>()
.len()
}
pub fn is_overtime(&self) -> bool {
if let Quarter::Overtime(_) = self.period {
true
} else {
false
}
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
@@ -175,8 +283,10 @@ pub enum Flags {
IgnoreTeam(Team),
IgnoreScore,
Interval(u8),
SheerDumbFuckingLuck
}
/*
#[cfg(test)]
mod tests {
use crate::*;
@@ -186,30 +296,20 @@ mod tests {
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),
],
},
events: vec![
Event::Quarter(Quarter::First),
Event::Kickoff(Team::Nebraska),
Event::Play(Play::default()),
Event::Turnover(Team::ArizonaState),
Event::Quarter(Quarter::Second),
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),
],
};
@@ -411,4 +511,146 @@ mod tests {
assert!(game.deltas(Team::Nebraska) == vec![10_i8, -3_i8, 5_i8, -2_i8, 12_i8]);
assert!(game.deltas(Team::ArizonaState) == vec![10_i8, 0_i8]);
}
#[test]
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::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::default()),
],
};
assert!(
period.team_plays(Team::Nebraska, None).unwrap()
== vec![Play::default(), Play::default(), Play::default()]
);
}
#[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

@@ -9,7 +9,7 @@ mod play;
mod terrain;
#[allow(unused)]
pub const MIN_VER: semver::Version = semver::Version::new(0, 6, 0);
pub const MIN_VER: semver::Version = semver::Version::new(0, 7, 0);
// I'm lazy.
pub use action::*;

View File

@@ -1,125 +1,7 @@
use crate::{Event, Play, Team, error};
use serde::Deserialize;
use strum::EnumIter;
#[derive(Debug, Deserialize, Clone)]
pub struct Period {
pub start: Quarter,
pub end: Option<Quarter>,
pub events: Vec<Event>,
}
impl Period {
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);
self.events.iter().for_each(|event| {
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;
}
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 {
self.start.is_overtime() || self.end.as_ref().is_some_and(|some| some.is_overtime())
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize, Clone, PartialEq, EnumIter)]
pub enum Quarter {
First,
Second,
@@ -127,159 +9,3 @@ pub enum Quarter {
Fourth,
Overtime(u8),
}
impl Quarter {
pub fn is_overtime(&self) -> bool {
if let Self::Overtime(_) = self {
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
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::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::default()),
],
};
assert!(
period.team_plays(Team::Nebraska, None).unwrap()
== vec![Play::default(), Play::default(), Play::default()]
);
}
#[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)
]
)
}
}

2
miller/Cargo.lock generated
View File

@@ -205,7 +205,7 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "gamelog"
version = "0.6.1"
version = "0.7.1"
dependencies = [
"ron",
"semver",