This commit is contained in:
Cutieguwu
2025-03-30 22:16:10 -04:00
parent fc00146b47
commit 65e202d78e
14 changed files with 349 additions and 102 deletions

35
gamelog/Cargo.lock generated
View File

@@ -24,8 +24,15 @@ dependencies = [
"ron",
"semver",
"serde",
"strum",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "proc-macro2"
version = "1.0.94"
@@ -57,6 +64,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "semver"
version = "1.0.26"
@@ -86,6 +99,28 @@ dependencies = [
"syn",
]
[[package]]
name = "strum"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "2.0.100"

View File

@@ -6,6 +6,10 @@ edition = "2024"
[dependencies]
ron = "0.9"
[dependencies.strum]
version = "0.27"
features = ["derive"]
[dependencies.semver]
version = "1.0"
features = ["serde"]

138
gamelog/src/action.rs Normal file
View File

@@ -0,0 +1,138 @@
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
pub enum Action {
CrackStudentBodyRightTackle,
Curls,
FleaFlicker,
HalfbackSlam,
HalfbackSlipScreen,
HalfbackSweep,
Mesh,
PlayActionBoot,
PlayActionComebacks,
PlayActionPowerZero,
PowerZero,
SlantBubble,
SlotOut,
SpeedOption,
StrongFlood,
#[default]
Unknown,
}
impl Action {
// I'd love a better way of doing these
// Attributes are probably the way to go,
// but I'm not about to write procedural macros for this project.
/// Returns `true` if `self` is a play action.
pub fn is_play_action(&self) -> bool {
if let Self::PlayActionBoot | Self::PlayActionComebacks | Self::PlayActionPowerZero = self {
true
} else {
false
}
}
/// Returns `true` if `self` is a halfback.
pub fn is_halfback(&self) -> bool {
if let Self::HalfbackSlam | Self::HalfbackSlipScreen | Self::HalfbackSweep = self {
true
} else {
true
}
}
/// Returns `true` if `self` is a running play.
pub fn is_run(&self) -> bool {
if let Self::HalfbackSlam
| Self::SpeedOption
| Self::HalfbackSweep
| Self::PowerZero
| Self::CrackStudentBodyRightTackle = self
{
true
} else {
false
}
}
/// Returns `true` if `self` is a passing play.
pub fn is_pass(&self) -> bool {
!self.is_run()
}
/// Returns `true` if `self` is `Event::Unknown`.
pub fn is_unknown(&self) -> bool {
if let Self::Unknown = self {
true
} else {
false
}
}
/// Returns the `Playset` that this action belongs to.
/// Returns `None` if `Event::Unknown`
pub fn playset(&self) -> Option<Playset> {
if self.is_unknown() {
return None;
}
Some(match self {
Self::SlantBubble | Self::HalfbackSlam | Self::PlayActionBoot => Playset::PistolSpread,
Self::StrongFlood | Self::SpeedOption | Self::HalfbackSlipScreen => {
Playset::ShotgunTripleWingsOffset
}
Self::SlotOut | Self::HalfbackSweep | Self::PlayActionComebacks => {
Playset::ShotgunDoubleFlex
}
Self::Curls | Self::PowerZero | Self::PlayActionPowerZero => Playset::IFormNormal,
Self::Mesh | Self::CrackStudentBodyRightTackle | Self::FleaFlicker => {
Playset::IFormTight
}
_ => unreachable!(),
})
}
/// Returns the `Key` that this action belongs to.
/// Returns `None` if `Event::Unknown`
pub fn key(&self) -> Option<Key> {
if self.is_unknown() {
return None;
}
// All running plays are on `Key::X`
if self.is_run() {
return Some(Key::X);
}
Some(match self {
Self::SlantBubble | Self::StrongFlood | Self::SlotOut | Self::Curls | Self::Mesh => {
Key::Square
}
Self::PlayActionBoot
| Self::HalfbackSlipScreen
| Self::PlayActionComebacks
| Self::PlayActionPowerZero
| Self::FleaFlicker => Key::Triangle,
_ => unreachable!(),
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Playset {
PistolSpread,
ShotgunTripleWingsOffset,
ShotgunDoubleFlex,
IFormNormal,
IFormTight,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Key {
Square,
X,
Triangle,
}

View File

@@ -4,32 +4,26 @@ use std::{fmt, io};
pub enum LogFileError {
FailedToOpen(io::Error),
RonSpannedError(ron::error::SpannedError),
CompatibilityCheck(semver::Version),
}
impl fmt::Display for LogFileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FailedToOpen(err) => write!(f, "{}", err),
Self::CompatibilityCheck(ver) => write!(
f,
"GameLogs cannot be older than {}, but {} was found in logfile.",
crate::MIN_VER.to_string(),
ver.to_string()
),
Self::RonSpannedError(err) => write!(f, "{}", err),
}
}
}
pub enum DownError {
NotKickoff,
#[derive(Debug)]
pub enum TeamsError {
NumberFound(usize),
}
impl fmt::Display for DownError {
impl fmt::Display for TeamsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::NotKickoff => write!(f, "Variant was not Down::Kickoff."),
match self {
Self::NumberFound(err) => write!(f, "Expected two, found: {:?}", err),
}
}
}

14
gamelog/src/event.rs Normal file
View File

@@ -0,0 +1,14 @@
use crate::{Play, ScoreChange, Team, TerrainState};
use serde::Deserialize;
type Offence = Team;
#[derive(Debug, Deserialize, Clone)]
pub enum Event {
Play(Play),
Kickoff(Offence),
Turnover(Offence),
Penalty(TerrainState),
ScoreChange(ScoreChange),
}

View File

@@ -5,6 +5,25 @@ use std::{fs::File, path::PathBuf};
#[derive(Debug, Deserialize, Clone)]
pub struct LogFile(Vec<super::Game>);
impl LogFile {
pub fn min_ver(&self) -> semver::Version {
let mut lowest = semver::Version::new(u64::MAX, u64::MAX, u64::MAX);
self.0.iter().for_each(|x| {
if x.version.cmp_precedence(&lowest).is_lt() {
lowest = x.version.clone()
}
});
lowest
}
/// Returns if the LogFile min version is compatible.
pub fn is_compatible(&self) -> bool {
self.min_ver().cmp_precedence(&super::MIN_VER).is_lt()
}
}
impl TryFrom<File> for LogFile {
type Error = ron::error::SpannedError;
@@ -31,34 +50,3 @@ impl TryFrom<PathBuf> for LogFile {
}
}
}
impl LogFile {
pub fn get_min_ver(self) -> semver::Version {
let mut lowest = semver::Version::new(u64::MAX, u64::MAX, u64::MAX);
self.0.iter().for_each(|x| {
if x.version.cmp_precedence(&lowest).is_lt() {
lowest = x.version.clone()
}
});
lowest
}
/// Returns if the LogFile min version is compatible.
fn is_compatible(&self) -> bool {
self.clone()
.get_min_ver()
.cmp_precedence(&super::MIN_VER)
.is_lt()
}
/// Ensures that the returned gamefile is compatible, else returns Error.
pub fn ensure_compatible(self) -> Result<Self, error::LogFileError> {
if self.is_compatible() {
Ok(self)
} else {
Err(error::LogFileError::CompatibilityCheck(self.get_min_ver()))
}
}
}

44
gamelog/src/game.rs Normal file
View File

@@ -0,0 +1,44 @@
use crate::{Event, Period, Play, PlayHandle, Team, error};
use serde::Deserialize;
#[deprecated(since = "0.2.0", note = "Migrated to Game")]
pub type GameRecord = Game;
#[derive(Debug, Deserialize, Clone)]
pub struct Game {
pub version: semver::Version,
pub periods: Vec<Period>,
}
impl Game {
/// Returns the teams of this game.
pub fn teams(&self) -> Result<Vec<Team>, error::TeamsError> {
let mut teams = vec![];
self.periods.iter().for_each(|period| {
period.events.iter().for_each(|event| {
if let Event::Kickoff(t) | Event::Turnover(t) = event {
if teams.contains(t) {
teams.push(t.to_owned())
}
}
})
});
if teams.len() == 2 {
Ok(teams)
} else {
Err(error::TeamsError::NumberFound(teams.len()))
}
}
}
impl PlayHandle for Game {
fn plays(&self) -> Vec<Play> {
self.periods
.iter()
.map(|period| period.plays())
.collect::<Vec<Vec<Play>>>() // Make compiler happy with turbofish.
.concat()
}
}

View File

@@ -1,13 +1,23 @@
#![allow(deprecated)]
mod action;
mod error;
mod event;
mod file;
mod game;
mod period;
mod play;
mod score;
mod terrain;
#[allow(unused)]
pub const MIN_VER: semver::Version = semver::Version::new(0, 3, 0);
pub use action::*;
pub use event::Event;
pub use file::LogFile;
pub use game::{Game, GameRecord};
pub use period::*;
pub use play::*;
pub use score::ScoreChange;
pub use terrain::TerrainState;

View File

@@ -1,22 +1,29 @@
use crate::{Event, PlayHandle};
use serde::Deserialize;
#[deprecated(since = "0.2.0", note = "Migrated to Game")]
type GameRecord = Game;
#[derive(Debug, Deserialize, Clone)]
pub struct Game {
pub version: semver::Version,
periods: Vec<Option<Period>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct Period {
start: Quarter,
end: Option<Quarter>,
plays: Vec<super::Play>,
pub start: Quarter,
pub end: Option<Quarter>,
pub events: Vec<Event>,
}
#[derive(Debug, Deserialize, Clone)]
impl PlayHandle for Period {
fn plays(&self) -> Vec<crate::Play> {
self.events
.iter()
.filter_map(|event| {
if let Event::Play(play) = event {
Some(play.to_owned())
} else {
None
}
})
.collect()
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
pub enum Quarter {
First,
Second,

View File

@@ -1,57 +1,35 @@
use crate::{TerrainState, error};
use crate::{Action, TerrainState};
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub trait PlayHandle {
/// Returns all plays within object's scope.
fn plays(&self) -> Vec<Play>;
}
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
pub struct Play {
down: Down,
terrain: TerrainState,
pub action: Action,
pub down: Down,
pub terrain: TerrainState,
}
type Offence = Team;
impl Offence {}
#[derive(Debug, Deserialize, Clone)]
pub enum Event {
CrackStudentBodyRightTackle(Play),
Curls(Play),
FleaFlicker(Play),
HalfbackSlam(Play),
HalfbackSlipScreen(Play),
HalfbackSweep(Play),
Mesh(Play),
PlayActionBoot(Play),
PlayActionComebacks(Play),
PlayActionPowerZero(Play),
PowerZero(Play),
SlantBubble(Play),
SlotOut(Play),
SpeedOption(Play),
StrongFlood(Play),
Unknown(Play),
Kickoff { offence: Team },
Turnover { offence: Team },
Penalty { terrain: TerrainState },
impl PlayHandle for Play {
fn plays(&self) -> Vec<Play> {
vec![self.to_owned()]
}
}
#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
pub enum Down {
#[default]
First,
Second,
Third,
Fourth,
PointAfterTouchdown,
PointAfterTouchdown(Option<u8>),
}
impl Down {
fn get_offence(&self) -> Result<&Team, error::DownError> {
match self {
Self::Kickoff { offence } => Ok(offence),
_ => Err(error::DownError::NotKickoff),
}
}
}
#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Clone, PartialEq)]
pub enum Team {
ArizonaState,
#[deprecated(since = "0.2.0", note = "Team left the project.")]

8
gamelog/src/score.rs Normal file
View File

@@ -0,0 +1,8 @@
use crate::Team;
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone, PartialEq)]
pub struct ScoreChange {
pub team: Team,
pub score: u8,
}

View File

@@ -1,11 +1,12 @@
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
pub enum TerrainState {
#[deprecated(since = "0.2.0", note = "Replaced in favour of TerrainState::Yards")]
Distance(u8),
Yards(u8),
GoalLine,
Inches,
#[default]
Unknown,
}