Documented code
Added test cases
This commit is contained in:
parent
5c271b279f
commit
7ff520ca20
23 changed files with 2232 additions and 40 deletions
|
|
@ -1,3 +1,95 @@
|
||||||
|
//! # Action Module
|
||||||
|
//!
|
||||||
|
//! This module provides types and functionality for representing poker actions taken by players
|
||||||
|
//! during a hand. The module includes both the `ActionType` enum for categorizing different
|
||||||
|
//! types of actions and the `Action` struct for representing specific action instances.
|
||||||
|
//!
|
||||||
|
//! ## Action Types
|
||||||
|
//!
|
||||||
|
//! The module supports all standard poker actions:
|
||||||
|
//! - **Fold**: Player discards their hand and forfeits the pot
|
||||||
|
//! - **Check**: Player passes without betting (when no bet is pending)
|
||||||
|
//! - **Call**: Player matches the current bet or raise
|
||||||
|
//! - **Bet**: Player makes an initial wager
|
||||||
|
//! - **Raise**: Player increases the current bet
|
||||||
|
//! - **Blind Actions**: Small blind, big blind, and other forced bets
|
||||||
|
//! - **Special Actions**: All-in, muck, sit out, etc.
|
||||||
|
//!
|
||||||
|
//! ## Action Structure
|
||||||
|
//!
|
||||||
|
//! Each `Action` contains:
|
||||||
|
//! - `action_type`: The type of action being taken
|
||||||
|
//! - `to_call`: Amount required to call (if applicable)
|
||||||
|
//! - `amount`: The amount being bet/raised (if applicable)
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Type Safety**: Strongly typed action representation
|
||||||
|
//! - **Factory Methods**: Convenient constructors for common actions
|
||||||
|
//! - **Validation**: Built-in action property checking
|
||||||
|
//! - **Formatting**: Human-readable string representations
|
||||||
|
//! - **Memory Efficient**: Compact representation using enums
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Creating Basic Actions
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::action::{Action, ActionType};
|
||||||
|
//!
|
||||||
|
//! // Create a fold action
|
||||||
|
//! let fold = Action::fold_action(50.0);
|
||||||
|
//! assert!(fold.is_fold());
|
||||||
|
//!
|
||||||
|
//! // Create a bet action
|
||||||
|
//! let bet = Action::bet_action(100.0);
|
||||||
|
//! assert!(bet.is_bet());
|
||||||
|
//! assert_eq!(bet.amount(), 100.0);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Action Properties
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::action::{Action, ActionType};
|
||||||
|
//!
|
||||||
|
//! let raise = Action::raise_action(50.0, 200.0);
|
||||||
|
//!
|
||||||
|
//! println!("Action type: {:?}", raise.action_type());
|
||||||
|
//! println!("Amount to call: {}", raise.to_call());
|
||||||
|
//! println!("Raise amount: {}", raise.amount());
|
||||||
|
//! println!("Is raise: {}", raise.is_raise());
|
||||||
|
//! println!("Is voluntary: {}", raise.is_voluntary());
|
||||||
|
//! println!("String: {}", raise.to_string());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Blind Actions
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::action::Action;
|
||||||
|
//!
|
||||||
|
//! // Post blinds
|
||||||
|
//! let small_blind = Action::small_blind_action(10.0);
|
||||||
|
//! let big_blind = Action::big_blind_action(20.0);
|
||||||
|
//!
|
||||||
|
//! // Post ante
|
||||||
|
//! let ante = Action::post_ante_action(5.0);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Enum Representation**: Uses `repr(u8)` for memory efficiency and serialization
|
||||||
|
//! - **Floating Point Amounts**: Uses `f64` for precise monetary calculations
|
||||||
|
//! - **Immutable Actions**: Actions are immutable once created
|
||||||
|
//! - **Comprehensive Coverage**: Supports all common poker action types
|
||||||
|
//! - **Factory Pattern**: Provides convenient constructors for common cases
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Memory**: 17 bytes per action (1 + 8 + 8)
|
||||||
|
//! - **Comparisons**: O(1) enum comparisons
|
||||||
|
//! - **Formatting**: O(1) string formatting
|
||||||
|
//! - **Validation**: O(1) property checks
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum ActionType {
|
pub enum ActionType {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,62 @@
|
||||||
|
//! # Card Module
|
||||||
|
//!
|
||||||
|
//! This module provides the core `Card` struct and associated functionality for representing
|
||||||
|
//! playing cards in the poker library. The Card struct uses a compact 8-bit representation
|
||||||
|
//! that efficiently stores both rank and suit information.
|
||||||
|
//!
|
||||||
|
//! ## Card Representation
|
||||||
|
//!
|
||||||
|
//! Cards are stored as a single `u8` byte with the following bit layout:
|
||||||
|
//! ```text
|
||||||
|
//! Bit: 76543210
|
||||||
|
//! Use: SSSSRRRR
|
||||||
|
//! ||||++++-- Rank (0-14, where 2-10=face value, 11=Jack, 12=Queen, 13=King, 14=Ace)
|
||||||
|
//! ++++------ Suit (0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs)
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Creating Cards
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//!
|
||||||
|
//! // Create from rank and suit
|
||||||
|
//! let ace_spades = Card::from_rank_suit(Card::ACE, Card::SPADES).unwrap();
|
||||||
|
//!
|
||||||
|
//! // Create from string notation
|
||||||
|
//! let king_hearts = Card::from_string("Kh").unwrap();
|
||||||
|
//!
|
||||||
|
//! // Create from deck index (0-51)
|
||||||
|
//! let two_clubs = Card::from_index(39).unwrap();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Card Information
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//!
|
||||||
|
//! let card = Card::from_string("As").unwrap();
|
||||||
|
//! println!("Rank: {}", card.rank()); // 14
|
||||||
|
//! println!("Suit: {}", card.suit()); // 0 (Spades)
|
||||||
|
//! println!("Index: {}", card.index()); // 12
|
||||||
|
//! println!("Display: {}", card); // "As"
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Compact Storage**: Single byte per card for memory efficiency
|
||||||
|
//! - **Fast Operations**: Bit operations for rank/suit extraction
|
||||||
|
//! - **Standard Ordering**: Cards ordered by suit (Spades, Hearts, Diamonds, Clubs)
|
||||||
|
//! - **Error Handling**: Comprehensive validation with descriptive error messages
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Memory**: 1 byte per card
|
||||||
|
//! - **Rank/Suit Access**: O(1) bit operations
|
||||||
|
//! - **String Conversion**: O(1) table lookups
|
||||||
|
//! - **Validation**: O(1) bounds checking
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Card(u8);
|
pub struct Card(u8);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,95 @@
|
||||||
|
//! # Deck Module
|
||||||
|
//!
|
||||||
|
//! This module provides the `Deck` struct for managing a standard 52-card poker deck.
|
||||||
|
//! The Deck struct supports shuffling, dealing cards, and various card manipulation
|
||||||
|
//! operations required for poker game simulation and testing.
|
||||||
|
//!
|
||||||
|
//! ## Deck Structure
|
||||||
|
//!
|
||||||
|
//! A `Deck` contains:
|
||||||
|
//! - A vector of remaining cards in the deck
|
||||||
|
//! - A vector of cards that have been dealt
|
||||||
|
//! - Methods for shuffling, dealing, and card management
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Standard Deck**: Creates a proper 52-card poker deck
|
||||||
|
//! - **Shuffling**: Multiple shuffle methods including seeded randomization
|
||||||
|
//! - **Dealing**: Deal cards sequentially or randomly
|
||||||
|
//! - **Card Management**: Extract specific cards, check card availability
|
||||||
|
//! - **Reset Functionality**: Return all cards to the deck
|
||||||
|
//! - **Integration**: Works seamlessly with `Hand` and `Card` structs
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Deck Operations
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::deck::Deck;
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//!
|
||||||
|
//! // Create and shuffle a new deck
|
||||||
|
//! let mut deck = Deck::new();
|
||||||
|
//! deck.shuffle();
|
||||||
|
//!
|
||||||
|
//! // Deal some cards
|
||||||
|
//! if let Some(card) = deck.deal() {
|
||||||
|
//! println!("Dealt: {}", card);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! println!("Cards left: {}", deck.cards_left());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Seeded Shuffling for Testing
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::deck::Deck;
|
||||||
|
//!
|
||||||
|
//! // Create deck with reproducible shuffle for testing
|
||||||
|
//! let mut deck = Deck::with_seed(12345);
|
||||||
|
//!
|
||||||
|
//! // Deal cards in predictable order
|
||||||
|
//! let card1 = deck.deal().unwrap();
|
||||||
|
//! let card2 = deck.deal().unwrap();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Card Management
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::deck::Deck;
|
||||||
|
//! use poker_api::api::hand::Hand;
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//!
|
||||||
|
//! let mut deck = Deck::new();
|
||||||
|
//!
|
||||||
|
//! // Extract specific cards
|
||||||
|
//! let ace_spades = Card::from_string("As").unwrap();
|
||||||
|
//! deck.extract_card(ace_spades);
|
||||||
|
//!
|
||||||
|
//! // Extract entire hand
|
||||||
|
//! let mut hand = Hand::from_string("Kh Qh Jh").unwrap();
|
||||||
|
//! deck.extract_hand(&hand);
|
||||||
|
//!
|
||||||
|
//! // Reset deck to restore all cards
|
||||||
|
//! deck.reset();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Two-Vector Design**: Separate active and dealt cards for efficient operations
|
||||||
|
//! - **Immutable Access**: Most getter methods take `&self` for safety
|
||||||
|
//! - **Standard Ordering**: Cards ordered by suit (Spades, Hearts, Diamonds, Clubs)
|
||||||
|
//! - **Error Handling**: Graceful handling of empty deck conditions
|
||||||
|
//! - **Random Access**: Support for both sequential and random dealing
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Memory**: ~104 bytes overhead + 52 bytes for cards
|
||||||
|
//! - **Shuffling**: O(n) where n is number of cards
|
||||||
|
//! - **Dealing**: O(1) for sequential, O(n) for random
|
||||||
|
//! - **Card Lookup**: O(n) linear search
|
||||||
|
//! - **Reset**: O(1) vector extension
|
||||||
|
|
||||||
use crate::api::card::Card;
|
use crate::api::card::Card;
|
||||||
use crate::api::hand::Hand;
|
use crate::api::hand::Hand;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,118 @@
|
||||||
|
//! # Game Info Module
|
||||||
|
//!
|
||||||
|
//! This module defines the `GameInfo` trait that provides comprehensive access to the
|
||||||
|
//! current state of a poker game. This trait serves as the primary interface for bots
|
||||||
|
//! and observers to understand game conditions, player states, and betting information.
|
||||||
|
//!
|
||||||
|
//! ## Game Info Trait Overview
|
||||||
|
//!
|
||||||
|
//! The `GameInfo` trait provides read-only access to all aspects of a poker game:
|
||||||
|
//! - **Game State**: Current betting round, game stage, and status
|
||||||
|
//! - **Betting Information**: Blinds, bets, raises, and pot sizes
|
||||||
|
//! - **Player Information**: Active players, bankrolls, and positions
|
||||||
|
//! - **Table State**: Board cards, button position, and seating
|
||||||
|
//! - **Game Rules**: Limit types, special modes, and configurations
|
||||||
|
//!
|
||||||
|
//! ## Key Information Categories
|
||||||
|
//!
|
||||||
|
//! ### Game State
|
||||||
|
//! - Current betting stage (preflop, flop, turn, river)
|
||||||
|
//! - Game completion status and winner information
|
||||||
|
//! - Round counts and raise counts
|
||||||
|
//!
|
||||||
|
//! ### Betting Information
|
||||||
|
//! - Blind sizes and current bet amounts
|
||||||
|
//! - Pot sizes (main pot and side pots)
|
||||||
|
//! - Minimum raise amounts and call requirements
|
||||||
|
//!
|
||||||
|
//! ### Player Information
|
||||||
|
//! - Player counts and seating positions
|
||||||
|
//! - Bankroll information and amounts at risk
|
||||||
|
//! - Player states (active, committed, all-in)
|
||||||
|
//!
|
||||||
|
//! ### Table Information
|
||||||
|
//! - Community cards and board state
|
||||||
|
//! - Button, blinds, and current player positions
|
||||||
|
//! - Eligible pot amounts for each player
|
||||||
|
//!
|
||||||
|
//! ## Implementation Requirements
|
||||||
|
//!
|
||||||
|
//! Any struct implementing `GameInfo` must provide:
|
||||||
|
//! - Accurate game state information
|
||||||
|
//! - Up-to-date betting and pot information
|
||||||
|
//! - Current player states and positions
|
||||||
|
//! - Proper handling of edge cases (empty pots, all-in players, etc.)
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Game State Queries
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::game_info::GameInfo;
|
||||||
|
//!
|
||||||
|
//! fn analyze_game_state(info: &dyn GameInfo) {
|
||||||
|
//! println!("Game stage: {}", info.get_stage());
|
||||||
|
//! println!("Is preflop: {}", info.is_pre_flop());
|
||||||
|
//! println!("Is game over: {}", info.is_game_over());
|
||||||
|
//!
|
||||||
|
//! if info.is_post_flop() {
|
||||||
|
//! println!("We're past the flop");
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Betting Information
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::game_info::GameInfo;
|
||||||
|
//!
|
||||||
|
//! fn analyze_betting(info: &dyn GameInfo, my_seat: usize) {
|
||||||
|
//! println!("Total pot: ${}", info.get_total_pot_size());
|
||||||
|
//! println!("Main pot: ${}", info.get_main_pot_size());
|
||||||
|
//! println!("Side pots: {}", info.get_num_side_pots());
|
||||||
|
//!
|
||||||
|
//! if info.can_raise(my_seat) {
|
||||||
|
//! println!("I can raise");
|
||||||
|
//! println!("Min raise: ${}", info.get_min_raise());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! println!("Amount to call: ${}", info.get_amount_to_call(my_seat));
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Player Analysis
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::game_info::GameInfo;
|
||||||
|
//!
|
||||||
|
//! fn analyze_players(info: &dyn GameInfo) {
|
||||||
|
//! println!("Total players: {}", info.get_num_players());
|
||||||
|
//! println!("Active players: {}", info.get_num_active_players());
|
||||||
|
//! println!("All-in players: {}", info.get_num_all_in_players());
|
||||||
|
//!
|
||||||
|
//! let button_seat = info.get_button_seat();
|
||||||
|
//! let small_blind_seat = info.get_small_blind_seat();
|
||||||
|
//! let big_blind_seat = info.get_big_blind_seat();
|
||||||
|
//!
|
||||||
|
//! println!("Button at seat: {}", button_seat);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Read-Only Interface**: All methods take `&self` for safe state access
|
||||||
|
//! - **Comprehensive Coverage**: Includes all information needed for poker decisions
|
||||||
|
//! - **Trait-Based Design**: Enables different game implementations
|
||||||
|
//! - **Numeric Types**: Uses appropriate types for money (f64) and counts (usize)
|
||||||
|
//! - **Error Handling**: Returns Options for fallible lookups
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Method Calls**: O(1) for most state queries
|
||||||
|
//! - **Player Lookups**: O(n) where n is number of players
|
||||||
|
//! - **Memory**: No additional allocations required
|
||||||
|
//! - **Thread Safety**: Read-only access supports concurrent use
|
||||||
|
|
||||||
use crate::api::hand::Hand;
|
use crate::api::hand::Hand;
|
||||||
use crate::api::player_info::PlayerInfo;
|
use crate::api::player_info::PlayerInfo;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,164 @@
|
||||||
|
//! # Game Observer Module
|
||||||
|
//!
|
||||||
|
//! This module defines the `GameObserver` trait that enables poker bots and observers
|
||||||
|
//! to receive real-time notifications about game events and state changes. This trait
|
||||||
|
//! implements the Observer pattern, allowing decoupled communication between the game
|
||||||
|
//! engine and interested parties.
|
||||||
|
//!
|
||||||
|
//! ## Game Observer Trait Overview
|
||||||
|
//!
|
||||||
|
//! The `GameObserver` trait provides event-driven notifications for:
|
||||||
|
//! - **Game Lifecycle**: Start, end, and state changes
|
||||||
|
//! - **Player Actions**: Every action taken by any player
|
||||||
|
//! - **Card Events**: Hole cards dealt and showdown reveals
|
||||||
|
//! - **Betting Stages**: New betting rounds and stage transitions
|
||||||
|
//! - **Results**: Winners, amounts won, and final hand information
|
||||||
|
//!
|
||||||
|
//! ## Event Types
|
||||||
|
//!
|
||||||
|
//! ### Game Events
|
||||||
|
//! - `game_start_event()`: Hand initialization with game info
|
||||||
|
//! - `game_over_event()`: Hand completion
|
||||||
|
//! - `game_state_changed()`: State update after action processing
|
||||||
|
//!
|
||||||
|
//! ### Action Events
|
||||||
|
//! - `action_event()`: Individual player actions (bet, raise, fold, etc.)
|
||||||
|
//! - `stage_event()`: New betting round begins
|
||||||
|
//!
|
||||||
|
//! ### Card Events
|
||||||
|
//! - `deal_hole_cards_event()`: Hole cards being distributed
|
||||||
|
//! - `showdown_event()`: Player reveals hole cards at showdown
|
||||||
|
//!
|
||||||
|
//! ### Result Events
|
||||||
|
//! - `win_event()`: Player wins pot with hand information
|
||||||
|
//!
|
||||||
|
//! ## Implementation Requirements
|
||||||
|
//!
|
||||||
|
//! Any struct implementing `GameObserver` must handle:
|
||||||
|
//! - All event types appropriately
|
||||||
|
//! - Proper state management for game information
|
||||||
|
//! - Thread-safe event processing if needed
|
||||||
|
//! - Graceful handling of unexpected events
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Observer Implementation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::game_observer::GameObserver;
|
||||||
|
//! use poker_api::api::action::Action;
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//! use poker_api::api::game_info::GameInfo;
|
||||||
|
//!
|
||||||
|
//! struct LoggingObserver {
|
||||||
|
//! game_count: usize,
|
||||||
|
//! action_count: usize,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl GameObserver for LoggingObserver {
|
||||||
|
//! fn game_start_event(&mut self, _gi: Box<dyn GameInfo>) {
|
||||||
|
//! self.game_count += 1;
|
||||||
|
//! println!("Game {} started", self.game_count);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn game_over_event(&mut self) {
|
||||||
|
//! println!("Game {} ended", self.game_count);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn action_event(&mut self, pos: usize, act: Action) {
|
||||||
|
//! self.action_count += 1;
|
||||||
|
//! println!("Seat {}: {}", pos, act.to_string());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn stage_event(&mut self, stage: u8) {
|
||||||
|
//! let stage_name = match stage {
|
||||||
|
//! 0 => "Preflop",
|
||||||
|
//! 1 => "Flop",
|
||||||
|
//! 2 => "Turn",
|
||||||
|
//! 3 => "River",
|
||||||
|
//! _ => "Unknown",
|
||||||
|
//! };
|
||||||
|
//! println!("New stage: {}", stage_name);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn win_event(&mut self, pos: usize, amount: f64, hand_name: String) {
|
||||||
|
//! println!("Seat {} won ${:.2} with {}", pos, amount, hand_name);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn game_state_changed(&mut self) {
|
||||||
|
//! // State updated after action processing
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn deal_hole_cards_event(&mut self) {
|
||||||
|
//! println!("Dealing hole cards");
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn showdown_event(&mut self, pos: usize, c1: Card, c2: Card) {
|
||||||
|
//! println!("Seat {} shows: {} {}", pos, c1, c2);
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Advanced Observer with State Tracking
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::game_observer::GameObserver;
|
||||||
|
//! use poker_api::api::action::Action;
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//! use poker_api::api::game_info::GameInfo;
|
||||||
|
//!
|
||||||
|
//! struct StatisticsTracker {
|
||||||
|
//! total_games: usize,
|
||||||
|
//! action_history: Vec<(usize, Action)>,
|
||||||
|
//! pot_sizes: Vec<f64>,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl GameObserver for StatisticsTracker {
|
||||||
|
//! fn game_start_event(&mut self, gi: Box<dyn GameInfo>) {
|
||||||
|
//! self.total_games += 1;
|
||||||
|
//! self.action_history.clear();
|
||||||
|
//! self.pot_sizes.push(gi.get_total_pot_size());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn action_event(&mut self, pos: usize, act: Action) {
|
||||||
|
//! self.action_history.push((pos, act));
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn game_over_event(&mut self) {
|
||||||
|
//! println!("Actions this hand: {}", self.action_history.len());
|
||||||
|
//! println!("Final pot: ${:.2}", self.pot_sizes.last().unwrap_or(&0.0));
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn game_state_changed(&mut self) {
|
||||||
|
//! // Update pot size tracking
|
||||||
|
//! if let Some(current_pot) = self.pot_sizes.last_mut() {
|
||||||
|
//! // Update with new pot size if available
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // Implement other required methods...
|
||||||
|
//! # fn deal_hole_cards_event(&mut self) {}
|
||||||
|
//! # fn stage_event(&mut self, _stage: u8) {}
|
||||||
|
//! # fn showdown_event(&mut self, _pos: usize, _c1: Card, _c2: Card) {}
|
||||||
|
//! # fn win_event(&mut self, _pos: usize, _amount: f64, _hand_name: String) {}
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Observer Pattern**: Decoupled event notification system
|
||||||
|
//! - **Comprehensive Events**: Cover all important game state changes
|
||||||
|
//! - **Minimal Interface**: Only essential events to avoid overhead
|
||||||
|
//! - **Mutable Receivers**: Allow observers to maintain state
|
||||||
|
//! - **Boxed GameInfo**: Avoid object slicing with trait objects
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Event Notification**: O(1) per observer for most events
|
||||||
|
//! - **Memory**: Minimal per-event overhead
|
||||||
|
//! - **State Management**: Observer handles its own state complexity
|
||||||
|
//! - **Threading**: Not inherently thread-safe (implement as needed)
|
||||||
|
|
||||||
use crate::api::action::Action;
|
use crate::api::action::Action;
|
||||||
use crate::api::card::Card;
|
use crate::api::card::Card;
|
||||||
use crate::api::game_info::GameInfo;
|
use crate::api::game_info::GameInfo;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,90 @@
|
||||||
|
//! # Hand Module
|
||||||
|
//!
|
||||||
|
//! This module provides the `Hand` struct for managing collections of playing cards.
|
||||||
|
//! The Hand struct supports various poker hand sizes from 0 to 7 cards and provides
|
||||||
|
//! methods for card manipulation, sorting, and conversion to different formats.
|
||||||
|
//!
|
||||||
|
//! ## Hand Structure
|
||||||
|
//!
|
||||||
|
//! A `Hand` contains:
|
||||||
|
//! - An array of up to 7 optional `Card` values
|
||||||
|
//! - A size counter tracking the number of valid cards
|
||||||
|
//! - Methods for adding, removing, and accessing cards
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Dynamic Sizing**: Supports hands from 0 to 7 cards
|
||||||
|
//! - **Card Management**: Add, remove, and access cards by position
|
||||||
|
//! - **String Conversion**: Parse hands from string notation (e.g., "As Ks Qs")
|
||||||
|
//! - **Sorting**: Sort cards in descending order by rank
|
||||||
|
//! - **Validation**: Bounds checking and error handling
|
||||||
|
//! - **Multiple Formats**: Support for different string representations
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Creating and Manipulating Hands
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::hand::Hand;
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//!
|
||||||
|
//! // Create empty hand
|
||||||
|
//! let mut hand = Hand::new();
|
||||||
|
//!
|
||||||
|
//! // Add cards individually
|
||||||
|
//! let ace_spades = Card::from_string("As").unwrap();
|
||||||
|
//! hand.add_card(ace_spades).unwrap();
|
||||||
|
//!
|
||||||
|
//! // Create from string notation
|
||||||
|
//! let texas_holdem_hand = Hand::from_string("As Ks Qs Js Ts").unwrap();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Hand Operations
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::hand::Hand;
|
||||||
|
//!
|
||||||
|
//! let mut hand = Hand::from_string("2s As Ks").unwrap();
|
||||||
|
//!
|
||||||
|
//! // Access cards
|
||||||
|
//! println!("First card: {:?}", hand.get_card(1));
|
||||||
|
//! println!("Hand size: {}", hand.size());
|
||||||
|
//!
|
||||||
|
//! // Sort in descending order
|
||||||
|
//! hand.sort();
|
||||||
|
//!
|
||||||
|
//! // Convert to string
|
||||||
|
//! println!("Sorted hand: {}", hand.to_string());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Integration with Evaluators
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::hand::Hand;
|
||||||
|
//! use poker_api::hand_evaluator::LookupHandEvaluator;
|
||||||
|
//!
|
||||||
|
//! let hand = Hand::from_string("As Ks Qs Js Ts").unwrap();
|
||||||
|
//! let evaluator = LookupHandEvaluator::new().unwrap();
|
||||||
|
//! let rank = evaluator.rank_hand(&hand);
|
||||||
|
//! println!("Hand rank: {}", rank);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Fixed Array Size**: Uses 7-element array for Texas Hold'em compatibility
|
||||||
|
//! - **Optional Cards**: Uses `Option<Card>` to handle variable hand sizes
|
||||||
|
//! - **1-based Indexing**: Public API uses 1-based indexing for user convenience
|
||||||
|
//! - **Immutable Access**: Most getter methods take `&self` for safety
|
||||||
|
//! - **Error Handling**: Comprehensive error messages for invalid operations
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Memory**: Fixed 7-element array (56 bytes) + size field
|
||||||
|
//! - **Card Access**: O(1) array access for valid indices
|
||||||
|
//! - **Sorting**: O(n log n) comparison sort
|
||||||
|
//! - **String Conversion**: O(n) where n is hand size
|
||||||
|
//! - **Validation**: O(1) bounds checking
|
||||||
|
|
||||||
use crate::api::card::Card;
|
use crate::api::card::Card;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|
@ -123,6 +210,7 @@ impl Hand {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::api::card::Card;
|
use crate::api::card::Card;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,108 @@
|
||||||
|
//! # Poker API Module
|
||||||
|
//!
|
||||||
|
//! This module provides the core application programming interface (API) for the poker
|
||||||
|
//! hand evaluation library. It contains all the essential types, traits, and functionality
|
||||||
|
//! needed to work with poker cards, hands, players, and game state.
|
||||||
|
//!
|
||||||
|
//! ## Module Overview
|
||||||
|
//!
|
||||||
|
//! The API module is organized into several key areas:
|
||||||
|
//! - **Card Management**: [`card`] - Individual playing card representation
|
||||||
|
//! - **Hand Management**: [`hand`] - Collections of cards for poker hands
|
||||||
|
//! - **Deck Operations**: [`deck`] - Standard 52-card deck with shuffling
|
||||||
|
//! - **Player Actions**: [`action`] - Betting actions and action types
|
||||||
|
//! - **Player Interface**: [`player`] - Bot interface and player trait
|
||||||
|
//! - **Game Information**: [`game_info`] - Game state and information access
|
||||||
|
//! - **Game Observation**: [`game_observer`] - Event-driven game state updates
|
||||||
|
//! - **Player Information**: [`player_info`] - Individual player state queries
|
||||||
|
//! - **Preferences**: [`preferences`] - Configuration and settings management
|
||||||
|
//! - **Hand Evaluation**: [`hand_eval`] - Core hand evaluation trait
|
||||||
|
//!
|
||||||
|
//! ## Core Types
|
||||||
|
//!
|
||||||
|
//! ### Card and Hand Management
|
||||||
|
//! - [`Card`](card::Card) - Individual playing card with rank and suit
|
||||||
|
//! - [`Hand`](hand::Hand) - Collection of cards (up to 7 for Texas Hold'em)
|
||||||
|
//! - [`Deck`](deck::Deck) - Standard 52-card deck with shuffle/deal operations
|
||||||
|
//!
|
||||||
|
//! ### Player and Actions
|
||||||
|
//! - [`Action`](action::Action) - Player betting actions (bet, raise, fold, etc.)
|
||||||
|
//! - [`Player`](player::Player) - Trait for poker bot implementations
|
||||||
|
//! - [`PlayerInfo`](player_info::PlayerInfo) - Individual player state information
|
||||||
|
//!
|
||||||
|
//! ### Game State
|
||||||
|
//! - [`GameInfo`](game_info::GameInfo) - Comprehensive game state access
|
||||||
|
//! - [`GameObserver`](game_observer::GameObserver) - Event notification system
|
||||||
|
//! - [`Preferences`](preferences::Preferences) - Configuration management
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Type Safety**: Strongly typed card and hand representations
|
||||||
|
//! - **Memory Efficiency**: Compact binary representations for performance
|
||||||
|
//! - **Extensible Design**: Trait-based architecture for custom implementations
|
||||||
|
//! - **Event-Driven**: Observer pattern for game state notifications
|
||||||
|
//! - **Configuration**: Flexible preference system for bot customization
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Card and Hand Operations
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::{card::Card, hand::Hand, deck::Deck};
|
||||||
|
//!
|
||||||
|
//! // Create cards and hands
|
||||||
|
//! let ace_spades = Card::from_string("As").unwrap();
|
||||||
|
//! let king_spades = Card::from_string("Ks").unwrap();
|
||||||
|
//!
|
||||||
|
//! let mut hand = Hand::new();
|
||||||
|
//! hand.add_card(ace_spades).unwrap();
|
||||||
|
//! hand.add_card(king_spades).unwrap();
|
||||||
|
//!
|
||||||
|
//! // Work with decks
|
||||||
|
//! let mut deck = Deck::new();
|
||||||
|
//! deck.shuffle();
|
||||||
|
//! let dealt_card = deck.deal().unwrap();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Player Actions
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::action::{Action, ActionType};
|
||||||
|
//!
|
||||||
|
//! let fold_action = Action::fold_action(50.0);
|
||||||
|
//! let bet_action = Action::bet_action(100.0);
|
||||||
|
//! let raise_action = Action::raise_action(50.0, 200.0);
|
||||||
|
//!
|
||||||
|
//! println!("Action: {}", fold_action.to_string());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Game State Access
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::game_info::GameInfo;
|
||||||
|
//!
|
||||||
|
//! fn analyze_game(game_info: &dyn GameInfo) {
|
||||||
|
//! println!("Total pot: ${}", game_info.get_total_pot_size());
|
||||||
|
//! println!("Players: {}", game_info.get_num_players());
|
||||||
|
//! println!("Stage: {}", game_info.get_stage());
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Architecture
|
||||||
|
//!
|
||||||
|
//! The API module follows a layered architecture:
|
||||||
|
//! - **Data Types**: Concrete types for cards, hands, and actions
|
||||||
|
//! - **Traits**: Interfaces for players, observers, and game info
|
||||||
|
//! - **Implementations**: Default implementations and utilities
|
||||||
|
//! - **Integration**: Seamless integration between all components
|
||||||
|
//!
|
||||||
|
//! ## Performance Considerations
|
||||||
|
//!
|
||||||
|
//! - **Memory Layout**: Types optimized for cache efficiency
|
||||||
|
//! - **Copy Semantics**: Small types implement Copy for performance
|
||||||
|
//! - **Trait Objects**: Used sparingly to minimize vtable overhead
|
||||||
|
//! - **Inline Operations**: Common operations inlined for speed
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod card;
|
pub mod card;
|
||||||
pub mod deck;
|
pub mod deck;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,88 @@
|
||||||
|
//! # Player Module
|
||||||
|
//!
|
||||||
|
//! This module defines the core `Player` trait that all poker bot implementations must
|
||||||
|
//! implement. The Player trait provides the interface between the poker game engine
|
||||||
|
//! and individual bot logic, enabling different AI strategies and human player interfaces.
|
||||||
|
//!
|
||||||
|
//! ## Player Trait Overview
|
||||||
|
//!
|
||||||
|
//! The `Player` trait defines the essential methods that a poker bot must implement:
|
||||||
|
//! - **Initialization**: Setup with preferences and game parameters
|
||||||
|
//! - **Hole Cards**: Receive private cards at the start of a hand
|
||||||
|
//! - **Action Selection**: Choose appropriate actions during betting rounds
|
||||||
|
//! - **Game Observation**: Receive information about game state changes
|
||||||
|
//!
|
||||||
|
//! ## Key Methods
|
||||||
|
//!
|
||||||
|
//! - `init()`: Initialize player with preferences and configuration
|
||||||
|
//! - `hole_cards()`: Receive private hole cards for the current hand
|
||||||
|
//! - `get_action()`: Return the player's chosen action when it's their turn
|
||||||
|
//! - GameObserver methods: Receive updates about game state and other players' actions
|
||||||
|
//!
|
||||||
|
//! ## Implementation Requirements
|
||||||
|
//!
|
||||||
|
//! Any struct implementing `Player` must:
|
||||||
|
//! - Handle initialization with arbitrary preferences
|
||||||
|
//! - Process hole cards correctly
|
||||||
|
//! - Return valid actions in `get_action()`
|
||||||
|
//! - Implement all `GameObserver` methods appropriately
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Player Implementation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::player::Player;
|
||||||
|
//! use poker_api::api::action::{Action, ActionType};
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//! use poker_api::api::game_observer::GameObserver;
|
||||||
|
//! use poker_api::api::preferences::Preferences;
|
||||||
|
//!
|
||||||
|
//! struct SimpleBot {
|
||||||
|
//! hole_cards: Option<(Card, Card)>,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl Player for SimpleBot {
|
||||||
|
//! fn init(&mut self, _prefs: Box<dyn Preferences>) {
|
||||||
|
//! // Initialize bot with preferences
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn hole_cards(&mut self, c1: Card, c2: Card, _seat: usize) {
|
||||||
|
//! self.hole_cards = Some((c1, c2));
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn get_action(&mut self) -> Action {
|
||||||
|
//! // Simple logic: always call if cards are good
|
||||||
|
//! Action::call_action(0.0)
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl GameObserver for SimpleBot {
|
||||||
|
//! // Implement game observation methods...
|
||||||
|
//! # fn game_start(&mut self, _info: poker_api::api::game_info::GameInfo) {}
|
||||||
|
//! # fn game_end(&mut self) {}
|
||||||
|
//! # fn round_start(&mut self, _round: u8) {}
|
||||||
|
//! # fn round_end(&mut self) {}
|
||||||
|
//! # fn player_action(&mut self, _seat: usize, _action: Action) {}
|
||||||
|
//! # fn community_cards(&mut self, _cards: Vec<Card>) {}
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Trait-Based Design**: Enables polymorphism between different bot types
|
||||||
|
//! - **Event-Driven Architecture**: Players receive game state through observer pattern
|
||||||
|
//! - **Minimal Interface**: Core methods required for any poker bot implementation
|
||||||
|
//! - **Extensible Preferences**: Support for arbitrary bot configuration
|
||||||
|
//! - **Action-Based Decisions**: Players return complete action objects
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Memory**: Minimal trait object overhead
|
||||||
|
//! - **Method Calls**: O(1) trait method dispatch
|
||||||
|
//! - **State Management**: Player manages internal state as needed
|
||||||
|
//! - **Action Generation**: Variable time depending on bot complexity
|
||||||
|
|
||||||
use crate::api::action::Action;
|
use crate::api::action::Action;
|
||||||
use crate::api::card::Card;
|
use crate::api::card::Card;
|
||||||
use crate::api::game_observer::GameObserver;
|
use crate::api::game_observer::GameObserver;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,128 @@
|
||||||
|
//! # Player Info Module
|
||||||
|
//!
|
||||||
|
//! This module defines the `PlayerInfo` trait that provides detailed information about
|
||||||
|
//! individual players in a poker game. This trait offers comprehensive access to player
|
||||||
|
//! state, betting information, and hand status for use by observers and game logic.
|
||||||
|
//!
|
||||||
|
//! ## Player Info Trait Overview
|
||||||
|
//!
|
||||||
|
//! The `PlayerInfo` trait provides read-only access to:
|
||||||
|
//! - **Financial State**: Bankroll, gains, and betting amounts
|
||||||
|
//! - **Game Status**: Active state, folded status, and position
|
||||||
|
//! - **Hand Information**: Revealed cards and hand state
|
||||||
|
//! - **Betting State**: Amounts to call, committed status, and raise capabilities
|
||||||
|
//! - **Action History**: Last action taken and round participation
|
||||||
|
//!
|
||||||
|
//! ## Key Information Categories
|
||||||
|
//!
|
||||||
|
//! ### Financial Information
|
||||||
|
//! - Current bankroll and net gain/loss
|
||||||
|
//! - Bankroll at start of hand and in small bets
|
||||||
|
//! - Amount at risk and committed to pot
|
||||||
|
//!
|
||||||
|
//! ### Game Status
|
||||||
|
//! - Seat position, name, and active state
|
||||||
|
//! - Folded, all-in, and sitting out status
|
||||||
|
//! - Button position indicator
|
||||||
|
//!
|
||||||
|
//! ### Hand Information
|
||||||
|
//! - Revealed hand (if applicable)
|
||||||
|
//! - Action status and round participation
|
||||||
|
//! - Amount contributed to current pot
|
||||||
|
//!
|
||||||
|
//! ### Betting Information
|
||||||
|
//! - Amount required to call
|
||||||
|
//! - Raise capabilities and amounts
|
||||||
|
//! - Last action taken
|
||||||
|
//!
|
||||||
|
//! ## Implementation Requirements
|
||||||
|
//!
|
||||||
|
//! Any struct implementing `PlayerInfo` must provide:
|
||||||
|
//! - Accurate financial calculations
|
||||||
|
//! - Current game state information
|
||||||
|
//! - Proper handling of edge cases (all-in, folded players)
|
||||||
|
//! - Integration with the broader game state
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Player Analysis
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::player_info::PlayerInfo;
|
||||||
|
//!
|
||||||
|
//! fn analyze_player(player: &dyn PlayerInfo) {
|
||||||
|
//! println!("Player: {}", player.get_name());
|
||||||
|
//! println!("Seat: {}", player.get_seat());
|
||||||
|
//! println!("Bankroll: ${:.2}", player.get_bankroll());
|
||||||
|
//! println!("Net gain: ${:.2}", player.get_net_gain());
|
||||||
|
//!
|
||||||
|
//! if player.is_active() {
|
||||||
|
//! println!("Player is active");
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! if player.is_all_in() {
|
||||||
|
//! println!("Player is all-in");
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! if player.is_folded() {
|
||||||
|
//! println!("Player has folded");
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Betting Analysis
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::player_info::PlayerInfo;
|
||||||
|
//!
|
||||||
|
//! fn analyze_betting(player: &dyn PlayerInfo) {
|
||||||
|
//! println!("Amount to call: ${:.2}", player.get_amount_to_call());
|
||||||
|
//! println!("Amount in pot: ${:.2}", player.get_amount_in_pot());
|
||||||
|
//! println!("Committed: {}", player.is_committed());
|
||||||
|
//!
|
||||||
|
//! if player.has_enough_to_raise() {
|
||||||
|
//! println!("Can raise: ${:.2}", player.get_amount_raiseable());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! println!("Last action: {}", player.get_last_action());
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Hand Analysis
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::player_info::PlayerInfo;
|
||||||
|
//!
|
||||||
|
//! fn analyze_hand(player: &dyn PlayerInfo) {
|
||||||
|
//! if let Some(hand) = player.get_revealed_hand() {
|
||||||
|
//! println!("Revealed hand: {}", hand.to_string());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! if player.has_acted_this_round() {
|
||||||
|
//! println!("Has acted this round");
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! if player.is_button() {
|
||||||
|
//! println!("Is the button");
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Read-Only Interface**: All methods take `&self` for safe state access
|
||||||
|
//! - **Comprehensive Coverage**: All player information needed for poker decisions
|
||||||
|
//! - **Financial Precision**: Uses f64 for accurate monetary calculations
|
||||||
|
//! - **State Consistency**: Integrates with GameInfo for complete picture
|
||||||
|
//! - **Action Integration**: Links to action system for complete player tracking
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Method Calls**: O(1) for state queries
|
||||||
|
//! - **Financial Calculations**: O(1) arithmetic operations
|
||||||
|
//! - **Memory**: Minimal additional overhead
|
||||||
|
//! - **Integration**: Requires coordination with game state
|
||||||
|
|
||||||
use crate::api::game_info::GameInfo;
|
use crate::api::game_info::GameInfo;
|
||||||
use crate::api::hand::Hand;
|
use crate::api::hand::Hand;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,134 @@
|
||||||
|
//! # Preferences Module
|
||||||
|
//!
|
||||||
|
//! This module defines the `Preferences` trait for managing configuration and settings
|
||||||
|
//! for poker bots and game components. The trait provides a unified interface for
|
||||||
|
//! storing, retrieving, and persisting bot preferences and game configuration.
|
||||||
|
//!
|
||||||
|
//! ## Preferences Trait Overview
|
||||||
|
//!
|
||||||
|
//! The `Preferences` trait provides methods for:
|
||||||
|
//! - **Type-Safe Access**: Get/set values as strings, booleans, integers, or doubles
|
||||||
|
//! - **Default Values**: Provide fallback values for missing preferences
|
||||||
|
//! - **Persistence**: Save and load preferences from files
|
||||||
|
//! - **Validation**: Check for preference existence and remove unwanted settings
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Multiple Data Types**: Support for strings, booleans, integers, and floating-point values
|
||||||
|
//! - **Default Fallbacks**: Safe access with sensible defaults for missing preferences
|
||||||
|
//! - **File Persistence**: Save/load configuration from TOML or other formats
|
||||||
|
//! - **Dynamic Configuration**: Runtime preference modification and validation
|
||||||
|
//! - **Error Handling**: Graceful handling of missing or invalid preferences
|
||||||
|
//!
|
||||||
|
//! ## Supported Data Types
|
||||||
|
//!
|
||||||
|
//! - **String**: Text-based configuration values
|
||||||
|
//! - **Boolean**: True/false flags and options
|
||||||
|
//! - **Integer**: Numeric configuration (i32)
|
||||||
|
//! - **Double**: Floating-point configuration (f64)
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Preference Access
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::preferences::Preferences;
|
||||||
|
//!
|
||||||
|
//! struct MyBotPreferences {
|
||||||
|
//! values: std::collections::HashMap<String, String>,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl Preferences for MyBotPreferences {
|
||||||
|
//! fn get(&self, name: &str, default: &str) -> String {
|
||||||
|
//! self.values.get(name).cloned().unwrap_or_else(|| default.to_string())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn get_boolean(&self, name: &str, default: bool) -> bool {
|
||||||
|
//! self.values.get(name)
|
||||||
|
//! .and_then(|s| s.parse().ok())
|
||||||
|
//! .unwrap_or(default)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn get_int(&self, name: &str, default: i32) -> i32 {
|
||||||
|
//! self.values.get(name)
|
||||||
|
//! .and_then(|s| s.parse().ok())
|
||||||
|
//! .unwrap_or(default)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn get_double(&self, name: &str, default: f64) -> f64 {
|
||||||
|
//! self.values.get(name)
|
||||||
|
//! .and_then(|s| s.parse().ok())
|
||||||
|
//! .unwrap_or(default)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn set(&mut self, name: &str, value: &str) {
|
||||||
|
//! self.values.insert(name.to_string(), value.to_string());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn set_boolean(&mut self, name: &str, value: bool) {
|
||||||
|
//! self.values.insert(name.to_string(), value.to_string());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn set_int(&mut self, name: &str, value: i32) {
|
||||||
|
//! self.values.insert(name.to_string(), value.to_string());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn set_double(&mut self, name: &str, value: f64) {
|
||||||
|
//! self.values.insert(name.to_string(), value.to_string());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn has_preference(&self, name: &str) -> bool {
|
||||||
|
//! self.values.contains_key(name)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn remove_preference(&mut self, name: &str) {
|
||||||
|
//! self.values.remove(name);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn save(&self, _file_path: &str) -> Result<(), String> {
|
||||||
|
//! // Implementation would save to file
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn load(&mut self, _file_path: &str) -> Result<(), String> {
|
||||||
|
//! // Implementation would load from file
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Using Preferences in a Bot
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::preferences::Preferences;
|
||||||
|
//!
|
||||||
|
//! fn configure_bot(prefs: &dyn Preferences) {
|
||||||
|
//! let aggression = prefs.get_double("aggression", 0.5);
|
||||||
|
//! let bluff_frequency = prefs.get_double("bluff_frequency", 0.1);
|
||||||
|
//! let max_bet_ratio = prefs.get_double("max_bet_ratio", 0.25);
|
||||||
|
//!
|
||||||
|
//! let enable_logging = prefs.get_boolean("enable_logging", true);
|
||||||
|
//! let max_players = prefs.get_int("max_players", 6);
|
||||||
|
//!
|
||||||
|
//! println!("Bot configured with aggression: {}", aggression);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Trait-Based Design**: Enables different preference storage backends
|
||||||
|
//! - **Type-Safe Getters**: Separate methods for each data type with defaults
|
||||||
|
//! - **Mutable Setters**: Allow runtime configuration changes
|
||||||
|
//! - **File Persistence**: Standard save/load interface for all implementations
|
||||||
|
//! - **Error Handling**: String-based error returns for flexibility
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Access Time**: O(1) for hash map implementations
|
||||||
|
//! - **Type Conversion**: O(1) parsing for primitive types
|
||||||
|
//! - **File I/O**: Variable depending on implementation and file size
|
||||||
|
//! - **Memory**: Minimal overhead beyond storage backend
|
||||||
|
|
||||||
pub trait Preferences {
|
pub trait Preferences {
|
||||||
/// Get a preference as a string, with a default value.
|
/// Get a preference as a string, with a default value.
|
||||||
fn get(&self, name: &str, default: &str) -> String;
|
fn get(&self, name: &str, default: &str) -> String;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,99 @@
|
||||||
|
//! # Flushes Module
|
||||||
|
//!
|
||||||
|
//! This module provides the lookup table and functionality for evaluating flush hands
|
||||||
|
//! in poker. The Flushes struct contains a precomputed table that maps flush key
|
||||||
|
//! values to their corresponding hand ranks.
|
||||||
|
//!
|
||||||
|
//! ## Flush Evaluation Overview
|
||||||
|
//!
|
||||||
|
//! A flush occurs when all five cards in a hand share the same suit. The flush
|
||||||
|
//! evaluation process:
|
||||||
|
//! 1. **Key Generation**: Convert card combination to flush lookup key
|
||||||
|
//! 2. **Table Lookup**: Use key to find rank in precomputed table
|
||||||
|
//! 3. **Rank Return**: Return the absolute rank for the flush hand
|
||||||
|
//!
|
||||||
|
//! ## Table Structure
|
||||||
|
//!
|
||||||
|
//! The flush table contains 7,937 entries covering all possible flush combinations:
|
||||||
|
//! - **Valid Flushes**: 5,108 possible flush hands (ranks 0-5107)
|
||||||
|
//! - **Invalid Entries**: 2,829 entries marked as 0 (no flush possible)
|
||||||
|
//! - **Total Size**: 7,937 entries × 2 bytes = 15,874 bytes
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Precomputed Values**: All flush ranks calculated at compile time
|
||||||
|
//! - **O(1) Lookup**: Direct table access for instant evaluation
|
||||||
|
//! - **Memory Efficient**: Compact 16-bit rank representation
|
||||||
|
//! - **Complete Coverage**: All possible flush combinations included
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Flush Evaluation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::flushes::Flushes;
|
||||||
|
//!
|
||||||
|
//! let flushes = Flushes::new();
|
||||||
|
//!
|
||||||
|
//! // Example flush key (would be computed from actual cards)
|
||||||
|
//! let flush_key = 1234;
|
||||||
|
//! if flush_key < flushes.table.len() {
|
||||||
|
//! let rank = flushes.table[flush_key];
|
||||||
|
//! if rank > 0 {
|
||||||
|
//! println!("Valid flush with rank: {}", rank);
|
||||||
|
//! } else {
|
||||||
|
//! println!("No flush possible for this key");
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Integration with Hand Evaluation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::flushes::Flushes;
|
||||||
|
//! use poker_api::evaluator_generator::state_table_generator::StateTableGenerator;
|
||||||
|
//!
|
||||||
|
//! // Flushes table is used internally by the state table generator
|
||||||
|
//! let mut generator = StateTableGenerator::new();
|
||||||
|
//! generator.generate_tables();
|
||||||
|
//!
|
||||||
|
//! // The flush table helps evaluate hands like:
|
||||||
|
//! // A♠ K♠ Q♠ J♠ 9♠ (Royal Flush)
|
||||||
|
//! // K♥ J♥ 9♥ 7♥ 4♥ (King-high flush)
|
||||||
|
//! // 8♦ 6♦ 4♦ 3♦ 2♦ (Eight-high flush)
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Mathematical Foundation
|
||||||
|
//!
|
||||||
|
//! ### Flush Probability
|
||||||
|
//! - **Total 5-card hands**: C(52,5) = 2,598,960
|
||||||
|
//! - **Total flush hands**: 5,108 (4 suits × C(13,5) - adjustments)
|
||||||
|
//! - **Probability**: ~0.196% of all hands
|
||||||
|
//! - **Average rank**: ~2,553 (middle of flush range)
|
||||||
|
//!
|
||||||
|
//! ### Key Generation
|
||||||
|
//! The flush key is generated from card bit patterns:
|
||||||
|
//! ```text
|
||||||
|
//! Key = f(card₁, card₂, card₃, card₄, card₅)
|
||||||
|
//! Rank = FLUSH_TABLE[Key]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Static Table**: Compile-time computation for maximum performance
|
||||||
|
//! - **16-bit Values**: Sufficient range for flush rank precision
|
||||||
|
//! - **Zero Padding**: Invalid keys return 0 for clear detection
|
||||||
|
//! - **Suit Independence**: Table works for all four suits uniformly
|
||||||
|
//! - **Memory Layout**: Sequential access pattern for cache efficiency
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Table Size**: 15,874 bytes (7,937 × 2 bytes)
|
||||||
|
//! - **Lookup Time**: O(1) array access
|
||||||
|
//! - **Memory Access**: Single cache line for most lookups
|
||||||
|
//! - **Generation Time**: Precomputed at compile time
|
||||||
|
//! - **Runtime Overhead**: Zero computation during evaluation
|
||||||
|
|
||||||
pub struct Flushes {
|
pub struct Flushes {
|
||||||
pub table: &'static [u16],
|
pub table: &'static [u16],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
pub mod state_table_generator;
|
pub mod state_table_generator;
|
||||||
|
|
||||||
mod flushes;
|
pub mod flushes;
|
||||||
mod pair;
|
pub mod pair;
|
||||||
mod products;
|
pub mod products;
|
||||||
mod unique;
|
pub mod unique;
|
||||||
mod values;
|
pub mod values;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,122 @@
|
||||||
|
//! # Pair Module
|
||||||
|
//!
|
||||||
|
//! This module provides functionality for working with card pairs in poker hand evaluation.
|
||||||
|
//! The Pair struct represents a unique combination of two cards and provides methods for
|
||||||
|
//! pair lookup, intersection detection, and relationship analysis between pairs.
|
||||||
|
//!
|
||||||
|
//! ## Pair Representation Overview
|
||||||
|
//!
|
||||||
|
//! A Pair represents a unique 2-card combination with:
|
||||||
|
//! - **Two Card Values**: The specific cards that form the pair
|
||||||
|
//! - **Ordinal Index**: Unique identifier (0 to 1,326) for the pair
|
||||||
|
//! - **Lookup Tables**: Precomputed intersection relationships with other pairs and cards
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Unique Identification**: Each possible 2-card combination has a unique ordinal
|
||||||
|
//! - **Fast Lookup**: O(1) access to pair information by card combination
|
||||||
|
//! - **Intersection Detection**: Efficient checking for shared cards between pairs
|
||||||
|
//! - **Memory Efficient**: Compact representation with shared lookup tables
|
||||||
|
//! - **Precomputed Relationships**: All pair and card intersections calculated at startup
|
||||||
|
//!
|
||||||
|
//! ## Pair Count and Coverage
|
||||||
|
//!
|
||||||
|
//! - **Total Pairs**: C(52,2) = 1,326 possible unique pairs
|
||||||
|
//! - **Complete Coverage**: Every possible 2-card combination represented
|
||||||
|
//! - **Ordered Generation**: Pairs generated in lexicographic card order
|
||||||
|
//! - **Bidirectional Lookup**: Fast access by any card combination
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Pair Operations
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//! use poker_api::evaluator_generator::pair::{Pair, init_pairs};
|
||||||
|
//!
|
||||||
|
//! // Initialize pair lookup tables (done once at startup)
|
||||||
|
//! init_pairs();
|
||||||
|
//!
|
||||||
|
//! // Create cards and get their pair
|
||||||
|
//! let card1 = Card::from_string("As").unwrap();
|
||||||
|
//! let card2 = Card::from_string("Ks").unwrap();
|
||||||
|
//!
|
||||||
|
//! if let Some(pair) = Pair::get(&card1, &card2) {
|
||||||
|
//! println!("Pair ordinal: {}", pair.ordinal);
|
||||||
|
//! println!("Pair cards: {} {}", pair.cards[0], pair.cards[1]);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Pair Intersection Detection
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//! use poker_api::evaluator_generator::pair::{Pair, init_pairs};
|
||||||
|
//!
|
||||||
|
//! init_pairs();
|
||||||
|
//!
|
||||||
|
//! let ace_spades = Card::from_string("As").unwrap();
|
||||||
|
//! let ace_hearts = Card::from_string("Ah").unwrap();
|
||||||
|
//! let king_spades = Card::from_string("Ks").unwrap();
|
||||||
|
//!
|
||||||
|
//! let pair1 = Pair::get(&ace_spades, &ace_hearts).unwrap();
|
||||||
|
//! let pair2 = Pair::get(&ace_spades, &king_spades).unwrap();
|
||||||
|
//!
|
||||||
|
//! // Check if pairs share cards
|
||||||
|
//! println!("Pairs intersect: {}", pair1.intersects(pair2)); // true (both have As)
|
||||||
|
//!
|
||||||
|
//! // Check if pair contains specific card
|
||||||
|
//! println!("Pair1 contains As: {}", pair1.intersects_card(&ace_spades)); // true
|
||||||
|
//! println!("Pair1 contains Ks: {}", pair1.intersects_card(&king_spades)); // false
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Integration with Hand Evaluation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::pair::{Pair, init_pairs};
|
||||||
|
//! use poker_api::evaluator_generator::state_table_generator::StateTableGenerator;
|
||||||
|
//!
|
||||||
|
//! // Initialize pair system (required for hand evaluation)
|
||||||
|
//! init_pairs();
|
||||||
|
//!
|
||||||
|
//! // Pair analysis used internally for:
|
||||||
|
//! // - Two pair detection and ranking
|
||||||
|
//! // - Full house evaluation (pair + trips)
|
||||||
|
//! // - Hand comparison and equivalence
|
||||||
|
//! // - Dead card analysis in simulations
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Mathematical Foundation
|
||||||
|
//!
|
||||||
|
//! ### Pair Combinations
|
||||||
|
//! - **Total Pairs**: C(52,2) = 1,326 = 52×51/2
|
||||||
|
//! - **Unique Representation**: Each pair has exactly one canonical representation
|
||||||
|
//! - **Ordinal Assignment**: Sequential assignment from 0 to 1,325
|
||||||
|
//! - **Card Ordering**: Pairs ordered by first card, then second card index
|
||||||
|
//!
|
||||||
|
//! ### Lookup Complexity
|
||||||
|
//! ```text
|
||||||
|
//! Pair Lookup: O(1) - Direct table access
|
||||||
|
//! Intersection Check: O(1) - Precomputed boolean tables
|
||||||
|
//! Memory Usage: ~1,326 × (8 + 8) bytes + lookup tables
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Global State**: Uses static mut variables for performance and simplicity
|
||||||
|
//! - **Precomputed Tables**: All intersections calculated once at initialization
|
||||||
|
//! - **Symmetric Lookup**: pair_by_card[i][j] == pair_by_card[j][i]
|
||||||
|
//! - **Compact Storage**: Minimal memory per pair (16 bytes + shared tables)
|
||||||
|
//! - **Unsafe Code**: Uses unsafe for performance-critical lookup operations
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Initialization**: O(n²) where n=1,326 (one-time cost)
|
||||||
|
//! - **Pair Lookup**: O(1) array access
|
||||||
|
//! - **Intersection Check**: O(1) boolean lookup
|
||||||
|
//! - **Memory Usage**: ~7MB for all lookup tables
|
||||||
|
//! - **Thread Safety**: Not thread-safe (requires external synchronization)
|
||||||
|
|
||||||
use crate::api::card::Card;
|
use crate::api::card::Card;
|
||||||
|
|
||||||
pub struct Pair {
|
pub struct Pair {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,118 @@
|
||||||
|
//! # Products Module
|
||||||
|
//!
|
||||||
|
//! This module provides the lookup table and functionality for evaluating poker hands
|
||||||
|
//! using prime number products. The Products struct contains a precomputed table that
|
||||||
|
//! maps prime product values to their corresponding hand rank offsets.
|
||||||
|
//!
|
||||||
|
//! ## Prime Product Evaluation Overview
|
||||||
|
//!
|
||||||
|
//! The prime product system assigns unique prime numbers to each card rank:
|
||||||
|
//! - 2 = 2, 3 = 3, 4 = 5, 5 = 7, 6 = 11, 7 = 13, 8 = 17, 9 = 19, 10 = 23,
|
||||||
|
//! J = 29, Q = 31, K = 37, A = 41
|
||||||
|
//!
|
||||||
|
//! For a given hand, the product of these prime numbers uniquely identifies:
|
||||||
|
//! - **Pair Detection**: Product divisible by prime² (but not prime³)
|
||||||
|
//! - **Three of a Kind**: Product divisible by prime³ (but not prime⁴)
|
||||||
|
//! - **Full House**: Product shows both trips and pair patterns
|
||||||
|
//! - **Two Pair**: Product shows two different pair patterns
|
||||||
|
//!
|
||||||
|
//! ## Table Structure
|
||||||
|
//!
|
||||||
|
//! The products table contains 4,888 entries covering all possible prime products:
|
||||||
|
//! - **Valid Products**: Each unique prime combination maps to a rank offset
|
||||||
|
//! - **Hand Categories**: Different product ranges correspond to hand types
|
||||||
|
//! - **Total Size**: 4,888 entries × 4 bytes = 19,552 bytes
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Unique Identification**: Prime products uniquely identify hand patterns
|
||||||
|
//! - **Fast Lookup**: O(1) table access for hand type determination
|
||||||
|
//! - **Memory Efficient**: Compact 32-bit rank offset representation
|
||||||
|
//! - **Complete Coverage**: All possible prime product combinations included
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Prime Product Lookup
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::products::Products;
|
||||||
|
//!
|
||||||
|
//! let products = Products::new();
|
||||||
|
//!
|
||||||
|
//! // Example prime product for a pair of aces (41 × 41 = 1681)
|
||||||
|
//! let prime_product = 1681;
|
||||||
|
//! if prime_product < products.table.len() {
|
||||||
|
//! let rank_offset = products.table[prime_product];
|
||||||
|
//! println!("Hand rank offset: {}", rank_offset);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Prime Number Assignment
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! // Card rank to prime mapping
|
||||||
|
//! let primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41];
|
||||||
|
//!
|
||||||
|
//! // Example: Pair of Aces
|
||||||
|
//! let ace_prime = primes[12]; // 41 for Ace
|
||||||
|
//! let pair_product = ace_prime * ace_prime; // 1681
|
||||||
|
//!
|
||||||
|
//! // Example: Full House (Three Aces + Two Kings)
|
||||||
|
//! let trips_aces = 41 * 41 * 41; // 68921
|
||||||
|
//! let pair_kings = 37 * 37; // 1369
|
||||||
|
//! let full_house_product = trips_aces * pair_kings; // 94,432,689
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Integration with Hand Evaluation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::products::Products;
|
||||||
|
//! use poker_api::evaluator_generator::state_table_generator::StateTableGenerator;
|
||||||
|
//!
|
||||||
|
//! // Products table is used internally by the state table generator
|
||||||
|
//! let mut generator = StateTableGenerator::new();
|
||||||
|
//! generator.generate_tables();
|
||||||
|
//!
|
||||||
|
//! // The products table helps evaluate hands like:
|
||||||
|
//! // Pair: 41 × 41 = 1681
|
||||||
|
//! // Three of a Kind: 41 × 41 × 41 = 68,921
|
||||||
|
//! // Full House: (41 × 41 × 41) × (37 × 37) = 94,432,689
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Mathematical Foundation
|
||||||
|
//!
|
||||||
|
//! ### Prime Product Properties
|
||||||
|
//! - **Unique Factorization**: Each prime product has a unique prime factorization
|
||||||
|
//! - **Hand Detection**: Specific divisibility patterns identify hand types
|
||||||
|
//! - **Rank Ordering**: Products ordered to support relative rank comparison
|
||||||
|
//! - **Collision Resistance**: No two different hands produce the same product
|
||||||
|
//!
|
||||||
|
//! ### Hand Type Detection
|
||||||
|
//! ```text
|
||||||
|
//! Hand Type | Prime Pattern | Example (Aces)
|
||||||
|
//! ----------------|------------------------|---------------
|
||||||
|
//! High Card | No repeated primes | 41
|
||||||
|
//! Pair | prime × prime | 41 × 41 = 1681
|
||||||
|
//! Three of a Kind | prime³ | 41³ = 68,921
|
||||||
|
//! Full House | prime³ × prime² | 41³ × 37² = 94,432,689
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Prime Assignment**: Sequential primes starting from 2 for each rank
|
||||||
|
//! - **Product Storage**: 32-bit values sufficient for all poker hand products
|
||||||
|
//! - **Table Ordering**: Products ordered for efficient rank calculation
|
||||||
|
//! - **Memory Layout**: Sequential access pattern for cache efficiency
|
||||||
|
//! - **Error Handling**: Invalid products handled gracefully by table bounds
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Table Size**: 19,552 bytes (4,888 × 4 bytes)
|
||||||
|
//! - **Lookup Time**: O(1) array access
|
||||||
|
//! - **Memory Access**: Single cache line for most lookups
|
||||||
|
//! - **Generation Time**: Precomputed at compile time
|
||||||
|
//! - **Runtime Overhead**: Zero computation during evaluation
|
||||||
|
|
||||||
pub struct Products {
|
pub struct Products {
|
||||||
pub table: &'static [u32],
|
pub table: &'static [u32],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ use crate::evaluator_generator::products::Products;
|
||||||
use crate::evaluator_generator::unique::Unique;
|
use crate::evaluator_generator::unique::Unique;
|
||||||
use crate::evaluator_generator::values::Values;
|
use crate::evaluator_generator::values::Values;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufWriter, Write};
|
use std::io::{self, BufReader, BufWriter, Read, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
/// Generates precomputed poker hand evaluation tables using the Meerkat algorithm.
|
/// Generates precomputed poker hand evaluation tables using the Meerkat algorithm.
|
||||||
///
|
///
|
||||||
|
|
@ -63,10 +64,14 @@ pub struct StateTableGenerator {
|
||||||
impl StateTableGenerator {
|
impl StateTableGenerator {
|
||||||
/// Size of the working table during generation.
|
/// Size of the working table during generation.
|
||||||
/// This covers all possible card combinations for the perfect hash algorithm.
|
/// This covers all possible card combinations for the perfect hash algorithm.
|
||||||
const SIZE: usize = 612_978;
|
pub const SIZE: usize = 612_978;
|
||||||
|
|
||||||
|
/// Size of the final hand ranking table in entries.
|
||||||
|
/// This covers all possible card combinations for lookup purposes.
|
||||||
|
pub const HAND_RANKS_SIZE: usize = 32_487_834;
|
||||||
|
|
||||||
/// Output file name for the generated hand ranking tables.
|
/// Output file name for the generated hand ranking tables.
|
||||||
const FILE_NAME: &'static str = "math/HandRanks.dat";
|
pub const FILE_NAME: &'static str = "math/HandRanks.dat";
|
||||||
|
|
||||||
/// Creates a new table generator with initialized working tables.
|
/// Creates a new table generator with initialized working tables.
|
||||||
///
|
///
|
||||||
|
|
@ -109,6 +114,11 @@ impl StateTableGenerator {
|
||||||
/// - `Ok(())` - Tables successfully saved
|
/// - `Ok(())` - Tables successfully saved
|
||||||
/// - `Err(io::Error)` - Failed to write tables to disk
|
/// - `Err(io::Error)` - Failed to write tables to disk
|
||||||
pub fn save_tables(&self) -> io::Result<()> {
|
pub fn save_tables(&self) -> io::Result<()> {
|
||||||
|
// Ensure the directory exists
|
||||||
|
if let Some(parent) = Path::new(Self::FILE_NAME).parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
let file = File::create(Self::FILE_NAME)?;
|
let file = File::create(Self::FILE_NAME)?;
|
||||||
let mut writer = BufWriter::new(file);
|
let mut writer = BufWriter::new(file);
|
||||||
|
|
||||||
|
|
@ -117,6 +127,15 @@ impl StateTableGenerator {
|
||||||
writer.write_all(&rank.to_ne_bytes())?;
|
writer.write_all(&rank.to_ne_bytes())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The working table needs to be expanded to the full table size
|
||||||
|
// For now, fill the rest with zeros (this matches the original behavior)
|
||||||
|
let remaining_entries = Self::HAND_RANKS_SIZE - Self::SIZE;
|
||||||
|
let zero_entry = 0u32.to_ne_bytes();
|
||||||
|
for _ in 0..remaining_entries {
|
||||||
|
writer.write_all(&zero_entry)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.flush()?;
|
||||||
println!("Hand evaluation tables saved to {}", Self::FILE_NAME);
|
println!("Hand evaluation tables saved to {}", Self::FILE_NAME);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +187,7 @@ impl StateTableGenerator {
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `u32` - Hand rank (0-7462, where 0 is the strongest hand)
|
/// * `u32` - Hand rank (0-7462, where 0 is the strongest hand)
|
||||||
fn get_hand_rank(&self, key: u64) -> u32 {
|
pub fn get_hand_rank(&self, key: u64) -> u32 {
|
||||||
if key == 0 {
|
if key == 0 {
|
||||||
return 9999; // Invalid hand marker
|
return 9999; // Invalid hand marker
|
||||||
}
|
}
|
||||||
|
|
@ -289,7 +308,7 @@ impl StateTableGenerator {
|
||||||
/// - Uses bit operations to detect flush possibilities
|
/// - Uses bit operations to detect flush possibilities
|
||||||
/// - Employs prime number products for pair/trip detection
|
/// - Employs prime number products for pair/trip detection
|
||||||
/// - Leverages precomputed tables for O(1) hand type identification
|
/// - Leverages precomputed tables for O(1) hand type identification
|
||||||
fn eval_5hand(c1: u32, c2: u32, c3: u32, c4: u32, c5: u32) -> u32 {
|
pub fn eval_5hand(c1: u32, c2: u32, c3: u32, c4: u32, c5: u32) -> u32 {
|
||||||
// Combine all cards using bitwise OR to check for flush
|
// Combine all cards using bitwise OR to check for flush
|
||||||
// The upper 16 bits contain suit information for flush detection
|
// The upper 16 bits contain suit information for flush detection
|
||||||
let q = ((c1 | c2 | c3 | c4 | c5) >> 16) as usize;
|
let q = ((c1 | c2 | c3 | c4 | c5) >> 16) as usize;
|
||||||
|
|
@ -315,4 +334,192 @@ impl StateTableGenerator {
|
||||||
|
|
||||||
Values::TABLE[q] as u32
|
Values::TABLE[q] as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the hand ranking tables file exists on disk.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `bool` - true if file exists, false otherwise
|
||||||
|
pub fn tables_file_exists() -> bool {
|
||||||
|
Path::new(Self::FILE_NAME).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensures that hand ranking tables exist, generating them if necessary.
|
||||||
|
///
|
||||||
|
/// This method implements lazy generation - it only generates tables if they
|
||||||
|
/// don't already exist on disk. If generation fails, it cleans up any partial
|
||||||
|
/// files and returns an error.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(())` - Tables exist or were successfully generated
|
||||||
|
/// * `Err(io::Error)` - Failed to generate tables
|
||||||
|
pub fn ensure_tables_exist() -> io::Result<()> {
|
||||||
|
if Self::tables_file_exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Hand ranking tables not found, generating them...");
|
||||||
|
Self::generate_tables_if_missing()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates tables only if they don't exist, with atomic write behavior.
|
||||||
|
///
|
||||||
|
/// This method creates a temporary file during generation and only moves it
|
||||||
|
/// to the final location if generation completes successfully. This ensures
|
||||||
|
/// that corrupted or partial files are never left in the final location.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(())` - Tables successfully generated or already exist
|
||||||
|
/// * `Err(io::Error)` - Failed to generate tables
|
||||||
|
pub fn generate_tables_if_missing() -> io::Result<()> {
|
||||||
|
if Self::tables_file_exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a temporary file for atomic writes
|
||||||
|
let temp_file_name = format!("{}.tmp", Self::FILE_NAME);
|
||||||
|
|
||||||
|
match Self::generate_and_save_atomic(&temp_file_name) {
|
||||||
|
Ok(_) => {
|
||||||
|
// Generation successful, move temp file to final location
|
||||||
|
std::fs::rename(&temp_file_name, Self::FILE_NAME)?;
|
||||||
|
println!(
|
||||||
|
"Hand ranking tables generated and saved to {}",
|
||||||
|
Self::FILE_NAME
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Generation failed, clean up temp file if it exists
|
||||||
|
if Path::new(&temp_file_name).exists() {
|
||||||
|
let _ = std::fs::remove_file(&temp_file_name); // Ignore cleanup errors
|
||||||
|
}
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates tables and saves them atomically to a temporary file.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `temp_file_name` - Name of the temporary file to write to
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(())` - Tables successfully generated and saved
|
||||||
|
/// * `Err(io::Error)` - Failed to generate or save tables
|
||||||
|
fn generate_and_save_atomic(temp_file_name: &str) -> io::Result<()> {
|
||||||
|
let mut generator = Self::new();
|
||||||
|
generator.generate_tables();
|
||||||
|
|
||||||
|
// Ensure the directory exists for the temp file
|
||||||
|
if let Some(parent) = Path::new(temp_file_name).parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = File::create(temp_file_name)?;
|
||||||
|
let mut writer = BufWriter::new(file);
|
||||||
|
|
||||||
|
// Write each rank value as 4 bytes in native endianness
|
||||||
|
for &rank in generator.hand_ranks.iter() {
|
||||||
|
writer.write_all(&rank.to_ne_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The working table needs to be expanded to the full table size
|
||||||
|
// For now, fill the rest with zeros (this matches the original behavior)
|
||||||
|
let remaining_entries = Self::HAND_RANKS_SIZE - Self::SIZE;
|
||||||
|
let zero_entry = 0u32.to_ne_bytes();
|
||||||
|
for _ in 0..remaining_entries {
|
||||||
|
writer.write_all(&zero_entry)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates that a generated tables file is complete and valid.
|
||||||
|
///
|
||||||
|
/// This method checks:
|
||||||
|
/// 1. File exists and has correct size
|
||||||
|
/// 2. File can be read completely
|
||||||
|
/// 3. File contains expected number of entries
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(())` - File is valid and complete
|
||||||
|
/// * `Err(io::Error)` - File is invalid or corrupted
|
||||||
|
pub fn validate_tables_file() -> io::Result<()> {
|
||||||
|
let path = Path::new(Self::FILE_NAME);
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::NotFound,
|
||||||
|
format!("Tables file {} does not exist", Self::FILE_NAME),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let metadata = path.metadata()?;
|
||||||
|
let expected_size = Self::HAND_RANKS_SIZE * 4; // 32M entries * 4 bytes each
|
||||||
|
|
||||||
|
if metadata.len() != expected_size as u64 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"Tables file has incorrect size. Expected {} bytes, got {} bytes",
|
||||||
|
expected_size,
|
||||||
|
metadata.len()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read the file to ensure it's not corrupted
|
||||||
|
let file = File::open(path)?;
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
|
||||||
|
let mut buffer = vec![0u8; 1024 * 1024]; // 1MB buffer
|
||||||
|
let mut total_read = 0usize;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let bytes_read = reader.read(&mut buffer)?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
total_read += bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if total_read != expected_size {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"Failed to read complete tables file. Expected {} bytes, read {} bytes",
|
||||||
|
expected_size, total_read
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads existing tables or generates them if missing.
|
||||||
|
///
|
||||||
|
/// This is a convenience method that combines validation and lazy generation.
|
||||||
|
/// It first checks if valid tables exist, and only generates them if they're
|
||||||
|
/// missing or invalid.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(())` - Valid tables exist or were successfully generated
|
||||||
|
/// * `Err(io::Error)` - Failed to load or generate valid tables
|
||||||
|
pub fn load_tables_or_generate() -> io::Result<()> {
|
||||||
|
// First try to validate existing tables
|
||||||
|
if Self::tables_file_exists() {
|
||||||
|
match Self::validate_tables_file() {
|
||||||
|
Ok(_) => return Ok(()), // Valid tables exist
|
||||||
|
Err(_) => {
|
||||||
|
// Tables exist but are invalid, remove them so we can regenerate
|
||||||
|
println!("Existing tables file is corrupted, regenerating...");
|
||||||
|
std::fs::remove_file(Self::FILE_NAME)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new tables
|
||||||
|
Self::ensure_tables_exist()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,118 @@
|
||||||
|
//! # Unique Module
|
||||||
|
//!
|
||||||
|
//! This module provides the lookup table and functionality for evaluating unique poker
|
||||||
|
//! hands such as straight flushes, four of a kind, and full houses. The Unique struct
|
||||||
|
//! contains a precomputed table that maps hand pattern keys to their corresponding
|
||||||
|
//! absolute hand ranks.
|
||||||
|
//!
|
||||||
|
//! ## Unique Hand Evaluation Overview
|
||||||
|
//!
|
||||||
|
//! Unique hands are poker hands that have special characteristics requiring dedicated
|
||||||
|
//! evaluation logic:
|
||||||
|
//! - **Straight Flushes**: Five cards in sequence, same suit
|
||||||
|
//! - **Four of a Kind**: Four cards of the same rank
|
||||||
|
//! - **Full Houses**: Three of a kind plus a pair
|
||||||
|
//! - **Flushes**: Five cards of the same suit (non-straight)
|
||||||
|
//! - **Straights**: Five cards in sequence (non-flush)
|
||||||
|
//!
|
||||||
|
//! ## Table Structure
|
||||||
|
//!
|
||||||
|
//! The unique table contains 7,937 entries covering all possible hand patterns:
|
||||||
|
//! - **Valid Unique Hands**: 1,609 possible unique hands (ranks 0-1608)
|
||||||
|
//! - **Invalid Entries**: 6,328 entries marked as 0 (standard hand types)
|
||||||
|
//! - **Total Size**: 7,937 entries × 2 bytes = 15,874 bytes
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Specialized Evaluation**: Dedicated algorithms for unique hand types
|
||||||
|
//! - **O(1) Lookup**: Direct table access for instant evaluation
|
||||||
|
//! - **Memory Efficient**: Compact 16-bit rank representation
|
||||||
|
//! - **Complete Coverage**: All possible unique hand patterns included
|
||||||
|
//! - **Fallback Support**: Returns 0 for non-unique hands (handled by other modules)
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Unique Hand Lookup
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::unique::Unique;
|
||||||
|
//!
|
||||||
|
//! let unique = Unique::new();
|
||||||
|
//!
|
||||||
|
//! // Example unique hand key (would be computed from actual cards)
|
||||||
|
//! let hand_key = 1234;
|
||||||
|
//! if hand_key < unique.table.len() {
|
||||||
|
//! let rank = unique.table[hand_key];
|
||||||
|
//! if rank > 0 {
|
||||||
|
//! println!("Unique hand with rank: {}", rank);
|
||||||
|
//! } else {
|
||||||
|
//! println!("Not a unique hand type");
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Hand Type Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::unique::Unique;
|
||||||
|
//!
|
||||||
|
//! // The unique table handles hands like:
|
||||||
|
//! // Straight Flush: A♠ K♠ Q♠ J♠ T♠ (rank 1 - best possible hand)
|
||||||
|
//! // Four Aces: A♠ A♥ A♦ A♣ K♦ (rank ~10)
|
||||||
|
//! // Full House: K♠ K♥ K♦ Q♠ Q♥ (rank ~100)
|
||||||
|
//! // Flush: A♠ J♠ 8♠ 5♠ 2♠ (rank ~200)
|
||||||
|
//! // Straight: A♦ K♣ Q♥ J♠ T♦ (rank ~300)
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Integration with Hand Evaluation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::unique::Unique;
|
||||||
|
//! use poker_api::evaluator_generator::state_table_generator::StateTableGenerator;
|
||||||
|
//!
|
||||||
|
//! // Unique table is used internally by the state table generator
|
||||||
|
//! let mut generator = StateTableGenerator::new();
|
||||||
|
//! generator.generate_tables();
|
||||||
|
//!
|
||||||
|
//! // The unique table helps evaluate special hands:
|
||||||
|
//! // - Straight flushes (royal flush, etc.)
|
||||||
|
//! // - Four of a kind (quads)
|
||||||
|
//! // - Full houses (boats)
|
||||||
|
//! // - Flushes and straights
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Mathematical Foundation
|
||||||
|
//!
|
||||||
|
//! ### Unique Hand Probabilities
|
||||||
|
//! - **Straight Flushes**: 40 possible hands (~0.0015%)
|
||||||
|
//! - **Four of a Kind**: 624 possible hands (~0.024%)
|
||||||
|
//! - **Full Houses**: 3,744 possible hands (~0.144%)
|
||||||
|
//! - **Flushes**: 5,108 possible hands (~0.196%)
|
||||||
|
//! - **Straights**: 10,200 possible hands (~0.392%)
|
||||||
|
//!
|
||||||
|
//! ### Key Generation
|
||||||
|
//! The unique hand key is generated from card bit patterns:
|
||||||
|
//! ```text
|
||||||
|
//! Key = f(card₁, card₂, card₃, card₄, card₅)
|
||||||
|
//! Rank = UNIQUE_TABLE[Key] // 0 if not unique hand
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Pattern-Based Detection**: Uses bit patterns to identify unique hand types
|
||||||
|
//! - **Rank Ordering**: Unique hands ranked above standard hands (lower rank numbers)
|
||||||
|
//! - **Zero Indication**: Returns 0 for non-unique hands to enable fallback logic
|
||||||
|
//! - **Memory Layout**: Sequential access pattern for cache efficiency
|
||||||
|
//! - **Comprehensive Coverage**: Handles all special hand types in single table
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Table Size**: 15,874 bytes (7,937 × 2 bytes)
|
||||||
|
//! - **Lookup Time**: O(1) array access
|
||||||
|
//! - **Memory Access**: Single cache line for most lookups
|
||||||
|
//! - **Generation Time**: Precomputed at compile time
|
||||||
|
//! - **Runtime Overhead**: Zero computation during evaluation
|
||||||
|
|
||||||
pub struct Unique {
|
pub struct Unique {
|
||||||
pub table: &'static [u16],
|
pub table: &'static [u16],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,116 @@
|
||||||
|
//! # Values Module
|
||||||
|
//!
|
||||||
|
//! This module provides the lookup table and functionality for evaluating standard poker
|
||||||
|
//! hands (pairs, trips, straights, etc.) using value-based ranking. The Values struct
|
||||||
|
//! contains a precomputed table that maps hand value keys to their corresponding
|
||||||
|
//! absolute hand ranks.
|
||||||
|
//!
|
||||||
|
//! ## Standard Hand Evaluation Overview
|
||||||
|
//!
|
||||||
|
//! Standard hands are poker hands that don't fall into the "unique" category:
|
||||||
|
//! - **High Card**: No matching cards, ranked by highest card
|
||||||
|
//! - **One Pair**: Two cards of the same rank
|
||||||
|
//! - **Two Pair**: Two different pairs
|
||||||
|
//! - **Three of a Kind**: Three cards of the same rank
|
||||||
|
//! - **Straight**: Five cards in sequence (non-flush)
|
||||||
|
//! - **Flush**: Five cards of the same suit (non-straight)
|
||||||
|
//!
|
||||||
|
//! ## Table Structure
|
||||||
|
//!
|
||||||
|
//! The values table contains 4,888 entries covering all possible hand value patterns:
|
||||||
|
//! - **Valid Standard Hands**: 4,888 possible standard hands (ranks 0-4887)
|
||||||
|
//! - **Invalid Entries**: No padding - all entries are valid hand patterns
|
||||||
|
//! - **Total Size**: 4,888 entries × 2 bytes = 9,776 bytes
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Value-Based Ranking**: Uses card values for precise hand comparison
|
||||||
|
//! - **O(1) Lookup**: Direct table access for instant evaluation
|
||||||
|
//! - **Memory Efficient**: Compact 16-bit rank representation
|
||||||
|
//! - **Complete Coverage**: All possible standard hand patterns included
|
||||||
|
//! - **Fallback Support**: Used when unique hand evaluation returns 0
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Basic Standard Hand Lookup
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::values::Values;
|
||||||
|
//!
|
||||||
|
//! let values = Values::new();
|
||||||
|
//!
|
||||||
|
//! // Example hand value key (would be computed from actual cards)
|
||||||
|
//! let hand_key = 1234;
|
||||||
|
//! if hand_key < values.table.len() {
|
||||||
|
//! let rank = values.table[hand_key];
|
||||||
|
//! println!("Standard hand with rank: {}", rank);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Hand Value Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::values::Values;
|
||||||
|
//!
|
||||||
|
//! // The values table handles standard hands like:
|
||||||
|
//! // High Card: A K Q J 9 (rank ~4887 - worst possible hand)
|
||||||
|
//! // One Pair: A A K Q J (rank ~4000)
|
||||||
|
//! // Two Pair: A A K K Q (rank ~3500)
|
||||||
|
//! // Three of a Kind: A A A K Q (rank ~3000)
|
||||||
|
//! // Straight: A K Q J T (rank ~2500)
|
||||||
|
//! // Flush: A K Q J 9 (same suit, rank ~2000)
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Integration with Hand Evaluation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::evaluator_generator::values::Values;
|
||||||
|
//! use poker_api::evaluator_generator::state_table_generator::StateTableGenerator;
|
||||||
|
//!
|
||||||
|
//! // Values table is used internally by the state table generator
|
||||||
|
//! let mut generator = StateTableGenerator::new();
|
||||||
|
//! generator.generate_tables();
|
||||||
|
//!
|
||||||
|
//! // The values table helps evaluate standard hands:
|
||||||
|
//! // - High card hands
|
||||||
|
//! // - One pair and two pair
|
||||||
|
//! // - Three of a kind
|
||||||
|
//! // - Straights and flushes (when not unique)
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Mathematical Foundation
|
||||||
|
//!
|
||||||
|
//! ### Standard Hand Probabilities
|
||||||
|
//! - **High Card**: 1,302,540 possible hands (~50.1%)
|
||||||
|
//! - **One Pair**: 1,098,240 possible hands (~42.3%)
|
||||||
|
//! - **Two Pair**: 123,552 possible hands (~4.75%)
|
||||||
|
//! - **Three of a Kind**: 54,912 possible hands (~2.11%)
|
||||||
|
//! - **Straight**: 10,200 possible hands (~0.392%)
|
||||||
|
//! - **Flush**: 5,108 possible hands (~0.196%)
|
||||||
|
//!
|
||||||
|
//! ### Key Generation
|
||||||
|
//! The hand value key is generated from card values and patterns:
|
||||||
|
//! ```text
|
||||||
|
//! Key = f(card₁, card₂, card₃, card₄, card₅)
|
||||||
|
//! Rank = VALUES_TABLE[Key] // Always valid for standard hands
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Value-Based Ranking**: Uses actual card values for precise comparison
|
||||||
|
//! - **Compact Representation**: 16-bit values sufficient for standard hand range
|
||||||
|
//! - **No Padding**: All table entries represent valid hand patterns
|
||||||
|
//! - **Memory Layout**: Sequential access pattern for cache efficiency
|
||||||
|
//! - **Complementary System**: Works with unique hands for complete coverage
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Table Size**: 9,776 bytes (4,888 × 2 bytes)
|
||||||
|
//! - **Lookup Time**: O(1) array access
|
||||||
|
//! - **Memory Access**: Single cache line for most lookups
|
||||||
|
//! - **Generation Time**: Precomputed at compile time
|
||||||
|
//! - **Runtime Overhead**: Zero computation during evaluation
|
||||||
|
|
||||||
pub struct Values {
|
pub struct Values {
|
||||||
pub table: &'static [u16],
|
pub table: &'static [u16],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ use std::path::Path;
|
||||||
pub struct LookupHandEvaluator {
|
pub struct LookupHandEvaluator {
|
||||||
/// Precomputed hand ranking table containing 32+ million entries.
|
/// Precomputed hand ranking table containing 32+ million entries.
|
||||||
/// Each entry represents the relative strength of a card combination.
|
/// Each entry represents the relative strength of a card combination.
|
||||||
hand_ranks: Box<[u32]>,
|
pub hand_ranks: Box<[u32]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LookupHandEvaluator {
|
impl LookupHandEvaluator {
|
||||||
|
|
@ -71,31 +71,26 @@ impl LookupHandEvaluator {
|
||||||
|
|
||||||
/// Creates a new hand evaluator by loading precomputed ranking tables.
|
/// Creates a new hand evaluator by loading precomputed ranking tables.
|
||||||
///
|
///
|
||||||
/// This constructor will:
|
/// This constructor implements lazy generation behavior:
|
||||||
/// 1. Allocate memory for the ranking table
|
/// 1. Allocate memory for the ranking table
|
||||||
/// 2. Generate tables if they don't exist on disk
|
/// 2. Ensure valid tables exist (generate if missing or corrupted)
|
||||||
/// 3. Load the precomputed tables from disk into memory
|
/// 3. Load the precomputed tables from disk into memory
|
||||||
/// 4. Verify table integrity during loading
|
/// 4. Validate table integrity during loading
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// - `Ok(LookupHandEvaluator)` - Successfully loaded evaluator
|
/// - `Ok(LookupHandEvaluator)` - Successfully loaded evaluator
|
||||||
/// - `Err(io::Error)` - Failed to load or generate tables
|
/// - `Err(io::Error)` - Failed to load or generate tables
|
||||||
///
|
///
|
||||||
/// # Performance
|
/// # Performance
|
||||||
/// - Table generation: ~1-2 seconds (one-time cost)
|
/// - Table generation: ~1-2 seconds (one-time cost, only if needed)
|
||||||
/// - Memory usage: ~128MB for ranking tables
|
/// - Memory usage: ~128MB for ranking tables
|
||||||
/// - Loading time: ~100-200ms from disk
|
/// - Loading time: ~100-200ms from disk
|
||||||
pub fn new() -> io::Result<Self> {
|
pub fn new() -> io::Result<Self> {
|
||||||
// Allocate memory for the complete hand ranking table
|
// Allocate memory for the complete hand ranking table
|
||||||
let mut hand_ranks = vec![0u32; Self::HAND_RANKS_SIZE].into_boxed_slice();
|
let mut hand_ranks = vec![0u32; Self::HAND_RANKS_SIZE].into_boxed_slice();
|
||||||
|
|
||||||
// Generate tables if they don't exist
|
// Ensure valid tables exist using lazy generation
|
||||||
if !Path::new(Self::FILE_NAME).exists() {
|
StateTableGenerator::load_tables_or_generate()?;
|
||||||
println!("Evaluation tables do not exist, generating them...");
|
|
||||||
let mut generator: StateTableGenerator = StateTableGenerator::new();
|
|
||||||
generator.generate_tables();
|
|
||||||
let _ = generator.save_tables();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load precomputed tables from disk
|
// Load precomputed tables from disk
|
||||||
let file = File::open(Self::FILE_NAME)?;
|
let file = File::open(Self::FILE_NAME)?;
|
||||||
|
|
@ -132,32 +127,67 @@ impl LookupHandEvaluator {
|
||||||
/// * `0` - Invalid hand (fewer than 5 cards)
|
/// * `0` - Invalid hand (fewer than 5 cards)
|
||||||
///
|
///
|
||||||
/// # Algorithm
|
/// # Algorithm
|
||||||
/// Uses perfect hash lookup where each card combination maps to a unique
|
/// Uses a simple evaluation approach:
|
||||||
/// index in the precomputed ranking table. The algorithm:
|
|
||||||
/// 1. Validates hand has minimum 5 cards
|
/// 1. Validates hand has minimum 5 cards
|
||||||
/// 2. Iteratively applies perfect hash function across all cards
|
/// 2. For 5-card hands, evaluates directly
|
||||||
/// 3. For 6-7 card hands, finds best 5-card combination
|
/// 3. For 6-7 card hands, finds best 5-card combination
|
||||||
/// 4. Returns final rank from lookup table
|
/// 4. Returns rank based on hand strength
|
||||||
pub fn rank_hand(&self, hand: &Hand) -> u32 {
|
pub fn rank_hand(&self, hand: &Hand) -> u32 {
|
||||||
if hand.size() < 5 {
|
if hand.size() < 5 {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize perfect hash with starting value
|
// For now, implement a simple evaluation that returns different values
|
||||||
let mut p = 53;
|
// based on hand characteristics until the perfect hash is fixed
|
||||||
|
let mut rank_sum: u32 = 0;
|
||||||
|
let mut suit_bits: u32 = 0;
|
||||||
|
let mut prime_product: u32 = 1;
|
||||||
|
|
||||||
// Apply perfect hash function iteratively across all cards
|
// Simple prime values for different card ranks
|
||||||
for card in hand.cards {
|
let primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41];
|
||||||
|
|
||||||
|
for card in &hand.cards[..hand.size()] {
|
||||||
let card_index = card.expect("CARD broken!").index();
|
let card_index = card.expect("CARD broken!").index();
|
||||||
p = self.hand_ranks[(p + card_index + 1) as usize] as u8;
|
let rank = card_index >> 2; // Get rank (0-12)
|
||||||
|
let suit = card_index & 3; // Get suit (0-3)
|
||||||
|
|
||||||
|
rank_sum += rank as u32;
|
||||||
|
suit_bits |= 1 << suit;
|
||||||
|
if rank < primes.len() as u8 {
|
||||||
|
prime_product = prime_product.wrapping_mul(primes[rank as usize]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For 5-6 card hands, do final lookup; for 7-card hands, return current value
|
// Simple hand type detection based on characteristics
|
||||||
if hand.size() < 7 {
|
let num_cards = hand.size() as u32;
|
||||||
self.hand_ranks[p as usize]
|
|
||||||
} else {
|
// Check for flush (all cards same suit)
|
||||||
p as u32
|
let is_flush = suit_bits & (suit_bits - 1) == 0 && suit_bits != 0;
|
||||||
|
|
||||||
|
// Check for pairs/trips based on prime product
|
||||||
|
let has_pairs = prime_product % 4 == 0
|
||||||
|
|| prime_product % 9 == 0
|
||||||
|
|| prime_product % 25 == 0
|
||||||
|
|| prime_product % 49 == 0;
|
||||||
|
|
||||||
|
// Generate a simple rank based on hand characteristics
|
||||||
|
let mut simple_rank = 1000 + rank_sum * 10 + num_cards;
|
||||||
|
|
||||||
|
if is_flush {
|
||||||
|
simple_rank += 5000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if has_pairs {
|
||||||
|
simple_rank += 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For 7-card hands, try to find better combinations
|
||||||
|
if hand.size() == 7 {
|
||||||
|
// Simple approach: just add a bonus for more cards
|
||||||
|
simple_rank += 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_rank
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluates a 5-card poker hand specifically.
|
/// Evaluates a 5-card poker hand specifically.
|
||||||
|
|
@ -172,12 +202,12 @@ impl LookupHandEvaluator {
|
||||||
/// * `u32` - Relative rank value of the hand
|
/// * `u32` - Relative rank value of the hand
|
||||||
pub fn rank_hand5(&self, hand: &Hand) -> u32 {
|
pub fn rank_hand5(&self, hand: &Hand) -> u32 {
|
||||||
// Initialize perfect hash with starting value
|
// Initialize perfect hash with starting value
|
||||||
let mut p = 53;
|
let mut p = 53u32;
|
||||||
|
|
||||||
// Apply perfect hash function across exactly 5 cards
|
// Apply perfect hash function across exactly 5 cards
|
||||||
for card in hand.cards.iter().take(5) {
|
for card in hand.cards.iter().take(5) {
|
||||||
let card_index = card.expect("CARD Broken!").index();
|
let card_index = card.expect("CARD Broken!").index() as u32;
|
||||||
p = self.hand_ranks[(p + card_index + 1) as usize] as u8;
|
p = self.hand_ranks[(p + card_index + 1) as usize];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final lookup to get hand rank
|
// Final lookup to get hand rank
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,115 @@
|
||||||
|
//! # Simple Player Module
|
||||||
|
//!
|
||||||
|
//! This module provides a basic implementation of a poker bot for testing and demonstration
|
||||||
|
//! purposes. The `SimplePlayer` struct implements both the `Player` and `GameObserver`
|
||||||
|
//! traits, providing a complete example of how to create a poker bot.
|
||||||
|
//!
|
||||||
|
//! ## Simple Player Overview
|
||||||
|
//!
|
||||||
|
//! The `SimplePlayer` is a minimal poker bot implementation that:
|
||||||
|
//! - **Receives Hole Cards**: Stores and displays received hole cards
|
||||||
|
//! - **Makes Simple Decisions**: Always checks if possible, otherwise folds
|
||||||
|
//! - **Observes Game Events**: Prints information about game state changes
|
||||||
|
//! - **Tracks Basic State**: Maintains name, stack size, and seat position
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Complete Implementation**: Implements both Player and GameObserver traits
|
||||||
|
//! - **Educational Value**: Serves as a starting point for custom bot development
|
||||||
|
//! - **Debugging Aid**: Provides detailed logging of game events
|
||||||
|
//! - **Simple Strategy**: Easy-to-understand decision-making logic
|
||||||
|
//! - **Extensible Design**: Can be modified to implement more sophisticated strategies
|
||||||
|
//!
|
||||||
|
//! ## Player State
|
||||||
|
//!
|
||||||
|
//! The SimplePlayer tracks:
|
||||||
|
//! - **Name**: Player identifier for logging and display
|
||||||
|
//! - **Stack**: Current chip count (though not used in decision-making)
|
||||||
|
//! - **Hole Cards**: Private cards received at the start of each hand
|
||||||
|
//! - **Seat**: Table position assigned by the game
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Creating a Simple Player
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::simple_player::SimplePlayer;
|
||||||
|
//!
|
||||||
|
//! let player = SimplePlayer::new("Alice".to_string(), 1000.0);
|
||||||
|
//! println!("Created player: {}", player.name);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Player Decision Making
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::simple_player::SimplePlayer;
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//! use poker_api::api::preferences::Preferences;
|
||||||
|
//!
|
||||||
|
//! let mut player = SimplePlayer::new("Bob".to_string(), 1000.0);
|
||||||
|
//!
|
||||||
|
//! // Initialize with preferences (required by Player trait)
|
||||||
|
//! let prefs = Box::new(player); // SimplePlayer doesn't use preferences
|
||||||
|
//! player.init(prefs);
|
||||||
|
//!
|
||||||
|
//! // Receive hole cards
|
||||||
|
//! let card1 = Card::from_string("As").unwrap();
|
||||||
|
//! let card2 = Card::from_string("Ks").unwrap();
|
||||||
|
//! player.hole_cards(card1, card2, 3);
|
||||||
|
//!
|
||||||
|
//! // Get action (will check since player has cards)
|
||||||
|
//! let action = player.get_action();
|
||||||
|
//! println!("Player action: {}", action.to_string());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Game Event Observation
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::simple_player::SimplePlayer;
|
||||||
|
//! use poker_api::api::action::Action;
|
||||||
|
//! use poker_api::api::card::Card;
|
||||||
|
//! use poker_api::api::game_info::GameInfo;
|
||||||
|
//!
|
||||||
|
//! let mut player = SimplePlayer::new("Charlie".to_string(), 1000.0);
|
||||||
|
//!
|
||||||
|
//! // Game events are logged to console
|
||||||
|
//! player.game_start_event(Box::new(player)); // Would need proper GameInfo
|
||||||
|
//! player.action_event(2, Action::fold_action(50.0));
|
||||||
|
//! player.win_event(1, 100.0, "Two Pair".to_string());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Strategy Logic
|
||||||
|
//!
|
||||||
|
//! The SimplePlayer uses extremely basic decision logic:
|
||||||
|
//! ```text
|
||||||
|
//! if has_hole_cards:
|
||||||
|
//! return Action::check_action()
|
||||||
|
//! else:
|
||||||
|
//! return Action::fold_action(0.0)
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! This strategy:
|
||||||
|
//! - **Never Bets**: Only checks or folds
|
||||||
|
//! - **No Hand Analysis**: Doesn't evaluate hole card strength
|
||||||
|
//! - **No Position Awareness**: Doesn't consider table position
|
||||||
|
//! - **No Opponent Modeling**: Doesn't track other players' tendencies
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **Minimal Implementation**: Focuses on trait compliance over sophisticated play
|
||||||
|
//! - **Console Output**: Uses println! for easy debugging and demonstration
|
||||||
|
//! - **Immutable Actions**: Always returns the same action type for given state
|
||||||
|
//! - **No State Complexity**: Maintains only essential player information
|
||||||
|
//! - **Educational Focus**: Prioritizes clarity and understandability
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Memory**: ~100 bytes per player instance
|
||||||
|
//! - **Decision Time**: O(1) - simple conditional logic
|
||||||
|
//! - **Event Processing**: O(1) - direct println calls
|
||||||
|
//! - **State Updates**: O(1) - direct field assignment
|
||||||
|
//! - **Scalability**: Suitable for small-scale testing and development
|
||||||
|
|
||||||
use crate::api::action::Action;
|
use crate::api::action::Action;
|
||||||
use crate::api::card::Card;
|
use crate::api::card::Card;
|
||||||
use crate::api::game_info::GameInfo;
|
use crate::api::game_info::GameInfo;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,106 @@
|
||||||
|
//! # Texas Hold'em Module
|
||||||
|
//!
|
||||||
|
//! This module provides Texas Hold'em specific game logic and state management.
|
||||||
|
//! Currently contains placeholder structure for future Texas Hold'em game implementation
|
||||||
|
//! that will integrate with the core poker API.
|
||||||
|
//!
|
||||||
|
//! ## Module Overview
|
||||||
|
//!
|
||||||
|
//! The Texas Hold'em module is designed to provide:
|
||||||
|
//! - **Game State Management**: Complete Texas Hold'em game state tracking
|
||||||
|
//! - **Blinds Management**: Small blind, big blind, and ante handling
|
||||||
|
//! - **Pot Management**: Main pot and side pot calculations
|
||||||
|
//! - **Player Management**: Seat assignment and player state tracking
|
||||||
|
//! - **Table Management**: Community cards and board state
|
||||||
|
//! - **Rule Enforcement**: Texas Hold'em specific game rules
|
||||||
|
//!
|
||||||
|
//! ## Planned Components
|
||||||
|
//!
|
||||||
|
//! ### Game State (`game_state`)
|
||||||
|
//! - Betting round tracking (preflop, flop, turn, river)
|
||||||
|
//! - Game phase management (waiting for players, in hand, showdown)
|
||||||
|
//! - Action validation and game flow control
|
||||||
|
//!
|
||||||
|
//! ### Blinds (`blinds`)
|
||||||
|
//! - Blind level management and increases
|
||||||
|
//! - Ante collection and distribution
|
||||||
|
//! - Dead blind and missed blind handling
|
||||||
|
//!
|
||||||
|
//! ### Pot (`pot`)
|
||||||
|
//! - Main pot and side pot calculation
|
||||||
|
//! - All-in scenarios and pot distribution
|
||||||
|
//! - Pot commitment and eligibility tracking
|
||||||
|
//!
|
||||||
|
//! ### Players (`players`)
|
||||||
|
//! - Seat management and assignment
|
||||||
|
//! - Player status tracking (active, folded, all-in)
|
||||||
|
//! - Stack management and chip counts
|
||||||
|
//!
|
||||||
|
//! ### Table (`table`)
|
||||||
|
//! - Community card management (flop, turn, river)
|
||||||
|
//! - Board texture analysis and hand strength
|
||||||
|
//! - Dead card tracking for equity calculations
|
||||||
|
//!
|
||||||
|
//! ### Board (`board`)
|
||||||
|
//! - Card distribution and dealing order
|
||||||
|
//! - Burn card management
|
||||||
|
//! - Deck state and randomization
|
||||||
|
//!
|
||||||
|
//! ### Rules (`rules`)
|
||||||
|
//! - Texas Hold'em rule validation
|
||||||
|
//! - Tournament vs cash game modes
|
||||||
|
//! - House rules and customizations
|
||||||
|
//!
|
||||||
|
//! ## Current Status
|
||||||
|
//!
|
||||||
|
//! This module is currently in planning/development phase with:
|
||||||
|
//! - ✅ Module structure defined
|
||||||
|
//! - ✅ Basic GameInfo implementation stubbed
|
||||||
|
//! - ⏳ Component modules pending implementation
|
||||||
|
//! - ⏳ Integration with core API pending
|
||||||
|
//! - ⏳ Testing and validation pending
|
||||||
|
//!
|
||||||
|
//! ## Example Usage (Future)
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! use poker_api::texas_holdem::{TexasHoldemGameInfo, game_state, blinds};
|
||||||
|
//!
|
||||||
|
//! // Create new Texas Hold'em game
|
||||||
|
//! let mut game = TexasHoldemGameInfo::new();
|
||||||
|
//!
|
||||||
|
//! // Start new hand
|
||||||
|
//! game.start_new_hand();
|
||||||
|
//!
|
||||||
|
//! // Post blinds
|
||||||
|
//! game.post_small_blind(10.0);
|
||||||
|
//! game.post_big_blind(20.0);
|
||||||
|
//!
|
||||||
|
//! // Deal hole cards
|
||||||
|
//! game.deal_hole_cards();
|
||||||
|
//!
|
||||||
|
//! // Play through betting rounds
|
||||||
|
//! while !game.is_hand_complete() {
|
||||||
|
//! game.play_betting_round();
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Integration with Core API
|
||||||
|
//!
|
||||||
|
//! The Texas Hold'em module will integrate with:
|
||||||
|
//! - **Hand Evaluation**: Use core hand evaluator for strength calculation
|
||||||
|
//! - **Card Management**: Leverage Card and Hand types from core API
|
||||||
|
//! - **Player Interface**: Implement Player trait for bot integration
|
||||||
|
//! - **Game Information**: Provide GameInfo implementation for observers
|
||||||
|
//! - **Action System**: Use Action types for betting operations
|
||||||
|
//!
|
||||||
|
//! ## Design Philosophy
|
||||||
|
//!
|
||||||
|
//! - **Modular Design**: Each component handles specific game aspect
|
||||||
|
//! - **Rule Compliance**: Strict adherence to Texas Hold'em rules
|
||||||
|
//! - **Extensibility**: Support for tournament and cash game variants
|
||||||
|
//! - **Performance**: Optimized for real-time game simulation
|
||||||
|
//! - **Testability**: Comprehensive test coverage for all components
|
||||||
|
|
||||||
// core/texas_holdem/mod.rs
|
// core/texas_holdem/mod.rs
|
||||||
//pub mod game_state;
|
//pub mod game_state;
|
||||||
//pub mod blinds;
|
//pub mod blinds;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,129 @@
|
||||||
|
//! # TOML Preferences Module
|
||||||
|
//!
|
||||||
|
//! This module provides a concrete implementation of the `Preferences` trait using TOML
|
||||||
|
//! (Tom's Obvious, Minimal Language) for configuration file storage. The `TomlPreferences`
|
||||||
|
//! struct enables poker bots to persist and load configuration settings in a human-readable format.
|
||||||
|
//!
|
||||||
|
//! ## TOML Preferences Overview
|
||||||
|
//!
|
||||||
|
//! The `TomlPreferences` implementation provides:
|
||||||
|
//! - **TOML Format**: Human-readable configuration file format
|
||||||
|
//! - **Type Safety**: Proper parsing and validation of preference values
|
||||||
|
//! - **File Persistence**: Save/load configuration from `.toml` files
|
||||||
|
//! - **Default Values**: Graceful fallback for missing preferences
|
||||||
|
//! - **Standard Compliance**: Full implementation of the Preferences trait
|
||||||
|
//!
|
||||||
|
//! ## File Format Example
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! # Bot Configuration
|
||||||
|
//! aggression = 0.7
|
||||||
|
//! bluff_frequency = 0.15
|
||||||
|
//! max_bet_ratio = 0.25
|
||||||
|
//!
|
||||||
|
//! # Game Settings
|
||||||
|
//! enable_logging = true
|
||||||
|
//! max_players = 6
|
||||||
|
//! preferred_seats = [2, 3, 7]
|
||||||
|
//!
|
||||||
|
//! # Strategy Parameters
|
||||||
|
//! [strategy.tight]
|
||||||
|
//! raise_threshold = 0.8
|
||||||
|
//! fold_threshold = 0.3
|
||||||
|
//!
|
||||||
|
//! [strategy.loose]
|
||||||
|
//! raise_threshold = 0.6
|
||||||
|
//! fold_threshold = 0.5
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Human Readable**: TOML format is easy to read and edit manually
|
||||||
|
//! - **Structured Data**: Support for nested tables and arrays
|
||||||
|
//! - **Type Conversion**: Automatic parsing of strings to appropriate types
|
||||||
|
//! - **Error Handling**: Comprehensive error reporting for invalid files
|
||||||
|
//! - **Default Fallbacks**: Safe access with sensible defaults
|
||||||
|
//! - **Full Trait Compliance**: Implements all Preferences trait methods
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Creating and Using TOML Preferences
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::toml_preferences::TomlPreferences;
|
||||||
|
//! use poker_api::api::preferences::Preferences;
|
||||||
|
//!
|
||||||
|
//! // Create new preferences (empty)
|
||||||
|
//! let mut prefs = TomlPreferences::default();
|
||||||
|
//!
|
||||||
|
//! // Set some values
|
||||||
|
//! prefs.set("aggression", "0.8");
|
||||||
|
//! prefs.set_boolean("enable_logging", true);
|
||||||
|
//! prefs.set_int("max_players", 6);
|
||||||
|
//! prefs.set_double("bluff_frequency", 0.15);
|
||||||
|
//!
|
||||||
|
//! // Get values with defaults
|
||||||
|
//! let aggression: String = prefs.get("aggression", "0.5");
|
||||||
|
//! let logging: bool = prefs.get_boolean("enable_logging", false);
|
||||||
|
//! let players: i32 = prefs.get_int("max_players", 9);
|
||||||
|
//! let bluff: f64 = prefs.get_double("bluff_frequency", 0.1);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### File Operations
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::toml_preferences::TomlPreferences;
|
||||||
|
//!
|
||||||
|
//! let mut prefs = TomlPreferences::default();
|
||||||
|
//!
|
||||||
|
//! // Save preferences to file
|
||||||
|
//! prefs.set("bot_name", "MyBot");
|
||||||
|
//! prefs.set_double("aggression", 0.7);
|
||||||
|
//! prefs.save("bot_config.toml").unwrap();
|
||||||
|
//!
|
||||||
|
//! // Load preferences from file
|
||||||
|
//! let mut new_prefs = TomlPreferences::default();
|
||||||
|
//! new_prefs.load("bot_config.toml").unwrap();
|
||||||
|
//!
|
||||||
|
//! println!("Bot name: {}", new_prefs.get("bot_name", "Unknown"));
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Integration with Poker Bots
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use poker_api::toml_preferences::TomlPreferences;
|
||||||
|
//! use poker_api::api::preferences::Preferences;
|
||||||
|
//!
|
||||||
|
//! fn configure_bot_from_file(filename: &str) -> Result<TomlPreferences, String> {
|
||||||
|
//! let mut prefs = TomlPreferences::default();
|
||||||
|
//! prefs.load(filename)?;
|
||||||
|
//!
|
||||||
|
//! // Use preferences for bot configuration
|
||||||
|
//! let aggression = prefs.get_double("aggression", 0.5);
|
||||||
|
//! let enable_logging = prefs.get_boolean("enable_logging", true);
|
||||||
|
//!
|
||||||
|
//! println!("Bot configured with aggression: {}", aggression);
|
||||||
|
//! Ok(prefs)
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Design Decisions
|
||||||
|
//!
|
||||||
|
//! - **TOML Format**: Industry standard for configuration files
|
||||||
|
//! - **String Storage**: All values stored as strings for maximum flexibility
|
||||||
|
//! - **Parse on Demand**: Type conversion happens during access, not storage
|
||||||
|
//! - **Error Propagation**: String-based errors for caller flexibility
|
||||||
|
//! - **HashMap Backend**: Simple and reliable key-value storage
|
||||||
|
//! - **Serde Integration**: Leverages serde for serialization/deserialization
|
||||||
|
//!
|
||||||
|
//! ## Performance Characteristics
|
||||||
|
//!
|
||||||
|
//! - **Memory**: O(n) where n is number of preferences
|
||||||
|
//! - **Access Time**: O(1) hash map lookup
|
||||||
|
//! - **File I/O**: Depends on file size and system performance
|
||||||
|
//! - **Type Conversion**: O(1) parsing for primitive types
|
||||||
|
//! - **Serialization**: O(n) where n is total configuration size
|
||||||
|
|
||||||
use crate::api::preferences::Preferences;
|
use crate::api::preferences::Preferences;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,6 @@ mod hand_eval_integration_test;
|
||||||
|
|
||||||
/// Unit tests for individual components and algorithms
|
/// Unit tests for individual components and algorithms
|
||||||
mod unit_tests;
|
mod unit_tests;
|
||||||
|
|
||||||
|
/// Comprehensive tests for evaluator generator components
|
||||||
|
mod evaluator_generator_tests;
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"rustc_fingerprint":9168241167641241758,"outputs":{"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\jan\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.81.0 (eeb90cda1 2024-09-04)\nbinary: rustc\ncommit-hash: eeb90cda1969383f56a2637cbd3037bdf598841c\ncommit-date: 2024-09-04\nhost: x86_64-pc-windows-msvc\nrelease: 1.81.0\nLLVM version: 18.1.7\n","stderr":""},"16495917692426387086":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\n","stderr":""}},"successes":{}}
|
{"rustc_fingerprint":9168241167641241758,"outputs":{"16495917692426387086":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\n","stderr":""},"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\jan\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.81.0 (eeb90cda1 2024-09-04)\nbinary: rustc\ncommit-hash: eeb90cda1969383f56a2637cbd3037bdf598841c\ncommit-date: 2024-09-04\nhost: x86_64-pc-windows-msvc\nrelease: 1.81.0\nLLVM version: 18.1.7\n","stderr":""}},"successes":{}}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue