Skip to content

Commit 05adefb

Browse files
author
DogLooksGood
committed
Refactor holdem game
1 parent 11958e3 commit 05adefb

File tree

8 files changed

+200
-273
lines changed

8 files changed

+200
-273
lines changed

base/src/game.rs

Lines changed: 26 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
//! Game state machine (or handler) of Holdem: the core of this lib.
1+
//! Game state machine of poker game (Hold'em or Omaha).
2+
23
use crate::account::HoldemAccount;
4+
use crate::variant::{ EvaluateHandsOutput, GameVariant };
35
use crate::errors;
46
use crate::essential::{
57
ActingPlayer, AwardPot, Display, GameEvent, GameMode, HoldemStage, InternalPlayerJoin, Player,
68
PlayerResult, PlayerStatus, Pot, Street, ACTION_TIMEOUT_AFK, ACTION_TIMEOUT_POSTFLOP,
79
ACTION_TIMEOUT_PREFLOP, ACTION_TIMEOUT_RIVER, ACTION_TIMEOUT_TURN, MAX_ACTION_TIMEOUT_COUNT,
810
TIME_CARD_EXTRA_SECS, WAIT_TIMEOUT_DEFAULT,
911
};
10-
use crate::evaluator::{compare_hands, create_cards, evaluate_cards, PlayerHand};
11-
use crate::hand_history::{BlindBet, BlindType, HandHistory, PlayerAction, Showdown};
12+
use crate::hand_history::{BlindBet, BlindType, HandHistory, PlayerAction};
1213
use race_api::prelude::*;
1314
use std::collections::BTreeMap;
1415
use std::mem::take;
1516

1617
// Holdem: the game state
1718
#[derive(BorshSerialize, BorshDeserialize, Default, Debug, PartialEq, Clone)]
18-
pub struct Holdem {
19+
pub struct PokerGame<V: GameVariant> {
1920
pub hand_id: usize,
2021
pub deck_random_id: usize,
2122
pub max_deposit: u64,
@@ -45,10 +46,11 @@ pub struct Holdem {
4546
pub hand_history: HandHistory,
4647
pub next_game_start: u64,
4748
pub rake_collected: u64,
49+
pub variant: V,
4850
}
4951

5052
// Methods that mutate or query the game state
51-
impl Holdem {
53+
impl<V: GameVariant> PokerGame<V> {
5254
// calc timeout that should be wait after settle by state
5355
fn calc_pre_settle_timeout(&self) -> Result<u64, HandleError> {
5456
// 0.5s for collect chips, 5s for players observer game result
@@ -958,80 +960,15 @@ impl Holdem {
958960
}
959961

960962
pub fn update_game_result(&mut self, effect: &mut Effect) -> Result<(), HandleError> {
961-
let decryption = effect.get_revealed(self.deck_random_id)?;
962-
// Board
963-
let board: Vec<&str> = self.board.iter().map(|c| c.as_str()).collect();
964-
// Player hands
965-
let mut player_hands: Vec<(u64, PlayerHand)> = Vec::with_capacity(self.player_order.len());
966-
967-
let mut showdowns = Vec::<(u64, Showdown)>::new();
963+
let revealed = effect.get_revealed(self.deck_random_id)?;
968964

969-
for (id, idxs) in self.hand_index_map.iter() {
970-
if idxs.len() != 2 {
971-
return Err(errors::invalid_hole_cards_number());
972-
}
965+
let EvaluateHandsOutput { winner_sets, showdown_map } = self.variant.evaluate_hands(
966+
&self.board,
967+
&self.hand_index_map,
968+
revealed
969+
)?;
973970

974-
let Some(player) = self.player_map.get(id) else {
975-
return Err(errors::internal_player_not_found());
976-
};
977-
978-
if player.status != PlayerStatus::Fold
979-
&& player.status != PlayerStatus::Init
980-
&& player.status != PlayerStatus::Waitbb
981-
&& player.status != PlayerStatus::Leave
982-
{
983-
let Some(first_card_idx) = idxs.first() else {
984-
return Err(errors::first_hole_card_index_missing());
985-
};
986-
let Some(first_card) = decryption.get(first_card_idx) else {
987-
return Err(errors::first_hole_card_error());
988-
};
989-
let Some(second_card_idx) = idxs.last() else {
990-
return Err(errors::second_hole_card_index_missing());
991-
};
992-
let Some(second_card) = decryption.get(second_card_idx) else {
993-
return Err(errors::second_hole_card_error());
994-
};
995-
let hole_cards = [first_card.as_str(), second_card.as_str()];
996-
let cards = create_cards(board.as_slice(), &hole_cards);
997-
let hand = evaluate_cards(cards);
998-
let hole_cards = hole_cards.iter().map(|c| c.to_string()).collect();
999-
let category = hand.category.clone();
1000-
let picks = hand.picks.iter().map(|c| c.to_string()).collect();
1001-
player_hands.push((*id, hand));
1002-
showdowns.push((
1003-
*id,
1004-
Showdown {
1005-
hole_cards,
1006-
category,
1007-
picks,
1008-
},
1009-
));
1010-
}
1011-
}
1012-
1013-
player_hands.sort_by(|(_, h1), (_, h2)| compare_hands(&h2.value, &h1.value));
1014-
1015-
println!("Player Hands from strong to weak {:?}", player_hands);
1016-
1017-
// Winners example: [[w1], [w2, w3], ... ] where w2 == w3, i.e. a draw/tie
1018-
let mut winners: Vec<Vec<u64>> = Vec::new();
1019-
let mut current_value = None;
1020-
1021-
for (id, hand) in player_hands.into_iter() {
1022-
if Some(&hand.value) != current_value.as_ref() {
1023-
current_value = Some(hand.value.clone());
1024-
winners.push(vec![]);
1025-
}
1026-
winners
1027-
.last_mut()
1028-
.ok_or(errors::strongest_hand_not_found())?
1029-
.push(id);
1030-
}
1031-
1032-
println!("Player rankings in order: {:?}", winners);
1033-
1034-
let award_pots = self.assign_winners(winners)?;
971+
let award_pots = self.assign_winners(winner_sets)?;
1035972
self.calc_prize()?;
1036973

1037974
let player_result_map = self.update_player_chips()?;
@@ -1041,7 +978,7 @@ impl Holdem {
1041978
self.mark_eliminated_players();
1042979

1043980
// Save to hand history
1044-
for (id, showdown) in showdowns.into_iter() {
981+
for (id, showdown) in showdown_map.into_iter() {
1045982
self.hand_history.add_showdown(id, showdown);
1046983
}
1047984

@@ -1346,9 +1283,16 @@ impl Holdem {
13461283
}
13471284

13481285
let betted = self.get_player_bet(sender);
1349-
if amount + betted < self.street_bet + self.min_raise && amount != player.chips {
1350-
return Err(errors::raise_amount_is_too_small());
1351-
}
1286+
1287+
self.variant.validate_raise_amount(
1288+
player.chips,
1289+
betted,
1290+
amount,
1291+
self.street_bet,
1292+
self.min_raise,
1293+
&self.pots,
1294+
)?;
1295+
13521296
let (allin, real_raise_amount) = self.take_bet(sender.clone(), amount)?;
13531297
self.set_player_acted(sender, allin)?;
13541298
let new_street_bet = betted + real_raise_amount;
@@ -1632,7 +1576,7 @@ impl Holdem {
16321576
}
16331577
}
16341578

1635-
impl GameHandler for Holdem {
1579+
impl<V: GameVariant> GameHandler for PokerGame<V> {
16361580
fn balances(&self) -> Vec<PlayerBalance> {
16371581
self.player_map
16381582
.values()

base/src/hand_history.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use race_api::prelude::HandleError;
44
use crate::{
55
errors,
66
essential::Street,
7-
evaluator::Category,
7+
holdem_evaluator::Category,
88
};
99
use std::collections::BTreeMap;
1010

base/src/holdem.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Holdem implementation for GameVariant
2+
3+
use crate::hand_history::Showdown;
4+
use crate::variant::{EvaluateHandsOutput, GameVariant};
5+
use crate::evaluator::evaluate_cards;
6+
use race_api::prelude::*;
7+
8+
#[derive(BorshSerialize, BorshDeserialize, Default, Debug, PartialEq, Clone)]
9+
pub struct HoldemVariant {
10+
11+
}
12+
13+
impl GameVariant for HoldemVariant {
14+
fn hole_card_count(&self) -> usize {
15+
2
16+
}
17+
18+
fn evaluate_hands(
19+
&self,
20+
board: &[&str],
21+
hole_cards: &[&str],
22+
) -> HandleResult<EvaluateHandsOutput> {
23+
// A temporary struct to hold all evaluation results before sorting
24+
struct EvalResult {
25+
player_id: u64,
26+
hand: PlayerHand,
27+
showdown: Showdown,
28+
}
29+
30+
let mut results: Vec<EvalResult> = Vec::with_capacity(hand_index_map.len());
31+
let mut showdown_map = BTreeMap::new();
32+
33+
// Step 1: Evaluate each player's hand and build the Showdown struct.
34+
for (&player_id, indices) in hand_index_map.iter() {
35+
let card1 = revealed.get(&indices[0]).ok_or_else(errors::first_hole_card_error)?;
36+
let card2 = revealed.get(&indices[1]).ok_or_else(errors::second_hole_card_error)?;
37+
let hole_cards = vec![card1.clone(), card2.clone()];
38+
39+
let all_cards = create_cards(board, &[card1.as_str(), card2.as_str()]);
40+
41+
// The evaluator now returns a richer PlayerHand struct
42+
let hand = evaluate_cards(all_cards);
43+
44+
let showdown = Showdown {
45+
hole_cards,
46+
category: hand.category.clone(),
47+
picks: hand.picks.clone(),
48+
};
49+
results.push(EvalResult { player_id, hand, showdown });
50+
}
51+
52+
// Step 2: Sort players from best hand to worst.
53+
results.sort_by(|a, b| compare_hands(&b.hand.value, &a.hand.value));
54+
55+
// Step 3: Group players into ranked sets and build the final data structures.
56+
let mut ranked_winners: Vec<Vec<u64>> = Vec::new();
57+
let mut current_value = None;
58+
59+
for result in results.into_iter() {
60+
if Some(&result.hand.value) != current_value.as_ref() {
61+
current_value = Some(result.hand.value.clone());
62+
ranked_winners.push(Vec::new());
63+
}
64+
65+
if let Some(last_tier) = ranked_winners.last_mut() {
66+
last_tier.push(result.player_id);
67+
}
68+
69+
showdown_map.insert(result.player_id, result.showdown);
70+
}
71+
72+
Ok(EvaluateHandsOutput {
73+
winner_sets: ranked_winners,
74+
showdown_map,
75+
})
76+
}
77+
78+
fn validate_raise_amount(
79+
&self,
80+
player_chips: u64,
81+
betted: u64,
82+
raise_amount: u64,
83+
street_bet: u64,
84+
min_raise: u64,
85+
pots: &[Pot],
86+
) -> HandleResult<()> {
87+
let total_new_bet = player_bet + raise_amount;
88+
89+
// An all-in is always a valid raise amount, even if it's less than a min-raise.
90+
if raise_amount == player_chips {
91+
return Ok(());
92+
}
93+
94+
// Otherwise, the new total bet must be at least the current bet plus the minimum raise.
95+
if total_new_bet < street_bet + min_raise {
96+
return Err(errors::raise_amount_is_too_small());
97+
}
98+
99+
Ok(())
100+
}
101+
}

base/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
//! - Game: the core logic of handling various events in the game
55
//!
66
7-
pub mod evaluator;
7+
pub mod holdem_evaluator;
88
pub mod account;
99
pub mod essential;
10+
pub mod variant;
1011
pub mod game;
1112
pub mod hand_history;
1213
pub mod errors;

base/src/omaha.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Omaha implementation for GameVariant
2+
3+
use crate::variant::{EvaluateHandsOutput, GameVariant};
4+
use crate::evaluator::evaluate_cards;
5+
use race_api::prelude::*;
6+
7+
pub struct OmahaVariant {
8+
9+
}
10+
11+
impl GameVariant for HoldemVariant {
12+
fn hole_card_count(&self) -> usize {
13+
4
14+
}
15+
16+
fn evaluate_hands(
17+
&self,
18+
board: &[&str],
19+
hole_cards: &[&str],
20+
) -> HandleResult<EvaluateHandsOutput> {
21+
// Holdem doesn't distinguish hole cards and community cards
22+
23+
}
24+
25+
fn validate_raise_amount(
26+
&self,
27+
player_chips: u64,
28+
betted: u64,
29+
raise_amount: u64,
30+
street_bet: u64,
31+
min_raise: u64,
32+
pots: &[Pot],
33+
) -> HandleResult<()> {
34+
35+
}
36+
}

base/src/variant.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use race_api::prelude::*;
2+
use std::collections::{HashMap, BTreeMap};
3+
use crate::essential::Pot;
4+
use crate::hand_history::Showdown;
5+
6+
pub struct EvaluateHandsOutput {
7+
pub winner_sets: Vec<Vec<u64>>,
8+
pub showdown_map: BTreeMap<u64, Showdown>,
9+
}
10+
11+
pub trait GameVariant: Default + BorshDeserialize + BorshSerialize {
12+
13+
/// Returns the number of hole cards to deal to each player.
14+
fn hole_card_count(&self) -> usize;
15+
16+
/// Evaluates all hands at showdown and returns ranked lists of winner IDs.
17+
fn evaluate_hands(
18+
&self,
19+
board: &[String],
20+
hand_index_map: &BTreeMap<u64, Vec<usize>>,
21+
revealed_cards: &HashMap<usize, String>,
22+
) -> HandleResult<EvaluateHandsOutput>;
23+
24+
/// Validates a raise amount (to handle No-Limit vs. Pot-Limit)
25+
fn validate_raise_amount(
26+
&self,
27+
player_chips: u64,
28+
betted: u64,
29+
raise_amount: u64,
30+
street_bet: u64,
31+
min_raise: u64,
32+
pots: &[Pot],
33+
) -> HandleResult<()>;
34+
}

0 commit comments

Comments
 (0)