chironaut 0.3.5

A poker game library for Texas Hold'em and other poker variants
Documentation
use std::fmt;
use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum GameVariant {
    TexasHoldem,
    OmahaHoldem4,  // Classic 4-card PLO
    OmahaHoldem5,  // 5-card PLO
    OmahaHoldem6,  // 6-card PLO
}

impl fmt::Display for GameVariant {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            GameVariant::TexasHoldem => write!(f, "Texas Hold'em"),
            GameVariant::OmahaHoldem4 => write!(f, "Pot-Limit Omaha (4-card)"),
            GameVariant::OmahaHoldem5 => write!(f, "Pot-Limit Omaha (5-card)"),
            GameVariant::OmahaHoldem6 => write!(f, "Pot-Limit Omaha (6-card)"),
        }
    }
}

/// Different ante structures used in poker
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnteStructure {
    /// No antes
    None,
    
    /// Fixed amount from each player
    Fixed(u32),
    
    /// Ante only from the big blind
    BigBlindOnly(u32),
    
    /// Ante only from the button
    ButtonOnly(u32),
    
    /// Percentage of the big blind (expressed as a value between 1-100)
    BigBlindPercentage(u8),
}

impl fmt::Display for AnteStructure {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AnteStructure::None => write!(f, "No Ante"),
            AnteStructure::Fixed(amount) => write!(f, "Fixed Ante: {}", amount),
            AnteStructure::BigBlindOnly(amount) => write!(f, "BB Ante: {}", amount),
            AnteStructure::ButtonOnly(amount) => write!(f, "Button Ante: {}", amount),
            AnteStructure::BigBlindPercentage(percent) => write!(f, "{}% of BB Ante", percent),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BettingStructure {
    NoLimit,
    PotLimit,
    FixedLimit,
}

impl fmt::Display for BettingStructure {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            BettingStructure::NoLimit => write!(f, "No-Limit"),
            BettingStructure::PotLimit => write!(f, "Pot-Limit"),
            BettingStructure::FixedLimit => write!(f, "Fixed-Limit"),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GameRules {
    pub variant: GameVariant,
    pub betting_structure: BettingStructure,
    pub small_blind: u32,
    pub big_blind: u32,
    pub ante_structure: AnteStructure,
    pub min_players: usize,
    pub max_players: usize,
    pub hole_cards: usize,
    pub is_pot_limit: bool,
    
    /// Whether straddles are allowed in the game
    pub allow_straddles: bool,
}

impl GameRules {
    /// Calculate the maximum number of players for a given game variant
    /// This is based on having enough cards in the deck:
    /// - 5 community cards
    /// - hole_cards per player
    pub fn max_players_for_variant(variant: GameVariant) -> usize {
        // Standard deck has 52 cards
        // Community cards for all Hold'em variants = 5
        const DECK_SIZE: usize = 52;
        const COMMUNITY_CARDS: usize = 5;
        
        let cards_per_player = match variant {
            GameVariant::TexasHoldem => 2,
            GameVariant::OmahaHoldem4 => 4,
            GameVariant::OmahaHoldem5 => 5,
            GameVariant::OmahaHoldem6 => 6,
        };
        
        // Math: (52 - 5) / cards_per_player = max whole number of players
        (DECK_SIZE - COMMUNITY_CARDS) / cards_per_player
    }
    
    /// Creates a standard No-Limit Texas Hold'em game
    pub fn nlhe(small_blind: u32, big_blind: u32) -> Self {
        let variant = GameVariant::TexasHoldem;
        // For standard home games, we use 10 as the maximum, although
        // theoretically Texas Hold'em can accommodate up to 23 players
        let max_players = 10.min(Self::max_players_for_variant(variant));
        
        Self {
            variant,
            betting_structure: BettingStructure::NoLimit,
            small_blind,
            big_blind,
            ante_structure: AnteStructure::None,
            min_players: 2,
            max_players,
            hole_cards: 2,
            is_pot_limit: false,
            allow_straddles: false,
        }
    }
    
    /// Creates a standard 4-card Pot-Limit Omaha game
    pub fn plo4(small_blind: u32, big_blind: u32) -> Self {
        let variant = GameVariant::OmahaHoldem4;
        // PLO4 can handle up to 11 players with a standard deck
        let max_players = 10.min(Self::max_players_for_variant(variant));
        
        Self {
            variant,
            betting_structure: BettingStructure::PotLimit,
            small_blind,
            big_blind,
            ante_structure: AnteStructure::None,
            min_players: 2,
            max_players,
            hole_cards: 4,
            is_pot_limit: true,
            allow_straddles: false,
        }
    }
    
    /// Creates a 5-card Pot-Limit Omaha game
    pub fn plo5(small_blind: u32, big_blind: u32) -> Self {
        let variant = GameVariant::OmahaHoldem5;
        // PLO5 can handle up to 9 players with a standard deck
        let max_players = Self::max_players_for_variant(variant);
        
        Self {
            variant,
            betting_structure: BettingStructure::PotLimit,
            small_blind,
            big_blind,
            ante_structure: AnteStructure::None,
            min_players: 2,
            max_players,
            hole_cards: 5,
            is_pot_limit: true,
            allow_straddles: false,
        }
    }
    
    /// Creates a 6-card Pot-Limit Omaha game
    pub fn plo6(small_blind: u32, big_blind: u32) -> Self {
        let variant = GameVariant::OmahaHoldem6;
        // PLO6 can handle up to 7 players with a standard deck
        let max_players = Self::max_players_for_variant(variant);
        
        Self {
            variant,
            betting_structure: BettingStructure::PotLimit,
            small_blind,
            big_blind,
            ante_structure: AnteStructure::None,
            min_players: 2,
            max_players,
            hole_cards: 6,
            is_pot_limit: true,
            allow_straddles: false,
        }
    }
    
    // Rename old plo to plo4 for backward compatibility
    pub fn plo(small_blind: u32, big_blind: u32) -> Self {
        Self::plo4(small_blind, big_blind)
    }
    
    /// Set a fixed ante for all players
    pub fn with_ante(mut self, ante_amount: u32) -> Self {
        self.ante_structure = AnteStructure::Fixed(ante_amount);
        self
    }
    
    /// Set a big blind ante (only BB posts)
    pub fn with_bb_ante(mut self, ante_amount: u32) -> Self {
        self.ante_structure = AnteStructure::BigBlindOnly(ante_amount);
        self
    }
    
    /// Set a button ante (only dealer posts)
    pub fn with_button_ante(mut self, ante_amount: u32) -> Self {
        self.ante_structure = AnteStructure::ButtonOnly(ante_amount);
        self
    }
    
    /// Set a percentage-based ante of the big blind
    pub fn with_percentage_ante(mut self, percent: u8) -> Self {
        // Clamp percentage to 1-100 range
        let clamped_percent = percent.clamp(1, 100);
        self.ante_structure = AnteStructure::BigBlindPercentage(clamped_percent);
        self
    }
    
    /// Calculate ante amount for different structures
    pub fn calculate_ante_amount(&self) -> u32 {
        match self.ante_structure {
            AnteStructure::None => 0,
            AnteStructure::Fixed(amount) => amount,
            AnteStructure::BigBlindOnly(amount) => amount,
            AnteStructure::ButtonOnly(amount) => amount,
            AnteStructure::BigBlindPercentage(percent) => {
                // Calculate amount as percentage of big blind
                let amount = (self.big_blind as u64 * percent as u64) / 100;
                amount as u32
            }
        }
    }
    
    /// Enable straddles in the game
    pub fn with_straddles(mut self) -> Self {
        self.allow_straddles = true;
        self
    }
    
    pub fn with_player_limits(mut self, min: usize, max: usize) -> Self {
        // Ensure that max_players doesn't exceed the deck capacity
        let deck_max = Self::max_players_for_variant(self.variant);
        let adjusted_max = max.min(deck_max);
        
        // Ensure min_players doesn't exceed adjusted max_players
        self.min_players = min.min(adjusted_max);
        self.max_players = adjusted_max;
        self
    }
    
    pub fn min_raise(&self) -> u32 {
        self.big_blind
    }
    
    pub fn is_valid_raise(&self, current_bet: u32, raise_to: u32, pot_size: u32) -> bool {
        match self.betting_structure {
            BettingStructure::NoLimit => {
                // Raise must be at least min raise
                raise_to >= current_bet + self.min_raise()
            },
            BettingStructure::PotLimit => {
                // Raise must be at least min raise and at most pot-sized
                let min_valid = current_bet + self.min_raise();
                let max_valid = current_bet + pot_size;
                raise_to >= min_valid && raise_to <= max_valid
            },
            BettingStructure::FixedLimit => {
                // Fixed raises
                raise_to == current_bet + self.big_blind
            },
        }
    }
}