Skip to main content

freezeout_server/table/
state.rs

1// Copyright (C) 2025 Vince Vasta
2// SPDX-License-Identifier: Apache-2.0
3
4//! Table state types.
5use ahash::AHashSet;
6use log::{error, info};
7use rand::{SeedableRng, rngs::StdRng};
8use std::{
9    sync::Arc,
10    time::{Duration, Instant},
11};
12use thiserror::Error;
13use tokio::sync::mpsc;
14
15use freezeout_core::{
16    crypto::{PeerId, SigningKey},
17    message::{HandPayoff, Message, PlayerAction, PlayerUpdate, SignedMessage},
18    poker::{Card, Chips, Deck, HandValue, PlayerCards, TableId},
19};
20
21use crate::db::Db;
22
23use super::{
24    TableMessage,
25    player::{Player, PlayersState},
26};
27
28/// The hand state.
29#[derive(Debug)]
30enum HandState {
31    /// The table is waiting for players to join before starting the game.
32    WaitForPlayers,
33    /// Start the game.
34    StartGame,
35    /// Start the hand, collect blinds and deal cards.
36    StartHand,
37    /// Handle preflop betting.
38    PreflopBetting,
39    /// Handle flop betting.
40    FlopBetting,
41    /// Handle turn betting.
42    TurnBetting,
43    /// Handle river players action.
44    RiverBetting,
45    /// Showdown.
46    Showdown,
47    /// The hand has ended.
48    EndHand,
49    /// The game has ended with a winner.
50    EndGame,
51}
52
53/// A pot that contains players bets.
54#[derive(Debug, Default)]
55struct Pot {
56    players: AHashSet<PeerId>,
57    chips: Chips,
58}
59
60/// An error from table join operations.
61#[derive(Error, Debug)]
62pub enum TableJoinError {
63    /// The game has already started.
64    #[error("game has started")]
65    GameStarted,
66    /// The table is full.
67    #[error("table full")]
68    TableFull,
69    /// The player has already joined the table.
70    #[error("player already joined")]
71    AlreadyJoined,
72    /// An unknown error used by upper layers.
73    #[error("unknown error")]
74    Unknown,
75}
76
77/// Internal table state.
78#[derive(Debug)]
79pub struct State {
80    table_id: TableId,
81    seats: usize,
82    sk: Arc<SigningKey>,
83    db: Db,
84    hand_state: HandState,
85    small_blind: Chips,
86    big_blind: Chips,
87    hand_count: usize,
88    players: PlayersState,
89    deck: Deck,
90    last_bet: Chips,
91    min_raise: Chips,
92    pots: Vec<Pot>,
93    board: Vec<Card>,
94    rng: StdRng,
95    new_hand_timer: Option<Instant>,
96    new_hand_timeout: Duration,
97}
98
99impl State {
100    const ACTION_TIMEOUT: Duration = Duration::from_secs(15);
101    const START_GAME_SB: Chips = Chips::new(10_000);
102    const START_GAME_BB: Chips = Chips::new(20_000);
103
104    /// Create a new state.
105    pub fn new(table_id: TableId, seats: usize, sk: Arc<SigningKey>, db: Db) -> Self {
106        Self::with_rng(table_id, seats, sk, db, StdRng::from_os_rng())
107    }
108
109    /// Create a new state with user initialized randomness.
110    fn with_rng(
111        table_id: TableId,
112        seats: usize,
113        sk: Arc<SigningKey>,
114        db: Db,
115        mut rng: StdRng,
116    ) -> Self {
117        Self {
118            table_id,
119            seats,
120            sk,
121            db,
122            hand_state: HandState::WaitForPlayers,
123            small_blind: Self::START_GAME_SB,
124            big_blind: Self::START_GAME_BB,
125            hand_count: 0,
126            players: PlayersState::default(),
127            deck: Deck::shuffled(&mut rng),
128            last_bet: Chips::ZERO,
129            min_raise: Chips::ZERO,
130            pots: vec![Pot::default()],
131            board: Vec::default(),
132            rng,
133            new_hand_timer: None,
134            new_hand_timeout: Duration::default(),
135        }
136    }
137
138    /// Checks if a player can join this table.
139    pub fn player_can_join(&self) -> bool {
140        if !matches!(self.hand_state, HandState::WaitForPlayers) {
141            false
142        } else {
143            self.players.count() < self.seats
144        }
145    }
146
147    /// A player tries to join the table.
148    pub async fn try_join(
149        &mut self,
150        player_id: &PeerId,
151        nickname: &str,
152        join_chips: Chips,
153        table_tx: mpsc::Sender<TableMessage>,
154    ) -> Result<(), TableJoinError> {
155        if self.players.count() == self.seats {
156            return Err(TableJoinError::TableFull);
157        }
158
159        if !matches!(self.hand_state, HandState::WaitForPlayers) {
160            return Err(TableJoinError::GameStarted);
161        }
162
163        if self.players.iter().any(|p| &p.player_id == player_id) {
164            return Err(TableJoinError::AlreadyJoined);
165        }
166
167        // Add new player to the table.
168        let join_player = Player::new(
169            player_id.clone(),
170            nickname.to_string(),
171            join_chips,
172            table_tx,
173        );
174
175        // Send a table joined confirmation to the player who joined.
176        let msg = Message::TableJoined {
177            table_id: self.table_id,
178            chips: join_player.chips,
179            seats: self.seats as u8,
180        };
181        let smsg = SignedMessage::new(&self.sk, msg);
182        let _ = join_player.table_tx.send(TableMessage::Send(smsg)).await;
183
184        // Send joined message for each player at the table to the new player.
185        for player in self.players.iter() {
186            let msg = Message::PlayerJoined {
187                player_id: player.player_id.clone(),
188                nickname: player.nickname.clone(),
189                chips: player.chips,
190            };
191            let smsg = SignedMessage::new(&self.sk, msg);
192            let _ = join_player.table_tx.send(TableMessage::Send(smsg)).await;
193        }
194
195        // Tell all players at the table that a player joined. Note that because the
196        // player has not beed added to the table yet it won't get the broadcast.
197        let msg = Message::PlayerJoined {
198            player_id: player_id.clone(),
199            nickname: nickname.to_string(),
200            chips: join_player.chips,
201        };
202        self.broadcast_message(msg).await;
203
204        // Add new player to the table.
205        self.players.join(join_player);
206
207        info!("Player {player_id} joined table {}", self.table_id);
208
209        // If all seats are full start the game.
210        if self.players.count() == self.seats {
211            self.enter_start_game().await;
212        }
213
214        Ok(())
215    }
216
217    /// A player leaves the table.
218    pub async fn leave(&mut self, player_id: &PeerId) {
219        let active_is_leaving = self.players.is_active(player_id);
220        if let Some(player) = self.players.leave(player_id) {
221            // Store the player bets into the pot.
222            if let Some(pot) = self.pots.last_mut() {
223                pot.chips += player.bet;
224            }
225
226            // Tell the other players this player has left.
227            let msg = Message::PlayerLeft(player_id.clone());
228            self.broadcast_message(msg).await;
229
230            // Notify the handler this player has left the table.
231            player.send_player_left().await;
232
233            if self.players.count_active() < 2 {
234                self.enter_end_hand().await;
235                return;
236            }
237
238            if active_is_leaving {
239                self.request_action().await;
240            }
241        }
242    }
243
244    /// Handle a message from a player.
245    pub async fn message(&mut self, msg: SignedMessage) {
246        if let Message::ActionResponse { action, amount } = msg.message() {
247            if let Some(player) = self.players.active_player() {
248                // Only process responses coming from active player.
249                if player.player_id == msg.sender() {
250                    player.action = *action;
251                    player.action_timer = None;
252
253                    match action {
254                        PlayerAction::Fold => {
255                            player.fold();
256                        }
257                        PlayerAction::Call => {
258                            player.bet(*action, self.last_bet);
259                        }
260                        PlayerAction::Check => {}
261                        PlayerAction::Bet | PlayerAction::Raise => {
262                            let amount = *amount.min(&(player.bet + player.chips));
263                            self.min_raise = (amount - self.last_bet).max(self.min_raise);
264                            self.last_bet = amount.max(self.last_bet);
265                            player.bet(*action, amount);
266                        }
267                        _ => {}
268                    }
269
270                    self.action_update().await;
271                }
272            }
273        }
274    }
275
276    pub async fn tick(&mut self) {
277        // Check if there is any player with an active timer.
278        if self.players.iter().any(|p| p.action_timer.is_some()) {
279            let player = self
280                .players
281                .iter_mut()
282                .find(|p| p.action_timer.is_some())
283                .unwrap();
284
285            // If timer has expired fold otherwise broadcast timer update.
286            if player.action_timer.unwrap().elapsed() > Self::ACTION_TIMEOUT {
287                player.fold();
288                self.action_update().await;
289            } else {
290                self.broadcast_game_update().await;
291            }
292        }
293
294        // Check if it is time to start a new hand.
295        if let Some(timer) = &self.new_hand_timer {
296            if timer.elapsed() > self.new_hand_timeout {
297                self.new_hand_timer = None;
298                self.enter_start_hand().await;
299            }
300        }
301    }
302
303    async fn action_update(&mut self) {
304        self.players.activate_next_player();
305        self.broadcast_game_update().await;
306
307        if self.is_round_complete() {
308            self.next_round().await;
309        } else {
310            self.request_action().await;
311        }
312    }
313
314    async fn enter_start_game(&mut self) {
315        self.hand_state = HandState::StartGame;
316
317        // Shuffle seats before starting the game.
318        self.players.shuffle_seats(&mut self.rng);
319
320        // Tell players to update their seats order.
321        let seats = self.players.iter().map(|p| p.player_id.clone()).collect();
322        self.broadcast_message(Message::StartGame(seats)).await;
323
324        self.enter_start_hand().await;
325    }
326
327    /// Start a new hand.
328    async fn enter_start_hand(&mut self) {
329        self.hand_state = HandState::StartHand;
330
331        self.players.start_hand();
332
333        // If there are fewer than 2 active players end the game.
334        if self.players.count_active() < 2 {
335            self.enter_end_game().await;
336            return;
337        }
338
339        self.update_blinds();
340
341        // Pay small and big blind.
342        if let Some(player) = self.players.active_player() {
343            player.bet(PlayerAction::SmallBlind, self.small_blind);
344        };
345
346        self.players.activate_next_player();
347
348        if let Some(player) = self.players.active_player() {
349            player.bet(PlayerAction::BigBlind, self.big_blind);
350        };
351
352        self.last_bet = self.big_blind;
353        self.min_raise = self.big_blind;
354
355        // Create a new deck.
356        self.deck = Deck::shuffled(&mut self.rng);
357
358        // Clear board.
359        self.board.clear();
360
361        // Reset pots.
362        self.pots = vec![Pot::default()];
363
364        // Tell clients to prepare for a new hand.
365        self.broadcast_message(Message::StartHand).await;
366
367        // Deal cards to each player.
368        for player in self.players.iter_mut() {
369            if player.is_active {
370                player.public_cards = PlayerCards::Covered;
371
372                // Sort cards for the UI.
373                let (c1, c2) = (self.deck.deal(), self.deck.deal());
374                player.hole_cards = if c1.rank() < c2.rank() {
375                    PlayerCards::Cards(c1, c2)
376                } else {
377                    PlayerCards::Cards(c2, c1)
378                };
379            } else {
380                player.public_cards = PlayerCards::None;
381                player.hole_cards = PlayerCards::None;
382            }
383        }
384
385        // Tell clients to update all players state.
386        self.broadcast_game_update().await;
387
388        // Deal the cards to each player.
389        for player in self.players.iter() {
390            if let PlayerCards::Cards(c1, c2) = player.hole_cards {
391                let msg = Message::DealCards(c1, c2);
392                let smsg = SignedMessage::new(&self.sk, msg);
393                player.send_message(smsg).await;
394            }
395        }
396
397        self.enter_preflop_betting().await;
398    }
399
400    async fn enter_preflop_betting(&mut self) {
401        self.hand_state = HandState::PreflopBetting;
402        self.action_update().await;
403    }
404
405    async fn enter_deal_flop(&mut self) {
406        for _ in 1..=3 {
407            self.board.push(self.deck.deal());
408        }
409
410        self.hand_state = HandState::FlopBetting;
411        self.start_round().await;
412    }
413
414    async fn enter_deal_turn(&mut self) {
415        self.board.push(self.deck.deal());
416
417        self.hand_state = HandState::TurnBetting;
418        self.start_round().await;
419    }
420
421    async fn enter_deal_river(&mut self) {
422        self.board.push(self.deck.deal());
423
424        self.hand_state = HandState::RiverBetting;
425        self.start_round().await;
426    }
427
428    async fn enter_showdown(&mut self) {
429        self.hand_state = HandState::Showdown;
430
431        for player in self.players.iter_mut() {
432            player.action = PlayerAction::None;
433            if player.is_active {
434                player.public_cards = player.hole_cards;
435            }
436        }
437
438        self.enter_end_hand().await;
439    }
440
441    async fn enter_end_hand(&mut self) {
442        self.new_hand_timeout = if matches!(self.hand_state, HandState::Showdown) {
443            // If coming from a showdown give players more time to see the winning
444            // hand and chips.
445            Duration::from_millis(7_000)
446        } else {
447            Duration::from_millis(3_000)
448        };
449
450        self.hand_state = HandState::EndHand;
451
452        self.update_pots();
453        self.broadcast_game_update().await;
454
455        // Give time to the UI to look at the updated pot and board.
456        self.broadcast_throttle(Duration::from_millis(1_000)).await;
457
458        let winners = self.pay_bets();
459
460        // Update players and broadcast update to all players.
461        self.players.end_hand();
462        self.broadcast_message(Message::EndHand {
463            payoffs: winners,
464            board: self.board.clone(),
465            cards: self
466                .players
467                .iter()
468                .map(|p| (p.player_id.clone(), p.public_cards))
469                .collect(),
470        })
471        .await;
472
473        // End game if only player has chips or move to next hand.
474        if self.players.count_with_chips() < 2 {
475            self.enter_end_game().await;
476        } else {
477            // All players that run out of chips must leave the table before the
478            // start of a new hand.
479            for player in self.players.iter() {
480                if player.chips == Chips::ZERO {
481                    // Notify the client that this player has left the table.
482                    let _ = player.table_tx.send(TableMessage::PlayerLeft).await;
483
484                    let msg = Message::PlayerLeft(player.player_id.clone());
485                    self.broadcast_message(msg).await;
486                }
487            }
488
489            self.players.remove_with_no_chips();
490            self.new_hand_timer = Some(Instant::now());
491        }
492    }
493
494    async fn enter_end_game(&mut self) {
495        // Give time to the UI to look at winning results before ending the game.
496        self.broadcast_throttle(Duration::from_millis(4500)).await;
497
498        self.hand_state = HandState::EndGame;
499
500        for player in self.players.iter() {
501            // Pay the winning player.
502            let res = self
503                .db
504                .pay_to_player(player.player_id.clone(), player.chips)
505                .await;
506            if let Err(e) = res {
507                error!("Db players update failed {e}");
508            }
509
510            // Notify the client that this player has left the table.
511            let _ = player.table_tx.send(TableMessage::PlayerLeft).await;
512        }
513
514        self.players.clear();
515
516        // Reset hand count for next game.
517        self.hand_count = 0;
518
519        // Wait for players to join.
520        self.hand_state = HandState::WaitForPlayers;
521    }
522
523    fn update_blinds(&mut self) {
524        let multiplier = (1 << (self.hand_count / 4).min(4)) as u32;
525        if multiplier < 16 {
526            self.small_blind = Self::START_GAME_SB * multiplier;
527            self.big_blind = Self::START_GAME_BB * multiplier;
528        } else {
529            // Cap at 12 times initial blinds.
530            self.small_blind = Self::START_GAME_SB * 12;
531            self.big_blind = Self::START_GAME_BB * 12;
532        }
533
534        self.hand_count += 1;
535    }
536
537    fn pay_bets(&mut self) -> Vec<HandPayoff> {
538        let mut payoffs = Vec::<HandPayoff>::new();
539
540        match self.players.count_active() {
541            1 => {
542                // If one player left gets all the chips.
543                if let Some(player) = self.players.active_player() {
544                    for pot in self.pots.drain(..) {
545                        player.chips += pot.chips;
546
547                        if let Some(payoff) = payoffs
548                            .iter_mut()
549                            .find(|po| po.player_id == player.player_id)
550                        {
551                            payoff.chips += pot.chips;
552                        } else {
553                            payoffs.push(HandPayoff {
554                                player_id: player.player_id.clone(),
555                                chips: pot.chips,
556                                cards: Vec::default(),
557                                rank: String::default(),
558                            });
559                        }
560                    }
561                }
562            }
563            n if n > 1 => {
564                // With more than 1 active player we need to compare hands for each pot
565                for pot in self.pots.drain(..) {
566                    // Evaluate all active players hands.
567                    let mut hands = self
568                        .players
569                        .iter_mut()
570                        .filter(|p| p.is_active && pot.players.contains(&p.player_id))
571                        .filter_map(|p| match p.hole_cards {
572                            PlayerCards::None | PlayerCards::Covered => None,
573                            PlayerCards::Cards(c1, c2) => Some((p, c1, c2)),
574                        })
575                        .map(|(p, c1, c2)| {
576                            let mut cards = vec![c1, c2];
577                            cards.extend_from_slice(&self.board);
578                            let (v, bh) = HandValue::eval_with_best_hand(&cards);
579                            (p, v, bh)
580                        })
581                        .collect::<Vec<_>>();
582
583                    // This may happen when the last pot is empty.
584                    if hands.is_empty() {
585                        continue;
586                    }
587
588                    // Sort descending order, winners first.
589                    hands.sort_by(|p1, p2| p2.1.cmp(&p1.1));
590
591                    // Count hands with the same value.
592                    let winners_count = hands.iter().filter(|(_, v, _)| v == &hands[0].1).count();
593                    let win_payoff = pot.chips / winners_count as u32;
594                    let win_remainder = pot.chips % winners_count as u32;
595
596                    for (idx, (player, v, bh)) in hands.iter_mut().take(winners_count).enumerate() {
597                        // Give remaineder to first player.
598                        let player_payoff = if idx == 0 {
599                            win_payoff + win_remainder
600                        } else {
601                            win_payoff
602                        };
603
604                        player.chips += player_payoff;
605
606                        // Sort by rank for the UI.
607                        let mut cards = bh.to_vec();
608                        cards.sort_by_key(|c| c.rank());
609
610                        // If a player has already a payoff add chips to that one.
611                        if let Some(payoff) = payoffs
612                            .iter_mut()
613                            .find(|po| po.player_id == player.player_id)
614                        {
615                            payoff.chips += player_payoff;
616                        } else {
617                            payoffs.push(HandPayoff {
618                                player_id: player.player_id.clone(),
619                                chips: player_payoff,
620                                cards,
621                                rank: v.rank().to_string(),
622                            });
623                        }
624                    }
625                }
626            }
627            _ => {}
628        }
629
630        payoffs
631    }
632
633    /// Checks if all players in the hand have acted.
634    fn is_round_complete(&self) -> bool {
635        if self.players.count_active() < 2 {
636            return true;
637        }
638
639        for player in self.players.iter() {
640            // If a player didn't match the last bet and is not all-in then the
641            // player has to act and the round is not complete.
642            if player.is_active && player.bet < self.last_bet && player.chips > Chips::ZERO {
643                return false;
644            }
645        }
646
647        // Only one player has chips all others are all in.
648        if self.players.count_active_with_chips() < 2 {
649            return true;
650        }
651
652        for player in self.players.iter() {
653            if player.is_active {
654                // If a player didn't act the round is not complete.
655                match player.action {
656                    PlayerAction::None | PlayerAction::SmallBlind | PlayerAction::BigBlind
657                        if player.chips > Chips::ZERO =>
658                    {
659                        return false;
660                    }
661                    _ => {}
662                }
663            }
664        }
665
666        true
667    }
668
669    async fn next_round(&mut self) {
670        if self.players.count_active() < 2 {
671            self.enter_end_hand().await;
672            return;
673        }
674
675        while self.is_round_complete() {
676            match self.hand_state {
677                HandState::PreflopBetting => self.enter_deal_flop().await,
678                HandState::FlopBetting => self.enter_deal_turn().await,
679                HandState::TurnBetting => self.enter_deal_river().await,
680                HandState::RiverBetting => {
681                    self.enter_showdown().await;
682                    return;
683                }
684                _ => {}
685            }
686        }
687    }
688
689    async fn start_round(&mut self) {
690        self.update_pots();
691
692        // Give some time to watch last action and pots.
693        self.broadcast_throttle(Duration::from_millis(1000)).await;
694
695        for player in self.players.iter_mut() {
696            player.bet = Chips::ZERO;
697            player.action = PlayerAction::None;
698        }
699
700        self.last_bet = Chips::ZERO;
701        self.min_raise = self.big_blind;
702
703        self.players.start_round();
704
705        self.broadcast_game_update().await;
706        self.request_action().await;
707    }
708
709    fn update_pots(&mut self) {
710        // Updates pots if there is a bet.
711        if self.last_bet > Chips::ZERO {
712            // Move bets to pots.
713            loop {
714                // Find minimum bet in case a player went all in.
715                let min_bet = self
716                    .players
717                    .iter()
718                    .filter(|p| p.bet > Chips::ZERO)
719                    .map(|p| p.bet)
720                    .min()
721                    .unwrap_or_default();
722
723                if min_bet == Chips::ZERO {
724                    break;
725                }
726
727                let mut went_all_in = false;
728                for player in self.players.iter_mut() {
729                    let pot = self.pots.last_mut().unwrap();
730                    if player.bet > Chips::ZERO {
731                        player.bet -= min_bet;
732                        pot.chips += min_bet;
733
734                        if !pot.players.contains(&player.player_id) {
735                            pot.players.insert(player.player_id.clone());
736                        }
737
738                        went_all_in = player.chips == Chips::ZERO;
739                    }
740                }
741
742                if went_all_in {
743                    self.pots.push(Pot::default());
744                }
745            }
746        }
747    }
748
749    /// Broadcast a game state update to all connected players.
750    async fn broadcast_game_update(&self) {
751        let players = self
752            .players
753            .iter()
754            .map(|p| {
755                let action_timer = p.action_timer.map(|t| {
756                    Self::ACTION_TIMEOUT
757                        .saturating_sub(t.elapsed())
758                        .as_secs_f32() as u16
759                });
760
761                PlayerUpdate {
762                    player_id: p.player_id.clone(),
763                    chips: p.chips,
764                    bet: p.bet,
765                    action: p.action,
766                    action_timer,
767                    cards: p.public_cards,
768                    has_button: p.has_button,
769                    is_active: p.is_active,
770                }
771            })
772            .collect();
773
774        let pot = self
775            .pots
776            .iter()
777            .map(|p| p.chips)
778            .fold(Chips::ZERO, |acc, c| acc + c);
779
780        let msg = Message::GameUpdate {
781            players,
782            board: self.board.clone(),
783            pot,
784        };
785        let smsg = SignedMessage::new(&self.sk, msg);
786        for player in self.players.iter() {
787            player.send_message(smsg.clone()).await;
788        }
789    }
790
791    /// Request action to the active player.
792    async fn request_action(&mut self) {
793        if let Some(player) = self.players.active_player() {
794            let mut actions = vec![PlayerAction::Fold];
795
796            if player.bet == self.last_bet {
797                actions.push(PlayerAction::Check);
798            }
799
800            if player.bet < self.last_bet {
801                actions.push(PlayerAction::Call);
802            }
803
804            if self.last_bet == Chips::ZERO && player.chips > Chips::ZERO {
805                actions.push(PlayerAction::Bet);
806            }
807
808            if player.chips + player.bet > self.last_bet
809                && self.last_bet > Chips::ZERO
810                && player.chips > Chips::ZERO
811            {
812                actions.push(PlayerAction::Raise);
813            }
814
815            player.action_timer = Some(Instant::now());
816
817            let msg = Message::ActionRequest {
818                player_id: player.player_id.clone(),
819                min_raise: self.min_raise + self.last_bet,
820                big_blind: self.big_blind,
821                actions,
822            };
823
824            self.broadcast_message(msg).await;
825        }
826    }
827
828    /// Broadcast a message to all players at the table.
829    async fn broadcast_message(&self, msg: Message) {
830        let smsg = SignedMessage::new(&self.sk, msg);
831        for player in self.players.iter() {
832            player.send_message(smsg.clone()).await;
833        }
834    }
835
836    /// Broadcast a throttle message to all players at the table.
837    async fn broadcast_throttle(&self, dt: Duration) {
838        for player in self.players.iter() {
839            player.send_throttle(dt).await;
840        }
841    }
842}
843
844#[cfg(test)]
845mod tests {
846    use super::*;
847    use freezeout_core::poker::{Rank, Suit};
848
849    struct TestPlayer {
850        p: Player,
851        rx: mpsc::Receiver<TableMessage>,
852        sk: SigningKey,
853        join_chips: Chips,
854    }
855
856    impl TestPlayer {
857        fn new(join_chips: Chips) -> Self {
858            let sk = SigningKey::default();
859            let peer_id = sk.verifying_key().peer_id();
860            let (tx, rx) = mpsc::channel(64);
861            let p = Player::new(peer_id.clone(), peer_id.digits(), join_chips, tx);
862            Self {
863                p,
864                rx,
865                sk,
866                join_chips,
867            }
868        }
869
870        fn rx(&mut self) -> Option<TableMessage> {
871            self.rx.try_recv().ok()
872        }
873
874        fn msg(&self, msg: Message) -> SignedMessage {
875            SignedMessage::new(&self.sk, msg)
876        }
877
878        fn id(&self) -> &PeerId {
879            &self.p.player_id
880        }
881    }
882
883    macro_rules! assert_message {
884        ($player:expr, $pattern:pat $(, $closure:expr)?) => {
885            loop {
886                let msg = $player.rx().expect("No message found");
887                match msg {
888                    TableMessage::Send(msg) => match msg.message() {
889                        $pattern => {
890                            $($closure();)?
891                            break;
892                        }
893                        msg => panic!("Unexpected message {msg:?}"),
894                    },
895                    TableMessage::Throttle(_) => {
896                        // Ignore throttle messages while testing.
897                    }
898                    msg => panic!("Unexpected table message {msg:?}"),
899                }
900            }
901        };
902    }
903
904    struct TestTable {
905        state: State,
906        players: Vec<TestPlayer>,
907    }
908
909    impl TestTable {
910        /// Creates a `State` with seeded randomness and memory database.
911        fn new(player_chips: Vec<u32>) -> Self {
912            let rng = StdRng::seed_from_u64(101333);
913            let db = Db::open_in_memory().unwrap();
914            let sk = Arc::new(SigningKey::default());
915            let state = State::with_rng(TableId::new_id(), player_chips.len(), sk, db, rng);
916            let players = player_chips
917                .into_iter()
918                .map(|c| TestPlayer::new(Chips::new(c)))
919                .collect();
920            Self { state, players }
921        }
922
923        /// Start the game and test it.
924        async fn test_start_game(&mut self) {
925            for p in self.players.iter_mut() {
926                // A player joins a table.
927                self.state
928                    .try_join(
929                        &p.p.player_id,
930                        &p.p.nickname,
931                        p.join_chips,
932                        p.p.table_tx.clone(),
933                    )
934                    .await
935                    .expect("Player should join table");
936
937                // After joining a player should get a TableJoined message.
938                assert_message!(p, Message::TableJoined { .. });
939            }
940
941            // List of player ids from the test players.
942            let player_ids = self
943                .players
944                .iter()
945                .map(|p| p.id().clone())
946                .collect::<Vec<_>>();
947
948            // After all players joined each player should have received a player
949            // joined for each other player at the table.
950            for p in self.players.iter_mut() {
951                for id in &player_ids {
952                    // Skip itself.
953                    if p.id() != id {
954                        assert_message!(p, Message::PlayerJoined { player_id, .. }, || {
955                            assert_eq!(player_id, id);
956                        });
957                    }
958                }
959            }
960
961            // Before starting the game the seats are shuffled and a StartGame
962            // message with the new seats is sent to each player. Check that shuffled
963            // seats id are different from the test players id.
964            for p in self.players.iter_mut() {
965                assert_message!(p, Message::StartGame(seats), || {
966                    assert_ne!(seats, &player_ids);
967                });
968            }
969
970            // Sort test players after shuffling.
971            for (idx, p) in self.state.players.iter().enumerate() {
972                let pos = self
973                    .players
974                    .iter()
975                    .position(|tp| tp.p.player_id == p.player_id)
976                    .unwrap();
977                self.players.swap(idx, pos);
978            }
979        }
980
981        /// Test a start hand, this should be called after test_start_game.
982        async fn test_start_hand(&mut self) {
983            // Before a new hand starts all players get a StartHand message.
984            for p in self.players.iter_mut() {
985                assert_message!(p, Message::StartHand);
986            }
987
988            // The small blind and big blind players pay the blinds.
989            let tp = &self.players[0];
990            // Use min in case join chips < small blind.
991            let sb_bet = self.state.small_blind.min(tp.join_chips);
992
993            let tp = &self.players[1];
994            // Use min in case join chips < small blind.
995            let bb_bet = self.state.big_blind.min(tp.join_chips);
996
997            // After players paid the blinds all players should get a game update so
998            // that they can update the UI and then the hole cards are dealt.
999            for p in self.players.iter_mut() {
1000                assert_message!(p, Message::GameUpdate { players, .. }, || {
1001                    assert_eq!(players[0].bet, sb_bet);
1002                    assert!(matches!(players[0].action, PlayerAction::SmallBlind));
1003
1004                    assert_eq!(players[1].bet, bb_bet);
1005                    assert!(matches!(players[1].action, PlayerAction::BigBlind));
1006                });
1007
1008                assert_message!(p, Message::DealCards(_, _));
1009            }
1010        }
1011
1012        /// Send an action from the current active player.
1013        async fn send_action(&mut self, msg: Message) {
1014            let active_id = self
1015                .state
1016                .players
1017                .active_player()
1018                .expect("No active player")
1019                .player_id
1020                .clone();
1021
1022            // Find the sender player.
1023            for p in self.players.iter_mut() {
1024                if p.id() == &active_id {
1025                    let msg = p.msg(msg);
1026                    self.state.message(msg).await;
1027                    break;
1028                }
1029            }
1030        }
1031
1032        async fn bet(&mut self, amount: Chips) {
1033            self.send_action(Message::ActionResponse {
1034                action: PlayerAction::Bet,
1035                amount,
1036            })
1037            .await;
1038        }
1039
1040        async fn call(&mut self) {
1041            self.send_action(Message::ActionResponse {
1042                action: PlayerAction::Call,
1043                amount: Chips::ZERO,
1044            })
1045            .await;
1046        }
1047
1048        async fn check(&mut self) {
1049            self.send_action(Message::ActionResponse {
1050                action: PlayerAction::Check,
1051                amount: Chips::ZERO,
1052            })
1053            .await;
1054        }
1055
1056        async fn fold(&mut self) {
1057            self.send_action(Message::ActionResponse {
1058                action: PlayerAction::Fold,
1059                amount: Chips::ZERO,
1060            })
1061            .await;
1062        }
1063
1064        /// Drain players messages for tests where we are not interested in the
1065        /// messages players are getting.
1066        fn drain_players_message(&mut self) {
1067            for p in self.players.iter_mut() {
1068                while p.rx().is_some() {}
1069            }
1070        }
1071    }
1072
1073    #[tokio::test]
1074    async fn all_players_all_in() {
1075        const JOIN_CHIPS: u32 = 100_000;
1076
1077        let mut table = TestTable::new(vec![JOIN_CHIPS, JOIN_CHIPS, JOIN_CHIPS]);
1078        table.test_start_game().await;
1079        table.test_start_hand().await;
1080
1081        // Request action from first player.
1082        for p in table.players.iter_mut() {
1083            assert_message!(p, Message::GameUpdate { .. });
1084            assert_message!(p, Message::ActionRequest { .. });
1085        }
1086
1087        // First player to act goes all in.
1088        table.bet(Chips::new(JOIN_CHIPS)).await;
1089
1090        // All players get a game update with the player action followed by an action
1091        // request for the next player to act.
1092        for p in table.players.iter_mut() {
1093            assert_message!(p, Message::GameUpdate { players, .. }, || {
1094                assert!(matches!(players[2].action, PlayerAction::Bet));
1095            });
1096            assert_message!(p, Message::ActionRequest { .. });
1097        }
1098
1099        // Next player calls.
1100        table.call().await;
1101
1102        for p in table.players.iter_mut() {
1103            assert_message!(p, Message::GameUpdate { players, .. }, || {
1104                assert!(matches!(players[0].action, PlayerAction::Call));
1105            });
1106            assert_message!(p, Message::ActionRequest { .. });
1107        }
1108
1109        // Last player calls.
1110        table.call().await;
1111
1112        // All players went all in we should get the following messages.
1113        for p in table.players.iter_mut() {
1114            // BB player calls.
1115            assert_message!(p, Message::GameUpdate { players, .. }, || {
1116                // BB playe calls.
1117                assert!(matches!(players[1].action, PlayerAction::Call));
1118            });
1119
1120            // All players get a game update with the flop cards.
1121            assert_message!(p, Message::GameUpdate { board, pot, .. }, || {
1122                assert_eq!(board.len(), 3);
1123                assert_eq!(*pot, Chips::new(3 * JOIN_CHIPS));
1124            });
1125
1126            // All players get an update for the turn.
1127            assert_message!(p, Message::GameUpdate { board, .. }, || {
1128                assert_eq!(board.len(), 4);
1129            });
1130
1131            // And the river.
1132            assert_message!(p, Message::GameUpdate { board, .. }, || {
1133                assert_eq!(board.len(), 5);
1134            });
1135
1136            // Showdown message with all players cards.
1137            assert_message!(p, Message::GameUpdate { players, .. }, || {
1138                for p in players {
1139                    assert!(matches!(p.cards, PlayerCards::Cards(_, _)));
1140                }
1141            });
1142
1143            // All players get a EndHand message with winner.
1144            assert_message!(p, Message::EndHand { payoffs, .. }, || {
1145                // Only one payoff
1146                assert_eq!(payoffs.len(), 1);
1147
1148                // Winner wins all chips.
1149                assert_eq!(payoffs[0].chips, Chips::new(300_000));
1150            });
1151        }
1152    }
1153
1154    #[tokio::test]
1155    async fn two_players_one_all_in() {
1156        const JOIN_CHIPS: u32 = 100_000;
1157        const JOIN_CHIPS_SMALL: u32 = JOIN_CHIPS / 2;
1158
1159        let mut table = TestTable::new(vec![JOIN_CHIPS_SMALL, JOIN_CHIPS]);
1160        table.test_start_game().await;
1161        table.test_start_hand().await;
1162
1163        // Request action from first player.
1164        for p in table.players.iter_mut() {
1165            assert_message!(p, Message::GameUpdate { .. });
1166            assert_message!(p, Message::ActionRequest { .. });
1167        }
1168
1169        // First player to act goes all in, this is the player with fewer chips.
1170        table.bet(Chips::new(JOIN_CHIPS_SMALL)).await;
1171
1172        // All players get a game update with the player action followed by an action
1173        // request for the next player to act.
1174        for p in table.players.iter_mut() {
1175            assert_message!(p, Message::GameUpdate { players, .. }, || {
1176                assert!(matches!(players[0].action, PlayerAction::Bet));
1177            });
1178            assert_message!(p, Message::ActionRequest { .. });
1179        }
1180
1181        table.call().await;
1182
1183        for p in table.players.iter_mut() {
1184            assert_message!(p, Message::GameUpdate { players, .. }, || {
1185                assert!(matches!(players[1].action, PlayerAction::Call));
1186            });
1187
1188            // New round deal flop update.
1189            assert_message!(p, Message::GameUpdate { board, .. }, || {
1190                assert_eq!(board.len(), 3);
1191            });
1192
1193            // Deal turn.
1194            assert_message!(p, Message::GameUpdate { board, .. }, || {
1195                assert_eq!(board.len(), 4);
1196            });
1197
1198            // Deal river.
1199            assert_message!(p, Message::GameUpdate { board, .. }, || {
1200                assert_eq!(board.len(), 5);
1201            });
1202
1203            // Showdown message with all players cards.
1204            assert_message!(p, Message::GameUpdate { players, .. }, || {
1205                for p in players {
1206                    assert!(matches!(p.cards, PlayerCards::Cards(_, _)));
1207                }
1208            });
1209
1210            // All players get a EndHand message with winner.
1211            assert_message!(p, Message::EndHand { payoffs, .. }, || {
1212                // Only one payoff
1213                assert_eq!(payoffs.len(), 1);
1214                assert_eq!(payoffs[0].chips, Chips::new(100_000));
1215            });
1216        }
1217    }
1218
1219    #[tokio::test]
1220    async fn three_players_one_all_in() {
1221        const JOIN_CHIPS: u32 = 100_000;
1222        const JOIN_CHIPS_SMALL: u32 = JOIN_CHIPS / 2;
1223
1224        let mut table = TestTable::new(vec![JOIN_CHIPS, JOIN_CHIPS, JOIN_CHIPS_SMALL]);
1225        table.test_start_game().await;
1226        table.test_start_hand().await;
1227
1228        // Request action from first player.
1229        for p in table.players.iter_mut() {
1230            assert_message!(p, Message::GameUpdate { .. });
1231            assert_message!(p, Message::ActionRequest { .. });
1232        }
1233
1234        // First player UG goes all in, this is the player with fewer chips.
1235        table.bet(Chips::new(JOIN_CHIPS_SMALL)).await;
1236
1237        // All players get a game update with the player action followed by an action
1238        // request for the next player to act.
1239        for p in table.players.iter_mut() {
1240            assert_message!(p, Message::GameUpdate { players, .. }, || {
1241                assert!(matches!(players[2].action, PlayerAction::Bet));
1242            });
1243            assert_message!(p, Message::ActionRequest { .. });
1244        }
1245
1246        // SB calls.
1247        table.call().await;
1248
1249        for p in table.players.iter_mut() {
1250            assert_message!(p, Message::GameUpdate { players, .. }, || {
1251                assert!(matches!(players[0].action, PlayerAction::Call));
1252            });
1253            assert_message!(p, Message::ActionRequest { .. });
1254        }
1255
1256        // BB calls.
1257        table.call().await;
1258
1259        for p in table.players.iter_mut() {
1260            assert_message!(p, Message::GameUpdate { players, .. }, || {
1261                assert!(matches!(players[1].action, PlayerAction::Call));
1262            });
1263
1264            // New round deal flop update.
1265            assert_message!(p, Message::GameUpdate { board, .. }, || {
1266                assert_eq!(board.len(), 3);
1267            });
1268
1269            // Request action to SB.
1270            assert_message!(p, Message::ActionRequest { .. });
1271        }
1272
1273        // SB check
1274        table.check().await;
1275
1276        for p in table.players.iter_mut() {
1277            assert_message!(p, Message::GameUpdate { players, .. }, || {
1278                assert!(matches!(players[0].action, PlayerAction::Check));
1279            });
1280
1281            assert_message!(p, Message::ActionRequest { .. });
1282        }
1283
1284        // BB bets so we can check that the action goes back to SB as the UG players
1285        // is all in.
1286        table.bet(table.state.big_blind).await;
1287
1288        for p in table.players.iter_mut() {
1289            assert_message!(p, Message::GameUpdate { players, .. }, || {
1290                assert!(matches!(players[1].action, PlayerAction::Bet));
1291            });
1292
1293            // Check action goes back to SB
1294            assert_message!(p, Message::ActionRequest { player_id, .. }, || {
1295                assert_eq!(player_id, &table.state.players.player(0).player_id);
1296            });
1297        }
1298    }
1299
1300    #[tokio::test]
1301    async fn small_blind_all_in() {
1302        // Test games where the small blind chips are lower than the small blind.
1303        let mut table = TestTable::new(vec![20_000, 100_000]);
1304        // Incremebt small blind to 40000 so that is greater than player chips.
1305
1306        loop {
1307            table.state.update_blinds();
1308            if table.state.small_blind == Chips::new(40_000) {
1309                break;
1310            }
1311        }
1312
1313        table.test_start_game().await;
1314        table.test_start_hand().await;
1315
1316        // The small blind player is all in we should go all the way to showdown.
1317        for p in table.players.iter_mut() {
1318            // Preflop game update.
1319            assert_message!(p, Message::GameUpdate { .. });
1320
1321            // New round deal flop update.
1322            assert_message!(p, Message::GameUpdate { board, .. }, || {
1323                assert_eq!(board.len(), 3);
1324            });
1325
1326            // New round deal turn update.
1327            assert_message!(p, Message::GameUpdate { board, .. }, || {
1328                assert_eq!(board.len(), 4);
1329            });
1330
1331            // New round deal river update.
1332            assert_message!(p, Message::GameUpdate { board, .. }, || {
1333                assert_eq!(board.len(), 5);
1334            });
1335
1336            // Pot update.
1337            assert_message!(p, Message::GameUpdate { pot, .. }, || {
1338                // Pot if the big blind plus the small blind chips that were half the
1339                // small blind.
1340                assert_eq!(*pot, table.state.big_blind + Chips::new(20_000));
1341            });
1342
1343            // End hand.
1344            assert_message!(p, Message::EndHand { payoffs, .. }, || {
1345                assert_eq!(payoffs.len(), 1);
1346
1347                // All chips go back to the BB winner.
1348                let payoff = &payoffs[0];
1349                assert_eq!(payoff.chips, table.state.big_blind + Chips::new(20_000));
1350            });
1351        }
1352    }
1353
1354    #[tokio::test]
1355    async fn all_players_fold() {
1356        let mut table = TestTable::new(vec![100_000, 100_000, 100_000]);
1357        table.test_start_game().await;
1358        table.test_start_hand().await;
1359
1360        for p in table.players.iter_mut() {
1361            assert_message!(p, Message::GameUpdate { .. });
1362            assert_message!(p, Message::ActionRequest { .. });
1363        }
1364
1365        let bb_player_id = table.state.players.player(1).player_id.clone();
1366
1367        // First player folds.
1368        table.fold().await;
1369
1370        // Game update with the last player action and request for next player.
1371        for p in table.players.iter_mut() {
1372            assert_message!(p, Message::GameUpdate { players, .. }, || {
1373                assert!(matches!(players[2].action, PlayerAction::Fold));
1374            });
1375            assert_message!(p, Message::ActionRequest { .. });
1376        }
1377
1378        // Next player folds.
1379        table.fold().await;
1380
1381        for p in table.players.iter_mut() {
1382            // Players get a game update where the small blind and the UTG folded.
1383            assert_message!(p, Message::GameUpdate { players, .. }, || {
1384                assert!(matches!(players[0].action, PlayerAction::Fold));
1385                assert!(matches!(players[2].action, PlayerAction::Fold));
1386            });
1387
1388            // Players get an update with pot.
1389            assert_message!(p, Message::GameUpdate { pot, .. }, || {
1390                assert_eq!(*pot, table.state.big_blind + table.state.small_blind);
1391            });
1392
1393            // Players get a EndHand message with the BB as winner.
1394            assert_message!(p, Message::EndHand { payoffs, .. }, || {
1395                let payoff = &payoffs[0];
1396                assert_eq!(payoff.player_id, bb_player_id);
1397
1398                // Winner wins blinds.
1399                assert_eq!(
1400                    payoff.chips,
1401                    table.state.big_blind + table.state.small_blind
1402                );
1403            });
1404        }
1405    }
1406
1407    #[tokio::test]
1408    async fn multi_pots() {
1409        let mut table = TestTable::new(vec![500_000, 300_000, 100_000]);
1410        table.test_start_game().await;
1411        table.test_start_hand().await;
1412
1413        for p in table.players.iter_mut() {
1414            assert_message!(p, Message::GameUpdate { .. });
1415            assert_message!(p, Message::ActionRequest { .. });
1416        }
1417
1418        // First player to act goes all in.
1419        let player = table.state.players.active_player().unwrap();
1420        let amount = player.chips + player.bet;
1421        table.bet(amount).await;
1422
1423        // All players get a game update with the player action followed by an action
1424        // request for the next player to act.
1425        for p in table.players.iter_mut() {
1426            assert_message!(p, Message::GameUpdate { .. });
1427            assert_message!(p, Message::ActionRequest { .. });
1428        }
1429
1430        // Next player calls and goes all in.
1431        let player = table.state.players.active_player().unwrap();
1432        let amount = player.chips + player.bet;
1433        table.bet(amount).await;
1434
1435        for p in table.players.iter_mut() {
1436            assert_message!(p, Message::GameUpdate { .. });
1437            assert_message!(p, Message::ActionRequest { .. });
1438        }
1439
1440        // Last player (BB) calls and goes all in.
1441        let player = table.state.players.active_player().unwrap();
1442        let amount = player.chips + player.bet;
1443        table.bet(amount).await;
1444
1445        // All players went all in we should get the following messages.
1446        for p in table.players.iter_mut() {
1447            assert_message!(p, Message::GameUpdate { .. });
1448
1449            // All players get a game update with the flop cards.
1450            assert_message!(p, Message::GameUpdate { .. });
1451
1452            // All players get an update for the turn.
1453            assert_message!(p, Message::GameUpdate { .. });
1454
1455            // And the river.
1456            assert_message!(p, Message::GameUpdate { .. });
1457
1458            // Showdown message with all players cards.
1459            assert_message!(p, Message::GameUpdate { .. });
1460
1461            // All players get a EndHand message with winner.
1462            assert_message!(p, Message::EndHand { payoffs, .. }, || {
1463                // We should have 3 payoffs, one player went all in with 100_000
1464                // another went all in for 300_000 and another for 500_000.
1465
1466                // The one that went all in for 100_000 won the first pot for a total
1467                // of 300_000 so the other remaining players have 200_000 and 400_000
1468                // left. Of these remaining players the 200_000 won for a total of
1469                // 400_000 leaving the remaining player with 200_000 that will get
1470                // refundend.
1471                assert_eq!(payoffs.len(), 3);
1472                assert_eq!(payoffs[0].chips, Chips::new(300_000));
1473                assert_eq!(payoffs[1].chips, Chips::new(400_000));
1474                assert_eq!(payoffs[2].chips, Chips::new(200_000));
1475            });
1476        }
1477    }
1478
1479    #[tokio::test]
1480    async fn split_win() {
1481        const JOIN_CHIPS: u32 = 100_000;
1482
1483        let mut table = TestTable::new(vec![JOIN_CHIPS, JOIN_CHIPS, JOIN_CHIPS]);
1484        table.test_start_game().await;
1485        table.test_start_hand().await;
1486
1487        // Set player cards so that we get a split win (another player has 7D 9H).
1488        let p = table.state.players.iter_mut().next().unwrap();
1489        p.hole_cards = PlayerCards::Cards(
1490            Card::new(Rank::Seven, Suit::Spades),
1491            Card::new(Rank::Nine, Suit::Diamonds),
1492        );
1493
1494        // Preflop.
1495        table.bet(Chips::new(50_000)).await;
1496        table.call().await;
1497        table.call().await;
1498        table.drain_players_message();
1499
1500        // Flop
1501        table.check().await;
1502        table.check().await;
1503        table.check().await;
1504        table.drain_players_message();
1505
1506        // Turn
1507        table.check().await;
1508        table.check().await;
1509        table.check().await;
1510        table.drain_players_message();
1511
1512        // River
1513        table.check().await;
1514        table.check().await;
1515        table.drain_players_message();
1516
1517        table.check().await;
1518
1519        for p in table.players.iter_mut() {
1520            // Update following last player check.
1521            assert_message!(p, Message::GameUpdate { .. });
1522
1523            // Game update with showdown.
1524            assert_message!(p, Message::GameUpdate { .. });
1525
1526            assert_message!(p, Message::EndHand { payoffs, .. }, || {
1527                // We should have 2 payoffs, with equal amount as two players have
1528                // the same cards value (7S, 9D) and (7D, 9H)
1529                assert_eq!(payoffs.len(), 2);
1530                assert_eq!(payoffs[0].chips, Chips::new(75_000));
1531                assert_eq!(payoffs[1].chips, Chips::new(75_000));
1532            });
1533        }
1534    }
1535
1536    #[tokio::test]
1537    async fn blinds_increment() {
1538        let mut table = TestTable::new(vec![100_000, 100_000]);
1539
1540        // First 4 hands blinds have initial value.
1541        (0..4).for_each(|_| table.state.update_blinds());
1542        assert_eq!(table.state.small_blind, State::START_GAME_SB);
1543        assert_eq!(table.state.big_blind, State::START_GAME_BB);
1544
1545        // Next for hands blinds double.
1546        (0..4).for_each(|_| table.state.update_blinds());
1547        assert_eq!(table.state.small_blind, State::START_GAME_SB * 2);
1548        assert_eq!(table.state.big_blind, State::START_GAME_BB * 2);
1549
1550        // Next 4 hands blinds double again.
1551        (0..4).for_each(|_| table.state.update_blinds());
1552        assert_eq!(table.state.small_blind, State::START_GAME_SB * 4);
1553        assert_eq!(table.state.big_blind, State::START_GAME_BB * 4);
1554
1555        // Next 4 hands blinds double again.
1556        (0..4).for_each(|_| table.state.update_blinds());
1557        assert_eq!(table.state.small_blind, State::START_GAME_SB * 8);
1558        assert_eq!(table.state.big_blind, State::START_GAME_BB * 8);
1559
1560        // After that we keep them at the same level
1561        (0..8).for_each(|_| table.state.update_blinds());
1562        assert_eq!(table.state.small_blind, State::START_GAME_SB * 12);
1563        assert_eq!(table.state.big_blind, State::START_GAME_BB * 12);
1564
1565        // Test for overflow bug.
1566        (0..128).for_each(|_| table.state.update_blinds());
1567        assert_eq!(table.state.small_blind, State::START_GAME_SB * 12);
1568        assert_eq!(table.state.big_blind, State::START_GAME_BB * 12);
1569    }
1570}