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}