Skip to main content

server/domain/
game.rs

1use constants::*;
2use cqrs_es::{Aggregate, event_sink::EventSink};
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    display::format_vec,
7    domain::{wrap::Wrap, *},
8    name_builder::generate_game_name,
9};
10
11/// Represents a game with a host, optional guest (i.e. no one has joined and game is deemed to
12/// be in the lobby), name, and current state.
13#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
14pub struct Game {
15    id: GameId,
16    host: UserId,
17    guest: Option<UserId>,
18    name: String,
19    phase: Phase,
20}
21
22impl Game {
23    /// Creates a new `Game` with the specified ID, host, optional guest, name, and state.
24    #[must_use]
25    pub fn new(id: GameId, host: UserId, guest: Option<UserId>, name: &str, state: Phase) -> Self {
26        let name = String::from(name);
27        Self {
28            id,
29            host,
30            guest,
31            name,
32            phase: state,
33        }
34    }
35
36    /// Returns the unique identifier of the game.
37    #[must_use]
38    pub fn id(&self) -> GameId {
39        self.id
40    }
41
42    /// Returns the user ID of the host.
43    #[must_use]
44    pub fn host(&self) -> UserId {
45        self.host
46    }
47
48    /// Returns the user ID of the guest, if any.
49    #[must_use]
50    pub fn guest(&self) -> Option<UserId> {
51        self.guest
52    }
53
54    /// Returns the name of the game.
55    #[must_use]
56    pub fn name(&self) -> &str {
57        &self.name
58    }
59
60    #[cfg(test)]
61    pub(crate) fn name_mut(&mut self) -> &mut String {
62        &mut self.name
63    }
64
65    /// Returns the current phase of the game.
66    #[must_use]
67    pub fn phase(&self) -> &Phase {
68        &self.phase
69    }
70
71    #[cfg(test)]
72    pub(crate) fn phase_mut(&mut self) -> &mut Phase {
73        &mut self.phase
74    }
75
76    /// Returns the player corresponding to the given user ID, if they are part of this game.
77    #[must_use]
78    pub fn validate_user(&self, user_id: UserId) -> Option<Player> {
79        match user_id {
80            id if id == self.host => Some(PLAYER0),
81            id if Some(id) == self.guest => Some(PLAYER1),
82            _ => None,
83        }
84    }
85}
86
87impl Game {
88    fn host_game(&self, host: UserId, game_id: GameId) -> Result<Vec<GameEvent>, DomainError> {
89        let not_permitted = || Err(DomainError::NotPermitted(String::from("host game")));
90
91        if self.id != GameId::default() {
92            not_permitted()
93        } else {
94            let name = generate_game_name();
95            let events = vec![GameEvent::LobbyGameCreated {
96                game_id,
97                host,
98                name,
99            }];
100            Ok(events)
101        }
102    }
103
104    fn join_game(&self, guest: UserId) -> Result<Vec<GameEvent>, DomainError> {
105        let not_permitted = || Err(DomainError::NotPermitted(String::from("join game")));
106
107        if self.id == GameId::default() || self.guest.is_some() {
108            not_permitted()
109        } else if self.host == guest {
110            Err(DomainError::InvalidOpponent)
111        } else {
112            let events = vec![GameEvent::LobbyGameJoined { guest }];
113            Ok(events)
114        }
115    }
116
117    fn play_computer(&self, host: UserId, game_id: GameId) -> Result<Vec<GameEvent>, DomainError> {
118        let not_permitted = || Err(DomainError::NotPermitted(String::from("play computer")));
119
120        if self.id != GameId::default() {
121            not_permitted()
122        } else {
123            let guest = UserId::new();
124            let name = generate_game_name();
125            let events = vec![GameEvent::ComputerGameCreated {
126                game_id,
127                host,
128                guest,
129                name,
130            }];
131            Ok(events)
132        }
133    }
134
135    fn cut_for_deal(&self, player: Player) -> Result<Vec<GameEvent>, DomainError> {
136        let not_permitted = || Err(DomainError::NotPermitted(String::from("cut for deal")));
137
138        let cut_for_deal = |starting: &Starting| {
139            if !starting.pending().waiting_on(player) {
140                not_permitted()
141            } else {
142                let mut deck = starting.deck().clone();
143                let cut = deck.cut();
144                let events = vec![GameEvent::CutForDealMade { player, cut }];
145                Ok(events)
146            }
147        };
148
149        if self.id == GameId::default() {
150            not_permitted()
151        } else {
152            match &self.phase {
153                Phase::Starting(starting) => {
154                    let events = cut_for_deal(starting)?;
155                    Ok(events)
156                }
157                _ => not_permitted(),
158            }
159        }
160    }
161
162    fn start_game(&self, player: Player) -> Result<Vec<GameEvent>, DomainError> {
163        let not_permitted = || Err(DomainError::NotPermitted(String::from("start game")));
164
165        let start_game = |starting: &Starting| {
166            let mut events = vec![GameEvent::GameStarted { player }];
167
168            let proceed = starting.pending().clone().acknowledge(player);
169            if proceed {
170                if let Some(roles) = Roles::from_cuts(starting.cuts_for_deal()) {
171                    let mut deck = Deck::shuffled_pack();
172                    let hands = deck.deal(PLAYER_COUNT);
173
174                    events.append(&mut vec![
175                        GameEvent::CutForDealDecided {
176                            dealer: *roles.dealer(),
177                        },
178                        GameEvent::HandDealt {
179                            player: PLAYER0,
180                            hand: hands[PLAYER0].clone(),
181                        },
182                        GameEvent::HandDealt {
183                            player: PLAYER1,
184                            hand: hands[PLAYER1].clone(),
185                        },
186                    ]);
187                } else {
188                    events.push(GameEvent::CutForDealTied);
189                }
190            }
191
192            events
193        };
194
195        if self.id == GameId::default() {
196            not_permitted()
197        } else {
198            match &self.phase {
199                Phase::Starting(starting) => {
200                    let events = start_game(starting);
201                    Ok(events)
202                }
203                _ => not_permitted(),
204            }
205        }
206    }
207
208    fn discard_cards(
209        &self,
210        player: Player,
211        cards: Vec<Card>,
212    ) -> Result<Vec<GameEvent>, DomainError> {
213        let not_permitted = || Err(DomainError::NotPermitted("discard cards".into()));
214
215        let discard_cards_to_crib = |discarding: &Discarding| {
216            if !discarding.pending().waiting_on(player) {
217                not_permitted()
218            } else if !discarding.hand(player).contains_all(&cards)
219                || cards.len() != CARDS_DISCARDED_TO_CRIB
220                || discarding.hand(player).len() - cards.len() != CARDS_KEPT_PER_HAND
221            {
222                Err(DomainError::InvalidDiscards(format_vec(&cards)))
223            } else {
224                let mut events = vec![GameEvent::CardsDiscarded {
225                    player,
226                    cards: cards.clone(),
227                }];
228
229                let proceed = discarding.pending().clone().acknowledge(player);
230                if proceed {
231                    let cut = discarding.deck().clone().cut();
232                    let dealer = discarding.dealer().player();
233                    let pegging = Pegging::new(dealer, ScoreSheet::his_heels(cut));
234                    events.push(GameEvent::StarterSelected { cut, pegging });
235                }
236
237                Ok(events)
238            }
239        };
240
241        if self.id == GameId::default() {
242            not_permitted()
243        } else {
244            match &self.phase {
245                Phase::Discarding(discarding) => {
246                    let events = discard_cards_to_crib(discarding)?;
247                    Ok(events)
248                }
249                _ => not_permitted(),
250            }
251        }
252    }
253
254    fn play_card(&self, player: Player, card: Card) -> Result<Vec<GameEvent>, DomainError> {
255        let not_permitted = || Err(DomainError::NotPermitted("play card".into()));
256
257        let play_card = |playing: &Playing| {
258            let play_state = playing.play_state();
259            if play_state.next_to_play() != player {
260                Err(DomainError::NotPlayersTurn(player))
261            } else if !play_state.legal_plays(player).contains(&card) {
262                Err(DomainError::InvalidPlay(card))
263            } else {
264                let mut play_state = play_state.clone();
265                let score_sheet = play_state.play(card);
266                let pegging = Pegging::new(player, score_sheet);
267                Ok(vec![GameEvent::CardPlayed {
268                    player,
269                    card,
270                    pegging,
271                }])
272            }
273        };
274
275        if self.id == GameId::default() {
276            not_permitted()
277        } else {
278            match &self.phase {
279                Phase::Playing(playing) => {
280                    let events = play_card(playing)?;
281                    Ok(events)
282                }
283                _ => not_permitted(),
284            }
285        }
286    }
287
288    fn go(&self, player: Player) -> Result<Vec<GameEvent>, DomainError> {
289        let not_permitted = || Err(DomainError::NotPermitted("go".into()));
290
291        let go = |playing: &Playing| {
292            let play_state = playing.play_state();
293            if play_state.next_to_play() != player {
294                Err(DomainError::NotPlayersTurn(player))
295            } else if !play_state.legal_plays(player).is_empty() {
296                Err(DomainError::InvalidGo)
297            } else {
298                // There will always be a valid play before a go can occur. The `or` condition
299                // in `map_or` will never occur.
300                let recipient = play_state
301                    .current_plays()
302                    .last()
303                    .map_or(player, Play::player);
304
305                let mut play_state = play_state.clone();
306                let score_sheet = play_state.go();
307                let pegging = Pegging::new(recipient, score_sheet);
308
309                Ok(vec![GameEvent::GoCalled { player, pegging }])
310            }
311        };
312
313        if self.id == GameId::default() {
314            not_permitted()
315        } else {
316            match &self.phase {
317                Phase::Playing(playing) => {
318                    let events = go(playing)?;
319                    Ok(events)
320                }
321                _ => not_permitted(),
322            }
323        }
324    }
325
326    fn score_pone(&self, player: Player) -> Result<Vec<GameEvent>, DomainError> {
327        let not_permitted = || Err(DomainError::NotPermitted(String::from("score pone")));
328
329        let score_pone = |playing: &Playing| {
330            let pone = playing.pone().player();
331            let hands = playing.play_state().clone().finish_plays();
332            let hand = &hands[pone];
333            let cut = playing.starter_cut();
334
335            let pegging = Pegging::new(pone, ScoreSheet::hand(hand, *cut));
336            vec![GameEvent::PoneScored { player, pegging }]
337        };
338
339        if self.id == GameId::default() {
340            not_permitted()
341        } else {
342            match &self.phase {
343                Phase::Playing(playing) => {
344                    let events = score_pone(playing);
345                    Ok(events)
346                }
347                _ => not_permitted(),
348            }
349        }
350    }
351
352    fn score_dealer(&self, player: Player) -> Result<Vec<GameEvent>, DomainError> {
353        let not_permitted = || Err(DomainError::NotPermitted(String::from("score dealer")));
354
355        let score_dealer = |scoring: &ScoringPone| {
356            let dealer = scoring.dealer().player();
357            let hand = scoring.hand(dealer);
358            let cut = scoring.starter_cut();
359
360            let pegging = Pegging::new(dealer, ScoreSheet::hand(hand, *cut));
361            vec![GameEvent::DealerScored { player, pegging }]
362        };
363
364        if self.id == GameId::default() {
365            not_permitted()
366        } else {
367            match &self.phase {
368                Phase::ScoringPone(scoring) => {
369                    let events = score_dealer(scoring);
370                    Ok(events)
371                }
372                _ => not_permitted(),
373            }
374        }
375    }
376
377    fn score_crib(&self, player: Player) -> Result<Vec<GameEvent>, DomainError> {
378        let not_permitted = || Err(DomainError::NotPermitted(String::from("score crib")));
379
380        let score_crib = |scoring: &ScoringDealer| {
381            let dealer = scoring.dealer().player();
382            let crib = scoring.crib();
383            let cut = scoring.starter_cut();
384
385            let pegging = Pegging::new(dealer, ScoreSheet::crib(crib, *cut));
386            vec![GameEvent::CribScored { player, pegging }]
387        };
388
389        if self.id == GameId::default() {
390            not_permitted()
391        } else {
392            match &self.phase {
393                Phase::ScoringDealer(scoring) => {
394                    let events = score_crib(scoring);
395                    Ok(events)
396                }
397                _ => not_permitted(),
398            }
399        }
400    }
401
402    fn start_next_round(&self, player: Player) -> Result<Vec<GameEvent>, DomainError> {
403        let not_permitted = || Err(DomainError::NotPermitted(String::from("start next round")));
404
405        let start_next_round = |scoring: &ScoringCrib| {
406            let mut events = vec![GameEvent::NextRoundStarted { player }];
407
408            let proceed = scoring.pending().clone().acknowledge(player);
409            if proceed {
410                let mut deck = Deck::shuffled_pack();
411                let hands = deck.deal(PLAYER_COUNT);
412                events.append(&mut vec![
413                    GameEvent::HandDealt {
414                        player: PLAYER0,
415                        hand: hands[PLAYER0].clone(),
416                    },
417                    GameEvent::HandDealt {
418                        player: PLAYER1,
419                        hand: hands[PLAYER1].clone(),
420                    },
421                ]);
422            };
423
424            events
425        };
426
427        if self.id == GameId::default() {
428            not_permitted()
429        } else {
430            match &self.phase {
431                Phase::ScoringCrib(scoring) => {
432                    let events = start_next_round(scoring);
433                    Ok(events)
434                }
435                _ => not_permitted(),
436            }
437        }
438    }
439
440    pub(crate) fn handle_command(
441        &self,
442        command: GameCommand,
443    ) -> Result<Vec<GameEvent>, DomainError> {
444        tracing::debug!("COMMAND --- Game:handle_command: {:?}", command);
445        match command {
446            GameCommand::HostGame { user_id, game_id } => self.host_game(user_id, game_id),
447            GameCommand::JoinGame { user_id } => self.join_game(user_id),
448            GameCommand::PlayComputer { user_id, game_id } => self.play_computer(user_id, game_id),
449            GameCommand::CutForDeal { player } => self.cut_for_deal(player),
450            GameCommand::StartGame { player } => self.start_game(player),
451            GameCommand::DiscardCards { player, cards } => self.discard_cards(player, cards),
452            GameCommand::PlayCard { player, card } => self.play_card(player, card),
453            GameCommand::Go { player } => self.go(player),
454            GameCommand::ScorePone { player } => self.score_pone(player),
455            GameCommand::ScoreDealer { player } => self.score_dealer(player),
456            GameCommand::ScoreCrib { player } => self.score_crib(player),
457            GameCommand::StartNextRound { player } => self.start_next_round(player),
458        }
459    }
460}
461
462impl Game {
463    fn lobby_game_created(&mut self, game_id: GameId, host: UserId, name: String) {
464        self.id = game_id;
465        self.host = host;
466        self.name = name;
467        self.phase = Starting::default().wrap();
468    }
469
470    fn lobby_game_joined(&mut self, guest: UserId) {
471        self.guest = Some(guest);
472    }
473
474    fn computer_game_created(
475        &mut self,
476        game_id: GameId,
477        host: UserId,
478        guest: UserId,
479        name: String,
480    ) {
481        self.id = game_id;
482        self.host = host;
483        self.guest = Some(guest);
484        self.name = name;
485        self.phase = Starting::default().wrap();
486    }
487
488    fn cut_for_deal_made(&mut self, player: Player, cut: Card) {
489        if let Phase::Starting(starting) = &mut self.phase {
490            *starting.cut_for_deal_mut(player) = Some(cut);
491            starting.deck_mut().remove(cut);
492        }
493    }
494
495    fn game_started(&mut self, player: Player) {
496        if let Phase::Starting(starting) = &mut self.phase {
497            let _ = starting.pending_mut().acknowledge(player);
498        }
499    }
500
501    fn cut_for_deal_decided(&mut self, dealer: Dealer) {
502        if let Phase::Starting(_) = &self.phase {
503            let scoreboard = Scoreboard::default();
504            let roles = Roles::new(dealer);
505            let deck = Deck::shuffled_pack();
506            let hands = Hands::default();
507            let crib = Crib::default();
508            let pending = Pending::default();
509            let discarding = Discarding::new(scoreboard, roles, hands, crib, deck, pending);
510            self.phase = discarding.wrap();
511        }
512    }
513
514    fn hand_dealt(&mut self, player: Player, hand: Hand) {
515        if let Phase::Discarding(discarding) = &mut self.phase {
516            let hands = discarding.hands_mut();
517            hands[player] = hand.clone();
518
519            let deck = discarding.deck_mut();
520            deck.remove_all(hand.as_ref());
521        }
522    }
523
524    fn cut_for_deal_tied(&mut self) {
525        if let Phase::Starting(_) = &self.phase {
526            let cuts = CutsForDeal::default();
527            let deck = Deck::shuffled_pack();
528            let pending = Pending::default();
529            let starting = Starting::new(cuts, deck, pending);
530            self.phase = starting.wrap();
531        }
532    }
533
534    fn cards_discarded(&mut self, player: Player, cards: &[Card]) {
535        if let Phase::Discarding(discarding) = &mut self.phase {
536            discarding.hand_mut(player).remove_all(cards);
537            discarding.crib_mut().add_all(cards);
538            let _ = discarding.pending_mut().acknowledge(player);
539        }
540    }
541
542    fn starter_selected(&mut self, starter_cut: StarterCut, pegging: Pegging) {
543        if let Phase::Discarding(discarding) = &mut self.phase {
544            let _ = discarding.scoreboard_mut().peg(&pegging);
545
546            let play_state = PlayState::new(discarding.pone().player())
547                .with_pending_plays(PLAYER0, discarding.hand(PLAYER0).as_ref())
548                .with_pending_plays(PLAYER1, discarding.hand(PLAYER1).as_ref());
549
550            let pending = Pending::default();
551
552            let playing = Playing::new(
553                discarding.scoreboard().clone(),
554                *discarding.roles(),
555                discarding.hands().clone(),
556                play_state,
557                discarding.crib().clone(),
558                starter_cut,
559                pending,
560            );
561
562            self.phase = playing.wrap().or_finished();
563        }
564    }
565
566    fn card_played(&mut self, _player: Player, card: Card, pegging: Pegging) {
567        if let Phase::Playing(playing) = &mut self.phase {
568            playing.play_card(card);
569            if let Some(_winner) = playing.scoreboard_mut().peg(&pegging) {
570                let hands = playing.play_state_mut().finish_plays();
571                playing.hand_mut(PLAYER0).add_all(hands[PLAYER0].as_ref());
572                playing.hand_mut(PLAYER1).add_all(hands[PLAYER1].as_ref());
573            }
574            self.phase = playing.clone().wrap().or_finished();
575        }
576    }
577
578    fn go_called(&mut self, _player: Player, pegging: Pegging) {
579        if let Phase::Playing(playing) = &mut self.phase {
580            playing.go();
581            if let Some(_winner) = playing.scoreboard_mut().peg(&pegging) {
582                let hands = playing.play_state_mut().finish_plays();
583                playing.hand_mut(PLAYER0).add_all(hands[PLAYER0].as_ref());
584                playing.hand_mut(PLAYER1).add_all(hands[PLAYER1].as_ref());
585            }
586            self.phase = playing.clone().wrap().or_finished();
587        }
588    }
589
590    fn pone_scored(&mut self, player: Player, pegging: Pegging) {
591        if let Phase::Playing(playing) = &mut self.phase {
592            let proceed = playing.pending_mut().acknowledge(player);
593            if proceed {
594                let _ = playing.scoreboard_mut().peg(&pegging);
595
596                let hands = playing.play_state_mut().finish_plays();
597                let pending = Pending::default();
598
599                let scoring = ScoringPone::new(
600                    playing.scoreboard().clone(),
601                    *playing.roles(),
602                    hands,
603                    playing.crib().clone(),
604                    *playing.starter_cut(),
605                    pegging,
606                    pending,
607                );
608
609                self.phase = scoring.wrap().or_finished();
610            }
611        }
612    }
613
614    fn dealer_scored(&mut self, player: Player, pegging: Pegging) {
615        if let Phase::ScoringPone(scoring) = &mut self.phase {
616            let proceed = scoring.pending_mut().acknowledge(player);
617            if proceed {
618                let _ = scoring.scoreboard_mut().peg(&pegging);
619
620                let pending = Pending::default();
621
622                let scoring = ScoringDealer::new(
623                    scoring.scoreboard().clone(),
624                    *scoring.roles(),
625                    scoring.hands().clone(),
626                    scoring.crib().clone(),
627                    *scoring.starter_cut(),
628                    pegging,
629                    pending,
630                );
631
632                self.phase = scoring.wrap().or_finished();
633            }
634        }
635    }
636
637    fn crib_scored(&mut self, player: Player, pegging: Pegging) {
638        if let Phase::ScoringDealer(scoring) = &mut self.phase {
639            let proceed = scoring.pending_mut().acknowledge(player);
640            if proceed {
641                let _ = scoring.scoreboard_mut().peg(&pegging);
642
643                let pending = Pending::default();
644
645                let scoring = ScoringCrib::new(
646                    scoring.scoreboard().clone(),
647                    *scoring.roles(),
648                    scoring.hands().clone(),
649                    scoring.crib().clone(),
650                    *scoring.starter_cut(),
651                    pegging,
652                    pending,
653                );
654
655                self.phase = scoring.wrap().or_finished();
656            }
657        }
658    }
659
660    fn next_round_started(&mut self, player: Player) {
661        if let Phase::ScoringCrib(scoring) = &mut self.phase {
662            let proceed = scoring.pending_mut().acknowledge(player);
663            if proceed {
664                let scoreboard = scoring.scoreboard().clone();
665                let mut roles = *scoring.roles();
666                roles.swap();
667                let hands = Hands::default();
668                let crib = Crib::default();
669                let deck = Deck::shuffled_pack();
670                let pending = Pending::default();
671
672                self.phase = Discarding::new(scoreboard, roles, hands, crib, deck, pending).wrap();
673            }
674        }
675    }
676
677    pub(crate) fn apply_event(&mut self, event: GameEvent) {
678        tracing::debug!("EVENT ----- Game:apply_event: {:?}", event);
679        match event {
680            GameEvent::LobbyGameCreated {
681                game_id,
682                host,
683                name,
684            } => self.lobby_game_created(game_id, host, name),
685            GameEvent::LobbyGameJoined { guest } => self.lobby_game_joined(guest),
686            GameEvent::ComputerGameCreated {
687                game_id,
688                host,
689                guest,
690                name,
691            } => self.computer_game_created(game_id, host, guest, name),
692            GameEvent::CutForDealMade { player, cut } => self.cut_for_deal_made(player, cut),
693            GameEvent::GameStarted { player } => self.game_started(player),
694            GameEvent::CutForDealDecided { dealer } => self.cut_for_deal_decided(dealer),
695            GameEvent::HandDealt { player, hand } => self.hand_dealt(player, hand),
696            GameEvent::CutForDealTied => self.cut_for_deal_tied(),
697            GameEvent::CardsDiscarded { player, cards } => self.cards_discarded(player, &cards),
698            GameEvent::StarterSelected { cut, pegging } => self.starter_selected(cut, pegging),
699            GameEvent::CardPlayed {
700                player,
701                card,
702                pegging,
703            } => self.card_played(player, card, pegging),
704            GameEvent::GoCalled { player, pegging } => self.go_called(player, pegging),
705            GameEvent::PoneScored { player, pegging } => self.pone_scored(player, pegging),
706            GameEvent::DealerScored { player, pegging } => self.dealer_scored(player, pegging),
707            GameEvent::CribScored { player, pegging } => self.crib_scored(player, pegging),
708            GameEvent::NextRoundStarted { player } => self.next_round_started(player),
709
710            #[cfg(test)]
711            GameEvent::GamePreloaded { game, .. } => *self = *game,
712        }
713    }
714
715    #[cfg(test)]
716    pub(crate) fn apply_events(&mut self, events: &[GameEvent]) {
717        let events = events.to_owned();
718        for event in events {
719            self.apply_event(event);
720        }
721    }
722}
723
724/// No external services required.
725#[derive(Clone, Default)]
726pub struct GameServices;
727
728impl Aggregate for Game {
729    const TYPE: &'static str = stringify!(Game);
730
731    type Command = GameCommand;
732    type Event = GameEvent;
733    type Error = DomainError;
734    type Services = GameServices;
735
736    async fn handle(
737        &mut self,
738        command: Self::Command,
739        _services: &Self::Services,
740        sink: &EventSink<Self>,
741    ) -> Result<(), Self::Error> {
742        let events = self.handle_command(command)?;
743
744        for event in events {
745            sink.write(event, self).await;
746        }
747
748        Ok(())
749    }
750
751    fn apply(&mut self, event: Self::Event) {
752        self.apply_event(event);
753    }
754}
755
756impl std::fmt::Display for Game {
757    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
758        write!(
759            f,
760            "{}\n{} {}\n{}",
761            self.name,
762            self.host,
763            self.guest
764                .map_or("-".into(), |g| std::convert::identity(g).to_string()),
765            self.phase
766        )
767    }
768}
769
770#[cfg(test)]
771impl From<&[GameEvent]> for Game {
772    fn from(value: &[GameEvent]) -> Self {
773        let mut game = Self::default();
774        game.apply_events(value);
775        game
776    }
777}
778
779#[cfg(test)]
780impl From<Phase> for Game {
781    fn from(state: Phase) -> Self {
782        let id = GameId::new();
783        let host = UserId::new();
784        let guest = Some(UserId::new());
785        let name = format!("test_game_{}_{}", state.as_ref(), chrono::Utc::now());
786        Self {
787            id,
788            host,
789            guest,
790            name,
791            phase: state,
792        }
793    }
794}
795
796#[cfg(test)]
797#[coverage(off)]
798mod test {
799    use std::str::FromStr;
800
801    use super::*;
802    use crate::{
803        domain::{
804            test::{GameBuilder, domain_macros::*, test_macros::*},
805            types::CardExt,
806        },
807        macros::function_name,
808    };
809
810    /// # [Cribbage Rules](https://www.officialgamerules.org/cribbage)
811    ///
812    /// ## Number of Players
813    ///
814    /// Two or three people can play. Or four people can play two against two as partners. But
815    /// Cribbage is basically best played by two people, and the rules that follow are for that
816    /// number.
817    #[allow(clippy::expect_used)]
818    mod players {
819        use super::*;
820
821        #[test]
822        fn a_user_can_host_game() {
823            let user_id = UserId::new();
824            let game_id = GameId::new();
825
826            game_test! {
827                when: GameCommand::HostGame { user_id, game_id },
828                then_events: |events: &[GameEvent]| {
829                    find_then!(events, GameEvent::LobbyGameCreated {
830                        game_id: event_game_id,
831                        host: event_host,
832                        name: event_name,
833                    } => {
834                        assert_eq!(event_game_id, &game_id);
835                        assert_eq!(event_host, &user_id);
836                        assert_ne!(event_name.trim(), &String::default());
837                    });
838                }
839            }
840        }
841
842        #[test]
843        fn a_user_can_play_the_computer() {
844            let user_id = UserId::new();
845            let game_id = GameId::new();
846
847            game_test! {
848                when: GameCommand::PlayComputer { user_id, game_id },
849                then_events: |events: &[GameEvent]| {
850                    find_then!(events, GameEvent::ComputerGameCreated {
851                        game_id: event_game_id,
852                        host: event_host,
853                        guest: event_guest,
854                        name: event_name,
855                    } => {
856                        assert_eq!(event_game_id, &game_id);
857                        assert_eq!(event_host, &user_id);
858                        assert_ne!(event_guest, &UserId::default());
859                        assert_ne!(event_guest, &user_id);
860                        assert_ne!(event_name.trim(), String::default());
861                    });
862                }
863            }
864        }
865
866        #[test]
867        fn a_user_can_join_lobby_game() {
868            let game_id = GameId::new();
869            let host = UserId::new();
870            let guest = UserId::new();
871            let name = function_name!();
872
873            game_test! {
874                given: &[GameEvent::LobbyGameCreated {
875                    game_id,
876                    host,
877                    name: name.clone(),
878                }],
879                when: GameCommand::JoinGame { user_id: guest },
880                then_events: |events: &[GameEvent]| {
881                    assert_eq!(events, vec![GameEvent::LobbyGameJoined { guest }]);
882                }
883            }
884        }
885
886        #[test]
887        fn a_user_cannot_join_active_game() {
888            let game_id = GameId::new();
889            let host = UserId::new();
890            let guest = UserId::new();
891            let name = function_name!();
892
893            game_test! {
894                given: &[GameEvent::ComputerGameCreated {
895                    game_id,
896                    host,
897                    guest,
898                    name,
899                }],
900                when: GameCommand::JoinGame { user_id: UserId::new() },
901                then_error: DomainError::NotPermitted(String::from("join game"))
902            }
903        }
904
905        #[test]
906        fn a_different_user_must_join_lobby_game() {
907            let game_id = GameId::new();
908            let host = UserId::new();
909            let guest = host;
910            let name = function_name!();
911
912            game_test! {
913                given: &[GameEvent::LobbyGameCreated {
914                    game_id,
915                    host,
916                    name,
917                }],
918                when: GameCommand::JoinGame { user_id: guest },
919                then_error: DomainError::InvalidOpponent
920
921            }
922        }
923    }
924
925    /// ## The Pack
926    ///
927    /// The standard 52-card pack is used.
928    ///
929    /// Rank of Cards: K (high), Q, J, 10, 9, 8, 7, 6, 5, 4, 3, 2, A.
930    #[allow(clippy::expect_used)]
931    mod deck {
932        use super::*;
933
934        #[test]
935        fn use_a_standard_pack_of_cards() {
936            let user_id = UserId::new();
937            let game_id = GameId::new();
938            game_test! {
939                when: GameCommand::PlayComputer { user_id, game_id },
940                then_phase: |phase: &Phase| {
941                    assert_phase_then!(phase, Phase::Starting(starting) => {
942                        let deck = starting.deck();
943                        assert_eq!(deck.len(), STANDARD_DECK_SIZE);
944                    });
945                }
946            }
947        }
948    }
949
950    /// ## The Draw, Shuffle and Cut
951    ///
952    /// From a shuffled pack face down, each player cuts a card, leaving at least four cards at
953    /// either end of the pack.
954    ///
955    /// If both players cut cards of the same rank, each draws again. The player with the lower card
956    /// deals the first hand. Thereafter, the turn to deal alternates between the two players,
957    /// except that the loser of the game deals first if another game is played. The dealer has the
958    /// right to shuffle last, and he presents the cards to the non-dealer for the cut prior to the
959    /// deal. (In some games, there is no cut at this time.)
960    #[allow(clippy::expect_used)]
961    mod deal_cut {
962        use super::*;
963
964        #[test]
965        fn user_must_cut_for_dealer_1() {
966            let game_id = GameId::new();
967            let host = UserId::new();
968            let guest = UserId::new();
969            let name = function_name!();
970
971            game_test! {
972                given: &[GameEvent::ComputerGameCreated {
973                    game_id,
974                    host,
975                    guest,
976                    name,
977                }],
978                when: GameCommand::CutForDeal { player: PLAYER0 },
979                then_events: |events: &[GameEvent]| {
980                    find_then!(events, GameEvent::CutForDealMade { player, .. } => {
981                        assert_eq!(player, &PLAYER0);
982                    });
983                }
984            }
985        }
986
987        #[test]
988        fn user_must_cut_for_dealer_2() {
989            let game_id = GameId::new();
990            let host = UserId::new();
991            let guest = UserId::new();
992            let name = function_name!();
993
994            let cut0 = card!("AS");
995
996            game_test! {
997                given: &[
998                    GameEvent::ComputerGameCreated {
999                        game_id,
1000                        host,
1001                        guest,
1002                        name,
1003                    },
1004                    GameEvent::CutForDealMade {
1005                        player: PLAYER0,
1006                        cut: cut0,
1007                    },
1008                ],
1009                when: GameCommand::CutForDeal { player: PLAYER1 },
1010                then_events: |events: &[GameEvent]| {
1011                    find_then!(events, GameEvent::CutForDealMade { player, cut } => {
1012                        assert_eq!(player, &PLAYER1);
1013                        assert_ne!(cut, &cut0);
1014                    });
1015                }
1016            }
1017        }
1018
1019        #[test]
1020        fn dealer_decided_with_lowest_cut() {
1021            let game_id = GameId::new();
1022            let host = UserId::new();
1023            let guest = UserId::new();
1024            let name = function_name!();
1025
1026            let cut0 = card!("AS");
1027            let cut1 = card!("QH");
1028
1029            game_test! {
1030                given: &[
1031                    GameEvent::ComputerGameCreated {
1032                        game_id,
1033                        host,
1034                        guest,
1035                        name,
1036                    },
1037                    GameEvent::CutForDealMade {
1038                        player: PLAYER0,
1039                        cut: cut0,
1040                    },
1041                    GameEvent::CutForDealMade {
1042                        player: PLAYER1,
1043                        cut: cut1,
1044                    },
1045                    GameEvent::GameStarted { player: PLAYER0 },
1046                ],
1047                when: GameCommand::StartGame { player: PLAYER1 },
1048                then_events: |events: &[GameEvent]| {
1049                    assert_eq!(
1050                        events[..2],
1051                        vec![
1052                            GameEvent::GameStarted { player: PLAYER1 },
1053                            GameEvent::CutForDealDecided {
1054                                dealer: Dealer::from(PLAYER0)
1055                            }
1056                        ]
1057                    );
1058                    let deals = events
1059                        .iter()
1060                        .filter(|e| matches!(e, GameEvent::HandDealt { .. }))
1061                        .collect::<Vec<_>>();
1062                    assert_eq!(deals.len(), PLAYER_COUNT);
1063                },
1064                then_phase: |phase: &Phase| {
1065                    assert_phase_then!(phase, Phase::Discarding(discarding) => {
1066                        assert_eq!(discarding.deck().len(), STANDARD_DECK_SIZE - (CARDS_DEALT_PER_HAND * PLAYER_COUNT));
1067                    });
1068                }
1069            }
1070        }
1071
1072        #[test]
1073        fn dealer_undecided_with_tied_cut() {
1074            let game_id = GameId::new();
1075            let host = UserId::new();
1076            let guest = UserId::new();
1077            let name = function_name!();
1078
1079            let cut0 = card!("AS");
1080            let cut1 = card!("AH");
1081
1082            game_test! {
1083                given: &[
1084                    GameEvent::ComputerGameCreated {
1085                        game_id,
1086                        host,
1087                        guest,
1088                        name,
1089                    },
1090                    GameEvent::CutForDealMade {
1091                        player: PLAYER0,
1092                        cut: cut0,
1093                    },
1094                    GameEvent::CutForDealMade {
1095                        player: PLAYER1,
1096                        cut: cut1,
1097                    },
1098                    GameEvent::GameStarted { player: PLAYER0 },
1099                ],
1100                when: GameCommand::StartGame { player: PLAYER1 },
1101                then_events: |events: &[GameEvent]| {
1102                    assert_eq!(
1103                        events,
1104                        vec![
1105                            GameEvent::GameStarted { player: PLAYER1 },
1106                            GameEvent::CutForDealTied
1107                        ]
1108                    )
1109                }
1110            }
1111        }
1112    }
1113
1114    /// ## The Deal
1115    ///
1116    /// The dealer distributes six cards face down to his opponent and himself, beginning with the
1117    /// opponent.
1118    #[allow(clippy::expect_used)]
1119    mod deal {
1120        use super::*;
1121
1122        #[test]
1123        fn dealer_deals_six_cards_each() {
1124            let game_id = GameId::new();
1125            let host = UserId::new();
1126            let guest = UserId::new();
1127            let name = function_name!();
1128
1129            let cut0 = card!("AS");
1130            let cut1 = card!("QH");
1131
1132            game_test! {
1133                given: &[
1134                    GameEvent::ComputerGameCreated {
1135                        game_id,
1136                        host,
1137                        guest,
1138                        name,
1139                    },
1140                    GameEvent::CutForDealMade {
1141                        player: PLAYER0,
1142                        cut: cut0,
1143                    },
1144                    GameEvent::CutForDealMade {
1145                        player: PLAYER1,
1146                        cut: cut1,
1147                    },
1148                    GameEvent::GameStarted { player: PLAYER0 },
1149                ],
1150                when: GameCommand::StartGame { player: PLAYER1 },
1151                then_events: |events: &[GameEvent]| {
1152                    assert_eq!(
1153                        events[..2],
1154                        vec![
1155                            GameEvent::GameStarted { player: PLAYER1 },
1156                            GameEvent::CutForDealDecided {
1157                                dealer: Dealer::from(PLAYER0)
1158                            }
1159                        ]
1160                    );
1161                    let hands = events
1162                        .iter()
1163                        .filter_map(|event| match event {
1164                            GameEvent::HandDealt { hand, player: _ } => Some(hand),
1165                            _ => None,
1166                        })
1167                        .collect::<Vec<_>>();
1168                    assert_eq!(hands.len(), PLAYER_COUNT);
1169                    assert_eq!(hands[PLAYER0].len(), CARDS_DEALT_PER_HAND);
1170                    assert_eq!(hands[PLAYER1].len(), CARDS_DEALT_PER_HAND);
1171                }
1172            }
1173        }
1174    }
1175
1176    /// ## Object of the Game
1177    ///
1178    /// The goal is to be the first player to score 121 points. (Some games are to 61 points.)
1179    /// Players earn points during play and for making various card combinations.
1180    #[allow(clippy::expect_used)]
1181    mod object_of_the_game {}
1182
1183    /// ## The Crib
1184    ///
1185    /// Each player looks at his six cards and "lays away" (discards) two of them face down to
1186    /// reduce the hand to four. The four cards laid away together constitute "the crib". The crib
1187    /// belongs to the dealer, but these cards are not exposed or used until after the hands have
1188    /// been played.
1189    #[allow(clippy::expect_used)]
1190    mod the_crib {
1191        use super::*;
1192
1193        #[test]
1194        fn player_can_discard_own_cards_to_the_crib() {
1195            game_test! {
1196                given: &scenario!(build_discarding;
1197                    with_hands("AH2H3H4H5H6H", "AC2C3C4C5C6C"),
1198                    with_cut("QD"),
1199                    with_crib("")
1200                ),
1201                when: GameCommand::DiscardCards {
1202                    player: PLAYER0,
1203                    cards: cards!("AH2H"),
1204                },
1205                then_events: |events: &[GameEvent]| {
1206                    assert_eq!(events, &[GameEvent::CardsDiscarded {
1207                        player: PLAYER0,
1208                        cards: cards!("AH2H"),
1209                    }])
1210                },
1211                then_phase: |phase: &Phase| {
1212                    assert_phase_then!(phase, Phase::Discarding(discarding) => {
1213                        assert!(discarding.deck().contains_none(&cards!("AH2H")));
1214                        assert_eq!(discarding.deck().len(), STANDARD_DECK_SIZE - (CARDS_DEALT_PER_HAND * PLAYER_COUNT));
1215                    });
1216                }
1217            }
1218        }
1219
1220        #[test]
1221        fn player_cannot_discard_other_than_two_held_cards_to_the_crib() {
1222            for cards in ["AH2H3H", "AH"] {
1223                let cards = cards!(cards);
1224                let expected_error_text = format_vec(&cards);
1225
1226                game_test! {
1227                    given: &scenario!(
1228                        build_discarding;
1229                        with_hands("AH2H3H4H5H6H", "AC2C3C4C5C6C"),
1230                        with_cut("QD"),
1231                        with_crib("")
1232                    ),
1233                    when: GameCommand::DiscardCards {
1234                        player: PLAYER0,
1235                        cards,
1236                    },
1237                    then_error: DomainError::InvalidDiscards(expected_error_text)
1238                }
1239            }
1240        }
1241
1242        #[test]
1243        fn player_cannot_discard_unowned_cards_to_the_crib() {
1244            let cards = cards!("AC2C");
1245            let expected_error_text = format_vec(&cards);
1246
1247            game_test! {
1248                given: &scenario!(
1249                    build_discarding;
1250                    with_hands("AH2H3H4H5H6H", "AC2C3C4C5C6C"),
1251                    with_cut("QD"),
1252                    with_crib("")
1253                ),
1254                when: GameCommand::DiscardCards {
1255                    player: PLAYER0,
1256                    cards,
1257                },
1258                then_error: DomainError::InvalidDiscards(expected_error_text)
1259            }
1260        }
1261
1262        #[test]
1263        fn player_cannot_discard_if_already_discarded() {
1264            game_test! {
1265                given: &scenario!(
1266                    build_discarding;
1267                    with_hands("3H4H5H6H", "AC2C3C4C5C6C"),
1268                    with_ack(0),
1269                    with_cut("QD"),
1270                    with_crib("AH2H")
1271                ),
1272                when: GameCommand::DiscardCards {
1273                    player: PLAYER0,
1274                    cards: cards!("5H6H"),
1275                },
1276                then_error: DomainError::NotPermitted("discard cards".into())
1277            }
1278        }
1279    }
1280
1281    /// ## Before the Play
1282    ///
1283    /// After the crib is laid away, the non-dealer cuts the pack. The dealer turns up the top card
1284    /// of the lower packet and places it face up on top of the pack. This card is the "starter." If
1285    /// the starter is a jack, it is called "His Heels," and the dealer pegs (scores) 2 points at
1286    /// once. The starter is not used in the play phase of Cribbage , but is used later for making
1287    /// various card combinations that score points.
1288    #[allow(clippy::expect_used)]
1289    mod before_the_play {
1290        use super::*;
1291
1292        #[test]
1293        fn start_the_play_after_discards() {
1294            game_test! {
1295                given: &scenario!(
1296                    build_discarding;
1297                    with_hands("3H4H5H6H", "AC2C3C4C5C6C"),
1298                    with_ack(0),
1299                    with_cut("QD"),
1300                    with_crib("AH2H")
1301                ),
1302                when: GameCommand::DiscardCards {
1303                    player: PLAYER1,
1304                    cards: cards!("AC2C"),
1305                },
1306                then_events: |events: &[GameEvent]| {
1307                    find_then!(events, GameEvent::CardsDiscarded { player, cards } => {
1308                        assert_eq!(player, &PLAYER1);
1309                        assert_eq!(cards, &cards!("AC2C"));
1310                    });
1311
1312                    find_then!(events, GameEvent::StarterSelected { .. } => {});
1313                },
1314                then_phase: |phase: &Phase| {
1315                    assert_phase_then!(phase, Phase::Playing(playing) => {
1316                        let starter_cut = *playing.starter_cut();
1317                        assert!(!playing.hand(PLAYER0).contains(starter_cut));
1318                        assert!(!playing.hand(PLAYER1).contains(starter_cut));
1319                        assert!(!playing.crib().contains(starter_cut));
1320                    });
1321                }
1322            }
1323        }
1324
1325        #[test]
1326        fn score_his_heels_when_jack_cut_after_discards() {
1327            game_test! {
1328                given: &scenario!(
1329                    build_discarding;
1330                    with_hands("3H4H5H6H", "AC2C3C4C5C6C"),
1331                    with_ack(0),
1332                    with_cut("JC"),
1333                    with_crib("AH2H")
1334                ),
1335                when: GameCommand::DiscardCards {
1336                    player: PLAYER1,
1337                    cards: cards!("AC2C"),
1338                },
1339                then_events: |events: &[GameEvent]| {
1340                    find_then!(events, GameEvent::StarterSelected { cut, pegging } => {
1341                        assert_eq!(pegging.recipient(), &PLAYER0);
1342                        assert_eq!(pegging.score_sheet().points(), Points::from(2));
1343                        assert_eq!(pegging.score_sheet(), &ScoreSheet::his_heels(card!("JC")));
1344                    });
1345                }
1346            }
1347        }
1348
1349        #[test]
1350        fn finish_game_when_jack_cut_after_discards() {
1351            game_test! {
1352                given: &scenario!(
1353                    build_discarding;
1354                    with_hands("3H4H5H6H", "AC2C3C4C5C6C"),
1355                    with_ack(0),
1356                    with_cut("JC"),
1357                    with_crib("AH2H"),
1358                    with_points(119, 0)
1359                ),
1360                when: GameCommand::DiscardCards {
1361                    player: PLAYER1,
1362                    cards: cards!("AC2C"),
1363                },
1364                then_phase: |phase: &Phase| {
1365                    assert_phase_then!(phase, Phase::Finished(finished) => {
1366                        assert_eq!(finished.winner(), PLAYER0)
1367                    });
1368                }
1369            }
1370        }
1371    }
1372
1373    /// ## The Play
1374    ///
1375    /// After the starter is turned, the non-dealer lays one of his cards face up on the table. The
1376    /// dealer similarly exposes a card, then non-dealer again, and so on - the hands are exposed
1377    /// card by card, alternately except for a "Go", as noted below. Each player keeps his
1378    /// cards separate from those of his opponent.
1379    ///
1380    /// As each person plays, he announces a running total of pips reached by the addition of the
1381    /// last card to all those previously2 played. (Example: The non-dealer begins with a four,
1382    /// saying "Four." The dealer plays a nine, saying "Thirteen".) The kings, queens and jacks
1383    /// count 10 each; every other card counts its pip value (the ace counts one).
1384    #[allow(clippy::expect_used)]
1385    mod the_play {
1386        use super::*;
1387
1388        #[test]
1389        fn accept_valid_play() {
1390            game_test! {
1391                given: &scenario!(
1392                    build_playing(1);
1393                    with_cut("AC"),
1394                    with_hands("QH", "4C")
1395                ),
1396                when: GameCommand::PlayCard {
1397                    player: PLAYER1,
1398                    card: card!("4C"),
1399                },
1400                then_events: |events: &[GameEvent]| {
1401                    assert_eq!(events, &[GameEvent::CardPlayed {
1402                        player: PLAYER1,
1403                        card: card!("4C"),
1404                        pegging: Pegging::new(PLAYER1, ScoreSheet::default()),
1405                    }]);
1406                }
1407            }
1408        }
1409
1410        #[test]
1411        fn accept_valid_play_after_opponent_go_called() {
1412            game_test! {
1413                given: &scenario!(
1414                    build_playing(1);
1415                    with_go(),
1416                    with_cut("AC"),
1417                    with_hands("9S", "4SAS"),
1418                    with_current_plays(&[(1, "TC"), (0, "TD"), (0, "5C")])
1419                ),
1420                when:GameCommand::PlayCard {
1421                    player: PLAYER1,
1422                    card: card!("4S"),
1423                },
1424                then_events: |events: &[GameEvent]| {
1425                    assert_eq!(events, &[GameEvent::CardPlayed {
1426                        player: PLAYER1,
1427                        card: card!("4S"),
1428                        pegging: Pegging::new(PLAYER1, ScoreSheet::default()),
1429                    }])
1430                }
1431            }
1432        }
1433
1434        #[test]
1435        fn cannot_play_when_unheld_card() {
1436            game_test! {
1437                given: &scenario!(
1438                    build_playing(1);
1439                    with_points(0,0),
1440                    with_hands("9S", "4S"),
1441                    with_cut("AS")
1442                ),
1443                when: GameCommand::PlayCard {
1444                    player: PLAYER1,
1445                    card: card!("9S"),
1446                },
1447                then_error: DomainError::InvalidPlay(card!("9S"))
1448            }
1449        }
1450
1451        #[test]
1452        fn cannot_play_when_not_their_turn() {
1453            game_test! {
1454                given: &scenario!(
1455                    build_playing(1);
1456                    with_points(0, 0),
1457                    with_hands("9S", "4S"),
1458                    with_cut("AS")
1459                ),
1460                when: GameCommand::PlayCard {
1461                    player: PLAYER0,
1462                    card: card!("9S"),
1463                },
1464                then_error: DomainError::NotPlayersTurn(PLAYER0)
1465            }
1466        }
1467
1468        #[test]
1469        fn cannot_play_when_play_exceeds_target() {
1470            game_test! {
1471                given: &scenario!(
1472                    build_playing(1);
1473                    with_points(0, 0),
1474                    with_hands("9S", "4S"),
1475                    with_cut("AS"),
1476                    with_current_plays(&[(0, "KH"), (0, "KC"), (0, "KD")])
1477                ),
1478                when: GameCommand::PlayCard {
1479                    player: PLAYER1,
1480                    card: card!("4S"),
1481                },
1482                then_error: DomainError::InvalidPlay(card!("4S"))
1483            }
1484        }
1485
1486        #[test]
1487        fn score_play_when_target_not_reached_mid_play() {
1488            game_test! {
1489                given: &scenario!(
1490                    build_playing(1);
1491                    with_points(0, 0),
1492                    with_hands("5S", "5H"),
1493                    with_cut("AS"),
1494                    with_current_plays(&[(0, "TH")])
1495                ),
1496                when: GameCommand::PlayCard {
1497                    player: PLAYER1,
1498                    card: card!("5H"),
1499                },
1500                then_events: |events: &[GameEvent]| {
1501                    assert_eq!(events, &[GameEvent::CardPlayed {
1502                        player: PLAYER1,
1503                        card: card!("5H"),
1504                        pegging: Pegging::new(
1505                            PLAYER1,
1506                            ScoreSheet::default().add_event(
1507                                ScoreKind::Fifteen,
1508                                &cards!("TH5H"),
1509                                Points::from(2),
1510                            ),
1511                        ),
1512                    }])
1513                }
1514            }
1515        }
1516
1517        #[test]
1518        fn score_play_when_target_not_reached_end_play() {
1519            game_test! {
1520                given: &scenario!(
1521                    build_playing(1);
1522                    with_points(0, 0),
1523                    with_hands("QS", "2H"),
1524                    with_cut("QC"),
1525                    with_current_plays(&[(0, "JH"), (0, "2C")]),
1526                    with_previous_plays(&[(0, "7C"), (1, "6S"), (1, "2S"), (1, "KS")])
1527                ),
1528                when: GameCommand::PlayCard {
1529                    player: PLAYER1,
1530                    card: card!("2H"),
1531                },
1532                then_events: |events: &[GameEvent]| {
1533                    assert_eq!(events, &[GameEvent::CardPlayed {
1534                        player: PLAYER1,
1535                        card: card!("2H"),
1536                        pegging: Pegging::new(
1537                            PLAYER1,
1538                            ScoreSheet::default().add_event(
1539                                ScoreKind::Pair,
1540                                &cards!("2H2C"),
1541                                Points::from(2),
1542                            ),
1543                        ),
1544                    }])
1545                }
1546            }
1547        }
1548
1549        #[test]
1550        fn score_play_when_target_not_reached_finished() {
1551            game_test! {
1552                given: &scenario!(
1553                    build_playing(1);
1554                    with_points(0, 120),
1555                    with_hands("AH", "5H"),
1556                    with_cut("QC"),
1557                    with_current_plays(&[(0, "JH")]),
1558                    with_previous_plays(&[(0, "9H"), (0, "7C"), (1, "6S"), (1, "2S"), (1, "KS")])
1559                ),
1560                when: GameCommand::PlayCard { player: PLAYER1, card: card!("5H") },
1561                then_events: |events: &[GameEvent]| {
1562                    assert_eq!(events, &[GameEvent::CardPlayed {
1563                        player: PLAYER1,
1564                        card: card!("5H"),
1565                        pegging: Pegging::new(
1566                            PLAYER1,
1567                            ScoreSheet::default().add_event(
1568                                ScoreKind::Fifteen,
1569                                &cards!("JH5H"),
1570                                Points::from(2),
1571                            ),
1572                        ),
1573                    }])
1574                }
1575            }
1576        }
1577
1578        #[test]
1579        fn score_play_when_target_reached_mid_play() {
1580            game_test! {
1581                given: &scenario!(build_playing(1);
1582                    with_points(0, 0),
1583                    with_hands("9H", "AH"),
1584                    with_cut("KC"),
1585                    with_current_plays(&[(0, "TH"), (0, "JH"), (0, "QH")]),
1586                    with_previous_plays(&[(1, "2S"), (1, "QS"), (1, "6S")])
1587                ),
1588                when: GameCommand::PlayCard {
1589                    player: PLAYER1,
1590                    card: card!("AH"),
1591                },
1592                then_events: |events: &[GameEvent]| {
1593                    assert_eq!(events, &[GameEvent::CardPlayed {
1594                        player: PLAYER1,
1595                        card: card!("AH"),
1596                        pegging: Pegging::new(
1597                            PLAYER1,
1598                            ScoreSheet::default().add_event(
1599                                ScoreKind::ThirtyOne,
1600                                &cards!("THJHQHAH"),
1601                                Points::from(2),
1602                            ),
1603                        ),
1604                    }])
1605                }
1606            }
1607        }
1608
1609        #[test]
1610        fn score_play_when_target_reached_end_play() {
1611            game_test! {
1612                given: &scenario!(build_playing(1);
1613                    with_points(0, 0),
1614                    with_hands("QC", "AH"),
1615                    with_cut("KC"),
1616                    with_current_plays(&[(0, "TH"), (0, "JH"), (0, "QH")]),
1617                    with_previous_plays(&[(1, "2S"), (1, "QS"), (1, "6S")])
1618                ),
1619                when: GameCommand::PlayCard {
1620                    player: PLAYER1,
1621                    card: card!("AH"),
1622                },
1623                then_events: |events: &[GameEvent]| {
1624                    assert_eq!(events, &[GameEvent::CardPlayed {
1625                        player: PLAYER1,
1626                        card: card!("AH"),
1627                        pegging: Pegging::new(
1628                            PLAYER1,
1629                            ScoreSheet::default().add_event(
1630                                ScoreKind::ThirtyOne,
1631                                &cards!("THJHQHAH"),
1632                                Points::from(2),
1633                            ),
1634                        ),
1635                    }])
1636                }
1637            }
1638        }
1639
1640        #[test]
1641        fn score_play_when_target_reached_finished() {
1642            game_test! {
1643                given: &scenario!(
1644                    build_playing(1);
1645                    with_points(0, 120),
1646                    with_hands("QC", "AH"),
1647                    with_cut("KC"),
1648                    with_current_plays(&[(0, "TH"), (1, "JH"), (0, "QH")]),
1649                    with_previous_plays(&[(1, "9H"), (1, "5S"), (0, "6S")])
1650                ),
1651                when: GameCommand::PlayCard {
1652                    player: PLAYER1,
1653                    card: card!("AH"),
1654                },
1655                then_events: |events: &[GameEvent]| {
1656                    assert_eq!(events, &[GameEvent::CardPlayed {
1657                        player: PLAYER1,
1658                        card: card!("AH"),
1659                        pegging: Pegging::new(
1660                            PLAYER1,
1661                            ScoreSheet::default().add_event(
1662                                ScoreKind::ThirtyOne,
1663                                &cards!("THJHQHAH"),
1664                                Points::from(2),
1665                            ),
1666                        ),
1667                    }]);
1668                }
1669            }
1670        }
1671
1672        #[test]
1673        fn score_play_when_plays_finished_and_game_not_finished() {
1674            game_test! {
1675                given: &scenario!(
1676                    build_playing(1);
1677                    with_points(0, 60),
1678                    with_hands("", "AH"),
1679                    with_cut("KC"),
1680                    with_current_plays(&[(0, "8H"), (1, "JH"), (0, "QH")]),
1681                    with_previous_plays(&[(1, "9H"), (0, "4S"), (1, "5S"), (0, "6S")])
1682                ),
1683                when: GameCommand::PlayCard {
1684                    player: PLAYER1,
1685                    card: card!("AH"),
1686                },
1687                then_events: |events: &[GameEvent]| {
1688                    assert_eq!(events, &[GameEvent::CardPlayed {
1689                        player: PLAYER1,
1690                        card: card!("AH"),
1691                        pegging: Pegging::new(
1692                            PLAYER1,
1693                            ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("AH"), Points::from(1)),
1694                        ),
1695                    }]);
1696                }
1697            }
1698        }
1699
1700        #[test]
1701        fn score_play_when_plays_finished_and_game_finished() {
1702            game_test! {
1703                given: &scenario!(
1704                    build_playing(1);
1705                    with_points(0, 120),
1706                    with_hands("", "AH"),
1707                    with_cut("KC"),
1708                    with_current_plays(&[(0, "8H"), (1, "JH"), (0, "QH")]),
1709                    with_previous_plays(&[(1, "9H"), (0, "4S"), (1, "5S"), (0, "6S")])
1710                ),
1711                when: GameCommand::PlayCard {
1712                    player: PLAYER1,
1713                    card: card!("AH"),
1714                },
1715                then_events: |events: &[GameEvent]| {
1716                    assert_eq!(events, &[GameEvent::CardPlayed {
1717                        player: PLAYER1,
1718                        card: card!("AH"),
1719                        pegging: Pegging::new(
1720                            PLAYER1,
1721                            ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("AH"), Points::from(1)),
1722                        ),
1723                    }]);
1724                }
1725            }
1726        }
1727
1728        #[test]
1729        fn swap_player_after_pone_play() {
1730            game_test! {
1731                given: &scenario!(
1732                    build_playing(1);
1733                    with_points(0, 0),
1734                    with_cut("AS"),
1735                    with_hands("7H8H8D9C", "4S5STHJH")
1736                ),
1737                when: GameCommand::PlayCard {
1738                    player: PLAYER1,
1739                    card: card!("4S"),
1740                },
1741                then_phase: |phase: &Phase| {
1742                    assert_phase_then!(phase, Phase::Playing(playing) => {
1743                        assert_eq!(playing.play_state().next_to_play(), PLAYER0);
1744                    });
1745                }
1746            }
1747        }
1748
1749        #[test]
1750        fn swap_player_after_dealer_play() {
1751            game_test! {
1752                given: &scenario!(
1753                    build_playing(0);
1754                    with_points(0, 0),
1755                    with_cut("AS"),
1756                    with_hands("7H8H8D9C", "5STHJH"),
1757                    with_current_plays(&[(1, "4S")])
1758                ),
1759                when: GameCommand::PlayCard {
1760                    player: PLAYER0,
1761                    card: card!("9C"),
1762                },
1763                then_phase: |phase: &Phase| {
1764                    assert_phase_then!(phase, Phase::Playing(playing) => {
1765                        assert_eq!(playing.play_state().next_to_play(), PLAYER1);
1766                    });
1767                }
1768            }
1769        }
1770
1771        #[test]
1772        fn reset_play_after_exact_target_reached() {
1773            game_test! {
1774                given: &scenario!(
1775                    build_playing(0);
1776                    with_points(0, 0),
1777                    with_cut("AS"),
1778                    with_hands("7H8H8D", "5STH"),
1779                    with_current_plays(&[(1, "JH"), (0, "9C"), (1, "4S")])
1780                ),
1781                when: GameCommand::PlayCard {
1782                    player: PLAYER0,
1783                    card: card!("8H"),
1784                },
1785                then_phase: |phase: &Phase| {
1786                    assert_phase_then!(phase, Phase::Playing(playing) => {
1787                        assert_eq!(playing.dealer(), &Dealer::from(PLAYER0));
1788                        assert_eq!(playing.pone(), &Pone::from(PLAYER1));
1789                        assert_eq!(playing.play_state().next_to_play(), PLAYER1);
1790                        assert_eq!(
1791                            playing.play_state().previous_plays(),
1792                            &plays!(&[(1, "JH"), (0, "9C"), (1, "4S"), (0, "8H")])
1793                        );
1794                        assert!(playing.play_state().current_plays().is_empty());
1795                    });
1796                }
1797            }
1798        }
1799
1800        #[test]
1801        fn score_play_points_for_fifteens() {
1802            game_test! {
1803                given: &scenario!(
1804                    build_playing(1);
1805                    with_points(0, 0),
1806                    with_cut("AS"),
1807                    with_hands("KH", "8D"),
1808                    with_current_plays(&[(0, "7D")])
1809                ),
1810                when: GameCommand::PlayCard {
1811                    player: PLAYER1,
1812                    card: card!("8D"),
1813                },
1814                then_events: |events: &[GameEvent]| {
1815                    assert_eq!(events, &[GameEvent::CardPlayed {
1816                        player: PLAYER1,
1817                        card: card!("8D"),
1818                        pegging: Pegging::new(
1819                            PLAYER1,
1820                            ScoreSheet::default().add_event(
1821                                ScoreKind::Fifteen,
1822                                &cards!("7D8D"),
1823                                Points::from(2),
1824                            ),
1825                        ),
1826                    }]);
1827                }
1828            }
1829        }
1830
1831        #[test]
1832        fn score_play_points_for_pair() {
1833            game_test! {
1834                given: &scenario!(
1835                    build_playing(1);
1836                    with_points(0, 0),
1837                    with_cut("AS"),
1838                    with_hands("KH", "8D"),
1839                    with_current_plays(&[(0, "8S")])
1840                ),
1841                when: GameCommand::PlayCard {
1842                    player: PLAYER1,
1843                    card: card!("8D"),
1844                },
1845                then_events: |events: &[GameEvent]| {
1846                    assert_eq!(events, &[GameEvent::CardPlayed {
1847                        player: PLAYER1,
1848                        card: card!("8D"),
1849                        pegging: Pegging::new(
1850                            PLAYER1,
1851                            ScoreSheet::default().add_event(
1852                                ScoreKind::Pair,
1853                                &cards!("8D8S"),
1854                                Points::from(2),
1855                            ),
1856                        ),
1857                    }])
1858                }
1859            }
1860        }
1861
1862        #[test]
1863        fn score_play_points_for_triplet() {
1864            game_test! {
1865                given: &scenario!(
1866                    build_playing(1);
1867                    with_points(0, 0),
1868                    with_cut("AS"),
1869                    with_hands("KH", "8DAH"),
1870                    with_current_plays(&[(1, "8C"), (0, "8S")])
1871                ),
1872                when: GameCommand::PlayCard {
1873                    player: PLAYER1,
1874                    card: card!("8D"),
1875                },
1876                then_events: |events: &[GameEvent]| {
1877                    assert_eq!(events, &[GameEvent::CardPlayed {
1878                        player: PLAYER1,
1879                        card: card!("8D"),
1880                        pegging: Pegging::new(
1881                            PLAYER1,
1882                            ScoreSheet::default().add_event(
1883                                ScoreKind::Triplet,
1884                                &cards!("8D8S8C"),
1885                                Points::from(6),
1886                            ),
1887                        ),
1888                    }])
1889                }
1890            }
1891        }
1892
1893        #[test]
1894        fn score_play_points_for_quartet() {
1895            game_test! {
1896                given: &scenario!(
1897                    build_playing(1);
1898                    with_points(0, 0),
1899                    with_cut("AS"),
1900                    with_hands("KH", "7DAH"),
1901                    with_current_plays(&[(1, "7C"), (0, "7S"), (0, "7H")])
1902                ),
1903                when: GameCommand::PlayCard {
1904                    player: PLAYER1,
1905                    card: card!("7D"),
1906                },
1907                then_events: |events: &[GameEvent]| {
1908                    assert_eq!(events, &[GameEvent::CardPlayed {
1909                        player: PLAYER1,
1910                        card: card!("7D"),
1911                        pegging: Pegging::new(
1912                            PLAYER1,
1913                            ScoreSheet::default().add_event(
1914                                ScoreKind::Quadruplet,
1915                                &cards!("7D7H7S7C"),
1916                                Points::from(12),
1917                            ),
1918                        ),
1919                    }])
1920                }
1921            }
1922        }
1923
1924        #[test]
1925        fn score_play_points_for_run() {
1926            game_test! {
1927                given: &scenario!(
1928                    build_playing(1);
1929                    with_points(0, 0),
1930                    with_cut("AC"),
1931                    with_hands("KH", "AS"),
1932                    with_current_plays(&[(1, "2D"), (0, "3H")])
1933                ),
1934                when: GameCommand::PlayCard {
1935                    player: PLAYER1,
1936                    card: card!("AS"),
1937                },
1938                then_events: |events: &[GameEvent]| {
1939                    assert_eq!(events, &[GameEvent::CardPlayed {
1940                        player: PLAYER1,
1941                        card: card!("AS"),
1942                        pegging: Pegging::new(
1943                            PLAYER1,
1944                            ScoreSheet::default().add_event(
1945                                ScoreKind::Run,
1946                                &cards!("AS2D3H"),
1947                                Points::from(3),
1948                            ),
1949                        ),
1950                    }])
1951                }
1952            }
1953        }
1954
1955        #[test]
1956        fn score_play_points_for_run_edge_case_1() {
1957            game_test! {
1958                given: &scenario!(
1959                    build_playing(0);
1960                    with_points(0, 0),
1961                    with_cut("AS"),
1962                    with_hands("7H6H", "AH"),
1963                    with_current_plays(&[(1, "8S"), (0, "7H"), (1, "7S")])
1964                ),
1965                when: GameCommand::PlayCard {
1966                    player: PLAYER0,
1967                    card: card!("6H"),
1968                },
1969                then_events: |events: &[GameEvent]| {
1970                    assert_eq!(events, &[GameEvent::CardPlayed {
1971                        player: PLAYER0,
1972                        card: card!("6H"),
1973                        pegging: Pegging::new(PLAYER0, ScoreSheet::default()),
1974                    }])
1975                }
1976            }
1977        }
1978
1979        #[test]
1980        fn score_play_points_for_run_edge_case_2() {
1981            game_test! {
1982                given: &scenario!(
1983                    build_playing(0);
1984                    with_points(0, 0),
1985                    with_cut("AS"),
1986                    with_hands("5H7H", "AH"),
1987                    with_current_plays(&[(1, "9S"), (0, "6H"), (1, "8S")])
1988                ),
1989                when: GameCommand::PlayCard {
1990                    player: PLAYER0,
1991                    card: card!("7H"),
1992                },
1993                then_events: |events: &[GameEvent]| {
1994                    assert_eq!(events, &[GameEvent::CardPlayed {
1995                        player: PLAYER0,
1996                        card: card!("7H"),
1997                        pegging: Pegging::new(
1998                            PLAYER0,
1999                            ScoreSheet::default().add_event(
2000                                ScoreKind::Run,
2001                                &cards!("6H7H8S9S"),
2002                                Points::from(4),
2003                            ),
2004                        ),
2005                    }])
2006                }
2007            }
2008        }
2009
2010        #[test]
2011        fn regather_played_cards_after_winning_play() {
2012            game_test! {
2013                given: &scenario!(
2014                    build_playing(0);
2015                    with_points(115, 116),
2016                    with_cut("9D"),
2017                    with_hands("5C", "4S"),
2018                    with_previous_plays(&[(0, "JH"), (1, "KD"), (0, "TH")]),
2019                    with_current_plays(&[(1, "6D"), (0, "5S"), (1, "5D")])
2020                ),
2021                when: GameCommand::PlayCard {
2022                    player: PLAYER0,
2023                    card: card!("5C"),
2024                },
2025                then_events: |events: &[GameEvent]| {
2026                    assert_eq!(events, &[GameEvent::CardPlayed {
2027                        player: PLAYER0,
2028                        card: card!("5C"),
2029                        pegging: Pegging::new(
2030                            PLAYER0,
2031                            ScoreSheet::default().add_event(
2032                                ScoreKind::Triplet,
2033                                &cards!("5C5D5S"),
2034                                Points::from(6),
2035                            ),
2036                        ),
2037                    }])
2038                },
2039                then_phase: |phase: &Phase| {
2040                    assert_phase_then!(phase, Phase::Finished(finished) => {
2041                        assert_eq!(finished.hand(PLAYER0).clone().sorted(), hand!("JHTH5S5C"));
2042                        assert_eq!(finished.hand(PLAYER1).clone().sorted(), hand!("KD6D5D4S"));
2043                    });
2044                }
2045            }
2046        }
2047    }
2048
2049    /// ## The Go
2050    ///
2051    /// During play, the running total of cards may never be carried beyond 31. If a player cannot
2052    /// add another card without exceeding 31, he or she says "Go" and the opponent pegs 1. After
2053    /// gaining the Go, the opponent must first lay down any additional cards he can without
2054    /// exceeding 31. Besides the point for Go, he may then score any additional points that can be
2055    /// made through pairs and runs (described later). If a player reaches exactly 31, he pegs two
2056    /// instead of one for Go.
2057    ///
2058    /// The player who called Go leads for the next series of plays, with the count starting at
2059    /// zero. The lead may not be combined with any cards previously played to form a scoring
2060    /// combination; the Go has interrupted the sequence.
2061    ///
2062    /// The person who plays the last card pegs one for Go, plus one extra if the card brings the
2063    /// count to exactly 31. The dealer is sure to peg at least one point in every hand, for he will
2064    /// have a Go on the last card if not earlier.
2065    #[allow(clippy::expect_used)]
2066    mod the_go {
2067        use super::*;
2068
2069        #[test]
2070        fn accept_go_when_pone_has_no_valid_card() {
2071            game_test! {
2072                given: &scenario!(
2073                    build_playing(1);
2074                    with_points(0, 0),
2075                    with_cut("AS"),
2076                    with_hands("AH", "KH"),
2077                    with_current_plays(&[(0, "TH"), (0, "JH"), (0, "QH")])
2078                ),
2079                when: GameCommand::Go { player: PLAYER1 },
2080                then_events: |events: &[GameEvent]| {
2081                    assert_eq!(events, &[GameEvent::GoCalled {
2082                        player: PLAYER1,
2083                        pegging: Pegging::new(PLAYER0, ScoreSheet::default()),
2084                    }])
2085                }
2086            }
2087        }
2088
2089        #[test]
2090        fn accept_go_when_dealer_has_no_valid_card() {
2091            game_test! {
2092                given: &scenario!(
2093                    build_playing(0);
2094                    with_go(),
2095                    with_points(0, 0),
2096                    with_cut("AS"),
2097                    with_hands("KH", "KS"),
2098                    with_current_plays(&[(0, "TH"), (1, "QH"), (0, "JH")])
2099                ),
2100                when: GameCommand::Go { player: PLAYER0 },
2101                then_events: |events: &[GameEvent]| {
2102                    assert_eq!(events, &[GameEvent::GoCalled {
2103                        player: PLAYER0,
2104                        pegging: Pegging::new(
2105                            PLAYER0,
2106                            ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("JH"), Points::from(1)),
2107                        ),
2108                    }])
2109                }
2110            }
2111        }
2112
2113        #[test]
2114        fn cannot_call_go_when_valid_card_held() {
2115            game_test! {
2116                given: &scenario!(
2117                    build_playing(1);
2118                    with_points(0, 0),
2119                    with_cut("AC"),
2120                    with_hands("AH", "AS"),
2121                    with_current_plays(&[(0, "TH"), (0, "JH"), (0, "8H")])
2122                ),
2123                when: GameCommand::Go { player: PLAYER1 },
2124                then_error: DomainError::InvalidGo
2125            }
2126        }
2127
2128        #[test]
2129        fn cannot_call_go_when_not_turn() {
2130            game_test! {
2131                given: &scenario!(
2132                    build_playing(1);
2133                    with_points(0, 0),
2134                    with_cut("AC"),
2135                    with_hands("AH", "AS"),
2136                    with_current_plays(&[(0, "TH"), (0, "JH"), (0, "8H")])
2137                ),
2138                when: GameCommand::Go { player: PLAYER0 },
2139                then_error: DomainError::NotPlayersTurn(PLAYER0)
2140            }
2141        }
2142
2143        #[test]
2144        fn score_go_when_both_players_called_go_playing() {
2145            game_test! {
2146                given: &scenario!(
2147                    build_playing(0);
2148                    with_go(),
2149                    with_points(0, 0),
2150                    with_cut("AS"),
2151                    with_hands("KH", "KS"),
2152                    with_current_plays(&[(0, "TH"), (1, "QH"), (0, "JH")])
2153                ),
2154                when: GameCommand::Go { player: PLAYER0 },
2155                then_events: |events: &[GameEvent]| {
2156                    assert_eq!(events, &[GameEvent::GoCalled {
2157                        player: PLAYER0,
2158                        pegging: Pegging::new(
2159                            PLAYER0,
2160                            ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("JH"), Points::from(1)),
2161                        ),
2162                    }])
2163                }
2164            }
2165        }
2166
2167        #[test]
2168        fn score_go_when_both_players_called_go_finished() {
2169            game_test! {
2170                given: &scenario!(
2171                    build_playing(0);
2172                    with_go(),
2173                    with_points(120, 0),
2174                    with_cut("AS"),
2175                    with_hands("KH", "KS"),
2176                    with_current_plays(&[(0, "TH"), (1, "QH"), (0, "JH")])
2177                ),
2178                when: GameCommand::Go { player: PLAYER0 },
2179                then_events: |events: &[GameEvent]| {
2180                    assert_eq!(events, &[GameEvent::GoCalled {
2181                        player: PLAYER0,
2182                        pegging: Pegging::new(
2183                            PLAYER0,
2184                            ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("JH"), Points::from(1)),
2185                        ),
2186                    }])
2187                },
2188                then_phase: |phase: &Phase| {
2189                    assert_phase_then!(phase, Phase::Finished(finished) => {
2190                        assert_eq!(finished.winner(), PLAYER0);
2191                    })
2192                }
2193            }
2194        }
2195
2196        #[test]
2197        fn score_go_when_played_last_card_and_opponent_calls_go_1() {
2198            game_test! {
2199                given: &scenario!(
2200                    build_playing(0);
2201                    with_points(0, 0),
2202                    with_cut("QH"),
2203                    with_hands("KH", ""),
2204                    with_previous_plays(&[(1, "KC"), (0, "TC"), (1, "JS")]),
2205                    with_current_plays(&[(0, "9C"), (1, "4C"), (0, "TS"), (1, "6D")])
2206                ),
2207                when: GameCommand::Go { player: PLAYER0 },
2208                then_events: |events: &[GameEvent]| {
2209                    assert_eq!(events, &[GameEvent::GoCalled {
2210                        player: PLAYER0,
2211                        pegging: Pegging::new(
2212                            PLAYER1,
2213                            ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("6D"), Points::from(1)),
2214                        ),
2215                    }])
2216                },
2217                then_phase: |phase: &Phase| {
2218                    assert_phase_then!(phase, Phase::Playing(playing) => {
2219                        assert_eq!(playing.play_state().next_to_play(), PLAYER0);
2220                        assert_eq!(playing.scoreboard().latest_pegging(), Some(&Pegging::new(PLAYER1, ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("6D"), Points::from(1)))));
2221                    })
2222                }
2223            }
2224        }
2225
2226        #[tracing_test::traced_test]
2227        #[test]
2228        fn score_go_when_played_last_card_and_opponent_calls_go_2() {
2229            game_test! {
2230                given: &scenario!(
2231                    build_playing(0);
2232                    with_go(),
2233                    with_points(0, 0),
2234                    with_cut("TS"),
2235                    with_hands("", "TH"),
2236                    with_previous_plays(&[(1, "KS"), (0, "7S"), (1, "JD")]),
2237                    with_current_plays(&[(0, "7D"), (1, "JS"), (0, "5H"), (0, "5S")])
2238                ),
2239                when: GameCommand::Go { player: PLAYER0 },
2240                then_events: |events: &[GameEvent]| {
2241                    assert_eq!(events, &[GameEvent::GoCalled {
2242                        player: PLAYER0,
2243                        pegging: Pegging::new(
2244                            PLAYER0,
2245                            ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("5S"), Points::from(1)),
2246                        ),
2247                    }])
2248                },
2249                then_phase: |phase: &Phase| {
2250                    assert_phase_then!(phase, Phase::Playing(playing) => {
2251                        assert_eq!(playing.play_state().next_to_play(), PLAYER1);
2252                        assert_eq!(playing.scoreboard().latest_pegging(), Some(&Pegging::new(PLAYER0, ScoreSheet::default().add_event(ScoreKind::LastCard, &cards!("5S"), Points::from(1)))));
2253                    })
2254                }
2255            }
2256        }
2257
2258        #[test]
2259        fn swap_player_after_pone_called_go() {
2260            game_test! {
2261                given: &scenario!(
2262                    build_playing(1);
2263                    with_points(0, 0),
2264                    with_cut("AS"),
2265                    with_hands("8H8D", "5SJH"),
2266                    with_current_plays(&[(1, "4S"), (0, "9C"), (1, "TH"), (0, "7H")])
2267                ),
2268                when: GameCommand::Go { player: PLAYER1 },
2269                then_phase: |phase: &Phase| {
2270                    assert_phase_then!(phase, Phase::Playing(playing) => {
2271                        assert_eq!(playing.play_state().next_to_play(), PLAYER0)
2272                    })
2273                }
2274            }
2275        }
2276
2277        #[test]
2278        fn swap_player_after_dealer_called_go() {
2279            game_test! {
2280                given: &scenario!(
2281                    build_playing(0);
2282                    with_points(0, 0),
2283                    with_cut("AS"),
2284                    with_hands("7H8H8D", "4S5S"),
2285                    with_current_plays(&[(1, "JH"), (0, "9C"), (1, "TH")])
2286                ),
2287                when: GameCommand::Go { player: PLAYER0 },
2288                then_phase: |phase: &Phase| {
2289                    assert_phase_then!(phase, Phase::Playing(playing) => {
2290                        assert_eq!(playing.play_state().next_to_play(), PLAYER1)
2291                    })
2292                }
2293            }
2294        }
2295
2296        #[test]
2297        fn reset_play_after_pone_then_dealer_called_go() {
2298            game_test! {
2299                given: &scenario!(
2300                    build_playing(0);
2301                    with_go(),
2302                    with_points(0, 0),
2303                    with_cut("AS"),
2304                    with_hands("8H8D", "5SJH"),
2305                    with_current_plays(&[(1, "4S"), (0, "9C"), (1, "TH"), (0, "7H")])
2306                ),
2307                when: GameCommand::Go { player: PLAYER0 },
2308                then_phase: |phase: &Phase| {
2309                    assert_phase_then!(phase, Phase::Playing(playing) => {
2310                        assert_eq!(playing.dealer(), &Dealer::from(PLAYER0));
2311                        assert_eq!(playing.pone(), &Pone::from(PLAYER1));
2312                        assert_eq!(playing.play_state().next_to_play(), PLAYER1);
2313                        assert_eq!(
2314                            playing.play_state().previous_plays(),
2315                            &plays!(&[(1, "4S"), (0, "9C"), (1, "TH"), (0, "7H")])
2316                        );
2317                        assert!(playing.play_state().current_plays().is_empty());
2318                    })
2319                }
2320            }
2321        }
2322
2323        #[test]
2324        fn reset_play_after_after_dealer_then_pone_called_go() {
2325            game_test! {
2326                given: &scenario!(
2327                    build_playing(1);
2328                    with_go(),
2329                    with_points(0, 0),
2330                    with_cut("AS"),
2331                    with_hands("7H8H8D", "4S5S"),
2332                    with_current_plays(&[(1, "JH"), (0, "9C"), (1, "TH")])
2333                ),
2334                when: GameCommand::Go { player: PLAYER1 },
2335                then_phase: |phase: &Phase| {
2336                    assert_phase_then!(phase, Phase::Playing(playing) => {
2337                        assert_eq!(playing.dealer(), &Dealer::from(PLAYER0));
2338                        assert_eq!(playing.pone(), &Pone::from(PLAYER1));
2339                        assert_eq!(playing.play_state().next_to_play(), PLAYER0);
2340                        assert_eq!(
2341                            playing.play_state().previous_plays(),
2342                            &plays!(&[(1, "JH"), (0, "9C"), (1, "TH")])
2343                        );
2344                        assert!(playing.play_state().current_plays().is_empty());
2345                    })
2346                }
2347            }
2348        }
2349
2350        #[test]
2351        fn regather_played_cards_after_winning_go() {
2352            game_test! {
2353                given: &scenario!(
2354                    build_playing(1);
2355                    with_go(),
2356                    with_points(115, 120),
2357                    with_cut("9D"),
2358                    with_hands("5C", "4S"),
2359                    with_previous_plays(&[(0, "JH"), (1, "KD"), (0, "TH")]),
2360                    with_current_plays(&[(1, "TC"), (0, "TS"), (1, "TD")])
2361                ),
2362                when: GameCommand::Go {
2363                    player: PLAYER1,
2364                },
2365                then_events: |events: &[GameEvent]| {
2366                    assert_eq!(events, &[GameEvent::GoCalled {
2367                        player: PLAYER1,
2368                        pegging: Pegging::new(
2369                            PLAYER1,
2370                            ScoreSheet::default().add_event(
2371                                ScoreKind::LastCard,
2372                                &cards!("TD"),
2373                                Points::from(1),
2374                            ),
2375                        ),
2376                    }])
2377                },
2378                then_phase: |phase: &Phase| {
2379                    assert_phase_then!(phase, Phase::Finished(finished) => {
2380                        assert_eq!(finished.hand(PLAYER0).clone().sorted(), hand!("JHTSTH5C"));
2381                        assert_eq!(finished.hand(PLAYER1).clone().sorted(), hand!("KDTDTC4S"));
2382                    });
2383                }
2384            }
2385        }
2386    }
2387
2388    /// ## Pegging
2389    ///
2390    /// The object in play is to score points by pegging. In addition to a Go, a player may score
2391    /// for the following combinations:
2392    ///
2393    ///   - Fifteen: For adding a card that makes the total 15 Peg 2
2394    ///   - Pair: For adding a card of the same rank as the card just played Peg 2 (Note that face
2395    ///     cards pair only by actual rank: jack with jack, but not jack with queen.)
2396    ///   - Triplet: For adding the third card of the same rank. Peg 6
2397    ///   - Four: (also called "Double Pair" or "Double Pair Royal") For adding the fourth card of
2398    ///     the same rank Peg 12
2399    ///   - Run (Sequence): For adding a card that forms, with those just played:
2400    ///     - For a sequence of three Peg 3
2401    ///     - For a sequence of four. Peg 4
2402    ///     - For a sequence of five. Peg 5
2403    ///     - (Peg one point more for each extra card of a sequence. Note that runs are independent
2404    ///       of suits, but go strictly by rank; to illustrate: 9, 10, J, or J, 9, 10 is a run but
2405    ///       9, 10, Q is not)
2406    ///
2407    /// It is important to keep track of the order in which cards are played to determine whether
2408    /// what looks like a sequence or a run has been interrupted by a "foreign card." Example:
2409    /// Cards are played in this order: 8, 7, 7, 6. The dealer pegs 2 for 15, and the opponent
2410    /// pegs 2 for pair, but the dealer cannot peg for run because of the extra seven (foreign
2411    /// card) that has been played. Example: Cards are played in this order: 9, 6, 8, 7. The
2412    /// dealer pegs 2 for fifteen when he plays the six and pegs 4 for run when he plays the seven
2413    /// (the 6, 7, 8, 9 sequence). The cards were not played in sequential order, but they form a
2414    /// true run with no foreign card.
2415    #[allow(clippy::expect_used)]
2416    mod pegging {
2417        use super::*;
2418
2419        #[test]
2420        fn should_score_fifteens() {
2421            let Phase::Playing(playing) = Game::from(
2422                scenario!(
2423                    build_playing(1);
2424                    with_points(0, 0),
2425                    with_hands("AC", ""),
2426                    with_current_plays(&[(0, "JD"), (0, "5H")]),
2427                    with_cut("AH")
2428                )
2429                .as_slice(),
2430            )
2431            .phase
2432            else {
2433                panic!("unexpected state");
2434            };
2435
2436            assert_eq!(
2437                ScoreSheet::play_card(playing.play_state()).points(),
2438                Points::from(2)
2439            )
2440        }
2441
2442        #[test]
2443        fn should_score_pairs() {
2444            let Phase::Playing(playing) = Game::from(
2445                scenario!(
2446                    build_playing(1);
2447                    with_points(0, 0),
2448                    with_hands("AC", ""),
2449                    with_current_plays(&[(0, "JD"), (0, "AH"), (0, "AS")]),
2450                    with_cut("KH")
2451                )
2452                .as_slice(),
2453            )
2454            .phase
2455            else {
2456                panic!("unexpected state");
2457            };
2458
2459            assert_eq!(
2460                ScoreSheet::play_card(playing.play_state()).points(),
2461                Points::from(2)
2462            )
2463        }
2464
2465        #[test]
2466        fn should_score_royal_pairs() {
2467            let Phase::Playing(playing) = Game::from(
2468                scenario!(
2469                    build_playing(1);
2470                    with_points(0, 0),
2471                    with_hands("AC", ""),
2472                    with_current_plays(&[(0, "AD"), (0, "AH"), (0, "AS")]),
2473                    with_cut("KH")
2474                )
2475                .as_slice(),
2476            )
2477            .phase
2478            else {
2479                panic!("unexpected state");
2480            };
2481
2482            assert_eq!(
2483                ScoreSheet::play_card(playing.play_state()).points(),
2484                Points::from(6)
2485            )
2486        }
2487
2488        #[test]
2489        fn should_score_double_royal_pairs() {
2490            let Phase::Playing(playing) = Game::from(
2491                scenario!(
2492                    build_playing(1);
2493                    with_points(0, 0),
2494                    with_hands("2H", ""),
2495                    with_current_plays(&[(0, "AC"), (0, "AD"), (0, "AH"), (0, "AS")]),
2496                    with_cut("KH")
2497                )
2498                .as_slice(),
2499            )
2500            .phase
2501            else {
2502                panic!("unexpected state")
2503            };
2504
2505            assert_eq!(
2506                ScoreSheet::play_card(playing.play_state()).points(),
2507                Points::from(12)
2508            )
2509        }
2510
2511        #[test]
2512        fn should_score_runs() {
2513            let current_plays = &[
2514                (0, "2C"),
2515                (0, "3C"),
2516                (0, "4C"),
2517                (0, "5C"),
2518                (0, "6C"),
2519                (0, "7C"),
2520            ];
2521
2522            for len in 1..=current_plays.len() {
2523                let current_plays = *current_plays;
2524                let current_plays = current_plays.into_iter().take(len);
2525                let current_plays = Vec::from_iter(current_plays);
2526                let Phase::Playing(playing) = Game::from(
2527                    scenario!(
2528                        build_playing(1);
2529                        with_points(0, 0),
2530                        with_hands("AS", "AD"),
2531                        with_current_plays(&current_plays),
2532                        with_cut("KH")
2533                    )
2534                    .as_slice(),
2535                )
2536                .phase
2537                else {
2538                    panic!("unexpected state")
2539                };
2540
2541                assert_eq!(
2542                    ScoreSheet::play_card(playing.play_state()).points(),
2543                    Points::from(if len < 3 { 0 } else { len })
2544                )
2545            }
2546        }
2547
2548        #[test]
2549        fn should_score_runs_unordered() {
2550            let Phase::Playing(playing) = Game::from(
2551                scenario!(
2552                    build_playing(1);
2553                    with_points(0, 0),
2554                    with_hands("KS", "KD"),
2555                    with_current_plays(&[(0, "3S"), (0, "2C"), (0, "AS")]),
2556                    with_cut("KH")
2557                )
2558                .as_slice(),
2559            )
2560            .phase
2561            else {
2562                panic!("unexpected state");
2563            };
2564
2565            assert_eq!(
2566                ScoreSheet::play_card(playing.play_state()).points(),
2567                Points::from(3)
2568            )
2569        }
2570
2571        #[test]
2572        fn should_score_rules_example_flush() {
2573            let Phase::Playing(playing) = Game::from(
2574                scenario!(
2575                    build_playing(0);
2576                    with_points(0, 0),
2577                    with_hands("", "2H"),
2578                    with_cut("3H"),
2579                    with_current_plays(&[(1, "TH"), (0, "8H"), (1, "QH"), (0, "AH")])
2580                )
2581                .as_slice(),
2582            )
2583            .phase
2584            else {
2585                panic!("unexpected state");
2586            };
2587
2588            assert_eq!(
2589                ScoreSheet::play_card(playing.play_state()).points(),
2590                Points::from(0)
2591            );
2592        }
2593
2594        #[test]
2595        fn should_score_when_target_not_reached() {
2596            let Phase::Playing(playing) = Game::from(
2597                scenario!(
2598                    build_playing(1);
2599                    with_go(),
2600                    with_points(0, 0),
2601                    with_hands("", ""),
2602                    with_current_plays(&[(0, "AC"), (0, "2D"), (0, "5H"), (0, "4S")]),
2603                    with_cut("KH")
2604                )
2605                .as_slice(),
2606            )
2607            .phase
2608            else {
2609                panic!("unexpected state");
2610            };
2611
2612            assert_eq!(
2613                ScoreSheet::go(playing.play_state()).points(),
2614                Points::from(1)
2615            );
2616        }
2617
2618        #[test]
2619        fn should_score_when_target_reached() {
2620            let Phase::Playing(playing) = Game::from(
2621                scenario!(
2622                    build_playing(1);
2623                    with_points(0, 0),
2624                    with_hands("", ""),
2625                    with_current_plays(&[(0, "KC"), (0, "KD"), (0, "KH"), (0, "AS")]),
2626                    with_cut("KS")
2627                )
2628                .as_slice(),
2629            )
2630            .phase
2631            else {
2632                panic!("unexpected state");
2633            };
2634
2635            assert_eq!(
2636                ScoreSheet::play_card(playing.play_state()).points(),
2637                Points::from(2)
2638            )
2639        }
2640    }
2641
2642    /// ## Counting the Hands
2643    ///
2644    /// When play ends, the three hands are counted in order: non-dealer's hand (first), dealer's
2645    /// hand (second), and then the crib (third). This order is important because, toward the end of
2646    /// a game, the non-dealer may "count out" and win before the dealer has a chance to count, even
2647    /// though the dealer's total would have exceeded that of the opponent. The starter is
2648    /// considered to be a part of each hand, so that all hands in counting comprise five cards. The
2649    /// basic scoring formations are as follows:
2650    ///
2651    /// Combinations counts
2652    ///   - Fifteen. Each combination of cards that totals 15 2
2653    ///   - Pair. Each pair of cards of the same rank 2
2654    ///   - Run. Each combination of three or more 1 cards in sequence (for each card in the
2655    ///     sequence)
2656    ///   - Flush.
2657    ///     - Four cards of the same suit in hand 4 (excluding the crib, and the starter)
2658    ///     - Four cards in hand or crib of the same 5 suit as the starter. (There is no count for
2659    ///       four-flush in the crib that is not of same suit as the starter)
2660    ///   - His Nobs. Jack of the same suit as starter in hand or crib 1
2661    #[allow(clippy::expect_used)]
2662    mod counting_the_hands {
2663        use super::*;
2664
2665        #[test]
2666        fn score_pone_hand_when_plays_finished() {
2667            game_test! {
2668                given: &scenario!(
2669                    build_playing(1);
2670                    with_points(0, 0),
2671                    with_hands("", "TH"),
2672                    with_cut("4H"),
2673                    with_previous_plays(&[
2674                        (0, "7H"), (0, "8C"), (0, "AC"), (0, "2C"),
2675                        (1, "QH"), (1, "KS"), (1, "5H"), (1, "TH"),
2676                    ]),
2677                    with_ack(0)
2678                ),
2679                when: GameCommand::ScorePone { player: PLAYER1 },
2680                then_events: |events: &[GameEvent]| {
2681                    assert_eq!(events.len(), 1);
2682
2683                    find_then!(events, GameEvent::PoneScored { player, pegging } => {
2684                        assert_eq!(player, &PLAYER1);
2685                        assert_eq!(pegging.recipient(), &PLAYER1);
2686                        assert_eq!(pegging.score_sheet().points(), Points::from(6));
2687                    });
2688                }
2689            }
2690        }
2691
2692        #[test]
2693        fn score_winning_pone_hand_when_plays_finished() {
2694            game_test! {
2695                given: &scenario!(
2696                    build_playing(1);
2697                    with_points(0, 115),
2698                    with_hands("", "TH"),
2699                    with_cut("4H"),
2700                    with_previous_plays(&[
2701                        (0, "7H"), (0, "8C"), (0, "AC"), (0, "2C"),
2702                        (1, "QH"), (1, "KS"), (1, "5H"), (1, "TH"),
2703                    ]),
2704                    with_ack(0)
2705                ),
2706                when: GameCommand::ScorePone { player: PLAYER1 },
2707                then_events: |events: &[GameEvent]| {
2708                    find_then!(events, GameEvent::PoneScored { player, pegging } => {
2709                        assert_eq!(player, &PLAYER1);
2710                        assert_eq!(pegging.recipient(), &PLAYER1);
2711                        assert_eq!(pegging.score_sheet().points(), Points::from(6));
2712                    });
2713                },
2714                then_phase: |phase: &Phase| {
2715                    assert_phase_then!(phase, Phase::Finished(finished) => {
2716                        assert_eq!(finished.winner(), PLAYER1);
2717                    });
2718                }
2719            }
2720        }
2721
2722        #[test]
2723        fn score_dealer_hand_when_pone_score_acknowledged() {
2724            game_test! {
2725                given: &scenario!(
2726                    build_scoring_pone;
2727                    with_points(0, 0),
2728                    with_cut("4H"),
2729                    with_hands("7H8CAC2C", "JCKS5HTH"),
2730                    with_crib("AHADASTD"),
2731                    with_ack(0),
2732                ),
2733                when: GameCommand::ScoreDealer { player: PLAYER1 },
2734                then_events: |events: &[GameEvent]| {
2735                    assert_eq!(events.len(), 1);
2736
2737                    find_then!(events, GameEvent::DealerScored { player, pegging } => {
2738                        assert_eq!(player, &PLAYER1);
2739                        assert_eq!(pegging.recipient(), &PLAYER0);
2740                        assert_eq!(pegging.score_sheet().points(), Points::from(4));
2741                    });
2742                }
2743            }
2744        }
2745
2746        #[test]
2747        fn score_winning_dealer_hand_when_pone_score_acknowledged() {
2748            game_test! {
2749                given: &scenario!(
2750                    build_scoring_pone;
2751                    with_points(117, 0),
2752                    with_cut("4H"),
2753                    with_hands("7H8CAC2C", "JCKS5HTH"),
2754                    with_crib("AHADASTD"),
2755                    with_ack(0),
2756                ),
2757                when: GameCommand::ScoreDealer { player: PLAYER1 },
2758                then_events: |events: &[GameEvent]| {
2759                    assert_eq!(events.len(), 1);
2760
2761                    find_then!(events, GameEvent::DealerScored { player, pegging } => {
2762                        assert_eq!(player, &PLAYER1);
2763                        assert_eq!(pegging.recipient(), &PLAYER0);
2764                        assert_eq!(pegging.score_sheet().points(), Points::from(4));
2765                    });
2766                },
2767                then_phase: |phase: &Phase| {
2768                    assert_phase_then!(phase, Phase::Finished(finished) => {
2769                        assert_eq!(finished.winner(), PLAYER0);
2770                    });
2771                }
2772            }
2773        }
2774
2775        #[test]
2776        fn score_crib_when_dealer_score_acknowledged() {
2777            game_test! {
2778                given: &scenario!(
2779                    build_scoring_dealer;
2780                    with_points(0, 0),
2781                    with_cut("4H"),
2782                    with_hands("7H8CAC2C", "JCKS5HTH"),
2783                    with_crib("AHADASTD"),
2784                    with_ack(0),
2785                ),
2786                when: GameCommand::ScoreCrib { player: PLAYER1 },
2787                then_events: |events: &[GameEvent]| {
2788                    assert_eq!(events.len(), 1);
2789
2790                    find_then!(events, GameEvent::CribScored { player, pegging } => {
2791                        assert_eq!(player, &PLAYER1);
2792                        assert_eq!(pegging.recipient(), &PLAYER0);
2793                        assert_eq!(pegging.score_sheet().points(), Points::from(12));
2794                    });
2795                }
2796            }
2797        }
2798
2799        #[test]
2800        fn score_winning_crib_when_dealer_score_acknowledged() {
2801            game_test! {
2802                given: &scenario!(
2803                    build_scoring_dealer;
2804                    with_points(109, 0),
2805                    with_cut("4H"),
2806                    with_hands("7H8CAC2C", "JCKS5HTH"),
2807                    with_crib("AHADASTD"),
2808                    with_ack(0),
2809                ),
2810                when: GameCommand::ScoreCrib { player: PLAYER1 },
2811                then_events: |events: &[GameEvent]| {
2812                    assert_eq!(events.len(), 1);
2813
2814                    find_then!(events, GameEvent::CribScored { player, pegging } => {
2815                        assert_eq!(player, &PLAYER1);
2816                        assert_eq!(pegging.recipient(), &PLAYER0);
2817                        assert_eq!(pegging.score_sheet().points(), Points::from(12));
2818                    });
2819                },
2820                then_phase: |phase: &Phase| {
2821                    assert_phase_then!(phase, Phase::Finished(finished) => {
2822                        assert_eq!(finished.winner(), PLAYER0);
2823                    });
2824                }
2825            }
2826        }
2827
2828        #[test]
2829        fn redeal_when_crib_score_acknowledged() {
2830            game_test! {
2831                given: &scenario!(
2832                    build_scoring_crib;
2833                    with_points(0, 0),
2834                    with_cut("4H"),
2835                    with_hands("7H8CAC2C", "JCKS5HTH"),
2836                    with_crib("AHADASTD"),
2837                    with_ack(0)
2838                ),
2839                when: GameCommand::StartNextRound { player: PLAYER1 },
2840                then_events: |events: &[GameEvent]| {
2841                    find_then!(events, GameEvent::NextRoundStarted { player } => {
2842                        assert_eq!(player, &PLAYER1);
2843                    });
2844                    let deals = events
2845                        .iter()
2846                        .filter(|e| matches!(e, GameEvent::HandDealt { .. }))
2847                        .collect::<Vec<_>>();
2848                    assert_eq!(deals.len(), PLAYER_COUNT);
2849                },
2850                then_phase: |phase: &Phase| {
2851                    assert_phase_then!(phase, Phase::Discarding(discarding) => {
2852                        assert_eq!(discarding.dealer(), &Dealer::from(PLAYER1));
2853                        assert_eq!(discarding.pone(), &Pone::from(PLAYER0));
2854                        assert_eq!(discarding.hand(PLAYER0).len(), CARDS_DEALT_PER_HAND);
2855                        assert_eq!(discarding.hand(PLAYER1).len(), CARDS_DEALT_PER_HAND);
2856                    })
2857                }
2858            }
2859        }
2860
2861        #[test]
2862        fn hand_should_score_fifteens() {
2863            assert_eq!(
2864                ScoreSheet::hand(&hand!("7H8CAC2C"), card!("4H")).points(),
2865                Points::from(4)
2866            );
2867            assert_eq!(
2868                ScoreSheet::hand(&hand!("THJCKS5H"), card!("4H")).points(),
2869                Points::from(6)
2870            );
2871        }
2872
2873        #[test]
2874        fn hand_should_score_pairs() {
2875            assert_eq!(
2876                ScoreSheet::hand(&hand!("2H4C5C2C"), card!("AH")).points(),
2877                Points::from(2)
2878            );
2879            assert_eq!(
2880                ScoreSheet::hand(&hand!("TCASADTH"), card!("AH")).points(),
2881                Points::from(8)
2882            );
2883        }
2884
2885        #[test]
2886        fn hand_should_score_royal_pairs() {
2887            assert_eq!(
2888                ScoreSheet::hand(&hand!("2H2D5C2C"), card!("AH")).points(),
2889                Points::from(6)
2890            );
2891            assert_eq!(
2892                ScoreSheet::hand(&hand!("TCASADTH"), card!("AH")).points(),
2893                Points::from(8)
2894            );
2895        }
2896
2897        #[test]
2898        fn hand_should_score_double_royal_pairs() {
2899            assert_eq!(
2900                ScoreSheet::hand(&hand!("2H2C2D2S"), card!("AH")).points(),
2901                Points::from(12)
2902            );
2903            assert_eq!(
2904                ScoreSheet::hand(&hand!("TCASADTH"), card!("AH")).points(),
2905                Points::from(8)
2906            );
2907        }
2908
2909        #[test]
2910        fn hand_should_score_runs() {
2911            assert_eq!(
2912                ScoreSheet::hand(&hand!("JDQCKC2C"), card!("AH")).points(),
2913                Points::from(3)
2914            );
2915            assert_eq!(
2916                ScoreSheet::hand(&hand!("3C3S2D5H"), card!("AH")).points(),
2917                Points::from(8)
2918            );
2919        }
2920
2921        #[test]
2922        fn hand_should_score_flushes() {
2923            assert_eq!(
2924                ScoreSheet::hand(&hand!("2H4H6H8H"), card!("TH")).points(),
2925                Points::from(5)
2926            );
2927            assert_eq!(
2928                ScoreSheet::hand(&hand!("2D4D6D8D"), card!("TH")).points(),
2929                Points::from(4)
2930            );
2931        }
2932
2933        #[test]
2934        fn hand_should_score_nobs() {
2935            assert_eq!(
2936                ScoreSheet::hand(&hand!("2D4H6HJH"), card!("TH")).points(),
2937                Points::from(1)
2938            );
2939            assert_eq!(
2940                ScoreSheet::hand(&hand!("2H4D6DJD"), card!("TH")).points(),
2941                Points::from(0)
2942            );
2943        }
2944
2945        #[test]
2946        fn crib_should_score_fifteens() {
2947            assert_eq!(
2948                ScoreSheet::crib(&crib!("7H8CAC2C"), card!("4H")).points(),
2949                Points::from(4)
2950            );
2951            assert_eq!(
2952                ScoreSheet::crib(&crib!("THJCKS5H"), card!("4H")).points(),
2953                Points::from(6)
2954            );
2955        }
2956
2957        #[test]
2958        fn crib_should_score_pairs() {
2959            assert_eq!(
2960                ScoreSheet::crib(&crib!("2H4C5C2C"), card!("AH")).points(),
2961                Points::from(2)
2962            );
2963            assert_eq!(
2964                ScoreSheet::crib(&crib!("TCASADTH"), card!("AH")).points(),
2965                Points::from(8)
2966            );
2967        }
2968
2969        #[test]
2970        fn crib_should_score_royal_pairs() {
2971            assert_eq!(
2972                ScoreSheet::crib(&crib!("2H2D5C2C"), card!("AH")).points(),
2973                Points::from(6)
2974            );
2975            assert_eq!(
2976                ScoreSheet::crib(&crib!("TCASADTH"), card!("AH")).points(),
2977                Points::from(8)
2978            );
2979        }
2980
2981        #[test]
2982        fn crib_should_score_double_royal_pairs() {
2983            assert_eq!(
2984                ScoreSheet::crib(&crib!("2H2C2D2S"), card!("AH")).points(),
2985                Points::from(12)
2986            );
2987            assert_eq!(
2988                ScoreSheet::crib(&crib!("TCASADTH"), card!("AH")).points(),
2989                Points::from(8)
2990            );
2991        }
2992
2993        #[test]
2994        fn crib_should_score_runs() {
2995            assert_eq!(
2996                ScoreSheet::crib(&crib!("JDQCKC2C"), card!("AH")).points(),
2997                Points::from(3)
2998            );
2999            assert_eq!(
3000                ScoreSheet::crib(&crib!("3C3S2D5H"), card!("AH")).points(),
3001                Points::from(8)
3002            );
3003        }
3004
3005        #[test]
3006        fn crib_should_score_flushes() {
3007            assert_eq!(
3008                ScoreSheet::crib(&crib!("2H4H6H8H"), card!("TH")).points(),
3009                Points::from(5)
3010            );
3011            assert_eq!(
3012                ScoreSheet::crib(&crib!("2D4D6D8D"), card!("TH")).points(),
3013                Points::from(0)
3014            );
3015        }
3016
3017        #[test]
3018        fn crib_should_score_nobs() {
3019            assert_eq!(
3020                ScoreSheet::crib(&crib!("2D4H6HJH"), card!("TH")).points(),
3021                Points::from(1)
3022            );
3023            assert_eq!(
3024                ScoreSheet::crib(&crib!("2H4D6DJD"), card!("TH")).points(),
3025                Points::from(0)
3026            );
3027        }
3028    }
3029
3030    /// ### Combinations
3031    ///
3032    /// In the above table, the word combination is used in the strict technical sense. Each and
3033    /// every combination of two cards that make a pair, of two or more cards that make 15, or of
3034    /// three or more cards that make a run, count separately.
3035    ///
3036    /// Example: A hand (including the starter) comprised of 8, 7, 7, 6, 2 scores 8 points for four
3037    /// combinations that total 15: the 8 with one 7, and the 8 with the other 7; the 6, 2 with each
3038    /// of the two 7s. The same hand also scores 2 for a pair, and 6 for two runs of three (8, 7, 6
3039    /// using each of the two 7s). The total score is 16. An experienced player computes the hand
3040    /// thus: "Fifteen 2, fifteen 4, fifteen 6, fifteen 8, and 8 for double run is 16."
3041    ///
3042    /// Note that the ace is always low and cannot form a sequence with a king. Further, a flush
3043    /// cannot happen during the play of the cards; it occurs only when the hands and the crib are
3044    /// counted.
3045    ///
3046    /// Certain basic formulations should be learned to facilitate counting. For pairs and runs
3047    /// alone:
3048    ///
3049    /// A. A triplet counts 6. A. Four of a kind counts 12. A. A run of three, with one card
3050    /// duplicated (double run) counts 8. A. A run of four, with one card duplicated, counts 10. A.
3051    /// A run of three, with one card triplicated (triple run), counts 15. A. A run of three, with
3052    /// two different cards duplicated, counts 16.
3053    #[allow(clippy::expect_used)]
3054    mod combinations {
3055        use super::*;
3056
3057        #[test]
3058        fn should_score_rules_example_eights_sevens_sixes() {
3059            assert_eq!(
3060                ScoreSheet::hand(&hand!("8H7C7D6S"), card!("2H")).points(),
3061                Points::from(16)
3062            );
3063        }
3064
3065        #[test]
3066        fn should_score_rules_example_runs() {
3067            assert_eq!(
3068                ScoreSheet::hand(&hand!("JHQCKDAS"), card!("2D")).points(),
3069                Points::from(3)
3070            );
3071        }
3072
3073        #[test]
3074        fn should_score_rules_example_flush() {
3075            assert_eq!(
3076                ScoreSheet::hand(&hand!("THQHKHAH"), card!("2H")).points(),
3077                Points::from(5)
3078            );
3079            assert_eq!(
3080                ScoreSheet::hand(&hand!("THQHKHAH"), card!("2S")).points(),
3081                Points::from(4)
3082            );
3083            assert_eq!(
3084                ScoreSheet::hand(&hand!("THQHKHAS"), card!("2H")).points(),
3085                Points::from(0)
3086            );
3087        }
3088    }
3089
3090    /// ### A PERFECT 29!
3091    ///
3092    /// The highest possible score for combinations in a single Cribbage deal is 29, and it may
3093    /// occur only once in a Cribbage fan's lifetime -in fact, experts say that a 29 is probably as
3094    /// rare as a hole-in-one in golf. To make this amazing score, a player must have a five as the
3095    /// starter (upcard) and the other three fives plus the jack of the same suit as the starter -
3096    /// His Nobs: 1 point - in his hand. The double pair royal (four 5s) peg another 12 points; the
3097    /// various fives used to hit 15 can be done four ways for 8 points; and the jack plus a 5 to
3098    /// hit 15 can also be done four ways for 8 points. Total = 29 points.
3099    #[allow(clippy::expect_used)]
3100    mod a_perfect_29 {
3101        use super::*;
3102
3103        #[test]
3104        fn should_score_rules_example_perfect_29() {
3105            assert_eq!(
3106                ScoreSheet::hand(&hand!("5H5C5DJS"), card!("5S")).points(),
3107                Points::from(29)
3108            );
3109        }
3110    }
3111
3112    /// ## Miscellaneous
3113    ///
3114    /// The following list includes many of the hands that may give the beginner some difficulty in
3115    /// counting. Note that no hand can make a count of 19, 25, 26, or 27. (In the chart below J
3116    /// stands for His Nobs, the jack of the same suit as the starter.
3117    ///
3118    /// ### Muggins (optional) - not implemented.
3119    ///
3120    /// Each player must count his hand (and crib) aloud and announce the total. If he overlooks any
3121    /// score, the opponent may say "Muggins" and then score the overlooked points for himself. For
3122    /// experienced players, the Muggins rule is always in effect and adds even more suspense to the
3123    /// game.
3124    #[allow(clippy::expect_used)]
3125    mod miscellaneous {}
3126
3127    /// ## Game
3128    ///
3129    /// Game may be fixed at either 121 points or 61 points. The play ends the moment either player
3130    /// reaches the agreed total, whether by pegging or counting one's hand. If the non-dealer "goes
3131    /// out" by the count of his hand, the game immediately ends and the dealer may not score either
3132    /// his hand or the crib.
3133    ///
3134    /// If a player wins the game before the loser has passed the halfway mark (did not reach 31 in
3135    /// a game of 61, or 61 in a game of 121), the loser is "lurched," and the winner scores two
3136    /// games instead of one. A popular variation of games played to 121, is a "skunk" (double game)
3137    /// for the winner if the losing player fails to pass the three-quarter mark - 91 points or more -
3138    /// and it is a "double skunk" (quadruple game) if the loser fails to pass the halfway mark (61
3139    /// or more points).
3140    #[allow(clippy::expect_used)]
3141    mod game {}
3142
3143    /// ## The Cribbage Board
3144    ///
3145    /// The Cribbage board (see illustration) has four rows of 30 holes each, divided into two pairs
3146    /// of rows by a central panel. There are usually four (or two) additional holes near one end,
3147    /// called "game holes." With the board come four pegs, usually in two contrasting colors. Note:
3148    /// There are also continuous track Cribbage boards available which, as the name implies, have
3149    /// one continuous line of 121 holes for each player.
3150    ///
3151    /// The board is placed to one side between the two players, and each player takes two pegs of
3152    /// the same color. (The pegs are placed in the game holes until the game begins.) Each time a
3153    /// player scores, he advances a peg along a row on his side of the board, counting one hole per
3154    /// point. Two pegs are used, and the rearmost peg jumps over the first peg to show the first
3155    /// increment in score. After another increase in score, the peg behind jumps over the peg in
3156    /// front to the appropriate hole to show the player's new score, and so on (see diagram next
3157    /// page). The custom is to "go down" (away from the game holes) on the outer rows and "come up"
3158    /// on the inner rows. A game of 61 is "once around" and a game of 121 is "twice around." As
3159    /// noted previously, continuous line Cribbage boards are available.
3160    ///
3161    /// If a Cribbage board is not available, each player may use a piece of paper or cardboard,
3162    /// marked thus:
3163    ///
3164    ///   - Units 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
3165    ///   - Tens 10, 20, 30, 40, 50, 60
3166    ///
3167    /// Two small markers, such as small coins or buttons, can substitute for pegs for counting in
3168    /// each row.
3169    #[allow(clippy::expect_used)]
3170    mod the_cribbage_board {}
3171
3172    /// ## Strategy
3173    ///
3174    /// ### The Crib.
3175    ///
3176    /// If the dealer is discarding for the crib, he should “salt” it with the best possible cards,
3177    /// but at the same time retain good cards in his hand that can be used for high scoring.
3178    /// Conversely, for the non-dealer, it is best to lay out cards that will be the least
3179    /// advantageous for the dealer. Laying out a five would be the worst choice, for the dealer
3180    /// could use it to make 15 with any one of the ten-cards (10, J, Q, K). Laying out a pair is
3181    /// usually a poor choice too, and the same goes for sequential cards, such as putting both a
3182    /// six and seven in the crib. The ace and king tend to be good cards to put in the crib because
3183    /// it is harder to use them in a run.
3184    ///
3185    /// ### The Play
3186    ///
3187    /// As expected, the five makes for the worst lead in that there are so many ten-cards that the
3188    /// opponent can use to make a 15. Leading from a pair is a good idea, for even if the opponent
3189    /// makes a pair, the leader can play the other matching card from his hand and collect for a
3190    /// pair royal. Leading an ace or deuce is not a good idea, for these cards should be saved
3191    /// until later to help make a 15, a Go, or a 31. The safest lead is a four because this card
3192    /// cannot be used to make a 15 at the opponent’s very next turn. Finally, when the opponent
3193    /// leads a card that can either be paired or make 15, the latter choice is preferred.
3194    ///
3195    /// During the play, it is advisable not to try to make a count of 21, for the opponent can then
3196    /// play one of the many 10-cards and make 31 to gain two points.
3197    #[allow(clippy::expect_used)]
3198    mod the_strategy {}
3199
3200    /// ## Internal
3201    #[allow(clippy::expect_used)]
3202    mod internal {
3203        use super::*;
3204
3205        fn common_filters() -> insta::Settings {
3206            let mut settings = insta::Settings::new();
3207            settings.add_filter(
3208                r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{1,9}",
3209                "<timestamp>",
3210            );
3211            settings.add_filter(
3212                r"UserId\([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\)",
3213                "<userid>",
3214            );
3215            settings.add_filter(r"Player\([0-1]\)", "<player>");
3216            settings.add_filter(r"(A|[2-9]|T|J|Q|K)(H|C|D|S)", "<card>");
3217            settings.add_filter(r"<card>(, <card>)*", "[<cards>]");
3218            settings.add_filter(r"\s*\d+ ->\s*\d+", "<score>");
3219            settings
3220        }
3221
3222        #[test]
3223        fn should_output_user_readable_starting_game_in_logs() {
3224            let game = GameBuilder::default().with_cuts("ASAC").build_starting();
3225            common_filters().bind(|| {
3226                insta::assert_snapshot!(game.to_string(), @r"
3227                test-game__<timestamp> U[<cards>]
3228                <userid> <userid>
3229                Starting(
3230                    cuts: [<cards>]
3231                    deck: Deck([<cards>])
3232                    pending: Pending(<player>, <player>)
3233                )
3234                ")
3235            });
3236        }
3237
3238        #[test]
3239        fn should_output_user_readable_discarding_game_in_logs() {
3240            let game = GameBuilder::default()
3241                .with_points(0, 0)
3242                .with_hands("AH2H3H4H5H6H", "AC2C3C4C5C6C")
3243                .build_discarding();
3244            common_filters().bind(|| {
3245                insta::assert_snapshot!(game.to_string(), @r"
3246                test-game__<timestamp> U[<cards>]
3247                <userid> <userid>
3248                Discarding(
3249                    scoreboard: Scoreboard(<score>,<score>)
3250                    roles: Dealer(<player>), Pone(<player>)
3251                    hands: Hand([<cards>]), Hand([<cards>])
3252                    crib: Crib()
3253                    deck: Deck([<cards>])
3254                    pending: Pending(<player>, <player>)
3255                )
3256                ")
3257            });
3258        }
3259
3260        #[test]
3261        fn should_output_user_readable_playing_game_in_logs() {
3262            let game = GameBuilder::default()
3263                .with_points(0, 0)
3264                .with_hands("9S", "4S")
3265                .with_cut("AS")
3266                .with_current_plays(&[(0, "AH")])
3267                .build_playing(1);
3268            common_filters().bind(|| insta::assert_snapshot!(game.to_string(), @r"
3269                                     test-game__<timestamp> U[<cards>]
3270                                     <userid> <userid>
3271                                     Playing(
3272                                         scoreboard: Scoreboard(<score>,<score>),
3273                                         roles: Dealer(<player>), Pone(<player>),
3274                                         hands: Hand([<cards>]), Hand([<cards>]),
3275                                         play_state: Next(<player>), GoStatus(NotCalled), Pending(<player> -> [<cards>], <player> -> [<cards>]), Current((<player> -> [<cards>])), Previous(),
3276                                         cut: [<cards>],
3277                                         crib: Crib(),
3278                                         pending: Pending(<player>, <player>)
3279                                     )
3280                                     "));
3281        }
3282
3283        #[test]
3284        fn should_output_user_readable_pone_scoring_game_in_logs() {
3285            let game = GameBuilder::default()
3286                .with_points(0, 0)
3287                .with_hands("AS2S3S4S", "AC2C3C4C")
3288                .with_cut("JH")
3289                .with_crib("TSJSQSKS")
3290                .build_scoring_pone();
3291            common_filters().bind(|| {
3292                insta::assert_snapshot!(game.to_string(), @r"
3293                test-game__<timestamp> U[<cards>]
3294                <userid> <userid>
3295                ScoringPone(
3296                    scoreboard: Scoreboard(<score>,<score>),
3297                    roles: Dealer(<player>), Pone(<player>),
3298                    hands: Hand([<cards>]), Hand([<cards>]),
3299                    cut: [<cards>],
3300                    crib: Crib([<cards>]),
3301                    pegging: <player> -> Fifteen: ([<cards>]) -> 2, Fifteen: ([<cards>]) -> 2, Run: ([<cards>]) -> 4, Flush: ([<cards>]) -> 4,
3302                    pending: Pending(<player>, <player>)
3303                )
3304                ")
3305            });
3306        }
3307
3308        #[test]
3309        fn should_output_user_readable_dealer_scoring_game_in_logs() {
3310            let game = GameBuilder::default()
3311                .with_points(0, 0)
3312                .with_hands("AS2S3S4S", "AC2C3C4C")
3313                .with_cut("JH")
3314                .with_crib("TSJSQSKS")
3315                .build_scoring_dealer();
3316            common_filters().bind(|| {
3317                insta::assert_snapshot!(game.to_string(), @r"
3318                test-game__<timestamp> U[<cards>]
3319                <userid> <userid>
3320                ScoringDealer(
3321                    scoreboard: Scoreboard(<score>,<score>),
3322                    roles: Dealer(<player>), Pone(<player>),
3323                    hands: Hand([<cards>]), Hand([<cards>]),
3324                    cut: [<cards>],
3325                    crib: Crib([<cards>]),
3326                    pegging: <player> -> Fifteen: ([<cards>]) -> 2, Fifteen: ([<cards>]) -> 2, Run: ([<cards>]) -> 4, Flush: ([<cards>]) -> 4,
3327                    pending: Pending(<player>, <player>)
3328                )
3329                ")
3330            });
3331        }
3332
3333        #[test]
3334        fn should_output_user_readable_crib_scoring_game_in_logs() {
3335            let game = GameBuilder::default()
3336                .with_points(0, 0)
3337                .with_hands("AS2S3S4S", "AC2C3C4C")
3338                .with_cut("JH")
3339                .with_crib("TSJSQSKS")
3340                .build_scoring_crib();
3341            common_filters().bind(|| {
3342                insta::assert_snapshot!(game.to_string(), @r"
3343                test-game__<timestamp> U[<cards>]
3344                <userid> <userid>
3345                ScoringCrib(
3346                    scoreboard: Scoreboard(<score>,<score>),
3347                    roles: Dealer(<player>), Pone(<player>),
3348                    hands: Hand([<cards>]), Hand([<cards>]),
3349                    cut: [<cards>],
3350                    crib: Crib([<cards>]),
3351                    pegging: <player> -> Pair: ([<cards>]) -> 2, Run: ([<cards>]) -> 4, Run: ([<cards>]) -> 4,
3352                    pending: Pending(<player>, <player>)
3353                )
3354                ")
3355            });
3356        }
3357
3358        #[test]
3359        fn should_output_user_readable_finished_game_in_logs() {
3360            let game = GameBuilder::default()
3361                .with_points(0, 121)
3362                .with_winner(1)
3363                .with_hands("AS2S3S4S", "AC2C3C4C")
3364                .with_cut("JH")
3365                .with_crib("TSJSQSKS")
3366                .build_finished();
3367            common_filters().bind(|| {
3368                insta::assert_snapshot!(game.to_string(), @r"
3369                test-game__<timestamp> U[<cards>]
3370                <userid> <userid>
3371                Finished(
3372                    winner: <player>,
3373                    scoreboard: Scoreboard(<score>,<score>),
3374                    roles: Dealer(<player>), Pone(<player>),
3375                    hands: Hand([<cards>]), Hand([<cards>]),
3376                    crib: Crib([<cards>]),
3377                    cut: [<cards>]
3378                )
3379                ")
3380            });
3381        }
3382    }
3383}