use std::collections::{HashMap, HashSet};
use crate::event::{Event, Info, PlayDescription, Player, Team};
pub use self::{Error as GameError, State as GameState};
pub type EventWithComments = (Event, Vec<Event>);
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
InvalidEvent(State, Event),
InvalidEventBeforeSub(Event),
NoEventBeforeSub,
ParsingDone,
ParsingIncomplete,
Unimplemented,
}
impl ::std::fmt::Display for Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Error::InvalidEvent(ref state, ref event) => write!(
f,
"the event {:?} is not valid in the {} state",
event, state
),
Error::InvalidEventBeforeSub(ref event) => write!(
f,
"substitutions must be preceded by a play event; got {:?}",
event
),
Error::NoEventBeforeSub => write!(f, "substitutions must be preceded by a play event"),
Error::ParsingDone => write!(f, "parsing already completed"),
Error::ParsingIncomplete => {
write!(f, "parsing not fully done before attempted completion")
}
Error::Unimplemented => write!(f, "the requested command is not yet implemented"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Substitution {
pub inning: u8,
pub batting_team: Team,
pub player: Player,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum State {
Info,
Starters,
Plays,
Data,
Done,
}
impl ::std::fmt::Display for State {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
State::Info => write!(f, "metadata parsing"),
State::Starters => write!(f, "starting roster parsing"),
State::Plays => write!(f, "play-by-play parsing"),
State::Data => write!(f, "data parsing"),
State::Done => write!(f, "finished"),
}
}
}
#[derive(Clone, Debug)]
pub struct Game {
pub id: String,
pub info: HashMap<Info, String>,
pub starters: HashSet<Player>,
pub plays: Vec<EventWithComments>,
pub substitutions: Vec<Substitution>,
pub data: Vec<Event>,
pub(crate) state: State,
}
impl PartialEq for Game {
fn eq(&self, other: &Game) -> bool {
self.id == other.id
&& self.info == other.info
&& self.starters == other.starters
&& self.plays == other.plays
&& self.substitutions == other.substitutions
&& self.data == other.data
}
}
impl Eq for Game {}
impl Game {
pub fn new<S: Into<String>>(id: S) -> Game {
Game {
id: id.into(),
info: HashMap::new(),
state: State::Info,
starters: HashSet::new(),
plays: vec![],
substitutions: vec![],
data: vec![],
}
}
fn process_info_event(&mut self, event: Event) -> Result<(), Error> {
match event {
Event::Info { key, data } => {
self.info.insert(key, data);
Ok(())
}
Event::Start { .. } => {
self.state = State::Starters;
self.process_event(event)
}
Event::Comment { .. } => Ok(()),
_ => Err(Error::InvalidEvent(self.state, event.clone())),
}
}
fn process_starter_event(&mut self, event: Event) -> Result<(), Error> {
match event {
Event::Start { player } => {
self.starters.insert(player);
Ok(())
}
Event::Play { .. } | Event::BattingAdjustment { .. } => {
self.state = State::Plays;
self.process_event(event)
}
Event::Comment { .. } => Ok(()),
_ => Err(Error::InvalidEvent(self.state, event.clone())),
}
}
fn process_play_event(&mut self, event: Event) -> Result<(), Error> {
match event {
Event::Play { .. } => {
self.plays.push((event, vec![]));
Ok(())
}
Event::BattingAdjustment { .. }
| Event::PitchingAdjustment { .. }
| Event::LineupAdjustment { .. } => {
self.plays.push((event, vec![]));
Ok(())
}
Event::Sub { player } => {
let last_evt = self.plays.pop();
match last_evt {
Some(event) => match event.0 {
Event::Play {
ref inning,
ref team,
event: ref play_event,
..
} => {
if play_event.description != PlayDescription::NoPlay {
Err(Error::InvalidEventBeforeSub(event.0.clone()))
} else {
self.substitutions.push(Substitution {
inning: *inning,
batting_team: *team,
player,
});
Ok(())
}
}
_ => Err(Error::InvalidEventBeforeSub(event.0.clone())),
},
None => Err(Error::NoEventBeforeSub),
}
}
Event::Comment { .. } => {
let len = self.plays.len();
self.plays[len - 1].1.push(event);
Ok(())
}
Event::Data { .. } => {
self.state = State::Data;
self.process_event(event)
}
_ => Err(Error::InvalidEvent(self.state, event.clone())),
}
}
fn process_data_event(&mut self, event: Event) -> Result<(), Error> {
match event {
Event::Data { .. } => {
self.data.push(event);
Ok(())
}
Event::Comment { .. } => Ok(()),
_ => Err(Error::InvalidEvent(self.state, event.clone())),
}
}
pub fn finish(&mut self) -> Result<(), Error> {
if let State::Data = self.state {
self.state = State::Done;
Ok(())
} else {
Err(Error::ParsingIncomplete)
}
}
pub fn process_event(&mut self, event: Event) -> Result<(), Error> {
match self.state {
State::Info => self.process_info_event(event),
State::Starters => self.process_starter_event(event),
State::Plays => self.process_play_event(event),
State::Data => self.process_data_event(event),
State::Done => Err(Error::ParsingDone),
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::iter::FromIterator;
use event::{
Advance, Base, DataEventType, Event, Hand, Info, Pitch, PlayDescription, PlayEvent, Player,
Team,
};
use super::*;
#[test]
fn test_process_info() {
let mut game = Game::new("foo");
assert_eq!(
Ok(()),
game.process_event(Event::Info {
key: Info::HomeTeam,
data: "bar".into()
})
);
assert_eq!(Some(&"bar".into()), game.info.get(&Info::HomeTeam));
let evt = Event::GameId { id: "blah".into() };
assert_eq!(
Err(Error::InvalidEvent(State::Info, evt.clone())),
game.process_event(evt.clone())
);
let player = Player {
id: "fred103".into(),
name: "fred".into(),
team: Team::Home,
batting_pos: 7,
fielding_pos: 6,
};
assert_eq!(Ok(()), game.process_event(Event::Start { player: player }));
assert_eq!(State::Starters, game.state);
}
#[test]
fn test_process_starters() {
let mut game = Game::new("foo");
let player1 = Player {
id: "fred103".into(),
name: "fred".into(),
team: Team::Home,
batting_pos: 7,
fielding_pos: 6,
};
let player2 = Player {
id: "jim104".into(),
name: "jim".into(),
team: Team::Visiting,
batting_pos: 3,
fielding_pos: 1,
};
assert_eq!(
Ok(()),
game.process_event(Event::Start {
player: player1.clone()
})
);
assert_eq!(
Ok(()),
game.process_event(Event::Start {
player: player2.clone()
})
);
assert_eq!(
HashSet::from_iter(vec![player1.clone(), player2.clone()].into_iter()),
game.starters
);
let evt = Event::Info {
key: Info::HomeTeam,
data: "bar".into(),
};
assert_eq!(
Err(Error::InvalidEvent(State::Starters, evt.clone())),
game.process_event(evt.clone())
);
assert_eq!(
Ok(()),
game.process_event(Event::Play {
inning: 2,
team: Team::Visiting,
player: "foo".into(),
count: None,
pitches: vec![],
event: PlayEvent {
description: PlayDescription::Balk,
modifiers: vec![],
advances: vec![],
},
})
);
assert_eq!(State::Plays, game.state);
}
#[test]
fn test_process_plays() {
let event1 = Event::Play {
inning: 2,
team: Team::Visiting,
player: "foo".into(),
count: None,
pitches: vec![],
event: PlayEvent {
description: PlayDescription::Balk,
modifiers: vec![],
advances: vec![],
},
};
let event_np = Event::Play {
inning: 2,
team: Team::Visiting,
player: "foo".into(),
count: None,
pitches: vec![],
event: PlayEvent {
description: PlayDescription::NoPlay,
modifiers: vec![],
advances: vec![],
},
};
let player = Player {
id: "fred103".into(),
name: "fred".into(),
team: Team::Home,
batting_pos: 7,
fielding_pos: 6,
};
let event2 = Event::Sub {
player: player.clone(),
};
let event3 = Event::Data {
data_type: DataEventType::EarnedRuns,
player: "fred103".into(),
value: "2".into(),
};
let comment = Event::Comment {
comment: "foo".into(),
};
let badj = Event::BattingAdjustment {
player: "bonib001".into(),
hand: Hand::Right,
};
let padj = Event::PitchingAdjustment {
player: "harrg001".into(),
hand: Hand::Left,
};
let ladj = Event::LineupAdjustment {
team: Team::Home,
position: 7,
};
{
let mut game = Game::new("foo");
game.state = State::Plays;
assert_eq!(Ok(()), game.process_event(event1.clone()));
assert_eq!(vec![(event1.clone(), vec![])], game.plays);
assert_eq!(Ok(()), game.process_event(comment.clone()));
assert_eq!(vec![(event1.clone(), vec![comment.clone()])], game.plays);
assert_eq!(Ok(()), game.process_event(badj.clone()));
assert_eq!(Ok(()), game.process_event(padj.clone()));
assert_eq!(Ok(()), game.process_event(ladj.clone()));
assert_eq!(Ok(()), game.process_event(event_np.clone()));
assert_eq!(Ok(()), game.process_event(event2.clone()));
assert_eq!(
vec![
(event1.clone(), vec![comment.clone()]),
(badj.clone(), vec![]),
(padj.clone(), vec![]),
(ladj.clone(), vec![])
],
game.plays
);
assert_eq!(
vec![Substitution {
inning: 2,
batting_team: Team::Visiting,
player: player.clone(),
}],
game.substitutions
);
assert_eq!(Ok(()), game.process_event(event3));
assert_eq!(State::Data, game.state);
}
{
let mut game = Game::new("foo");
game.state = State::Plays;
assert_eq!(Ok(()), game.process_event(event1.clone()));
assert_eq!(vec![(event1.clone(), vec![])], game.plays);
assert_eq!(
Err(Error::InvalidEventBeforeSub(event1.clone())),
game.process_event(event2.clone())
);
assert_eq!(Ok(()), game.process_event(badj.clone()));
assert_eq!(
Err(Error::InvalidEventBeforeSub(badj.clone())),
game.process_event(event2.clone())
);
}
{
let mut game = Game::new("foo");
game.state = State::Plays;
assert_eq!(
Err(Error::NoEventBeforeSub),
game.process_event(event2.clone())
);
}
{
let mut game = Game::new("foo");
game.state = State::Plays;
let info = Event::Info {
key: Info::HomeTeam,
data: "bar".into(),
};
assert_eq!(
Err(Error::InvalidEvent(State::Plays, info.clone())),
game.process_event(info.clone())
);
}
}
#[test]
fn test_data() {
let data = Event::Data {
data_type: DataEventType::EarnedRuns,
player: "foo".into(),
value: "3".into(),
};
let info = Event::Info {
key: Info::HomeTeam,
data: "bar".into(),
};
{
let mut game = Game::new("foo");
game.state = State::Data;
assert_eq!(Ok(()), game.process_event(data.clone()));
assert_eq!(vec![data.clone()], game.data);
}
{
let mut game = Game::new("foo");
game.state = State::Data;
assert_eq!(
Err(Error::InvalidEvent(State::Data, info.clone())),
game.process_event(info.clone())
);
}
}
#[test]
fn test_done() {
let mut game = Game::new("foo");
assert_eq!(Err(Error::ParsingIncomplete), game.finish());
game.state = State::Data;
assert_eq!(Ok(()), game.finish());
assert_eq!(
Err(Error::ParsingDone),
game.process_event(Event::Info {
key: Info::HomeTeam,
data: "bar".into(),
})
);
}
#[test]
fn test_error_printing() {
let event = Event::Data {
data_type: DataEventType::EarnedRuns,
player: "fred103".into(),
value: "2".into(),
};
assert_eq!(
"the requested command is not yet implemented".to_string(),
format!("{}", Error::Unimplemented)
);
assert_eq!(
"parsing not fully done before attempted completion".to_string(),
format!("{}", Error::ParsingIncomplete)
);
assert_eq!(
"parsing already completed".to_string(),
format!("{}", Error::ParsingDone)
);
assert_eq!(
"substitutions must be preceded by a play event".to_string(),
format!("{}", Error::NoEventBeforeSub)
);
assert_eq!(
format!(
"substitutions must be preceded by a play event; got {:?}",
event
),
format!("{}", Error::InvalidEventBeforeSub(event.clone()))
);
assert_eq!(
format!(
"the event {:?} is not valid in the metadata parsing state",
event
),
format!("{}", Error::InvalidEvent(State::Info, event.clone()))
);
assert_eq!(
format!(
"the event {:?} is not valid in the starting roster parsing state",
event
),
format!("{}", Error::InvalidEvent(State::Starters, event.clone()))
);
assert_eq!(
format!(
"the event {:?} is not valid in the play-by-play parsing state",
event
),
format!("{}", Error::InvalidEvent(State::Plays, event.clone()))
);
assert_eq!(
format!(
"the event {:?} is not valid in the data parsing state",
event
),
format!("{}", Error::InvalidEvent(State::Data, event.clone()))
);
assert_eq!(
format!("the event {:?} is not valid in the finished state", event),
format!("{}", Error::InvalidEvent(State::Done, event.clone()))
);
}
#[test]
fn test_eq() {
let info = {
let mut map = HashMap::new();
map.insert(Info::Number, "0".into());
map
};
let starters = HashSet::from_iter(
vec![Player {
id: "foo".into(),
name: "Bar".into(),
team: Team::Visiting,
batting_pos: 1,
fielding_pos: 6,
}]
.into_iter(),
);
let plays = vec![(
Event::Play {
inning: 4,
team: Team::Home,
player: "meh".into(),
count: Some((1, 2)),
pitches: vec![
Pitch::CalledStrike,
Pitch::CalledStrike,
Pitch::BallInPlayBatter,
],
event: PlayEvent {
description: PlayDescription::Single(vec![8]),
modifiers: vec![],
advances: vec![Advance {
from: Base::Home,
to: Base::Second,
success: true,
parameters: vec![],
}],
},
},
vec![],
)];
let data = vec![Event::Data {
data_type: DataEventType::EarnedRuns,
player: "foo".into(),
value: "2".into(),
}];
let substitutions = vec![Substitution {
inning: 4,
batting_team: Team::Home,
player: Player {
id: "buz".into(),
name: "Bax".into(),
team: Team::Home,
batting_pos: 3,
fielding_pos: 4,
},
}];
let game1 = Game {
id: "foo".into(),
info: info.clone(),
starters: starters.clone(),
plays: plays.clone(),
substitutions: substitutions.clone(),
data: data.clone(),
state: State::Starters,
};
let game2 = Game {
id: "foo".into(),
info: info.clone(),
starters: starters.clone(),
plays: plays.clone(),
substitutions: substitutions.clone(),
data: data.clone(),
state: State::Plays,
};
assert_eq!(game1, game2);
}
}