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#[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 #[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 #[must_use]
38 pub fn id(&self) -> GameId {
39 self.id
40 }
41
42 #[must_use]
44 pub fn host(&self) -> UserId {
45 self.host
46 }
47
48 #[must_use]
50 pub fn guest(&self) -> Option<UserId> {
51 self.guest
52 }
53
54 #[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 #[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 #[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 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#[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 #[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 #[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 #[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 #[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 #[allow(clippy::expect_used)]
1181 mod object_of_the_game {}
1182
1183 #[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 #[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 #[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 #[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 #[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(¤t_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 #[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 #[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 #[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 #[allow(clippy::expect_used)]
3125 mod miscellaneous {}
3126
3127 #[allow(clippy::expect_used)]
3141 mod game {}
3142
3143 #[allow(clippy::expect_used)]
3170 mod the_cribbage_board {}
3171
3172 #[allow(clippy::expect_used)]
3198 mod the_strategy {}
3199
3200 #[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}