42 Commits

Author SHA1 Message Date
Olivia Brooks
182d7b29b4 Restore some stats readouts. 2025-06-18 12:46:08 -04:00
Olivia Brooks
56ba50e4ea Add error trait to error type. 2025-06-18 12:45:49 -04:00
Olivia Brooks
b11f6037c2 Hide stuff and correct sorting of SouthCarolina and Syracuse. 2025-06-13 12:27:22 -04:00
Olivia Brooks
d591d2ed0d Update gamelog.ron 2025-06-13 12:21:21 -04:00
Cutieguwu
59590d767f Various fixes. 2025-05-27 12:30:52 -04:00
Olivia Brooks
3c09230da3 Update gamelog.ron 2025-05-23 10:59:27 -04:00
Olivia Brooks
8ae88601be Update gamelog.ron 2025-05-16 16:07:45 -04:00
Olivia Brooks
8e8ba12386 Play frequency tallies 2025-05-15 20:11:41 -04:00
Olivia Brooks
fc035558f9 Fixed the "improved" unit tests. 2025-05-15 10:54:57 -04:00
Olivia Brooks
b42203f9db Improve unit test and a bunch of I forget what. 2025-05-15 10:53:48 -04:00
Cutieguwu
fa183cbe91 impl Default for Game 2025-05-03 12:04:21 -04:00
Cutieguwu
5d17a2b5b6 Dependency updates and gamelog version bump. 2025-05-02 16:39:49 -04:00
Olivia Brooks
0483921b24 Update gamelog.ron 2025-05-02 09:33:10 -04:00
Cutieguwu
ecd41d262e Progress on implementing most_effective_play 2025-05-01 21:53:21 -04:00
Cutieguwu
2fbef3c83f Add least_frequent_action method; patch test faults. 2025-05-01 20:45:43 -04:00
Cutieguwu
4d8f1d2d46 Clean up delta calculation. 2025-04-30 21:14:57 -04:00
Olivia Brooks
07e9d0b5b6 Add Stub flag. 2025-04-30 09:47:23 -04:00
Olivia Brooks
719e816c9c Add LowDataCredibility flag. 2025-04-30 09:45:41 -04:00
Olivia Brooks
a8d2758ad3 Switch to moving target goals into Issues tagged with Enhancement 2025-04-30 09:45:41 -04:00
Cutieguwu
21f9c530cc Update team_events and unit test. 2025-04-29 17:05:01 -04:00
Cutieguwu
0a82be4c0b Update deltas unit test. 2025-04-29 16:43:27 -04:00
Cutieguwu
60746ff018 Update teams unit test. 2025-04-29 16:40:14 -04:00
Cutieguwu
6a3e9b57e4 Update team_plays unit test. 2025-04-29 16:34:30 -04:00
Cutieguwu
050f8ebc86 Remove old quarters test. 2025-04-29 16:30:46 -04:00
Cutieguwu
4b6a652a87 Fix avg_plays_per_quarter calculations. 2025-04-29 16:28:14 -04:00
Cutieguwu
d7a7c6667f Correct naming of ShotgunWingOffsetWk. 2025-04-28 20:01:38 -04:00
Cutieguwu
d37e235e90 Add ClockBleeding flag. 2025-04-28 19:55:46 -04:00
Cutieguwu
673078d6be Update file.rs 2025-04-26 16:13:12 -04:00
Cutieguwu
c65d60ffee Update file.rs 2025-04-26 16:09:07 -04:00
Cutieguwu
bacd90d96f Slight readability improvement by mutating found instead of shadowing
it repeatedly in LogFile::most_frequent_action
2025-04-26 16:07:32 -04:00
Cutieguwu
ce89752009 Improved code comments. 2025-04-26 16:00:39 -04:00
Cutieguwu
352df77645 Improved most_frequent_action method; Introduced LogFile check for improper composition where games have more than two teams. 2025-04-26 14:45:25 -04:00
Cutieguwu
fd5d7951f9 Move out my zed settings to their own repo. 2025-04-26 12:08:54 -04:00
Olivia Brooks
dd26678779 Fix gamelog formatting errors. 2025-04-25 15:10:42 -04:00
Olivia Brooks
60c90f0748 Update logfile template to v0.7 2025-04-24 11:37:56 -04:00
Olivia Brooks
5cbe2abeb4 Update gamelog.ron 2025-04-24 09:51:21 -04:00
Cutieguwu
4c151c1d81 Update README.adoc 2025-04-23 21:19:15 -04:00
Cutieguwu
b634841136 Assume all PATs were probably Field Goals, rather than Safeties. 2025-04-23 21:06:57 -04:00
Cutieguwu
a5535354a3 Deprecate NoTeamAttribute and CannotDetermineTeams 2025-04-23 20:33:18 -04:00
Cutieguwu
bfd135df94 Documentation improvements. 2025-04-23 19:37:49 -04:00
Cutieguwu
b8db749a50 Deprecate LogFile version checks. 2025-04-23 19:18:53 -04:00
Cutieguwu
31515fc806 Implement most_frequent_action method. 2025-04-23 19:15:27 -04:00
16 changed files with 2235 additions and 566 deletions

View File

@@ -9,58 +9,3 @@ For my stats course (4U Data Management) I have to interpret a bunch of data gen
In an effort to not spend ages mindlessly using a calculator every summative check-in, I have started this project. In an effort to not spend ages mindlessly using a calculator every summative check-in, I have started this project.
I figured, that since I already had to digitize every note, that I was required to make by hand, from during the game, that I might as well employ a format of my own. This format, would allow me to use code to interpret the monotonous mathematics for me. Even if I don't fully complete the code, it would ease the burden by completing some of the math for me. I figured, that since I already had to digitize every note, that I was required to make by hand, from during the game, that I might as well employ a format of my own. This format, would allow me to use code to interpret the monotonous mathematics for me. Even if I don't fully complete the code, it would ease the burden by completing some of the math for me.
== Goals
* [ ] Auto Ranking system?
* [ ] Data Visualizer?
* [ ] Dynamic Web Page?
* [ ] Pattern Analysis / Play Trend Analysis
** [ ] Most Frequent Play
** [ ] Least Frequent Play
** [ ] Most Effective Play (Greatest Terrain Gain on average)
** [ ] Most frequent play set.
** [ ] Repeating play pattern.
** [ ] Slow after score.
** [ ] Bias to using Play Actions
** [ ] Bias to using Runs
=== 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
** [*] Avg. Terrain Delta
** [*] Avg. Offence Plays per quarter
** [*] Avg. Offence Plays per game
** [*] Avg. Penalties per game

File diff suppressed because it is too large Load Diff

10
gamelog/Cargo.lock generated
View File

@@ -19,7 +19,7 @@ dependencies = [
[[package]] [[package]]
name = "gamelog" name = "gamelog"
version = "0.7.1" version = "0.7.3"
dependencies = [ dependencies = [
"ron", "ron",
"semver", "semver",
@@ -35,9 +35,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.94" version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -123,9 +123,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.100" version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

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

View File

@@ -1,6 +1,7 @@
use serde::Deserialize; use serde::Deserialize;
use strum::EnumIter;
#[derive(Debug, Deserialize, Clone, Default, PartialEq)] #[derive(Debug, Deserialize, Clone, Default, PartialEq, EnumIter)]
pub enum Action { pub enum Action {
CrackStudentBodyRightTackle, CrackStudentBodyRightTackle,
Curls, Curls,
@@ -14,6 +15,7 @@ pub enum Action {
PlayActionComebacks, PlayActionComebacks,
PlayActionPowerZero, PlayActionPowerZero,
PowerZero, PowerZero,
Punt,
SlantBubble, SlantBubble,
SlotOut, SlotOut,
SpeedOption, SpeedOption,
@@ -83,7 +85,7 @@ impl Action {
Some(match self { Some(match self {
Self::SlantBubble | Self::HalfbackSlam | Self::PlayActionBoot => Playset::PistolSpread, Self::SlantBubble | Self::HalfbackSlam | Self::PlayActionBoot => Playset::PistolSpread,
Self::StrongFlood | Self::SpeedOption | Self::HalfbackSlipScreen => { Self::StrongFlood | Self::SpeedOption | Self::HalfbackSlipScreen => {
Playset::ShotgunTripleWingsOffset Playset::ShotgunWingOffsetWk
} }
Self::SlotOut | Self::HalfbackSweep | Self::PlayActionComebacks => { Self::SlotOut | Self::HalfbackSweep | Self::PlayActionComebacks => {
Playset::ShotgunDoubleFlex Playset::ShotgunDoubleFlex
@@ -125,7 +127,7 @@ impl Action {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Playset { pub enum Playset {
PistolSpread, PistolSpread,
ShotgunTripleWingsOffset, ShotgunWingOffsetWk,
ShotgunDoubleFlex, ShotgunDoubleFlex,
IFormNormal, IFormNormal,
IFormTight, IFormTight,

View File

@@ -1,47 +1,33 @@
use ron::de::SpannedError;
use std::{fmt, io}; use std::{fmt, io};
#[derive(Debug)] #[derive(Debug)]
pub enum LogFileError { pub enum LogFileError {
FailedToOpen(io::Error), IOError(io::Error),
RonSpannedError(ron::error::SpannedError), RonSpanned(ron::error::SpannedError),
TeamCount(usize),
} }
impl fmt::Display for LogFileError { impl fmt::Display for LogFileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::FailedToOpen(err) => write!(f, "{}", err), Self::IOError(err) => write!(f, "{}", err),
Self::RonSpannedError(err) => write!(f, "{}", err), Self::RonSpanned(err) => write!(f, "{}", err),
Self::TeamCount(err) => write!(f, "Expected two, found: {:?}", err),
} }
} }
} }
#[derive(Debug)] impl std::error::Error for LogFileError {}
pub enum TeamsError {
NumberFound(usize),
}
impl fmt::Display for TeamsError { impl From<SpannedError> for LogFileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn from(value: SpannedError) -> Self {
match self { Self::RonSpanned(value)
Self::NumberFound(err) => write!(f, "Expected two, found: {:?}", err),
}
} }
} }
#[derive(Debug)] impl From<io::Error> for LogFileError {
pub struct NoTeamAttribute; fn from(value: io::Error) -> Self {
Self::IOError(value)
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,5 +1,6 @@
use crate::{Down, Play, Quarter, TerrainState, error}; use crate::{Down, Play, Quarter, TerrainState};
use serde::Deserialize; use serde::Deserialize;
use strum::EnumIter;
type Offence = Team; type Offence = Team;
@@ -14,35 +15,17 @@ pub enum Event {
} }
impl Event { impl Event {
/// Returns the terrain delta between the self and given following events.
/// Returns `None` if no delta can be calculated between
/// `self` and following events.
pub fn delta(&self, following: &Self) -> Option<i8> { pub fn delta(&self, following: &Self) -> Option<i8> {
// Clean this trash spaghetti code up. let preceeding = self.to_play()?;
fn make_play(event: &Event) -> Option<Play> {
match event {
Event::Kickoff(_) | Event::Turnover(_) => 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 = if let Event::Turnover(_) = following { let following = if let Event::Turnover(_) = following {
// I should really just early return // I should really just early return
// but this is too funny to look at. // but this is too funny to look at.
None? None?
} else { } else {
make_play(following)? following.to_play()?
}; };
if following.down? == Down::First { if following.down? == Down::First {
@@ -68,14 +51,18 @@ impl Event {
} }
} }
pub fn team(&self) -> Result<Team, error::NoTeamAttribute> { /// Returns the team for variants which possess this attribute.
/// Errors if `self` has no team attribute.
pub fn team(&self) -> Option<Team> {
match self { match self {
Self::Kickoff(team) => Ok(team.to_owned()), Self::Kickoff(team) => Some(team.to_owned()),
Self::Turnover(team) => Ok(team.to_owned()), Self::Turnover(team) => Some(team.to_owned()),
_ => Err(error::NoTeamAttribute), _ => None,
} }
} }
/// Returns the team for variants which possess this attribute.
/// Returns `None` if `self` has no team attribute.
pub fn quarter(&self) -> Option<Quarter> { pub fn quarter(&self) -> Option<Quarter> {
if let Event::Quarter(quarter) = self { if let Event::Quarter(quarter) = self {
Some(quarter.to_owned()) Some(quarter.to_owned())
@@ -83,16 +70,33 @@ impl Event {
None None
} }
} }
/// Converts an event into it's associated Play object, if there is one.
fn to_play(self: &Event) -> Option<Play> {
if let Event::Play(play) = self {
if play.down.is_none() || play.terrain.is_none() {
None
} else {
Some(play.to_owned())
}
} else if let Event::Kickoff(_) | Event::Turnover(_) = self {
Some(Play::default())
} else {
None
}
}
} }
#[derive(Debug, Deserialize, Clone, PartialEq)] #[derive(Debug, Deserialize, Clone, PartialEq, Default, EnumIter)]
pub enum Team { pub enum Team {
ArizonaState, ArizonaState,
#[deprecated(since = "0.2.0", note = "Team left the project.")] #[deprecated(since = "0.2.0", note = "Team left the project.")]
BoiseState, BoiseState,
Colorado, Colorado,
Iowa, Iowa,
#[default]
Nebraska, Nebraska,
#[deprecated(since = "0.7.3", note = "Team left the project.")]
SouthCarolina, SouthCarolina,
Syracuse, Syracuse,
TexasAnM, TexasAnM,

View File

@@ -1,36 +1,180 @@
use crate::error; use crate::{Action, Event, Game, Play, Team, TeamEvents, TerrainState, error};
use serde::Deserialize; use serde::Deserialize;
use std::{fs::File, path::PathBuf}; use std::{fs::File, path::PathBuf, usize};
use strum::IntoEnumIterator;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone, Default)]
pub struct LogFile(pub Vec<super::Game>); pub struct LogFile(pub Vec<Game>);
impl LogFile { impl LogFile {
pub fn min_ver(&self) -> semver::Version { /// Returns the most common action for a given team.
let mut lowest = semver::Version::new(u64::MAX, u64::MAX, u64::MAX); pub fn most_frequent_action(&self, team: Team) -> (Action, usize) {
let mut most_freq_action = Action::default();
let mut frequency = usize::MIN;
let mut found = usize::MIN;
let team_actions = self.team_actions(team).into_iter();
self.0.iter().for_each(|x| { Action::iter()
if x.version.cmp_precedence(&lowest).is_lt() { .filter(|action| *action != Action::Unknown)
lowest = x.version.clone() .for_each(|action| {
} found = team_actions.clone().filter(|a| *a == action).count();
});
lowest if found > frequency {
frequency = found;
most_freq_action = action.to_owned();
}
});
(most_freq_action, frequency)
} }
/// Returns if the LogFile min version is compatible. /// Returns the least common action for a given team.
pub fn is_compatible(&self) -> bool { /// This action has to have been played at least once.
self.min_ver().cmp_precedence(&crate::MIN_VER).is_lt() pub fn least_frequent_action(&self, team: Team) -> (Action, usize) {
let mut least_freq_action = Action::default();
let mut frequency = usize::MAX;
let mut found = usize::MAX;
let team_actions = self.team_actions(team).into_iter();
Action::iter()
.filter(|action| *action != Action::Unknown)
.for_each(|action| {
found = team_actions.clone().filter(|a| *a == action).count();
if (found != 0_usize) && (found < frequency) {
frequency = found;
least_freq_action = action.to_owned();
}
});
(least_freq_action, frequency)
}
pub fn frequency_of_plays(&self, team: Team) -> Vec<(Action, usize)> {
let team_actions = self.team_actions(team.to_owned()).into_iter();
let mut plays: Vec<(Action, usize)> = vec![];
Action::iter().for_each(|action| {
plays.push((
action.to_owned(),
team_actions.clone().filter(|a| *a == action).count(),
))
});
plays.sort_by(|a, b| a.1.cmp(&b.1));
plays
}
pub fn most_effective_play(&self, team: Team) -> (Action, TerrainState) {
let deltas = self
.0
.iter()
.map(|game| game.deltas(team.to_owned()))
.collect::<Vec<Vec<i8>>>();
let team_events: Vec<Vec<Event>> = self
.0
.iter()
.filter_map(|game| game.team_events(team.to_owned()))
.collect::<Vec<TeamEvents>>()
.iter()
.map(|team_events| team_events.0.to_owned())
.collect();
let mut action_return = Action::Unknown;
let mut terrain_delta: u8 = 0;
let mut action_deltas: Vec<i8>;
let mut game_idx: usize;
let mut event_idx: usize;
for action in Action::iter().filter(|action| *action != Action::Unknown) {
action_deltas = vec![];
game_idx = 0;
event_idx = 0;
for game in &team_events {
for _ in game {
if let Event::Play(play) = &team_events[game_idx][event_idx] {
if play.action == action {
action_deltas.push(deltas[game_idx][event_idx]);
}
}
if (event_idx + 1) == game.len() {
event_idx = 0;
continue;
} else {
event_idx += 1;
}
}
game_idx += 1;
}
let sum: u8 = action_deltas.iter().sum::<i8>() as u8;
if sum > terrain_delta {
terrain_delta = sum;
action_return = action.to_owned();
}
}
(action_return, TerrainState::Yards(terrain_delta))
}
pub fn check_teams(self) -> Result<LogFile, error::LogFileError> {
for game in &self.0 {
if let Err(err) = game.teams() {
return Err(err);
}
}
Ok(self)
}
/// Returns the team actions.
/// The following code is equivalent to:
fn team_actions(&self, team: Team) -> Vec<Action> {
// ```
// fn foo(&self, team: Team) -> Vec<Action> {
// let mut actions = vec![];
//
// for game in &self.0 {
// for play in game.team_plays(team.to_owned()).0 {
// actions.push(play.action.to_owned())
// }
// }
//
// actions
// }
// ```
// I just write iterators more naturally for some reason
// despite them being less readable afterward. I came from
// loving python's list comprehensions.
// I suppose I like the lack of nesting.
//
// I have no clue if the iterator is actually more efficient.
self.0
.iter()
.filter_map(|game| Some(game.team_plays(team.to_owned()).0))
.collect::<Vec<Vec<Play>>>()
.concat()
.iter()
.filter_map(|play| Some(play.action.to_owned()))
.collect::<Vec<Action>>()
} }
} }
impl TryFrom<File> for LogFile { impl TryFrom<File> for LogFile {
type Error = ron::error::SpannedError; type Error = error::LogFileError;
fn try_from(file: File) -> Result<Self, Self::Error> { fn try_from(file: File) -> Result<LogFile, Self::Error> {
ron::Options::default() let file: LogFile = ron::Options::default()
.with_default_extension(ron::extensions::Extensions::EXPLICIT_STRUCT_NAMES) .with_default_extension(ron::extensions::Extensions::EXPLICIT_STRUCT_NAMES)
.from_reader(file) .from_reader(file)?;
file.check_teams()
} }
} }
@@ -38,17 +182,89 @@ impl TryFrom<PathBuf> for LogFile {
type Error = error::LogFileError; type Error = error::LogFileError;
fn try_from(path: PathBuf) -> Result<Self, Self::Error> { fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
match Self::try_from( Self::try_from(
match std::fs::OpenOptions::new() // Defaults to setting all options false. std::fs::OpenOptions::new() // Defaults to setting all options false.
.read(true) // Only need ensure that reading is possible. .read(true) // Only need ensure that reading is possible.
.open(path.as_path()) .open(path.as_path())?,
{ )
Ok(f) => f, }
Err(err) => return Err(error::LogFileError::FailedToOpen(err)), }
},
) { #[cfg(test)]
Ok(f) => Ok(f), mod tests {
Err(err) => Err(error::LogFileError::RonSpannedError(err)), use crate::*;
}
#[test]
fn most_frequent_action() {
let a = LogFile(vec![Game {
events: vec![
Event::Kickoff(Team::Nebraska),
Event::Play(Play {
action: Action::Mesh,
..Default::default()
}),
Event::Play(Play {
action: Action::Mesh,
..Default::default()
}),
Event::Play(Play {
action: Action::Curls,
..Default::default()
}),
Event::Play(Play {
action: Action::Mesh,
..Default::default()
}),
Event::Play(Play {
action: Action::Curls,
..Default::default()
}),
Event::Play(Play {
action: Action::SlotOut,
..Default::default()
}),
Event::Kickoff(Team::ArizonaState),
],
..Default::default()
}]);
assert!(a.most_frequent_action(Team::Nebraska) == (Action::Mesh, 3_usize))
}
#[test]
fn least_frequent_action() {
let a = LogFile(vec![Game {
events: vec![
Event::Kickoff(Team::Nebraska),
Event::Play(Play {
action: Action::Mesh,
..Default::default()
}),
Event::Play(Play {
action: Action::Mesh,
..Default::default()
}),
Event::Play(Play {
action: Action::Mesh,
..Default::default()
}),
Event::Play(Play {
action: Action::Curls,
..Default::default()
}),
Event::Play(Play {
action: Action::SlotOut,
..Default::default()
}),
Event::Play(Play {
action: Action::Curls,
..Default::default()
}),
Event::Kickoff(Team::ArizonaState),
],
..Default::default()
}]);
assert!(a.least_frequent_action(Team::Nebraska) == (Action::SlotOut, 1_usize))
} }
} }

View File

@@ -1,4 +1,4 @@
use crate::{Event, Quarter, Team, error}; use crate::{Event, MIN_VER, Play, Quarter, Team, error};
use serde::Deserialize; use serde::Deserialize;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -10,8 +10,8 @@ pub struct Game {
} }
impl Game { impl Game {
/// Returns the teams of this game. /// Returns the teams that played.
pub fn teams(&self) -> Result<Vec<Team>, error::TeamsError> { pub fn teams(&self) -> Result<Vec<Team>, error::LogFileError> {
let ignore: Vec<Team> = self let ignore: Vec<Team> = self
.flags .flags
.iter() .iter()
@@ -27,7 +27,7 @@ impl Game {
let mut teams = vec![]; let mut teams = vec![];
self.events.iter().for_each(|event| { self.events.iter().for_each(|event| {
if let Ok(team) = event.team() { if let Some(team) = event.team() {
if !ignore.contains(&team) && !teams.contains(&team) { if !ignore.contains(&team) && !teams.contains(&team) {
teams.push(team) teams.push(team)
} }
@@ -37,13 +37,16 @@ impl Game {
if teams.len() == 2 || ignore.len() != 0 { if teams.len() == 2 || ignore.len() != 0 {
Ok(teams) Ok(teams)
} else { } else {
Err(error::TeamsError::NumberFound(teams.len())) Err(error::LogFileError::TeamCount(teams.len()))
} }
} }
/// Returns all of the terrain deltas of a team.
pub fn deltas(&self, team: Team) -> Vec<i8> { pub fn deltas(&self, team: Team) -> Vec<i8> {
let events: Vec<Event> = self let events: Vec<Event> = self
.team_events(team) .team_events(team)
.unwrap_or(TeamEvents(vec![]))
.0
.iter() .iter()
.filter_map(|event| { .filter_map(|event| {
if let Event::Quarter(_) = event { if let Event::Quarter(_) = event {
@@ -53,7 +56,13 @@ impl Game {
} }
}) })
.collect(); .collect();
let len = events.len() - 1;
let len = if events.len() == 0 {
return vec![0];
} else {
events.len() - 1
};
let mut idx: usize = 0; let mut idx: usize = 0;
let mut deltas: Vec<i8> = vec![]; let mut deltas: Vec<i8> = vec![];
@@ -68,48 +77,55 @@ impl Game {
deltas deltas
} }
pub fn team_plays(&self, team: Team) -> usize { /// Returns all of the plays of a team.
self.team_events(team) pub fn team_plays(&self, team: Team) -> TeamPlays {
.iter() TeamPlays(
.filter_map(|event| { self.team_events(team)
if let Event::Play(_) = event { .unwrap_or(TeamEvents(vec![]))
Some(event) .0
} else { .iter()
None .filter_map(|event| {
} if let Event::Play(play) = event {
}) Some(play.to_owned())
.collect::<Vec<&Event>>() } else {
.len() None
}
})
.collect::<Vec<Play>>(),
)
} }
/// The average number of plays in a quarter. /// The average number of plays in a quarter.
pub fn avg_plays_per_quarter(&self, team: Team) -> f32 { pub fn avg_plays_per_quarter(&self, team: Team) -> f64 {
let periods: Vec<Period> = Quarter::iter() let periods: Vec<Period> = Quarter::iter()
.filter_map(|quarter| Some(self.to_owned().get_period(quarter.to_owned())).to_owned()) .filter_map(|quarter| self.get_period(quarter.to_owned()))
.to_owned()
.collect(); .collect();
let quarterly_avgs: Vec<f32> = periods let quarterly_avgs: Vec<f64> = periods
.iter() .iter()
.filter_map(|period| { .filter_map(|period| {
if !period.is_overtime() { if !period.is_overtime() {
Some(period.team_plays(team.to_owned()) as f32) Some(period.team_plays(team.to_owned()) as f64)
} else { } else {
None None
} }
}) })
.collect::<Vec<f32>>(); .collect::<Vec<f64>>();
quarterly_avgs.iter().sum::<f32>() / quarterly_avgs.len() as f32 quarterly_avgs.iter().sum::<f64>() / quarterly_avgs.len() as f64
} }
pub fn avg_delta(&self, team: Team) -> f32 { /// Returns the average delta of a team.
pub fn avg_delta(&self, team: Team) -> f64 {
let deltas = self.deltas(team); let deltas = self.deltas(team);
// Summation doesn't like directly returning f32 from i8. // Summation doesn't like directly returning f64 from i8.
deltas.iter().sum::<i8>() as f32 / deltas.len() as f32 deltas.iter().sum::<i8>() as f64 / deltas.len() as f64
} }
pub fn avg_gain(&self, team: Team) -> f32 { /// Returns the average delta for a team's positive deltas.
pub fn avg_gain(&self, team: Team) -> f64 {
let deltas: Vec<u8> = self let deltas: Vec<u8> = self
.deltas(team) .deltas(team)
.iter() .iter()
@@ -122,11 +138,12 @@ impl Game {
}) })
.collect(); .collect();
// Summation doesn't like directly returning f32 from u8. // Summation doesn't like directly returning f64 from u8.
deltas.iter().sum::<u8>() as f32 / deltas.len() as f32 deltas.iter().sum::<u8>() as f64 / deltas.len() as f64
} }
pub fn avg_loss(&self, team: Team) -> f32 { /// Returns the average delta for a team's negative deltas.
pub fn avg_loss(&self, team: Team) -> f64 {
let deltas: Vec<i8> = self let deltas: Vec<i8> = self
.deltas(team) .deltas(team)
.iter() .iter()
@@ -139,11 +156,14 @@ impl Game {
}) })
.collect(); .collect();
deltas.iter().sum::<i8>() as f32 / deltas.len() as f32 deltas.iter().sum::<i8>() as f64 / deltas.len() as f64
} }
/// Returns the number of penalties that a team experienced.
pub fn penalties(&self, team: Team) -> usize { pub fn penalties(&self, team: Team) -> usize {
self.team_events(team) self.team_events(team)
.unwrap_or(TeamEvents(vec![]))
.0
.iter() .iter()
.filter_map(|event| { .filter_map(|event| {
if let Event::Penalty(_) = event { if let Event::Penalty(_) = event {
@@ -156,10 +176,13 @@ impl Game {
.len() .len()
} }
pub fn get_period(&self, quarter: Quarter) -> Period { /// Returns the requested quarter.
/// If there is no history logged for the requested quarter, returns `None`.
/// For example, if requesting an OT that doesn't exist.
pub fn get_period(&self, quarter: Quarter) -> Option<Period> {
let mut record = false; let mut record = false;
Period { let period = Period {
period: quarter.to_owned(), period: quarter.to_owned(),
events: self events: self
.events .events
@@ -176,10 +199,21 @@ impl Game {
None None
}) })
.collect::<Vec<Event>>(), .collect::<Vec<Event>>(),
};
if period.events.len() != 0 {
Some(period)
} else {
None
} }
} }
pub fn team_events(&self, team: Team) -> Vec<Event> { /// Returns all events relevent to a team's deltas and score.
pub fn team_events(&self, team: Team) -> Option<TeamEvents> {
if !self.teams().is_ok_and(|ok| ok.contains(&team)) {
return None;
}
let mut events: Vec<Event> = vec![]; let mut events: Vec<Event> = vec![];
let mut first = true; let mut first = true;
let mut record: bool = true; let mut record: bool = true;
@@ -210,10 +244,26 @@ impl Game {
}); });
// If already handled or assumption override applicable // If already handled or assumption override applicable
events Some(TeamEvents(events))
} }
} }
impl Default for Game {
fn default() -> Self {
Game {
version: MIN_VER,
flags: vec![],
events: vec![Event::Quarter(Quarter::default())],
}
}
}
#[derive(Debug)]
pub struct TeamEvents(pub Vec<Event>);
#[derive(Debug)]
pub struct TeamPlays(pub Vec<Play>);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Period { pub struct Period {
period: Quarter, period: Quarter,
@@ -221,6 +271,7 @@ pub struct Period {
} }
impl Period { impl Period {
/// Returns all events relevent to a team's deltas and score.
pub fn team_events(&self, team: Team) -> Vec<Event> { pub fn team_events(&self, team: Team) -> Vec<Event> {
let mut events: Vec<Event> = vec![]; let mut events: Vec<Event> = vec![];
let mut first = true; let mut first = true;
@@ -254,6 +305,7 @@ impl Period {
events events
} }
/// Returns all of the plays of a team.
pub fn team_plays(&self, team: Team) -> usize { pub fn team_plays(&self, team: Team) -> usize {
self.team_events(team) self.team_events(team)
.iter() .iter()
@@ -268,6 +320,7 @@ impl Period {
.len() .len()
} }
/// Returns true if the current period is overtime.
pub fn is_overtime(&self) -> bool { pub fn is_overtime(&self) -> bool {
if let Quarter::Overtime(_) = self.period { if let Quarter::Overtime(_) = self.period {
true true
@@ -279,14 +332,17 @@ impl Period {
#[derive(Debug, Deserialize, Clone, PartialEq)] #[derive(Debug, Deserialize, Clone, PartialEq)]
pub enum Flags { pub enum Flags {
ClockBleed(Team),
Gameplan(u8),
IgnoreActions, IgnoreActions,
IgnoreTeam(Team), IgnoreTeam(Team),
IgnoreScore, IgnoreScore,
Interval(u8), Interval(u8),
SheerDumbFuckingLuck LowDataCredibility,
SheerDumbFuckingLuck,
Stub,
} }
/*
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::*; use crate::*;
@@ -294,8 +350,6 @@ mod tests {
#[test] #[test]
fn avg_plays_per_quarter() { fn avg_plays_per_quarter() {
let a = Game { let a = Game {
version: crate::MIN_VER,
flags: vec![],
events: vec![ events: vec![
Event::Quarter(Quarter::First), Event::Quarter(Quarter::First),
Event::Kickoff(Team::Nebraska), Event::Kickoff(Team::Nebraska),
@@ -311,201 +365,103 @@ mod tests {
Event::Play(Play::default()), Event::Play(Play::default()),
Event::Turnover(Team::ArizonaState), Event::Turnover(Team::ArizonaState),
], ],
..Default::default()
}; };
let b = Game { let b = Game {
version: crate::MIN_VER, events: vec![
flags: vec![], Event::Quarter(Quarter::First),
periods: vec![Period { Event::Turnover(Team::Nebraska),
start: Quarter::Second, Event::Play(Play::default()),
end: Some(Quarter::Fourth), Event::Turnover(Team::ArizonaState),
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()),
],
},
], ],
..Default::default()
}; };
assert!(a.team_plays(Team::Nebraska) == 12_usize) dbg!(a.avg_plays_per_quarter(Team::Nebraska));
dbg!(b.avg_plays_per_quarter(Team::Nebraska));
assert!(a.avg_plays_per_quarter(Team::Nebraska) == ((1_f64 + 6_f64) / 2_f64));
assert!(b.avg_plays_per_quarter(Team::Nebraska) == 1_f64)
} }
#[test] #[test]
#[allow(deprecated)] #[allow(deprecated)]
fn teams() { fn teams() {
let a = Game { let a = Game {
version: crate::MIN_VER, events: vec![
flags: vec![], Event::Kickoff(Team::Nebraska),
periods: vec![ Event::Turnover(Team::ArizonaState),
Period { Event::Kickoff(Team::Nebraska),
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),
],
},
], ],
..Default::default()
}; };
let b = Game { let b = Game {
version: crate::MIN_VER, events: vec![
flags: vec![], Event::Kickoff(Team::Nebraska),
periods: vec![ Event::Turnover(Team::ArizonaState),
Period { Event::Kickoff(Team::BoiseState),
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),
],
},
], ],
..Default::default()
}; };
let c = Game { let c = Game {
version: crate::MIN_VER,
flags: vec![Flags::IgnoreTeam(Team::Nebraska)], flags: vec![Flags::IgnoreTeam(Team::Nebraska)],
periods: vec![ events: vec![Event::Kickoff(Team::Nebraska)],
Period { ..Default::default()
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!(a.teams().unwrap() == vec![Team::Nebraska, Team::ArizonaState]);
assert!(b.teams().is_err() == true); assert!(b.teams().is_err() == true);
assert!(c.teams().unwrap() == vec![Team::ArizonaState]); assert!(c.teams().unwrap() == vec![]);
assert!(d.teams().unwrap() == vec![]);
} }
#[test] #[test]
fn deltas() { fn deltas() {
let game = Game { let game = Game {
version: crate::MIN_VER, events: vec![
flags: vec![], Event::Kickoff(Team::Nebraska),
periods: vec![ Event::Play(Play {
Period { action: Action::Unknown,
start: Quarter::First, down: Some(Down::First),
end: None, terrain: Some(TerrainState::Yards(10)),
events: vec![ }),
Event::Kickoff(Team::Nebraska), Event::Play(Play {
Event::Play(Play { action: Action::Unknown,
action: Action::Unknown, down: Some(Down::Second),
down: Some(Down::First), terrain: Some(TerrainState::Yards(13)),
terrain: Some(TerrainState::Yards(10)), }),
}), Event::Play(Play {
Event::Play(Play { action: Action::Unknown,
action: Action::Unknown, down: Some(Down::Third),
down: Some(Down::Second), terrain: Some(TerrainState::Yards(8)),
terrain: Some(TerrainState::Yards(13)), }),
}), Event::Turnover(Team::ArizonaState),
Event::Play(Play { Event::Play(Play {
action: Action::Unknown, action: Action::Unknown,
down: Some(Down::Third), down: Some(Down::First),
terrain: Some(TerrainState::Yards(8)), terrain: Some(TerrainState::Yards(10)),
}), }),
Event::Turnover(Team::ArizonaState), Event::Play(Play {
Event::Play(Play { action: Action::Unknown,
action: Action::Unknown, down: Some(Down::Second),
down: Some(Down::First), terrain: Some(TerrainState::Yards(10)),
terrain: Some(TerrainState::Yards(10)), }),
}), Event::Turnover(Team::Nebraska),
Event::Play(Play { Event::Play(Play {
action: Action::Unknown, action: Action::Unknown,
down: Some(Down::Second), down: Some(Down::Second),
terrain: Some(TerrainState::Yards(10)), terrain: Some(TerrainState::Yards(12)),
}), }),
Event::Turnover(Team::Nebraska), Event::Play(Play {
Event::Play(Play { action: Action::Unknown,
action: Action::Unknown, down: Some(Down::First),
down: Some(Down::Second), terrain: Some(TerrainState::Yards(10)),
terrain: Some(TerrainState::Yards(12)), }),
}), Event::Turnover(Team::ArizonaState),
],
},
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),
],
},
], ],
..Default::default()
}; };
assert!(game.deltas(Team::Nebraska) == vec![10_i8, -3_i8, 5_i8, -2_i8, 12_i8]); assert!(game.deltas(Team::Nebraska) == vec![10_i8, -3_i8, 5_i8, -2_i8, 12_i8]);
@@ -513,76 +469,39 @@ mod tests {
} }
#[test] #[test]
#[allow(deprecated)]
fn team_events() { fn team_events() {
let a = Period { let a = Game {
start: Quarter::First,
end: None,
events: vec![ events: vec![
Event::Kickoff(Team::Nebraska), Event::Kickoff(Team::Nebraska),
Event::Play(Play::default()), Event::Play(Play::default()),
Event::Turnover(Team::ArizonaState), Event::Turnover(Team::SouthCarolina),
Event::Play(Play::default()), Event::Play(Play::default()),
Event::Play(Play::default()), Event::Play(Play::default()),
Event::Kickoff(Team::Nebraska), Event::Kickoff(Team::Nebraska),
Event::Score(ScorePoints::Touchdown), Event::Score(ScorePoints::Touchdown),
Event::Kickoff(Team::SouthCarolina), Event::Kickoff(Team::SouthCarolina),
], ],
}; ..Default::default()
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!( assert!(
a.team_events(Team::Nebraska, None).unwrap() a.team_events(Team::Nebraska).unwrap().0
== vec![ == vec![
Event::Kickoff(Team::Nebraska), Event::Kickoff(Team::Nebraska),
Event::Play(Play::default()), Event::Play(Play::default()),
Event::Turnover(Team::ArizonaState), Event::Turnover(Team::SouthCarolina),
Event::Kickoff(Team::Nebraska), Event::Kickoff(Team::Nebraska),
Event::Score(ScorePoints::Touchdown), Event::Score(ScorePoints::Touchdown),
Event::Kickoff(Team::SouthCarolina), Event::Kickoff(Team::SouthCarolina),
] ]
); );
assert!( assert!(a.team_events(Team::BoiseState).is_none());
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] #[test]
fn team_plays() { fn team_plays() {
let period = Period { let game = Game {
start: Quarter::First,
end: None,
events: vec![ events: vec![
Event::Kickoff(Team::Nebraska), Event::Kickoff(Team::Nebraska),
Event::Play(Play::default()), Event::Play(Play::default()),
@@ -592,65 +511,17 @@ mod tests {
Event::Kickoff(Team::Nebraska), Event::Kickoff(Team::Nebraska),
Event::Play(Play::default()), Event::Play(Play::default()),
Event::Score(ScorePoints::default()), Event::Score(ScorePoints::default()),
Event::Kickoff(Team::SouthCarolina), Event::Kickoff(Team::ArizonaState),
Event::Play(Play::default()), Event::Play(Play::default()),
Event::Turnover(Team::Nebraska), Event::Turnover(Team::Nebraska),
Event::Play(Play::default()), Event::Play(Play::default()),
], ],
..Default::default()
}; };
assert!( assert!(
period.team_plays(Team::Nebraska, None).unwrap() game.team_plays(Team::Nebraska).0
== vec![Play::default(), Play::default(), Play::default()] == 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

@@ -1,8 +1,9 @@
use serde::Deserialize; use serde::Deserialize;
use strum::EnumIter; use strum::EnumIter;
#[derive(Debug, Deserialize, Clone, PartialEq, EnumIter)] #[derive(Debug, Deserialize, Clone, PartialEq, EnumIter, Default)]
pub enum Quarter { pub enum Quarter {
#[default]
First, First,
Second, Second,
Third, Third,

View File

@@ -1,10 +1,14 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Deserialize, Clone, Default, PartialEq)] #[derive(Debug, Deserialize, Clone, PartialEq)]
pub enum TerrainState { pub enum TerrainState {
Yards(u8), Yards(u8),
GoalLine, GoalLine,
Inches, Inches,
#[default] }
Unknown,
impl Default for TerrainState {
fn default() -> Self {
TerrainState::Yards(10)
}
} }

35
miller/Cargo.lock generated
View File

@@ -58,9 +58,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.32" version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -68,9 +68,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.32" version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"clap_lex", "clap_lex",
@@ -205,7 +205,7 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]] [[package]]
name = "gamelog" name = "gamelog"
version = "0.7.1" version = "0.7.3"
dependencies = [ dependencies = [
"ron", "ron",
"semver", "semver",
@@ -215,9 +215,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@@ -272,9 +272,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.171" version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
@@ -314,6 +314,7 @@ dependencies = [
"clap", "clap",
"gamelog", "gamelog",
"ratatui", "ratatui",
"strum 0.27.1",
] ]
[[package]] [[package]]
@@ -359,9 +360,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.94" version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -407,9 +408,9 @@ dependencies = [
[[package]] [[package]]
name = "ron" name = "ron"
version = "0.9.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63f3aa105dea217ef30d89581b65a4d527a19afc95ef5750be3890e8d3c5b837" checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f"
dependencies = [ dependencies = [
"base64", "base64",
"bitflags", "bitflags",
@@ -501,9 +502,9 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.2" version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -572,9 +573,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.100" version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -6,6 +6,7 @@ license = "MIT"
[dependencies] [dependencies]
ratatui = "0.29" ratatui = "0.29"
strum = "0.27"
[dependencies.clap] [dependencies.clap]
version = "4.5" version = "4.5"

View File

@@ -2,8 +2,9 @@ mod tui;
use clap::{ArgAction, Parser}; use clap::{ArgAction, Parser};
use core::panic; use core::panic;
use gamelog::{Action, Down, Flags, Key, LogFile, Team}; use gamelog::{Action, Down, Flags, Key, LogFile, Team, TerrainState};
use std::{io, path::PathBuf, sync::mpsc, thread}; use std::{io, path::PathBuf, sync::mpsc, thread};
use strum::IntoEnumIterator;
use tui::App; use tui::App;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
@@ -42,17 +43,15 @@ fn main() -> io::Result<()> {
TeamStats::new(Team::Colorado), TeamStats::new(Team::Colorado),
TeamStats::new(Team::Iowa), TeamStats::new(Team::Iowa),
TeamStats::new(Team::Nebraska), TeamStats::new(Team::Nebraska),
TeamStats::new(Team::Syracuse), #[allow(deprecated)]
TeamStats::new(Team::SouthCarolina), TeamStats::new(Team::SouthCarolina),
TeamStats::new(Team::Syracuse),
TeamStats::new(Team::TexasAnM), TeamStats::new(Team::TexasAnM),
]; ];
// Work on knocking down the nesting here? // Work on knocking down the nesting here?
for game in log.0.iter() { for game in log.0.iter() {
let teams = match game.teams() { let teams = game.teams().unwrap();
Ok(teams) => teams,
Err(_) => continue,
};
for team in teams { for team in teams {
// Skip team if they are to be ignored this game. // Skip team if they are to be ignored this game.
@@ -83,12 +82,28 @@ fn main() -> io::Result<()> {
stats[team_idx] stats[team_idx]
.plays_per_game .plays_per_game
.push(game.team_plays(team.to_owned())); .push(game.team_plays(team.to_owned()).0.len());
stats[team_idx] stats[team_idx]
.penalties_per_game .penalties_per_game
.push(game.penalties(team.to_owned())); .push(game.penalties(team.to_owned()));
} }
for team in gamelog::Team::iter() {
let team_idx = stats
.iter()
.position(|stat| stat.team == team.to_owned())
.unwrap();
stats[team_idx].most_common_play = Some(log.most_frequent_action(team.to_owned()));
stats[team_idx].least_common_play =
Some(log.least_frequent_action(team.to_owned()));
//stats[team_idx].most_effective_play =
// Some(log.most_effective_play(team.to_owned()));
//stats[team_idx].play_frequencies = Some(log.frequency_of_plays(team.to_owned()))
}
} }
// :#? for pretty-printing. // :#? for pretty-printing.
@@ -119,22 +134,21 @@ fn main() -> io::Result<()> {
struct TeamStats { struct TeamStats {
team: gamelog::Team, team: gamelog::Team,
// Terrain // Terrain
avg_terrain_gain: Vec<f32>, avg_terrain_gain: Vec<f64>,
avg_terrain_loss: Vec<f32>, avg_terrain_loss: Vec<f64>,
avg_terrain_delta: Vec<f32>, avg_terrain_delta: Vec<f64>,
// Play rate // Play rate
plays_per_quarter: Vec<f32>, plays_per_quarter: Vec<f64>,
plays_per_game: Vec<usize>, plays_per_game: Vec<usize>,
// Penalties // Penalties
penalties_per_game: Vec<usize>, penalties_per_game: Vec<usize>,
// Score
points_per_quarter: Vec<u8>,
points_per_game: Vec<u8>,
// Biases // Biases
most_common_play: Option<Action>, most_common_play: Option<(Action, usize)>,
least_common_play: Option<Action>, least_common_play: Option<(Action, usize)>,
most_common_key: Option<Key>, most_common_key: Option<Key>,
least_common_key: Option<Key>, least_common_key: Option<Key>,
most_effective_play: Option<(Action, TerrainState)>,
play_frequencies: Option<Vec<(Action, usize)>>,
// Traits // Traits
// Typical number of downs to achieve 10 yards. // Typical number of downs to achieve 10 yards.
time_to_first_down: Option<Down>, time_to_first_down: Option<Down>,
@@ -150,12 +164,12 @@ impl TeamStats {
plays_per_quarter: vec![], plays_per_quarter: vec![],
plays_per_game: vec![], plays_per_game: vec![],
penalties_per_game: vec![], penalties_per_game: vec![],
points_per_quarter: vec![],
points_per_game: vec![],
most_common_play: None, most_common_play: None,
least_common_play: None, least_common_play: None,
most_common_key: None, most_common_key: None,
least_common_key: None, least_common_key: None,
most_effective_play: None,
play_frequencies: None,
time_to_first_down: None, time_to_first_down: None,
} }
} }

View File

@@ -4,22 +4,16 @@
[ [
Game( Game(
version: "0.5.0", version: "0.7.0",
flags: [], flags: [],
periods: [ events: [
Period( Quarter(First),
start: First, Kickoff(Nebraska),
end: Fourth, Play(
events: [ action: Unknown,
Kickoff(Nebraska), down: Second,
Play( terrain: Yards(10),
action: Unknown, ),
down: First,
terrain: Yards(10)
),
Score(FieldGoal),
]
)
] ]
) )
] ]

View File

@@ -1,52 +0,0 @@
// Zed settings
//
// For information on how to configure Zed, see the Zed
// documentation: https://zed.dev/docs/configuring-zed
//
// To see all of Zed's default settings without changing your
// custom settings, run `zed: open default settings` from the
// command palette (cmd-shift-p / ctrl-shift-p)
{
"telemetry": {
"diagnostics": true,
"metrics": false,
},
"ui_font_size": 16,
"buffer_font_size": 13,
"icon_theme": "Colored Zed Icons Theme Dark",
"theme": {
"mode": "system",
"light": "One Light",
"dark": "One Dark",
},
"restore_on_startup": "last_workspace",
"soft_wrap": "preferred_line_length",
// Remove AI Crap
"features": {
"edit_prediction_provider": "none",
"copilot": false,
},
"assistant": {
"version": "1",
"enabled": false,
},
// Language Overrides
"languages": {
"Python": {
"show_wrap_guides": true,
"preferred_line_length": 80,
},
"Rust": {
"show_wrap_guides": true,
"preferred_line_length": 100,
},
"JSON": {
"tab_size": 4,
},
"JSONC": {
"tab_size": 4,
},
},
}