1use 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#[derive(Debug)]
30enum HandState {
31 WaitForPlayers,
33 StartGame,
35 StartHand,
37 PreflopBetting,
39 FlopBetting,
41 TurnBetting,
43 RiverBetting,
45 Showdown,
47 EndHand,
49 EndGame,
51}
52
53#[derive(Debug, Default)]
55struct Pot {
56 players: AHashSet<PeerId>,
57 chips: Chips,
58}
59
60#[derive(Error, Debug)]
62pub enum TableJoinError {
63 #[error("game has started")]
65 GameStarted,
66 #[error("table full")]
68 TableFull,
69 #[error("player already joined")]
71 AlreadyJoined,
72 #[error("unknown error")]
74 Unknown,
75}
76
77#[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 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 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 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 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 let join_player = Player::new(
169 player_id.clone(),
170 nickname.to_string(),
171 join_chips,
172 table_tx,
173 );
174
175 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 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 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 self.players.join(join_player);
206
207 info!("Player {player_id} joined table {}", self.table_id);
208
209 if self.players.count() == self.seats {
211 self.enter_start_game().await;
212 }
213
214 Ok(())
215 }
216
217 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 if let Some(pot) = self.pots.last_mut() {
223 pot.chips += player.bet;
224 }
225
226 let msg = Message::PlayerLeft(player_id.clone());
228 self.broadcast_message(msg).await;
229
230 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 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 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 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 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 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 self.players.shuffle_seats(&mut self.rng);
319
320 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 async fn enter_start_hand(&mut self) {
329 self.hand_state = HandState::StartHand;
330
331 self.players.start_hand();
332
333 if self.players.count_active() < 2 {
335 self.enter_end_game().await;
336 return;
337 }
338
339 self.update_blinds();
340
341 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 self.deck = Deck::shuffled(&mut self.rng);
357
358 self.board.clear();
360
361 self.pots = vec![Pot::default()];
363
364 self.broadcast_message(Message::StartHand).await;
366
367 for player in self.players.iter_mut() {
369 if player.is_active {
370 player.public_cards = PlayerCards::Covered;
371
372 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 self.broadcast_game_update().await;
387
388 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 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 self.broadcast_throttle(Duration::from_millis(1_000)).await;
457
458 let winners = self.pay_bets();
459
460 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 if self.players.count_with_chips() < 2 {
475 self.enter_end_game().await;
476 } else {
477 for player in self.players.iter() {
480 if player.chips == Chips::ZERO {
481 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 self.broadcast_throttle(Duration::from_millis(4500)).await;
497
498 self.hand_state = HandState::EndGame;
499
500 for player in self.players.iter() {
501 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 let _ = player.table_tx.send(TableMessage::PlayerLeft).await;
512 }
513
514 self.players.clear();
515
516 self.hand_count = 0;
518
519 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 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 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 for pot in self.pots.drain(..) {
566 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 if hands.is_empty() {
585 continue;
586 }
587
588 hands.sort_by(|p1, p2| p2.1.cmp(&p1.1));
590
591 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 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 let mut cards = bh.to_vec();
608 cards.sort_by_key(|c| c.rank());
609
610 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 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 player.is_active && player.bet < self.last_bet && player.chips > Chips::ZERO {
643 return false;
644 }
645 }
646
647 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 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 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 if self.last_bet > Chips::ZERO {
712 loop {
714 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 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 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 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 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 }
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 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 async fn test_start_game(&mut self) {
925 for p in self.players.iter_mut() {
926 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 assert_message!(p, Message::TableJoined { .. });
939 }
940
941 let player_ids = self
943 .players
944 .iter()
945 .map(|p| p.id().clone())
946 .collect::<Vec<_>>();
947
948 for p in self.players.iter_mut() {
951 for id in &player_ids {
952 if p.id() != id {
954 assert_message!(p, Message::PlayerJoined { player_id, .. }, || {
955 assert_eq!(player_id, id);
956 });
957 }
958 }
959 }
960
961 for p in self.players.iter_mut() {
965 assert_message!(p, Message::StartGame(seats), || {
966 assert_ne!(seats, &player_ids);
967 });
968 }
969
970 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 async fn test_start_hand(&mut self) {
983 for p in self.players.iter_mut() {
985 assert_message!(p, Message::StartHand);
986 }
987
988 let tp = &self.players[0];
990 let sb_bet = self.state.small_blind.min(tp.join_chips);
992
993 let tp = &self.players[1];
994 let bb_bet = self.state.big_blind.min(tp.join_chips);
996
997 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 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 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 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 for p in table.players.iter_mut() {
1083 assert_message!(p, Message::GameUpdate { .. });
1084 assert_message!(p, Message::ActionRequest { .. });
1085 }
1086
1087 table.bet(Chips::new(JOIN_CHIPS)).await;
1089
1090 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 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 table.call().await;
1111
1112 for p in table.players.iter_mut() {
1114 assert_message!(p, Message::GameUpdate { players, .. }, || {
1116 assert!(matches!(players[1].action, PlayerAction::Call));
1118 });
1119
1120 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 assert_message!(p, Message::GameUpdate { board, .. }, || {
1128 assert_eq!(board.len(), 4);
1129 });
1130
1131 assert_message!(p, Message::GameUpdate { board, .. }, || {
1133 assert_eq!(board.len(), 5);
1134 });
1135
1136 assert_message!(p, Message::GameUpdate { players, .. }, || {
1138 for p in players {
1139 assert!(matches!(p.cards, PlayerCards::Cards(_, _)));
1140 }
1141 });
1142
1143 assert_message!(p, Message::EndHand { payoffs, .. }, || {
1145 assert_eq!(payoffs.len(), 1);
1147
1148 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 for p in table.players.iter_mut() {
1165 assert_message!(p, Message::GameUpdate { .. });
1166 assert_message!(p, Message::ActionRequest { .. });
1167 }
1168
1169 table.bet(Chips::new(JOIN_CHIPS_SMALL)).await;
1171
1172 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 assert_message!(p, Message::GameUpdate { board, .. }, || {
1190 assert_eq!(board.len(), 3);
1191 });
1192
1193 assert_message!(p, Message::GameUpdate { board, .. }, || {
1195 assert_eq!(board.len(), 4);
1196 });
1197
1198 assert_message!(p, Message::GameUpdate { board, .. }, || {
1200 assert_eq!(board.len(), 5);
1201 });
1202
1203 assert_message!(p, Message::GameUpdate { players, .. }, || {
1205 for p in players {
1206 assert!(matches!(p.cards, PlayerCards::Cards(_, _)));
1207 }
1208 });
1209
1210 assert_message!(p, Message::EndHand { payoffs, .. }, || {
1212 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 for p in table.players.iter_mut() {
1230 assert_message!(p, Message::GameUpdate { .. });
1231 assert_message!(p, Message::ActionRequest { .. });
1232 }
1233
1234 table.bet(Chips::new(JOIN_CHIPS_SMALL)).await;
1236
1237 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 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 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 assert_message!(p, Message::GameUpdate { board, .. }, || {
1266 assert_eq!(board.len(), 3);
1267 });
1268
1269 assert_message!(p, Message::ActionRequest { .. });
1271 }
1272
1273 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 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 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 let mut table = TestTable::new(vec![20_000, 100_000]);
1304 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 for p in table.players.iter_mut() {
1318 assert_message!(p, Message::GameUpdate { .. });
1320
1321 assert_message!(p, Message::GameUpdate { board, .. }, || {
1323 assert_eq!(board.len(), 3);
1324 });
1325
1326 assert_message!(p, Message::GameUpdate { board, .. }, || {
1328 assert_eq!(board.len(), 4);
1329 });
1330
1331 assert_message!(p, Message::GameUpdate { board, .. }, || {
1333 assert_eq!(board.len(), 5);
1334 });
1335
1336 assert_message!(p, Message::GameUpdate { pot, .. }, || {
1338 assert_eq!(*pot, table.state.big_blind + Chips::new(20_000));
1341 });
1342
1343 assert_message!(p, Message::EndHand { payoffs, .. }, || {
1345 assert_eq!(payoffs.len(), 1);
1346
1347 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 table.fold().await;
1369
1370 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 table.fold().await;
1380
1381 for p in table.players.iter_mut() {
1382 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 assert_message!(p, Message::GameUpdate { pot, .. }, || {
1390 assert_eq!(*pot, table.state.big_blind + table.state.small_blind);
1391 });
1392
1393 assert_message!(p, Message::EndHand { payoffs, .. }, || {
1395 let payoff = &payoffs[0];
1396 assert_eq!(payoff.player_id, bb_player_id);
1397
1398 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 let player = table.state.players.active_player().unwrap();
1420 let amount = player.chips + player.bet;
1421 table.bet(amount).await;
1422
1423 for p in table.players.iter_mut() {
1426 assert_message!(p, Message::GameUpdate { .. });
1427 assert_message!(p, Message::ActionRequest { .. });
1428 }
1429
1430 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 let player = table.state.players.active_player().unwrap();
1442 let amount = player.chips + player.bet;
1443 table.bet(amount).await;
1444
1445 for p in table.players.iter_mut() {
1447 assert_message!(p, Message::GameUpdate { .. });
1448
1449 assert_message!(p, Message::GameUpdate { .. });
1451
1452 assert_message!(p, Message::GameUpdate { .. });
1454
1455 assert_message!(p, Message::GameUpdate { .. });
1457
1458 assert_message!(p, Message::GameUpdate { .. });
1460
1461 assert_message!(p, Message::EndHand { payoffs, .. }, || {
1463 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 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 table.bet(Chips::new(50_000)).await;
1496 table.call().await;
1497 table.call().await;
1498 table.drain_players_message();
1499
1500 table.check().await;
1502 table.check().await;
1503 table.check().await;
1504 table.drain_players_message();
1505
1506 table.check().await;
1508 table.check().await;
1509 table.check().await;
1510 table.drain_players_message();
1511
1512 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 assert_message!(p, Message::GameUpdate { .. });
1522
1523 assert_message!(p, Message::GameUpdate { .. });
1525
1526 assert_message!(p, Message::EndHand { payoffs, .. }, || {
1527 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 (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 (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 (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 (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 (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 (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}