fbsim_core/game/
play.rs

1#![doc = include_str!("../../docs/game/play.md")]
2pub mod call;
3pub mod context;
4pub mod result;
5
6#[cfg(feature = "rocket_okapi")]
7use rocket_okapi::okapi::schemars;
8#[cfg(feature = "rocket_okapi")]
9use rocket_okapi::okapi::schemars::JsonSchema;
10use serde::{Serialize, Deserialize};
11use rand::Rng;
12
13use crate::game::context::GameContext;
14use crate::game::play::call::{PlayCallSimulator, PlayCall};
15use crate::game::play::result::{PlayResultSimulator, PlayResult, PlayTypeResult, ScoreResult};
16use crate::game::play::result::betweenplay::BetweenPlayResultSimulator;
17use crate::game::play::result::fieldgoal::FieldGoalResultSimulator;
18use crate::game::play::result::kickoff::KickoffResultSimulator;
19use crate::game::play::result::punt::PuntResultSimulator;
20use crate::game::play::result::pass::PassResultSimulator;
21use crate::game::play::result::run::RunResultSimulator;
22use crate::game::stat::{PassingStats, RushingStats, ReceivingStats};
23use crate::team::FootballTeam;
24use crate::team::coach::FootballTeamCoach;
25use crate::team::defense::FootballTeamDefense;
26use crate::team::offense::FootballTeamOffense;
27
28pub trait PlaySimulatable {
29    fn coach(&self) -> &FootballTeamCoach;
30    fn defense(&self) -> &FootballTeamDefense;
31    fn offense(&self) -> &FootballTeamOffense;
32}
33
34/// # `Play` struct
35///
36/// A `Play` represents the outcome of a play
37#[cfg_attr(feature = "rocket_okapi", derive(JsonSchema))]
38#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Serialize, Deserialize)]
39pub struct Play {
40    context: GameContext,
41    result: PlayTypeResult,
42    post_play: PlayTypeResult
43}
44
45impl Play {
46    /// Initialize a new play
47    ///
48    /// ```
49    /// use fbsim_core::game::play::Play;
50    /// use fbsim_core::game::play::result::PlayTypeResult;
51    /// use fbsim_core::game::play::result::betweenplay::BetweenPlayResult;
52    /// use fbsim_core::game::play::result::pass::PassResult;
53    /// use fbsim_core::game::context::GameContext;
54    /// 
55    /// // Initialize a game context
56    /// let my_context = GameContext::new();
57    ///
58    /// // Initialize a play type result
59    /// let my_res = PlayTypeResult::Pass(PassResult::new());
60    /// let my_between = PlayTypeResult::BetweenPlay(BetweenPlayResult::new());
61    ///
62    /// // Initialize a play
63    /// let my_play = Play::new(my_context, my_res, my_between);
64    /// ```
65    pub fn new(context: GameContext, result: PlayTypeResult, post_play: PlayTypeResult) -> Play {
66        Play{
67            context,
68            result,
69            post_play
70        }
71    }
72
73    /// Borrow the play result
74    ///
75    /// ### Example
76    /// ```
77    /// use fbsim_core::game::play::Play;
78    /// use fbsim_core::game::play::result::PlayTypeResult;
79    /// use fbsim_core::game::play::result::betweenplay::BetweenPlayResult;
80    /// use fbsim_core::game::play::result::pass::PassResult;
81    /// use fbsim_core::game::context::GameContext;
82    ///
83    /// // Initialize a game context
84    /// let my_context = GameContext::new();
85    ///
86    /// // Initialize a play type result
87    /// let my_res = PlayTypeResult::Pass(PassResult::new());
88    /// let my_between = PlayTypeResult::BetweenPlay(BetweenPlayResult::new());
89    ///
90    /// // Initialize a play and borrow its result
91    /// let my_play = Play::new(my_context, my_res, my_between);
92    /// let my_borrowed_res = my_play.result();
93    /// ```
94    pub fn result(&self) -> &PlayTypeResult {
95        &self.result
96    }
97
98    /// Borrow the post-play result
99    ///
100    /// ### Example
101    /// ```
102    /// use fbsim_core::game::play::Play;
103    /// use fbsim_core::game::play::result::PlayTypeResult;
104    /// use fbsim_core::game::play::result::betweenplay::BetweenPlayResult;
105    /// use fbsim_core::game::play::result::pass::PassResult;
106    /// use fbsim_core::game::context::GameContext;
107    ///
108    /// // Initialize a game context
109    /// let my_context = GameContext::new();
110    ///
111    /// // Initialize a play type result
112    /// let my_res = PlayTypeResult::Pass(PassResult::new());
113    /// let my_between = PlayTypeResult::BetweenPlay(BetweenPlayResult::new());
114    ///
115    /// // Initialize a play and borrow its result
116    /// let my_play = Play::new(my_context, my_res, my_between);
117    /// let my_borrowed_post_play = my_play.post_play();
118    /// ```
119    pub fn post_play(&self) -> &PlayTypeResult {
120        &self.post_play
121    }
122
123    /// Borrow the play's game context
124    ///
125    /// ### Example
126    /// ```
127    /// use fbsim_core::game::play::Play;
128    /// use fbsim_core::game::play::result::PlayTypeResult;
129    /// use fbsim_core::game::play::result::betweenplay::BetweenPlayResult;
130    /// use fbsim_core::game::play::result::pass::PassResult;
131    /// use fbsim_core::game::context::GameContext;
132    ///
133    /// // Initialize a game context
134    /// let my_context = GameContext::new();
135    ///
136    /// // Initialize a play type result
137    /// let my_res = PlayTypeResult::Pass(PassResult::new());
138    /// let my_between = PlayTypeResult::BetweenPlay(BetweenPlayResult::new());
139    ///
140    /// // Initialize a play and borrow its result
141    /// let my_play = Play::new(my_context, my_res, my_between);
142    /// let my_borrowed_context = my_play.context();
143    /// ```
144    pub fn context(&self) -> &GameContext {
145        &self.context
146    }
147}
148
149impl std::fmt::Display for Play {
150    /// Format a `Play` as a string.
151    ///
152    /// ### Example
153    /// ```
154    /// use fbsim_core::game::play::Play;
155    /// use fbsim_core::game::play::result::PlayTypeResult;
156    /// use fbsim_core::game::play::result::betweenplay::BetweenPlayResult;
157    /// use fbsim_core::game::play::result::pass::PassResult;
158    /// use fbsim_core::game::context::GameContext;
159    ///
160    /// // Initialize a game context
161    /// let my_context = GameContext::new();
162    ///
163    /// // Initialize a play type result
164    /// let my_res = PlayTypeResult::Pass(PassResult::new());
165    /// let my_between = PlayTypeResult::BetweenPlay(BetweenPlayResult::new());
166    ///
167    /// // Initialize a play and display it
168    /// let my_play = Play::new(my_context, my_res, my_between);
169    /// println!("{}", my_play);
170    /// ```
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        let score_str = format!(
173            "{} {} {}",
174            &self.context,
175            self.result,
176            self.post_play
177        );
178        f.write_str(score_str.trim())
179    }
180}
181
182/// # `PlaySimulator` struct
183///
184/// A `PlaySimulator` can simulate a play given a context, returning an
185/// updated context
186pub struct PlaySimulator {
187    betweenplay: BetweenPlayResultSimulator,
188    fieldgoal: FieldGoalResultSimulator,
189    kickoff: KickoffResultSimulator,
190    pass: PassResultSimulator,
191    punt: PuntResultSimulator,
192    run: RunResultSimulator,
193    playcall: PlayCallSimulator
194}
195
196impl Default for PlaySimulator {
197    /// Default constructor for the `PlaySimulator` struct
198    ///
199    /// ### Example
200    /// ```
201    /// use fbsim_core::game::play::PlaySimulator;
202    /// 
203    /// let my_sim = PlaySimulator::default();
204    /// ```
205    fn default() -> Self {
206        PlaySimulator{
207            betweenplay: BetweenPlayResultSimulator::new(),
208            fieldgoal: FieldGoalResultSimulator::new(),
209            kickoff: KickoffResultSimulator::new(),
210            pass: PassResultSimulator::new(),
211            punt: PuntResultSimulator::new(),
212            run: RunResultSimulator::new(),
213            playcall: PlayCallSimulator::new()
214        }
215    }
216}
217
218impl PlaySimulator {
219    /// Initialize a new play simulator
220    ///
221    /// ### Example
222    /// ```
223    /// use fbsim_core::game::play::PlaySimulator;
224    /// 
225    /// // Initialize a play simulator
226    /// let my_sim = PlaySimulator::new();
227    /// ```
228    pub fn new() -> PlaySimulator {
229        PlaySimulator::default()
230    }
231
232    /// Simulate a play
233    ///
234    /// ### Example
235    /// ```
236    /// use fbsim_core::game::context::GameContext;
237    /// use fbsim_core::game::play::PlaySimulator;
238    /// use fbsim_core::team::FootballTeam;
239    ///
240    /// // Initialize home & away teams
241    /// let my_home = FootballTeam::new();
242    /// let my_away = FootballTeam::new();
243    ///
244    /// // Initialize a game context
245    /// let my_context = GameContext::new();
246    ///
247    /// // Initialize a play simulator and simulate a play
248    /// let my_sim = PlaySimulator::new();
249    /// let mut rng = rand::thread_rng();
250    /// let (play, new_context) = my_sim.sim(&my_home, &my_away, my_context, &mut rng);
251    /// ```
252    pub fn sim(&self, home: &FootballTeam, away: &FootballTeam, context: GameContext, rng: &mut impl Rng) -> (Play, GameContext) {
253        // Determine the play call
254        let play_call = if context.next_play_extra_point() {
255            PlayCall::ExtraPoint
256        } else if context.next_play_kickoff() {
257            PlayCall::Kickoff
258        } else if context.home_possession() {
259            self.playcall.sim(home, &context, rng)
260        } else {
261            self.playcall.sim(away, &context, rng)
262        };
263
264        // Simulate the play
265        let result = if context.home_possession() {
266            match play_call {
267                PlayCall::Run => self.run.sim(home, away, &context, rng),
268                PlayCall::Pass => self.pass.sim(home, away, &context, rng),
269                PlayCall::FieldGoal => self.fieldgoal.sim(home, away, &context, rng),
270                PlayCall::Punt => self.punt.sim(home, away, &context, rng),
271                PlayCall::Kickoff => self.kickoff.sim(home, away, &context, rng),
272                PlayCall::ExtraPoint => self.fieldgoal.sim(home, away, &context, rng),
273                PlayCall::QbKneel => self.run.sim(home, away, &context, rng),
274                PlayCall::QbSpike => self.pass.sim(home, away, &context, rng)
275            }
276        } else {
277            match play_call {
278                PlayCall::Run => self.run.sim(away, home, &context, rng),
279                PlayCall::Pass => self.pass.sim(away, home, &context, rng),
280                PlayCall::FieldGoal => self.fieldgoal.sim(away, home, &context, rng),
281                PlayCall::Punt => self.punt.sim(away, home, &context, rng),
282                PlayCall::Kickoff => self.kickoff.sim(away, home, &context, rng),
283                PlayCall::ExtraPoint => self.fieldgoal.sim(away, home, &context, rng),
284                PlayCall::QbKneel => self.run.sim(away, home, &context, rng),
285                PlayCall::QbSpike => self.pass.sim(away, home, &context, rng)
286            }
287        };
288        let next_context = result.next_context(&context);
289
290        // Simulate between plays
291        let between_res = if context.home_possession() {
292            self.betweenplay.sim(home, away, &next_context, rng)
293        } else {
294            self.betweenplay.sim(away, home, &next_context, rng)
295        };
296        let new_context = between_res.next_context(&next_context);
297        (Play::new(context, result, between_res), new_context)
298    }
299}
300
301/// # `DriveResult` enum
302///
303/// Enumerates the possible outcomes of a drive
304#[cfg_attr(feature = "rocket_okapi", derive(JsonSchema))]
305#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
306pub enum DriveResult {
307    None,
308    Punt,
309    FieldGoal,
310    FieldGoalMissed,
311    Touchdown,
312    Safety,
313    Interception,
314    PickSix,
315    Fumble,
316    ScoopAndScore,
317    Downs,
318    EndOfHalf
319}
320
321impl std::fmt::Display for DriveResult {
322    /// Display a drive result as a human readable string
323    ///
324    /// ### Example
325    /// ```
326    /// use fbsim_core::game::play::DriveResult;
327    /// 
328    /// println!("{}", DriveResult::None);
329    /// ```
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        match self {
332            DriveResult::None => f.write_str("In Progress"),
333            DriveResult::Punt => f.write_str("Punt"),
334            DriveResult::FieldGoal => f.write_str("Field Goal"),
335            DriveResult::FieldGoalMissed => f.write_str("Missed Field Goal"),
336            DriveResult::Touchdown => f.write_str("Touchdown"),
337            DriveResult::Safety => f.write_str("Safety"),
338            DriveResult::Interception => f.write_str("Interception"),
339            DriveResult::PickSix => f.write_str("Pick Six"),
340            DriveResult::Fumble => f.write_str("Fumble"),
341            DriveResult::ScoopAndScore => f.write_str("Scoop and Score"),
342            DriveResult::Downs => f.write_str("Turnover on Downs"),
343            DriveResult::EndOfHalf => f.write_str("End of Half")
344        }
345    }
346}
347
348/// # `Drive` struct
349///
350/// A `Drive` represents the outcome of a drive
351#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Serialize, Deserialize)]
352pub struct Drive {
353    plays: Vec<Play>,
354    result: DriveResult,
355    complete: bool
356}
357
358impl Default for Drive {
359    /// Default constructor for the Drive struct
360    ///
361    /// ### Example
362    /// ```
363    /// use fbsim_core::game::play::Drive;
364    /// 
365    /// let my_drive = Drive::default();
366    /// ```
367    fn default() -> Self {
368        Drive {
369            plays: Vec::new(),
370            result: DriveResult::None,
371            complete: false
372        }
373    }
374}
375
376impl Drive {
377    /// Initialize a new drive
378    ///
379    /// ### Example
380    /// ```
381    /// use fbsim_core::game::play::Drive;
382    /// 
383    /// let my_drive = Drive::new();
384    /// ```
385    pub fn new() -> Drive {
386        Drive::default()
387    }
388
389    /// Borrow the plays in the drive
390    ///
391    /// ### Example
392    /// ```
393    /// use fbsim_core::game::play::Drive;
394    /// 
395    /// let my_drive = Drive::new();
396    /// let plays = my_drive.plays();
397    /// ```
398    pub fn plays(&self) -> &Vec<Play> {
399        &self.plays
400    }
401
402    /// Mutably borrow the plays in the drive
403    fn plays_mut(&mut self) -> &mut Vec<Play> {
404        &mut self.plays
405    }
406
407    /// Get the result of the drive
408    ///
409    /// ### Example
410    /// ```
411    /// use fbsim_core::game::play::{Drive, DriveResult};
412    /// 
413    /// let my_drive = Drive::new();
414    /// let result = my_drive.result();
415    /// ```
416    pub fn result(&self) -> &DriveResult {
417        &self.result
418    }
419
420    /// Mutably borrow the result of the drive
421    fn result_mut(&mut self) -> &mut DriveResult {
422        &mut self.result
423    }
424
425    /// Get whether the drive is complete
426    ///
427    /// ### Example
428    /// ```
429    /// use fbsim_core::game::play::Drive;
430    /// 
431    /// let mut my_drive = Drive::new();
432    /// let complete = my_drive.complete();
433    /// assert!(!complete);
434    /// ```
435    pub fn complete(&self) -> bool {
436        self.complete
437    }
438
439    /// Mutably borrow the drive's complete property
440    fn complete_mut(&mut self) -> &mut bool {
441        &mut self.complete
442    }
443
444    /// Get the rushing stats on the drive
445    ///
446    /// ### Example
447    /// ```
448    /// use fbsim_core::game::play::Drive;
449    ///
450    /// let drive = Drive::new();
451    /// let rushing_stats = drive.rushing_stats();
452    /// assert!(rushing_stats.yards() == 0);
453    /// assert!(rushing_stats.rushes() == 0);
454    /// ```
455    pub fn rushing_stats(&self) -> RushingStats {
456        let mut stats = RushingStats::new();
457        for play in self.plays().iter() {
458            match play.result() {
459                PlayTypeResult::Run(res) => {
460                    // Increment rushes & rushing yards
461                    stats.increment_rushes();
462                    stats.increment_yards(res.net_yards());
463
464                    // Increment rushing TDs & fumbles if either occur
465                    if res.touchdown() {
466                        stats.increment_touchdowns();
467                    }
468                    if res.fumble() {
469                        stats.increment_fumbles();
470                    }
471                },
472                _ => continue
473            }
474        }
475        stats
476    }
477
478    /// Get the passing stats on the drive
479    ///
480    /// ### Example
481    /// ```
482    /// use fbsim_core::game::play::Drive;
483    ///
484    /// let drive = Drive::new();
485    /// let passing_stats = drive.passing_stats();
486    /// assert!(passing_stats.yards() == 0);
487    /// assert!(passing_stats.completions() == 0);
488    /// ```
489    pub fn passing_stats(&self) -> PassingStats {
490        let mut stats = PassingStats::new();
491        for play in self.plays().iter() {
492            match play.result() {
493                PlayTypeResult::Pass(res) => {
494                    // Increment attempts
495                    stats.increment_attempts();
496                    
497                    // Increment completions and yards if complete
498                    if res.complete() {
499                        stats.increment_completions();
500                        stats.increment_yards(res.net_yards());
501                        
502                        // Increment TDs if completion and touchdown
503                        if res.touchdown() {
504                            stats.increment_touchdowns();
505                        }
506                    }
507
508                    // Increment interceptions if this was an INT
509                    if res.interception() {
510                        stats.increment_interceptions();
511                    }
512                },
513                _ => continue
514            }
515        }
516        stats
517    }
518
519    /// Get the total yards on the drive
520    ///
521    /// ### Example
522    /// ```
523    /// use fbsim_core::game::play::Drive;
524    /// 
525    /// let my_drive = Drive::new();
526    /// let total_yards = my_drive.total_yards();
527    /// assert!(total_yards == 0);
528    /// ```
529    pub fn total_yards(&self) -> i32 {
530        let mut yards: i32 = 0;
531        for play in self.plays.iter() {
532            match play.result() {
533                PlayTypeResult::Run(res) => {
534                    yards += res.net_yards();
535                },
536                PlayTypeResult::Pass(res) => {
537                    yards += res.net_yards();
538                },
539                _ => continue
540            }
541        }
542        yards
543    }
544}
545
546impl std::fmt::Display for Drive {
547    /// Display a drive as a human readable string
548    ///
549    /// ### Example
550    /// ```
551    /// use fbsim_core::game::play::Drive;
552    /// 
553    /// let my_drive = Drive::new();
554    /// println!("{}", my_drive);
555    /// ```
556    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557        let mut drive_str = format!(
558            "{} plays, {} yards | Result: {} | Passing: {} | Rushing: {}",
559            self.plays().len(),
560            self.total_yards(),
561            self.result(),
562            self.passing_stats(),
563            self.rushing_stats()
564        );
565        for play in self.plays() {
566            drive_str = format!("{}\n{}", drive_str, play);
567        }
568        f.write_str(&drive_str)
569    }
570}
571
572/// # `DriveSimulator` struct
573///
574/// A `DriveSimulator` can simulate a drive given a context, returning an
575/// updated context and a drive
576pub struct DriveSimulator {
577    play: PlaySimulator
578}
579
580impl Default for DriveSimulator {
581    /// Default constructor for the DriveSimulator struct
582    ///
583    /// ### Example
584    /// ```
585    /// use fbsim_core::game::play::DriveSimulator;
586    /// 
587    /// let my_sim = DriveSimulator::default();
588    /// ```
589    fn default() -> Self {
590        DriveSimulator{
591            play: PlaySimulator::new()
592        }
593    }
594}
595
596impl DriveSimulator {
597    /// Initialize a new drive simulator
598    ///
599    /// ### Example
600    /// ```
601    /// use fbsim_core::game::play::DriveSimulator;
602    /// 
603    /// let my_sim = DriveSimulator::new();
604    /// ```
605    pub fn new() -> DriveSimulator {
606        DriveSimulator::default()
607    }
608
609    /// Simulate the next play of a drive
610    ///
611    /// ### Example
612    /// ```
613    /// use fbsim_core::game::context::GameContext;
614    /// use fbsim_core::game::play::{Drive, DriveSimulator};
615    /// use fbsim_core::team::FootballTeam;
616    ///
617    /// // Initialize home & away teams
618    /// let my_home = FootballTeam::new();
619    /// let my_away = FootballTeam::new();
620    ///
621    /// // Initialize a game context
622    /// let mut my_context = GameContext::new();
623    /// let mut drive = Drive::new();
624    /// 
625    /// // Initialize a drive simulator & simulate a drive
626    /// let my_sim = DriveSimulator::new();
627    /// let mut rng = rand::thread_rng();
628    /// my_context = my_sim.sim_play(&my_home, &my_away, my_context, &mut drive, &mut rng).unwrap();
629    /// ```
630    pub fn sim_play(&self, home: &FootballTeam, away: &FootballTeam, context: GameContext, drive: &mut Drive, rng: &mut impl Rng) -> Result<GameContext, String> {
631        // Ensure the result is none, unless last play was a touchdown
632        let drive_res = *drive.result();
633        let result_was_none = drive_res == DriveResult::None;
634        let result_was_touchdown = drive_res == DriveResult::Touchdown ||
635            drive_res == DriveResult::ScoopAndScore ||
636            drive_res == DriveResult::PickSix;
637        if !(result_was_none || result_was_touchdown) {
638            return Err(
639                format!(
640                    "Cannot simulate play, result was not None ({}) and last play was not TD",
641                    drive.result()
642                )
643            )
644        }
645
646        // Simulate a play
647        let mut complete = false;
648        let mut result = DriveResult::None;
649        let prev_context = context.clone();
650        let (play, next_context) = self.play.sim(home, away, prev_context, rng);
651        let play_result = play.result();
652        let new_context = next_context;
653
654        // Determine if a drive result occurred
655        let result_was_none = *drive.result() == DriveResult::None;
656        if result_was_none {
657            let field_goal: bool = match play_result {
658                PlayTypeResult::FieldGoal(res) => res.made(),
659                _ => false
660            };
661            if field_goal {
662                result = DriveResult::FieldGoal;
663                complete = true;
664            }
665            let field_goal_missed: bool = match play_result {
666                PlayTypeResult::FieldGoal(res) => res.missed(),
667                _ => false
668            };
669            if field_goal_missed {
670                result = DriveResult::FieldGoalMissed;
671                complete = true;
672            }
673
674            // Punt
675            if matches!(play_result, PlayTypeResult::Punt(_)) {
676                result = DriveResult::Punt;
677                complete = true;
678            }
679
680            // Touchdown
681            let touchdown = play_result.offense_score() == ScoreResult::Touchdown ||
682                play_result.defense_score() == ScoreResult::Touchdown;
683            if touchdown {
684                result = DriveResult::Touchdown;
685                complete = false;
686            }
687
688            // Safety
689            let safety: bool = play_result.defense_score() == ScoreResult::Safety;
690            if safety {
691                result = DriveResult::Safety;
692                complete = true;
693            }
694
695            // Interception
696            let turnover = play_result.turnover();
697            let interception = if turnover {
698                match play_result {
699                    PlayTypeResult::Pass(res) => res.interception(),
700                    _ => false
701                }
702            } else {
703                false
704            };
705            if interception {
706                if touchdown {
707                    result = DriveResult::PickSix;
708                    complete = false;
709                } else {
710                    result = DriveResult::Interception;
711                    complete = true;
712                }
713            }
714
715            // Fumble
716            let fumble = if turnover {
717                match play_result {
718                    PlayTypeResult::Run(res) => res.fumble(),
719                    PlayTypeResult::Pass(res) => res.fumble(),
720                    PlayTypeResult::FieldGoal(res) => res.blocked(),
721                    PlayTypeResult::Punt(res) => res.fumble(),
722                    PlayTypeResult::Kickoff(res) => res.fumble(),
723                    PlayTypeResult::QbKneel(res) => res.fumble(),
724                    PlayTypeResult::QbSpike(res) => res.fumble(),
725                    _ => false
726                }
727            } else {
728                false
729            };
730            if fumble {
731                if touchdown {
732                    result = DriveResult::ScoopAndScore;
733                    complete = false;
734                } else {
735                    result = DriveResult::Fumble;
736                    complete = true;
737                }
738            }
739
740            // Downs
741            let prev_context = play.context();
742            let downs = prev_context.down() == 4 && new_context.down() == 1 && !turnover &&
743                prev_context.home_possession() != new_context.home_possession() &&
744                play_result.net_yards() < prev_context.distance() as i32;
745            if downs {
746                result = DriveResult::Downs;
747                complete = true;
748            }
749
750            // End of half
751            let end_of_half = ((prev_context.quarter() == 2 || prev_context.quarter() >= 4) &&
752                (prev_context.quarter() != new_context.quarter())) || new_context.game_over();
753            if end_of_half {
754                result = DriveResult::EndOfHalf;
755                complete = true;
756            }
757        } else if result_was_touchdown {
758            result = drive_res;
759            complete = true;
760        }
761
762        // Add the play to the drive, update result, return the new drive & context
763        let plays = drive.plays_mut();
764        plays.push(play);
765        let drive_res = drive.result_mut();
766        *drive_res = result;
767        let drive_complete = drive.complete_mut();
768        *drive_complete = complete;
769        Ok(new_context)
770    }
771
772    /// Simulate the remaining plays of a drive
773    ///
774    /// ### Example
775    /// ```
776    /// use fbsim_core::game::context::GameContext;
777    /// use fbsim_core::game::play::{Drive, DriveSimulator};
778    /// use fbsim_core::team::FootballTeam;
779    ///
780    /// // Initialize home & away teams
781    /// let my_home = FootballTeam::new();
782    /// let my_away = FootballTeam::new();
783    ///
784    /// // Initialize a game context
785    /// let my_context = GameContext::new();
786    /// 
787    /// // Initialize a drive simulator & simulate a drive
788    /// let mut my_drive = Drive::new();
789    /// let my_sim = DriveSimulator::new();
790    /// let mut rng = rand::thread_rng();
791    /// let next_context = my_sim.sim_drive(&my_home, &my_away, my_context, &mut my_drive, &mut rng).unwrap();
792    /// ```
793    pub fn sim_drive(&self, home: &FootballTeam, away: &FootballTeam, context: GameContext, drive: &mut Drive, rng: &mut impl Rng) -> Result<GameContext, String> {
794        let mut extra_point_complete: bool = false;
795        let mut prev_context = context.clone();
796        while !drive.complete() {
797            // Simulate a play
798            let prev_result = *drive.result();
799            let next_context = match self.sim_play(home, away, prev_context, drive, rng) {
800                Ok(c) => c,
801                Err(e) => return Err(format!("Error simulating the next play of drive: {}", e))
802            };
803            let result = *drive.result();
804
805            // Check if the result was something other than a touchdown
806            // Or whether the previous result was a touchdown and this was the extra point
807            if (prev_result == DriveResult::Touchdown || prev_result == DriveResult::PickSix || prev_result == DriveResult::ScoopAndScore) ||
808                (prev_result == DriveResult::None && result != DriveResult::None && result != DriveResult::Touchdown) {
809                extra_point_complete = true;
810            }
811            
812            // Break the loop if necessary
813            if (result == DriveResult::None && prev_result == DriveResult::None) || !extra_point_complete {
814                prev_context = next_context
815            } else {
816                return Ok(next_context)
817            }
818        }
819        Err(String::from("Drive was already complete"))
820    }
821
822    /// Simulate a new drive
823    ///
824    /// ### Example
825    /// ```
826    /// use fbsim_core::game::context::GameContext;
827    /// use fbsim_core::game::play::DriveSimulator;
828    /// use fbsim_core::team::FootballTeam;
829    ///
830    /// // Initialize home & away teams
831    /// let my_home = FootballTeam::new();
832    /// let my_away = FootballTeam::new();
833    ///
834    /// // Initialize a game context
835    /// let my_context = GameContext::new();
836    /// 
837    /// // Initialize a drive simulator & simulate a drive
838    /// let my_sim = DriveSimulator::new();
839    /// let mut rng = rand::thread_rng();
840    /// let (drive, next_context) = my_sim.sim(&my_home, &my_away, my_context, &mut rng);
841    /// ```
842    pub fn sim(&self, home: &FootballTeam, away: &FootballTeam, context: GameContext, rng: &mut impl Rng) -> (Drive, GameContext) {
843        let mut extra_point_complete: bool = false;
844        let mut drive: Drive = Drive::new();
845        let mut prev_context = context.clone();
846        loop {
847            // Simulate a play
848            let prev_result = *drive.result();
849            let next_context = self.sim_play(home, away, prev_context, &mut drive, rng).unwrap();
850            let result = *drive.result();
851
852            // Check if the result was something other than a touchdown
853            // Or whether the previous result was a touchdown and this was the extra point
854            if (prev_result == DriveResult::Touchdown || prev_result == DriveResult::PickSix || prev_result == DriveResult::ScoopAndScore) ||
855                (prev_result == DriveResult::None && result != DriveResult::None && result != DriveResult::Touchdown) {
856                extra_point_complete = true;
857            }
858            
859            // Break the loop if necessary
860            if (result == DriveResult::None && prev_result == DriveResult::None) || !extra_point_complete {
861                prev_context = next_context
862            } else {
863                return (drive, next_context)
864            }
865        }
866    }
867}
868
869/// # `Game` struct
870///
871/// A `Game` represents the outcome of a game
872#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Serialize, Deserialize)]
873pub struct Game {
874    drives: Vec<Drive>,
875    complete: bool
876}
877
878impl Default for Game {
879    /// Default constructor for the Game struct
880    ///
881    /// ### Example
882    /// ```
883    /// use fbsim_core::game::play::Game;
884    ///
885    /// let game = Game::default();
886    /// ```
887    fn default() -> Self {
888        Game {
889            drives: Vec::new(),
890            complete: false
891        }
892    }
893}
894
895impl Game {
896    /// Initialize a new game
897    ///
898    /// ### Example
899    /// ```
900    /// use fbsim_core::game::play::Game;
901    ///
902    /// let game = Game::new();
903    /// ```
904    pub fn new() -> Game {
905        Game::default()
906    }
907
908    /// Get whether the game is complete
909    ///
910    /// ### Example
911    /// ```
912    /// use fbsim_core::game::play::Game;
913    ///
914    /// let game = Game::new();
915    /// let complete = game.complete();
916    /// assert!(!complete);
917    /// ```
918    pub fn complete(&self) -> bool {
919        self.complete
920    }
921
922    /// Borrow the game's drives
923    ///
924    /// ### Example
925    /// ```
926    /// use fbsim_core::game::play::Game;
927    ///
928    /// let game = Game::new();
929    /// let drives = game.drives();
930    /// ```
931    pub fn drives(&self) -> &Vec<Drive> {
932        &self.drives
933    }
934
935    /// Borrow the game's drives mutably
936    ///
937    /// ### Example
938    /// ```
939    /// use fbsim_core::game::play::Game;
940    ///
941    /// let mut game = Game::new();
942    /// let drives = game.drives_mut();
943    /// ```
944    pub fn drives_mut(&mut self) -> &mut Vec<Drive> {
945        &mut self.drives
946    }
947
948    /// Get the rushing stats for either team
949    ///
950    /// ### Example
951    /// ```
952    /// use fbsim_core::game::play::Game;
953    ///
954    /// let game = Game::new();
955    /// let rushing_stats = game.rushing_stats(true);
956    /// assert!(rushing_stats.yards() == 0);
957    /// assert!(rushing_stats.rushes() == 0);
958    /// ```
959    pub fn rushing_stats(&self, home: bool) -> RushingStats {
960        let mut stats = RushingStats::new();
961        for drive in self.drives.iter() {
962            for play in drive.plays().iter() {
963                if play.context().home_possession() == home {
964                    match play.result() {
965                        PlayTypeResult::Run(res) => {
966                            // Increment rushes & rushing yards
967                            stats.increment_rushes();
968                            stats.increment_yards(res.net_yards());
969
970                            // Increment rushing TDs & fumbles if either occur
971                            if res.touchdown() {
972                                stats.increment_touchdowns();
973                            }
974                            if res.fumble() {
975                                stats.increment_fumbles();
976                            }
977                        },
978                        _ => continue
979                    }
980                }
981            }
982        }
983        stats
984    }
985
986    /// Get the passing stats for either team
987    ///
988    /// ### Example
989    /// ```
990    /// use fbsim_core::game::play::Game;
991    ///
992    /// let game = Game::new();
993    /// let passing_stats = game.passing_stats(true);
994    /// assert!(passing_stats.yards() == 0);
995    /// assert!(passing_stats.completions() == 0);
996    /// ```
997    pub fn passing_stats(&self, home: bool) -> PassingStats {
998        let mut stats = PassingStats::new();
999        for drive in self.drives.iter() {
1000            for play in drive.plays().iter() {
1001                if play.context().home_possession() == home {
1002                    match play.result() {
1003                        PlayTypeResult::Pass(res) => {
1004                            // Increment attempts
1005                            stats.increment_attempts();
1006                            
1007                            // Increment completions and yards if complete
1008                            if res.complete() {
1009                                stats.increment_completions();
1010                                stats.increment_yards(res.net_yards());
1011                                
1012                                // Increment TDs if completion and touchdown
1013                                if res.touchdown() {
1014                                    stats.increment_touchdowns();
1015                                }
1016                            }
1017
1018                            // Increment interceptions if this was an INT
1019                            if res.interception() {
1020                                stats.increment_interceptions();
1021                            }
1022                        },
1023                        _ => continue
1024                    }
1025                }
1026            }
1027        }
1028        stats
1029    }
1030
1031    /// Get the receiving stats for either team
1032    ///
1033    /// ### Example
1034    /// ```
1035    /// use fbsim_core::game::play::Game;
1036    ///
1037    /// let game = Game::new();
1038    /// let receiving_stats = game.receiving_stats(true);
1039    /// assert!(receiving_stats.yards() == 0);
1040    /// assert!(receiving_stats.receptions() == 0);
1041    /// ```
1042    pub fn receiving_stats(&self, home: bool) -> ReceivingStats {
1043        let mut stats = ReceivingStats::new();
1044        for drive in self.drives.iter() {
1045            for play in drive.plays().iter() {
1046                if play.context().home_possession() == home {
1047                    match play.result() {
1048                        PlayTypeResult::Pass(res) => {
1049                            // Increment targets
1050                            stats.increment_targets(1);
1051                            
1052                            // Increment receptions and yards if complete
1053                            if res.complete() {
1054                                stats.increment_receptions(1);
1055                                stats.increment_yards(res.net_yards());
1056                                
1057                                // Increment TDs or fumbles if either occur
1058                                if res.touchdown() {
1059                                    stats.increment_touchdowns(1);
1060                                }
1061                                if res.fumble() {
1062                                    stats.increment_fumbles(1);
1063                                }
1064                            }
1065                        },
1066                        _ => continue
1067                    }
1068                }
1069            }
1070        }
1071        stats
1072    }
1073}
1074
1075impl std::fmt::Display for Game {
1076    /// Display a game as a human readable string
1077    ///
1078    /// ### Example
1079    /// ```
1080    /// use fbsim_core::game::play::Game;
1081    /// 
1082    /// let my_game = Game::new();
1083    /// println!("{}", my_game);
1084    /// ```
1085    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1086        // Format the game log
1087        let mut game_log = String::from("");
1088        for drive in self.drives() {
1089            game_log = format!("{}\n\n{}", game_log, drive);
1090        }
1091        f.write_str(game_log.trim())
1092    }
1093}
1094
1095/// # `GameSimulator` struct
1096///
1097/// A `GameSimulator` can simulate a game given a context, returning an
1098/// updated context and a drive
1099pub struct GameSimulator {
1100    drive: DriveSimulator
1101}
1102
1103impl Default for GameSimulator {
1104    /// Default constructor for the `GameSimulator` struct
1105    ///
1106    /// ### Example
1107    /// ```
1108    /// use fbsim_core::game::play::GameSimulator;
1109    ///
1110    /// let my_sim = GameSimulator::default();
1111    /// ```
1112    fn default() -> Self {
1113        GameSimulator {
1114            drive: DriveSimulator::new()
1115        }
1116    }
1117}
1118
1119impl GameSimulator {
1120    /// Constructor for the `GameSimulator` struct
1121    ///
1122    /// ### Example
1123    /// ```
1124    /// use fbsim_core::game::play::GameSimulator;
1125    ///
1126    /// let my_sim = GameSimulator::new();
1127    /// ```
1128    pub fn new() -> GameSimulator {
1129        GameSimulator::default()
1130    }
1131
1132    /// Simulate the next play of a game
1133    ///
1134    /// ### Example
1135    /// ```
1136    /// use fbsim_core::game::context::GameContext;
1137    /// use fbsim_core::game::play::{GameSimulator, Game};
1138    /// use fbsim_core::team::FootballTeam;
1139    ///
1140    /// // Initialize home & away teams
1141    /// let my_home = FootballTeam::new();
1142    /// let my_away = FootballTeam::new();
1143    ///
1144    /// // Initialize a game context
1145    /// let my_context = GameContext::new();
1146    /// 
1147    /// // Initialize a game simulator & simulate a drive
1148    /// let mut my_game = Game::new();
1149    /// let my_sim = GameSimulator::new();
1150    /// let mut rng = rand::thread_rng();
1151    /// let next_context = my_sim.sim_play(&my_home, &my_away, my_context, &mut my_game, &mut rng).unwrap();
1152    /// ```
1153    pub fn sim_play(&self, home: &FootballTeam, away: &FootballTeam, context: GameContext, game: &mut Game, rng: &mut impl Rng) -> Result<GameContext, String> {
1154        // Error if the game is over
1155        if context.game_over() {
1156            return Err(String::from("Game is already over, cannot simulate next play"))
1157        }
1158
1159        // Get the latest drive to sim or create new one if latest is complete
1160        let drives = game.drives_mut();
1161        let new_context = match drives.last_mut() {
1162            Some(d) => {
1163                if !d.complete() {
1164                    match self.drive.sim_play(home, away, context, d, rng) {
1165                        Ok(c) => c,
1166                        Err(e) => return Err(format!("Error simulating next play of game: {}", e))
1167                    }
1168                } else {
1169                    let mut new_drive = Drive::new();
1170                    let new_context = match self.drive.sim_play(home, away, context, &mut new_drive, rng) {
1171                        Ok(c) => c,
1172                        Err(e) => return Err(format!("Error simulating the next play of game: {}", e))
1173                    };
1174                    drives.push(new_drive);
1175                    new_context
1176                }
1177            },
1178            None => {
1179                let mut new_drive = Drive::new();
1180                let new_context = match self.drive.sim_play(home, away, context, &mut new_drive, rng) {
1181                    Ok(c) => c,
1182                    Err(e) => return Err(format!("Error simulating the next play of game: {}", e))
1183                };
1184                drives.push(new_drive);
1185                new_context
1186            }
1187        };
1188
1189        // Return the new context
1190        Ok(new_context)
1191    }
1192
1193    /// Simulate the next drive of a game
1194    ///
1195    /// ### Example
1196    /// ```
1197    /// use fbsim_core::game::context::GameContext;
1198    /// use fbsim_core::game::play::{GameSimulator, Game};
1199    /// use fbsim_core::team::FootballTeam;
1200    ///
1201    /// // Initialize home & away teams
1202    /// let my_home = FootballTeam::new();
1203    /// let my_away = FootballTeam::new();
1204    ///
1205    /// // Initialize a game context
1206    /// let my_context = GameContext::new();
1207    /// 
1208    /// // Initialize a game simulator & simulate a drive
1209    /// let mut my_game = Game::new();
1210    /// let my_sim = GameSimulator::new();
1211    /// let mut rng = rand::thread_rng();
1212    /// let next_context = my_sim.sim_drive(&my_home, &my_away, my_context, &mut my_game, &mut rng).unwrap();
1213    /// ```
1214    pub fn sim_drive(&self, home: &FootballTeam, away: &FootballTeam, context: GameContext, game: &mut Game, rng: &mut impl Rng) -> Result<GameContext, String> {
1215        // Error if the game is over
1216        if context.game_over() {
1217            return Err(String::from("Game is already over, cannot simulate next drive"))
1218        }
1219
1220        // Get the latest drive to sim or create new one if latest is complete
1221        let drives = game.drives_mut();
1222        let new_context = match drives.last_mut() {
1223            Some(d) => {
1224                if !d.complete() {
1225                    match self.drive.sim_drive(home, away, context, d, rng) {
1226                        Ok(c) => c,
1227                        Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1228                    }
1229                } else {
1230                    let mut new_drive = Drive::new();
1231                    let new_context = match self.drive.sim_drive(home, away, context, &mut new_drive, rng) {
1232                        Ok(c) => c,
1233                        Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1234                    };
1235                    drives.push(new_drive);
1236                    new_context
1237                }
1238            },
1239            None => {
1240                let mut new_drive = Drive::new();
1241                let new_context = match self.drive.sim_drive(home, away, context, &mut new_drive, rng) {
1242                    Ok(c) => c,
1243                    Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1244                };
1245                drives.push(new_drive);
1246                new_context
1247            }
1248        };
1249
1250        // Return the new context
1251        Ok(new_context)
1252    }
1253
1254    /// Simulate the remainder of a game
1255    ///
1256    /// ### Example
1257    /// ```
1258    /// use fbsim_core::game::context::GameContext;
1259    /// use fbsim_core::game::play::{GameSimulator, Game};
1260    /// use fbsim_core::team::FootballTeam;
1261    ///
1262    /// // Initialize home & away teams
1263    /// let my_home = FootballTeam::new();
1264    /// let my_away = FootballTeam::new();
1265    ///
1266    /// // Initialize a game context
1267    /// let my_context = GameContext::new();
1268    /// 
1269    /// // Initialize a game simulator and game, simulate the game
1270    /// let mut my_game = Game::new();
1271    /// let my_sim = GameSimulator::new();
1272    /// let mut rng = rand::thread_rng();
1273    /// let next_context = my_sim.sim_game(&my_home, &my_away, my_context, &mut my_game, &mut rng).unwrap();
1274    /// ```
1275    pub fn sim_game(&self, home: &FootballTeam, away: &FootballTeam, context: GameContext, game: &mut Game, rng: &mut impl Rng) -> Result<GameContext, String> {
1276        // Error if the game is over
1277        if context.game_over() {
1278            return Err(String::from("Game is already over, cannot simulate remainder of game"))
1279        }
1280
1281        // Get the latest drive to sim or create new one if latest is complete
1282        let drives = game.drives_mut();
1283        let mut next_context = context.clone();
1284        let mut game_over = next_context.game_over();
1285        while !game_over {
1286            let new_context = match drives.last_mut() {
1287                Some(d) => {
1288                    if !d.complete() {
1289                        match self.drive.sim_drive(home, away, next_context, d, rng) {
1290                            Ok(c) => c,
1291                            Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1292                        }
1293                    } else {
1294                        let mut new_drive = Drive::new();
1295                        let new_context = match self.drive.sim_drive(home, away, next_context, &mut new_drive, rng) {
1296                            Ok(c) => c,
1297                            Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1298                        };
1299                        drives.push(new_drive);
1300                        new_context
1301                    }
1302                },
1303                None => {
1304                    let mut new_drive = Drive::new();
1305                    let new_context = match self.drive.sim_drive(home, away, next_context, &mut new_drive, rng) {
1306                        Ok(c) => c,
1307                        Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1308                    };
1309                    drives.push(new_drive);
1310                    new_context
1311                }
1312            };
1313            game_over = new_context.game_over();
1314            next_context = new_context;
1315        }
1316
1317        // Return the final context
1318        Ok(next_context)
1319    }
1320
1321    /// Simulate a new game
1322    ///
1323    /// ### Example
1324    /// ```
1325    /// use fbsim_core::game::context::GameContext;
1326    /// use fbsim_core::game::play::GameSimulator;
1327    /// use fbsim_core::team::FootballTeam;
1328    ///
1329    /// // Initialize home & away teams
1330    /// let my_home = FootballTeam::new();
1331    /// let my_away = FootballTeam::new();
1332    ///
1333    /// // Initialize a game context
1334    /// let my_context = GameContext::new();
1335    /// 
1336    /// // Initialize a game simulator & simulate a game
1337    /// let my_sim = GameSimulator::new();
1338    /// let mut rng = rand::thread_rng();
1339    /// let (game, final_context) = my_sim.sim(&my_home, &my_away, my_context, &mut rng).unwrap();
1340    /// ```
1341    pub fn sim(&self, home: &FootballTeam, away: &FootballTeam, context: GameContext, rng: &mut impl Rng) -> Result<(Game, GameContext), String> {
1342        // Get the latest drive to sim or create new one if latest is complete
1343        let mut game = Game::new();
1344        let drives = game.drives_mut();
1345        let mut next_context = context.clone();
1346        let mut game_over = next_context.game_over();
1347        while !game_over {
1348            let new_context = match drives.last_mut() {
1349                Some(d) => {
1350                    if !d.complete() {
1351                        match self.drive.sim_drive(home, away, next_context, d, rng) {
1352                            Ok(c) => c,
1353                            Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1354                        }
1355                    } else {
1356                        let mut new_drive = Drive::new();
1357                        let new_context = match self.drive.sim_drive(home, away, next_context, &mut new_drive, rng) {
1358                            Ok(c) => c,
1359                            Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1360                        };
1361                        drives.push(new_drive);
1362                        new_context
1363                    }
1364                },
1365                None => {
1366                    let mut new_drive = Drive::new();
1367                    let new_context = match self.drive.sim_drive(home, away, next_context, &mut new_drive, rng) {
1368                        Ok(c) => c,
1369                        Err(e) => return Err(format!("Error simulating the next drive of game: {}", e))
1370                    };
1371                    drives.push(new_drive);
1372                    new_context
1373                }
1374            };
1375            game_over = new_context.game_over();
1376            next_context = new_context;
1377        }
1378
1379        // Return the final context
1380        Ok((game, next_context))
1381    }
1382}