diff --git a/Cargo.toml b/Cargo.toml index 8317833..dbfb185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,5 @@ [workspace] resolver = "2" members = [ - "core", -# "simulator", -# "bots/stupid_bot", -# "online", -# "tools/log_analyzer", -# "tools/monitor", + "holdem_core", ] \ No newline at end of file diff --git a/PROJECT_README.md b/PROJECT_README.md deleted file mode 100644 index 270f4de..0000000 --- a/PROJECT_README.md +++ /dev/null @@ -1,239 +0,0 @@ -# Poker Hand Evaluation Project - -## Overview - -This project is a comprehensive Rust-based poker hand evaluation and bot development framework. Built around a high-performance poker hand evaluation library, it provides tools for poker bot development, game simulation, strategy analysis, and performance monitoring. - -## Architecture - -The project is organized into several key components: - -``` -poker-project/ -├── core/ # Core poker API and hand evaluation -│ ├── src/api/ # Core data types and traits -│ ├── src/hand_evaluator.rs # High-performance hand evaluation -│ ├── src/evaluator_generator/ # Lookup table generation -│ └── src/texas_holdem/ # Texas Hold'em game logic -├── bots/ # Poker bot implementations -├── simulator/ # Game simulation and testing tools -├── tools/ # Analysis and monitoring utilities -└── docs/ # Documentation and guides -``` - -## Core Components - -### 🃏 Core Library (`core/`) - -**High-Performance Poker Hand Evaluation** -- **Perfect Hash Algorithm**: O(1) hand evaluation using precomputed tables -- **Multi-Hand Support**: Evaluate 5, 6, or 7-card poker hands -- **Memory Efficient**: ~128MB for complete evaluation tables -- **Standards Compliant**: Based on proven poker evaluation algorithms - -**Key Features:** -- Card and Hand management -- Deck operations and shuffling -- Player action representation -- Game state information -- Bot interface definitions -- Configuration management - -### 🤖 Poker Bots (`bots/`) - -**AI Player Implementations** -- Multiple bot strategies (random, tight, aggressive, adaptive) -- Complete Player and GameObserver trait implementations -- Configurable behavior and decision-making -- Performance tracking and analysis - -### 🎲 Simulator (`simulator/`) - -**Game Simulation Framework** -- Complete Texas Hold'em game simulation -- Monte Carlo analysis for strategy testing -- Range vs range analysis -- Tournament and cash game simulation -- Statistical analysis and reporting - -### 🔧 Tools (`tools/`) - -**Development and Analysis Utilities** -- Log analysis and pattern detection -- Real-time performance monitoring -- Automated testing frameworks -- Report generation and visualization - -## Performance Characteristics - -| Component | Performance | Memory Usage | -|-----------|-------------|--------------| -| Hand Evaluation | ~10-20ns per hand | ~128MB tables | -| Bot Decision | < 10ms per action | ~10KB per bot | -| Game Simulation | ~1ms per hand | ~1MB per simulation | -| Log Analysis | ~100ms per 1000 hands | ~50MB for analysis | - -## Quick Start - -### Basic Hand Evaluation - -```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::hand::Hand; -use poker_api::api::card::Card; - -// Create evaluator (generates tables on first use) -let evaluator = LookupHandEvaluator::new().unwrap(); - -// Create a poker hand -let mut hand = Hand::new(); -hand.add_card(Card::from_string("As").unwrap()).unwrap(); -hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); -hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); -hand.add_card(Card::from_string("Js").unwrap()).unwrap(); -hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - -// Evaluate hand strength -let rank = evaluator.rank_hand(&hand); -println!("Hand rank: {}", rank); // Higher values = stronger hands -``` - -### Creating a Poker Bot - -```rust -use poker_api::api::player::Player; -use poker_api::api::preferences::Preferences; -use poker_api::api::action::Action; - -struct MyBot { - hole_cards: Option<(Card, Card)>, - aggression: f64, -} - -impl Player for MyBot { - fn init(&mut self, prefs: Box) { - self.aggression = prefs.get_double("aggression", 0.5); - } - - fn hole_cards(&mut self, c1: Card, c2: Card, _seat: usize) { - self.hole_cards = Some((c1, c2)); - } - - fn get_action(&mut self) -> Action { - if self.hole_cards.is_some() { - // Make decision based on hole cards and aggression - Action::check_action() - } else { - Action::fold_action(0.0) - } - } -} -``` - -### Running Simulations - -```rust -use poker_api::simulator::texas_holdem::TexasHoldemSimulation; - -// Create and configure simulation -let mut simulation = TexasHoldemSimulation::new(); -simulation.set_player_range(0, "AA,KK,QQ".to_string()); -simulation.set_player_range(1, "22+,A2+".to_string()); - -// Run simulation -let results = simulation.run(10000); -println!("Simulation results: {:?}", results); -``` - -## Algorithm Details - -### Perfect Hash Hand Evaluation - -The core algorithm uses a perfect hash function to map each possible card combination to a unique index in a precomputed ranking table: - -1. **Card Encoding**: Each card is encoded into a compact binary representation -2. **Perfect Hash**: Cards are mapped to unique indices using mathematical functions -3. **Table Lookup**: Precomputed ranking tables provide instant hand strength values -4. **Best Hand Selection**: For 6-7 card hands, finds optimal 5-card combination - -### Table Generation Process - -The first time an evaluator is created, it generates evaluation tables (~1-2 seconds): -- **612,978 combinations** processed for 5-card hands -- **32+ million entries** in final lookup table -- **Mathematical verification** ensures accuracy -- **Binary serialization** for fast loading on subsequent runs - -## Development - -### Building the Project - -```bash -# Clone the repository -git clone -cd poker-project - -# Build all components -cargo build --release - -# Run tests -cargo test - -# Generate documentation -cargo doc --open -``` - -### Project Structure - -Each component is designed to work independently while integrating seamlessly: - -- **Core Library**: Standalone poker hand evaluation -- **Bot Framework**: Pluggable AI player system -- **Simulation Engine**: Controlled testing environment -- **Analysis Tools**: Performance monitoring and strategy analysis - -### Contributing - -The project welcomes contributions in several areas: -- **Bot Strategies**: New AI playing strategies -- **Game Variants**: Additional poker game types -- **Performance**: Optimizations and hardware acceleration -- **Analysis Tools**: Enhanced monitoring and reporting -- **Documentation**: Guides and tutorials - -## Use Cases - -### Poker Bot Development -- Develop and test poker AI strategies -- Analyze bot performance against known ranges -- Optimize decision-making algorithms - -### Game Simulation -- Test poker scenarios and edge cases -- Analyze range vs range situations -- Study game theory optimal strategies - -### Strategy Research -- Research optimal bet sizing -- Analyze position and stack size effects -- Develop exploitative strategies - -### Performance Analysis -- Monitor poker application performance -- Identify bottlenecks and optimization opportunities -- Load testing for production systems - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. - -## Acknowledgments - -- **Meerkat API**: Original Java implementation by Ray Wotton -- **Poker Standards**: 2+2 Poker hand ranking standards -- **Rust Community**: Performance optimizations and best practices -- **Contributors**: All contributors to the poker evaluation algorithms - ---- - -**Note**: This project focuses on poker hand evaluation and bot development. It does not include real-money gambling functionality and is intended for educational and research purposes. \ No newline at end of file diff --git a/README.md b/README.md index 5c90e85..5fc8935 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ A comprehensive poker bot testing framework with Java-compatible hand evaluation. This library implements the Meerkat API algorithm with full Java compatibility, specifically designed for automated poker bot testing, development, and integration with existing poker tools. +## Recent Updates + +- **Input Validation**: Added comprehensive validation to `Card::new()` ensuring rank (0-12) and suit (0-3) are within valid ranges +- **Enhanced Documentation**: All public methods now include working code examples and panic condition documentation +- **Error Handling**: Improved error handling throughout the codebase with proper `Result` types + ## Features - **Java-Compatible**: Full compatibility with Java Meerkat API for seamless tool integration @@ -27,6 +33,14 @@ A comprehensive poker bot testing framework with Java-compatible hand evaluation | 6-Card | ~50-80ns | 6 combinations | | 7-Card | ~150-250ns | 21 combinations | +## Documentation + +For detailed project information, see: + +- **[VISION.md](docs/VISION.md)** - Project goals and architectural vision +- **[ACHIEVEMENTS.md](docs/ACHIEVEMENTS.md)** - Completed milestones and success metrics +- **[ROADMAP.md](docs/ROADMAP.md)** - Future development phases and implementation plan + ## Installation Add this to your `Cargo.toml`: @@ -39,8 +53,8 @@ poker-api = "0.1.0" ## Quick Start - Bot Testing ```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::{hand::Hand, card::Card}; +use holdem_core::hand_evaluator::LookupHandEvaluator; +use holdem_core::api::{hand::Hand, card::Card}; // Initialize Java-compatible evaluator for bot testing let evaluator = LookupHandEvaluator::new().unwrap(); @@ -125,8 +139,8 @@ The evaluator recognizes all standard poker hand types: ### Texas Hold'em Bot Analysis ```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::{card::Card, hand::Hand}; +use holdem_core::hand_evaluator::LookupHandEvaluator; +use holdem_core::api::{card::Card, hand::Hand}; let evaluator = LookupHandEvaluator::new().unwrap(); @@ -152,8 +166,8 @@ if hand_strength < 2000 { // Strong hand ### Bot Range Analysis ```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::card::Card; +use holdem_core::hand_evaluator::LookupHandEvaluator; +use holdem_core::api::card::Card; let evaluator = LookupHandEvaluator::new().unwrap(); @@ -179,8 +193,8 @@ for (card1, card2) in premium_hands { ### 7-Card Hand Evaluation for Bot Testing ```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::card::Card; +use holdem_core::hand_evaluator::LookupHandEvaluator; +use holdem_core::api::card::Card; let evaluator = LookupHandEvaluator::new().unwrap(); @@ -207,7 +221,7 @@ println!("Bot best hand rank: {}", best_rank); ### Custom Table Generation ```rust -use poker_api::evaluator_generator::state_table_generator::StateTableGenerator; +use holdem_core::evaluator_generator::state_table_generator::StateTableGenerator; let mut generator = StateTableGenerator::new(); generator.generate_tables(); // ~1-2 seconds diff --git a/bots/README.md b/bots/README.md deleted file mode 100644 index f4549e1..0000000 --- a/bots/README.md +++ /dev/null @@ -1,202 +0,0 @@ -# Poker Bots - -## Overview - -The `bots/` directory contains poker bot implementations that can participate in poker games using the core poker API. These bots demonstrate different AI strategies and serve as examples for developing custom poker bots. - -## Available Bots - -### Stupid Bot (`stupid_bot/`) - -A collection of poker bot implementations with varying levels of sophistication: - -#### Features -- **Multiple Strategies**: Different bot implementations for testing -- **API Compliance**: Full implementation of Player and GameObserver traits -- **Configurable Behavior**: Customizable decision-making parameters -- **Logging Support**: Detailed game state logging for analysis - -#### Bot Types -- **Random Bot**: Makes random valid decisions -- **Tight Bot**: Conservative playing style -- **Aggressive Bot**: High-risk, high-reward strategy -- **Adaptive Bot**: Adjusts strategy based on game conditions - -## Usage - -### Running a Bot - -```rust -use poker_api::simple_player::SimplePlayer; - -// Create a simple bot -let mut bot = SimplePlayer::new("MyBot".to_string(), 1000.0); - -// Initialize with preferences -let prefs = Box::new(TomlPreferences::default()); -bot.init(prefs); - -// Receive hole cards -bot.hole_cards(card1, card2, seat); - -// Get bot's action -let action = bot.get_action(); -``` - -### Bot Development - -#### Implementing the Player Trait - -All bots must implement the `Player` trait: - -```rust -use poker_api::api::player::Player; -use poker_api::api::preferences::Preferences; - -struct MyBot { - name: String, - hole_cards: Option<(Card, Card)>, - // Add bot-specific fields -} - -impl Player for MyBot { - fn init(&mut self, prefs: Box) { - // Initialize bot with preferences - } - - fn hole_cards(&mut self, c1: Card, c2: Card, seat: usize) { - self.hole_cards = Some((c1, c2)); - self.seat = seat; - } - - fn get_action(&mut self) -> Action { - // Implement bot decision logic - Action::check_action() - } -} -``` - -#### Game Observation - -Bots can also implement `GameObserver` for enhanced awareness: - -```rust -impl GameObserver for MyBot { - fn action_event(&mut self, pos: usize, action: Action) { - // Track opponent actions - } - - fn game_start_event(&mut self, game_info: Box) { - // Prepare for new hand - } - - // Implement other observer methods... -} -``` - -## Configuration - -### TOML Configuration - -Bots can be configured using TOML files: - -```toml -# Bot Configuration -[bot] -name = "MyBot" -strategy = "aggressive" -risk_tolerance = 0.8 - -[betting] -min_bet = 10.0 -max_bet_ratio = 0.25 -bluff_frequency = 0.15 - -[analysis] -hand_strength_threshold = 0.7 -position_awareness = true -``` - -### Runtime Configuration - -```rust -use poker_api::toml_preferences::TomlPreferences; - -let mut prefs = TomlPreferences::default(); -prefs.set_double("aggression", 0.8); -prefs.set_boolean("enable_logging", true); -prefs.save("bot_config.toml").unwrap(); -``` - -## Testing - -### Bot vs Bot Games - -```bash -# Run automated bot testing -cargo test bot_integration_tests - -# Run performance benchmarks -cargo test --release bot_performance_benchmark -``` - -### Manual Testing - -```rust -// Create multiple bots for testing -let bot1 = SimplePlayer::new("Bot1".to_string(), 1000.0); -let bot2 = SimplePlayer::new("Bot2".to_string(), 1000.0); - -// Simulate game interactions -// ... game logic would go here -``` - -## Development Guidelines - -### Best Practices - -1. **Separation of Concerns**: Keep strategy logic separate from game mechanics -2. **State Management**: Maintain clean, consistent bot state -3. **Error Handling**: Graceful handling of unexpected game states -4. **Performance**: Efficient decision-making for real-time play -5. **Testability**: Easy to test and debug bot behavior - -### Strategy Development - -#### Hand Strength Analysis -```rust -// Analyze hole cards -let hand_strength = evaluate_hole_cards(hole_cards); -let position_factor = calculate_position_advantage(seat, num_players); -let adjusted_strength = hand_strength * position_factor; -``` - -#### Opponent Modeling -```rust -// Track opponent tendencies -opponent_stats.insert(player_id, PlayerStats { - aggression_factor: calculate_aggression(actions), - bluff_frequency: detect_bluff_patterns(actions), - fold_frequency: calculate_fold_frequency(actions), -}); -``` - -## Integration - -Bots integrate with: -- **Core API**: Use hand evaluation and card management -- **Game Engine**: Participate in actual poker games -- **Simulation Tools**: Test strategies in controlled environments -- **Analysis Tools**: Review performance and improve strategies - -## Performance - -### Decision Time -- **Simple Bots**: < 1ms per decision -- **Complex Bots**: < 10ms per decision -- **Memory Usage**: 1-10KB per bot instance - -### Scalability -- **Multiple Bots**: Support for hundreds of concurrent bots -- **Game Speed**: Fast enough for real-time online play -- **Resource Usage**: Minimal CPU and memory footprint \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml deleted file mode 100644 index f738fbc..0000000 --- a/core/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "poker-api" -version = "0.1.0" -edition = "2021" -authors = ["Jan-Peter v. Hunnius"] -description = "Java-compatible poker hand evaluation library for bot testing frameworks - implements Meerkat API algorithm with full Java compatibility for poker bot development and testing" -readme = "README.md" -license = "MIT" -repository = "https://github.com/jhunnius/rusty_marvin" -documentation = "https://docs.rs/poker-api" -homepage = "https://github.com/your-org/poker-api" -keywords = ["poker", "bot-testing", "hand-evaluation", "texas-holdem", "meerkat-api", "poker-bots", "testbed", "gaming", "cards"] -categories = ["games", "api-bindings", "mathematics"] -exclude = ["docs/*", "target/*", "*.md"] - -[dependencies] -serde = { version = "1.0", features = ["derive"] } -rand = "0.9.0" -toml = "0.8.20" - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/core/src/api/action.rs b/core/src/api/action.rs deleted file mode 100644 index 32577be..0000000 --- a/core/src/api/action.rs +++ /dev/null @@ -1,326 +0,0 @@ -//! # 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)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ActionType { - Invalid = 0, - Fold = 1, - Check = 2, - Call = 3, - Bet = 4, - Raise = 5, - SmallBlind = 6, - BigBlind = 7, - PostBlind = 8, - AllInPass = 9, - Muck = 10, - PostAnte = 11, - SitOut = 12, - PostDeadBlind = 13, -} - -impl ActionType { - // Convert from u8 to ActionType - pub fn from_u8(value: u8) -> Option { - match value { - 0 => Some(ActionType::Invalid), - 1 => Some(ActionType::Fold), - 2 => Some(ActionType::Check), - 3 => Some(ActionType::Call), - 4 => Some(ActionType::Bet), - 5 => Some(ActionType::Raise), - 6 => Some(ActionType::SmallBlind), - 7 => Some(ActionType::BigBlind), - 8 => Some(ActionType::PostBlind), - 9 => Some(ActionType::AllInPass), - 10 => Some(ActionType::Muck), - 11 => Some(ActionType::PostAnte), - 12 => Some(ActionType::SitOut), - 13 => Some(ActionType::PostDeadBlind), - _ => None, - } - } - - // Convert from ActionType to u8 - pub fn to_u8(&self) -> u8 { - *self as u8 - } -} -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Action { - action_type: ActionType, - to_call: f64, // Amount required to call - amount: f64, // Amount of the action (e.g., bet or raise amount) -} - -impl Action { - // Constants for action types - pub const INVALID: ActionType = ActionType::Invalid; - pub const FOLD: ActionType = ActionType::Fold; - pub const CHECK: ActionType = ActionType::Check; - pub const CALL: ActionType = ActionType::Call; - pub const BET: ActionType = ActionType::Bet; - pub const RAISE: ActionType = ActionType::Raise; - pub const SMALL_BLIND: ActionType = ActionType::SmallBlind; - pub const BIG_BLIND: ActionType = ActionType::BigBlind; - pub const POST_BLIND: ActionType = ActionType::PostBlind; - pub const ALLIN_PASS: ActionType = ActionType::AllInPass; - pub const MUCK: ActionType = ActionType::Muck; - pub const POST_ANTE: ActionType = ActionType::PostAnte; - pub const SIT_OUT: ActionType = ActionType::SitOut; - pub const POST_DEAD_BLIND: ActionType = ActionType::PostDeadBlind; - - // Constructor - pub fn new(action_type: ActionType, to_call: f64, amount: f64) -> Self { - Self { - action_type, - to_call, - amount, - } - } - - // Factory methods for creating actions - pub fn fold_action(to_call: f64) -> Self { - Self::new(ActionType::Fold, to_call, 0.0) - } - - pub fn check_action() -> Self { - Self::new(ActionType::Check, 0.0, 0.0) - } - - pub fn call_action(to_call: f64) -> Self { - Self::new(ActionType::Call, to_call, to_call) - } - - pub fn bet_action(amount: f64) -> Self { - Self::new(ActionType::Bet, 0.0, amount) - } - - pub fn raise_action(to_call: f64, amount: f64) -> Self { - Self::new(ActionType::Raise, to_call, amount) - } - - pub fn small_blind_action(to_post: f64) -> Self { - Self::new(ActionType::SmallBlind, 0.0, to_post) - } - - pub fn big_blind_action(to_post: f64) -> Self { - Self::new(ActionType::BigBlind, 0.0, to_post) - } - - pub fn post_blind_action(to_post: f64) -> Self { - Self::new(ActionType::PostBlind, 0.0, to_post) - } - - pub fn all_in_pass_action() -> Self { - Self::new(ActionType::AllInPass, 0.0, 0.0) - } - - pub fn muck_action() -> Self { - Self::new(ActionType::Muck, 0.0, 0.0) - } - - pub fn post_ante_action(to_post: f64) -> Self { - Self::new(ActionType::PostAnte, 0.0, to_post) - } - - pub fn sit_out_action() -> Self { - Self::new(ActionType::SitOut, 0.0, 0.0) - } - - pub fn post_dead_blind_action(to_post: f64) -> Self { - Self::new(ActionType::PostDeadBlind, 0.0, to_post) - } - - // Getters - pub fn action_type(&self) -> ActionType { - self.action_type - } - - pub fn to_call(&self) -> f64 { - self.to_call - } - - pub fn amount(&self) -> f64 { - self.amount - } - - // Check action properties - pub fn is_fold(&self) -> bool { - self.action_type == ActionType::Fold - } - - pub fn is_check(&self) -> bool { - self.action_type == ActionType::Check - } - - pub fn is_call(&self) -> bool { - self.action_type == ActionType::Call - } - - pub fn is_bet(&self) -> bool { - self.action_type == ActionType::Bet - } - - pub fn is_raise(&self) -> bool { - self.action_type == ActionType::Raise - } - - pub fn is_voluntary(&self) -> bool { - match self.action_type { - ActionType::Fold - | ActionType::Check - | ActionType::Call - | ActionType::Bet - | ActionType::Raise => true, - _ => false, - } - } - - // Formatting - pub fn to_string(&self) -> String { - match self.action_type { - ActionType::Fold => "Fold".to_string(), - ActionType::Check => "Check".to_string(), - ActionType::Call => format!("Call {}", self.to_call), - ActionType::Bet => format!("Bet {}", self.amount), - ActionType::Raise => format!("Raise {}", self.amount), - _ => "Invalid Action".to_string(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_fold_action() { - let action = Action::fold_action(10.0); - assert!(action.is_fold()); - assert_eq!(action.to_call(), 10.0); - assert_eq!(action.amount(), 0.0); - } - - #[test] - fn test_check_action() { - let action = Action::check_action(); - assert!(action.is_check()); - assert_eq!(action.to_call(), 0.0); - assert_eq!(action.amount(), 0.0); - } - - #[test] - fn test_call_action() { - let action = Action::call_action(20.0); - assert!(action.is_call()); - assert_eq!(action.to_call(), 20.0); - assert_eq!(action.amount(), 20.0); - } - - #[test] - fn test_bet_action() { - let action = Action::bet_action(50.0); - assert!(action.is_bet()); - assert_eq!(action.to_call(), 0.0); - assert_eq!(action.amount(), 50.0); - } - - #[test] - fn test_raise_action() { - let action = Action::raise_action(30.0, 100.0); - assert!(action.is_raise()); - assert_eq!(action.to_call(), 30.0); - assert_eq!(action.amount(), 100.0); - } -} diff --git a/core/src/api/card.rs b/core/src/api/card.rs deleted file mode 100644 index 2176862..0000000 --- a/core/src/api/card.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! # 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: rrrr-sss -//! ++++----- Rank (1-14, where 1=Deuce, 2=Trey, ..., 13=King, 14=Ace) -//! +++--- Suit (1=Clubs, 2=Diamonds, 3=Hearts, 4=Spades) -//! ``` -//! -//! ## 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)] -pub struct Card(u8); - -impl Card { - // Constants for suits (Java-style encoding: 1-4) - pub const CLUBS: u8 = 1; - pub const DIAMONDS: u8 = 2; - pub const HEARTS: u8 = 3; - pub const SPADES: u8 = 4; - - // Constants for ranks (Java-style encoding: 1-14) - pub const DEUCE: u8 = 1; - pub const TREY: u8 = 2; - pub const FOUR: u8 = 3; - pub const FIVE: u8 = 4; - pub const SIX: u8 = 5; - pub const SEVEN: u8 = 6; - pub const EIGHT: u8 = 7; - pub const NINE: u8 = 8; - pub const TEN: u8 = 9; - pub const JACK: u8 = 10; - pub const QUEEN: u8 = 11; - pub const KING: u8 = 12; - pub const ACE: u8 = 13; - - // Special constants - pub const BAD_CARD: u8 = 255; - pub const NUM_SUITS: u8 = 4; - pub const NUM_RANKS: u8 = 13; - pub const NUM_CARDS: u8 = 52; - - // Constructor for an empty/invalid card - pub fn new() -> Self { - Self(Self::BAD_CARD) - } - - // Constructor from rank and suit - pub fn from_rank_suit(rank: u8, suit: u8) -> Result { - if rank < Self::DEUCE || rank > Self::ACE { - return Err("Invalid rank"); - } - if suit < Self::CLUBS || suit > Self::SPADES { - return Err("Invalid suit"); - } - Ok(Self((rank << 3) | (suit & 0x7))) - } - - // Constructor from index (0..51) - pub fn from_index(index: u8) -> Result { - if index >= Self::NUM_CARDS { - return Err("Invalid index"); - } - let rank = (index % Self::NUM_RANKS) + Self::DEUCE; - let suit = index / Self::NUM_RANKS + Self::CLUBS; - Self::from_rank_suit(rank, suit) - } - - // Constructor from string (e.g., "As" for Ace of Spades) - pub fn from_string(s: &str) -> Result { - if s.len() != 2 { - return Err("Invalid card string"); - } - let rank_char = s.chars().nth(0).unwrap(); - let suit_char = s.chars().nth(1).unwrap(); - let rank = Self::rank_from_char(rank_char)?; - let suit = Self::suit_from_char(suit_char)?; - Self::from_rank_suit(rank, suit) - } - - // Get the rank of the card - pub fn rank(&self) -> u8 { - (self.0 >> 3) & 0xF - } - - // Get the suit of the card - pub fn suit(&self) -> u8 { - self.0 & 0x7 - } - - // Get the index of the card (0..51) - pub fn index(&self) -> u8 { - ((self.suit() - Self::CLUBS) * Self::NUM_RANKS) + (self.rank() - Self::DEUCE) - } - - // Check if the card is valid - pub fn is_valid(&self) -> bool { - self.0 != Self::BAD_CARD - } - - // Convert rank to character (e.g., 13 -> 'A') - pub fn rank_to_char(rank: u8) -> Result { - match rank { - Self::DEUCE => Ok('2'), - Self::TREY => Ok('3'), - Self::FOUR => Ok('4'), - Self::FIVE => Ok('5'), - Self::SIX => Ok('6'), - Self::SEVEN => Ok('7'), - Self::EIGHT => Ok('8'), - Self::NINE => Ok('9'), - Self::TEN => Ok('T'), - Self::JACK => Ok('J'), - Self::QUEEN => Ok('Q'), - Self::KING => Ok('K'), - Self::ACE => Ok('A'), - _ => Err("Invalid rank"), - } - } - - // Convert suit to character (e.g., 4 -> 's') - pub fn suit_to_char(suit: u8) -> Result { - match suit { - Self::SPADES => Ok('s'), - Self::HEARTS => Ok('h'), - Self::DIAMONDS => Ok('d'), - Self::CLUBS => Ok('c'), - _ => Err("Invalid suit"), - } - } - - // Convert rank character to rank (e.g., 'A' -> 13) - pub fn rank_from_char(rank_char: char) -> Result { - match rank_char { - '2' => Ok(Self::DEUCE), - '3' => Ok(Self::TREY), - '4' => Ok(Self::FOUR), - '5' => Ok(Self::FIVE), - '6' => Ok(Self::SIX), - '7' => Ok(Self::SEVEN), - '8' => Ok(Self::EIGHT), - '9' => Ok(Self::NINE), - 'T' => Ok(Self::TEN), - 'J' => Ok(Self::JACK), - 'Q' => Ok(Self::QUEEN), - 'K' => Ok(Self::KING), - 'A' => Ok(Self::ACE), - _ => Err("Invalid rank character"), - } - } - - // Convert suit character to suit (e.g., 's' -> 0) - pub fn suit_from_char(suit_char: char) -> Result { - match suit_char { - 's' => Ok(Self::SPADES), - 'h' => Ok(Self::HEARTS), - 'd' => Ok(Self::DIAMONDS), - 'c' => Ok(Self::CLUBS), - _ => Err("Invalid suit character"), - } - } -} - -impl std::fmt::Display for Card { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if !self.is_valid() { - write!(f, "Invalid Card") - } else { - let rank_char = Self::rank_to_char(self.rank()).unwrap(); - let suit_char = Self::suit_to_char(self.suit()).unwrap(); - write!(f, "{}{}", rank_char, suit_char) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_card_creation() { - let card = Card::from_rank_suit(Card::ACE, Card::SPADES).unwrap(); - assert_eq!(card.rank(), Card::ACE); - assert_eq!(card.suit(), Card::SPADES); - assert_eq!(card.index(), 51); // Ace of Spades is index 51 (last card) - assert!(card.is_valid()); - } - - #[test] - fn test_card_from_string() { - let card = Card::from_string("As").unwrap(); - assert_eq!(card.rank(), Card::ACE); - assert_eq!(card.suit(), Card::SPADES); - assert_eq!(card.to_string(), "As"); - } - - #[test] - fn test_card_from_index() { - let card = Card::from_index(0).unwrap(); - assert_eq!(card.rank(), Card::DEUCE); - assert_eq!(card.suit(), Card::CLUBS); - assert_eq!(card.to_string(), "2c"); - } - - #[test] - fn test_invalid_card() { - let card = Card::new(); - assert!(!card.is_valid()); - assert_eq!(card.to_string(), "Invalid Card"); - } -} diff --git a/core/src/api/deck.rs b/core/src/api/deck.rs deleted file mode 100644 index aba3726..0000000 --- a/core/src/api/deck.rs +++ /dev/null @@ -1,267 +0,0 @@ -//! # 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::hand::Hand; -use rand::seq::SliceRandom; -use rand::SeedableRng; -use std::fmt; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Deck { - cards: Vec, - dealt: Vec, // Cards that have been dealt -} - -impl Deck { - pub const NUM_CARDS: usize = 52; - - // Create a new, unshuffled deck - pub fn new() -> Self { - let mut cards = Vec::with_capacity(Self::NUM_CARDS); - for suit in 1..=4 { - for rank in Card::DEUCE..=Card::ACE { - cards.push(Card::from_rank_suit(rank, suit).unwrap()); - } - } - Self { - cards, - dealt: Vec::new(), - } - } - - // Create a new deck with a specific shuffle seed - pub fn with_seed(seed: u64) -> Self { - let mut deck = Self::new(); - deck.shuffle_with_seed(seed); - deck - } - - // Shuffle the deck using a random seed - pub fn shuffle(&mut self) { - self.cards.shuffle(&mut rand::rng()); - } - - // Shuffle the deck using a specific seed - pub fn shuffle_with_seed(&mut self, seed: u64) { - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - self.cards.shuffle(&mut rng); - } - - // Deal the next card from the deck - pub fn deal(&mut self) -> Option { - if self.cards.is_empty() { - None - } else { - let card = self.cards.pop().unwrap(); - self.dealt.push(card); - Some(card) - } - } - - // Deal a random card from the deck - pub fn deal_random(&mut self) -> Option { - if self.cards.is_empty() { - None - } else { - let index = rand::random::() as usize % self.cards.len(); - let card = self.cards.remove(index); - self.dealt.push(card); - Some(card) - } - } - - // Reset the deck (put all cards back) - pub fn reset(&mut self) { - self.cards.extend(self.dealt.drain(..)); - } - - // Check if a card is still in the deck - pub fn in_deck(&self, card: Card) -> bool { - self.cards.contains(&card) - } - - // Remove a specific card from the deck - pub fn extract_card(&mut self, card: Card) -> Option { - if let Some(index) = self.cards.iter().position(|&c| c == card) { - Some(self.cards.remove(index)) - } else { - None - } - } - - // Remove a specific card by index from the deck - pub fn extract_card_by_index(&mut self, index: u8) -> Option { - if let Some(pos) = self.cards.iter().position(|&c| c.index() == index) { - Some(self.cards.remove(pos)) - } else { - None - } - } - - // Remove all cards in a hand from the deck - pub fn extract_hand(&mut self, hand: &Hand) { - for card in hand.get_card_array() { - self.extract_card_by_index(card); - } - } - - // Get the number of cards left in the deck - pub fn cards_left(&self) -> usize { - self.cards.len() - } - - // Get the top card (next card to be dealt) - pub fn get_top_card(&self) -> Option { - self.cards.last().copied() - } - - // Get the index of the top card - pub fn get_top_card_index(&self) -> Option { - self.get_top_card().map(|card| card.index()) - } - - // Get a card at a specific index in the deck - pub fn get_card(&self, index: usize) -> Option { - self.cards.get(index).copied() - } -} - -impl fmt::Display for Deck { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Deck ({} cards left)", self.cards_left()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_deck_creation() { - let deck = Deck::new(); - assert_eq!(deck.cards_left(), Deck::NUM_CARDS); - } - - #[test] - fn test_deck_shuffle() { - let mut deck = Deck::new(); - deck.shuffle(); - assert_eq!(deck.cards_left(), Deck::NUM_CARDS); - } - - #[test] - fn test_deck_deal() { - let mut deck = Deck::new(); - let card = deck.deal().unwrap(); - assert_eq!(deck.cards_left(), Deck::NUM_CARDS - 1); - assert!(!deck.in_deck(card)); - } - - #[test] - fn test_deck_reset() { - let mut deck = Deck::new(); - let card = deck.deal().unwrap(); - deck.reset(); - assert_eq!(deck.cards_left(), Deck::NUM_CARDS); - assert!(deck.in_deck(card)); - } - - #[test] - fn test_deck_extract_card() { - let mut deck = Deck::new(); - let card = Card::from_string("As").unwrap(); - deck.extract_card(card).unwrap(); - assert_eq!(deck.cards_left(), Deck::NUM_CARDS - 1); - assert!(!deck.in_deck(card)); - } -} diff --git a/core/src/api/game_info.rs b/core/src/api/game_info.rs deleted file mode 100644 index d3294c9..0000000 --- a/core/src/api/game_info.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! # 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::player_info::PlayerInfo; - -pub trait GameInfo { - // Game State - fn get_stage(&self) -> u8; - fn is_pre_flop(&self) -> bool; - fn is_post_flop(&self) -> bool; - fn is_flop(&self) -> bool; - fn is_turn(&self) -> bool; - fn is_river(&self) -> bool; - fn is_game_over(&self) -> bool; - - // Blinds and Antes - fn get_ante(&self) -> f64; - fn get_small_blind_size(&self) -> f64; - fn get_big_blind_size(&self) -> f64; - fn get_current_bet_size(&self) -> f64; - fn get_min_raise(&self) -> f64; - - // Pot Information - fn get_total_pot_size(&self) -> f64; - fn get_main_pot_size(&self) -> f64; - fn get_num_side_pots(&self) -> usize; - fn get_side_pot_size(&self, index: usize) -> f64; - fn get_rake(&self) -> f64; - - // Player Information - fn get_num_players(&self) -> usize; - fn get_num_active_players(&self) -> usize; - fn get_num_active_players_not_all_in(&self) -> usize; - fn get_num_all_in_players(&self) -> usize; - fn get_num_to_act(&self) -> usize; - fn get_unacted(&self) -> usize; - fn get_num_seats(&self) -> usize; - - // Player Actions - fn can_raise(&self, seat: usize) -> bool; - fn get_amount_to_call(&self, seat: usize) -> f64; - fn get_bets_to_call(&self, seat: usize) -> f64; - fn get_bankroll(&self, seat: usize) -> f64; - fn get_bankroll_at_risk(&self, seat: usize) -> f64; - - // Player State - fn in_game(&self, seat: usize) -> bool; - fn is_active(&self, seat: usize) -> bool; - fn is_committed(&self, seat: usize) -> bool; - - // Table Information - fn get_button_seat(&self) -> usize; - fn get_small_blind_seat(&self) -> usize; - fn get_big_blind_seat(&self) -> usize; - fn get_current_player_seat(&self) -> usize; - - // Board and Hand Information - fn get_board(&self) -> Hand; - fn get_eligible_pot(&self, seat: usize) -> f64; - - // Game Rules - fn is_no_limit(&self) -> bool; - fn is_fixed_limit(&self) -> bool; - fn is_pot_limit(&self) -> bool; - fn is_simulation(&self) -> bool; - fn is_zip_mode(&self) -> bool; - fn is_reverse_blinds(&self) -> bool; - - // Miscellaneous - fn get_game_id(&self) -> u64; - fn get_log_directory(&self) -> String; - fn get_player(&self, seat: usize) -> Option>; - fn get_player_by_name(&self, name: &str) -> Option>; - fn get_player_name(&self, seat: usize) -> Option; - fn get_player_seat(&self, name: &str) -> Option; - fn get_players_in_pot(&self, amount_in: f64) -> Vec; - fn get_num_winners(&self) -> usize; - fn get_num_raises(&self) -> usize; -} diff --git a/core/src/api/game_observer.rs b/core/src/api/game_observer.rs deleted file mode 100644 index 9c9c44b..0000000 --- a/core/src/api/game_observer.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! # 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) { -//! 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, -//! } -//! -//! impl GameObserver for StatisticsTracker { -//! fn game_start_event(&mut self, gi: Box) { -//! 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::card::Card; -use crate::api::game_info::GameInfo; - -pub trait GameObserver { - /// A player can override this method to receive events for each action made by a player. - fn action_event(&mut self, pos: usize, act: Action); - - /// The hole cards are being dealt. - fn deal_hole_cards_event(&mut self); - - /// The hand is now over. - fn game_over_event(&mut self); - - /// The hand is starting. - fn game_start_event(&mut self, gi: Box); - - /// The game info state has been updated. Called after an action event has been fully processed. - fn game_state_changed(&mut self); - - /// Player `pos` has shown two cards. - fn showdown_event(&mut self, pos: usize, c1: Card, c2: Card); - - /// A new stage (betting round) has begun. - fn stage_event(&mut self, stage: u8); - - /// A player at `pos` has won `amount` with the hand `hand_name`. - fn win_event(&mut self, pos: usize, amount: f64, hand_name: String); -} diff --git a/core/src/api/hand.rs b/core/src/api/hand.rs deleted file mode 100644 index 74b3fd3..0000000 --- a/core/src/api/hand.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! # 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` 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; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Hand { - pub(crate) cards: [Option; 7], // Up to 7 cards - size: usize, // Number of cards in the hand -} - -impl Hand { - pub const MAX_CARDS: usize = 7; - - // Create a new empty hand - pub fn new() -> Self { - Self { - cards: [None; Self::MAX_CARDS], - size: 0, - } - } - - // Create a hand from a string representation (e.g., "As Ks Qs") - pub fn from_string(s: &str) -> Result { - let mut hand = Self::new(); - for card_str in s.split_whitespace() { - let card = Card::from_string(card_str)?; - hand.add_card(card)?; - } - Ok(hand) - } - - // Add a card to the hand - pub fn add_card(&mut self, card: Card) -> Result<(), &'static str> { - if self.size >= Self::MAX_CARDS { - return Err("Hand is full"); - } - self.cards[self.size] = Some(card); - self.size += 1; - Ok(()) - } - - // Add a card to the hand by its index (0-51) - pub fn add_card_from_index(&mut self, index: u8) -> Result<(), &'static str> { - let card = Card::from_index(index)?; - self.add_card(card) - } - - // Remove the last card in the hand - pub fn remove_card(&mut self) { - if self.size > 0 { - self.size -= 1; - self.cards[self.size] = None; - } - } - - // Get the card at a specific position (1-based indexing) - pub fn get_card(&self, pos: usize) -> Option { - if pos == 0 || pos > self.size { - return None; - } - self.cards[pos - 1] - } - - // Get the card index at a specific position (1-based indexing) - pub fn get_card_index(&self, pos: usize) -> Option { - self.get_card(pos).map(|card| card.index()) - } - - // Get the last card in the hand - pub fn get_last_card(&self) -> Option { - if self.size == 0 { - None - } else { - self.cards[self.size - 1] - } - } - - // Get the last card index in the hand - pub fn get_last_card_index(&self) -> Option { - self.get_last_card().map(|card| card.index()) - } - - // Clear the hand (remove all cards) - pub fn clear(&mut self) { - self.cards = [None; Self::MAX_CARDS]; - self.size = 0; - } - - // Sort the hand in descending order by card index - pub fn sort(&mut self) { - self.cards[..self.size].sort_by(|a, b| { - let a_index = a.map(|card| card.index()).unwrap_or(0); - let b_index = b.map(|card| card.index()).unwrap_or(0); - b_index.cmp(&a_index) // Sort in descending order - }); - } - - // Check if the hand contains a specific card - pub fn contains(&self, card: Card) -> bool { - self.cards[..self.size].contains(&Some(card)) - } - - // Get a string representation of the hand (e.g., "As Ks Qs") - pub fn to_string(&self) -> String { - self.cards[..self.size] - .iter() - .filter_map(|&card| card.map(|c| c.to_string())) - .collect::>() - .join(" ") - } - - // Get a string representation for flashing purposes - pub fn flashing_string(&self) -> String { - self.to_string() - } - - // Get the size of the hand - pub fn size(&self) -> usize { - self.size - } - - // Get the array of card indexes (for LUT-based evaluators) - pub fn get_card_array(&self) -> Vec { - self.cards[..self.size] - .iter() - .filter_map(|&card| card.map(|c| c.index())) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::api::card::Card; - - #[test] - fn test_hand_creation() { - let hand = Hand::new(); - assert_eq!(hand.size(), 0); - } - - #[test] - fn test_add_card() { - let mut hand = Hand::new(); - let card = Card::from_string("As").unwrap(); - hand.add_card(card).unwrap(); - assert_eq!(hand.size(), 1); - assert_eq!(hand.get_card(1), Some(card)); - } - - #[test] - fn test_remove_card() { - let mut hand = Hand::new(); - let card = Card::from_string("As").unwrap(); - hand.add_card(card).unwrap(); - hand.remove_card(); - assert_eq!(hand.size(), 0); - } - - #[test] - fn test_hand_from_string() { - let hand = Hand::from_string("As Ks Qs").unwrap(); - assert_eq!(hand.size(), 3); - assert_eq!(hand.get_card(1), Some(Card::from_string("As").unwrap())); - assert_eq!(hand.get_card(2), Some(Card::from_string("Ks").unwrap())); - assert_eq!(hand.get_card(3), Some(Card::from_string("Qs").unwrap())); - } - - #[test] - fn test_hand_sort() { - let mut hand = Hand::from_string("2s As Ks").unwrap(); - hand.sort(); - assert_eq!(hand.get_card(1), Some(Card::from_string("As").unwrap())); - assert_eq!(hand.get_card(2), Some(Card::from_string("Ks").unwrap())); - assert_eq!(hand.get_card(3), Some(Card::from_string("2s").unwrap())); - } - - #[test] - fn test_hand_sort_with_empty_slots() { - let mut hand = Hand::new(); - hand.add_card(Card::from_string("2s").unwrap()).unwrap(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.sort(); - assert_eq!(hand.get_card(1), Some(Card::from_string("As").unwrap())); - assert_eq!(hand.get_card(2), Some(Card::from_string("Ks").unwrap())); - assert_eq!(hand.get_card(3), Some(Card::from_string("2s").unwrap())); - } -} diff --git a/core/src/api/hand_eval.rs b/core/src/api/hand_eval.rs deleted file mode 100644 index 553f6ad..0000000 --- a/core/src/api/hand_eval.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! # Poker Hand Evaluation API -//! -//! This module defines the core trait for poker hand evaluation implementations. -//! The `HandEval` trait provides a standardized interface for evaluating poker -//! hands and generating human-readable descriptions of hand strengths. -//! -//! ## Hand Ranking System -//! -//! The evaluation system uses a 32-bit integer ranking where: -//! - Bits 24-27: Hand category (straight flush, quads, etc.) -//! - Bits 0-23: Relative value within the category -//! -//! Higher values represent stronger hands, with the absolute strongest -//! hand (royal flush) having the highest value. -//! -//! ## Trait Design -//! -//! The trait is designed to support multiple evaluation implementations: -//! - Lookup table based (current implementation) -//! - Procedural evaluation algorithms -//! - Hardware-accelerated evaluators -//! -//! All implementations must produce identical rankings for fair comparison. - -use crate::api::card::Card; -use crate::api::hand::Hand; - -/// Core trait for poker hand evaluation implementations. -/// -/// This trait defines the standard interface that all poker hand evaluators -/// must implement. It supports evaluation of 5, 6, and 7-card hands as well -/// as generation of human-readable hand descriptions. -/// -/// # Hand Categories -/// The ranking system recognizes these hand categories in descending order: -/// - Straight Flush (9) -/// - Four of a Kind (8) -/// - Full House (7) -/// - Flush (6) -/// - Straight (5) -/// - Three of a Kind (4) -/// - Two Pair (3) -/// - One Pair (2) -/// - High Card (1) -pub trait HandEval { - /// Bit shift for extracting hand category from rank value. - /// Categories occupy bits 24-27 of the 32-bit rank. - const HAND_CATEGORY_SHIFT: u32 = 24; - - /// Bit mask for extracting hand category from rank value. - /// Uses bits 24-27: 0xF0000000 - const HAND_CATEGORY_MASK: u32 = 0xF << Self::HAND_CATEGORY_SHIFT; - - /// Bit mask for extracting relative value within hand category. - /// Uses bits 0-23: 0x00FFFFFF - const VALUE_MASK: u32 = 0x000FFFFF; - - /// Evaluates a poker hand of any size (5, 6, or 7 cards). - /// - /// This method automatically handles hands of different sizes by finding - /// the best 5-card combination for hands with 6 or 7 cards. - /// - /// # Arguments - /// * `hand` - The poker hand to evaluate (5-7 cards) - /// - /// # Returns - /// * `u32` - Standardized hand rank value - fn rank_hand(&self, hand: &Hand) -> u32; - - /// Evaluates a 7-card poker hand specifically. - /// - /// This method considers all possible 5-card combinations from the 7 cards - /// and returns the rank of the best possible hand. - /// - /// # Arguments - /// * `cards` - Array of exactly 7 cards to evaluate - /// - /// # Returns - /// * `u32` - Standardized hand rank value of best 5-card combination - fn rank_hand7(&self, cards: &[Card; 7]) -> u32; - - /// Generates a human-readable description of a hand rank value. - /// - /// Converts a numeric rank into a descriptive string showing the - /// hand type and relevant card values. - /// - /// # Arguments - /// * `hand_value` - Numeric rank value to describe - /// - /// # Returns - /// * `String` - Human-readable hand description - /// - /// # Example - /// ```text - /// "Straight Flush (A-K-Q-J-T)" - /// "Four of a Kind (Aces)" - /// "Full House (Kings over Queens)" - /// ``` - fn hand_description(&self, hand_value: u32) -> String; - - /// Generates a description of a 5-card hand. - /// - /// Convenience method that evaluates the hand and then describes it. - /// - /// # Arguments - /// * `hand` - The 5-card hand to evaluate and describe - /// - /// # Returns - /// * `String` - Human-readable description of the hand - fn hand_description_hand(&self, hand: &Hand) -> String; - - /// Generates a description of a 7-card hand. - /// - /// Convenience method that evaluates the best 5-card combination - /// from 7 cards and then describes it. - /// - /// # Arguments - /// * `cards` - Array of 7 cards to evaluate and describe - /// - /// # Returns - /// * `String` - Human-readable description of the best hand - fn hand_description7(&self, cards: &[Card; 7]) -> String; -} diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs deleted file mode 100644 index 321c628..0000000 --- a/core/src/api/mod.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! # Poker API Module - Bot Testing Framework -//! -//! This module provides the core application programming interface (API) specifically -//! designed for poker bot testing and development. It offers Java-compatible types, -//! traits, and functionality optimized for automated poker bot testing frameworks, -//! ensuring consistent behavior across different poker tools and testing environments. -//! -//! ## Poker Testbed Context -//! -//! The API module serves as the foundation for comprehensive poker bot testing: -//! - **Bot Development**: Standardized interfaces for poker bot implementations -//! - **Testing Frameworks**: Consistent APIs for automated bot testing scenarios -//! - **Tool Integration**: Java-compatible interfaces for existing poker tool ecosystems -//! - **Performance Testing**: Efficient data structures for high-performance bot evaluation -//! - **Cross-Platform Testing**: Consistent behavior across different poker environments -//! -//! ## Java Compatibility Design -//! -//! All API components maintain strict Java compatibility: -//! - **Card Encoding**: Uses Java Meerkat API card representation standards -//! - **Hand Evaluation**: Compatible with Java poker hand evaluation results -//! - **Action Types**: Matches Java poker action semantics and behavior -//! - **Game State**: Provides Java-compatible game state information -//! - **Observer Pattern**: Event system compatible with Java poker frameworks -//! -//! ## Module Overview -//! -//! The API module is organized into bot-focused areas: -//! - **Card Management**: [`card`] - Java-compatible playing card representation -//! - **Hand Management**: [`hand`] - Poker hand collections optimized for bot testing -//! - **Deck Operations**: [`deck`] - Standard 52-card deck with bot-friendly operations -//! - **Player Actions**: [`action`] - Betting actions for bot decision making -//! - **Player Interface**: [`player`] - Trait-based bot implementation interface -//! - **Game Information**: [`game_info`] - Game state access for bot analysis -//! - **Game Observation**: [`game_observer`] - Event-driven updates for bot testing -//! - **Player Information**: [`player_info`] - Individual player state for bot queries -//! - **Preferences**: [`preferences`] - Configuration management for bot customization -//! - **Hand Evaluation**: [`hand_eval`] - Trait for pluggable evaluation algorithms -//! -//! ## 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 for Bot Testing -//! -//! - **Java Compatibility**: Full compatibility with Java Meerkat API for tool integration -//! - **Bot-Friendly APIs**: Optimized interfaces for poker bot development and testing -//! - **Type Safety**: Strongly typed card and hand representations for reliable bot code -//! - **Memory Efficiency**: Compact binary representations for high-performance testing -//! - **Extensible Design**: Trait-based architecture for custom bot implementations -//! - **Event-Driven**: Observer pattern for reactive bot testing scenarios -//! - **Configuration**: Flexible preference system for bot customization and testing -//! - **Performance Optimized**: Fast operations for large-scale bot testing scenarios -//! -//! ## Examples - Bot Testing Scenarios -//! -//! ### Basic Bot Card and Hand Operations -//! -//! ```rust -//! use poker_api::api::{card::Card, hand::Hand, deck::Deck}; -//! -//! // Create hole cards for bot analysis (Java-compatible encoding) -//! let ace_spades = Card::from_string("As").unwrap(); -//! let king_spades = Card::from_string("Ks").unwrap(); -//! -//! let mut hole_cards = Hand::new(); -//! hole_cards.add_card(ace_spades).unwrap(); -//! hole_cards.add_card(king_spades).unwrap(); -//! -//! // Bot can quickly assess hole card strength -//! println!("Hole cards: {}", hole_cards.to_string()); -//! println!("Card count: {}", hole_cards.size()); -//! ``` -//! -//! ### Bot Action Decision Making -//! -//! ```rust -//! use poker_api::api::action::{Action, ActionType}; -//! -//! // Bot creates actions for different scenarios -//! let fold_action = Action::fold_action(50.0); // Weak hand -//! let bet_action = Action::bet_action(100.0); // Strong hand -//! let raise_action = Action::raise_action(50.0, 200.0); // Premium hand -//! -//! // Bot can analyze action properties for decision making -//! println!("Action type: {:?}", fold_action.get_action_type()); -//! println!("Amount: ${}", fold_action.get_amount()); -//! ``` -//! -//! ### Bot Game State Analysis -//! -//! ```rust -//! use poker_api::api::game_info::GameInfo; -//! -//! // Bot analyzes game state for decision making -//! fn bot_analyze_game(game_info: &dyn GameInfo) { -//! // Assess pot odds for bot strategy -//! let pot_size = game_info.get_total_pot_size(); -//! let num_players = game_info.get_num_players(); -//! let current_stage = game_info.get_stage(); -//! -//! println!("Pot size: ${} - Players: {} - Stage: {:?}", -//! pot_size, num_players, current_stage); -//! -//! // Bot uses this information for strategy decisions -//! let pot_odds = pot_size as f64 / 100.0; // Simplified calculation -//! println!("Pot odds: {:.2}", pot_odds); -//! } -//! ``` -//! -//! ### Bot Hand Evaluation Integration -//! -//! ```rust -//! use poker_api::api::{card::Card, hand::Hand}; -//! use poker_api::hand_evaluator::LookupHandEvaluator; -//! -//! // Bot evaluates hand strength for decision making -//! fn bot_evaluate_hand_strength() { -//! let evaluator = LookupHandEvaluator::new().unwrap(); -//! -//! let mut hand = Hand::new(); -//! hand.add_card(Card::from_string("As").unwrap()).unwrap(); -//! hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); -//! hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); -//! hand.add_card(Card::from_string("Js").unwrap()).unwrap(); -//! hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); -//! -//! // Bot gets Java-compatible hand rank for strategy -//! let rank = evaluator.rank_hand(&hand); -//! println!("Hand rank (Java-compatible): {}", rank); -//! -//! // Lower rank = stronger hand (matches Java convention) -//! if rank < 1000 { // Strong hand threshold -//! println!("Bot: This is a strong hand!"); -//! } -//! } -//! ``` -//! -//! ## Bot Testing Architecture -//! -//! The API module follows a bot-focused layered architecture: -//! - **Data Types**: Java-compatible concrete types for cards, hands, and actions -//! - **Bot Interfaces**: Trait-based architecture for poker bot implementations -//! - **Testing Utilities**: Specialized utilities for automated bot testing scenarios -//! - **Event System**: Observer pattern for reactive bot testing and analysis -//! - **Configuration**: Flexible preference system for bot customization and testing -//! - **Integration**: Seamless integration with existing Java poker tools and frameworks -//! -//! ## Bot Testing Performance Considerations -//! -//! - **Memory Layout**: Types optimized for cache efficiency in bot testing scenarios -//! - **Copy Semantics**: Small types implement Copy for zero-cost bot operations -//! - **Java Compatibility**: Maintains Java performance characteristics for tool integration -//! - **Trait Objects**: Used sparingly to minimize vtable overhead in bot implementations -//! - **Inline Operations**: Common bot operations inlined for maximum testing speed -//! - **Batch Processing**: Optimized for batch hand evaluation in bot testing frameworks -//! - **Memory Efficiency**: Compact representations for large-scale bot testing scenarios -//! - **Fast Cloning**: Efficient hand and deck cloning for bot simulation scenarios - -pub mod action; -pub mod card; -pub mod deck; -pub mod game_info; -pub mod game_observer; -pub mod hand; -mod hand_eval; -pub mod player; -pub mod player_info; -pub mod preferences; diff --git a/core/src/api/player.rs b/core/src/api/player.rs deleted file mode 100644 index 24ce893..0000000 --- a/core/src/api/player.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! # 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) { -//! // 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) {} -//! } -//! ``` -//! -//! ## 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::card::Card; -use crate::api::game_observer::GameObserver; -use crate::api::preferences::Preferences; - -pub trait Player: GameObserver { - /// Initialize your player from the given preferences. - fn init(&mut self, prefs: Box); - - /// Receive your hole cards. - fn hole_cards(&mut self, c1: Card, c2: Card, seat: usize); - - /// Requests an Action from the player. Called when it is the Player's turn to act. - fn get_action(&mut self) -> Action; -} diff --git a/core/src/api/player_info.rs b/core/src/api/player_info.rs deleted file mode 100644 index 633a6e0..0000000 --- a/core/src/api/player_info.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! # 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::hand::Hand; - -pub trait PlayerInfo { - // Player State - fn get_net_gain(&self) -> f64; - fn get_bankroll(&self) -> f64; - fn get_bankroll_at_start_of_hand(&self) -> f64; - fn get_bankroll_in_small_bets(&self) -> f64; - fn in_game(&self) -> bool; - fn get_seat(&self) -> usize; - fn get_name(&self) -> String; - - // Hand State - fn is_all_in(&self) -> bool; - fn is_sitting_out(&self) -> bool; - fn get_revealed_hand(&self) -> Option; - fn is_active(&self) -> bool; - fn is_folded(&self) -> bool; - fn is_button(&self) -> bool; - - // Pot and Betting - fn get_amount_to_call(&self) -> f64; - fn is_committed(&self) -> bool; - fn has_acted_this_round(&self) -> bool; - fn get_amount_in_pot(&self) -> f64; - fn get_amount_in_pot_this_round(&self) -> f64; - - // Actions - fn get_last_action(&self) -> u8; // Use constants like Holdem::FOLD, Holdem::CALL, etc. - fn get_game_info(&self) -> &dyn GameInfo; - - // Raising and Calling - fn has_enough_to_raise(&self) -> bool; - fn get_amount_raiseable(&self) -> f64; - fn get_amount_callable(&self) -> f64; - fn get_bankroll_at_risk(&self) -> f64; - fn get_raise_amount(&self, amount_to_raise: f64) -> f64; - - // Miscellaneous - fn to_string(&self) -> String; -} diff --git a/core/src/api/preferences.rs b/core/src/api/preferences.rs deleted file mode 100644 index 83f1cd2..0000000 --- a/core/src/api/preferences.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! # 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, -//! } -//! -//! 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 { - /// Get a preference as a string, with a default value. - fn get(&self, name: &str, default: &str) -> String; - - /// Get a preference as a boolean, with a default value. - fn get_boolean(&self, name: &str, default: bool) -> bool; - - /// Get a preference as an integer, with a default value. - fn get_int(&self, name: &str, default: i32) -> i32; - - /// Get a preference as a double, with a default value. - fn get_double(&self, name: &str, default: f64) -> f64; - - /// Set a preference as a string. - fn set(&mut self, name: &str, value: &str); - - /// Set a preference as a boolean. - fn set_boolean(&mut self, name: &str, value: bool); - - /// Set a preference as an integer. - fn set_int(&mut self, name: &str, value: i32); - - /// Set a preference as a double. - fn set_double(&mut self, name: &str, value: f64); - - /// Check if a preference exists. - fn has_preference(&self, name: &str) -> bool; - - /// Remove a preference. - fn remove_preference(&mut self, name: &str); - - /// Save preferences to a file. - fn save(&self, file_path: &str) -> Result<(), String>; - - /// Load preferences from a file. - fn load(&mut self, file_path: &str) -> Result<(), String>; -} diff --git a/core/src/evaluator_generator/flushes.rs b/core/src/evaluator_generator/flushes.rs deleted file mode 100644 index a0d27f7..0000000 --- a/core/src/evaluator_generator/flushes.rs +++ /dev/null @@ -1,409 +0,0 @@ -//! # 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 table: &'static [u16], -} - -impl Flushes { - pub const TABLE: [u16; 7937] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1599, 0, 0, 0, 0, 0, 0, 0, 1598, 0, 0, - 0, 1597, 0, 1596, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1595, 0, 0, 0, 0, 0, - 0, 0, 1594, 0, 0, 0, 1593, 0, 1592, 1591, 0, 0, 0, 0, 0, 0, 0, 0, 1590, 0, 0, 0, 1589, 0, - 1588, 1587, 0, 0, 0, 0, 1586, 0, 1585, 1584, 0, 0, 1583, 1582, 0, 7, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1581, 0, 0, 0, 0, 0, 0, 0, 1580, 0, 0, 0, 1579, 0, 1578, - 1577, 0, 0, 0, 0, 0, 0, 0, 0, 1576, 0, 0, 0, 1575, 0, 1574, 1573, 0, 0, 0, 0, 1572, 0, - 1571, 1570, 0, 0, 1569, 1568, 0, 1567, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1566, 0, 0, 0, 1565, - 0, 1564, 1563, 0, 0, 0, 0, 1562, 0, 1561, 1560, 0, 0, 1559, 1558, 0, 1557, 0, 0, 0, 0, 0, - 0, 1556, 0, 1555, 1554, 0, 0, 1553, 1552, 0, 1551, 0, 0, 0, 0, 1550, 1549, 0, 1548, 0, 0, - 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1547, 0, 0, 0, 0, - 0, 0, 0, 1546, 0, 0, 0, 1545, 0, 1544, 1543, 0, 0, 0, 0, 0, 0, 0, 0, 1542, 0, 0, 0, 1541, - 0, 1540, 1539, 0, 0, 0, 0, 1538, 0, 1537, 1536, 0, 0, 1535, 1534, 0, 1533, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1532, 0, 0, 0, 1531, 0, 1530, 1529, 0, 0, 0, 0, 1528, 0, 1527, 1526, 0, 0, - 1525, 1524, 0, 1523, 0, 0, 0, 0, 0, 0, 1522, 0, 1521, 1520, 0, 0, 1519, 1518, 0, 1517, 0, - 0, 0, 0, 1516, 1515, 0, 1514, 0, 0, 0, 1513, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1512, 0, 0, 0, 1511, 0, 1510, 1509, 0, 0, 0, 0, 1508, 0, 1507, 1506, 0, 0, 1505, 1504, 0, - 1503, 0, 0, 0, 0, 0, 0, 1502, 0, 1501, 1500, 0, 0, 1499, 1498, 0, 1497, 0, 0, 0, 0, 1496, - 1495, 0, 1494, 0, 0, 0, 1493, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1492, 0, 1491, 1490, 0, 0, - 1489, 1488, 0, 1487, 0, 0, 0, 0, 1486, 1485, 0, 1484, 0, 0, 0, 1483, 0, 0, 0, 0, 0, 0, 0, - 0, 1482, 1481, 0, 1480, 0, 0, 0, 1479, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1478, 0, 0, 0, 0, 0, 0, 0, - 1477, 0, 0, 0, 1476, 0, 1475, 1474, 0, 0, 0, 0, 0, 0, 0, 0, 1473, 0, 0, 0, 1472, 0, 1471, - 1470, 0, 0, 0, 0, 1469, 0, 1468, 1467, 0, 0, 1466, 1465, 0, 1464, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1463, 0, 0, 0, 1462, 0, 1461, 1460, 0, 0, 0, 0, 1459, 0, 1458, 1457, 0, 0, 1456, - 1455, 0, 1454, 0, 0, 0, 0, 0, 0, 1453, 0, 1452, 1451, 0, 0, 1450, 1449, 0, 1448, 0, 0, 0, - 0, 1447, 1446, 0, 1445, 0, 0, 0, 1444, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1443, 0, - 0, 0, 1442, 0, 1441, 1440, 0, 0, 0, 0, 1439, 0, 1438, 1437, 0, 0, 1436, 1435, 0, 1434, 0, - 0, 0, 0, 0, 0, 1433, 0, 1432, 1431, 0, 0, 1430, 1429, 0, 1428, 0, 0, 0, 0, 1427, 1426, 0, - 1425, 0, 0, 0, 1424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1423, 0, 1422, 1421, 0, 0, 1420, 1419, - 0, 1418, 0, 0, 0, 0, 1417, 1416, 0, 1415, 0, 0, 0, 1414, 0, 0, 0, 0, 0, 0, 0, 0, 1413, - 1412, 0, 1411, 0, 0, 0, 1410, 0, 0, 0, 0, 0, 0, 0, 1409, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1408, 0, 0, 0, 1407, 0, 1406, 1405, 0, 0, 0, 0, 1404, 0, - 1403, 1402, 0, 0, 1401, 1400, 0, 1399, 0, 0, 0, 0, 0, 0, 1398, 0, 1397, 1396, 0, 0, 1395, - 1394, 0, 1393, 0, 0, 0, 0, 1392, 1391, 0, 1390, 0, 0, 0, 1389, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1388, 0, 1387, 1386, 0, 0, 1385, 1384, 0, 1383, 0, 0, 0, 0, 1382, 1381, 0, 1380, 0, 0, - 0, 1379, 0, 0, 0, 0, 0, 0, 0, 0, 1378, 1377, 0, 1376, 0, 0, 0, 1375, 0, 0, 0, 0, 0, 0, 0, - 1374, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1373, 0, 1372, 1371, 0, 0, - 1370, 1369, 0, 1368, 0, 0, 0, 0, 1367, 1366, 0, 1365, 0, 0, 0, 1364, 0, 0, 0, 0, 0, 0, 0, - 0, 1363, 1362, 0, 1361, 0, 0, 0, 1360, 0, 0, 0, 0, 0, 0, 0, 1359, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1358, 1357, 0, 1356, 0, 0, 0, 1355, 0, 0, 0, 0, 0, 0, 0, 1354, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1353, 0, 0, 0, 0, 0, 0, 0, 1352, 0, 0, 0, 1351, 0, 1350, 1349, 0, 0, 0, 0, 0, 0, 0, 0, - 1348, 0, 0, 0, 1347, 0, 1346, 1345, 0, 0, 0, 0, 1344, 0, 1343, 1342, 0, 0, 1341, 1340, 0, - 1339, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1338, 0, 0, 0, 1337, 0, 1336, 1335, 0, 0, 0, 0, 1334, - 0, 1333, 1332, 0, 0, 1331, 1330, 0, 1329, 0, 0, 0, 0, 0, 0, 1328, 0, 1327, 1326, 0, 0, - 1325, 1324, 0, 1323, 0, 0, 0, 0, 1322, 1321, 0, 1320, 0, 0, 0, 1319, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1318, 0, 0, 0, 1317, 0, 1316, 1315, 0, 0, 0, 0, 1314, 0, 1313, 1312, - 0, 0, 1311, 1310, 0, 1309, 0, 0, 0, 0, 0, 0, 1308, 0, 1307, 1306, 0, 0, 1305, 1304, 0, - 1303, 0, 0, 0, 0, 1302, 1301, 0, 1300, 0, 0, 0, 1299, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1298, - 0, 1297, 1296, 0, 0, 1295, 1294, 0, 1293, 0, 0, 0, 0, 1292, 1291, 0, 1290, 0, 0, 0, 1289, - 0, 0, 0, 0, 0, 0, 0, 0, 1288, 1287, 0, 1286, 0, 0, 0, 1285, 0, 0, 0, 0, 0, 0, 0, 1284, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1283, 0, 0, 0, 1282, 0, - 1281, 1280, 0, 0, 0, 0, 1279, 0, 1278, 1277, 0, 0, 1276, 1275, 0, 1274, 0, 0, 0, 0, 0, 0, - 1273, 0, 1272, 1271, 0, 0, 1270, 1269, 0, 1268, 0, 0, 0, 0, 1267, 1266, 0, 1265, 0, 0, 0, - 1264, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1263, 0, 1262, 1261, 0, 0, 1260, 1259, 0, 1258, 0, 0, - 0, 0, 1257, 1256, 0, 1255, 0, 0, 0, 1254, 0, 0, 0, 0, 0, 0, 0, 0, 1253, 1252, 0, 1251, 0, - 0, 0, 1250, 0, 0, 0, 0, 0, 0, 0, 1249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1248, 0, 1247, 1246, 0, 0, 1245, 1244, 0, 1243, 0, 0, 0, 0, 1242, 1241, 0, 1240, 0, 0, - 0, 1239, 0, 0, 0, 0, 0, 0, 0, 0, 1238, 1237, 0, 1236, 0, 0, 0, 1235, 0, 0, 0, 0, 0, 0, 0, - 1234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1233, 1232, 0, 1231, 0, 0, 0, 1230, - 0, 0, 0, 0, 0, 0, 0, 1229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1228, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1227, 0, 0, 0, 1226, 0, 1225, 1224, 0, 0, 0, 0, 1223, 0, 1222, 1221, 0, 0, - 1220, 1219, 0, 1218, 0, 0, 0, 0, 0, 0, 1217, 0, 1216, 1215, 0, 0, 1214, 1213, 0, 1212, 0, - 0, 0, 0, 1211, 1210, 0, 1209, 0, 0, 0, 1208, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1207, 0, 1206, - 1205, 0, 0, 1204, 1203, 0, 1202, 0, 0, 0, 0, 1201, 1200, 0, 1199, 0, 0, 0, 1198, 0, 0, 0, - 0, 0, 0, 0, 0, 1197, 1196, 0, 1195, 0, 0, 0, 1194, 0, 0, 0, 0, 0, 0, 0, 1193, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1192, 0, 1191, 1190, 0, 0, 1189, 1188, 0, 1187, - 0, 0, 0, 0, 1186, 1185, 0, 1184, 0, 0, 0, 1183, 0, 0, 0, 0, 0, 0, 0, 0, 1182, 1181, 0, - 1180, 0, 0, 0, 1179, 0, 0, 0, 0, 0, 0, 0, 1178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1177, 1176, 0, 1175, 0, 0, 0, 1174, 0, 0, 0, 0, 0, 0, 0, 1173, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1171, 0, 1170, 1169, 0, 0, 1168, 1167, 0, 1166, - 0, 0, 0, 0, 1165, 1164, 0, 1163, 0, 0, 0, 1162, 0, 0, 0, 0, 0, 0, 0, 0, 1161, 1160, 0, - 1159, 0, 0, 0, 1158, 0, 0, 0, 0, 0, 0, 0, 1157, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1156, 1155, 0, 1154, 0, 0, 0, 1153, 0, 0, 0, 0, 0, 0, 0, 1152, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1151, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1150, 1149, 0, 1148, 0, 0, 0, 1147, 0, 0, 0, 0, 0, 0, - 0, 1146, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1145, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1144, 0, 0, 0, 0, 0, 0, 0, 1143, 0, 0, 0, 1142, 0, 1141, - 1140, 0, 0, 0, 0, 0, 0, 0, 0, 1139, 0, 0, 0, 1138, 0, 1137, 1136, 0, 0, 0, 0, 1135, 0, - 1134, 1133, 0, 0, 1132, 1131, 0, 1130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1129, 0, 0, 0, 1128, - 0, 1127, 1126, 0, 0, 0, 0, 1125, 0, 1124, 1123, 0, 0, 1122, 1121, 0, 1120, 0, 0, 0, 0, 0, - 0, 1119, 0, 1118, 1117, 0, 0, 1116, 1115, 0, 1114, 0, 0, 0, 0, 1113, 1112, 0, 1111, 0, 0, - 0, 1110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1109, 0, 0, 0, 1108, 0, 1107, 1106, 0, - 0, 0, 0, 1105, 0, 1104, 1103, 0, 0, 1102, 1101, 0, 1100, 0, 0, 0, 0, 0, 0, 1099, 0, 1098, - 1097, 0, 0, 1096, 1095, 0, 1094, 0, 0, 0, 0, 1093, 1092, 0, 1091, 0, 0, 0, 1090, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1089, 0, 1088, 1087, 0, 0, 1086, 1085, 0, 1084, 0, 0, 0, 0, 1083, - 1082, 0, 1081, 0, 0, 0, 1080, 0, 0, 0, 0, 0, 0, 0, 0, 1079, 1078, 0, 1077, 0, 0, 0, 1076, - 0, 0, 0, 0, 0, 0, 0, 1075, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1074, 0, 0, 0, 1073, 0, 1072, 1071, 0, 0, 0, 0, 1070, 0, 1069, 1068, 0, 0, 1067, 1066, - 0, 1065, 0, 0, 0, 0, 0, 0, 1064, 0, 1063, 1062, 0, 0, 1061, 1060, 0, 1059, 0, 0, 0, 0, - 1058, 1057, 0, 1056, 0, 0, 0, 1055, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1054, 0, 1053, 1052, 0, - 0, 1051, 1050, 0, 1049, 0, 0, 0, 0, 1048, 1047, 0, 1046, 0, 0, 0, 1045, 0, 0, 0, 0, 0, 0, - 0, 0, 1044, 1043, 0, 1042, 0, 0, 0, 1041, 0, 0, 0, 0, 0, 0, 0, 1040, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1039, 0, 1038, 1037, 0, 0, 1036, 1035, 0, 1034, 0, 0, 0, - 0, 1033, 1032, 0, 1031, 0, 0, 0, 1030, 0, 0, 0, 0, 0, 0, 0, 0, 1029, 1028, 0, 1027, 0, 0, - 0, 1026, 0, 0, 0, 0, 0, 0, 0, 1025, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1024, - 1023, 0, 1022, 0, 0, 0, 1021, 0, 0, 0, 0, 0, 0, 0, 1020, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1019, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1018, 0, 0, 0, 1017, 0, 1016, 1015, 0, 0, 0, 0, - 1014, 0, 1013, 1012, 0, 0, 1011, 1010, 0, 1009, 0, 0, 0, 0, 0, 0, 1008, 0, 1007, 1006, 0, - 0, 1005, 1004, 0, 1003, 0, 0, 0, 0, 1002, 1001, 0, 1000, 0, 0, 0, 999, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 998, 0, 997, 996, 0, 0, 995, 994, 0, 993, 0, 0, 0, 0, 992, 991, 0, 990, 0, 0, 0, - 989, 0, 0, 0, 0, 0, 0, 0, 0, 988, 987, 0, 986, 0, 0, 0, 985, 0, 0, 0, 0, 0, 0, 0, 984, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 983, 0, 982, 981, 0, 0, 980, 979, 0, - 978, 0, 0, 0, 0, 977, 976, 0, 975, 0, 0, 0, 974, 0, 0, 0, 0, 0, 0, 0, 0, 973, 972, 0, 971, - 0, 0, 0, 970, 0, 0, 0, 0, 0, 0, 0, 969, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 968, 967, 0, 966, 0, 0, 0, 965, 0, 0, 0, 0, 0, 0, 0, 964, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 963, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 962, 0, 961, 960, 0, 0, 959, 958, 0, 957, 0, 0, 0, 0, 956, 955, - 0, 954, 0, 0, 0, 953, 0, 0, 0, 0, 0, 0, 0, 0, 952, 951, 0, 950, 0, 0, 0, 949, 0, 0, 0, 0, - 0, 0, 0, 948, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 947, 946, 0, 945, 0, 0, 0, - 944, 0, 0, 0, 0, 0, 0, 0, 943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 942, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 941, - 940, 0, 939, 0, 0, 0, 938, 0, 0, 0, 0, 0, 0, 0, 937, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 936, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 935, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 934, 0, 0, 0, 933, 0, 932, 931, 0, 0, 0, 0, - 930, 0, 929, 928, 0, 0, 927, 926, 0, 925, 0, 0, 0, 0, 0, 0, 924, 0, 923, 922, 0, 0, 921, - 920, 0, 919, 0, 0, 0, 0, 918, 917, 0, 916, 0, 0, 0, 915, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 914, - 0, 913, 912, 0, 0, 911, 910, 0, 909, 0, 0, 0, 0, 908, 907, 0, 906, 0, 0, 0, 905, 0, 0, 0, - 0, 0, 0, 0, 0, 904, 903, 0, 902, 0, 0, 0, 901, 0, 0, 0, 0, 0, 0, 0, 900, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 899, 0, 898, 897, 0, 0, 896, 895, 0, 894, 0, 0, 0, 0, - 893, 892, 0, 891, 0, 0, 0, 890, 0, 0, 0, 0, 0, 0, 0, 0, 889, 888, 0, 887, 0, 0, 0, 886, 0, - 0, 0, 0, 0, 0, 0, 885, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 884, 883, 0, 882, 0, - 0, 0, 881, 0, 0, 0, 0, 0, 0, 0, 880, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 879, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 878, 0, 877, 876, 0, 0, 875, 874, 0, 873, 0, 0, 0, 0, 872, 871, 0, 870, 0, 0, 0, - 869, 0, 0, 0, 0, 0, 0, 0, 0, 868, 867, 0, 866, 0, 0, 0, 865, 0, 0, 0, 0, 0, 0, 0, 864, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 863, 862, 0, 861, 0, 0, 0, 860, 0, 0, 0, 0, 0, - 0, 0, 859, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 858, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 857, 856, 0, 855, 0, 0, - 0, 854, 0, 0, 0, 0, 0, 0, 0, 853, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 852, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 851, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 850, 0, 849, 848, 0, 0, 847, 846, 0, 845, 0, 0, 0, 0, 844, 843, 0, 842, - 0, 0, 0, 841, 0, 0, 0, 0, 0, 0, 0, 0, 840, 839, 0, 838, 0, 0, 0, 837, 0, 0, 0, 0, 0, 0, 0, - 836, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 835, 834, 0, 833, 0, 0, 0, 832, 0, 0, - 0, 0, 0, 0, 0, 831, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 830, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 829, 828, 0, - 827, 0, 0, 0, 826, 0, 0, 0, 0, 0, 0, 0, 825, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 824, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 823, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 822, 821, 0, 820, 0, 0, 0, 819, 0, 0, 0, 0, 0, 0, 0, 818, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 817, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 816, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 815, 0, 0, 0, 814, 0, 813, 812, 0, - 0, 0, 0, 0, 0, 0, 0, 811, 0, 0, 0, 810, 0, 809, 808, 0, 0, 0, 0, 807, 0, 806, 805, 0, 0, - 804, 803, 0, 802, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 801, 0, 0, 0, 800, 0, 799, 798, 0, 0, 0, 0, - 797, 0, 796, 795, 0, 0, 794, 793, 0, 792, 0, 0, 0, 0, 0, 0, 791, 0, 790, 789, 0, 0, 788, - 787, 0, 786, 0, 0, 0, 0, 785, 784, 0, 783, 0, 0, 0, 782, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 781, 0, 0, 0, 780, 0, 779, 778, 0, 0, 0, 0, 777, 0, 776, 775, 0, 0, 774, 773, 0, - 772, 0, 0, 0, 0, 0, 0, 771, 0, 770, 769, 0, 0, 768, 767, 0, 766, 0, 0, 0, 0, 765, 764, 0, - 763, 0, 0, 0, 762, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 761, 0, 760, 759, 0, 0, 758, 757, 0, 756, - 0, 0, 0, 0, 755, 754, 0, 753, 0, 0, 0, 752, 0, 0, 0, 0, 0, 0, 0, 0, 751, 750, 0, 749, 0, 0, - 0, 748, 0, 0, 0, 0, 0, 0, 0, 747, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 746, 0, 0, 0, 745, 0, 744, 743, 0, 0, 0, 0, 742, 0, 741, 740, 0, 0, 739, 738, 0, - 737, 0, 0, 0, 0, 0, 0, 736, 0, 735, 734, 0, 0, 733, 732, 0, 731, 0, 0, 0, 0, 730, 729, 0, - 728, 0, 0, 0, 727, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 726, 0, 725, 724, 0, 0, 723, 722, 0, 721, - 0, 0, 0, 0, 720, 719, 0, 718, 0, 0, 0, 717, 0, 0, 0, 0, 0, 0, 0, 0, 716, 715, 0, 714, 0, 0, - 0, 713, 0, 0, 0, 0, 0, 0, 0, 712, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 711, 0, 710, 709, 0, 0, 708, 707, 0, 706, 0, 0, 0, 0, 705, 704, 0, 703, 0, 0, 0, 702, 0, 0, - 0, 0, 0, 0, 0, 0, 701, 700, 0, 699, 0, 0, 0, 698, 0, 0, 0, 0, 0, 0, 0, 697, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 696, 695, 0, 694, 0, 0, 0, 693, 0, 0, 0, 0, 0, 0, 0, 692, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 691, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 690, 0, 0, 0, 689, - 0, 688, 687, 0, 0, 0, 0, 686, 0, 685, 684, 0, 0, 683, 682, 0, 681, 0, 0, 0, 0, 0, 0, 680, - 0, 679, 678, 0, 0, 677, 676, 0, 675, 0, 0, 0, 0, 674, 673, 0, 672, 0, 0, 0, 671, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 670, 0, 669, 668, 0, 0, 667, 666, 0, 665, 0, 0, 0, 0, 664, 663, 0, - 662, 0, 0, 0, 661, 0, 0, 0, 0, 0, 0, 0, 0, 660, 659, 0, 658, 0, 0, 0, 657, 0, 0, 0, 0, 0, - 0, 0, 656, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 655, 0, 654, 653, 0, 0, - 652, 651, 0, 650, 0, 0, 0, 0, 649, 648, 0, 647, 0, 0, 0, 646, 0, 0, 0, 0, 0, 0, 0, 0, 645, - 644, 0, 643, 0, 0, 0, 642, 0, 0, 0, 0, 0, 0, 0, 641, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 640, 639, 0, 638, 0, 0, 0, 637, 0, 0, 0, 0, 0, 0, 0, 636, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 635, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 634, 0, 633, 632, 0, 0, 631, 630, 0, 629, 0, 0, 0, 0, - 628, 627, 0, 626, 0, 0, 0, 625, 0, 0, 0, 0, 0, 0, 0, 0, 624, 623, 0, 622, 0, 0, 0, 621, 0, - 0, 0, 0, 0, 0, 0, 620, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 619, 618, 0, 617, 0, - 0, 0, 616, 0, 0, 0, 0, 0, 0, 0, 615, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 614, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 613, 612, 0, 611, 0, 0, 0, 610, 0, 0, 0, 0, 0, 0, 0, 609, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 608, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 607, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 606, 0, 0, 0, 605, 0, 604, 603, 0, 0, - 0, 0, 602, 0, 601, 600, 0, 0, 599, 598, 0, 597, 0, 0, 0, 0, 0, 0, 596, 0, 595, 594, 0, 0, - 593, 592, 0, 591, 0, 0, 0, 0, 590, 589, 0, 588, 0, 0, 0, 587, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 586, 0, 585, 584, 0, 0, 583, 582, 0, 581, 0, 0, 0, 0, 580, 579, 0, 578, 0, 0, 0, 577, 0, 0, - 0, 0, 0, 0, 0, 0, 576, 575, 0, 574, 0, 0, 0, 573, 0, 0, 0, 0, 0, 0, 0, 572, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 571, 0, 570, 569, 0, 0, 568, 567, 0, 566, 0, 0, 0, - 0, 565, 564, 0, 563, 0, 0, 0, 562, 0, 0, 0, 0, 0, 0, 0, 0, 561, 560, 0, 559, 0, 0, 0, 558, - 0, 0, 0, 0, 0, 0, 0, 557, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 556, 555, 0, 554, - 0, 0, 0, 553, 0, 0, 0, 0, 0, 0, 0, 552, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 551, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 550, 0, 549, 548, 0, 0, 547, 546, 0, 545, 0, 0, 0, 0, 544, 543, 0, 542, 0, 0, - 0, 541, 0, 0, 0, 0, 0, 0, 0, 0, 540, 539, 0, 538, 0, 0, 0, 537, 0, 0, 0, 0, 0, 0, 0, 536, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 535, 534, 0, 533, 0, 0, 0, 532, 0, 0, 0, 0, - 0, 0, 0, 531, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 530, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 529, 528, 0, 527, 0, - 0, 0, 526, 0, 0, 0, 0, 0, 0, 0, 525, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 524, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 523, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 522, 0, 521, 520, 0, 0, 519, 518, 0, 517, 0, 0, 0, 0, 516, 515, 0, - 514, 0, 0, 0, 513, 0, 0, 0, 0, 0, 0, 0, 0, 512, 511, 0, 510, 0, 0, 0, 509, 0, 0, 0, 0, 0, - 0, 0, 508, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 507, 506, 0, 505, 0, 0, 0, 504, - 0, 0, 0, 0, 0, 0, 0, 503, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 502, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 501, 500, - 0, 499, 0, 0, 0, 498, 0, 0, 0, 0, 0, 0, 0, 497, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 496, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 495, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 494, 493, 0, 492, 0, 0, 0, 491, 0, 0, 0, 0, 0, 0, 0, 490, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 489, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 488, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 486, 0, 0, 0, 485, 0, 484, 483, 0, 0, 0, 0, 482, 0, 481, 480, 0, 0, 479, 478, 0, - 477, 0, 0, 0, 0, 0, 0, 476, 0, 475, 474, 0, 0, 473, 472, 0, 471, 0, 0, 0, 0, 470, 469, 0, - 468, 0, 0, 0, 467, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 466, 0, 465, 464, 0, 0, 463, 462, 0, 461, - 0, 0, 0, 0, 460, 459, 0, 458, 0, 0, 0, 457, 0, 0, 0, 0, 0, 0, 0, 0, 456, 455, 0, 454, 0, 0, - 0, 453, 0, 0, 0, 0, 0, 0, 0, 452, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 451, 0, 450, 449, 0, 0, 448, 447, 0, 446, 0, 0, 0, 0, 445, 444, 0, 443, 0, 0, 0, 442, 0, 0, - 0, 0, 0, 0, 0, 0, 441, 440, 0, 439, 0, 0, 0, 438, 0, 0, 0, 0, 0, 0, 0, 437, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 436, 435, 0, 434, 0, 0, 0, 433, 0, 0, 0, 0, 0, 0, 0, 432, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 430, 0, 429, 428, 0, 0, 427, - 426, 0, 425, 0, 0, 0, 0, 424, 423, 0, 422, 0, 0, 0, 421, 0, 0, 0, 0, 0, 0, 0, 0, 420, 419, - 0, 418, 0, 0, 0, 417, 0, 0, 0, 0, 0, 0, 0, 416, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 415, 414, 0, 413, 0, 0, 0, 412, 0, 0, 0, 0, 0, 0, 0, 411, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 410, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 409, 408, 0, 407, 0, 0, 0, 406, 0, 0, 0, 0, 0, 0, 0, 405, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 404, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 403, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 402, 0, 401, 400, 0, 0, - 399, 398, 0, 397, 0, 0, 0, 0, 396, 395, 0, 394, 0, 0, 0, 393, 0, 0, 0, 0, 0, 0, 0, 0, 392, - 391, 0, 390, 0, 0, 0, 389, 0, 0, 0, 0, 0, 0, 0, 388, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 387, 386, 0, 385, 0, 0, 0, 384, 0, 0, 0, 0, 0, 0, 0, 383, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 382, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 381, 380, 0, 379, 0, 0, 0, 378, 0, 0, 0, 0, 0, 0, 0, 377, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 376, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 375, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 374, 373, 0, 372, 0, 0, 0, - 371, 0, 0, 0, 0, 0, 0, 0, 370, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 369, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 368, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 367, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 366, 0, 365, 364, 0, 0, 363, 362, 0, 361, 0, 0, 0, - 0, 360, 359, 0, 358, 0, 0, 0, 357, 0, 0, 0, 0, 0, 0, 0, 0, 356, 355, 0, 354, 0, 0, 0, 353, - 0, 0, 0, 0, 0, 0, 0, 352, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 351, 350, 0, 349, - 0, 0, 0, 348, 0, 0, 0, 0, 0, 0, 0, 347, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 346, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 345, 344, 0, 343, 0, 0, 0, 342, 0, 0, 0, 0, 0, 0, 0, 341, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 339, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 338, 337, 0, 336, 0, 0, 0, 335, 0, 0, 0, 0, 0, 0, 0, - 334, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 332, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 331, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 330, 329, 0, 328, 0, 0, 0, 327, 0, 0, 0, 0, 0, 0, 0, 326, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 325, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 324, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 323, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - ]; - - pub fn new() -> Self { - Flushes { - table: &Self::TABLE, - } - } -} diff --git a/core/src/evaluator_generator/mod.rs b/core/src/evaluator_generator/mod.rs deleted file mode 100644 index d73f241..0000000 --- a/core/src/evaluator_generator/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! # Poker Hand Evaluation Table Generator Module -//! -//! This module provides comprehensive table generation capabilities for poker hand evaluation, -//! specifically designed for poker bot testing frameworks. It implements the Java Meerkat -//! API algorithm with full compatibility for consistent cross-platform hand evaluation. -//! -//! ## Poker Testbed Purpose -//! -//! The table generator serves as the foundation for automated poker bot testing: -//! - **Standardized Evaluation**: Creates Java-compatible tables for consistent bot testing -//! - **Performance Optimization**: Enables fast O(1) hand evaluation for large-scale analysis -//! - **Cross-Tool Compatibility**: Generates tables compatible with existing Java poker tools -//! - **Testing Framework Integration**: Supports comprehensive bot performance analysis -//! -//! ## Module Structure -//! -//! - [`state_table_generator`]: Core table generation using Java-compatible state machine -//! - [`flushes`]: Specialized algorithms for flush hand detection and ranking -//! - [`pair`]: Pair and two-pair hand evaluation logic -//! - [`products`]: Mathematical utilities for hand strength calculation -//! - [`unique`]: Unique hand type detection algorithms -//! - [`values`]: Hand value computation and ranking utilities -//! -//! ## Java Compatibility -//! -//! All modules maintain strict compatibility with the Java Meerkat API: -//! - Card encoding uses Java-style 8-bit format (rrrr-sss) -//! - Ranking system matches Java values (1 = best, 7462 = worst) -//! - Algorithm implementations follow Java reference patterns -//! - Generated tables are binary-compatible with Java tools -//! -//! ## Bot Testing Integration -//! -//! The generated tables enable efficient poker bot development workflows: -//! - Fast hand evaluation for real-time bot decision making -//! - Deterministic results for reproducible bot testing -//! - Memory-efficient table loading for testing environments -//! - Incremental evaluation for complex multi-street analysis - -pub mod state_table_generator; - -pub mod flushes; -pub mod pair; -pub mod products; -pub mod unique; -pub mod values; diff --git a/core/src/evaluator_generator/pair.rs b/core/src/evaluator_generator/pair.rs deleted file mode 100644 index 815ef9b..0000000 --- a/core/src/evaluator_generator/pair.rs +++ /dev/null @@ -1,200 +0,0 @@ -//! # 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; - -pub struct Pair { - pub ordinal: usize, - pub cards: [Card; 2], -} - -static mut PAIR_VALUES: Vec = Vec::new(); -static mut PAIR_BY_CARD: Vec> = Vec::new(); -static mut INTERSECTS_PAIR: Vec> = Vec::new(); -static mut INTERSECTS_CARD: Vec> = Vec::new(); - -impl Pair { - pub const COUNT: usize = ((Card::NUM_CARDS as u16 * (Card::NUM_CARDS - 1) as u16) / 2) as usize; - - pub fn new(c1: Card, c2: Card, ordinal: usize) -> Self { - Self { - cards: [c1, c2], - ordinal, - } - } - - pub fn get(c1: &Card, c2: &Card) -> Option<&'static Pair> { - unsafe { - let index = PAIR_BY_CARD - .get_unchecked(c1.index() as usize) - .get_unchecked(c2.index() as usize); - PAIR_VALUES.get(*index) - } - } - - pub fn intersects(&self, other: &Pair) -> bool { - self.cards.iter().any(|c| other.cards.contains(c)) - } - - pub fn intersects_card(&self, card: &Card) -> bool { - self.cards.contains(card) - } -} - -pub fn init_pairs() { - unsafe { - let mut pairs = Vec::with_capacity(Pair::COUNT); - let mut pair_by_card = vec![vec![0; Card::NUM_CARDS as usize]; Card::NUM_CARDS as usize]; - let mut intersects_pair = vec![vec![false; Pair::COUNT]; Pair::COUNT]; - let mut intersects_card = vec![vec![false; Pair::COUNT]; Card::NUM_CARDS as usize]; - - let mut k = 0; - for i in 0..Card::NUM_CARDS { - for j in (i + 1)..Card::NUM_CARDS { - let pair = Pair::new( - Card::from_index(i).expect("Invalid Index!"), - Card::from_index(j).expect("Invalid Index!"), - k, - ); - pairs.push(pair); - pair_by_card[i as usize][j as usize] = k; - pair_by_card[j as usize][i as usize] = k; - k += 1; - } - } - - for i in 0..Pair::COUNT { - for j in 0..Pair::COUNT { - intersects_pair[i][j] = pairs[i].intersects(&pairs[j]); - } - } - - for i in 0..Pair::COUNT { - for j in 0..Card::NUM_CARDS { - intersects_card[i as usize][j as usize] = pairs[i as usize] - .intersects_card(&Card::from_index(j).expect("Invalid Index!")); - } - } - - PAIR_VALUES = pairs; - PAIR_BY_CARD = pair_by_card; - INTERSECTS_PAIR = intersects_pair; - INTERSECTS_CARD = intersects_card; - } -} diff --git a/core/src/evaluator_generator/products.rs b/core/src/evaluator_generator/products.rs deleted file mode 100644 index b410553..0000000 --- a/core/src/evaluator_generator/products.rs +++ /dev/null @@ -1,570 +0,0 @@ -//! # 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 table: &'static [u32], -} - -impl Products { - pub const TABLE: [u32; 4888] = [ - 48, 72, 80, 108, 112, 120, 162, 168, 176, 180, 200, 208, 252, 264, 270, 272, 280, 300, 304, - 312, 368, 378, 392, 396, 405, 408, 420, 440, 450, 456, 464, 468, 496, 500, 520, 552, 567, - 588, 592, 594, 612, 616, 630, 656, 660, 675, 680, 684, 696, 700, 702, 728, 744, 750, 760, - 780, 828, 882, 888, 891, 918, 920, 924, 945, 952, 968, 980, 984, 990, 1020, 1026, 1044, - 1050, 1053, 1064, 1092, 1100, 1116, 1125, 1140, 1144, 1160, 1170, 1240, 1242, 1250, 1288, - 1300, 1323, 1332, 1352, 1372, 1377, 1380, 1386, 1428, 1452, 1470, 1476, 1480, 1485, 1496, - 1530, 1539, 1540, 1566, 1575, 1596, 1624, 1638, 1640, 1650, 1672, 1674, 1700, 1710, 1716, - 1736, 1740, 1750, 1755, 1768, 1820, 1860, 1863, 1875, 1900, 1932, 1950, 1976, 1998, 2024, - 2028, 2058, 2070, 2072, 2079, 2142, 2156, 2178, 2205, 2214, 2220, 2244, 2295, 2296, 2300, - 2312, 2349, 2380, 2392, 2394, 2420, 2436, 2450, 2457, 2460, 2475, 2508, 2511, 2548, 2550, - 2552, 2565, 2574, 2584, 2604, 2610, 2625, 2652, 2660, 2728, 2750, 2790, 2850, 2860, 2888, - 2898, 2900, 2925, 2964, 2997, 3016, 3036, 3042, 3087, 3100, 3105, 3108, 3128, 3213, 3220, - 3224, 3234, 3250, 3256, 3267, 3321, 3330, 3332, 3366, 3380, 3388, 3430, 3444, 3450, 3465, - 3468, 3496, 3588, 3591, 3608, 3630, 3654, 3675, 3690, 3700, 3724, 3740, 3762, 3822, 3825, - 3828, 3848, 3850, 3861, 3876, 3906, 3915, 3944, 3978, 4004, 4060, 4092, 4095, 4100, 4125, - 4180, 4185, 4216, 4232, 4250, 4264, 4275, 4332, 4340, 4347, 4350, 4375, 4408, 4420, 4446, - 4508, 4524, 4550, 4554, 4563, 4650, 4662, 4692, 4712, 4732, 4750, 4802, 4836, 4851, 4875, - 4884, 4940, 4995, 4998, 5032, 5049, 5060, 5070, 5082, 5145, 5166, 5175, 5180, 5202, 5236, - 5244, 5324, 5336, 5355, 5382, 5390, 5412, 5445, 5481, 5535, 5550, 5576, 5586, 5624, 5643, - 5684, 5704, 5733, 5740, 5742, 5750, 5772, 5775, 5780, 5814, 5852, 5859, 5916, 5950, 5967, - 5980, 5985, 6050, 6076, 6125, 6138, 6150, 6188, 6232, 6292, 6324, 6348, 6370, 6375, 6380, - 6396, 6435, 6460, 6498, 6525, 6612, 6650, 6669, 6728, 6762, 6786, 6808, 6820, 6825, 6831, - 6875, 6916, 6975, 6993, 7038, 7068, 7084, 7098, 7125, 7150, 7192, 7203, 7220, 7245, 7250, - 7252, 7254, 7326, 7436, 7497, 7540, 7544, 7546, 7548, 7605, 7623, 7688, 7749, 7750, 7803, - 7820, 7866, 7986, 8004, 8036, 8050, 8060, 8073, 8085, 8092, 8118, 8125, 8140, 8228, 8325, - 8330, 8364, 8372, 8379, 8415, 8436, 8450, 8470, 8526, 8556, 8575, 8584, 8613, 8625, 8658, - 8670, 8721, 8740, 8788, 8874, 8918, 8925, 8932, 9009, 9020, 9044, 9075, 9114, 9135, 9176, - 9196, 9207, 9225, 9250, 9310, 9348, 9350, 9405, 9438, 9486, 9512, 9522, 9548, 9555, 9594, - 9620, 9625, 9724, 9747, 9765, 9860, 9918, 9945, 9975, 10092, 10108, 10143, 10150, 10168, - 10179, 10212, 10250, 10450, 10540, 10556, 10557, 10580, 10602, 10625, 10647, 10660, 10725, - 10788, 10830, 10850, 10868, 10875, 10878, 10881, 10948, 10952, 10989, 11020, 11050, 11115, - 11132, 11154, 11270, 11284, 11316, 11319, 11322, 11375, 11385, 11396, 11492, 11532, 11625, - 11655, 11662, 11780, 11781, 11799, 11830, 11858, 11875, 11979, 12005, 12006, 12054, 12075, - 12136, 12138, 12177, 12236, 12342, 12350, 12495, 12546, 12580, 12628, 12650, 12654, 12675, - 12705, 12716, 12789, 12834, 12844, 12876, 12915, 12950, 12987, 13005, 13034, 13156, 13167, - 13182, 13310, 13311, 13340, 13377, 13448, 13455, 13468, 13475, 13671, 13764, 13794, 13804, - 13875, 13923, 13940, 13965, 14014, 14022, 14025, 14036, 14060, 14157, 14210, 14212, 14229, - 14260, 14268, 14283, 14350, 14355, 14375, 14391, 14450, 14535, 14756, 14812, 14875, 14877, - 14924, 14950, 15004, 15028, 15125, 15138, 15162, 15190, 15225, 15252, 15318, 15345, 15375, - 15428, 15548, 15561, 15580, 15675, 15730, 15778, 15870, 15884, 15903, 15925, 15939, 15950, - 16150, 16182, 16245, 16275, 16317, 16428, 16492, 16562, 16575, 16588, 16625, 16698, 16731, - 16796, 16820, 16905, 16965, 16974, 16983, 17020, 17050, 17204, 17238, 17298, 17493, 17595, - 17612, 17732, 17745, 17787, 17875, 17908, 17980, 18009, 18050, 18081, 18125, 18130, 18135, - 18204, 18207, 18315, 18326, 18513, 18525, 18590, 18634, 18676, 18772, 18819, 18837, 18850, - 18860, 18865, 18975, 18981, 19074, 19220, 19228, 19251, 19266, 19314, 19375, 19425, 19516, - 19550, 19551, 19604, 19652, 19665, 19684, 19773, 19844, 19894, 19964, 19965, 20090, 20097, - 20125, 20150, 20172, 20230, 20295, 20332, 20349, 20350, 20482, 20570, 20646, 20691, 20825, - 20956, 21021, 21033, 21054, 21125, 21164, 21175, 21266, 21315, 21402, 21460, 21483, 21525, - 21645, 21658, 21675, 21692, 21812, 21850, 21879, 21964, 21970, 22022, 22185, 22218, 22295, - 22425, 22506, 22542, 22550, 22707, 22724, 22743, 22785, 22878, 22940, 22977, 22990, 23125, - 23188, 23275, 23276, 23322, 23375, 23452, 23548, 23595, 23667, 23715, 23751, 23780, 23805, - 23826, 23828, 23925, 23985, 24050, 24206, 24225, 24244, 24273, 24453, 24548, 24633, 24642, - 24650, 24794, 24795, 24843, 25012, 25025, 25047, 25172, 25230, 25270, 25375, 25382, 25389, - 25420, 25461, 25575, 25625, 25636, 25641, 25857, 25916, 25947, 26026, 26125, 26350, 26404, - 26411, 26450, 26505, 26588, 26650, 26862, 26908, 27075, 27125, 27195, 27306, 27380, 27404, - 27436, 27489, 27508, 27531, 27550, 27625, 27676, 27716, 27830, 27885, 27951, 28126, 28158, - 28175, 28275, 28305, 28322, 28413, 28611, 28652, 28730, 28798, 28830, 28899, 28971, 29155, - 29282, 29302, 29325, 29348, 29406, 29450, 29478, 29575, 29601, 29645, 29716, 29766, 29841, - 30015, 30044, 30135, 30225, 30258, 30303, 30340, 30345, 30525, 30628, 30668, 30723, 30758, - 30855, 30875, 30932, 30969, 31059, 31213, 31262, 31365, 31372, 31434, 31450, 31581, 31625, - 31635, 31654, 31790, 31899, 31977, 32085, 32103, 32110, 32116, 32186, 32375, 32487, 32585, - 32708, 32725, 32775, 32946, 32955, 33033, 33201, 33212, 33275, 33292, 33327, 33350, 33418, - 33524, 33579, 33620, 33759, 33813, 33825, 34276, 34317, 34485, 34606, 34684, 34713, 34850, - 34914, 34983, 35035, 35055, 35090, 35150, 35322, 35378, 35525, 35588, 35650, 35739, 35836, - 35875, 35972, 36075, 36125, 36244, 36309, 36556, 36575, 36822, 36946, 36963, 36975, 37004, - 37030, 37076, 37107, 37191, 37323, 37375, 37444, 37468, 37510, 37518, 37570, 37791, 37845, - 37905, 37975, 38073, 38295, 38318, 38332, 38675, 38709, 38870, 38950, 38962, 39039, 39325, - 39445, 39494, 39525, 39556, 39627, 39675, 39710, 39875, 39882, 39886, 39897, 39975, 40052, - 40204, 40222, 40293, 40362, 40375, 40455, 40508, 40817, 40898, 40959, 41070, 41154, 41262, - 41325, 41405, 41492, 41503, 41574, 41745, 41876, 42021, 42050, 42189, 42237, 42284, 42435, - 42476, 42483, 42550, 42625, 42772, 42826, 43095, 43197, 43225, 43245, 43263, 43732, 43911, - 43923, 43953, 44109, 44175, 44198, 44217, 44252, 44275, 44289, 44506, 44649, 44764, 44770, - 44919, 44950, 44954, 45125, 45254, 45325, 45356, 45387, 45619, 45747, 45815, 46137, 46475, - 46585, 46748, 46893, 46930, 47068, 47125, 47138, 47150, 47151, 47175, 47212, 47396, 47481, - 47619, 47685, 47804, 48050, 48165, 48279, 48285, 48314, 48334, 48484, 48668, 48807, 48875, - 49010, 49036, 49049, 49077, 49126, 49130, 49419, 49610, 49735, 49818, 49972, 50025, 50127, - 50225, 50286, 50375, 50430, 50468, 50575, 50578, 50692, 50875, 51129, 51205, 51425, 51615, - 51646, 51842, 51909, 52173, 52234, 52275, 52316, 52325, 52371, 52390, 52514, 52598, 52635, - 52725, 52767, 52972, 52983, 53067, 53165, 53428, 53475, 53482, 53505, 53613, 53650, 53754, - 53958, 53998, 54145, 54188, 54418, 54549, 54625, 54910, 54925, 55055, 55223, 55233, 55419, - 55506, 55545, 55594, 55796, 55825, 55924, 56265, 56277, 56355, 56375, 56525, 56637, 57122, - 57188, 57195, 57350, 57475, 57477, 57498, 57681, 57722, 57868, 57967, 58190, 58305, 58311, - 58425, 58443, 58870, 59204, 59241, 59409, 59450, 59565, 59644, 59675, 59774, 59823, 59829, - 60125, 60236, 60306, 60333, 60515, 60543, 60775, 61132, 61226, 61347, 61364, 61370, 61605, - 61625, 61642, 61659, 61731, 61828, 61893, 61985, 62271, 62361, 62530, 62678, 62814, 63075, - 63175, 63206, 63426, 63455, 63550, 63825, 63916, 64124, 64141, 64158, 64239, 64467, 64676, - 65065, 65219, 65348, 65366, 65596, 65598, 65702, 65875, 65975, 66033, 66092, 66125, 66297, - 66470, 66625, 66748, 66759, 66861, 67146, 67155, 67270, 67425, 67431, 67599, 67881, 67925, - 68265, 68306, 68324, 68425, 68450, 68590, 68614, 68770, 68782, 68875, 68894, 68913, 69003, - 69290, 69454, 69575, 69597, 69629, 69874, 69938, 70315, 70395, 70525, 70587, 70602, 70642, - 70707, 70725, 70805, 71094, 71188, 71225, 71668, 71687, 71825, 71995, 72075, 72261, 72358, - 72471, 72501, 72964, 73002, 73036, 73205, 73255, 73346, 73515, 73593, 73625, 73689, 73695, - 73964, 74415, 74431, 74698, 74727, 74907, 74958, 75429, 75645, 75803, 75850, 75867, 76342, - 76475, 76874, 76895, 77077, 77121, 77198, 77372, 77469, 77763, 77996, 78039, 78155, 78166, - 78292, 78351, 78585, 78625, 78771, 78884, 78897, 78925, 79135, 79475, 80073, 80142, 80223, - 80275, 80465, 80475, 80631, 80852, 80937, 80997, 81466, 81548, 81549, 81627, 82225, 82251, - 82365, 82418, 82522, 82654, 82708, 83030, 83259, 83375, 83391, 83398, 83421, 83486, 83545, - 83810, 84050, 84175, 84249, 84303, 84721, 85514, 85683, 85782, 85918, 86025, 86247, 86275, - 86428, 86515, 86583, 86756, 86779, 87125, 87172, 87285, 87362, 87412, 87542, 87725, 87875, - 88102, 88305, 88412, 88445, 88806, 88825, 88837, 89001, 89125, 89175, 89590, 89661, 89930, - 90117, 90354, 90364, 90459, 91091, 91143, 91234, 91839, 92046, 92055, 92225, 92365, 92414, - 92463, 92510, 92575, 93058, 93092, 93275, 93357, 93775, 93795, 93925, 94017, 94178, 94221, - 94622, 94809, 95139, 95325, 95571, 95795, 95830, 95874, 96026, 96237, 96278, 96425, 96596, - 97006, 97175, 97375, 97405, 97526, 97556, 97682, 98022, 98049, 98394, 98397, 98441, 98494, - 98553, 98716, 98735, 99127, 99275, 99567, 99705, 99715, 100510, 100555, 100719, 100793, - 100905, 101062, 102051, 102245, 102459, 102487, 102557, 102675, 102885, 102921, 103075, - 103155, 103156, 103173, 103246, 103341, 103675, 103935, 104044, 104181, 104284, 104690, - 104811, 104907, 104975, 105125, 105154, 105183, 105524, 105710, 105754, 105903, 105963, - 106227, 106375, 106641, 106782, 106930, 107065, 107525, 107559, 107653, 107822, 108086, - 108537, 109089, 109142, 109174, 109330, 109388, 109417, 109503, 109554, 110019, 110075, - 110331, 110495, 110789, 110825, 110946, 111265, 111476, 111910, 111925, 112047, 112375, - 112385, 112406, 112437, 112651, 113135, 113553, 113775, 114057, 114308, 114513, 115258, - 115292, 115311, 115797, 116058, 116242, 116402, 116522, 116725, 116932, 116963, 117249, - 117325, 117334, 117438, 117670, 117711, 117845, 117875, 118490, 119119, 119164, 119187, - 119306, 120125, 120175, 120213, 120785, 120802, 120835, 121121, 121670, 121923, 121975, - 122018, 122199, 122525, 122815, 122825, 123025, 123627, 123783, 123823, 123981, 124025, - 124468, 124545, 124558, 124775, 124930, 125097, 125229, 125426, 125541, 125715, 125829, - 125902, 125948, 126075, 126445, 127075, 127426, 127534, 127738, 127756, 128018, 128271, - 128673, 128877, 128986, 129115, 129311, 129514, 129605, 130134, 130203, 130585, 130975, - 131043, 131118, 131285, 131313, 131495, 132153, 132158, 132275, 132618, 133052, 133133, - 133209, 133342, 133570, 133705, 134113, 134125, 134162, 134199, 134385, 134895, 134995, - 135014, 135531, 135575, 136045, 136214, 136325, 136367, 136851, 137275, 137547, 137566, - 137924, 138069, 138229, 138621, 138765, 138985, 139113, 139564, 139587, 139601, 139638, - 140714, 140777, 141267, 141933, 142025, 142228, 142538, 142766, 142805, 142970, 143143, - 143375, 143745, 143811, 144039, 144279, 144305, 144417, 144925, 145475, 145509, 145521, - 146234, 146289, 146334, 146523, 146566, 146575, 147033, 147175, 147436, 147591, 147706, - 147741, 147994, 148010, 148625, 148666, 148707, 148925, 149435, 149702, 149891, 150183, - 150590, 150765, 150898, 151294, 151525, 151593, 152218, 152438, 153062, 153065, 153410, - 153425, 153729, 154105, 154652, 154693, 154869, 155771, 156066, 156325, 156426, 156674, - 156695, 157035, 157325, 157339, 157604, 157731, 158015, 158389, 158565, 158631, 158804, - 158875, 159562, 159790, 160173, 160225, 160395, 161161, 161253, 161414, 161733, 161975, - 162129, 162578, 163370, 163415, 163713, 163761, 163990, 163995, 164169, 164255, 164331, - 164738, 164983, 165025, 165886, 166175, 166419, 166634, 167042, 167214, 167865, 168175, - 168609, 168674, 169099, 169169, 169756, 170126, 170338, 170765, 171125, 171275, 171462, - 171475, 171535, 171925, 171941, 171955, 172235, 172546, 172822, 172887, 172975, 173225, - 173635, 174087, 174097, 174363, 174603, 174685, 174783, 174845, 174902, 175491, 175972, - 176001, 176157, 176505, 176605, 177023, 177489, 177735, 177970, 178126, 178334, 178746, - 178802, 178959, 179075, 180154, 180761, 180895, 181203, 181447, 181917, 182505, 182590, - 182666, 182819, 183027, 183365, 183425, 183483, 183799, 184093, 184382, 184910, 185725, - 186093, 186238, 186694, 186702, 186745, 186837, 186998, 187187, 187395, 187775, 188108, - 188139, 188518, 188853, 188922, 188993, 189625, 190333, 190463, 190855, 191139, 191301, - 191425, 191607, 191634, 191675, 192027, 192185, 192995, 193325, 193430, 193479, 194271, - 194463, 194579, 194996, 195201, 195415, 195730, 196075, 196137, 196677, 197098, 197846, - 198237, 198927, 199082, 199927, 200013, 200158, 200355, 200725, 201243, 202027, 202521, - 202612, 203203, 203319, 203522, 203665, 204321, 204425, 205751, 205942, 206045, 206305, - 206349, 206635, 206886, 207214, 207575, 208075, 208444, 208495, 208658, 208715, 209209, - 209457, 209525, 210125, 210749, 210826, 211071, 212602, 213342, 213785, 213807, 214149, - 214225, 214291, 214455, 214774, 214795, 215747, 215878, 216775, 216890, 217217, 217341, - 217558, 217906, 218405, 218530, 218855, 219351, 219373, 219501, 219849, 220255, 221030, - 221122, 221221, 221559, 221991, 222015, 222111, 222425, 222999, 223706, 223975, 224516, - 224553, 224825, 224939, 225446, 225885, 225998, 226347, 226525, 226941, 228085, 228206, - 228327, 228475, 228657, 228718, 228781, 229586, 229593, 229957, 230115, 230318, 231035, - 231275, 231725, 231978, 232101, 232562, 232645, 232730, 232934, 233206, 233818, 234025, - 234099, 234175, 234639, 235011, 235246, 235445, 235543, 235586, 236406, 236555, 237429, - 237614, 238206, 239071, 239343, 239575, 239685, 240065, 240149, 240526, 240695, 240737, - 240994, 241129, 242121, 242515, 243089, 243815, 243867, 243890, 244205, 244559, 244783, - 245055, 245985, 246123, 246202, 246235, 247107, 247225, 247247, 248788, 248829, 248897, - 249067, 249158, 249951, 250325, 250563, 250821, 251275, 252586, 252655, 253011, 253175, - 253253, 254634, 255189, 255507, 255626, 256711, 257193, 258115, 258819, 258874, 259233, - 259259, 259325, 259407, 259666, 260110, 260642, 260678, 260710, 261326, 261443, 261725, - 262353, 262885, 263097, 263302, 264275, 264385, 265475, 265727, 265837, 266955, 267189, - 267197, 267325, 267501, 267674, 268119, 268203, 269059, 269555, 270193, 270215, 270231, - 270802, 272194, 272855, 272935, 273325, 273581, 273885, 273999, 274022, 274846, 275684, - 276573, 276575, 277365, 277574, 278018, 278179, 278369, 278690, 279357, 279775, 280041, - 280053, 280497, 281015, 282302, 282777, 283383, 283475, 284053, 284258, 284954, 285131, - 285770, 287287, 287451, 287638, 287738, 288145, 288463, 288827, 289289, 290145, 290605, - 290966, 291005, 291305, 291893, 292175, 292201, 292494, 293335, 293595, 293854, 294151, - 294175, 295075, 295647, 296225, 296769, 296989, 297910, 298265, 298623, 298775, 299299, - 299367, 300237, 300713, 302005, 303025, 303646, 303862, 303918, 304175, 304606, 305045, - 305283, 305762, 305767, 305942, 306397, 306475, 307582, 308074, 308357, 308913, 309442, - 310329, 310821, 311170, 311395, 312325, 312666, 312987, 313565, 314019, 314041, 314171, - 314534, 314755, 314870, 315425, 315514, 316239, 316342, 316825, 317471, 318478, 318565, - 318734, 318835, 318903, 319319, 319345, 319390, 320013, 320045, 322161, 322465, 323449, - 323785, 323817, 324818, 325335, 325622, 325703, 325822, 326337, 326859, 326975, 327795, - 328757, 329623, 330395, 331075, 331177, 331298, 331545, 331683, 331731, 333355, 333925, - 335405, 335559, 335699, 336091, 336743, 336774, 336973, 337502, 337535, 338169, 338675, - 338997, 339031, 339521, 340442, 340535, 341341, 341446, 341734, 341887, 342309, 343077, - 343915, 344379, 344729, 344810, 345477, 347282, 347633, 347967, 348725, 348843, 349095, - 349401, 349525, 349809, 350727, 350987, 351538, 351785, 352869, 353379, 353717, 354609, - 355570, 355946, 356345, 356421, 356915, 357309, 357425, 359414, 359513, 360778, 360789, - 361361, 361491, 361675, 362674, 363562, 364021, 364154, 364994, 365585, 365835, 366415, - 367114, 368039, 369265, 369303, 369985, 370025, 370139, 371665, 371722, 372775, 373182, - 373737, 374255, 375193, 375683, 376475, 377245, 377377, 378235, 378301, 378879, 378917, - 380494, 380545, 381095, 381938, 381951, 381997, 382075, 382109, 382655, 383439, 383525, - 384307, 384659, 384826, 385526, 386425, 386630, 387686, 388311, 388531, 389499, 390165, - 390166, 390963, 391017, 391065, 391534, 391685, 391989, 393421, 394010, 394953, 395937, - 397010, 397822, 397969, 398866, 398905, 399475, 400078, 400673, 400775, 401511, 401698, - 401882, 402866, 403403, 403535, 404225, 406203, 406334, 406445, 406802, 406847, 407407, - 407827, 408291, 408425, 409975, 410669, 410839, 411033, 411845, 412114, 412269, 413075, - 413526, 413678, 414715, 415454, 416361, 416585, 417027, 417074, 417175, 417571, 417605, - 418035, 419881, 421685, 422807, 423243, 423453, 424390, 424589, 424762, 424879, 425258, - 425315, 425546, 425845, 426374, 426387, 427025, 427063, 427431, 428655, 429598, 429913, - 430606, 431365, 431457, 431607, 432055, 435638, 435953, 436449, 437255, 438741, 438991, - 440657, 440781, 440818, 443989, 444925, 445315, 445835, 445991, 446369, 446865, 447005, - 447083, 447146, 447811, 447925, 448063, 450262, 450385, 451451, 453299, 453871, 454138, - 454181, 454597, 455469, 455793, 455877, 456025, 456475, 456665, 456909, 458643, 458689, - 458913, 458983, 459173, 460955, 461373, 462111, 462275, 462346, 462553, 462722, 464163, - 465595, 466697, 466735, 466755, 467495, 468999, 469567, 470327, 471295, 471801, 472305, - 472549, 473271, 474513, 474734, 476749, 477158, 477717, 478101, 479085, 480491, 480766, - 481481, 481574, 482734, 483575, 484561, 485537, 486098, 486266, 487227, 487475, 487490, - 488433, 488733, 489325, 490637, 491878, 492499, 492745, 493025, 494615, 496223, 496947, - 497705, 497798, 498883, 499681, 500395, 501787, 502918, 503234, 505161, 505325, 506253, - 506530, 507566, 508079, 508277, 508805, 508898, 509675, 510663, 511819, 512006, 512169, - 512601, 512746, 512981, 514786, 514855, 516925, 516971, 517215, 517979, 518035, 519622, - 520331, 520421, 520923, 521110, 521594, 521645, 523957, 527065, 527307, 528143, 529529, - 531505, 532763, 533355, 533533, 533919, 535717, 536393, 536558, 536935, 537251, 539121, - 539695, 540175, 541167, 541282, 541717, 542087, 542225, 542659, 543286, 543895, 544011, - 544765, 544825, 545054, 545343, 546231, 546325, 547491, 548359, 550671, 551614, 552575, - 552805, 555458, 555611, 555814, 555841, 557566, 557583, 558467, 559265, 559682, 559773, - 561290, 562438, 563615, 563914, 564775, 564949, 564995, 567853, 568178, 569023, 570515, - 570741, 571795, 572242, 572663, 572907, 573562, 573965, 574678, 575795, 576583, 577239, - 578289, 578347, 579945, 580601, 581405, 581529, 581647, 581825, 582335, 582958, 583015, - 583219, 584545, 584647, 585249, 585599, 587301, 588115, 588965, 590359, 591015, 593021, - 593929, 594035, 594146, 594473, 595441, 595515, 596183, 596733, 598299, 600117, 600281, - 600457, 600691, 601315, 602485, 602547, 602823, 603725, 603911, 604299, 604877, 605098, - 607202, 609501, 609725, 610203, 612157, 613118, 614422, 615043, 615505, 616975, 618171, - 618233, 620194, 620289, 620517, 620806, 620977, 621970, 622895, 623162, 623181, 623441, - 624169, 625611, 625807, 628694, 630539, 631465, 633919, 634114, 634933, 636585, 637143, - 637887, 638319, 639065, 639331, 639561, 640211, 640871, 644397, 644725, 645337, 645909, - 647185, 648907, 649078, 649165, 650275, 651605, 651695, 651775, 651833, 653315, 653429, - 653457, 654493, 655402, 656183, 656903, 657662, 658255, 659525, 659813, 661227, 662966, - 663803, 664411, 665482, 669185, 670719, 671099, 675393, 676286, 677005, 677846, 680485, - 680846, 681207, 682486, 683501, 683675, 684574, 685055, 685069, 687115, 687242, 687401, - 689210, 689843, 692461, 692714, 693519, 693842, 693935, 694083, 695045, 696725, 696787, - 700553, 700843, 701437, 702559, 702658, 704099, 705686, 705755, 708883, 709142, 709423, - 709631, 710645, 712101, 712327, 712385, 714425, 715737, 719095, 719345, 720575, 720797, - 721149, 722361, 724101, 724594, 725249, 726869, 727415, 729147, 729399, 729554, 730303, - 730639, 730825, 731235, 733381, 734635, 734638, 735034, 737426, 737817, 737891, 742577, - 743002, 743774, 744107, 744775, 746697, 748867, 749177, 751502, 751709, 754354, 754377, - 754851, 755573, 756613, 757393, 758582, 759115, 759655, 759795, 761349, 761453, 761515, - 762671, 763347, 764405, 764855, 768009, 768955, 769119, 770185, 772179, 773605, 773927, - 774566, 774706, 775489, 777925, 779433, 781665, 782254, 782391, 782971, 783959, 785213, - 785519, 785806, 786335, 787175, 788785, 789061, 790855, 790993, 791282, 792281, 793117, - 796195, 796835, 798475, 798721, 800513, 803551, 804287, 804837, 806113, 809042, 809627, - 811923, 812045, 812383, 813967, 814055, 814555, 814929, 815269, 816221, 817581, 817663, - 818363, 818662, 823361, 824182, 824551, 827421, 828134, 828245, 828269, 828971, 829226, - 829939, 830297, 830414, 831575, 831649, 832117, 833187, 833721, 836349, 836969, 837199, - 838409, 839523, 839914, 841841, 841935, 843479, 843657, 843755, 845871, 850586, 851105, - 852267, 853615, 854335, 858363, 858458, 859027, 860343, 861707, 862017, 862025, 866723, - 866822, 868205, 870758, 872053, 872275, 873422, 874437, 876826, 877591, 877933, 878845, - 884051, 884374, 885391, 886414, 887777, 888925, 889778, 889865, 891219, 893809, 894179, - 894691, 896506, 898535, 898909, 900358, 901945, 906059, 906685, 907647, 908831, 908905, - 910385, 910803, 912247, 912373, 912485, 914641, 916487, 917662, 917785, 918731, 919677, - 921475, 921557, 921633, 924482, 926497, 926782, 927707, 927979, 929305, 930291, 931209, - 932955, 933658, 934743, 935693, 936859, 943041, 947546, 947807, 949003, 950521, 951142, - 951171, 951235, 952679, 954845, 955451, 959077, 960089, 961961, 962065, 963815, 964894, - 966329, 966575, 969215, 971509, 971618, 973063, 973617, 975415, 978835, 979693, 980837, - 983103, 983411, 985025, 986493, 988057, 988418, 989417, 990437, 990698, 990847, 992525, - 994449, 994555, 994903, 997165, 997339, 997694, 998223, 998963, 1000195, 1004245, 1004663, - 1004705, 1005238, 1006733, 1007083, 1007165, 1012894, 1013173, 1014101, 1014429, 1015835, - 1016738, 1016769, 1017005, 1018381, 1021269, 1023729, 1024309, 1024426, 1026817, 1026861, - 1028489, 1030285, 1030863, 1032226, 1033815, 1034195, 1036849, 1037153, 1038635, 1039071, - 1040763, 1042685, 1049191, 1053987, 1056757, 1057978, 1058529, 1058743, 1059022, 1060975, - 1061905, 1062761, 1063145, 1063517, 1063713, 1063865, 1065935, 1066121, 1067857, 1070167, - 1070558, 1070797, 1072478, 1073995, 1076515, 1076537, 1078259, 1083047, 1083121, 1084039, - 1085773, 1085926, 1086891, 1088153, 1089095, 1094331, 1094951, 1095274, 1096381, 1099825, - 1100869, 1101957, 1102045, 1102551, 1103414, 1104299, 1105819, 1106139, 1106959, 1107197, - 1114366, 1114503, 1114673, 1115569, 1115661, 1117865, 1119371, 1121549, 1121894, 1123343, - 1125655, 1127253, 1131531, 1132058, 1132681, 1133407, 1135234, 1135345, 1136863, 1137873, - 1139677, 1140377, 1146442, 1147619, 1155865, 1156805, 1157819, 1159171, 1159543, 1161849, - 1162059, 1162213, 1169311, 1171001, 1172354, 1173381, 1175675, 1178709, 1181257, 1182446, - 1183301, 1186835, 1186923, 1187329, 1191547, 1192895, 1195061, 1196069, 1196506, 1196569, - 1198483, 1199266, 1201915, 1203935, 1206835, 1208938, 1209271, 1210547, 1211573, 1213511, - 1213526, 1213563, 1213682, 1215245, 1215487, 1215665, 1216171, 1218725, 1225367, 1227993, - 1229695, 1230383, 1234838, 1236273, 1239953, 1242201, 1242989, 1243839, 1244495, 1245621, - 1245811, 1255133, 1255501, 1257295, 1257949, 1257962, 1258085, 1259871, 1262723, 1263661, - 1266325, 1266749, 1267474, 1268915, 1269359, 1272245, 1272467, 1274539, 1275879, 1277479, - 1279091, 1280015, 1281137, 1281865, 1281974, 1282633, 1284899, 1285999, 1286965, 1287687, - 1292669, 1293853, 1294033, 1295723, 1299055, 1300233, 1301027, 1302775, 1303985, 1306137, - 1306877, 1310133, 1310278, 1314542, 1315239, 1316978, 1322893, 1325467, 1326561, 1329621, - 1331729, 1334667, 1336783, 1338623, 1339634, 1340003, 1341395, 1344718, 1344759, 1346891, - 1349341, 1349834, 1350537, 1351166, 1353205, 1354111, 1354886, 1356277, 1356901, 1358215, - 1362635, 1365581, 1368334, 1370369, 1370386, 1372019, 1376493, 1379035, 1381913, 1386723, - 1388645, 1389223, 1389535, 1390173, 1392377, 1393915, 1396031, 1399205, 1400273, 1400487, - 1403207, 1403225, 1405943, 1406095, 1406587, 1409785, 1410031, 1412327, 1414127, 1414562, - 1416389, 1420445, 1421319, 1422169, 1423807, 1426713, 1428163, 1430605, 1431382, 1432417, - 1433531, 1433729, 1433905, 1436695, 1437293, 1442399, 1442926, 1446071, 1447341, 1447873, - 1448161, 1448402, 1454089, 1457395, 1457427, 1459354, 1459759, 1465399, 1466641, 1468987, - 1469194, 1472207, 1482627, 1483339, 1485365, 1486047, 1486667, 1488403, 1489411, 1492309, - 1496541, 1497067, 1497238, 1503593, 1507121, 1507857, 1508638, 1511653, 1512118, 1512745, - 1514071, 1515839, 1516262, 1518005, 1519341, 1519817, 1524733, 1525107, 1526657, 1529099, - 1531309, 1532795, 1533433, 1536055, 1536639, 1542863, 1544491, 1548339, 1550485, 1552015, - 1552661, 1554925, 1557905, 1563419, 1565011, 1566461, 1567247, 1571735, 1575917, 1582009, - 1582559, 1583023, 1585285, 1586126, 1586899, 1586967, 1588533, 1589483, 1600313, 1602403, - 1604986, 1605837, 1608717, 1612682, 1616197, 1616402, 1617122, 1618211, 1619527, 1622695, - 1628889, 1629887, 1635622, 1638505, 1639187, 1641809, 1642911, 1644155, 1655121, 1657415, - 1657466, 1661569, 1663705, 1670053, 1671241, 1671549, 1675333, 1681691, 1682681, 1682841, - 1685509, 1687829, 1689569, 1690715, 1691701, 1692197, 1694173, 1694407, 1694615, 1698087, - 1698619, 1701343, 1701931, 1702115, 1702851, 1706215, 1709659, 1711435, 1711463, 1718105, - 1719663, 1721573, 1722202, 1723025, 1727878, 1729937, 1731785, 1734605, 1735327, 1739881, - 1742293, 1750507, 1751629, 1753037, 1756645, 1758531, 1760213, 1761319, 1764215, 1769261, - 1771774, 1772855, 1773593, 1773669, 1776481, 1778498, 1781143, 1786499, 1790921, 1791946, - 1792021, 1794611, 1794759, 1798899, 1801751, 1804231, 1804786, 1806091, 1807117, 1811485, - 1812446, 1813407, 1818677, 1820289, 1820523, 1822139, 1823885, 1825579, 1826246, 1834963, - 1836595, 1837585, 1843565, 1847042, 1847677, 1849243, 1852201, 1852257, 1852462, 1856261, - 1857505, 1859435, 1869647, 1870297, 1872431, 1877953, 1878755, 1879537, 1885885, 1886943, - 1891279, 1894487, 1896455, 1901211, 1901501, 1907689, 1908386, 1910051, 1916291, 1920983, - 1922961, 1924814, 1929254, 1930649, 1933459, 1936415, 1936765, 1939751, 1944103, 1945349, - 1951481, 1952194, 1955635, 1956449, 1957703, 1958887, 1964515, 1965417, 1968533, 1971813, - 1973699, 1975103, 1975467, 1976777, 1978205, 1979939, 1980218, 1982251, 1984279, 1987453, - 1988623, 1994707, 1999283, 1999591, 1999898, 2002481, 2002847, 2007467, 2009451, 2011373, - 2017077, 2019127, 2019719, 2022605, 2024751, 2026749, 2032329, 2040353, 2044471, 2046655, - 2048449, 2050841, 2052501, 2055579, 2056223, 2060455, 2062306, 2066801, 2070107, 2070335, - 2071771, 2073065, 2076035, 2079511, 2092717, 2099785, 2100659, 2111317, 2114698, 2116543, - 2117843, 2120393, 2121843, 2125207, 2126465, 2132273, 2132902, 2137822, 2141737, 2145913, - 2146145, 2146981, 2147073, 2150477, 2153437, 2155657, 2164389, 2167055, 2167957, 2170679, - 2172603, 2172821, 2176895, 2181067, 2183555, 2188021, 2189031, 2192065, 2193763, 2200429, - 2203791, 2204534, 2207161, 2209339, 2210351, 2210935, 2212873, 2215457, 2215763, 2216035, - 2219399, 2221271, 2224445, 2234837, 2237411, 2238067, 2241265, 2242454, 2245857, 2250895, - 2257333, 2262957, 2266627, 2268177, 2271773, 2274393, 2275229, 2284997, 2285258, 2289443, - 2293907, 2294155, 2301817, 2302658, 2304323, 2311205, 2313649, 2316955, 2320381, 2329187, - 2330038, 2334145, 2336191, 2338919, 2340503, 2343314, 2345057, 2357381, 2359379, 2362789, - 2363153, 2363486, 2367001, 2368333, 2368865, 2372461, 2377855, 2379189, 2382961, 2386241, - 2388701, 2396009, 2397106, 2399567, 2405347, 2407479, 2412235, 2416193, 2419023, 2422109, - 2424499, 2424603, 2425683, 2428447, 2429045, 2442862, 2444923, 2445773, 2453433, 2459303, - 2461462, 2466827, 2469901, 2471045, 2473211, 2476441, 2476745, 2481997, 2482597, 2486199, - 2494235, 2497759, 2501369, 2501917, 2505919, 2513095, 2519959, 2532235, 2536079, 2541845, - 2542903, 2544971, 2551594, 2553439, 2561065, 2571233, 2572619, 2580565, 2580991, 2581934, - 2582827, 2583303, 2585843, 2589151, 2591817, 2592629, 2598977, 2600507, 2603209, 2611037, - 2612233, 2614447, 2618629, 2618998, 2624369, 2630257, 2631218, 2636953, 2640239, 2641171, - 2644213, 2644945, 2647555, 2648657, 2655037, 2657661, 2667747, 2673539, 2674463, 2676395, - 2678741, 2681195, 2681869, 2687919, 2688907, 2700451, 2705329, 2707063, 2707179, 2709239, - 2710981, 2711471, 2714815, 2718669, 2732561, 2733511, 2737889, 2738185, 2739369, 2750321, - 2758535, 2760953, 2764177, 2766049, 2767787, 2769487, 2770563, 2771431, 2778693, 2785915, - 2791613, 2792387, 2798939, 2804735, 2816033, 2820103, 2827442, 2830145, 2831323, 2831647, - 2838085, 2857921, 2861062, 2862579, 2865317, 2866105, 2868767, 2884637, 2886689, 2887221, - 2893757, 2893881, 2898469, 2902291, 2904739, 2906449, 2915674, 2922029, 2926703, 2928291, - 2930885, 2937874, 2939699, 2951069, 2951897, 2956115, 2970327, 2977051, 2986159, 2988073, - 2991265, 2997383, 2997797, 2998165, 2999847, 3004603, 3005249, 3007693, 3022345, 3022438, - 3025541, 3027973, 3033815, 3033877, 3034205, 3047653, 3055019, 3056977, 3066613, 3068891, - 3078251, 3082729, 3085771, 3087095, 3090277, 3093409, 3093459, 3095309, 3101527, 3102449, - 3114223, 3120469, 3124979, 3130231, 3137771, 3140486, 3144905, 3147331, 3151253, 3154591, - 3159637, 3160729, 3168685, 3170366, 3172047, 3192101, 3197207, 3199353, 3204935, 3206269, - 3206733, 3211817, 3230882, 3234199, 3235687, 3243737, 3246473, 3255482, 3267803, 3268967, - 3271021, 3275695, 3276971, 3286355, 3292445, 3295331, 3299179, 3306801, 3307837, 3308987, - 3316411, 3328039, 3328997, 3332849, 3339611, 3346109, 3349085, 3361795, 3363681, 3372149, - 3374585, 3377129, 3377543, 3377915, 3379321, 3381487, 3387215, 3390361, 3400663, 3411067, - 3414433, 3415997, 3420835, 3424361, 3425965, 3427391, 3427887, 3445403, 3453839, 3453987, - 3457817, 3459463, 3467443, 3479998, 3487583, 3487627, 3491929, 3494413, 3495057, 3502969, - 3514971, 3516263, 3518333, 3531359, 3536405, 3537193, 3542851, 3545129, 3545229, 3558583, - 3569929, 3578455, 3585491, 3595659, 3604711, 3607315, 3607426, 3610477, 3612791, 3614693, - 3617141, 3621005, 3624179, 3628411, 3637933, 3646313, 3648385, 3651583, 3655847, 3660151, - 3662497, 3664293, 3665441, 3672985, 3683017, 3692193, 3693157, 3702923, 3706577, 3719573, - 3728153, 3735407, 3743095, 3744653, 3746953, 3748322, 3753673, 3765157, 3771595, 3779309, - 3779831, 3780295, 3789227, 3790655, 3800741, 3809927, 3816131, 3817879, 3827227, 3827391, - 3833459, 3856214, 3860173, 3861949, 3864619, 3872901, 3881273, 3900281, 3915083, 3926629, - 3928497, 3929941, 3933137, 3946813, 3946827, 3962203, 3965315, 3973319, 3985267, 3993743, - 3997418, 4012465, 4012547, 4024823, 4031261, 4031705, 4035239, 4039951, 4040509, 4041005, - 4042687, 4042805, 4050553, 4055843, 4081181, 4086511, 4089055, 4090757, 4093379, 4103239, - 4121741, 4131833, 4133261, 4138561, 4143665, 4148947, 4153546, 4170751, 4172201, 4180963, - 4187771, 4197431, 4219007, 4221811, 4231283, 4241163, 4247341, 4247887, 4260113, 4260883, - 4273102, 4274803, 4277489, 4291593, 4302397, 4305505, 4309279, 4314311, 4319695, 4321933, - 4325633, 4352051, 4358341, 4373511, 4375681, 4392287, 4395859, 4402867, 4405999, 4406811, - 4416787, 4425499, 4429435, 4433549, 4436159, 4446245, 4449731, 4458389, 4459939, 4467073, - 4479865, 4486909, 4502641, 4509973, 4511965, 4531115, 4533001, 4533657, 4554737, 4560743, - 4565615, 4567277, 4574953, 4585973, 4586959, 4600897, 4602578, 4609423, 4617605, 4617931, - 4619527, 4621643, 4631155, 4632959, 4672841, 4678223, 4688719, 4706513, 4709861, 4710729, - 4721393, 4721519, 4724419, 4729081, 4739311, 4742101, 4755549, 4757297, 4767521, 4770965, - 4775147, 4777721, 4780723, 4789169, 4793269, 4796351, 4803821, 4812035, 4821877, 4822543, - 4823135, 4829513, 4834531, 4846323, 4864057, 4871087, 4875277, 4880485, 4883223, 4884763, - 4890467, 4893779, 4903301, 4930783, 4936409, 4940377, 4950545, 4950967, 4951969, 4955143, - 4999745, 5009837, 5034679, 5035589, 5047141, 5050241, 5069407, 5084651, 5097301, 5100154, - 5107739, 5135119, 5142179, 5143333, 5155765, 5161217, 5178013, 5211503, 5219997, 5222587, - 5231281, 5240333, 5258773, 5271649, 5276851, 5280233, 5286745, 5292413, 5296877, 5306917, - 5316979, 5321303, 5323153, 5332255, 5343161, 5343899, 5344555, 5357183, 5382871, 5389969, - 5397691, 5411139, 5436299, 5448839, 5459441, 5487317, 5511335, 5517163, 5528809, 5538101, - 5551441, 5570917, 5579977, 5590127, 5592059, 5606135, 5617451, 5621447, 5622483, 5634343, - 5635211, 5644387, 5651522, 5656597, 5657407, 5659927, 5677243, 5690267, 5699369, 5713145, - 5724677, 5748431, 5756645, 5761691, 5768419, 5783557, 5784321, 5787191, 5801131, 5818879, - 5824621, 5825095, 5827289, 5837009, 5841557, 5852327, 5858285, 5888069, 5891843, 5896579, - 5897657, 5898629, 5908715, 5920039, 5964803, 5972593, 5975653, 5992765, 5996127, 5998331, - 6009133, 6024007, 6024083, 6027707, 6047573, 6068777, 6107155, 6129013, 6153655, 6159049, - 6166241, 6170417, 6182423, 6201209, 6224743, 6226319, 6229171, 6230319, 6243787, 6244423, - 6247789, 6268121, 6271811, 6298177, 6305431, 6315517, 6316751, 6322079, 6343561, 6378985, - 6387767, 6391861, 6409653, 6412009, 6424717, 6439537, 6447947, 6454835, 6464647, 6468037, - 6483617, 6485011, 6503453, 6528799, 6534047, 6547495, 6578045, 6580783, 6583811, 6585001, - 6591499, 6595963, 6608797, 6649159, 6658769, 6674393, 6675251, 6679351, 6704017, 6709469, - 6725897, 6736849, 6752389, 6791609, 6832679, 6876857, 6883643, 6903867, 6918791, 6930763, - 6958627, 6971107, 6979061, 6982823, 6999643, 7005547, 7039139, 7048421, 7050857, 7058519, - 7065853, 7068605, 7119281, 7132231, 7139269, 7152655, 7166363, 7172191, 7206529, 7218071, - 7229981, 7243379, 7289185, 7292311, 7296893, 7344685, 7358377, 7359707, 7367987, 7379021, - 7395949, 7401443, 7424087, 7431413, 7434817, 7451873, 7453021, 7464397, 7465157, 7482377, - 7517179, 7525837, 7534519, 7537123, 7556095, 7563113, 7620301, 7624109, 7650231, 7653043, - 7685899, 7715869, 7777289, 7780091, 7795229, 7800127, 7829729, 7848589, 7851215, 7858097, - 7867273, 7872601, 7877647, 7887919, 7888933, 7903283, 7925915, 7936093, 7947563, 7966211, - 7979183, 7998403, 8026447, 8054141, 8059303, 8077205, 8080567, 8084707, 8115389, 8138705, - 8155133, 8155351, 8176753, 8201599, 8234809, 8238581, 8258753, 8272201, 8297509, 8316649, - 8329847, 8332831, 8339441, 8389871, 8401553, 8420933, 8448337, 8452891, 8477283, 8480399, - 8516807, 8544523, 8550017, 8553401, 8560357, 8609599, 8615117, 8642273, 8675071, 8699995, - 8707621, 8717789, 8723693, 8740667, 8773921, 8782579, 8804429, 8806759, 8827423, 8869751, - 8890211, 8894171, 8907509, 8909119, 8930579, 8992813, 8995921, 9001687, 9018565, 9035849, - 9036769, 9099743, 9116063, 9166493, 9194653, 9209263, 9230371, 9303983, 9309829, 9370805, - 9379019, 9389971, 9411631, 9414613, 9472111, 9478093, 9485801, 9503329, 9523541, 9536099, - 9549761, 9613007, 9622493, 9640535, 9649489, 9659011, 9732047, 9744757, 9781739, 9806147, - 9828767, 9855703, 9872267, 9896047, 9926323, 9965009, 9968453, 9993545, 10013717, 10044353, - 10050791, 10060709, 10083499, 10158731, 10170301, 10188541, 10193761, 10204859, 10232447, - 10275973, 10282559, 10309819, 10314971, 10316297, 10354117, 10383865, 10405103, 10432409, - 10482433, 10496123, 10506613, 10511293, 10553113, 10578533, 10586477, 10610897, 10631543, - 10652251, 10657993, 10682755, 10692677, 10737067, 10754551, 10773529, 10784723, 10891199, - 10896779, 10938133, 10991701, 10999439, 11096281, 11137363, 11173607, 11194313, 11231207, - 11233237, 11308087, 11342683, 11366807, 11386889, 11393027, 11394187, 11430103, 11473481, - 11473589, 11484911, 11506445, 11516531, 11528497, 11529979, 11560237, 11630839, 11647649, - 11648281, 11692487, 11730961, 11731109, 11758021, 11780899, 11870599, 11950639, 12005773, - 12007943, 12023777, 12041003, 12124937, 12166747, 12178753, 12179993, 12264871, 12311417, - 12333497, 12404509, 12447641, 12488149, 12511291, 12540151, 12568919, 12595651, 12625991, - 12664619, 12689261, 12713977, 12726523, 12750385, 12774821, 12815209, 12823423, 12836077, - 12853003, 12871417, 12888227, 12901781, 12999173, 12999337, 13018667, 13055191, 13119127, - 13184083, 13306099, 13404989, 13435741, 13438339, 13482071, 13496749, 13538041, 13590803, - 13598129, 13642381, 13707797, 13739417, 13745537, 13759819, 13791559, 13863863, 13895843, - 13902787, 13955549, 13957343, 13990963, 14033767, 14088461, 14128805, 14200637, 14223761, - 14329471, 14332061, 14365121, 14404489, 14466563, 14471699, 14537411, 14575951, 14638717, - 14686963, 14742701, 14854177, 14955857, 14967277, 15060079, 15068197, 15117233, 15145247, - 15231541, 15247367, 15320479, 15340681, 15355819, 15362659, 15405791, 15464257, 15523091, - 15538409, 15550931, 15581189, 15699857, 15735841, 15745927, 15759439, 15878603, 15881473, - 15999503, 16036207, 16109023, 16158307, 16221281, 16267463, 16360919, 16398659, 16414841, - 16460893, 16585361, 16593649, 16623409, 16656623, 16782571, 16831853, 16895731, 16976747, - 16999133, 17023487, 17102917, 17145467, 17218237, 17272673, 17349337, 17389357, 17437013, - 17529601, 17546899, 17596127, 17598389, 17769851, 17850539, 17905151, 17974933, 18129667, - 18171487, 18240449, 18285733, 18327913, 18378373, 18457339, 18545843, 18588623, 18596903, - 18738539, 18809653, 18812071, 18951881, 18999031, 19060859, 19096181, 19139989, 19424693, - 19498411, 19572593, 19591907, 19645847, 19780327, 19805323, 19840843, 19870597, 19918169, - 20089631, 20262569, 20309309, 20375401, 20413159, 20452727, 20607379, 20615771, 20755039, - 20764327, 20843129, 20922427, 20943073, 21000733, 21001829, 21160633, 21209177, 21240983, - 21303313, 21688549, 21709951, 21875251, 21925711, 21946439, 21985799, 22135361, 22186421, - 22261483, 22365353, 22450231, 22453117, 22619987, 22772507, 22844503, 22998827, 23207189, - 23272297, 23383889, 23437829, 23448269, 23502061, 23716519, 24033257, 24240143, 24319027, - 24364093, 24528373, 24584953, 24783229, 24877283, 24880481, 24971929, 24996571, 25054231, - 25065391, 25314179, 25352141, 25690723, 25788221, 25983217, 26169397, 26280467, 26480567, - 26694131, 26782109, 26795437, 26860699, 26948111, 26998049, 27180089, 27462497, 27566719, - 27671597, 27698903, 27775163, 27909803, 27974183, 28050847, 28092913, 28306813, 28713161, - 28998521, 29343331, 29579983, 29692241, 29834617, 29903437, 29916757, 30118477, 30259007, - 30663121, 30693379, 30927079, 30998419, 31083371, 31860737, 31965743, 32515583, 32777819, - 32902213, 33059981, 33136241, 33151001, 33388541, 33530251, 33785551, 33978053, 34170277, - 34270547, 34758037, 35305141, 35421499, 35609059, 35691199, 36115589, 36321367, 36459209, - 36634033, 36734893, 36998113, 37155143, 37438043, 37864361, 37975471, 38152661, 39121913, - 39458687, 39549707, 40019977, 40594469, 40783879, 40997909, 41485399, 42277273, 42599173, - 43105703, 43351309, 43724491, 43825351, 44346461, 45192947, 45537047, 45970307, 46847789, - 47204489, 47765779, 48037937, 48451463, 48677533, 49140673, 50078671, 50459971, 52307677, - 52929647, 53689459, 53939969, 54350669, 55915103, 57962561, 58098991, 58651771, 59771317, - 60226417, 61959979, 64379963, 64992503, 66233081, 66737381, 71339959, 73952233, 76840601, - 79052387, 81947069, 85147693, 87598591, 94352849, 104553157, - ]; - - pub fn new() -> Self { - Products { - table: &Self::TABLE, - } - } -} diff --git a/core/src/evaluator_generator/state_table_generator.rs b/core/src/evaluator_generator/state_table_generator.rs deleted file mode 100644 index 785ebe9..0000000 --- a/core/src/evaluator_generator/state_table_generator.rs +++ /dev/null @@ -1,631 +0,0 @@ -//! # Poker Hand Evaluation Table Generator - Java-Compatible Testbed Implementation -//! -//! This module implements the core algorithm for generating poker hand evaluation tables -//! specifically designed for poker bot testing frameworks. It creates Java-compatible -//! precomputed ranking tables that ensure consistent hand evaluation across different -//! poker tools and testing environments. -//! -//! ## Poker Testbed Context -//! -//! This table generator serves as the foundation for automated poker bot testing: -//! - **Bot Testing Consistency**: Generates Java-compatible tables for standardized bot evaluation -//! - **Cross-Platform Compatibility**: Creates tables that work with existing Java poker tools -//! - **Deterministic Results**: Ensures reproducible hand evaluation for bot testing scenarios -//! - **Performance Optimization**: Fast table generation for testing environment setup -//! - **Large-Scale Analysis**: Supports comprehensive hand range analysis for bot development -//! -//! ## Java Compatibility Design -//! -//! The generator maintains strict compatibility with the Java Meerkat API: -//! - **Card Encoding**: Uses Java-style 8-bit encoding (rrrr-sss format) throughout -//! - **Ranking System**: Produces identical rank values to Java implementation (1-7462) -//! - **State Machine**: Implements Java-style breadth-first state machine algorithm -//! - **Table Format**: Generates binary tables compatible with Java Meerkat API -//! - **API Consistency**: Matches Java method signatures and behavior patterns -//! -//! ## Algorithm Overview -//! -//! The table generation process follows the proven Java Meerkat approach: -//! 1. **Java-Style Encoding**: Each card encoded using rrrr-sss 8-bit format -//! 2. **State Machine Building**: Breadth-first state construction matching Java algorithm -//! 3. **Perfect Hash Generation**: Mathematical function maps combinations to unique indices -//! 4. **Hand Evaluation**: Each combination evaluated using Java-compatible logic -//! 5. **Rank Assignment**: Relative ranks computed and stored in Java-compatible format -//! -//! ## Hand Types Supported -//! -//! - **5-Card Hands**: Direct evaluation using Java-compatible single table lookup -//! - **6-Card Hands**: Evaluates all C(6,5) = 6 combinations, keeps best (Java logic) -//! - **7-Card Hands**: Evaluates all C(7,5) = 21 combinations, keeps best (Hold'em analysis) -//! -//! ## Performance Characteristics -//! -//! - **Generation Time**: ~1-2 seconds for complete Java-compatible tables -//! - **Memory Usage**: ~2.4MB for working tables during generation -//! - **Output Size**: ~128MB binary file with Java-compatible rankings -//! - **Coverage**: 32+ million possible card combinations (full deck coverage) -//! - **Java Compatibility**: 100% compatible with Java Meerkat API results -//! -//! ## Bot Testing Integration -//! -//! The generated tables enable efficient poker bot testing workflows: -//! - **Pre-Flop Analysis**: Fast evaluation of starting hand ranges -//! - **Post-Flop Analysis**: Quick assessment of hand strength development -//! - **Range vs Range**: Efficient comparison of bot playing strategies -//! - **Monte Carlo**: Fast hand evaluation for simulation-based testing -//! - **Decision Trees**: Quick strength calculation for bot decision making -//! -//! ## Mathematical Foundation -//! -//! The algorithm uses Java-compatible techniques: -//! - Prime number encoding for card rank uniqueness (matching Java) -//! - Bit manipulation for suit detection and flush identification -//! - Java-style state machine for comprehensive hand coverage -//! - Perfect hashing for O(1) runtime evaluation (Java-compatible) -//! - Binary search insertion for efficient table building - -use crate::api::card::Card; -use std::fs::File; -use std::io::{self, BufReader, BufWriter, Read, Write}; -use std::path::Path; - -/// Generates precomputed poker hand evaluation tables using the Meerkat algorithm. -/// -/// This struct manages the creation of perfect hash lookup tables that enable -/// O(1) poker hand evaluation. The generator creates tables covering all possible -/// card combinations and their relative hand strengths. -/// -/// # Example -/// ```rust,no_run -/// use poker_api::evaluator_generator::state_table_generator::StateTableGenerator; -/// -/// let mut generator = StateTableGenerator::new(); -/// generator.generate_tables(); -/// generator.save_tables().unwrap(); -/// ``` -pub struct StateTableGenerator { - /// Working table for hand rank calculations during generation. - /// Contains 612,978 entries covering all possible card combinations. - pub hand_ranks: Box<[u32]>, -} - -impl StateTableGenerator { - /// Size of the working table during generation. - /// This covers all possible card combinations for the perfect hash algorithm. - 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. - pub const FILE_NAME: &'static str = "math/HandRanks.dat"; - - /// Creates a new table generator with initialized working tables. - /// - /// Allocates memory for the working tables used during generation. - /// The tables are initially zero-filled and populated during generation. - pub fn new() -> Self { - let hand_ranks = vec![0u32; Self::SIZE].into_boxed_slice(); - Self { hand_ranks } - } - - /// Generates complete hand evaluation tables using Java-style state machine approach. - /// - /// This method implements the Java Meerkat API algorithm using a breadth-first - /// state machine approach. The process involves: - /// - /// 1. **State Building**: Build states breadth-first starting from empty hand - /// 2. **Card Addition**: Iteratively add cards to existing states - /// 3. **Binary Search**: Use binary search for key insertion/lookup - /// 4. **Table Population**: Store state transitions and final ranks - /// - /// # Performance - /// - Takes ~1-2 seconds on modern hardware - /// - Uses state machine approach like Java implementation - /// - Optimized with binary search insertion - pub fn generate_tables(&mut self) { - println!("Generating state table using Java-style state machine..."); - - // Initialize the table with invalid hand marker - for i in 0..Self::SIZE { - self.hand_ranks[i] = 9999; - } - - // Java-style state machine: build states breadth-first - // Start with empty hand (no cards) - let mut current_states = Vec::new(); - current_states.push(0u64); // Empty hand state - - // Iteratively add cards to build up states (0-6 cards per state) - for card_count in 0..7 { - println!("Building states for {}-card hands...", card_count); - - let mut next_states = Vec::new(); - - for &state in ¤t_states { - // Try adding each possible card to this state - for card in 0..52 { - let card_obj = Card::from_index(card).unwrap(); - let rank = card_obj.rank() as u64; // 1-13 - let suit = card_obj.suit() as u64; // 1-4 - - // Create Java-style 8-bit encoding: rrrr-sss - let encoded_card = (rank << 3) | suit; - - // Find the position to insert this card in the state - let mut card_position = 0; - let mut temp_state = state; - while (temp_state & 0xFF) != 0 { - temp_state >>= 8; - card_position += 1; - } - - // Check if this card is already in the state - let mut card_exists = false; - temp_state = state; - for _ in 0..card_position { - if (temp_state & 0xFF) == encoded_card as u64 { - card_exists = true; - break; - } - temp_state >>= 8; - } - - if card_exists { - continue; // Card already in state, skip - } - - // Add the new card to the state - let new_state = state | (encoded_card as u64) << (card_position * 8); - - // Evaluate the hand if we have 5+ cards - if card_position + 1 >= 5 { - let hand_rank = self.get_hand_rank(new_state); - - // Use binary search to insert into table - let index = self.insert_key(new_state); - if index < Self::SIZE { - self.hand_ranks[index] = hand_rank; - } - } - - // Add to next states if not complete - if card_position + 1 < 7 { - next_states.push(new_state); - } - } - } - - current_states = next_states; - } - - println!("State machine table generation complete."); - } - - /// Saves the generated hand evaluation tables to disk. - /// - /// Serializes the working tables to a binary file for later use by - /// the hand evaluator. The file format uses native endianness for - /// optimal loading performance. - /// - /// # Returns - /// - `Ok(())` - Tables successfully saved - /// - `Err(io::Error)` - Failed to write tables to disk - 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 mut writer = BufWriter::new(file); - - // Write each rank value as 4 bytes in native endianness - for &rank in self.hand_ranks.iter() { - writer.write_all(&rank.to_ne_bytes())?; - } - - // The working table needs to be expanded to the full table size - // Fill the rest with 9999 (invalid hand marker) to match Java behavior - let remaining_entries = Self::HAND_RANKS_SIZE - Self::SIZE; - let invalid_entry = 9999u32.to_ne_bytes(); - for _ in 0..remaining_entries { - writer.write_all(&invalid_entry)?; - } - - writer.flush()?; - println!("Hand evaluation tables saved to {}", Self::FILE_NAME); - Ok(()) - } - - /// Inserts a key into the hand ranking table using binary search insertion. - /// - /// This method maintains the table in sorted order for efficient lookups. - /// Used during table generation to organize computed hand ranks. - /// - /// # Arguments - /// * `key` - The hand key to insert into the table - /// - /// # Returns - /// * `usize` - The index where the key was inserted - pub fn insert_key(&mut self, key: u64) -> usize { - if key == 0 { - return 0; - } - - // Binary search insertion to maintain sorted order - let mut high = self.hand_ranks.len(); - let mut low = 0; - - while high - low > 1 { - let mid = (high + low) / 2; - if self.hand_ranks[mid] < key as u32 { - low = mid; - } else if self.hand_ranks[mid] > key as u32 { - high = mid; - } else { - return mid; // Key already exists - } - } - - if high < self.hand_ranks.len() { - self.hand_ranks[high] = key as u32; - } - high - } - - /// Computes the relative rank of a poker hand from its encoded key. - /// - /// This is the core evaluation function that: - /// 1. Decodes the key into individual cards using Java-style 8-bit encoding - /// 2. Encodes cards using prime numbers and bit patterns - /// 3. Evaluates hand strength based on card count - /// 4. Returns a rank value (lower = stronger hand) - /// - /// # Arguments - /// * `key` - Encoded representation of a card combination (rrrr-sss format) - /// - /// # Returns - /// * `u32` - Hand rank (0-7462, where 0 is the strongest hand) - pub fn get_hand_rank(&self, key: u64) -> u32 { - if key == 0 { - return 9999; // Invalid hand marker - } - - // Decode cards from the key and prepare for evaluation - let mut hand = Vec::with_capacity(7); - - // Prime numbers used for unique card rank encoding - // Each card rank maps to a unique prime for hand type detection - let primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]; - let mut num_cards = 0; - let mut holdrank = 9999; // Initialize with worst possible rank - - // Extract up to 7 cards from the encoded key (Java-style rrrr-sss format) - for card_index in 0..7 { - let current_card = ((key >> (8 * card_index)) & 0xFF) as u8; - if current_card == 0 { - break; // No more cards - } - - num_cards += 1; - let rank = current_card >> 3; // Extract rank (1-13, where 1=Deuce, 13=Ace) - let suit = current_card & 0x7; // Extract suit (1-4, where 1=Clubs, 4=Spades) - - // Convert from Java-style encoding to internal representation - let rank_internal = if rank > 0 { rank - 1 } else { 0 }; // Convert to 0-12 - let suit_internal = suit; // Already in correct range (1-4) - - // Ensure rank is within valid bounds for prime table access - let safe_rank = if usize::from(rank_internal) < primes.len() { - rank_internal - } else { - 0 - }; - - // Encode card using multiple techniques: - // - Prime number for rank uniqueness - // - Bit position for rank identification - // - Suit bits for flush detection - // - Additional bits for hand type analysis - let encoded_card = primes[usize::from(safe_rank)] - | (usize::from(safe_rank) << 8) - | (1 << (suit_internal + 11)) - | (1 << (16 + usize::from(safe_rank))); - hand.push(encoded_card); - } - - // Evaluate hand based on number of cards - match num_cards { - 5 => { - // Direct 5-card evaluation - holdrank = Self::eval_5hand( - hand[0] as u32, - hand[1] as u32, - hand[2] as u32, - hand[3] as u32, - hand[4] as u32, - ); - } - 6 => { - // Evaluate all possible 5-card combinations from 6 cards - holdrank = Self::eval_5hand( - hand[0] as u32, - hand[1] as u32, - hand[2] as u32, - hand[3] as u32, - hand[4] as u32, - ); - - // Check each card removal for potentially better hands - for i in 0..6 { - let mut temp_hand = hand.clone(); - temp_hand.remove(i); - let alt_rank = Self::eval_5hand( - temp_hand[0] as u32, - temp_hand[1] as u32, - temp_hand[2] as u32, - temp_hand[3] as u32, - temp_hand[4] as u32, - ); - holdrank = holdrank.min(alt_rank); - } - } - 7 => { - // Evaluate all possible 5-card combinations from 7 cards (C(7,5) = 21) - for i in 0..7 { - for j in (i + 1)..7 { - let mut temp_hand = hand.clone(); - temp_hand.remove(j); - temp_hand.remove(i); - let alt_rank = Self::eval_5hand( - temp_hand[0] as u32, - temp_hand[1] as u32, - temp_hand[2] as u32, - temp_hand[3] as u32, - temp_hand[4] as u32, - ); - holdrank = holdrank.min(alt_rank); - } - } - } - _ => return 9999, // Invalid card count - } - - // Convert internal rank to Java-style API rank (higher API values = stronger hands) - // Java: 1 = best hand (Royal Flush), 7462 = worst (7-High) - // Rust internal: 0 = best hand, 7462 = worst hand - // Conversion: java_rank = 7463 - rust_internal_rank - 7463u32.saturating_sub(holdrank) - } - - /// Evaluates a 5-card poker hand using Java-style single table approach. - /// - /// This function implements the core 5-card hand evaluation using a single - /// consolidated table that matches the Java implementation. The evaluation - /// process uses the same algorithm as the Java Meerkat API: - /// - /// 1. **Key Generation**: Create a unique key from the 5 cards - /// 2. **Table Lookup**: Use key to find rank in single precomputed table - /// 3. **Rank Return**: Return the absolute rank for the hand - /// - /// # Arguments - /// * `c1-c5` - Encoded card representations - /// - /// # Returns - /// * `u32` - Internal hand rank (lower values = stronger hands) - /// - /// # Algorithm Details - /// - Uses Java-style 8-bit card encoding (rrrr-sss) - /// - Single table lookup for O(1) evaluation - /// - Matches Java Meerkat API exactly - pub fn eval_5hand(c1: u32, c2: u32, c3: u32, c4: u32, c5: u32) -> u32 { - // Create Java-style 64-bit key from the 5 cards - let mut key: u64 = 0; - - // Convert each card to Java-style 8-bit encoding (rrrr-sss) - let cards = [c1, c2, c3, c4, c5]; - for (i, &card) in cards.iter().enumerate() { - // Extract rank and suit from internal encoding - let rank = ((card >> 8) & 0xF) + 1; // Convert to 1-13 (Deuce=1, Ace=13) - let suit = ((card >> 12) & 0xF) + 1; // Convert to 1-4 (Clubs=1, Spades=4) - - // Create Java-style 8-bit encoding: rrrr-sss - let encoded_card = (rank << 3) | suit; - key |= (encoded_card as u64) << (i * 8); - } - - // For now, return a placeholder - this will be replaced with actual table lookup - // In the full implementation, this would use a precomputed table like Java - key as u32 % 7463 // Placeholder: distribute across valid rank range - } - - /// 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 - // Fill the rest with 9999 (invalid hand marker) to match Java behavior - let remaining_entries = Self::HAND_RANKS_SIZE - Self::SIZE; - let invalid_entry = 9999u32.to_ne_bytes(); - for _ in 0..remaining_entries { - writer.write_all(&invalid_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() - } -} diff --git a/core/src/evaluator_generator/unique.rs b/core/src/evaluator_generator/unique.rs deleted file mode 100644 index 0b90eb6..0000000 --- a/core/src/evaluator_generator/unique.rs +++ /dev/null @@ -1,439 +0,0 @@ -//! # 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 table: &'static [u16], -} - -impl Unique { - pub const TABLE: [u16; 7937] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1608, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7462, 0, 0, 0, 0, 0, 0, 0, 7461, 0, - 0, 0, 7460, 0, 7459, 1607, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7458, 0, 0, 0, - 0, 0, 0, 0, 7457, 0, 0, 0, 7456, 0, 7455, 7454, 0, 0, 0, 0, 0, 0, 0, 0, 7453, 0, 0, 0, - 7452, 0, 7451, 7450, 0, 0, 0, 0, 7449, 0, 7448, 7447, 0, 0, 7446, 7445, 0, 1606, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7444, 0, 0, 0, 0, 0, 0, 0, 7443, 0, 0, 0, - 7442, 0, 7441, 7440, 0, 0, 0, 0, 0, 0, 0, 0, 7439, 0, 0, 0, 7438, 0, 7437, 7436, 0, 0, 0, - 0, 7435, 0, 7434, 7433, 0, 0, 7432, 7431, 0, 7430, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7429, 0, - 0, 0, 7428, 0, 7427, 7426, 0, 0, 0, 0, 7425, 0, 7424, 7423, 0, 0, 7422, 7421, 0, 7420, 0, - 0, 0, 0, 0, 0, 7419, 0, 7418, 7417, 0, 0, 7416, 7415, 0, 7414, 0, 0, 0, 0, 7413, 7412, 0, - 7411, 0, 0, 0, 1605, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 7410, 0, 0, 0, 0, 0, 0, 0, 7409, 0, 0, 0, 7408, 0, 7407, 7406, 0, 0, 0, 0, 0, 0, 0, 0, - 7405, 0, 0, 0, 7404, 0, 7403, 7402, 0, 0, 0, 0, 7401, 0, 7400, 7399, 0, 0, 7398, 7397, 0, - 7396, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7395, 0, 0, 0, 7394, 0, 7393, 7392, 0, 0, 0, 0, 7391, - 0, 7390, 7389, 0, 0, 7388, 7387, 0, 7386, 0, 0, 0, 0, 0, 0, 7385, 0, 7384, 7383, 0, 0, - 7382, 7381, 0, 7380, 0, 0, 0, 0, 7379, 7378, 0, 7377, 0, 0, 0, 7376, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 7375, 0, 0, 0, 7374, 0, 7373, 7372, 0, 0, 0, 0, 7371, 0, 7370, 7369, - 0, 0, 7368, 7367, 0, 7366, 0, 0, 0, 0, 0, 0, 7365, 0, 7364, 7363, 0, 0, 7362, 7361, 0, - 7360, 0, 0, 0, 0, 7359, 7358, 0, 7357, 0, 0, 0, 7356, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7355, - 0, 7354, 7353, 0, 0, 7352, 7351, 0, 7350, 0, 0, 0, 0, 7349, 7348, 0, 7347, 0, 0, 0, 7346, - 0, 0, 0, 0, 0, 0, 0, 0, 7345, 7344, 0, 7343, 0, 0, 0, 7342, 0, 0, 0, 0, 0, 0, 0, 1604, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 7341, 0, 0, 0, 0, 0, 0, 0, 7340, 0, 0, 0, 7339, 0, 7338, 7337, 0, 0, 0, 0, 0, 0, 0, 0, - 7336, 0, 0, 0, 7335, 0, 7334, 7333, 0, 0, 0, 0, 7332, 0, 7331, 7330, 0, 0, 7329, 7328, 0, - 7327, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7326, 0, 0, 0, 7325, 0, 7324, 7323, 0, 0, 0, 0, 7322, - 0, 7321, 7320, 0, 0, 7319, 7318, 0, 7317, 0, 0, 0, 0, 0, 0, 7316, 0, 7315, 7314, 0, 0, - 7313, 7312, 0, 7311, 0, 0, 0, 0, 7310, 7309, 0, 7308, 0, 0, 0, 7307, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 7306, 0, 0, 0, 7305, 0, 7304, 7303, 0, 0, 0, 0, 7302, 0, 7301, 7300, - 0, 0, 7299, 7298, 0, 7297, 0, 0, 0, 0, 0, 0, 7296, 0, 7295, 7294, 0, 0, 7293, 7292, 0, - 7291, 0, 0, 0, 0, 7290, 7289, 0, 7288, 0, 0, 0, 7287, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7286, - 0, 7285, 7284, 0, 0, 7283, 7282, 0, 7281, 0, 0, 0, 0, 7280, 7279, 0, 7278, 0, 0, 0, 7277, - 0, 0, 0, 0, 0, 0, 0, 0, 7276, 7275, 0, 7274, 0, 0, 0, 7273, 0, 0, 0, 0, 0, 0, 0, 7272, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7271, 0, 0, 0, 7270, 0, - 7269, 7268, 0, 0, 0, 0, 7267, 0, 7266, 7265, 0, 0, 7264, 7263, 0, 7262, 0, 0, 0, 0, 0, 0, - 7261, 0, 7260, 7259, 0, 0, 7258, 7257, 0, 7256, 0, 0, 0, 0, 7255, 7254, 0, 7253, 0, 0, 0, - 7252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7251, 0, 7250, 7249, 0, 0, 7248, 7247, 0, 7246, 0, 0, - 0, 0, 7245, 7244, 0, 7243, 0, 0, 0, 7242, 0, 0, 0, 0, 0, 0, 0, 0, 7241, 7240, 0, 7239, 0, - 0, 0, 7238, 0, 0, 0, 0, 0, 0, 0, 7237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 7236, 0, 7235, 7234, 0, 0, 7233, 7232, 0, 7231, 0, 0, 0, 0, 7230, 7229, 0, 7228, 0, 0, - 0, 7227, 0, 0, 0, 0, 0, 0, 0, 0, 7226, 7225, 0, 7224, 0, 0, 0, 7223, 0, 0, 0, 0, 0, 0, 0, - 7222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7221, 7220, 0, 7219, 0, 0, 0, 7218, - 0, 0, 0, 0, 0, 0, 0, 7217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1603, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7216, 0, 0, 0, 0, 0, 0, 0, 7215, 0, 0, 0, 7214, 0, - 7213, 7212, 0, 0, 0, 0, 0, 0, 0, 0, 7211, 0, 0, 0, 7210, 0, 7209, 7208, 0, 0, 0, 0, 7207, - 0, 7206, 7205, 0, 0, 7204, 7203, 0, 7202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7201, 0, 0, 0, - 7200, 0, 7199, 7198, 0, 0, 0, 0, 7197, 0, 7196, 7195, 0, 0, 7194, 7193, 0, 7192, 0, 0, 0, - 0, 0, 0, 7191, 0, 7190, 7189, 0, 0, 7188, 7187, 0, 7186, 0, 0, 0, 0, 7185, 7184, 0, 7183, - 0, 0, 0, 7182, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7181, 0, 0, 0, 7180, 0, 7179, - 7178, 0, 0, 0, 0, 7177, 0, 7176, 7175, 0, 0, 7174, 7173, 0, 7172, 0, 0, 0, 0, 0, 0, 7171, - 0, 7170, 7169, 0, 0, 7168, 7167, 0, 7166, 0, 0, 0, 0, 7165, 7164, 0, 7163, 0, 0, 0, 7162, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7161, 0, 7160, 7159, 0, 0, 7158, 7157, 0, 7156, 0, 0, 0, 0, - 7155, 7154, 0, 7153, 0, 0, 0, 7152, 0, 0, 0, 0, 0, 0, 0, 0, 7151, 7150, 0, 7149, 0, 0, 0, - 7148, 0, 0, 0, 0, 0, 0, 0, 7147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 7146, 0, 0, 0, 7145, 0, 7144, 7143, 0, 0, 0, 0, 7142, 0, 7141, 7140, 0, 0, 7139, - 7138, 0, 7137, 0, 0, 0, 0, 0, 0, 7136, 0, 7135, 7134, 0, 0, 7133, 7132, 0, 7131, 0, 0, 0, - 0, 7130, 7129, 0, 7128, 0, 0, 0, 7127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7126, 0, 7125, 7124, - 0, 0, 7123, 7122, 0, 7121, 0, 0, 0, 0, 7120, 7119, 0, 7118, 0, 0, 0, 7117, 0, 0, 0, 0, 0, - 0, 0, 0, 7116, 7115, 0, 7114, 0, 0, 0, 7113, 0, 0, 0, 0, 0, 0, 0, 7112, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7111, 0, 7110, 7109, 0, 0, 7108, 7107, 0, 7106, 0, 0, - 0, 0, 7105, 7104, 0, 7103, 0, 0, 0, 7102, 0, 0, 0, 0, 0, 0, 0, 0, 7101, 7100, 0, 7099, 0, - 0, 0, 7098, 0, 0, 0, 0, 0, 0, 0, 7097, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 7096, 7095, 0, 7094, 0, 0, 0, 7093, 0, 0, 0, 0, 0, 0, 0, 7092, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 7091, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7090, 0, 0, 0, 7089, 0, 7088, 7087, 0, 0, - 0, 0, 7086, 0, 7085, 7084, 0, 0, 7083, 7082, 0, 7081, 0, 0, 0, 0, 0, 0, 7080, 0, 7079, - 7078, 0, 0, 7077, 7076, 0, 7075, 0, 0, 0, 0, 7074, 7073, 0, 7072, 0, 0, 0, 7071, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 7070, 0, 7069, 7068, 0, 0, 7067, 7066, 0, 7065, 0, 0, 0, 0, 7064, - 7063, 0, 7062, 0, 0, 0, 7061, 0, 0, 0, 0, 0, 0, 0, 0, 7060, 7059, 0, 7058, 0, 0, 0, 7057, - 0, 0, 0, 0, 0, 0, 0, 7056, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7055, 0, - 7054, 7053, 0, 0, 7052, 7051, 0, 7050, 0, 0, 0, 0, 7049, 7048, 0, 7047, 0, 0, 0, 7046, 0, - 0, 0, 0, 0, 0, 0, 0, 7045, 7044, 0, 7043, 0, 0, 0, 7042, 0, 0, 0, 0, 0, 0, 0, 7041, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7040, 7039, 0, 7038, 0, 0, 0, 7037, 0, 0, 0, 0, - 0, 0, 0, 7036, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7035, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7034, 0, - 7033, 7032, 0, 0, 7031, 7030, 0, 7029, 0, 0, 0, 0, 7028, 7027, 0, 7026, 0, 0, 0, 7025, 0, - 0, 0, 0, 0, 0, 0, 0, 7024, 7023, 0, 7022, 0, 0, 0, 7021, 0, 0, 0, 0, 0, 0, 0, 7020, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7019, 7018, 0, 7017, 0, 0, 0, 7016, 0, 0, 0, 0, - 0, 0, 0, 7015, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7014, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7013, 7012, 0, - 7011, 0, 0, 0, 7010, 0, 0, 0, 0, 0, 0, 0, 7009, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 7008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1602, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7007, 0, 0, 0, 0, - 0, 0, 0, 7006, 0, 0, 0, 7005, 0, 7004, 7003, 0, 0, 0, 0, 0, 0, 0, 0, 7002, 0, 0, 0, 7001, - 0, 7000, 6999, 0, 0, 0, 0, 6998, 0, 6997, 6996, 0, 0, 6995, 6994, 0, 6993, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 6992, 0, 0, 0, 6991, 0, 6990, 6989, 0, 0, 0, 0, 6988, 0, 6987, 6986, 0, 0, - 6985, 6984, 0, 6983, 0, 0, 0, 0, 0, 0, 6982, 0, 6981, 6980, 0, 0, 6979, 6978, 0, 6977, 0, - 0, 0, 0, 6976, 6975, 0, 6974, 0, 0, 0, 6973, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 6972, 0, 0, 0, 6971, 0, 6970, 6969, 0, 0, 0, 0, 6968, 0, 6967, 6966, 0, 0, 6965, 6964, 0, - 6963, 0, 0, 0, 0, 0, 0, 6962, 0, 6961, 6960, 0, 0, 6959, 6958, 0, 6957, 0, 0, 0, 0, 6956, - 6955, 0, 6954, 0, 0, 0, 6953, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6952, 0, 6951, 6950, 0, 0, - 6949, 6948, 0, 6947, 0, 0, 0, 0, 6946, 6945, 0, 6944, 0, 0, 0, 6943, 0, 0, 0, 0, 0, 0, 0, - 0, 6942, 6941, 0, 6940, 0, 0, 0, 6939, 0, 0, 0, 0, 0, 0, 0, 6938, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6937, 0, 0, 0, 6936, 0, 6935, 6934, 0, 0, 0, 0, - 6933, 0, 6932, 6931, 0, 0, 6930, 6929, 0, 6928, 0, 0, 0, 0, 0, 0, 6927, 0, 6926, 6925, 0, - 0, 6924, 6923, 0, 6922, 0, 0, 0, 0, 6921, 6920, 0, 6919, 0, 0, 0, 6918, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6917, 0, 6916, 6915, 0, 0, 6914, 6913, 0, 6912, 0, 0, 0, 0, 6911, 6910, 0, - 6909, 0, 0, 0, 6908, 0, 0, 0, 0, 0, 0, 0, 0, 6907, 6906, 0, 6905, 0, 0, 0, 6904, 0, 0, 0, - 0, 0, 0, 0, 6903, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6902, 0, 6901, - 6900, 0, 0, 6899, 6898, 0, 6897, 0, 0, 0, 0, 6896, 6895, 0, 6894, 0, 0, 0, 6893, 0, 0, 0, - 0, 0, 0, 0, 0, 6892, 6891, 0, 6890, 0, 0, 0, 6889, 0, 0, 0, 0, 0, 0, 0, 6888, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6887, 6886, 0, 6885, 0, 0, 0, 6884, 0, 0, 0, 0, 0, 0, - 0, 6883, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6882, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6881, - 0, 0, 0, 6880, 0, 6879, 6878, 0, 0, 0, 0, 6877, 0, 6876, 6875, 0, 0, 6874, 6873, 0, 6872, - 0, 0, 0, 0, 0, 0, 6871, 0, 6870, 6869, 0, 0, 6868, 6867, 0, 6866, 0, 0, 0, 0, 6865, 6864, - 0, 6863, 0, 0, 0, 6862, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6861, 0, 6860, 6859, 0, 0, 6858, - 6857, 0, 6856, 0, 0, 0, 0, 6855, 6854, 0, 6853, 0, 0, 0, 6852, 0, 0, 0, 0, 0, 0, 0, 0, - 6851, 6850, 0, 6849, 0, 0, 0, 6848, 0, 0, 0, 0, 0, 0, 0, 6847, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 6846, 0, 6845, 6844, 0, 0, 6843, 6842, 0, 6841, 0, 0, 0, 0, - 6840, 6839, 0, 6838, 0, 0, 0, 6837, 0, 0, 0, 0, 0, 0, 0, 0, 6836, 6835, 0, 6834, 0, 0, 0, - 6833, 0, 0, 0, 0, 0, 0, 0, 6832, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6831, - 6830, 0, 6829, 0, 0, 0, 6828, 0, 0, 0, 0, 0, 0, 0, 6827, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6826, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6825, 0, 6824, 6823, 0, 0, 6822, 6821, 0, 6820, 0, 0, 0, 0, - 6819, 6818, 0, 6817, 0, 0, 0, 6816, 0, 0, 0, 0, 0, 0, 0, 0, 6815, 6814, 0, 6813, 0, 0, 0, - 6812, 0, 0, 0, 0, 0, 0, 0, 6811, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6810, - 6809, 0, 6808, 0, 0, 0, 6807, 0, 0, 0, 0, 0, 0, 0, 6806, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6805, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 6804, 6803, 0, 6802, 0, 0, 0, 6801, 0, 0, 0, 0, 0, 0, 0, 6800, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6799, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6798, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 6797, 0, 0, 0, 6796, 0, 6795, 6794, 0, 0, 0, 0, 6793, 0, 6792, 6791, 0, 0, 6790, 6789, 0, - 6788, 0, 0, 0, 0, 0, 0, 6787, 0, 6786, 6785, 0, 0, 6784, 6783, 0, 6782, 0, 0, 0, 0, 6781, - 6780, 0, 6779, 0, 0, 0, 6778, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6777, 0, 6776, 6775, 0, 0, - 6774, 6773, 0, 6772, 0, 0, 0, 0, 6771, 6770, 0, 6769, 0, 0, 0, 6768, 0, 0, 0, 0, 0, 0, 0, - 0, 6767, 6766, 0, 6765, 0, 0, 0, 6764, 0, 0, 0, 0, 0, 0, 0, 6763, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6762, 0, 6761, 6760, 0, 0, 6759, 6758, 0, 6757, 0, 0, 0, 0, - 6756, 6755, 0, 6754, 0, 0, 0, 6753, 0, 0, 0, 0, 0, 0, 0, 0, 6752, 6751, 0, 6750, 0, 0, 0, - 6749, 0, 0, 0, 0, 0, 0, 0, 6748, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6747, - 6746, 0, 6745, 0, 0, 0, 6744, 0, 0, 0, 0, 0, 0, 0, 6743, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6742, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6741, 0, 6740, 6739, 0, 0, 6738, 6737, 0, 6736, 0, 0, 0, 0, - 6735, 6734, 0, 6733, 0, 0, 0, 6732, 0, 0, 0, 0, 0, 0, 0, 0, 6731, 6730, 0, 6729, 0, 0, 0, - 6728, 0, 0, 0, 0, 0, 0, 0, 6727, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6726, - 6725, 0, 6724, 0, 0, 0, 6723, 0, 0, 0, 0, 0, 0, 0, 6722, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6721, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 6720, 6719, 0, 6718, 0, 0, 0, 6717, 0, 0, 0, 0, 0, 0, 0, 6716, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6715, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6714, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6713, 0, 6712, - 6711, 0, 0, 6710, 6709, 0, 6708, 0, 0, 0, 0, 6707, 6706, 0, 6705, 0, 0, 0, 6704, 0, 0, 0, - 0, 0, 0, 0, 0, 6703, 6702, 0, 6701, 0, 0, 0, 6700, 0, 0, 0, 0, 0, 0, 0, 6699, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6698, 6697, 0, 6696, 0, 0, 0, 6695, 0, 0, 0, 0, 0, 0, - 0, 6694, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6693, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6692, 6691, 0, 6690, 0, - 0, 0, 6689, 0, 0, 0, 0, 0, 0, 0, 6688, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6687, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 6686, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 6685, 6684, 0, 6683, 0, 0, 0, 6682, 0, 0, 0, 0, 0, 0, 0, 6681, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6680, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6679, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1601, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1609, 0, 0, 0, 0, 0, 0, 0, 6678, 0, 0, 0, - 6677, 0, 6676, 6675, 0, 0, 0, 0, 0, 0, 0, 0, 6674, 0, 0, 0, 6673, 0, 6672, 6671, 0, 0, 0, - 0, 6670, 0, 6669, 6668, 0, 0, 6667, 6666, 0, 6665, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6664, 0, - 0, 0, 6663, 0, 6662, 6661, 0, 0, 0, 0, 6660, 0, 6659, 6658, 0, 0, 6657, 6656, 0, 6655, 0, - 0, 0, 0, 0, 0, 6654, 0, 6653, 6652, 0, 0, 6651, 6650, 0, 6649, 0, 0, 0, 0, 6648, 6647, 0, - 6646, 0, 0, 0, 6645, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6644, 0, 0, 0, 6643, 0, - 6642, 6641, 0, 0, 0, 0, 6640, 0, 6639, 6638, 0, 0, 6637, 6636, 0, 6635, 0, 0, 0, 0, 0, 0, - 6634, 0, 6633, 6632, 0, 0, 6631, 6630, 0, 6629, 0, 0, 0, 0, 6628, 6627, 0, 6626, 0, 0, 0, - 6625, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6624, 0, 6623, 6622, 0, 0, 6621, 6620, 0, 6619, 0, 0, - 0, 0, 6618, 6617, 0, 6616, 0, 0, 0, 6615, 0, 0, 0, 0, 0, 0, 0, 0, 6614, 6613, 0, 6612, 0, - 0, 0, 6611, 0, 0, 0, 0, 0, 0, 0, 6610, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 6609, 0, 0, 0, 6608, 0, 6607, 6606, 0, 0, 0, 0, 6605, 0, 6604, 6603, 0, 0, - 6602, 6601, 0, 6600, 0, 0, 0, 0, 0, 0, 6599, 0, 6598, 6597, 0, 0, 6596, 6595, 0, 6594, 0, - 0, 0, 0, 6593, 6592, 0, 6591, 0, 0, 0, 6590, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6589, 0, 6588, - 6587, 0, 0, 6586, 6585, 0, 6584, 0, 0, 0, 0, 6583, 6582, 0, 6581, 0, 0, 0, 6580, 0, 0, 0, - 0, 0, 0, 0, 0, 6579, 6578, 0, 6577, 0, 0, 0, 6576, 0, 0, 0, 0, 0, 0, 0, 6575, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6574, 0, 6573, 6572, 0, 0, 6571, 6570, 0, 6569, - 0, 0, 0, 0, 6568, 6567, 0, 6566, 0, 0, 0, 6565, 0, 0, 0, 0, 0, 0, 0, 0, 6564, 6563, 0, - 6562, 0, 0, 0, 6561, 0, 0, 0, 0, 0, 0, 0, 6560, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 6559, 6558, 0, 6557, 0, 0, 0, 6556, 0, 0, 0, 0, 0, 0, 0, 6555, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 6554, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6553, 0, 0, 0, 6552, 0, 6551, 6550, - 0, 0, 0, 0, 6549, 0, 6548, 6547, 0, 0, 6546, 6545, 0, 6544, 0, 0, 0, 0, 0, 0, 6543, 0, - 6542, 6541, 0, 0, 6540, 6539, 0, 6538, 0, 0, 0, 0, 6537, 6536, 0, 6535, 0, 0, 0, 6534, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 6533, 0, 6532, 6531, 0, 0, 6530, 6529, 0, 6528, 0, 0, 0, 0, - 6527, 6526, 0, 6525, 0, 0, 0, 6524, 0, 0, 0, 0, 0, 0, 0, 0, 6523, 6522, 0, 6521, 0, 0, 0, - 6520, 0, 0, 0, 0, 0, 0, 0, 6519, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 6518, 0, 6517, 6516, 0, 0, 6515, 6514, 0, 6513, 0, 0, 0, 0, 6512, 6511, 0, 6510, 0, 0, 0, - 6509, 0, 0, 0, 0, 0, 0, 0, 0, 6508, 6507, 0, 6506, 0, 0, 0, 6505, 0, 0, 0, 0, 0, 0, 0, - 6504, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6503, 6502, 0, 6501, 0, 0, 0, 6500, - 0, 0, 0, 0, 0, 0, 0, 6499, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6498, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 6497, 0, 6496, 6495, 0, 0, 6494, 6493, 0, 6492, 0, 0, 0, 0, 6491, 6490, 0, 6489, 0, 0, 0, - 6488, 0, 0, 0, 0, 0, 0, 0, 0, 6487, 6486, 0, 6485, 0, 0, 0, 6484, 0, 0, 0, 0, 0, 0, 0, - 6483, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6482, 6481, 0, 6480, 0, 0, 0, 6479, - 0, 0, 0, 0, 0, 0, 0, 6478, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6477, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6476, - 6475, 0, 6474, 0, 0, 0, 6473, 0, 0, 0, 0, 0, 0, 0, 6472, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6471, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 6470, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6469, 0, 0, 0, 6468, 0, 6467, - 6466, 0, 0, 0, 0, 6465, 0, 6464, 6463, 0, 0, 6462, 6461, 0, 6460, 0, 0, 0, 0, 0, 0, 6459, - 0, 6458, 6457, 0, 0, 6456, 6455, 0, 6454, 0, 0, 0, 0, 6453, 6452, 0, 6451, 0, 0, 0, 6450, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6449, 0, 6448, 6447, 0, 0, 6446, 6445, 0, 6444, 0, 0, 0, 0, - 6443, 6442, 0, 6441, 0, 0, 0, 6440, 0, 0, 0, 0, 0, 0, 0, 0, 6439, 6438, 0, 6437, 0, 0, 0, - 6436, 0, 0, 0, 0, 0, 0, 0, 6435, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 6434, 0, 6433, 6432, 0, 0, 6431, 6430, 0, 6429, 0, 0, 0, 0, 6428, 6427, 0, 6426, 0, 0, 0, - 6425, 0, 0, 0, 0, 0, 0, 0, 0, 6424, 6423, 0, 6422, 0, 0, 0, 6421, 0, 0, 0, 0, 0, 0, 0, - 6420, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6419, 6418, 0, 6417, 0, 0, 0, 6416, - 0, 0, 0, 0, 0, 0, 0, 6415, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6414, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 6413, 0, 6412, 6411, 0, 0, 6410, 6409, 0, 6408, 0, 0, 0, 0, 6407, 6406, 0, 6405, 0, 0, 0, - 6404, 0, 0, 0, 0, 0, 0, 0, 0, 6403, 6402, 0, 6401, 0, 0, 0, 6400, 0, 0, 0, 0, 0, 0, 0, - 6399, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6398, 6397, 0, 6396, 0, 0, 0, 6395, - 0, 0, 0, 0, 0, 0, 0, 6394, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6393, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6392, - 6391, 0, 6390, 0, 0, 0, 6389, 0, 0, 0, 0, 0, 0, 0, 6388, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6387, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 6386, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6385, 0, 6384, 6383, 0, 0, 6382, 6381, 0, - 6380, 0, 0, 0, 0, 6379, 6378, 0, 6377, 0, 0, 0, 6376, 0, 0, 0, 0, 0, 0, 0, 0, 6375, 6374, - 0, 6373, 0, 0, 0, 6372, 0, 0, 0, 0, 0, 0, 0, 6371, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 6370, 6369, 0, 6368, 0, 0, 0, 6367, 0, 0, 0, 0, 0, 0, 0, 6366, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 6365, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6364, 6363, 0, 6362, 0, 0, 0, 6361, 0, 0, 0, 0, 0, - 0, 0, 6360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6359, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6358, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6357, - 6356, 0, 6355, 0, 0, 0, 6354, 0, 0, 0, 0, 0, 0, 0, 6353, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6352, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 6351, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6350, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6349, - 0, 0, 0, 6348, 0, 6347, 6346, 0, 0, 0, 0, 6345, 0, 6344, 6343, 0, 0, 6342, 6341, 0, 6340, - 0, 0, 0, 0, 0, 0, 6339, 0, 6338, 6337, 0, 0, 6336, 6335, 0, 6334, 0, 0, 0, 0, 6333, 6332, - 0, 6331, 0, 0, 0, 6330, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6329, 0, 6328, 6327, 0, 0, 6326, - 6325, 0, 6324, 0, 0, 0, 0, 6323, 6322, 0, 6321, 0, 0, 0, 6320, 0, 0, 0, 0, 0, 0, 0, 0, - 6319, 6318, 0, 6317, 0, 0, 0, 6316, 0, 0, 0, 0, 0, 0, 0, 6315, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 6314, 0, 6313, 6312, 0, 0, 6311, 6310, 0, 6309, 0, 0, 0, 0, - 6308, 6307, 0, 6306, 0, 0, 0, 6305, 0, 0, 0, 0, 0, 0, 0, 0, 6304, 6303, 0, 6302, 0, 0, 0, - 6301, 0, 0, 0, 0, 0, 0, 0, 6300, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6299, - 6298, 0, 6297, 0, 0, 0, 6296, 0, 0, 0, 0, 0, 0, 0, 6295, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6293, 0, 6292, 6291, 0, 0, 6290, 6289, 0, 6288, 0, 0, 0, 0, - 6287, 6286, 0, 6285, 0, 0, 0, 6284, 0, 0, 0, 0, 0, 0, 0, 0, 6283, 6282, 0, 6281, 0, 0, 0, - 6280, 0, 0, 0, 0, 0, 0, 0, 6279, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6278, - 6277, 0, 6276, 0, 0, 0, 6275, 0, 0, 0, 0, 0, 0, 0, 6274, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 6273, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 6272, 6271, 0, 6270, 0, 0, 0, 6269, 0, 0, 0, 0, 0, 0, 0, 6268, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6267, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6266, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6265, 0, 6264, - 6263, 0, 0, 6262, 6261, 0, 6260, 0, 0, 0, 0, 6259, 6258, 0, 6257, 0, 0, 0, 6256, 0, 0, 0, - 0, 0, 0, 0, 0, 6255, 6254, 0, 6253, 0, 0, 0, 6252, 0, 0, 0, 0, 0, 0, 0, 6251, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6250, 6249, 0, 6248, 0, 0, 0, 6247, 0, 0, 0, 0, 0, 0, - 0, 6246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6244, 6243, 0, 6242, 0, - 0, 0, 6241, 0, 0, 0, 0, 0, 0, 0, 6240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6239, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 6238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 6237, 6236, 0, 6235, 0, 0, 0, 6234, 0, 0, 0, 0, 0, 0, 0, 6233, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6230, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 6229, 0, 6228, 6227, 0, 0, 6226, 6225, 0, 6224, 0, 0, 0, 0, 6223, 6222, 0, 6221, - 0, 0, 0, 6220, 0, 0, 0, 0, 0, 0, 0, 0, 6219, 6218, 0, 6217, 0, 0, 0, 6216, 0, 0, 0, 0, 0, - 0, 0, 6215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6214, 6213, 0, 6212, 0, 0, 0, - 6211, 0, 0, 0, 0, 0, 0, 0, 6210, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6209, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 6208, 6207, 0, 6206, 0, 0, 0, 6205, 0, 0, 0, 0, 0, 0, 0, 6204, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 6203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 6202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6201, 6200, 0, 6199, 0, 0, 0, 6198, 0, 0, 0, - 0, 0, 0, 0, 6197, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6196, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6195, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 6194, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6193, 6192, 0, 6191, 0, 0, 0, 6190, 0, 0, 0, 0, 0, 0, 0, - 6189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6188, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6187, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6186, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1600, - ]; - - pub fn new() -> Self { - Unique { - table: &Self::TABLE, - } - } -} diff --git a/core/src/evaluator_generator/values.rs b/core/src/evaluator_generator/values.rs deleted file mode 100644 index 340aec1..0000000 --- a/core/src/evaluator_generator/values.rs +++ /dev/null @@ -1,451 +0,0 @@ -//! # 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 table: &'static [u16], -} - -impl Values { - pub const TABLE: [u16; 4888] = [ - 166, 322, 165, 310, 164, 2467, 154, 2466, 163, 3325, 321, 162, 3324, 2464, 2401, 161, 2465, - 3314, 160, 2461, 159, 2400, 320, 3323, 153, 2457, 6185, 2463, 3303, 2452, 158, 3322, 157, - 298, 2460, 2446, 152, 3292, 156, 2398, 3321, 2462, 5965, 155, 6184, 309, 2456, 3320, 2439, - 3313, 2395, 2459, 2431, 2335, 2451, 6181, 3319, 3281, 2422, 151, 2391, 2445, 6183, 2399, - 2455, 319, 3291, 2412, 5964, 6175, 2386, 3318, 5745, 150, 2450, 6180, 3312, 3317, 297, - 6165, 2458, 2438, 5961, 2430, 2380, 142, 2444, 3311, 308, 3316, 318, 286, 149, 6150, 5963, - 6174, 3259, 5525, 3315, 2421, 2397, 2454, 5955, 148, 6182, 2373, 3302, 6164, 2437, 5960, - 2411, 5744, 2449, 2365, 3310, 5945, 6178, 2429, 6129, 2334, 2394, 2453, 6179, 6101, 147, - 141, 3309, 6149, 5741, 2448, 2356, 2443, 3215, 2269, 5930, 2420, 2396, 5954, 3290, 3248, - 3280, 2346, 6065, 6172, 2390, 2410, 3308, 317, 146, 6173, 2442, 5944, 3258, 6128, 3270, - 2393, 6020, 3301, 6162, 145, 3289, 5735, 2436, 2385, 5958, 2447, 6100, 5909, 2333, 6169, - 6163, 2428, 2332, 5881, 5725, 6177, 316, 5929, 3307, 3300, 6159, 144, 2435, 6147, 3204, - 285, 3306, 2379, 6064, 2441, 2389, 6148, 2427, 5524, 2329, 2419, 307, 143, 5845, 3288, - 5952, 3214, 3257, 2268, 6019, 5710, 5962, 3160, 2440, 6144, 2384, 2409, 5305, 5908, 3269, - 5800, 3305, 3287, 6171, 5942, 5521, 3299, 6126, 2418, 5743, 2392, 6155, 5880, 2372, 2434, - 5949, 6176, 6127, 6098, 5959, 3304, 2331, 6161, 2364, 2426, 315, 2325, 2408, 3298, 3094, - 6099, 2378, 5689, 140, 2433, 6168, 5939, 3286, 6123, 5740, 5927, 306, 5661, 5844, 6140, - 2425, 3213, 2320, 130, 6095, 3279, 2328, 6062, 6158, 2355, 5515, 2417, 2388, 6146, 5085, - 5304, 2267, 5799, 3297, 6063, 3149, 6170, 6135, 274, 2432, 5953, 5924, 5523, 6017, 3247, - 2371, 2345, 5625, 2407, 5505, 2416, 2383, 3285, 2424, 3278, 6018, 5906, 2314, 6059, 5742, - 3159, 5935, 6160, 2363, 6119, 5734, 2387, 6143, 5943, 3237, 3284, 296, 5878, 5580, 6167, - 2406, 3256, 6091, 3017, 5520, 2324, 6125, 6014, 5957, 6154, 3083, 3296, 6114, 5724, 2382, - 314, 5490, 5903, 2415, 6097, 5739, 2377, 139, 6157, 3295, 2354, 5920, 6086, 6145, 5084, - 2319, 5738, 2423, 129, 3093, 5928, 2307, 3283, 5875, 5842, 3212, 3277, 6122, 2405, 2266, - 6055, 3203, 3246, 313, 2344, 2299, 305, 6139, 5915, 2203, 6108, 3282, 5709, 6094, 2376, - 5522, 3158, 5797, 138, 6061, 3255, 3294, 5514, 6010, 6142, 3276, 5951, 6050, 3193, 5303, - 5469, 6080, 284, 2414, 2370, 2313, 5839, 4865, 2381, 6134, 262, 5899, 2263, 5733, 6124, - 5956, 6016, 6153, 3236, 5441, 5907, 2413, 3254, 2362, 3293, 2290, 5504, 6005, 5732, 5941, - 5301, 5871, 2404, 3006, 6096, 5519, 5794, 6058, 2330, 6166, 304, 5879, 6118, 5894, 5948, - 5723, 2929, 3092, 3275, 5688, 2403, 2369, 6044, 2280, 5722, 6090, 6121, 2375, 3016, 5866, - 137, 3202, 6013, 5737, 6073, 4645, 5660, 6156, 2306, 5405, 2361, 6138, 312, 2353, 6113, - 5729, 5938, 3253, 5081, 5489, 6093, 5999, 2265, 5835, 2327, 5926, 6060, 3211, 2830, 2298, - 5843, 2259, 6085, 5950, 2374, 5083, 3226, 136, 273, 128, 5888, 5360, 5708, 2402, 4864, - 2343, 6133, 5295, 5719, 5513, 5790, 6054, 6015, 5707, 5830, 3192, 5302, 3157, 3274, 5860, - 3210, 6037, 5798, 5624, 2352, 3148, 2254, 6141, 5940, 2137, 2202, 2368, 6107, 2262, 311, - 5923, 6057, 3268, 3273, 6029, 5285, 6117, 2289, 5947, 6009, 5503, 5518, 5785, 5731, 3252, - 6049, 3245, 5468, 6152, 2360, 6079, 5992, 303, 5579, 5905, 135, 2342, 3138, 5934, 6089, - 3015, 2323, 2367, 6012, 5704, 3251, 3156, 295, 2918, 4644, 5440, 5687, 5984, 5824, 5877, - 2279, 6112, 3209, 5937, 6004, 5721, 5300, 2248, 4425, 3091, 2359, 3267, 5925, 5686, 5715, - 5853, 3082, 5659, 3272, 2720, 6084, 3182, 5728, 6120, 2318, 5270, 3201, 6151, 2928, 5488, - 5902, 5779, 2351, 6043, 5658, 6137, 5075, 2819, 2258, 5919, 6053, 6092, 5082, 3225, 2326, - 3250, 6072, 2366, 3072, 3271, 134, 5404, 5874, 5975, 3147, 5841, 5512, 3244, 5718, 5080, - 2200, 6106, 3090, 2341, 5922, 5683, 5998, 2264, 5706, 2350, 4861, 2829, 6132, 2358, 5065, - 5817, 133, 5623, 6008, 5700, 2253, 3208, 250, 5914, 6048, 261, 3249, 2241, 6078, 2201, - 5359, 5904, 2312, 5655, 2599, 4863, 5796, 6136, 5933, 5622, 5502, 5294, 5809, 3243, 3266, - 3207, 5517, 2340, 5249, 294, 6056, 3235, 2233, 5467, 5772, 6036, 5876, 5578, 5838, 5509, - 3137, 6116, 6003, 5695, 5946, 3155, 2136, 5298, 5898, 4424, 2261, 5703, 5221, 4855, 5577, - 302, 6131, 3081, 5439, 5764, 6028, 2349, 5284, 132, 6088, 3265, 3014, 5050, 2322, 6011, - 2927, 5299, 2247, 5870, 5901, 5991, 3005, 4641, 6042, 5685, 5793, 5619, 5499, 5714, 6111, - 2357, 5936, 3089, 5918, 2709, 5679, 5487, 5893, 3181, 3206, 5736, 3242, 6071, 4205, 4643, - 2305, 2224, 5873, 5983, 2339, 5657, 131, 6115, 5840, 3200, 6083, 301, 5078, 2317, 5651, - 5997, 127, 2995, 5865, 3154, 5574, 5185, 2828, 3071, 2297, 5403, 5755, 2719, 6087, 238, - 5511, 3013, 5913, 5674, 2321, 6052, 3205, 5269, 5079, 2199, 2214, 4635, 3264, 5682, 5834, - 3127, 5795, 3146, 6110, 5074, 5292, 3985, 3199, 2348, 2257, 118, 5484, 5699, 6105, 5029, - 5646, 2071, 3191, 5921, 3224, 6130, 5140, 2240, 5887, 6035, 5358, 5654, 2588, 5837, 5974, - 4862, 5621, 6082, 6007, 5501, 2134, 5293, 2316, 6047, 2347, 5897, 126, 5466, 5789, 6077, - 5001, 5615, 3241, 2311, 5829, 5495, 4860, 2232, 5932, 5859, 2338, 5064, 6027, 5282, 2288, - 5508, 2252, 6051, 5730, 5694, 4845, 2135, 5297, 5869, 3088, 272, 5990, 3004, 5668, 5438, - 3153, 5792, 2598, 3240, 3145, 5576, 6002, 2337, 5283, 2197, 6104, 5892, 5570, 4421, 3198, - 5516, 5784, 5248, 5610, 4204, 3061, 3263, 5982, 5640, 3080, 3152, 2278, 3012, 5618, 293, - 6006, 5498, 6046, 5720, 4625, 5463, 300, 5678, 2926, 4423, 6076, 5864, 5486, 5900, 2310, - 6041, 6109, 5220, 4965, 4854, 5931, 2917, 4642, 3262, 2223, 5823, 5480, 2718, 5727, 5917, - 5049, 5565, 5267, 5077, 3234, 2246, 5435, 5650, 6070, 5833, 2994, 4640, 2304, 4830, 5402, - 5872, 5573, 6081, 3011, 5072, 3239, 3984, 2315, 5852, 6001, 125, 3171, 2336, 3765, 2005, - 4415, 5673, 3180, 5996, 283, 4920, 5268, 3087, 5886, 2907, 2213, 3079, 2827, 5778, 5973, - 3126, 5604, 2296, 3151, 5475, 5073, 5291, 5717, 2818, 5912, 2925, 5788, 117, 5483, 3197, - 5645, 5357, 249, 6040, 5705, 5828, 4858, 3238, 3086, 5184, 5858, 5633, 5062, 292, 2193, - 3261, 6103, 299, 124, 5916, 5510, 2133, 3190, 2198, 6069, 5465, 4634, 2597, 2303, 5399, - 5559, 3196, 5614, 6034, 3150, 5494, 5836, 4859, 6045, 2808, 5063, 5281, 5816, 5459, 2131, - 6075, 226, 5896, 2309, 5028, 5995, 2260, 5783, 5246, 2070, 3144, 5139, 2239, 4610, 2826, - 5667, 5437, 3260, 4809, 2295, 3545, 6026, 3136, 2188, 6102, 2287, 5911, 5500, 3233, 5808, - 5431, 2984, 2196, 5868, 5354, 5569, 5989, 5702, 3003, 5000, 5218, 4852, 5247, 5609, 5791, - 6000, 2916, 3060, 2231, 3085, 5639, 5289, 5771, 5822, 5597, 4781, 4405, 5454, 5507, 6074, - 5047, 5891, 2308, 4844, 260, 5296, 123, 3078, 5462, 4201, 4422, 4638, 6033, 5684, 5981, - 5219, 3195, 4853, 2277, 5713, 5851, 106, 2924, 5763, 5589, 3232, 5479, 3764, 5895, 5426, - 6039, 282, 4420, 5048, 5863, 5564, 5266, 4203, 3084, 5434, 5777, 5552, 4639, 6025, 5656, - 5279, 3143, 5401, 2286, 2717, 4390, 5071, 5497, 2817, 5726, 6068, 2182, 3170, 3010, 4624, - 2708, 2302, 5395, 5867, 237, 5988, 3002, 5485, 5832, 3194, 4964, 5182, 4589, 2906, 3070, - 5069, 3981, 2222, 5544, 5603, 2923, 5994, 2256, 4745, 5474, 5890, 6038, 5076, 271, 2825, - 5448, 3009, 4195, 4632, 2294, 5681, 5885, 5980, 291, 5356, 4829, 2276, 5972, 4857, 5910, - 4561, 5183, 3983, 5632, 5061, 5815, 2192, 5716, 5754, 5350, 6067, 5698, 2698, 2004, 5026, - 4414, 2068, 2301, 5390, 5862, 5787, 4919, 5137, 3231, 5827, 122, 5420, 3116, 2212, 4633, - 5653, 5857, 3544, 5059, 5398, 5558, 3125, 4700, 2716, 5620, 5993, 2251, 3189, 5290, 2807, - 5807, 5264, 5458, 2130, 6032, 1939, 2824, 116, 5482, 4998, 5027, 5831, 2293, 5245, 2069, - 2596, 5138, 121, 2127, 3077, 5770, 3975, 3142, 2587, 2255, 5535, 2187, 5345, 5693, 4842, - 2132, 3223, 5782, 2175, 2922, 5430, 2983, 6024, 5884, 5464, 5275, 3008, 5353, 4999, 2285, - 5217, 5971, 4851, 5575, 5493, 3135, 5762, 4525, 5288, 3188, 5280, 5596, 3141, 5987, 3001, - 5453, 4418, 6031, 5786, 5046, 5701, 5826, 4843, 2896, 2167, 4849, 6066, 4609, 2915, 2300, - 4637, 5384, 5856, 2122, 5436, 4808, 2577, 5617, 5821, 5889, 2250, 5044, 105, 4185, 4622, - 5588, 2707, 5677, 5979, 2195, 5425, 3007, 2245, 2275, 6023, 4419, 3050, 2595, 4962, 3230, - 2284, 5413, 4202, 2823, 3059, 4480, 5712, 120, 5850, 2292, 5551, 4780, 5278, 4404, 5861, - 3761, 5986, 3000, 3179, 5781, 5243, 2181, 4369, 4623, 5649, 5461, 5339, 5394, 4200, 2993, - 4827, 2715, 5572, 5776, 3229, 4963, 3134, 5181, 2797, 3076, 5260, 5068, 2816, 5543, 5753, - 5478, 3763, 4170, 2002, 3140, 4412, 5672, 5978, 4917, 3187, 2274, 5265, 5215, 214, 3105, - 3965, 5447, 4341, 2914, 119, 2158, 4631, 6030, 5433, 281, 3069, 5820, 4828, 5400, 4389, - 5070, 3075, 3222, 3982, 2116, 5883, 3169, 5349, 115, 2244, 2697, 2003, 5025, 5644, 4413, - 5970, 2067, 4629, 5389, 5680, 4918, 2714, 5136, 2921, 4588, 5419, 3115, 5711, 290, 5377, - 5849, 6022, 3980, 5255, 2586, 5058, 5814, 2283, 3139, 3755, 4744, 5473, 5697, 5825, 259, - 5023, 2065, 5263, 5855, 2148, 5055, 4194, 5985, 2238, 225, 3950, 4997, 5613, 5775, 5355, - 2249, 5652, 3541, 4856, 2822, 4560, 3228, 2126, 2291, 5060, 5369, 2815, 3221, 2191, 5806, - 5534, 5882, 2594, 5344, 4995, 5969, 4841, 2174, 4149, 4607, 5179, 5332, 5666, 5977, 2230, - 5274, 3068, 4806, 4305, 3543, 5769, 5397, 2273, 4699, 5506, 202, 5780, 5239, 289, 5692, - 3074, 5457, 4839, 2129, 2194, 1938, 5854, 5568, 3039, 4417, 3186, 5244, 248, 5608, 2895, - 2166, 280, 4848, 3227, 2920, 4608, 5324, 5638, 3974, 5383, 2121, 4778, 5813, 4807, 5761, - 4402, 2713, 2576, 2186, 5696, 2109, 5211, 2061, 2593, 2973, 5043, 2913, 4621, 5134, 5429, - 2237, 4198, 2982, 4260, 5819, 5352, 3185, 3049, 3535, 5216, 4961, 4850, 5412, 5040, 5616, - 3929, 6021, 5496, 3073, 5234, 4524, 5287, 2243, 2282, 2687, 5805, 4779, 4403, 5452, 4619, - 2706, 5676, 5045, 2101, 5563, 3220, 5242, 3133, 5848, 4959, 2919, 2999, 2229, 5338, 4199, - 4636, 5768, 5968, 4826, 2221, 3745, 4387, 3178, 2796, 5259, 5691, 2821, 5206, 4835, 104, - 4184, 3168, 2281, 3762, 2912, 2001, 5774, 5424, 4411, 5648, 2992, 4916, 5818, 4824, 5214, - 1873, 3104, 4586, 5571, 2814, 2905, 5976, 2998, 5035, 2157, 3978, 4479, 2272, 5315, 5760, - 5602, 5277, 4742, 2242, 5752, 3760, 4388, 1999, 4409, 5671, 2115, 5175, 4914, 4192, 2180, - 4368, 3067, 5847, 5393, 2592, 2211, 4628, 3124, 3730, 3184, 4121, 4558, 5180, 4587, 5631, - 3177, 2820, 5376, 5067, 2190, 3979, 5254, 2712, 2271, 4615, 4169, 2705, 5675, 4743, 5481, - 5773, 5228, 5022, 5643, 2064, 2092, 3964, 5446, 2147, 5054, 4340, 4193, 5812, 4630, 2813, - 2566, 2220, 5557, 4697, 3132, 2585, 5019, 94, 3901, 4559, 2806, 5368, 5130, 2236, 2128, - 2711, 5170, 1936, 5348, 288, 5647, 3525, 236, 5024, 2991, 3219, 2066, 5388, 5200, 4820, - 4994, 5612, 3183, 5135, 2911, 5492, 4606, 5178, 5418, 5331, 3114, 3972, 5804, 5967, 4805, - 2997, 3542, 5057, 2185, 5751, 4698, 3754, 4991, 1995, 1807, 2962, 5238, 5670, 2082, 2228, - 5262, 4838, 279, 5767, 1937, 3949, 4604, 2210, 3038, 4996, 5665, 5811, 3218, 3123, 4803, - 3540, 5690, 5846, 5014, 2056, 4085, 2125, 5323, 4522, 5286, 3973, 5595, 5966, 4777, 5125, - 4401, 3709, 2235, 2270, 114, 3176, 5343, 2108, 5210, 5642, 2060, 3510, 5567, 2972, 4840, - 2173, 5607, 4148, 5133, 4197, 5759, 3058, 2591, 2996, 5273, 4304, 5637, 5803, 2584, 4775, - 4399, 5039, 2812, 4986, 103, 5233, 4182, 4523, 5587, 2686, 2227, 4618, 190, 5460, 5766, - 2885, 4416, 2100, 5611, 5491, 5164, 2894, 2165, 4958, 4847, 4040, 4477, 3066, 5550, 2590, - 5382, 3028, 2120, 5276, 2704, 3131, 287, 5477, 3758, 4386, 4955, 3865, 5042, 5205, 4834, - 5562, 2179, 4183, 4366, 4620, 2219, 4600, 5664, 4259, 5432, 5758, 5193, 4799, 3048, 3534, - 4960, 4823, 3217, 213, 4585, 5411, 3928, 4384, 5066, 5034, 3977, 4478, 5810, 5542, 5314, - 4167, 3130, 2710, 4741, 2990, 270, 5008, 3759, 2050, 1998, 5566, 4408, 5241, 5119, 5174, - 5606, 4913, 3962, 2234, 4338, 4191, 3057, 4367, 4583, 5337, 2904, 5636, 3489, 5750, 2786, - 4825, 3744, 4771, 1990, 4395, 5601, 2703, 5669, 2910, 4557, 4739, 2795, 5472, 4910, 3820, - 5258, 5802, 4950, 3681, 2209, 4614, 2696, 4168, 2000, 3175, 4189, 4410, 247, 4980, 2218, - 5227, 4915, 3216, 5213, 2091, 1872, 3103, 2226, 3113, 3963, 4339, 5765, 4555, 2156, 2565, - 5630, 5056, 2589, 4696, 113, 5476, 3752, 5018, 5641, 93, 2811, 2989, 4815, 2114, 5129, - 5561, 5261, 3645, 5169, 1935, 3947, 3174, 2583, 4627, 5199, 3538, 4819, 5396, 5556, 5749, - 5157, 3729, 82, 4694, 4120, 4380, 2124, 3065, 3971, 5375, 5757, 4905, 2805, 5253, 5533, - 5456, 258, 3753, 4990, 2208, 3129, 1994, 1933, 201, 2961, 3122, 5021, 2172, 2063, 2081, - 4146, 4579, 2146, 5053, 2903, 5272, 3948, 4603, 4302, 3969, 178, 4802, 5600, 3539, 5149, - 4735, 112, 5471, 3900, 5013, 3064, 2055, 2909, 4521, 5367, 4595, 5124, 2702, 5663, 5428, - 2874, 2043, 2981, 3524, 5351, 2582, 4944, 5112, 4993, 278, 2164, 4846, 4147, 4605, 4551, - 5177, 5330, 2217, 5629, 2119, 3461, 4804, 4303, 4519, 2189, 2575, 5594, 4774, 3128, 4398, - 5451, 1806, 5237, 4985, 5605, 5041, 5801, 4181, 3056, 4837, 5635, 4257, 4973, 1741, 224, - 2035, 3037, 2884, 2951, 3047, 3532, 3173, 5555, 5104, 4690, 2225, 5163, 3926, 2908, 4476, - 4084, 5322, 2804, 3425, 3027, 4776, 5748, 5455, 102, 4179, 4400, 3708, 5586, 1984, 3757, - 1929, 5662, 5423, 4794, 2107, 4899, 5209, 4954, 5240, 2059, 3509, 2810, 2971, 4365, 5132, - 2207, 4196, 4599, 2775, 4258, 4474, 3121, 3742, 5192, 4798, 5549, 3533, 2184, 277, 5038, - 5560, 5257, 2676, 3927, 4383, 5756, 5232, 3063, 2685, 4166, 5427, 235, 111, 3600, 2980, - 4363, 4617, 5007, 5634, 2049, 5392, 3172, 4766, 2099, 5212, 1870, 4375, 3102, 5118, 3961, - 4957, 4337, 2155, 4039, 4582, 4515, 3167, 2581, 5593, 2785, 3743, 4770, 5541, 1989, 4394, - 5450, 4164, 4385, 4738, 4909, 2113, 2809, 3864, 4574, 5204, 4949, 4833, 2701, 2902, 3959, - 5445, 4335, 4188, 4626, 4979, 5599, 4937, 2026, 5470, 3727, 4118, 4822, 1871, 4584, 5095, - 2216, 5033, 4554, 3976, 3062, 5252, 5313, 4175, 5585, 3380, 3751, 4740, 5422, 5347, 2695, - 1997, 5020, 4407, 2062, 4814, 5387, 4546, 5173, 4912, 2940, 2700, 2145, 5628, 5052, 4190, - 3946, 2988, 5417, 269, 4470, 4788, 5548, 3488, 4929, 3537, 3166, 5156, 3728, 3898, 81, - 4693, 4119, 3749, 4556, 4379, 2215, 3819, 4904, 5747, 3680, 1977, 2178, 4359, 4613, 2901, - 3522, 5391, 5554, 1932, 3944, 4892, 2016, 4992, 5226, 5598, 4145, 4730, 2090, 2555, 3055, - 5176, 2206, 4578, 2803, 2987, 3120, 2123, 4301, 2564, 4760, 3968, 5540, 1675, 1924, 4695, - 4160, 5148, 5017, 4734, 1804, 5532, 5236, 92, 3899, 5342, 5128, 4836, 5746, 4594, 3644, - 110, 3955, 5444, 1969, 5168, 4143, 1934, 4331, 2873, 5627, 3036, 2042, 3523, 4884, 2183, - 4299, 5198, 4943, 5111, 4818, 4082, 2205, 4550, 3970, 2580, 3119, 2979, 4518, 3706, 5346, - 2694, 4989, 1993, 2106, 5208, 1805, 2960, 2058, 3507, 5386, 5553, 2970, 4685, 2080, 5131, - 2893, 109, 4510, 5416, 3112, 4256, 4972, 189, 5592, 2802, 4602, 2034, 2950, 5381, 3531, - 5449, 2118, 4801, 5103, 4689, 2574, 1918, 5037, 2665, 3925, 5012, 5231, 2054, 4083, 4520, - 2579, 276, 3165, 5123, 4178, 3707, 4616, 1983, 1928, 3940, 2098, 4254, 4793, 4898, 3508, - 268, 3529, 4956, 4568, 4037, 2900, 5410, 101, 2863, 3923, 2774, 5584, 3460, 4473, 3741, - 2986, 5421, 4724, 2978, 4773, 5531, 4397, 5341, 2675, 4984, 3862, 5203, 4832, 4180, 2171, - 4139, 4465, 2699, 5547, 4362, 1740, 1960, 5271, 5336, 2883, 4295, 5591, 4765, 4821, 3739, - 1869, 4374, 4875, 3054, 4540, 5162, 5626, 5032, 4038, 2794, 4475, 4753, 2204, 2177, 4514, - 3424, 4354, 3026, 3118, 3756, 4163, 1996, 4406, 4953, 5172, 3863, 4911, 4573, 2892, 2163, - 1867, 4364, 3101, 3958, 4598, 5539, 4334, 3486, 108, 5380, 2985, 100, 4155, 5191, 4936, - 4797, 5583, 4679, 2025, 3726, 2573, 4117, 3053, 5094, 3817, 2801, 4382, 2764, 5443, 3678, - 2112, 4326, 4174, 4612, 4165, 70, 2578, 3599, 1950, 5006, 4250, 5546, 5225, 2048, 3046, - 2544, 2089, 5117, 4545, 3960, 3724, 5409, 2939, 4115, 4336, 3919, 4581, 275, 4469, 4787, - 5374, 3487, 3117, 2784, 4928, 2176, 2693, 4769, 4348, 1988, 5016, 4393, 91, 3897, 5385, - 3748, 4737, 4908, 5127, 3818, 3164, 5415, 4948, 3642, 246, 5167, 3679, 223, 1976, 4358, - 3521, 107, 5051, 5335, 4187, 4978, 3943, 4891, 5538, 5197, 2015, 4817, 3735, 2852, 4729, - 212, 2554, 2793, 3895, 4504, 5256, 4553, 5590, 4759, 5366, 4717, 177, 1923, 3935, 5442, - 3379, 3750, 4320, 4159, 4988, 1992, 1803, 2959, 3519, 2079, 4813, 3163, 1863, 257, 3643, - 3954, 1968, 4142, 3945, 4601, 4330, 2154, 5329, 4883, 5530, 4800, 4298, 3536, 5340, 4533, - 5155, 2692, 80, 4692, 2899, 5011, 4378, 2053, 4081, 3052, 1801, 2170, 99, 4134, 4903, 5582, - 5122, 3705, 4709, 5414, 3111, 4290, 1931, 3506, 3035, 4684, 3720, 4144, 4111, 4577, 4459, - 4509, 3458, 5373, 5545, 4079, 4300, 5321, 3967, 4672, 5251, 1674, 4772, 4396, 3703, 1917, - 2753, 5147, 2664, 4733, 2800, 4983, 2891, 2105, 2162, 2057, 3504, 267, 1911, 4593, 5379, - 1738, 2144, 2117, 2872, 3939, 2882, 2041, 2572, 4253, 4942, 5110, 5529, 5161, 3528, 4567, - 4036, 3891, 3051, 5036, 4549, 2862, 3922, 3422, 3025, 5365, 5537, 3459, 2169, 4517, 4664, - 4128, 4245, 4723, 2684, 3045, 3515, 4284, 4952, 200, 3861, 5408, 2097, 3914, 2977, 1903, - 4138, 4464, 4597, 3162, 5328, 4034, 4255, 4971, 1739, 1959, 5190, 2033, 4796, 4294, 2949, - 3530, 3738, 5102, 4874, 4688, 4539, 3924, 4381, 1797, 4497, 5235, 2898, 4752, 3423, 3859, - 4353, 2890, 2161, 4831, 5334, 3597, 4177, 2691, 1982, 5005, 1927, 2047, 2654, 5378, 256, - 4792, 4897, 2571, 5116, 2792, 2976, 3110, 1866, 4580, 4075, 5320, 3485, 2773, 5031, 2783, - 4472, 3740, 4154, 4768, 1987, 4678, 5312, 4392, 3699, 4736, 4239, 4907, 3816, 4489, 2674, - 98, 5207, 1858, 234, 245, 3500, 5581, 4947, 2969, 2763, 3677, 4325, 5407, 2153, 3161, 69, - 3908, 4186, 3598, 4977, 1949, 4361, 4249, 3483, 4764, 2543, 1868, 4373, 3723, 4452, 2111, - 4114, 4552, 3918, 2897, 5230, 3814, 4513, 3377, 2683, 5528, 3675, 4347, 4655, 4611, 5333, - 4162, 4812, 3715, 97, 4106, 2168, 2799, 2841, 4572, 3641, 5372, 2088, 2791, 4030, 3957, - 5250, 1894, 4333, 2563, 4935, 3734, 5154, 2024, 3725, 2851, 79, 4691, 4116, 4377, 5015, - 4444, 5093, 90, 3894, 5536, 4902, 4503, 3855, 5202, 1852, 2143, 3100, 4173, 4716, 3934, - 3378, 3639, 4319, 2152, 1930, 3518, 3886, 2889, 2160, 4816, 4313, 1862, 4544, 4576, 2938, - 5364, 2975, 2110, 3966, 4468, 4786, 1672, 5311, 2570, 4927, 5146, 2533, 4732, 4532, 3896, - 3747, 4987, 1991, 1800, 2958, 2798, 4133, 4592, 2643, 5171, 5327, 4100, 2078, 2690, 4708, - 1975, 2871, 4357, 2040, 1884, 4289, 5371, 3520, 3942, 3044, 4890, 3479, 4941, 5109, 2014, - 1792, 5406, 3109, 3719, 4728, 2742, 4110, 2553, 4548, 4458, 3457, 5010, 3810, 2052, 4078, - 4516, 4758, 4671, 3671, 1673, 1922, 2142, 3034, 4158, 3702, 2752, 1802, 5224, 3503, 96, - 4070, 1910, 5319, 3880, 2689, 3953, 2974, 1967, 4970, 1737, 4141, 4329, 2032, 5363, 2948, - 3694, 2562, 3455, 4882, 4297, 5101, 4687, 2790, 2104, 3108, 89, 3495, 3890, 2968, 4080, - 3421, 4982, 4435, 5126, 5527, 4176, 4663, 3704, 4127, 3635, 1981, 5166, 4244, 5326, 1926, - 1735, 3514, 4791, 4896, 4283, 3505, 266, 5196, 1845, 3099, 4683, 3913, 1902, 1786, 2151, - 5229, 4277, 4508, 2772, 4033, 4471, 2682, 3419, 3024, 1916, 2663, 2096, 233, 2673, 1796, - 4496, 255, 4951, 95, 4025, 3858, 5526, 3596, 4360, 4064, 5318, 3938, 2653, 4596, 4763, - 4252, 211, 4372, 3688, 2159, 4795, 4093, 3527, 4566, 4035, 3850, 5370, 2103, 5201, 2051, - 4269, 4074, 2522, 2861, 4512, 3921, 2967, 2569, 5121, 3698, 4722, 4161, 3594, 4238, 5004, - 4488, 2046, 1857, 3860, 3499, 4571, 2141, 5030, 4137, 3956, 4232, 4463, 3907, 4332, 5310, - 188, 3043, 3451, 1958, 4934, 4293, 2023, 2681, 3482, 2888, 265, 3737, 4767, 4873, 3873, - 1986, 5092, 4391, 4538, 4451, 5362, 3107, 2095, 4906, 4751, 3813, 4172, 2568, 4352, 3376, - 4946, 3674, 4019, 3474, 4654, 1731, 2881, 4976, 3714, 4105, 4543, 2840, 2937, 5160, 3805, - 5325, 1865, 4224, 4029, 4467, 4785, 3666, 1893, 3844, 3484, 3042, 3415, 3023, 4926, 4153, - 4677, 2789, 3374, 3746, 1779, 5223, 4443, 3815, 2087, 3854, 2762, 4811, 3676, 1851, 1974, - 4324, 4356, 68, 2561, 3638, 3033, 2688, 3941, 1948, 4889, 4248, 2013, 5309, 5189, 58, 3098, - 2542, 3885, 4727, 2552, 4312, 2150, 3722, 4057, 5317, 78, 3106, 4113, 3917, 4376, 4757, - 3630, 5165, 1671, 1921, 4901, 2632, 4157, 4346, 2532, 3590, 199, 2102, 5195, 2045, 3468, - 222, 2642, 5115, 3640, 4099, 3952, 1966, 4140, 4328, 1883, 4575, 3799, 4881, 4296, 3478, - 3660, 2782, 1837, 3733, 3097, 1985, 1669, 2850, 1791, 2957, 2887, 2741, 2149, 4731, 2077, - 3893, 5222, 4502, 3809, 2680, 2086, 3670, 4715, 3933, 4591, 2567, 4318, 2870, 2560, 2094, - 2039, 3517, 4682, 4940, 2140, 5009, 1861, 4012, 88, 4069, 3879, 4507, 4547, 5120, 4215, - 3693, 3454, 3624, 3041, 2731, 3370, 1915, 2662, 4531, 3494, 5361, 3837, 1799, 5194, 4810, - 4434, 4132, 3634, 4707, 3446, 3937, 4288, 4251, 4969, 1734, 2031, 2947, 3526, 1844, 4565, - 5153, 2886, 3718, 2139, 4981, 77, 4686, 4109, 1785, 2956, 2860, 3920, 4457, 4276, 3456, - 5308, 4077, 2076, 4670, 3418, 4721, 1726, 176, 1771, 2880, 3701, 2751, 1980, 1925, 2788, - 5159, 4790, 4895, 3502, 4024, 4136, 4462, 1909, 3032, 3410, 1736, 244, 4063, 1957, 2511, - 4292, 2771, 3040, 1665, 3736, 4872, 3687, 4092, 4537, 5145, 1828, 5316, 3096, 3889, 3792, - 3849, 4750, 4268, 2521, 264, 3420, 4351, 3653, 4590, 4662, 4126, 4243, 46, 254, 5188, 2038, - 3593, 3440, 2966, 3513, 4282, 2085, 5108, 4762, 1864, 3912, 4371, 1901, 4231, 3031, 2559, - 4032, 3450, 4152, 4676, 3585, 4511, 5003, 87, 3872, 1720, 4049, 2787, 2879, 1795, 4495, - 5114, 2761, 2679, 4323, 3617, 3857, 5158, 4570, 67, 3595, 4018, 1947, 3473, 4247, 2093, - 1730, 2781, 2652, 2030, 3404, 232, 2965, 2946, 2541, 4933, 5100, 2022, 1818, 3095, 3721, - 4112, 3804, 3916, 4223, 2138, 4945, 4073, 3665, 3843, 3414, 4345, 4171, 3697, 4975, 1979, - 3373, 1778, 221, 4237, 3829, 5187, 4789, 4487, 2075, 1856, 3498, 2678, 4542, 3906, 2936, - 253, 3365, 4466, 3732, 4784, 57, 2849, 3481, 4925, 3579, 4004, 5002, 3892, 4450, 2044, - 4056, 4501, 2672, 5307, 3629, 3812, 5113, 4714, 2631, 3932, 3375, 4317, 3673, 1973, 3589, - 4653, 4355, 3516, 3467, 1762, 5152, 2780, 4888, 3713, 4761, 76, 2012, 4104, 1860, 4370, - 2839, 4726, 263, 4900, 4028, 3433, 3798, 1892, 3030, 3659, 4756, 1836, 4530, 1668, 1920, - 4156, 3784, 4974, 1798, 4442, 4131, 2621, 5306, 3853, 4569, 1850, 4706, 4287, 1713, 3637, - 3951, 1965, 2878, 1660, 4327, 2084, 5144, 4880, 2021, 3359, 3717, 2964, 3884, 4108, 4311, - 4011, 4456, 5091, 2558, 4076, 3397, 3022, 4669, 4214, 1670, 2869, 3623, 86, 3700, 2730, - 3369, 2750, 2531, 1752, 4939, 5107, 3836, 3501, 3609, 5151, 1908, 2641, 4541, 4681, 4098, - 187, 2935, 3445, 1882, 4506, 3029, 5186, 4783, 3477, 2083, 1790, 3888, 2740, 1914, 2661, - 3995, 2557, 3808, 4661, 4125, 3669, 4242, 3572, 4968, 1725, 1972, 85, 1770, 2955, 243, - 3512, 4281, 3936, 2074, 5099, 2011, 1654, 2963, 3911, 2610, 5143, 1900, 4725, 2551, 4068, - 3878, 4564, 4031, 3409, 2510, 2859, 2779, 3692, 4755, 3453, 1978, 1664, 1919, 2868, 4720, - 2037, 1827, 4894, 1794, 4494, 3493, 3791, 4938, 5106, 3856, 4433, 3652, 2677, 3633, 2770, - 1964, 4135, 4461, 2651, 45, 2954, 3439, 1733, 1956, 2073, 4291, 1843, 2671, 4871, 2500, - 1784, 4536, 4072, 4275, 4749, 3352, 3584, 3696, 3417, 4350, 4236, 4967, 1719, 2029, 4048, - 4486, 1855, 2945, 3497, 3775, 5098, 4680, 3616, 4023, 1705, 3905, 210, 4505, 4062, 3403, - 3480, 5150, 4151, 3686, 75, 4675, 4091, 1817, 4449, 1913, 252, 3848, 3389, 3021, 4893, - 4267, 3811, 2520, 2556, 2760, 3672, 4322, 4652, 66, 4932, 2769, 3592, 84, 1946, 3828, 3712, - 4246, 4103, 2838, 5090, 2540, 4563, 4027, 1696, 4230, 2670, 1891, 2877, 2858, 3915, 3449, - 1647, 3364, 5142, 4719, 3578, 3871, 4344, 4003, 4441, 2489, 3020, 3852, 1849, 2934, 3564, - 3636, 2867, 4460, 4017, 2036, 3472, 1729, 4924, 1955, 1761, 2953, 5105, 3883, 3731, 4310, - 2072, 2848, 4535, 3803, 4222, 3432, 2778, 3664, 175, 4748, 3842, 4500, 1971, 3413, 4349, - 2530, 4713, 3931, 4887, 3372, 83, 1777, 4316, 3783, 4931, 2020, 2620, 2550, 2640, 4097, - 3555, 5089, 1859, 1881, 4966, 1712, 2028, 1659, 220, 3476, 4150, 56, 5097, 4674, 1789, - 3358, 2739, 4529, 4055, 3807, 3396, 198, 3628, 3344, 2759, 3668, 1963, 4130, 2630, 4321, - 231, 65, 4705, 3588, 4879, 1945, 4286, 4782, 1751, 2952, 3466, 4923, 3608, 251, 4067, 3877, - 3716, 4107, 2768, 3797, 4455, 3691, 34, 3452, 2876, 3658, 74, 4668, 1835, 4343, 1667, 3492, - 2669, 2749, 4886, 2010, 3994, 4432, 3335, 3019, 3632, 2549, 3571, 1907, 1732, 4754, 1842, - 1653, 1912, 2660, 2847, 2609, 1783, 4010, 4274, 3887, 4499, 1639, 4213, 3416, 5141, 4712, - 3622, 3930, 73, 4660, 2729, 4124, 3368, 4315, 4241, 4878, 3511, 3835, 4280, 4562, 4022, - 209, 242, 3910, 4061, 3444, 1899, 1686, 4930, 2875, 2019, 3685, 4090, 4528, 5088, 3847, - 2499, 4266, 2519, 1793, 4493, 1630, 4129, 3018, 3351, 2777, 1724, 4704, 1954, 1769, 3591, - 4285, 2650, 4870, 3774, 4534, 219, 2659, 4229, 2866, 1704, 2027, 4454, 3408, 2944, 3448, - 2509, 4071, 4922, 5096, 4667, 1663, 3870, 3695, 2748, 1826, 3790, 4235, 3388, 4485, 1854, - 3651, 3496, 1970, 4016, 1906, 3471, 2478, 1728, 44, 2857, 3904, 4885, 3438, 2009, 4718, - 2548, 3802, 4221, 2767, 1695, 241, 4448, 3663, 3841, 2943, 3412, 1646, 64, 2776, 3583, - 4659, 4123, 1944, 3371, 4240, 1776, 2668, 1718, 72, 4651, 4047, 2539, 4279, 2488, 3711, - 4869, 4102, 3615, 3563, 3909, 1962, 2837, 1898, 4026, 4747, 4877, 3402, 55, 1890, 4342, - 1816, 4054, 197, 4492, 4440, 3627, 2629, 3851, 1848, 1620, 3587, 2667, 3465, 2649, 3827, - 2846, 4673, 3882, 3554, 4498, 4309, 3796, 2865, 2018, 2758, 3657, 3363, 1834, 4314, 1666, - 63, 2658, 5087, 3577, 71, 2529, 4002, 4234, 4484, 1853, 2538, 3343, 2639, 4096, 3903, 1880, - 1760, 4527, 3475, 2933, 4009, 1788, 4447, 2856, 2738, 3431, 4212, 4921, 33, 3806, 2017, - 3621, 22, 2942, 2728, 3367, 3667, 5086, 4650, 3782, 3834, 3710, 2619, 4101, 230, 2836, - 3334, 4453, 3443, 4066, 3876, 1711, 2864, 1953, 2008, 1889, 1658, 3690, 4711, 4868, 2747, - 2547, 3357, 2932, 4439, 3491, 4746, 3395, 1638, 1905, 2766, 4431, 1847, 1723, 1768, 3631, - 1750, 186, 3607, 3881, 1961, 1841, 4308, 3407, 2508, 1782, 4876, 1685, 4273, 2007, 4122, - 2941, 1662, 4703, 2546, 2528, 1825, 4278, 3789, 3993, 2757, 3650, 1629, 1897, 2638, 4095, - 4021, 3570, 43, 1943, 3437, 1879, 4060, 4666, 2537, 1652, 2608, 3684, 1787, 4491, 229, - 4089, 2737, 3846, 2765, 4265, 2518, 3582, 1904, 2657, 240, 1717, 4046, 2666, 3614, 4065, - 3875, 2477, 4228, 3401, 3689, 3447, 4658, 2845, 1815, 4233, 4483, 208, 3869, 3490, 2931, - 2498, 4430, 4710, 3902, 3350, 1896, 2656, 4015, 3826, 3470, 1727, 3773, 1840, 4446, 1703, - 1781, 1952, 3801, 4272, 4220, 3362, 3662, 3840, 4867, 3411, 2006, 4526, 3576, 4001, 2648, - 2545, 2855, 1775, 3387, 2835, 4020, 4702, 1619, 1888, 4059, 1759, 3683, 54, 4088, 4438, - 2930, 3430, 1694, 3845, 1951, 4053, 1846, 4264, 2517, 4665, 1645, 3626, 4866, 2628, 2746, - 3781, 3586, 2756, 2618, 2487, 3464, 4307, 62, 3562, 1710, 1942, 4227, 1657, 3795, 2536, - 239, 3356, 3656, 1833, 4649, 3868, 174, 3394, 2637, 4094, 4657, 2834, 21, 1878, 4014, 3469, - 1749, 1887, 185, 196, 3606, 2736, 61, 3553, 3800, 1941, 4008, 4219, 3661, 3839, 207, 2535, - 4211, 3620, 2727, 3366, 1774, 4490, 3992, 2854, 3833, 3874, 3342, 4306, 3569, 2647, 3442, - 1651, 53, 2607, 2527, 4052, 4429, 32, 3625, 228, 2844, 2627, 1722, 1877, 2655, 1767, 4482, - 1839, 3463, 4701, 1780, 3333, 4271, 2735, 3794, 3406, 2507, 3655, 1832, 1661, 4445, 2497, - 1824, 2853, 3788, 1637, 3349, 3649, 4058, 2745, 4648, 42, 3682, 3436, 4087, 3772, 218, - 2755, 1702, 4007, 4263, 2516, 60, 1684, 1940, 4210, 3619, 3581, 2726, 2534, 4437, 3386, - 1716, 4045, 3832, 4656, 1838, 1628, 4226, 3613, 195, 3441, 4270, 3400, 3867, 1895, 1693, - 1814, 1644, 4013, 2526, 1721, 1766, 2843, 2486, 3825, 2636, 2754, 4086, 3561, 4218, 59, - 2646, 3838, 2476, 3405, 4262, 227, 2506, 3361, 173, 1773, 217, 3575, 1823, 4000, 3787, - 3648, 4225, 41, 4481, 52, 3435, 1758, 4051, 3866, 3552, 2645, 2626, 3429, 3580, 2842, 3462, - 1715, 4044, 3780, 4428, 3341, 2617, 3612, 4647, 3793, 1618, 4217, 1709, 3654, 2744, 1831, - 3399, 1656, 206, 3355, 1813, 1772, 1886, 31, 3393, 4436, 3824, 1748, 51, 4006, 3332, 3605, - 4646, 4050, 4209, 3618, 2725, 3360, 2625, 2833, 3574, 3999, 3831, 1885, 2515, 1636, 3991, - 2525, 20, 3568, 2743, 1757, 2635, 1830, 1650, 1876, 2606, 1683, 3428, 184, 1765, 2734, - 3779, 1627, 2616, 2524, 4005, 2505, 1708, 1655, 4208, 2634, 1822, 2724, 3354, 3786, 1875, - 3647, 3830, 2496, 3392, 40, 3348, 3434, 194, 1747, 4427, 3604, 3771, 2475, 1701, 2644, 50, - 1714, 4043, 1764, 2832, 3990, 3611, 3385, 216, 3567, 3398, 2504, 4426, 1812, 1649, 2605, - 1821, 3785, 1692, 3646, 1829, 1643, 3823, 39, 4261, 2514, 2485, 1617, 3560, 2523, 3573, - 3998, 2831, 183, 4042, 2495, 1874, 3610, 2723, 3347, 1756, 2733, 2513, 3770, 1811, 3427, - 1700, 3551, 3778, 4216, 2615, 3822, 3384, 19, 1707, 3340, 1763, 172, 3353, 2633, 3997, - 3391, 1691, 215, 1642, 30, 1820, 1746, 2732, 3603, 1755, 2484, 2624, 3559, 3331, 38, 3426, - 3989, 3777, 2614, 49, 3566, 1635, 1706, 4041, 1648, 2604, 2623, 2512, 3550, 3390, 1682, - 1810, 1745, 4207, 3602, 205, 3339, 1626, 3821, 2494, 3988, 3346, 29, 3565, 3996, 3769, - 4206, 171, 1699, 2603, 193, 3330, 2474, 1754, 3383, 2503, 1634, 48, 3776, 2613, 1690, 37, - 182, 2493, 1641, 1681, 3345, 2483, 2502, 3558, 3768, 1625, 1698, 1819, 1616, 1744, 3601, - 3382, 47, 3987, 3549, 2622, 1689, 2722, 2473, 1640, 2602, 3338, 2482, 3557, 1809, 18, 28, - 1753, 2492, 3329, 2501, 3548, 2721, 1615, 204, 3767, 1697, 1633, 36, 3337, 3381, 1680, - 1743, 27, 2612, 1688, 1624, 170, 3328, 17, 1808, 2481, 3556, 35, 1632, 2601, 2472, 1679, - 3986, 3547, 1623, 192, 203, 3336, 3766, 181, 26, 1614, 2471, 2491, 3327, 1742, 1687, 1631, - 2480, 2611, 1678, 16, 1613, 180, 1622, 191, 3546, 2490, 2470, 15, 2600, 25, 3326, 169, 24, - 1612, 2479, 1677, 1621, 1676, 14, 168, 2469, 2468, 1611, 23, 1610, 13, 179, 12, 167, 11, - ]; - - pub fn new() -> Self { - Values { - table: &Self::TABLE, - } - } -} diff --git a/core/src/hand_evaluator.rs b/core/src/hand_evaluator.rs deleted file mode 100644 index 1296369..0000000 --- a/core/src/hand_evaluator.rs +++ /dev/null @@ -1,520 +0,0 @@ -//! # Poker Hand Evaluator - Java-Compatible Testbed Implementation -//! -//! This module provides a high-performance poker hand evaluation system designed for -//! poker bot testing and analysis. It implements the Meerkat API algorithm with full -//! Java compatibility to ensure consistency with existing poker tools and frameworks. -//! -//! ## Poker Testbed Context -//! -//! This evaluator serves as the core component of a comprehensive poker testing framework -//! that enables: -//! - **Bot Testing**: Standardized hand evaluation for comparing poker bot performance -//! - **Game Analysis**: Consistent hand strength calculation across different poker variants -//! - **Tool Integration**: Java-compatible API for seamless integration with existing poker tools -//! - **Performance Benchmarking**: Fast, deterministic hand evaluation for large-scale testing -//! -//! ## Java Compatibility Design -//! -//! The implementation maintains strict compatibility with the Java Meerkat API: -//! - **Card Encoding**: Uses Java-style 8-bit encoding (rrrr-sss format) for all cards -//! - **Ranking System**: Matches Java ranking values (1 = best hand, 7462 = worst) -//! - **API Interface**: Provides equivalent method signatures and behavior -//! - **Table Format**: Compatible binary table format for cross-platform usage -//! -//! ## Algorithm Overview -//! -//! The evaluation process follows the proven Meerkat approach: -//! 1. Cards are encoded using Java-compatible 8-bit format (rank: 4 bits, suit: 3 bits) -//! 2. 64-bit hand keys are constructed using the same encoding scheme as Java -//! 3. Perfect hash function maps card combinations to table indices -//! 4. Precomputed rank values are looked up in the table -//! 5. For 6-7 card hands, the best 5-card combination is found using Java-compatible logic -//! -//! ## Performance Characteristics -//! -//! - **Table Generation**: ~1-2 seconds on modern hardware (one-time cost) -//! - **Hand Evaluation**: ~10-20 nanoseconds per hand (O(1) lookup) -//! - **Memory Usage**: ~128MB for complete ranking tables -//! - **Java Compatibility**: 100% compatible with Java Meerkat API results -//! -//! ## Bot Developer API -//! -//! This evaluator provides several methods optimized for poker bot development: -//! - `rank_hand()`: Evaluate complete hands (5-7 cards) -//! - `rank_hand5()`: Fast 5-card hand evaluation -//! - `rank_hand6()` / `rank_hand7()`: Best 5-card combination finding -//! - `rank_hand_increment()`: Incremental hand building for complex scenarios -//! - `rank_hand_with_hole_cards()`: Hole card + board evaluation for Texas Hold'em -//! -//! ## References -//! -//! Based on the Java Meerkat API by Ray Wotton and the C implementation -//! by Paul Senzee. Original algorithm by Kevin Suffecool. Adapted for poker -//! testbed usage with enhanced Java compatibility and bot-focused APIs. - -use crate::api::card::Card; -use crate::api::hand::Hand; -use crate::evaluator_generator::state_table_generator::StateTableGenerator; -use std::fs::File; -use std::io::{self, BufReader, Read}; - -/// High-performance poker hand evaluator with full Java API compatibility. -/// -/// This evaluator implements the Meerkat algorithm using Java-compatible card encoding -/// and ranking systems, making it ideal for poker bot testing frameworks that need -/// to integrate with existing Java-based poker tools. -/// -/// ## Java Compatibility Features -/// - **Card Encoding**: Uses Java Meerkat's 8-bit encoding (rrrr-sss format) -/// - **Ranking Values**: Returns identical values to Java implementation (1-7462 range) -/// - **API Methods**: Provides equivalent functionality to Java Meerkat API -/// - **Table Format**: Compatible with Java-generated ranking tables -/// -/// ## Poker Testbed Integration -/// Designed specifically for automated poker bot testing and analysis: -/// - Deterministic results for reproducible bot testing -/// - Fast evaluation for large-scale hand analysis -/// - Memory-efficient table loading for testing environments -/// - Incremental evaluation methods for complex game scenarios -/// -/// The evaluator supports: -/// - 5-card hand evaluation (direct lookup with Java-compatible encoding) -/// - 6-card hand evaluation (finds best 5-card combination using Java logic) -/// - 7-card hand evaluation (finds best 5-card combination for Hold'em analysis) -/// - Incremental hand building for multi-street analysis -/// - Hole card + board evaluation for Texas Hold'em scenarios -/// -/// # Example - Basic Bot Testing Usage -/// ```rust,no_run -/// use poker_api::hand_evaluator::LookupHandEvaluator; -/// use poker_api::api::hand::Hand; -/// -/// // Initialize evaluator (loads Java-compatible tables) -/// let evaluator = LookupHandEvaluator::new().unwrap(); -/// -/// // Evaluate a complete hand for bot testing -/// let mut hand = Hand::new(); -/// // ... add cards to hand -/// let rank = evaluator.rank_hand(&hand); -/// -/// // Use rank for bot strength assessment (lower = stronger hand) -/// assert!(rank > 0); // Valid hand returns non-zero rank -/// ``` -/// -/// # Example - Texas Hold'em Bot Analysis -/// ```rust,no_run -/// use poker_api::hand_evaluator::LookupHandEvaluator; -/// use poker_api::api::hand::Hand; -/// -/// let evaluator = LookupHandEvaluator::new().unwrap(); -/// -/// // Analyze hole cards + flop for bot decision making -/// let hole_card1 = Card::from_string("As").unwrap().index() as u32; -/// let hole_card2 = Card::from_string("Ks").unwrap().index() as u32; -/// let mut board = Hand::new(); -/// // ... add flop cards -/// -/// let hand_strength = evaluator.rank_hand_with_hole_cards(hole_card1, hole_card2, &board); -/// // Use hand_strength for bot pre-flop/post-flop analysis -/// ``` -pub struct LookupHandEvaluator { - /// Precomputed hand ranking table containing 32+ million entries. - /// Each entry represents the relative strength of a card combination. - pub hand_ranks: Box<[u32]>, -} - -impl LookupHandEvaluator { - /// Size of the hand ranking table in entries. - /// This covers all possible card combinations for lookup purposes. - const HAND_RANKS_SIZE: usize = 32_487_834; - - /// File name where the precomputed hand ranking tables are stored. - /// The tables are saved in binary format for fast loading. - const FILE_NAME: &'static str = "math/HandRanks.dat"; - - /// Creates a new hand evaluator by loading precomputed ranking tables. - /// - /// This constructor implements lazy generation behavior: - /// 1. Allocate memory for the ranking table - /// 2. Ensure valid tables exist (generate if missing or corrupted) - /// 3. Load the precomputed tables from disk into memory - /// 4. Validate table integrity during loading - /// - /// # Returns - /// - `Ok(LookupHandEvaluator)` - Successfully loaded evaluator - /// - `Err(io::Error)` - Failed to load or generate tables - /// - /// # Performance - /// - Table generation: ~1-2 seconds (one-time cost, only if needed) - /// - Memory usage: ~128MB for ranking tables - /// - Loading time: ~100-200ms from disk - pub fn new() -> io::Result { - // Allocate memory for the complete hand ranking table - let mut hand_ranks = vec![0u32; Self::HAND_RANKS_SIZE].into_boxed_slice(); - - // Ensure valid tables exist using lazy generation - StateTableGenerator::load_tables_or_generate()?; - - // Load precomputed tables from disk - let file = File::open(Self::FILE_NAME)?; - let mut reader = BufReader::new(file); - - // Read the entire table into memory (32M entries * 4 bytes each) - let mut buffer = vec![0u8; Self::HAND_RANKS_SIZE * 4]; // u32 = 4 bytes - reader.read_exact(&mut buffer)?; - - // Convert byte buffer to u32 array in native endianness - hand_ranks.iter_mut().enumerate().for_each(|(i, slot)| { - *slot = u32::from_ne_bytes([ - buffer[4 * i], - buffer[4 * i + 1], - buffer[4 * i + 2], - buffer[4 * i + 3], - ]); - }); - - println!("Evaluation tables loaded."); - - // Debug: Check first few table entries - println!("First 10 table entries:"); - for i in 0..10 { - println!(" [{}]: {}", i, hand_ranks[i]); - } - println!("Entry at 53: {}", hand_ranks[53]); - println!("Entry at 54: {}", hand_ranks[54]); - - Ok(Self { hand_ranks }) - } - - /// Evaluates a poker hand and returns its relative rank value. - /// - /// This method supports hands of 5, 6, or 7 cards using a simplified but correct - /// evaluation algorithm that matches the Java API. For hands with more than 5 cards, - /// it finds the best 5-card poker hand within the given cards. - /// - /// # Arguments - /// * `hand` - The poker hand to evaluate (must contain at least 5 cards) - /// - /// # Returns - /// * `u32` - Relative rank value (higher values = stronger hands) - /// * `0` - Invalid hand (fewer than 5 cards) - pub fn rank_hand(&self, hand: &Hand) -> u32 { - if hand.size() < 5 { - return 0; - } - - // For now, implement a working evaluation using the specialized methods - // until the state machine table is fixed - match hand.size() { - 5 => self.rank_hand5(hand), - 6 => { - // For 6-card hands, try removing each card and evaluate the best 5-card hand - let mut best_rank = 0u32; - for i in 0..6 { - let mut temp_hand = Hand::new(); - for j in 0..6 { - if i != j { - if let Some(card) = hand.get_card(j + 1) { - let _ = temp_hand.add_card(card); - } - } - } - let rank = self.rank_hand5(&temp_hand); - if rank > best_rank { - best_rank = rank; - } - } - best_rank - } - 7 => { - // For 7-card hands, we need to find the best 5-card combination - // This is a simplified approach - in practice, you'd use a more efficient algorithm - let mut best_rank = 0u32; - - // Check a few key combinations for now - // In a full implementation, you'd check all C(7,5) = 21 combinations - for exclude1 in 0..6 { - for exclude2 in (exclude1 + 1)..7 { - let mut temp_hand = Hand::new(); - for j in 0..7 { - if j != exclude1 && j != exclude2 { - if let Some(card) = hand.get_card(j + 1) { - let _ = temp_hand.add_card(card); - } - } - } - if temp_hand.size() == 5 { - let rank = self.rank_hand5(&temp_hand); - if rank > best_rank { - best_rank = rank; - } - } - } - } - best_rank - } - _ => 0, - } - } - - /// Evaluates a 5-card poker hand specifically. - /// - /// This is a specialized method for 5-card hands that uses Java-style - /// 64-bit key encoding for optimal performance. - /// - /// # Arguments - /// * `hand` - The 5-card poker hand to evaluate - /// - /// # Returns - /// * `u32` - Relative rank value of the hand - pub fn rank_hand5(&self, hand: &Hand) -> u32 { - // Create 64-bit key using Java-style 8-bit card encoding (rrrr-sss) - let mut key: u64 = 0; - - for (i, card) in hand.cards.iter().take(5).enumerate() { - if let Some(card) = card { - // Convert card to Java-style 8-bit encoding: rrrr-sss - let rank = card.rank() as u64; // 1-13 (Deuce=1, Ace=13) - let suit = card.suit() as u64; // 1-4 (Clubs=1, Spades=4) - let encoded_card = (rank << 3) | suit; - key |= (encoded_card as u64) << (i * 8); - } - } - - // Convert to table index and lookup rank - let table_index = (key % self.hand_ranks.len() as u64) as usize; - self.hand_ranks[table_index] - } - - /// Evaluates a 6-card poker hand by finding the best 5-card combination. - /// - /// This method considers all possible 5-card combinations from the 6 cards - /// and returns the rank of the best possible hand. - /// - /// # Arguments - /// * `cards` - Array of 6 card indices to evaluate - /// - /// # Returns - /// * `u32` - Relative rank value of the best 5-card hand - pub fn rank_hand6(&self, cards: &[u32]) -> u32 { - // For 6-card hands, we need to find the best 5-card combination - let mut best_rank = 0u32; - - // Check all C(6,5) = 6 combinations - for i in 0..6 { - let mut key: u64 = 0; - let mut card_count = 0; - - for (j, &card_index) in cards.iter().enumerate() { - if i != j { - // Convert card index to Java-style 8-bit encoding - let card = Card::from_index(card_index as u8).unwrap(); - let rank = card.rank() as u64; // 1-13 - let suit = card.suit() as u64; // 1-4 - let encoded_card = (rank << 3) | suit; - key |= (encoded_card as u64) << (card_count * 8); - card_count += 1; - } - } - - let table_index = (key % self.hand_ranks.len() as u64) as usize; - let rank = self.hand_ranks[table_index]; - if rank > best_rank { - best_rank = rank; - } - } - - best_rank - } - - /// Evaluates a 7-card poker hand by finding the best 5-card combination. - /// - /// This method considers all possible 5-card combinations from the 7 cards - /// (C(7,5) = 21 combinations) and returns the rank of the best possible hand. - /// - /// # Arguments - /// * `cards` - Array of 7 card indices to evaluate - /// - /// # Returns - /// * `u32` - Relative rank value of the best 5-card hand - pub fn rank_hand7(&self, cards: &[u32]) -> u32 { - // For 7-card hands, we need to find the best 5-card combination - // This is a simplified approach - check key combinations for now - let mut best_rank = 0u32; - - // Check a few key combinations for now - // In a full implementation, you'd check all C(7,5) = 21 combinations - for exclude1 in 0..6 { - for exclude2 in (exclude1 + 1)..7 { - let mut key: u64 = 0; - let mut card_count = 0; - - for (j, &card_index) in cards.iter().enumerate() { - if j != exclude1 && j != exclude2 { - // Convert card index to Java-style 8-bit encoding - let card = Card::from_index(card_index as u8).unwrap(); - let rank = card.rank() as u64; // 1-13 - let suit = card.suit() as u64; // 1-4 - let encoded_card = (rank << 3) | suit; - key |= (encoded_card as u64) << (card_count * 8); - card_count += 1; - } - } - - let table_index = (key % self.hand_ranks.len() as u64) as usize; - let rank = self.hand_ranks[table_index]; - if rank > best_rank { - best_rank = rank; - } - } - } - - best_rank - } - - /// Incrementally adds multiple cards to an existing hand state. - /// - /// # Arguments - /// * `key` - Current hand key (use 0 for empty hand) - /// * `cards` - Array of card indices to add - /// - /// # Returns - /// * `u32` - New hand state after adding cards - pub fn rank_hand_increment(&self, mut key: u64, cards: &[u32]) -> u32 { - for &card_index in cards { - // Convert card index to Java-style 8-bit encoding and add to key - let card = Card::from_index(card_index as u8).unwrap(); - let rank = card.rank() as u64; // 1-13 - let suit = card.suit() as u64; // 1-4 - let encoded_card = (rank << 3) | suit; - - // Find the next available slot in the key (count existing cards) - let mut card_count = 0; - let mut temp_key = key; - while (temp_key & 0xFF) != 0 { - temp_key >>= 8; - card_count += 1; - } - - // Add the new card to the key - key |= (encoded_card as u64) << (card_count * 8); - } - - // Convert to table index and lookup rank - let table_index = (key % self.hand_ranks.len() as u64) as usize; - self.hand_ranks[table_index] - } - - /// Incrementally adds a single card (by index) to an existing hand state. - /// - /// # Arguments - /// * `key` - Current hand key - /// * `card` - Card index (0-51) to add - /// - /// # Returns - /// * `u32` - New hand state after adding the card - pub fn rank_hand_increment_single(&self, key: u64, card: u32) -> u32 { - // Convert card index to Java-style 8-bit encoding and add to key - let card_obj = Card::from_index(card as u8).unwrap(); - let rank = card_obj.rank() as u64; // 1-13 - let suit = card_obj.suit() as u64; // 1-4 - let encoded_card = (rank << 3) | suit; - - // Find the next available slot in the key (count existing cards) - let mut card_count = 0; - let mut temp_key = key; - while (temp_key & 0xFF) != 0 { - temp_key >>= 8; - card_count += 1; - } - - // Add the new card to the key - let new_key = key | ((encoded_card as u64) << (card_count * 8)); - - // Convert to table index and lookup rank - let table_index = (new_key % self.hand_ranks.len() as u64) as usize; - self.hand_ranks[table_index] - } - - /// Evaluates a hand consisting of two specific cards plus an existing hand. - /// Temporarily adds the cards to the hand, evaluates, then removes them. - /// - /// # Arguments - /// * `card1` - First hole card index - /// * `card2` - Second hole card index - /// * `board` - Community/board cards hand - /// - /// # Returns - /// * `u32` - Rank of the complete hand - pub fn rank_hand_with_hole_cards(&self, card1: u32, card2: u32, board: &Hand) -> u32 { - // Create a temporary hand with board cards - let mut temp_hand = Hand::new(); - for pos in 1..=board.size() { - if let Some(card) = board.get_card(pos) { - let _ = temp_hand.add_card_from_index(card.index()); - } - } - - // Add hole cards - let _ = temp_hand.add_card_from_index(card1 as u8); - let _ = temp_hand.add_card_from_index(card2 as u8); - - // Evaluate the complete hand - let rank = self.rank_hand(&temp_hand); - - rank - } -} - -#[cfg(test)] -mod tests { - use crate::api::card::Card; - use crate::api::hand::Hand; - - #[test] - fn test_java_style_card_encoding() { - // Test that cards are encoded in Java-style rrrr-sss format - let ace_spades = Card::from_string("As").unwrap(); - assert_eq!(ace_spades.rank(), Card::ACE); // 13 - assert_eq!(ace_spades.suit(), Card::SPADES); // 4 - - let deuce_clubs = Card::from_string("2c").unwrap(); - assert_eq!(deuce_clubs.rank(), Card::DEUCE); // 1 - assert_eq!(deuce_clubs.suit(), Card::CLUBS); // 1 - - // Test index calculation with new encoding - assert_eq!(ace_spades.index(), 51); // Last card in deck - assert_eq!(deuce_clubs.index(), 0); // First card in deck - } - - #[test] - fn test_java_style_key_generation() { - // Test that 64-bit keys are generated correctly - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - // The hand should be valid for evaluation - assert_eq!(hand.size(), 5); - } - - #[test] - fn test_ranking_direction_conversion() { - // Test that ranking direction matches Java (1 = best, 7462 = worst) - // This is a basic smoke test - in a full implementation we'd verify - // specific hand rankings match Java exactly - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - // Royal flush should be a very high rank (close to 1 in Java ranking) - // Note: This test would need actual table data to verify exact values - assert_eq!(hand.size(), 5); - } -} diff --git a/core/src/lib.rs b/core/src/lib.rs deleted file mode 100644 index c76625a..0000000 --- a/core/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! # Poker API Library -//! -//! A high-performance poker hand evaluation library based on the Meerkat API algorithm. -//! This library provides lightning-fast poker hand strength evaluation using precomputed -//! lookup tables and perfect hashing. -//! -//! ## Quick Start -//! -//! ```rust,no_run -//! use poker_api::hand_evaluator::LookupHandEvaluator; -//! use poker_api::api::hand::Hand; -//! -//! # async fn example() -> Result<(), Box> { -//! let evaluator = LookupHandEvaluator::new()?; -//! let mut hand = Hand::new(); -//! // ... add cards to hand -//! let rank = evaluator.rank_hand(&hand); -//! # Ok(()) -//! # } -//! ``` -//! -//! ## Features -//! -//! - **Blazing Fast**: O(1) hand evaluation using perfect hash lookup tables -//! - **Memory Efficient**: ~128MB for complete evaluation tables -//! - **Standards Compliant**: Based on proven poker evaluation algorithms -//! - **Multi-Hand Support**: Evaluate 5, 6, or 7-card poker hands -//! - **Texas Hold'em Ready**: Optimized for Texas Hold'em hand evaluation - -mod simple_player; -mod texas_holdem; -mod toml_preferences; - -/// Core poker API types and traits -pub mod api; - -/// High-performance poker hand evaluator -pub mod hand_evaluator; - -/// Table generation algorithms for poker hand evaluation -pub mod evaluator_generator; - -#[cfg(test)] -mod tests {} diff --git a/core/src/simple_player.rs b/core/src/simple_player.rs deleted file mode 100644 index f0f702f..0000000 --- a/core/src/simple_player.rs +++ /dev/null @@ -1,197 +0,0 @@ -//! # 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::card::Card; -use crate::api::game_info::GameInfo; -use crate::api::game_observer::GameObserver; -use crate::api::player::Player; -use crate::api::preferences::Preferences; - -pub struct SimplePlayer { - name: String, - stack: f64, - hole_cards: Option<(Card, Card)>, - seat: usize, -} - -impl SimplePlayer { - pub fn new(name: String, stack: f64) -> Self { - Self { - name, - stack, - hole_cards: None, - seat: 0, - } - } -} - -impl GameObserver for SimplePlayer { - fn action_event(&mut self, pos: usize, act: Action) { - println!("Player {} made action: {:?}", pos, act); - } - - fn deal_hole_cards_event(&mut self) { - println!("Hole cards are being dealt."); - } - - fn game_over_event(&mut self) { - println!("The hand is over."); - } - - fn game_start_event(&mut self, gi: Box) { - println!( - "The hand (ID: {:?}) is starting. Game Info: ", - gi.get_game_id() - ); - } - - fn game_state_changed(&mut self) { - println!("Game state has been updated."); - } - - fn showdown_event(&mut self, pos: usize, c1: Card, c2: Card) { - println!("Player {} showed cards: {} and {}", pos, c1, c2); - } - - fn stage_event(&mut self, stage: u8) { - println!("New stage: {}", stage); - } - - fn win_event(&mut self, pos: usize, amount: f64, hand_name: String) { - println!("Player {} won {} with {}", pos, amount, hand_name); - } -} - -impl Player for SimplePlayer { - fn init(&mut self, prefs: Box) { - println!("Initializing player with preferences."); - } - - fn hole_cards(&mut self, c1: Card, c2: Card, seat: usize) { - self.hole_cards = Some((c1, c2)); - self.seat = seat; - println!( - "Player {} received hole cards: {} and {}", - self.name, c1, c2 - ); - } - - fn get_action(&mut self) -> Action { - // Simple decision-making logic: always check if possible, otherwise fold - if self.hole_cards.is_some() { - Action::check_action() - } else { - Action::fold_action(0.0) - } - } -} diff --git a/core/src/texas_holdem/mod.rs b/core/src/texas_holdem/mod.rs deleted file mode 100644 index cc7fd88..0000000 --- a/core/src/texas_holdem/mod.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! # 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 -//pub mod game_state; -//pub mod blinds; -//pub mod pot; -//pub mod players; -//pub mod table; -//pub mod board; -//pub mod rules; - -use crate::api::game_info::GameInfo; - -pub struct TexasHoldemGameInfo { - // Fields for game state, blinds, pots, players, etc. -} -/* -impl GameInfo for TexasHoldemGameInfo { - fn get_stage(&self) -> u8 { - game_state::get_stage(self) - } - - fn is_pre_flop(&self) -> bool { - game_state::is_pre_flop(self) - } - - fn get_ante(&self) -> f64 { - blinds::get_ante(self) - } - - fn get_total_pot_size(&self) -> f64 { - pot::get_total_pot_size(self) - } - - // Implement other methods... -} -*/ diff --git a/core/src/toml_preferences.rs b/core/src/toml_preferences.rs deleted file mode 100644 index eb3204d..0000000 --- a/core/src/toml_preferences.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! # 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 { -//! 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 serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs; -use toml::{from_str, to_string}; - -#[derive(Serialize, Deserialize, Default)] -pub struct TomlPreferences { - preferences: HashMap, -} - -impl Preferences for TomlPreferences { - fn get(&self, name: &str, default: &str) -> String { - self.preferences - .get(name) - .cloned() - .unwrap_or_else(|| default.to_string()) - } - - fn get_boolean(&self, name: &str, default: bool) -> bool { - self.preferences - .get(name) - .and_then(|v| v.parse().ok()) - .unwrap_or(default) - } - - fn get_int(&self, name: &str, default: i32) -> i32 { - self.preferences - .get(name) - .and_then(|v| v.parse().ok()) - .unwrap_or(default) - } - - fn get_double(&self, name: &str, default: f64) -> f64 { - self.preferences - .get(name) - .and_then(|v| v.parse().ok()) - .unwrap_or(default) - } - - fn set(&mut self, name: &str, value: &str) { - self.preferences.insert(name.to_string(), value.to_string()); - } - - fn set_boolean(&mut self, name: &str, value: bool) { - self.preferences.insert(name.to_string(), value.to_string()); - } - - fn set_int(&mut self, name: &str, value: i32) { - self.preferences.insert(name.to_string(), value.to_string()); - } - - fn set_double(&mut self, name: &str, value: f64) { - self.preferences.insert(name.to_string(), value.to_string()); - } - - fn has_preference(&self, name: &str) -> bool { - self.preferences.contains_key(name) - } - - fn remove_preference(&mut self, name: &str) { - self.preferences.remove(name); - } - - fn save(&self, file_path: &str) -> Result<(), String> { - let toml_str = to_string(self).map_err(|e| e.to_string())?; - fs::write(file_path, toml_str).map_err(|e| e.to_string()) - } - - fn load(&mut self, file_path: &str) -> Result<(), String> { - let toml_str = fs::read_to_string(file_path).map_err(|e| e.to_string())?; - *self = from_str(&toml_str).map_err(|e| e.to_string())?; - Ok(()) - } -} diff --git a/core/tests/api_integration_test.rs b/core/tests/api_integration_test.rs deleted file mode 100644 index a9cd5c3..0000000 --- a/core/tests/api_integration_test.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! # API Integration Tests -//! -//! Tests for the core poker API components including cards, hands, and actions. -//! These tests verify that the basic building blocks of the poker system -//! work correctly and integrate properly with each other. -//! -//! ## Test Coverage -//! -//! - **Card Operations**: Creation, parsing, string conversion -//! - **Hand Management**: Adding cards, size validation, display -//! - **Action Types**: Poker action creation and validation -//! - **Deck Operations**: Card dealing and deck state management -//! -//! ## Test Data -//! -//! Uses deterministic test cases to ensure consistent behavior across runs. -//! All card strings follow the standard format: "{Rank}{Suit}" -//! - Ranks: A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, 2 -//! - Suits: s (spades), h (hearts), d (diamonds), c (clubs) - -use poker_api::api::action::{Action, ActionType}; -use poker_api::api::card::Card; -use poker_api::api::deck::Deck; -use poker_api::api::hand::Hand; - -#[test] -fn test_action_integration() { - // Tests poker action creation and validation. - // - // Verifies that all poker action types (fold, check, call, bet, raise) - // are created correctly and report accurate properties. - // - // Test Cases: - // - Fold action with call amount - // - Check action (zero cost) - // - Call action matching bet - // - Bet action (positive amount) - // - Raise action (call + raise amounts) - // - ActionType enum conversions - // - // Assertions: - // - Action type identification methods - // - Amount and call value accuracy - // - Enum conversion round-trips - // Test fold action - let fold_action = Action::fold_action(10.0); - assert!(fold_action.is_fold()); - assert_eq!(fold_action.to_call(), 10.0); - assert_eq!(fold_action.amount(), 0.0); - - // Test check action - let check_action = Action::check_action(); - assert!(check_action.is_check()); - assert_eq!(check_action.to_call(), 0.0); - assert_eq!(check_action.amount(), 0.0); - - // Test call action - let call_action = Action::call_action(20.0); - assert!(call_action.is_call()); - assert_eq!(call_action.to_call(), 20.0); - assert_eq!(call_action.amount(), 20.0); - - // Test bet action - let bet_action = Action::bet_action(50.0); - assert!(bet_action.is_bet()); - assert_eq!(bet_action.to_call(), 0.0); - assert_eq!(bet_action.amount(), 50.0); - - // Test raise action - let raise_action = Action::raise_action(30.0, 100.0); - assert!(raise_action.is_raise()); - assert_eq!(raise_action.to_call(), 30.0); - assert_eq!(raise_action.amount(), 100.0); - - // Test ActionType conversions - let action_type = ActionType::Call; - assert_eq!(action_type.to_u8(), 3); - assert_eq!(ActionType::from_u8(3), Some(ActionType::Call)); - assert_eq!(ActionType::from_u8(99), None); // Invalid value -} - -#[test] -fn test_card_integration() { - // Tests card creation, parsing, and string conversion. - // - // Verifies that cards can be created from standard string notation - // and converted back to strings correctly. - // - // Test Cases: - // - Ace of spades parsing and string conversion - // - // Assertions: - // - Card creation from string succeeds - // - String representation matches input - // - Round-trip conversion preserves card identity - let card = Card::from_string("As").unwrap(); - assert_eq!(card.to_string(), "As"); -} - -#[test] -fn test_deck_integration() { - // Tests deck operations and state management. - // - // Verifies that deck dealing works correctly and maintains - // accurate state information. - // - // Test Cases: - // - Deck creation with 52 cards - // - Card dealing reduces count - // - Dealt card no longer in deck - // - // Assertions: - // - Initial deck has 52 cards - // - Dealing reduces count by 1 - // - Dealt card is not in remaining deck - let mut deck = Deck::new(); - let card = deck.deal().unwrap(); - assert_eq!(deck.cards_left(), 51); - assert!(!deck.in_deck(card)); -} - -#[test] -fn test_hand_integration() { - // Tests hand creation and card management. - // - // Verifies that hands can be created, cards added, and - // hand state accurately tracked. - // - // Test Cases: - // - Empty hand creation - // - Adding two cards (Ace and King of spades) - // - Size tracking - // - String representation - // - // Assertions: - // - Hand starts empty - // - Cards added successfully - // - Size reflects added cards - // - String representation shows cards in order - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - assert_eq!(hand.size(), 2); - assert_eq!(hand.to_string(), "As Ks"); -} diff --git a/core/tests/hand_eval_integration_test.rs b/core/tests/hand_eval_integration_test.rs deleted file mode 100644 index c10b280..0000000 --- a/core/tests/hand_eval_integration_test.rs +++ /dev/null @@ -1,202 +0,0 @@ -//! # Hand Evaluation Integration Tests -//! -//! Comprehensive integration tests for the poker hand evaluation system. -//! These tests validate the LookupHandEvaluator against a reference implementation -//! to ensure correctness across millions of random test cases. -//! -//! ## Test Strategy -//! -//! This test suite uses a **reference implementation comparison** approach: -//! 1. Generate random poker hands using a deterministic seed -//! 2. Evaluate hands using both our implementation and reference implementation -//! 3. Compare results to detect any discrepancies -//! 4. Report detailed information for any failing cases -//! -//! ## Reference Implementation -//! -//! Uses the "Steve Brecher" evaluation algorithm as the reference standard. -//! This is a well-established, correct implementation against which we validate -//! our Meerkat-based evaluator. -//! -//! ## Test Coverage -//! -//! - **Correctness**: Validates against reference implementation -//! - **Performance**: Measures evaluation speed and memory usage -//! - **Edge Cases**: Tests unusual hand combinations -//! - **Regression**: Catches changes that break existing functionality -//! -//! ## Running Tests -//! -//! ```bash -//! # Run basic correctness test (1M hands) -//! cargo test hand_eval_integration_test -//! -//! # Run with output capture for debugging -//! cargo test hand_eval_integration_test -- --nocapture -//! ``` -//! -//! ## Test Results -//! -//! Expected output for successful test: -//! ```text -//! Performing 1000000 correctness tests -//! Done. -//! test hand_eval_integration_test::run ... ok -//! ``` - -#[path = "./helpers/mod.rs"] -mod helpers; - -use helpers::hand_encoder::HandEncoder; -use helpers::hand_eval::HandEval; - -use poker_api::hand_evaluator::LookupHandEvaluator; -use rand::rng; -use rand::seq::SliceRandom; - -/// Main integration test comparing our evaluator against reference implementation. -/// -/// This test performs extensive validation by: -/// 1. Creating evaluator instance (generates tables if needed) -/// 2. Running 1 million random test cases -/// 3. Comparing results against reference implementation -/// 4. Reporting any discrepancies with detailed hand information -/// -/// # Performance -/// - **Test Duration**: ~30-60 seconds depending on hardware -/// - **Memory Usage**: ~128MB for evaluation tables -/// - **Coverage**: All possible hand types and strengths -/// -/// # Failure Cases -/// If discrepancies are found, the test will: -/// - Print the failing hand in readable format -/// - Show rank values from both implementations -/// - Indicate which implementation is incorrect -/// - Fail the test to prevent shipping incorrect code -#[test] -pub fn run() { - // Integration test that validates hand evaluation against reference implementation - // Currently disabled due to file system permissions in test environment - // TODO: Re-enable when file system access is properly configured for tests - - /* - let evaluator = LookupHandEvaluator::new().unwrap(); - let tests = 1_000_000; - - println!("Performing {} correctness tests", tests); - for _ in 0..tests { - let h1 = deal_cards(6); - let h2 = deal_cards(6); - - // For now, just test that the evaluator can be created and basic evaluation works - // The original integration test had compatibility issues with the helper methods - let test_hand = vec![0u8, 1u8, 2u8, 3u8, 4u8, 5u8]; // Simple test hand - let res_i1 = evaluator.rank_hand6(&[0u32, 1u32, 2u32, 3u32, 4u32, 5u32]); - let res_i2 = evaluator.rank_hand6(&[1u32, 2u32, 3u32, 4u32, 5u32, 6u32]); - - // Basic sanity check - different hands should potentially have different ranks - assert!(res_i1 != res_i2 || res_i1 == res_i2); // Either different or same is fine - - // Basic validation - hands should evaluate to reasonable rank values - assert!(res_i1 > 0, "Hand 1 should have valid rank"); - assert!(res_i2 > 0, "Hand 2 should have valid rank"); - } - println!("Done."); - */ -} - -/// Generates a random set of cards for testing. -/// -/// Creates a shuffled deck and deals the specified number of cards. -/// Uses a fixed random seed for reproducible test results. -/// -/// # Arguments -/// * `count` - Number of cards to deal (1-52) -/// -/// # Returns -/// * `Vec` - Card indices (0-51) representing the dealt cards -/// -/// # Panics -/// * If count is 0 or greater than 52 -fn deal_cards(count: usize) -> Vec { - let mut deck: Vec = (0..52).collect(); - let mut rng = rng(); - deck.shuffle(&mut rng); - deck.into_iter().take(count).collect() -} - -/// Converts a slice of card indices to a human-readable string. -/// -/// Formats card indices into standard poker notation (e.g., "As", "Kh", "Td"). -/// Used for displaying failing test cases in error messages. -/// -/// # Arguments -/// * `cards` - Slice of card indices (0-51, or 255 for placeholder) -/// -/// # Returns -/// * `String` - Space-separated card representations -/// -/// # Examples -/// ```text -/// print_hand(&[0, 1, 2]) // "2c 2d 2h" -/// print_hand(&[48, 49, 50, 51]) // "Ac Ad As Ah" -/// ``` -fn print_hand(cards: &[u8]) -> String { - cards - .iter() - .map(|&c| print_card(c)) - .collect::>() - .join(" ") -} - -/// Converts a single card index to human-readable format. -/// -/// Translates a card index (0-51) into standard poker notation. -/// Handles special case of 255 (placeholder/empty card). -/// -/// # Arguments -/// * `card` - Card index (0-51) or 255 for placeholder -/// -/// # Returns -/// * `String` - Card in format "{Rank}{Suit}" or "-" for placeholder -/// -/// # Card Encoding -/// - Ranks: 0-7 = 2-9, 8 = T, 9 = J, 10 = Q, 11 = K, 12 = A -/// - Suits: 0 = c, 1 = d, 2 = s, 3 = h -/// -/// # Examples -/// ```text -/// print_card(51) // "As" (Ace of spades) -/// print_card(0) // "2c" (Two of clubs) -/// print_card(255) // "-" (placeholder) -/// ``` -fn print_card(card: u8) -> String { - if card == 255 { - return "-".to_string(); - } - - let rank = card / 4; - let suit = card % 4; - - // Convert rank index to rank character - let rank_str = match rank { - 0..=7 => (rank + 2).to_string(), // 2-9 - 8 => "T".to_string(), // Ten - 9 => "J".to_string(), // Jack - 10 => "Q".to_string(), // Queen - 11 => "K".to_string(), // King - 12 => "A".to_string(), // Ace - _ => "?".to_string(), // Invalid - }; - - // Convert suit index to suit character - let suit_str = match suit { - 0 => "c", // Clubs - 1 => "d", // Diamonds - 2 => "s", // Spades - 3 => "h", // Hearts - _ => "?", // Invalid - }; - - format!("{}{}", rank_str, suit_str) -} diff --git a/core/tests/helpers/hand_encoder.rs b/core/tests/helpers/hand_encoder.rs deleted file mode 100644 index 0d6ea57..0000000 --- a/core/tests/helpers/hand_encoder.rs +++ /dev/null @@ -1,40 +0,0 @@ -pub struct HandEncoder; - -impl HandEncoder { - pub fn encode_to_meerkat(hand: &[u8]) -> Vec { - hand.iter().map(|&card| Self::print_card(card)).collect() - } - - pub fn encode_to_steve_brecher(hand: &[u8]) -> u64 { - let mut result = 0; - for &card in hand.iter() { - result |= 0x1 << ((card % 4) * 13 + (card / 4)); - } - result - } - - pub fn print_card(card: u8) -> String { - if card == 255 { - return "-".to_string(); - } - let rank = card / 4; - let suit = card % 4; - let rank_str = match rank { - 0..=7 => (rank + 2).to_string(), - 8 => "T".to_string(), - 9 => "J".to_string(), - 10 => "Q".to_string(), - 11 => "K".to_string(), - 12 => "A".to_string(), - _ => "?".to_string(), - }; - let suit_str = match suit { - 0 => "c", - 1 => "d", - 2 => "s", - 3 => "h", - _ => "?", - }; - format!("{}{}", rank_str, suit_str) - } -} diff --git a/core/tests/helpers/hand_eval.rs b/core/tests/helpers/hand_eval.rs deleted file mode 100644 index 2bcfd11..0000000 --- a/core/tests/helpers/hand_eval.rs +++ /dev/null @@ -1,85 +0,0 @@ -pub struct HandEval; - -impl HandEval { - pub fn encode(card: u8) -> u64 { - 1 << (card / 4 * 13 + card % 4) - } - - pub fn encode_hand(cards: &[u8]) -> u64 { - cards.iter().fold(0, |acc, &c| acc | Self::encode(c)) - } - - pub fn hand7_eval(hand: u64) -> u32 { - let c = (hand & 0x1FFF) as u32; - let d = ((hand >> 13) & 0x1FFF) as u32; - let h = ((hand >> 26) & 0x1FFF) as u32; - let s = ((hand >> 39) & 0x1FFF) as u32; - let ranks = c | d | h | s; - - match ranks.count_ones() { - 5 => Self::eval_5_card_hand(ranks, c, d, h, s), - 6 => Self::eval_6_card_hand(ranks, c, d, h, s), - 7 => Self::eval_7_card_hand(ranks, c, d, h, s), - _ => 0, - } - } - - fn eval_5_card_hand(ranks: u32, c: u32, d: u32, h: u32, s: u32) -> u32 { - if Self::is_flush(c, d, h, s) { - return Self::flush_or_straight_flush(ranks); - } - Self::rank_hand_category(ranks) - } - - fn eval_6_card_hand(ranks: u32, c: u32, d: u32, h: u32, s: u32) -> u32 { - let mut best_rank = u32::MAX; - for i in 0..6 { - let modified_ranks = ranks & !(1 << i); - best_rank = best_rank.min(Self::eval_5_card_hand(modified_ranks, c, d, h, s)); - } - best_rank - } - - fn eval_7_card_hand(ranks: u32, c: u32, d: u32, h: u32, s: u32) -> u32 { - let mut best_rank = u32::MAX; - for i in 0..7 { - for j in (i + 1)..7 { - let modified_ranks = ranks & !(1 << i) & !(1 << j); - best_rank = best_rank.min(Self::eval_5_card_hand(modified_ranks, c, d, h, s)); - } - } - best_rank - } - - fn is_flush(c: u32, d: u32, h: u32, s: u32) -> bool { - c.count_ones() >= 5 || d.count_ones() >= 5 || h.count_ones() >= 5 || s.count_ones() >= 5 - } - - fn flush_or_straight_flush(ranks: u32) -> u32 { - if Self::is_straight(ranks) { - return 9; // Straight flush category - } - 6 // Flush category - } - - fn is_straight(ranks: u32) -> bool { - let mut sequence = ranks; - while sequence > 0 { - if sequence & 0b11111 == 0b11111 { - return true; - } - sequence >>= 1; - } - false - } - - fn rank_hand_category(ranks: u32) -> u32 { - match ranks.count_ones() { - 5 => 1, // High card - 4 => 2, // One pair - 3 => 4, // Three of a kind - 2 => 7, // Full house - _ => 0, - } - } -} diff --git a/core/tests/helpers/mod.rs b/core/tests/helpers/mod.rs deleted file mode 100644 index d4c040e..0000000 --- a/core/tests/helpers/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod hand_encoder; -pub mod hand_eval; diff --git a/core/tests/mod.rs b/core/tests/mod.rs deleted file mode 100644 index fd62762..0000000 --- a/core/tests/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! # Poker API Test Suite -//! -//! Comprehensive test suite for the poker hand evaluation library. -//! Tests are organized into logical groups covering different aspects -//! of the poker evaluation system. -//! -//! ## Test Categories -//! -//! - **Unit Tests**: Test individual components and algorithms -//! - **Integration Tests**: Test complete workflows and interactions -//! - **Performance Tests**: Validate speed and memory characteristics -//! - **Regression Tests**: Ensure compatibility with reference implementations -//! -//! ## Running Tests -//! -//! ```bash -//! # Run all tests -//! cargo test -//! -//! # Run specific test categories -//! cargo test api_integration -//! cargo test hand_eval_integration -//! -//! # Run with performance benchmarks -//! cargo test hand_eval_integration_test -- --nocapture -//! ``` -//! -//! ## Test Data -//! -//! Tests use both deterministic test cases and randomized inputs to ensure -//! comprehensive coverage of the evaluation space. - -/// Helper modules for test utilities and fixtures -mod helpers; - -/// Integration tests for the poker API components -mod api_integration_test; - -/// Comprehensive hand evaluation integration tests -mod hand_eval_integration_test; - -/// Unit tests for individual components and algorithms -mod unit_tests; - -/// Comprehensive tests for evaluator generator components -mod evaluator_generator_tests; diff --git a/core/tests/unit_tests.rs b/core/tests/unit_tests.rs deleted file mode 100644 index 3ad010b..0000000 --- a/core/tests/unit_tests.rs +++ /dev/null @@ -1,3211 +0,0 @@ -//! # Unit Tests for Poker Hand Evaluator -//! -//! Focused unit tests for individual components of the poker evaluation system. -//! These tests validate specific algorithms and edge cases in isolation. -//! -//! ## Test Categories -//! -//! - **Hand Rankings**: Test specific hand types and their relative strengths -//! - **Edge Cases**: Test unusual or boundary condition inputs -//! - **Performance**: Validate speed characteristics of key algorithms -//! - **Correctness**: Test against known good values for specific hands -//! -//! ## Test Data -//! -//! Uses carefully constructed test cases with known expected outcomes. -//! All test hands are deterministic and well-documented. - -use poker_api::api::card::Card; -use poker_api::api::hand::Hand; -use poker_api::hand_evaluator::LookupHandEvaluator; - -/// Test suite for specific hand rankings and their relative strengths. -/// -/// These tests verify that the evaluator correctly ranks different hand types -/// and that stronger hands always receive higher rank values than weaker hands. -#[cfg(test)] -mod hand_rankings { - use super::*; - - #[test] - fn test_royal_flush_ranking() { - // Tests that royal flush is the strongest possible hand. - // - // Creates a royal flush (A-K-Q-J-T of same suit) and verifies - // it receives the highest possible rank value. - // - // Test Case: - // - Hand: A♠ K♠ Q♠ J♠ T♠ - // - Expected: Highest rank value (> 368M) - // - Verification: Stronger than all other hand types - let evaluator = LookupHandEvaluator::new().unwrap(); - - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - let rank = evaluator.rank_hand(&hand); - assert!( - rank > 368_793_253, - "Royal flush should have rank > 368M, got {}", - rank - ); - } - - #[test] - fn test_straight_flush_vs_four_of_kind() { - /// Tests that straight flush beats four of a kind. - /// - /// Compares a straight flush against four of a kind to ensure - /// the straight flush receives a higher rank value. - /// - /// # Test Cases - /// - Straight Flush: K♠ Q♠ J♠ T♠ 9♠ - /// - Four of a Kind: A♠ A♥ A♦ A♣ K♦ - /// - Expected: Straight flush > Four of a kind - let evaluator = LookupHandEvaluator::new().unwrap(); - - let mut straight_flush = Hand::new(); - straight_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("9s").unwrap()) - .unwrap(); - - let mut four_of_kind = Hand::new(); - four_of_kind - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - four_of_kind - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - four_of_kind - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - four_of_kind - .add_card(Card::from_string("Ac").unwrap()) - .unwrap(); - four_of_kind - .add_card(Card::from_string("Kd").unwrap()) - .unwrap(); - - let sf_rank = evaluator.rank_hand(&straight_flush); - let fk_rank = evaluator.rank_hand(&four_of_kind); - - assert!( - sf_rank > fk_rank, - "Straight flush should beat four of a kind: {} > {}", - sf_rank, - fk_rank - ); - } - - #[test] - fn test_full_house_vs_flush() { - /// Tests that full house beats flush. - /// - /// # Test Cases - /// - Full House: K♠ K♥ K♦ Q♠ Q♥ - /// - Flush: A♠ J♠ 8♠ 5♠ 2♠ - /// - Expected: Full house > Flush - let evaluator = LookupHandEvaluator::new().unwrap(); - - let mut full_house = Hand::new(); - full_house - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Kh").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Kd").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - - let mut flush = Hand::new(); - flush.add_card(Card::from_string("As").unwrap()).unwrap(); - flush.add_card(Card::from_string("Js").unwrap()).unwrap(); - flush.add_card(Card::from_string("8s").unwrap()).unwrap(); - flush.add_card(Card::from_string("5s").unwrap()).unwrap(); - flush.add_card(Card::from_string("2s").unwrap()).unwrap(); - - let fh_rank = evaluator.rank_hand(&full_house); - let f_rank = evaluator.rank_hand(&flush); - - assert!( - fh_rank > f_rank, - "Full house should beat flush: {} > {}", - fh_rank, - f_rank - ); - } - - #[test] - fn test_all_hand_types_basic() { - /// Tests all 9 basic hand types with proper ranking verification. - /// - /// This test ensures that all hand types are correctly identified and ranked - /// according to poker hand strength hierarchy. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // High Card: A K Q J 9 (different suits) - let mut high_card = Hand::new(); - high_card - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - high_card - .add_card(Card::from_string("Kd").unwrap()) - .unwrap(); - high_card - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - high_card - .add_card(Card::from_string("Jc").unwrap()) - .unwrap(); - high_card - .add_card(Card::from_string("9s").unwrap()) - .unwrap(); - - // Pair: A A K Q J - let mut pair = Hand::new(); - pair.add_card(Card::from_string("As").unwrap()).unwrap(); - pair.add_card(Card::from_string("Ah").unwrap()).unwrap(); - pair.add_card(Card::from_string("Kd").unwrap()).unwrap(); - pair.add_card(Card::from_string("Qh").unwrap()).unwrap(); - pair.add_card(Card::from_string("Jc").unwrap()).unwrap(); - - // Two Pair: A A K K Q - let mut two_pair = Hand::new(); - two_pair.add_card(Card::from_string("As").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("Ah").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("Ks").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("Kh").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("Qc").unwrap()).unwrap(); - - // Three of a Kind: A A A K Q - let mut three_kind = Hand::new(); - three_kind - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - - // Straight: A K Q J T (different suits) - let mut straight = Hand::new(); - straight.add_card(Card::from_string("As").unwrap()).unwrap(); - straight.add_card(Card::from_string("Kd").unwrap()).unwrap(); - straight.add_card(Card::from_string("Qh").unwrap()).unwrap(); - straight.add_card(Card::from_string("Jc").unwrap()).unwrap(); - straight.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - // Flush: A J 8 5 2 (same suit) - let mut flush = Hand::new(); - flush.add_card(Card::from_string("As").unwrap()).unwrap(); - flush.add_card(Card::from_string("Js").unwrap()).unwrap(); - flush.add_card(Card::from_string("8s").unwrap()).unwrap(); - flush.add_card(Card::from_string("5s").unwrap()).unwrap(); - flush.add_card(Card::from_string("2s").unwrap()).unwrap(); - - // Full House: A A A K K - let mut full_house = Hand::new(); - full_house - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Kh").unwrap()) - .unwrap(); - - // Four of a Kind: A A A A K - let mut four_kind = Hand::new(); - four_kind - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ac").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - - // Straight Flush: A K Q J T (same suit) - let mut straight_flush = Hand::new(); - straight_flush - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - // Get ranks for all hands - let hc_rank = evaluator.rank_hand(&high_card); - let p_rank = evaluator.rank_hand(&pair); - let tp_rank = evaluator.rank_hand(&two_pair); - let tk_rank = evaluator.rank_hand(&three_kind); - let s_rank = evaluator.rank_hand(&straight); - let f_rank = evaluator.rank_hand(&flush); - let fh_rank = evaluator.rank_hand(&full_house); - let fk_rank = evaluator.rank_hand(&four_kind); - let sf_rank = evaluator.rank_hand(&straight_flush); - - // Verify hand type hierarchy (higher rank = stronger hand) - assert!( - sf_rank > fk_rank, - "Straight flush should beat four of a kind" - ); - assert!(fk_rank > fh_rank, "Four of a kind should beat full house"); - assert!(fh_rank > f_rank, "Full house should beat flush"); - assert!(f_rank > s_rank, "Flush should beat straight"); - assert!(s_rank > tk_rank, "Straight should beat three of a kind"); - assert!(tk_rank > tp_rank, "Three of a kind should beat two pair"); - assert!(tp_rank > p_rank, "Two pair should beat pair"); - assert!(p_rank > hc_rank, "Pair should beat high card"); - } - - #[test] - fn test_hand_type_edge_cases() { - /// Tests edge cases for hand types including wheel straight and Broadway. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Wheel Straight: A 2 3 4 5 (different suits) - let mut wheel_straight = Hand::new(); - wheel_straight - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("3h").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("4c").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - - // Broadway Straight: A K Q J T (different suits) - let mut broadway_straight = Hand::new(); - broadway_straight - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - broadway_straight - .add_card(Card::from_string("Kh").unwrap()) - .unwrap(); - broadway_straight - .add_card(Card::from_string("Qc").unwrap()) - .unwrap(); - broadway_straight - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - broadway_straight - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - // Wheel Straight Flush: A 2 3 4 5 (same suit) - let mut wheel_straight_flush = Hand::new(); - wheel_straight_flush - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - wheel_straight_flush - .add_card(Card::from_string("2s").unwrap()) - .unwrap(); - wheel_straight_flush - .add_card(Card::from_string("3s").unwrap()) - .unwrap(); - wheel_straight_flush - .add_card(Card::from_string("4s").unwrap()) - .unwrap(); - wheel_straight_flush - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - - // Get ranks - let wheel_rank = evaluator.rank_hand(&wheel_straight); - let broadway_rank = evaluator.rank_hand(&broadway_straight); - let wheel_sf_rank = evaluator.rank_hand(&wheel_straight_flush); - - // Both straights should be valid (wheel is lowest straight) - assert!(wheel_rank > 0, "Wheel straight should be valid"); - assert!(broadway_rank > 0, "Broadway straight should be valid"); - - // Broadway should beat wheel straight - assert!( - broadway_rank > wheel_rank, - "Broadway should beat wheel straight" - ); - - // Wheel straight flush should be valid and strong - assert!(wheel_sf_rank > 0, "Wheel straight flush should be valid"); - } - - #[test] - fn test_hand_type_boundaries() { - /// Tests exact boundaries between hand types to ensure correct ranking. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test boundary between High Card and Pair - let mut high_card = Hand::new(); - high_card - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - high_card - .add_card(Card::from_string("Kd").unwrap()) - .unwrap(); - high_card - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - high_card - .add_card(Card::from_string("Jc").unwrap()) - .unwrap(); - high_card - .add_card(Card::from_string("9s").unwrap()) - .unwrap(); - - let mut weak_pair = Hand::new(); - weak_pair - .add_card(Card::from_string("2s").unwrap()) - .unwrap(); - weak_pair - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); - weak_pair - .add_card(Card::from_string("3h").unwrap()) - .unwrap(); - weak_pair - .add_card(Card::from_string("4c").unwrap()) - .unwrap(); - weak_pair - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - - let hc_rank = evaluator.rank_hand(&high_card); - let wp_rank = evaluator.rank_hand(&weak_pair); - - assert!(wp_rank < hc_rank, "Even weak pair should beat high card"); - - // Test boundary between Pair and Two Pair - let mut pair = Hand::new(); - pair.add_card(Card::from_string("As").unwrap()).unwrap(); - pair.add_card(Card::from_string("Ah").unwrap()).unwrap(); - pair.add_card(Card::from_string("Kd").unwrap()).unwrap(); - pair.add_card(Card::from_string("Qh").unwrap()).unwrap(); - pair.add_card(Card::from_string("Jc").unwrap()).unwrap(); - - let mut two_pair = Hand::new(); - two_pair.add_card(Card::from_string("2s").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("2d").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("3h").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("3c").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("4s").unwrap()).unwrap(); - - let p_rank = evaluator.rank_hand(&pair); - let tp_rank = evaluator.rank_hand(&two_pair); - - assert!(tp_rank < p_rank, "Two pair should beat pair"); - - // Test boundary between Three of a Kind and Straight - let mut three_kind = Hand::new(); - three_kind - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Kc").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - - let mut weak_straight = Hand::new(); - weak_straight - .add_card(Card::from_string("2s").unwrap()) - .unwrap(); - weak_straight - .add_card(Card::from_string("3d").unwrap()) - .unwrap(); - weak_straight - .add_card(Card::from_string("4h").unwrap()) - .unwrap(); - weak_straight - .add_card(Card::from_string("5c").unwrap()) - .unwrap(); - weak_straight - .add_card(Card::from_string("6s").unwrap()) - .unwrap(); - - let tk_rank = evaluator.rank_hand(&three_kind); - let ws_rank = evaluator.rank_hand(&weak_straight); - - assert!(ws_rank < tk_rank, "Straight should beat three of a kind"); - } - - #[test] - fn test_steel_wheel_vs_wheel_straight() { - /// Tests steel wheel (A 2 3 4 5 same suit) vs regular wheel straight. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Steel wheel (wheel straight flush) - let mut steel_wheel = Hand::new(); - steel_wheel - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - steel_wheel - .add_card(Card::from_string("2s").unwrap()) - .unwrap(); - steel_wheel - .add_card(Card::from_string("3s").unwrap()) - .unwrap(); - steel_wheel - .add_card(Card::from_string("4s").unwrap()) - .unwrap(); - steel_wheel - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - - // Regular wheel straight - let mut wheel_straight = Hand::new(); - wheel_straight - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("3h").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("4c").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - - let sw_rank = evaluator.rank_hand(&steel_wheel); - let ws_rank = evaluator.rank_hand(&wheel_straight); - - assert!(sw_rank < ws_rank, "Steel wheel should beat wheel straight"); - assert!(sw_rank < 1000, "Steel wheel should be very strong hand"); - } - - #[test] - fn test_royal_flush_variations() { - /// Tests different royal flush variations and their relative strengths. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Spades royal flush - let mut rf_spades = Hand::new(); - rf_spades - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - rf_spades - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - rf_spades - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - rf_spades - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - rf_spades - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - // Hearts royal flush - let mut rf_hearts = Hand::new(); - rf_hearts - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - rf_hearts - .add_card(Card::from_string("Kh").unwrap()) - .unwrap(); - rf_hearts - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - rf_hearts - .add_card(Card::from_string("Jh").unwrap()) - .unwrap(); - rf_hearts - .add_card(Card::from_string("Th").unwrap()) - .unwrap(); - - let rf_s_rank = evaluator.rank_hand(&rf_spades); - let rf_h_rank = evaluator.rank_hand(&rf_hearts); - - // All royal flushes should be equally strong (same rank) - assert_eq!( - rf_s_rank, rf_h_rank, - "All royal flushes should have same rank" - ); - assert!(rf_s_rank < 100, "Royal flush should be very strong"); - } - - #[test] - fn test_flush_vs_straight_comparison() { - /// Tests that flush beats straight and verifies ranking within types. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Strong Flush: A K Q J 9 (same suit) - let mut strong_flush = Hand::new(); - strong_flush - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - strong_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - strong_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - strong_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - strong_flush - .add_card(Card::from_string("9s").unwrap()) - .unwrap(); - - // Weak Flush: K Q J 8 7 (same suit) - let mut weak_flush = Hand::new(); - weak_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - weak_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - weak_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - weak_flush - .add_card(Card::from_string("8s").unwrap()) - .unwrap(); - weak_flush - .add_card(Card::from_string("7s").unwrap()) - .unwrap(); - - // Strong Straight: K Q J T 9 (different suits) - let mut strong_straight = Hand::new(); - strong_straight - .add_card(Card::from_string("Kd").unwrap()) - .unwrap(); - strong_straight - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - strong_straight - .add_card(Card::from_string("Jc").unwrap()) - .unwrap(); - strong_straight - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - strong_straight - .add_card(Card::from_string("9s").unwrap()) - .unwrap(); - - // Get ranks - let sf_rank = evaluator.rank_hand(&strong_flush); - let wf_rank = evaluator.rank_hand(&weak_flush); - let s_rank = evaluator.rank_hand(&strong_straight); - - // Flush should beat straight - assert!(sf_rank > s_rank, "Strong flush should beat strong straight"); - - // Stronger flush should beat weaker flush - assert!(sf_rank > wf_rank, "Strong flush should beat weak flush"); - } -} - -/// Test suite for Java compatibility verification. -/// -/// These tests ensure that the Rust implementation matches the Java Meerkat API -/// in terms of card encoding, key generation, and ranking values. -#[cfg(test)] -mod java_compatibility { - use super::*; - - #[test] - fn test_java_card_encoding_format() { - /// Tests that card encoding matches Java's 8-bit rrrr-sss format. - /// - /// Java uses: rrrr-sss where rrrr = rank (1-13), sss = suit (1-4) - /// This should match our Card implementation exactly. - // Test specific card encodings - let ace_spades = Card::from_string("As").unwrap(); - assert_eq!(ace_spades.rank(), 13); // Ace = 13 in Java - assert_eq!(ace_spades.suit(), 4); // Spades = 4 in Java - - let deuce_clubs = Card::from_string("2c").unwrap(); - assert_eq!(deuce_clubs.rank(), 1); // Deuce = 1 in Java - assert_eq!(deuce_clubs.suit(), 1); // Clubs = 1 in Java - - let king_hearts = Card::from_string("Kh").unwrap(); - assert_eq!(king_hearts.rank(), 12); // King = 12 in Java - assert_eq!(king_hearts.suit(), 3); // Hearts = 3 in Java - - // Test index calculation (should match Java's card ordering) - assert_eq!(ace_spades.index(), 51); // Last card in deck - assert_eq!(deuce_clubs.index(), 0); // First card in deck - assert_eq!(king_hearts.index(), 37); // King of hearts position - } - - #[test] - fn test_java_key_generation() { - /// Tests that 64-bit key generation matches Java implementation. - /// - /// Java creates keys by packing 5 cards as: card1|card2|card3|card4|card5 - /// Each card is 8 bits: rrrr-sss (rank 1-13, suit 1-4) - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Create a royal flush and verify key generation - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - // Test the rank_hand5 method which uses Java-style key generation - let rank = evaluator.rank_hand5(&hand); - - // Should return a valid rank (non-zero) - assert!( - rank > 0, - "Java-style key generation should produce valid rank" - ); - - // Test key generation for specific cards - let mut key: u64 = 0; - for i in 0..5 { - if let Some(card) = hand.get_card(i + 1) { - let rank_val = card.rank() as u64; - let suit_val = card.suit() as u64; - let encoded_card = (rank_val << 3) | suit_val; - key |= (encoded_card as u64) << (i * 8); - } - } - - // Key should be non-zero for valid hand - assert!(key > 0, "64-bit key should be non-zero for valid hand"); - - // Test table index calculation - let table_index = (key % evaluator.hand_ranks.len() as u64) as usize; - assert!( - table_index < evaluator.hand_ranks.len(), - "Table index should be valid" - ); - - // Verify the rank from table matches direct evaluation - assert_eq!( - rank, evaluator.hand_ranks[table_index], - "Table lookup should match direct evaluation" - ); - } - - #[test] - fn test_java_ranking_direction() { - /// Tests that ranking direction matches Java (1 = best, 7462 = worst). - /// - /// In Java: - /// - Royal flush = 1 (best possible hand) - /// - 7-5-4-3-2 high card = 7462 (worst possible hand) - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Royal flush should be very close to 1 (best hand) - let mut royal_flush = Hand::new(); - royal_flush - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - royal_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - royal_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - royal_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - royal_flush - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - let rf_rank = evaluator.rank_hand(&royal_flush); - - // Royal flush should have a very low rank number (close to 1) - // Note: Exact value depends on table implementation, but should be < 1000 - assert!( - rf_rank < 1000, - "Royal flush should have very low rank number, got {}", - rf_rank - ); - - // Worst high card hand should have high rank number (close to 7462) - let mut worst_hand = Hand::new(); - worst_hand - .add_card(Card::from_string("7c").unwrap()) - .unwrap(); - worst_hand - .add_card(Card::from_string("5d").unwrap()) - .unwrap(); - worst_hand - .add_card(Card::from_string("4h").unwrap()) - .unwrap(); - worst_hand - .add_card(Card::from_string("3s").unwrap()) - .unwrap(); - worst_hand - .add_card(Card::from_string("2c").unwrap()) - .unwrap(); - - let wh_rank = evaluator.rank_hand(&worst_hand); - - // Worst hand should have high rank number - assert!( - wh_rank > 7000, - "Worst hand should have high rank number, got {}", - wh_rank - ); - - // Royal flush should be better than worst hand - assert!( - rf_rank < wh_rank, - "Royal flush should be better than worst hand" - ); - } - - #[test] - fn test_java_specific_hand_rankings() { - /// Tests specific hand rankings that should match Java implementation. - /// - /// These are known good hands with expected Java ranking values. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test case 1: Royal Flush (should be very close to 1) - let mut royal_flush = Hand::new(); - royal_flush - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - royal_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - royal_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - royal_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - royal_flush - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - let rf_rank = evaluator.rank_hand(&royal_flush); - assert!(rf_rank < 100, "Royal flush should be in top 100 hands"); - - // Test case 2: Straight Flush (should be very good but not royal) - let mut straight_flush = Hand::new(); - straight_flush - .add_card(Card::from_string("9s").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("8s").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("7s").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("6s").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - - let sf_rank = evaluator.rank_hand(&straight_flush); - assert!( - sf_rank > rf_rank, - "Straight flush should be worse than royal flush" - ); - assert!(sf_rank < 5000, "Straight flush should be in top 5000 hands"); - - // Test case 3: Four of a Kind (should be good but not straight flush) - let mut four_kind = Hand::new(); - four_kind - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ac").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Kd").unwrap()) - .unwrap(); - - let fk_rank = evaluator.rank_hand(&four_kind); - assert!( - fk_rank > sf_rank, - "Four of kind should be worse than straight flush" - ); - assert!(fk_rank < 10000, "Four of kind should be in top 10000 hands"); - } - - #[test] - fn test_java_known_good_hands() { - /// Tests specific hands with known Java rankings for compatibility verification. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Known good hands with expected rank ranges (based on Java implementation) - let known_hands = vec![ - // (hand_cards, expected_rank_range) - (vec!["As", "Ks", "Qs", "Js", "Ts"], (1, 100)), // Royal flush - (vec!["9s", "8s", "7s", "6s", "5s"], (1000, 5000)), // 9-high straight flush - (vec!["Ah", "Ad", "Ac", "As", "Kd"], (5000, 10000)), // Four Aces - (vec!["Kh", "Kd", "Kc", "Ks", "Ad"], (10000, 15000)), // Four Kings - (vec!["As", "Ah", "Ad", "Ks", "Kh"], (15000, 20000)), // AAAKK full house - (vec!["As", "Ks", "Qs", "Js", "9s"], (20000, 25000)), // AKQJ9 flush - (vec!["As", "Kd", "Qh", "Jc", "Ts"], (25000, 30000)), // AKQJT straight - (vec!["As", "Ah", "Ad", "Ks", "Qs"], (30000, 35000)), // AAAKQ three of kind - (vec!["As", "Ah", "Ks", "Kd", "Qs"], (35000, 40000)), // AAKKQ two pair - (vec!["As", "Ah", "Ks", "Qs", "Js"], (40000, 45000)), // AAKQJ pair - (vec!["As", "Kd", "Qh", "Jc", "9s"], (45000, 50000)), // AKQJ9 high card - ]; - - for (cards, (min_rank, max_rank)) in known_hands { - let mut hand = Hand::new(); - for card_str in &cards { - hand.add_card(Card::from_string(card_str).unwrap()).unwrap(); - } - - let rank = evaluator.rank_hand(&hand); - assert!( - rank >= min_rank && rank <= max_rank, - "Hand {:?} should have rank in range {}-{}, got {}", - cards, - min_rank, - max_rank, - rank - ); - } - } - - #[test] - fn test_java_card_encoding_comprehensive() { - /// Comprehensive test of Java-compatible card encoding for all cards. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test all card ranks and suits - let ranks = [ - "A", "K", "Q", "J", "T", "9", "8", "7", "6", "5", "4", "3", "2", - ]; - let suits = ["s", "h", "d", "c"]; - - for rank in &ranks { - for suit in &suits { - let card_str = format!("{}{}", rank, suit); - let card = Card::from_string(&card_str).unwrap(); - - // Verify Java-style encoding - let expected_rank = match *rank { - "A" => 13, - "K" => 12, - "Q" => 11, - "J" => 10, - "T" => 9, - "9" => 8, - "8" => 7, - "7" => 6, - "6" => 5, - "5" => 4, - "4" => 3, - "3" => 2, - "2" => 1, - _ => panic!("Invalid rank"), - }; - - let expected_suit = match *suit { - "s" => 4, - "h" => 3, - "d" => 2, - "c" => 1, - _ => panic!("Invalid suit"), - }; - - assert_eq!( - card.rank(), - expected_rank, - "Card {} should have rank {}", - card_str, - expected_rank - ); - assert_eq!( - card.suit(), - expected_suit, - "Card {} should have suit {}", - card_str, - expected_suit - ); - - // Test that card can be used in hand evaluation - let mut hand = Hand::new(); - hand.add_card(card).unwrap(); - // Add dummy cards to make 5-card hand - hand.add_card(Card::from_string("2s").unwrap()).unwrap(); - hand.add_card(Card::from_string("3s").unwrap()).unwrap(); - hand.add_card(Card::from_string("4s").unwrap()).unwrap(); - hand.add_card(Card::from_string("5s").unwrap()).unwrap(); - - let rank = evaluator.rank_hand(&hand); - assert!( - rank > 0, - "Hand with card {} should produce valid rank", - card_str - ); - } - } - } - - #[test] - fn test_java_key_generation_comprehensive() { - /// Comprehensive test of Java-compatible 64-bit key generation. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test key generation for various hand types - let test_hands = vec![ - vec!["As", "Ks", "Qs", "Js", "Ts"], // Royal flush - vec!["9s", "8s", "7s", "6s", "5s"], // Straight flush - vec!["Ah", "Ad", "Ac", "As", "Kd"], // Four of a kind - vec!["As", "Ah", "Ad", "Ks", "Kh"], // Full house - vec!["As", "Ks", "Qs", "Js", "9s"], // Flush - vec!["As", "Kd", "Qh", "Jc", "Ts"], // Straight - vec!["As", "Ah", "Ad", "Ks", "Qs"], // Three of a kind - vec!["As", "Ah", "Ks", "Kd", "Qs"], // Two pair - vec!["As", "Ah", "Ks", "Qs", "Js"], // Pair - vec!["As", "Kd", "Qh", "Jc", "9s"], // High card - ]; - - for cards in &test_hands { - let mut hand = Hand::new(); - for card_str in cards { - hand.add_card(Card::from_string(card_str).unwrap()).unwrap(); - } - - // Test rank_hand5 method (Java-style key generation) - let rank5 = evaluator.rank_hand5(&hand); - let rank_general = evaluator.rank_hand(&hand); - - // Both methods should produce same result for 5-card hands - assert_eq!( - rank5, rank_general, - "rank_hand5 and rank_hand should match for 5-card hands" - ); - - // Generate manual 64-bit key for verification - let mut key: u64 = 0; - for (i, card_str) in cards.iter().enumerate() { - let card = Card::from_string(card_str).unwrap(); - let rank_val = card.rank() as u64; - let suit_val = card.suit() as u64; - let encoded_card = (rank_val << 3) | suit_val; - key |= (encoded_card as u64) << (i * 8); - } - - // Key should be non-zero and properly formatted - assert!(key > 0, "64-bit key should be non-zero"); - assert!( - key < 2u64.pow(40), - "Key should fit in 40 bits (5 * 8-bit cards)" - ); - - // Table lookup should work - let table_index = (key % evaluator.hand_ranks.len() as u64) as usize; - assert!( - table_index < evaluator.hand_ranks.len(), - "Table index should be valid" - ); - assert_eq!( - rank5, evaluator.hand_ranks[table_index], - "Table lookup should match direct evaluation" - ); - } - } -} - -/// Test suite for multi-card hand evaluation. -/// -/// These tests verify correct evaluation of 5, 6, and 7-card hands, -/// including best 5-card combination selection for larger hands. -#[cfg(test)] -mod multi_card_hands { - use super::*; - - #[test] - fn test_five_card_evaluation() { - /// Tests basic 5-card hand evaluation. - let evaluator = LookupHandEvaluator::new().unwrap(); - - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - let rank = evaluator.rank_hand(&hand); - assert!(rank > 0, "5-card hand should produce valid rank"); - } - - #[test] - fn test_six_card_best_five() { - /// Tests 6-card hand evaluation finds the best 5-card combination. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // 6 cards: royal flush in spades + extra card - let mut six_cards = Hand::new(); - six_cards - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); // Extra card - - let rank = evaluator.rank_hand(&six_cards); - assert!(rank > 0, "6-card hand should produce valid rank"); - - // Should be able to find the royal flush within the 6 cards - let mut royal_flush_only = Hand::new(); - royal_flush_only - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - royal_flush_only - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - royal_flush_only - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - royal_flush_only - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - royal_flush_only - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - let rf_rank = evaluator.rank_hand(&royal_flush_only); - - // The 6-card hand should find the royal flush and get same rank - assert_eq!( - rank, rf_rank, - "6-card hand should find the royal flush within" - ); - } - - #[test] - fn test_seven_card_best_five() { - /// Tests 7-card hand evaluation finds the best 5-card combination. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // 7 cards: royal flush in spades + two extra cards - let mut seven_cards = Hand::new(); - seven_cards - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); // Extra card 1 - seven_cards - .add_card(Card::from_string("3c").unwrap()) - .unwrap(); // Extra card 2 - - let rank = evaluator.rank_hand(&seven_cards); - assert!(rank > 0, "7-card hand should produce valid rank"); - - // Should be able to find the royal flush within the 7 cards - let mut royal_flush_only = Hand::new(); - royal_flush_only - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - royal_flush_only - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - royal_flush_only - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - royal_flush_only - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - royal_flush_only - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - let rf_rank = evaluator.rank_hand(&royal_flush_only); - - // The 7-card hand should find the royal flush and get same rank - assert_eq!( - rank, rf_rank, - "7-card hand should find the royal flush within" - ); - } - - #[test] - fn test_incremental_hand_building() { - /// Tests incremental hand building and evaluation. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Start with empty key - let key: u64 = 0; - - // Add cards incrementally - let card1 = Card::from_string("As").unwrap(); - let card2 = Card::from_string("Ks").unwrap(); - let card3 = Card::from_string("Qs").unwrap(); - let card4 = Card::from_string("Js").unwrap(); - let card5 = Card::from_string("Ts").unwrap(); - - // Build hand incrementally - let _result1 = evaluator.rank_hand_increment_single(key, card1.index() as u32); - let _result2 = evaluator.rank_hand_increment_single(key, card2.index() as u32); - let _result3 = evaluator.rank_hand_increment_single(key, card3.index() as u32); - let _result4 = evaluator.rank_hand_increment_single(key, card4.index() as u32); - let _result5 = evaluator.rank_hand_increment_single(key, card5.index() as u32); - - // For this test, we'll just verify the method can be called without panicking - // The actual incremental functionality would need the proper implementation - - // Should have valid key after adding 5 cards - assert!(key > 0, "Key should be non-zero after adding cards"); - - // Final rank should match direct evaluation - let mut hand = Hand::new(); - hand.add_card(card1).unwrap(); - hand.add_card(card2).unwrap(); - hand.add_card(card3).unwrap(); - hand.add_card(card4).unwrap(); - hand.add_card(card5).unwrap(); - - let direct_rank = evaluator.rank_hand(&hand); - let table_index = (key % evaluator.hand_ranks.len() as u64) as usize; - let incremental_rank = evaluator.hand_ranks[table_index]; - - assert_eq!( - direct_rank, incremental_rank, - "Incremental and direct evaluation should match" - ); - } - - #[test] - fn test_hand_with_hole_cards() { - /// Tests evaluation of hands with hole cards and community cards. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Community cards (board) - let mut board = Hand::new(); - board.add_card(Card::from_string("As").unwrap()).unwrap(); - board.add_card(Card::from_string("Ks").unwrap()).unwrap(); - board.add_card(Card::from_string("Qs").unwrap()).unwrap(); - board.add_card(Card::from_string("Js").unwrap()).unwrap(); - board.add_card(Card::from_string("2d").unwrap()).unwrap(); - - // Hole cards that complete royal flush - let hole1 = Card::from_string("Ts").unwrap(); - - // Evaluate with hole cards - let rank = evaluator.rank_hand_with_hole_cards( - hole1.index() as u32, - 0, // No second hole card for this test - &board, - ); - - assert!(rank > 0, "Hand with hole cards should produce valid rank"); - } - - #[test] - fn test_comprehensive_hole_card_scenarios() { - /// Tests various hole card scenarios with different board textures. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Scenario 1: Hole cards complete royal flush - let mut board1 = Hand::new(); - board1.add_card(Card::from_string("As").unwrap()).unwrap(); - board1.add_card(Card::from_string("Ks").unwrap()).unwrap(); - board1.add_card(Card::from_string("Qs").unwrap()).unwrap(); - board1.add_card(Card::from_string("Js").unwrap()).unwrap(); - board1.add_card(Card::from_string("2d").unwrap()).unwrap(); - - let hole1 = Card::from_string("Ts").unwrap(); - let rank1 = evaluator.rank_hand_with_hole_cards(hole1.index() as u32, 0, &board1); - assert!( - rank1 < 1000, - "Royal flush with hole cards should be very strong" - ); - - // Scenario 2: Hole cards make full house - let mut board2 = Hand::new(); - board2.add_card(Card::from_string("As").unwrap()).unwrap(); - board2.add_card(Card::from_string("Ah").unwrap()).unwrap(); - board2.add_card(Card::from_string("Kd").unwrap()).unwrap(); - board2.add_card(Card::from_string("Kc").unwrap()).unwrap(); - board2.add_card(Card::from_string("2d").unwrap()).unwrap(); - - let hole2 = Card::from_string("Ad").unwrap(); - let rank2 = evaluator.rank_hand_with_hole_cards(hole2.index() as u32, 0, &board2); - assert!(rank2 < 20000, "Full house with hole cards should be strong"); - - // Scenario 3: Two hole cards make straight - let mut board3 = Hand::new(); - board3.add_card(Card::from_string("9s").unwrap()).unwrap(); - board3.add_card(Card::from_string("8d").unwrap()).unwrap(); - board3.add_card(Card::from_string("7h").unwrap()).unwrap(); - board3.add_card(Card::from_string("6c").unwrap()).unwrap(); - board3.add_card(Card::from_string("2s").unwrap()).unwrap(); - - let hole3a = Card::from_string("Ts").unwrap(); - let hole3b = Card::from_string("Jd").unwrap(); - let rank3 = evaluator.rank_hand_with_hole_cards( - hole3a.index() as u32, - hole3b.index() as u32, - &board3, - ); - assert!(rank3 < 30000, "Straight with hole cards should be decent"); - } - - #[test] - fn test_incremental_hand_building_comprehensive() { - /// Comprehensive test of incremental hand building with various scenarios. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test incremental building of royal flush - let royal_cards = vec![ - Card::from_string("As").unwrap(), - Card::from_string("Ks").unwrap(), - Card::from_string("Qs").unwrap(), - Card::from_string("Js").unwrap(), - Card::from_string("Ts").unwrap(), - ]; - - // Build hand incrementally and verify improving strength - let mut current_best = 0u32; - - for (i, _card) in royal_cards.iter().enumerate() { - let mut hand = Hand::new(); - for &c in &royal_cards[0..=i] { - hand.add_card(c).unwrap(); - } - - if hand.size() >= 5 { - let rank = evaluator.rank_hand(&hand); - assert!(rank > 0, "Hand should produce valid rank"); - - // As we add more cards, the hand should get stronger or stay strong - if i > 0 { - assert!( - rank <= current_best || rank > 0, - "Hand should maintain strength or improve" - ); - } - current_best = rank; - } - } - - // Final royal flush should be very strong - assert!( - current_best < 1000, - "Final royal flush should be very strong" - ); - } - - #[test] - fn test_omaha_hand_evaluation() { - /// Tests Omaha-style hand evaluation (4 hole cards, 5 community cards). - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Hole cards - let mut hole_cards = Hand::new(); - hole_cards - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - hole_cards - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - hole_cards - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - hole_cards - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - - // Community cards - let mut board = Hand::new(); - board.add_card(Card::from_string("Ts").unwrap()).unwrap(); - board.add_card(Card::from_string("9d").unwrap()).unwrap(); - board.add_card(Card::from_string("8h").unwrap()).unwrap(); - board.add_card(Card::from_string("7c").unwrap()).unwrap(); - board.add_card(Card::from_string("2s").unwrap()).unwrap(); - - // Test different 2-hole-card combinations - let hole_indices: Vec = hole_cards - .get_card_array() - .iter() - .map(|&idx| idx as u32) - .collect(); - - let mut best_rank = u32::MAX; - - // Try all combinations of 2 hole cards with 3 community cards - for i in 0..4 { - for j in (i + 1)..4 { - let rank = - evaluator.rank_hand_with_hole_cards(hole_indices[i], hole_indices[j], &board); - best_rank = best_rank.min(rank); - } - } - - assert!( - best_rank < 30000, - "Omaha hand should find good 5-card combination" - ); - } - - #[test] - fn test_hand_evaluation_with_duplicates() { - /// Tests hand evaluation when duplicate cards are present (edge case). - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Create hand with some duplicate cards (shouldn't happen in real poker) - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); // Duplicate - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - - // Should still produce a valid rank without panicking - let rank = evaluator.rank_hand(&hand); - assert!(rank > 0, "Hand with duplicates should still evaluate"); - - // Test 6-card hand with duplicates - let mut six_card = Hand::new(); - six_card.add_card(Card::from_string("As").unwrap()).unwrap(); - six_card.add_card(Card::from_string("As").unwrap()).unwrap(); // Duplicate - six_card.add_card(Card::from_string("Ks").unwrap()).unwrap(); - six_card.add_card(Card::from_string("Qs").unwrap()).unwrap(); - six_card.add_card(Card::from_string("Js").unwrap()).unwrap(); - six_card.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - let rank6 = evaluator.rank_hand(&six_card); - assert!( - rank6 > 0, - "6-card hand with duplicates should still evaluate" - ); - } -} - -/// Test suite for regression testing. -/// -/// These tests verify specific hands that were problematic in the original -/// implementation and ensure consistency between different evaluation methods. -#[cfg(test)] -mod regression_tests { - use super::*; - - #[test] - fn test_problematic_hands() { - /// Tests specific hands that were problematic in original implementation. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test case 1: A-2-3-4-5 straight (wheel straight) - let mut wheel_straight = Hand::new(); - wheel_straight - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("3h").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("4c").unwrap()) - .unwrap(); - wheel_straight - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - - let wheel_rank = evaluator.rank_hand(&wheel_straight); - assert!(wheel_rank > 0, "Wheel straight should be valid"); - - // Test case 2: Duplicate suits in flush (should not be flush) - let mut not_flush = Hand::new(); - not_flush - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - not_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - not_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - not_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - not_flush - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - let not_flush_rank = evaluator.rank_hand(¬_flush); - assert!(not_flush_rank > 0, "Non-flush should still be valid hand"); - - // Test case 3: Four of a kind vs full house distinction - let mut four_kind = Hand::new(); - four_kind - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ac").unwrap()) - .unwrap(); - four_kind - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - - let mut full_house = Hand::new(); - full_house - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Kh").unwrap()) - .unwrap(); - - let fk_rank = evaluator.rank_hand(&four_kind); - let fh_rank = evaluator.rank_hand(&full_house); - - assert!(fk_rank < fh_rank, "Four of kind should beat full house"); - } - - #[test] - fn test_additional_problematic_hands() { - /// Tests additional hands that have been problematic in poker evaluators. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Problematic hand 1: Straight vs straight flush edge case - let mut straight = Hand::new(); - straight.add_card(Card::from_string("As").unwrap()).unwrap(); - straight.add_card(Card::from_string("Kd").unwrap()).unwrap(); - straight.add_card(Card::from_string("Qh").unwrap()).unwrap(); - straight.add_card(Card::from_string("Jc").unwrap()).unwrap(); - straight.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - let mut straight_flush = Hand::new(); - straight_flush - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - straight_flush - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - let s_rank = evaluator.rank_hand(&straight); - let sf_rank = evaluator.rank_hand(&straight_flush); - assert!(sf_rank < s_rank, "Straight flush should beat straight"); - - // Problematic hand 2: Full house vs three of a kind - let mut three_kind = Hand::new(); - three_kind - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - three_kind - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - - let mut full_house = Hand::new(); - full_house - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - full_house - .add_card(Card::from_string("Kh").unwrap()) - .unwrap(); - - let tk_rank = evaluator.rank_hand(&three_kind); - let fh_rank = evaluator.rank_hand(&full_house); - assert!(fh_rank < tk_rank, "Full house should beat three of a kind"); - - // Problematic hand 3: Two pair vs pair - let mut pair = Hand::new(); - pair.add_card(Card::from_string("As").unwrap()).unwrap(); - pair.add_card(Card::from_string("Ah").unwrap()).unwrap(); - pair.add_card(Card::from_string("Kd").unwrap()).unwrap(); - pair.add_card(Card::from_string("Qs").unwrap()).unwrap(); - pair.add_card(Card::from_string("Js").unwrap()).unwrap(); - - let mut two_pair = Hand::new(); - two_pair.add_card(Card::from_string("As").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("Ah").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("Ks").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("Kh").unwrap()).unwrap(); - two_pair.add_card(Card::from_string("Qs").unwrap()).unwrap(); - - let p_rank = evaluator.rank_hand(&pair); - let tp_rank = evaluator.rank_hand(&two_pair); - assert!(tp_rank < p_rank, "Two pair should beat pair"); - } - - #[test] - fn test_consistency_across_evaluation_methods() { - /// Tests consistency between different evaluation methods and scenarios. - let evaluator = LookupHandEvaluator::new().unwrap(); - - let test_hands = vec![ - vec!["As", "Ks", "Qs", "Js", "Ts"], // Royal flush - vec!["9s", "8s", "7s", "6s", "5s"], // Straight flush - vec!["Ah", "Ad", "Ac", "As", "Kd"], // Four of a kind - vec!["As", "Ah", "Ad", "Ks", "Kh"], // Full house - vec!["As", "Ks", "Qs", "Js", "9s"], // Flush - vec!["As", "Kd", "Qh", "Jc", "Ts"], // Straight - vec!["As", "Ah", "Ad", "Ks", "Qs"], // Three of a kind - vec!["As", "Ah", "Ks", "Kd", "Qs"], // Two pair - vec!["As", "Ah", "Ks", "Qs", "Js"], // Pair - vec!["As", "Kd", "Qh", "Jc", "9s"], // High card - ]; - - for cards in &test_hands { - let mut hand = Hand::new(); - for card_str in cards { - hand.add_card(Card::from_string(card_str).unwrap()).unwrap(); - } - - // Test consistency between rank_hand and rank_hand5 for 5-card hands - let general_rank = evaluator.rank_hand(&hand); - let specific_rank = evaluator.rank_hand5(&hand); - - assert_eq!( - general_rank, specific_rank, - "rank_hand and rank_hand5 should be consistent for 5-card hands" - ); - - // Test that evaluation is deterministic across multiple calls - let rank2 = evaluator.rank_hand(&hand); - let rank3 = evaluator.rank_hand(&hand); - let rank4 = evaluator.rank_hand5(&hand); - let rank5 = evaluator.rank_hand5(&hand); - - assert_eq!( - general_rank, rank2, - "Hand evaluation should be deterministic" - ); - assert_eq!(rank2, rank3, "Hand evaluation should be deterministic"); - assert_eq!(specific_rank, rank4, "rank_hand5 should be deterministic"); - assert_eq!(rank4, rank5, "rank_hand5 should be deterministic"); - } - } - - #[test] - fn test_6card_vs_7card_consistency() { - /// Tests that 6-card and 7-card hands find the same best 5-card combination. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Create a 7-card hand with a royal flush - let mut seven_cards = Hand::new(); - seven_cards - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); - seven_cards - .add_card(Card::from_string("3c").unwrap()) - .unwrap(); - - // Create corresponding 6-card hand (remove one extra card) - let mut six_cards = Hand::new(); - six_cards - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - six_cards - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); - - // Create 5-card royal flush for comparison - let mut five_cards = Hand::new(); - five_cards - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - five_cards - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - five_cards - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - five_cards - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - five_cards - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - let seven_rank = evaluator.rank_hand(&seven_cards); - let six_rank = evaluator.rank_hand(&six_cards); - let five_rank = evaluator.rank_hand(&five_cards); - - // All should find the royal flush and have same rank - assert_eq!( - five_rank, six_rank, - "5-card and 6-card should find same best hand" - ); - assert_eq!( - six_rank, seven_rank, - "6-card and 7-card should find same best hand" - ); - assert!(five_rank < 1000, "Royal flush should be very strong"); - } - - #[test] - fn test_evaluation_consistency() { - /// Tests consistency between different evaluation methods. - let evaluator = LookupHandEvaluator::new().unwrap(); - - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - // Test consistency between rank_hand and rank_hand5 for 5-card hands - let general_rank = evaluator.rank_hand(&hand); - let specific_rank = evaluator.rank_hand5(&hand); - - assert_eq!( - general_rank, specific_rank, - "rank_hand and rank_hand5 should be consistent for 5-card hands" - ); - - // Test that hand evaluation is deterministic - let rank2 = evaluator.rank_hand(&hand); - let rank3 = evaluator.rank_hand(&hand); - - assert_eq!( - general_rank, rank2, - "Hand evaluation should be deterministic" - ); - assert_eq!(rank2, rank3, "Hand evaluation should be deterministic"); - } - - #[test] - fn test_boundary_conditions() { - /// Tests boundary conditions and edge cases. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test with exactly 5 cards (minimum for evaluation) - let mut min_hand = Hand::new(); - min_hand.add_card(Card::from_string("As").unwrap()).unwrap(); - min_hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - min_hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - min_hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - min_hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - let min_rank = evaluator.rank_hand(&min_hand); - assert!(min_rank > 0, "Minimum 5-card hand should be valid"); - - // Test with insufficient cards - let mut small_hand = Hand::new(); - small_hand - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - small_hand - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - - let small_rank = evaluator.rank_hand(&small_hand); - assert_eq!(small_rank, 0, "Insufficient cards should return 0"); - } -} - -/// Test suite for performance characteristics. -/// -/// These tests validate that the evaluator meets performance requirements -/// and maintains O(1) lookup performance. -#[cfg(test)] -mod performance_tests { - use super::*; - use std::time::Instant; - - #[test] - fn test_evaluation_speed() { - /// Tests that hand evaluation meets performance requirements. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Create diverse test hands - let test_hands = create_diverse_test_hands(10_000); - - let start = Instant::now(); - - for hand in &test_hands { - let _rank = evaluator.rank_hand(hand); - } - - let duration = start.elapsed(); - let per_hand = duration / test_hands.len() as u32; - - println!("Evaluated {} hands in {:?}", test_hands.len(), duration); - println!("Average time per hand: {:?}", per_hand); - - // Performance assertions - should be very fast (O(1) lookup) - assert!( - per_hand.as_micros() < 100, - "Average evaluation should be < 100μs, was {:?}", - per_hand - ); - - // Total time for 10k hands should be very fast - assert!( - duration.as_secs() < 5, - "10k evaluations should take < 5s, took {:?}", - duration - ); - } - - #[test] - fn test_o1_lookup_performance() { - /// Tests that lookup table provides O(1) performance. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test with many random hands to verify O(1) behavior - let test_hands = create_diverse_test_hands(1_000); - - let mut total_time = std::time::Duration::new(0, 0); - - for hand in &test_hands { - let start = Instant::now(); - let _rank = evaluator.rank_hand(hand); - let duration = start.elapsed(); - total_time += duration; - } - - let avg_time = total_time / test_hands.len() as u32; - - // O(1) lookup should be very consistent (low variance) - assert!( - avg_time.as_micros() < 50, - "O(1) lookup should be very fast, average was {:?}", - avg_time - ); - } - - #[test] - fn test_performance_across_hand_types() { - /// Tests performance consistency across different hand types. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test each hand type separately to ensure consistent performance - let hand_types = vec![ - create_royal_flush_hands(100), - create_straight_flush_hands(100), - create_four_kind_hands(100), - create_full_house_hands(100), - create_flush_hands(100), - create_straight_hands(100), - create_three_kind_hands(100), - create_two_pair_hands(100), - create_pair_hands(100), - create_high_card_hands(100), - ]; - - let mut all_times = Vec::new(); - - for hands in hand_types { - let start = Instant::now(); - for hand in &hands { - let _rank = evaluator.rank_hand(hand); - } - let duration = start.elapsed(); - let avg_time = duration / hands.len() as u32; - all_times.push(avg_time.as_micros()); - } - - // Calculate mean and standard deviation - let sum: u128 = all_times.iter().sum(); - let mean = sum / all_times.len() as u128; - - let variance = all_times - .iter() - .map(|&time| { - let diff = time as i64 - mean as i64; - (diff * diff) as u64 - }) - .sum::() - / all_times.len() as u64; - let std_dev = (variance as f64).sqrt() as u64; - - println!("Performance stats across hand types:"); - println!("Mean time: {} μs", mean); - println!("Std dev: {} μs", std_dev); - println!("Max time: {} μs", all_times.iter().max().unwrap()); - println!("Min time: {} μs", all_times.iter().min().unwrap()); - - // Performance should be very consistent across hand types - assert!(mean < 100, "Average evaluation should be very fast"); - assert!( - std_dev < 50, - "Performance variance should be low across hand types" - ); - } - - #[test] - fn test_memory_usage_stability() { - /// Tests that memory usage remains stable during extended evaluation. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Basic memory stability test - let initial_memory_estimate = evaluator.hand_ranks.len() * std::mem::size_of::(); - - // Run many evaluations and verify no memory leaks - let test_hands = create_diverse_test_hands(10_000); - - for hand in &test_hands { - let _rank = evaluator.rank_hand(hand); - } - - // Verify table is still intact and accessible - assert_eq!( - evaluator.hand_ranks.len(), - 32_487_834, - "Hand ranks table should maintain expected size" - ); - - // Verify some known entries are still accessible - let royal_flush_cards = [41, 37, 33, 29, 25]; - let rf_rank = - poker_api::evaluator_generator::state_table_generator::StateTableGenerator::eval_5hand( - royal_flush_cards[0], - royal_flush_cards[1], - royal_flush_cards[2], - royal_flush_cards[3], - royal_flush_cards[4], - ); - assert!( - rf_rank > 0, - "Table should still be functional after many evaluations" - ); - - println!("Memory usage estimate: {} bytes", initial_memory_estimate); - println!("Table entries: {}", evaluator.hand_ranks.len()); - } - - #[test] - fn test_scalability_performance() { - /// Tests performance scalability with increasing hand counts. - let evaluator = LookupHandEvaluator::new().unwrap(); - - let hand_counts = vec![100, 1_000, 10_000, 100_000]; - - for &count in &hand_counts { - let test_hands = create_diverse_test_hands(count); - let start = Instant::now(); - - for hand in &test_hands { - let _rank = evaluator.rank_hand(hand); - } - - let duration = start.elapsed(); - let per_hand = duration / count as u32; - - println!("Evaluated {} hands in {:?}", count, duration); - println!("Average time per hand: {:?}", per_hand); - - // Performance should scale linearly (O(n) for n evaluations) - // Each evaluation should still be O(1) - assert!( - per_hand.as_micros() < 100, - "Average evaluation should remain fast even at scale: {:?}", - per_hand - ); - - // Total time should be reasonable - assert!( - duration.as_secs() < 30, - "Large batch should complete in reasonable time: {:?}", - duration - ); - } - } - - #[test] - fn test_memory_usage_bounds() { - /// Tests that memory usage stays within acceptable bounds. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Basic memory usage validation - assert_eq!( - evaluator.hand_ranks.len(), - 32_487_834, - "Hand ranks table should have expected size" - ); - - // Verify table is properly loaded (not all zeros) - let populated_entries = evaluator - .hand_ranks - .iter() - .filter(|&&rank| rank > 0) - .count(); - assert!( - populated_entries > 1_000_000, - "Table should have many populated entries, got {}", - populated_entries - ); - - // Test that we can access any table entry without panicking - let test_indices = [0, 1000, 1_000_000, 32_000_000]; - for &index in &test_indices { - if index < evaluator.hand_ranks.len() { - let _rank = evaluator.hand_ranks[index]; - // Rank can be 0 for unpopulated entries, but access should not panic - } - } - } - - #[test] - fn test_table_loading_performance() { - /// Tests that table loading performance is acceptable. - let start = Instant::now(); - let evaluator = LookupHandEvaluator::new().unwrap(); - let load_time = start.elapsed(); - - println!("Table loading took: {:?}", load_time); - - // Table loading should be reasonable (depends on disk speed) - assert!( - load_time.as_secs() < 10, - "Table loading should take < 10s, took {:?}", - load_time - ); - - // After loading, evaluation should work - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - let _rank = evaluator.rank_hand(&hand); - // If we get here without panicking, the table was loaded correctly - } -} - -/// Test suite for hand type boundary conditions. -/// -/// These tests verify the exact boundaries between different hand types -/// and ensure correct ranking at the transitions between categories. -#[cfg(test)] -mod hand_type_boundaries { - use super::*; - - #[test] - fn test_flush_vs_straight_boundaries() { - /// Tests the boundary between flush and straight hand types. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Strongest straight: A K Q J T - let mut best_straight = Hand::new(); - best_straight - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - best_straight - .add_card(Card::from_string("Kd").unwrap()) - .unwrap(); - best_straight - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - best_straight - .add_card(Card::from_string("Jc").unwrap()) - .unwrap(); - best_straight - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - // Weakest flush: K J 8 5 2 same suit - let mut weakest_flush = Hand::new(); - weakest_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - weakest_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - weakest_flush - .add_card(Card::from_string("8s").unwrap()) - .unwrap(); - weakest_flush - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - weakest_flush - .add_card(Card::from_string("2s").unwrap()) - .unwrap(); - - let bs_rank = evaluator.rank_hand(&best_straight); - let wf_rank = evaluator.rank_hand(&weakest_flush); - - assert!( - wf_rank < bs_rank, - "Even weakest flush should beat strongest straight" - ); - } - - #[test] - fn test_full_house_vs_flush_boundaries() { - /// Tests the boundary between full house and flush hand types. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Strongest flush: A K Q J 9 same suit - let mut best_flush = Hand::new(); - best_flush - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - best_flush - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - best_flush - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - best_flush - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - best_flush - .add_card(Card::from_string("9s").unwrap()) - .unwrap(); - - // Weakest full house: A A A 2 2 - let mut weakest_fh = Hand::new(); - weakest_fh - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - weakest_fh - .add_card(Card::from_string("Ah").unwrap()) - .unwrap(); - weakest_fh - .add_card(Card::from_string("Ad").unwrap()) - .unwrap(); - weakest_fh - .add_card(Card::from_string("2s").unwrap()) - .unwrap(); - weakest_fh - .add_card(Card::from_string("2h").unwrap()) - .unwrap(); - - let bf_rank = evaluator.rank_hand(&best_flush); - let wfh_rank = evaluator.rank_hand(&weakest_fh); - - assert!( - wfh_rank < bf_rank, - "Even weakest full house should beat strongest flush" - ); - } - - #[test] - fn test_straight_vs_three_kind_boundaries() { - /// Tests the boundary between straight and three of a kind hand types. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Strongest three of a kind: A A A K Q - let mut best_tk = Hand::new(); - best_tk.add_card(Card::from_string("As").unwrap()).unwrap(); - best_tk.add_card(Card::from_string("Ah").unwrap()).unwrap(); - best_tk.add_card(Card::from_string("Ad").unwrap()).unwrap(); - best_tk.add_card(Card::from_string("Ks").unwrap()).unwrap(); - best_tk.add_card(Card::from_string("Qs").unwrap()).unwrap(); - - // Weakest straight: 6 5 4 3 2 - let mut weakest_straight = Hand::new(); - weakest_straight - .add_card(Card::from_string("6s").unwrap()) - .unwrap(); - weakest_straight - .add_card(Card::from_string("5d").unwrap()) - .unwrap(); - weakest_straight - .add_card(Card::from_string("4h").unwrap()) - .unwrap(); - weakest_straight - .add_card(Card::from_string("3c").unwrap()) - .unwrap(); - weakest_straight - .add_card(Card::from_string("2s").unwrap()) - .unwrap(); - - let btk_rank = evaluator.rank_hand(&best_tk); - let ws_rank = evaluator.rank_hand(&weakest_straight); - - assert!( - ws_rank < btk_rank, - "Even weakest straight should beat strongest three of a kind" - ); - } - - #[test] - fn test_all_hand_type_boundaries() { - /// Tests all hand type boundaries to ensure proper ranking hierarchy. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Create hands at the boundaries of each hand type - let hands = vec![ - ("Royal Flush", create_royal_flush_hands(1)[0].clone()), - ("Straight Flush", create_straight_flush_hands(1)[0].clone()), - ("Four of Kind", create_four_kind_hands(1)[0].clone()), - ("Full House", create_full_house_hands(1)[0].clone()), - ("Flush", create_flush_hands(1)[0].clone()), - ("Straight", create_straight_hands(1)[0].clone()), - ("Three of Kind", create_three_kind_hands(1)[0].clone()), - ("Two Pair", create_two_pair_hands(1)[0].clone()), - ("Pair", create_pair_hands(1)[0].clone()), - ("High Card", create_high_card_hands(1)[0].clone()), - ]; - - // Get ranks for all hands - let mut ranks: Vec<(String, u32)> = Vec::new(); - for (name, hand) in hands { - let rank = evaluator.rank_hand(&hand); - ranks.push((name.to_string(), rank)); - } - - // Verify hand hierarchy (lower rank = better hand) - for i in 0..ranks.len() - 1 { - assert!( - ranks[i].1 < ranks[i + 1].1, - "Hand type {} should be better than {}: {} < {}", - ranks[i].0, - ranks[i + 1].0, - ranks[i].1, - ranks[i + 1].1 - ); - } - - // Print the ranking hierarchy for verification - println!("\nHand Type Ranking Hierarchy:"); - for (name, rank) in ranks { - println!("{}: {}", name, rank); - } - } -} - -/// Test suite for edge cases and boundary conditions. -/// -/// These tests verify correct behavior in unusual or extreme situations -/// that might not be covered by normal gameplay scenarios. -#[cfg(test)] -mod edge_cases { - use super::*; - - #[test] - fn test_insufficient_cards() { - /// Tests behavior with insufficient cards for evaluation. - /// - /// The evaluator should handle hands with fewer than 5 cards - /// gracefully, returning 0 or a safe default value. - /// - /// # Test Cases - /// - 0 cards: Should return 0 - /// - 1-4 cards: Should return 0 - /// - 5+ cards: Should return valid rank - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test with insufficient cards - let mut small_hand = Hand::new(); - small_hand - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - small_hand - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - - let rank = evaluator.rank_hand(&small_hand); - assert_eq!(rank, 0, "Hands with < 5 cards should return 0"); - } - - #[test] - fn test_duplicate_cards() { - /// Tests behavior when duplicate cards are added to a hand. - /// - /// In a real poker scenario, duplicate cards shouldn't exist, - /// but the evaluator should handle this case gracefully. - /// - /// # Test Case - /// - Hand with duplicate cards - /// - Expected: Should not crash, should return some rank value - let evaluator = LookupHandEvaluator::new().unwrap(); - - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); // Duplicate - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - - // Should not panic, should return some value - let rank = evaluator.rank_hand(&hand); - assert!(rank > 0, "Should handle duplicate cards gracefully"); - } - - #[test] - fn test_unusual_card_combinations() { - /// Tests unusual card combinations that might cause issues. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test 1: All cards same rank (five of a kind - impossible but should handle) - let mut five_same_rank = Hand::new(); - five_same_rank - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - five_same_rank - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - five_same_rank - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - five_same_rank - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - five_same_rank - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - - let rank1 = evaluator.rank_hand(&five_same_rank); - assert!(rank1 > 0, "Should handle five same rank cards"); - - // Test 2: All cards same suit (should be flush) - let mut all_same_suit = Hand::new(); - all_same_suit - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - all_same_suit - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - all_same_suit - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - all_same_suit - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - all_same_suit - .add_card(Card::from_string("9s").unwrap()) - .unwrap(); - - let rank2 = evaluator.rank_hand(&all_same_suit); - assert!(rank2 > 0, "Should handle all same suit cards"); - - // Test 3: Alternating high and low cards - let mut alternating = Hand::new(); - alternating - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - alternating - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); - alternating - .add_card(Card::from_string("Kh").unwrap()) - .unwrap(); - alternating - .add_card(Card::from_string("3c").unwrap()) - .unwrap(); - alternating - .add_card(Card::from_string("Qh").unwrap()) - .unwrap(); - - let rank3 = evaluator.rank_hand(&alternating); - assert!(rank3 > 0, "Should handle alternating high/low cards"); - } - - #[test] - fn test_boundary_card_values() { - /// Tests cards at the extreme ends of the ranking spectrum. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test with lowest possible cards - let mut low_cards = Hand::new(); - low_cards - .add_card(Card::from_string("2s").unwrap()) - .unwrap(); - low_cards - .add_card(Card::from_string("2d").unwrap()) - .unwrap(); - low_cards - .add_card(Card::from_string("3h").unwrap()) - .unwrap(); - low_cards - .add_card(Card::from_string("4c").unwrap()) - .unwrap(); - low_cards - .add_card(Card::from_string("5s").unwrap()) - .unwrap(); - - let low_rank = evaluator.rank_hand(&low_cards); - assert!(low_rank > 7000, "Low cards should have high rank number"); - - // Test with highest possible cards - let mut high_cards = Hand::new(); - high_cards - .add_card(Card::from_string("As").unwrap()) - .unwrap(); - high_cards - .add_card(Card::from_string("Ks").unwrap()) - .unwrap(); - high_cards - .add_card(Card::from_string("Qs").unwrap()) - .unwrap(); - high_cards - .add_card(Card::from_string("Js").unwrap()) - .unwrap(); - high_cards - .add_card(Card::from_string("Ts").unwrap()) - .unwrap(); - - let high_rank = evaluator.rank_hand(&high_cards); - assert!(high_rank < 1000, "High cards should have low rank number"); - - assert!( - high_rank < low_rank, - "High cards should be better than low cards" - ); - } - - #[test] - fn test_malformed_hands() { - /// Tests various malformed or unusual hand scenarios. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test with exactly 5 cards (minimum) - let mut min_hand = Hand::new(); - min_hand.add_card(Card::from_string("As").unwrap()).unwrap(); - min_hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - min_hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - min_hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - min_hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - - let min_rank = evaluator.rank_hand(&min_hand); - assert!(min_rank > 0, "Minimum 5-card hand should evaluate"); - - // Test with more than 5 cards (should find best 5) - let mut max_hand = Hand::new(); - max_hand.add_card(Card::from_string("As").unwrap()).unwrap(); - max_hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - max_hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - max_hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - max_hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - max_hand.add_card(Card::from_string("2d").unwrap()).unwrap(); - max_hand.add_card(Card::from_string("3c").unwrap()).unwrap(); - - let max_rank = evaluator.rank_hand(&max_hand); - assert_eq!(min_rank, max_rank, "Should find same best 5 cards"); - } - - #[test] - fn test_stress_test_edge_cases() { - /// Stress test with many unusual combinations. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Generate various edge case combinations - let edge_cases = vec![ - vec!["Ad", "Ah", "Ac", "As", "2s"], // Four aces with deuce - vec!["Kd", "Kh", "Kc", "2s", "2d"], // Full house with low pair - vec!["As", "2s", "3s", "4s", "6s"], // Flush with gap - vec!["As", "2d", "3h", "4c", "5s"], // Wheel straight - vec!["2s", "3d", "4h", "5c", "6s"], // Low straight - vec!["9s", "8d", "7h", "6c", "5s"], // Medium straight - vec!["As", "Kd", "Qh", "Jc", "Ts"], // Broadway straight - ]; - - for cards in &edge_cases { - let mut hand = Hand::new(); - for card_str in cards { - hand.add_card(Card::from_string(card_str).unwrap()).unwrap(); - } - - let rank = evaluator.rank_hand(&hand); - assert!( - rank > 0, - "Edge case hand {:?} should evaluate successfully", - cards - ); - - // Test that evaluation is deterministic - let rank2 = evaluator.rank_hand(&hand); - assert_eq!(rank, rank2, "Edge case evaluation should be deterministic"); - } - } -} - -/// Test suite for performance characteristics. -/// -/// These tests validate that the evaluator meets performance requirements -/// and doesn't regress in speed or memory usage. -#[cfg(test)] -mod performance { - use super::*; - use std::time::Instant; - - #[test] - fn test_evaluation_speed() { - /// Tests that hand evaluation meets performance requirements. - /// - /// Verifies that evaluating a large number of hands completes - /// within reasonable time constraints. - /// - /// # Performance Target - /// - 1 million evaluations should complete in < 30 seconds - /// - Average evaluation time should be < 30 microseconds - /// - /// # Test Data - /// Uses a variety of hand types to ensure balanced testing - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Create diverse test hands - let test_hands = create_diverse_test_hands(100_000); - - let start = Instant::now(); - - for hand in &test_hands { - let _rank = evaluator.rank_hand(hand); - } - - let duration = start.elapsed(); - let per_hand = duration / test_hands.len() as u32; - - println!("Evaluated {} hands in {:?}", test_hands.len(), duration); - println!("Average time per hand: {:?}", per_hand); - - // Performance assertions - assert!( - duration.as_secs() < 30, - "1M evaluations should take < 30s, took {:?}", - duration - ); - assert!( - per_hand.as_micros() < 30, - "Average evaluation should be < 30μs, was {:?}", - per_hand - ); - } - - #[test] - fn test_memory_usage() { - /// Tests that memory usage stays within acceptable bounds. - /// - /// Verifies that the evaluator doesn't leak memory or use - /// excessive resources during operation. - /// - /// # Memory Constraints - /// - Peak memory usage should be < 200MB - /// - No memory leaks during repeated evaluations - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Force table generation and loading - let hand = create_test_hand(); - let _rank = evaluator.rank_hand(&hand); - - // Basic memory usage check - // In a real implementation, you might use a memory profiler - assert!( - true, - "Memory usage should be monitored in integration tests" - ); - } -} - -/// Test suite for integration testing of the complete evaluation pipeline. -/// -/// These tests verify that all components work together correctly -/// and that the complete system produces expected results. -#[cfg(test)] -mod integration_tests { - use super::*; - - #[test] - fn test_complete_poker_scenario() { - /// Tests a complete poker scenario with multiple hands and evaluation. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Simulate a Texas Hold'em scenario - let board_cards = vec![ - Card::from_string("As").unwrap(), - Card::from_string("Ks").unwrap(), - Card::from_string("Qs").unwrap(), - Card::from_string("Js").unwrap(), - Card::from_string("2d").unwrap(), - ]; - - // Player hands - let player1_hole = vec![ - Card::from_string("Ts").unwrap(), - Card::from_string("9s").unwrap(), - ]; - - let player2_hole = vec![ - Card::from_string("Kh").unwrap(), - Card::from_string("Qh").unwrap(), - ]; - - let player3_hole = vec![ - Card::from_string("2s").unwrap(), - Card::from_string("3s").unwrap(), - ]; - - // Evaluate each player's complete hand - let mut board = Hand::new(); - for card in board_cards { - board.add_card(card).unwrap(); - } - - let player1_rank = evaluator.rank_hand_with_hole_cards( - player1_hole[0].index() as u32, - player1_hole[1].index() as u32, - &board, - ); - - let player2_rank = evaluator.rank_hand_with_hole_cards( - player2_hole[0].index() as u32, - player2_hole[1].index() as u32, - &board, - ); - - let player3_rank = evaluator.rank_hand_with_hole_cards( - player3_hole[0].index() as u32, - player3_hole[1].index() as u32, - &board, - ); - - // Player 1 should have royal flush (best hand) - assert!(player1_rank < 1000, "Player 1 should have royal flush"); - - // Player 2 should have full house (good hand) - assert!(player2_rank < 20000, "Player 2 should have full house"); - - // Player 3 should have pair of deuces (weak hand) - assert!(player3_rank > 50000, "Player 3 should have weak hand"); - - // Verify hand hierarchy - assert!(player1_rank < player2_rank, "Player 1 should beat Player 2"); - assert!(player2_rank < player3_rank, "Player 2 should beat Player 3"); - } - - #[test] - fn test_evaluation_pipeline_consistency() { - /// Tests that the complete evaluation pipeline is consistent across different inputs. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test various hand creation methods - let test_hands = vec![ - // Method 1: Direct hand creation - { - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - hand - }, - // Method 2: Using helper function - create_test_hand(), - // Method 3: Manual card array creation - { - let mut hand = Hand::new(); - let cards = ["As", "Ks", "Qs", "Js", "Ts"]; - for card_str in cards { - hand.add_card(Card::from_string(card_str).unwrap()).unwrap(); - } - hand - }, - ]; - - // All methods should produce the same result - let first_rank = evaluator.rank_hand(&test_hands[0]); - for (i, hand) in test_hands.iter().enumerate().skip(1) { - let rank = evaluator.rank_hand(hand); - assert_eq!( - first_rank, rank, - "Hand creation method {} should produce same result as method 0", - i - ); - } - } - - #[test] - fn test_large_scale_evaluation() { - /// Tests evaluation of many hands to ensure system stability. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Create a large number of diverse hands - let mut all_hands = Vec::new(); - all_hands.extend(create_royal_flush_hands(10)); - all_hands.extend(create_straight_flush_hands(50)); - all_hands.extend(create_four_kind_hands(100)); - all_hands.extend(create_full_house_hands(200)); - all_hands.extend(create_flush_hands(300)); - all_hands.extend(create_straight_hands(400)); - all_hands.extend(create_three_kind_hands(500)); - all_hands.extend(create_two_pair_hands(600)); - all_hands.extend(create_pair_hands(700)); - all_hands.extend(create_high_card_hands(800)); - - // Evaluate all hands - let mut ranks = Vec::new(); - for hand in &all_hands { - let rank = evaluator.rank_hand(hand); - assert!(rank > 0, "All hands should produce valid ranks"); - ranks.push(rank); - } - - // Verify we got expected number of results - assert_eq!( - ranks.len(), - all_hands.len(), - "Should have rank for each hand" - ); - - // Verify all ranks are within valid range - for &rank in &ranks { - assert!(rank <= 7462, "All ranks should be within Java range"); - } - - // Verify hand hierarchy is maintained - let mut sorted_ranks = ranks.clone(); - sorted_ranks.sort(); - assert_eq!( - ranks.len(), - sorted_ranks.len(), - "Sorting should preserve count" - ); - - // Verify no duplicate ranks (should be very unlikely with proper distribution) - let unique_ranks: std::collections::HashSet = ranks.iter().cloned().collect(); - assert!( - unique_ranks.len() > ranks.len() / 2, - "Should have reasonable rank diversity" - ); - - println!("Successfully evaluated {} hands", all_hands.len()); - println!( - "Rank range: {} - {}", - sorted_ranks[0], - sorted_ranks[sorted_ranks.len() - 1] - ); - println!("Unique ranks: {}", unique_ranks.len()); - } - - #[test] - fn test_end_to_end_java_compatibility() { - /// End-to-end test of Java compatibility across the entire pipeline. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test complete pipeline with Java-style inputs and outputs - let java_style_hands = vec![ - // (hand_cards, expected_rank_range) - (vec!["As", "Ks", "Qs", "Js", "Ts"], (1, 100)), // Royal flush - (vec!["9s", "8s", "7s", "6s", "5s"], (1000, 5000)), // Straight flush - (vec!["Ah", "Ad", "Ac", "As", "Kd"], (5000, 10000)), // Four aces - (vec!["As", "Ah", "Ad", "Ks", "Kh"], (15000, 20000)), // Full house - (vec!["As", "Ks", "Qs", "Js", "9s"], (20000, 25000)), // Flush - (vec!["As", "Kd", "Qh", "Jc", "Ts"], (25000, 30000)), // Straight - (vec!["As", "Ah", "Ad", "Ks", "Qs"], (30000, 35000)), // Three of kind - (vec!["As", "Ah", "Ks", "Kd", "Qs"], (35000, 40000)), // Two pair - (vec!["As", "Ah", "Ks", "Qs", "Js"], (40000, 45000)), // Pair - (vec!["As", "Kd", "Qh", "Jc", "9s"], (45000, 50000)), // High card - ]; - - for (cards, (min_rank, max_rank)) in &java_style_hands { - let mut hand = Hand::new(); - for card_str in cards { - hand.add_card(Card::from_string(card_str).unwrap()).unwrap(); - } - - // Test complete pipeline: card creation -> hand creation -> evaluation - let rank = evaluator.rank_hand(&hand); - - assert!( - rank >= *min_rank && rank <= *max_rank, - "Java-style hand {:?} should have rank in range {}-{}, got {}", - cards, - min_rank, - max_rank, - rank - ); - - // Test Java-style key generation as well - let key_rank = evaluator.rank_hand5(&hand); - assert_eq!( - rank, key_rank, - "Java-style and general evaluation should match" - ); - - // Verify card encoding matches Java expectations - for card_str in cards { - let card = Card::from_string(card_str).unwrap(); - assert!( - card.rank() >= 1 && card.rank() <= 13, - "Card rank should be in Java range" - ); - assert!( - card.suit() >= 1 && card.suit() <= 4, - "Card suit should be in Java range" - ); - } - } - } - - #[test] - fn test_system_resource_usage() { - /// Tests that the system uses resources efficiently during extended operation. - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Verify evaluator was created successfully - assert_eq!( - evaluator.hand_ranks.len(), - 32_487_834, - "Evaluator should have correct table size" - ); - - // Test memory estimate - let memory_estimate = evaluator.hand_ranks.len() * std::mem::size_of::(); - println!( - "Estimated memory usage: {} MB", - memory_estimate / (1024 * 1024) - ); - - // Verify table is populated - let populated_entries = evaluator - .hand_ranks - .iter() - .filter(|&&rank| rank > 0) - .count(); - assert!( - populated_entries > 1_000_000, - "Table should have many populated entries" - ); - - // Test that system remains responsive after many evaluations - let test_hands = create_diverse_test_hands(1000); - - let start = std::time::Instant::now(); - for hand in &test_hands { - let _rank = evaluator.rank_hand(hand); - } - let duration = start.elapsed(); - - println!("1000 evaluations took: {:?}", duration); - assert!(duration.as_secs() < 5, "System should remain responsive"); - - // Verify table integrity after heavy usage - let royal_flush_cards = [41, 37, 33, 29, 25]; - let rf_rank = - poker_api::evaluator_generator::state_table_generator::StateTableGenerator::eval_5hand( - royal_flush_cards[0], - royal_flush_cards[1], - royal_flush_cards[2], - royal_flush_cards[3], - royal_flush_cards[4], - ); - assert!( - rf_rank > 0, - "Table should maintain integrity after heavy usage" - ); - } -} - -/// Helper functions for creating test data. -fn create_diverse_test_hands(count: usize) -> Vec { - // Creates a diverse set of test hands for performance testing. - // - // Generates hands of different types and strengths to ensure - // balanced performance characteristics across the evaluation space. - let mut hands = Vec::with_capacity(count); - - // Sample different hand types - let hand_types = vec![ - vec!["As", "Ks", "Qs", "Js", "Ts"], // Royal flush - vec!["As", "Ah", "Ad", "Ac", "Ks"], // Four of a kind - vec!["As", "Ah", "Ad", "Ks", "Kh"], // Full house - vec!["As", "Ks", "Qs", "Js", "9s"], // Straight - vec!["As", "Ks", "Qs", "Js", "Ts"], // Flush (different from royal) - vec!["As", "Ah", "Ad", "Ks", "Qs"], // Three of a kind - vec!["As", "Ah", "Ks", "Kd", "Qs"], // Two pair - vec!["As", "Ah", "Ks", "Qs", "Js"], // One pair - vec!["As", "Ks", "Qs", "Js", "9d"], // High card - ]; - - for i in 0..count { - let hand_type = &hand_types[i % hand_types.len()]; - let mut hand = Hand::new(); - - for &card_str in hand_type { - hand.add_card(Card::from_string(card_str).unwrap()).unwrap(); - } - - hands.push(hand); - } - - hands -} - -fn create_test_hand() -> Hand { - // Creates a simple test hand for basic testing. - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - hand -} - -fn create_royal_flush_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ks").unwrap()).unwrap(); - hand.add_card(Card::from_string("Qs").unwrap()).unwrap(); - hand.add_card(Card::from_string("Js").unwrap()).unwrap(); - hand.add_card(Card::from_string("Ts").unwrap()).unwrap(); - hands.push(hand); - } - hands -} - -fn create_straight_flush_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - let base = 9 - (i % 8); // Vary the straight flush high card - hand.add_card(Card::from_string(&format!("{}s", rank_char(base + 4))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(base + 3))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(base + 2))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(base + 1))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(base))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn create_four_kind_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - let rank = 13 - (i % 11); // Vary the four of a kind rank - hand.add_card(Card::from_string(&format!("{}s", rank_char(rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}d", rank_char(rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}c", rank_char(rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 12 + 1)))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn create_full_house_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - let trip_rank = 13 - (i % 10); - let pair_rank = 13 - ((i + 1) % 10); - hand.add_card(Card::from_string(&format!("{}s", rank_char(trip_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(trip_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}d", rank_char(trip_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(pair_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(pair_rank))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn create_flush_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - let suit = ["s", "h", "d", "c"][i % 4]; - hand.add_card(Card::from_string(&format!("A{}", suit)).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("K{}", suit)).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("Q{}", suit)).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("J{}", suit)).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(9 - (i % 5)))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn create_straight_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - let high_card = 13 - (i % 9); // Vary straight high card - hand.add_card(Card::from_string(&format!("{}s", rank_char(high_card))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(high_card - 1))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}d", rank_char(high_card - 2))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}c", rank_char(high_card - 3))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(high_card - 4))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn create_three_kind_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - let trip_rank = 13 - (i % 11); - hand.add_card(Card::from_string(&format!("{}s", rank_char(trip_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(trip_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}d", rank_char(trip_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 10 + 1)))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(13 - (i % 9 + 2)))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn create_two_pair_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - let high_pair = 13 - (i % 10); - let low_pair = 13 - ((i + 1) % 9); - hand.add_card(Card::from_string(&format!("{}s", rank_char(high_pair))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(high_pair))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(low_pair))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(low_pair))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 8 + 3)))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn create_pair_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - let pair_rank = 13 - (i % 12); - hand.add_card(Card::from_string(&format!("{}s", rank_char(pair_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(pair_rank))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 10 + 1)))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(13 - (i % 9 + 2)))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 8 + 3)))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn create_high_card_hands(count: usize) -> Vec { - let mut hands = Vec::with_capacity(count); - for i in 0..count { - let mut hand = Hand::new(); - hand.add_card(Card::from_string("As").unwrap()).unwrap(); - hand.add_card(Card::from_string(&format!("{}h", rank_char(12 - (i % 11)))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}d", rank_char(11 - (i % 10)))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}c", rank_char(10 - (i % 9)))).unwrap()) - .unwrap(); - hand.add_card(Card::from_string(&format!("{}s", rank_char(9 - (i % 8)))).unwrap()) - .unwrap(); - hands.push(hand); - } - hands -} - -fn rank_char(rank: usize) -> String { - match rank { - 13 => "A".to_string(), - 12 => "K".to_string(), - 11 => "Q".to_string(), - 10 => "J".to_string(), - 9 => "T".to_string(), - 8 => "9".to_string(), - 7 => "8".to_string(), - 6 => "7".to_string(), - 5 => "6".to_string(), - 4 => "5".to_string(), - 3 => "4".to_string(), - 2 => "3".to_string(), - 1 => "2".to_string(), - _ => "2".to_string(), - } -} diff --git a/docs/ACHIEVEMENTS.md b/docs/ACHIEVEMENTS.md new file mode 100644 index 0000000..40b3e3a --- /dev/null +++ b/docs/ACHIEVEMENTS.md @@ -0,0 +1,19 @@ +# Poker AI Testbed & Live Server Achievements + +## Completed Phases and Items + +### Phase 1: Foundation ✅ + +**Basic poker engine** +- Card/Hand/Board representations ✅ + + + +## Success Metrics + +- **Bot Performance:** Profitability across different game types +- **Simulation Speed:** Ability to run millions of hands efficiently +- **Live Reliability:** 99.9% uptime with correct action execution +- **Flexibility:** Easy configuration for new sites and game types +- **Evolution:** Continuous improvement through simulation results +- **Monitoring:** Meaningful debug and performance logging and visualization \ No newline at end of file diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..c97efea --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,99 @@ +# Poker AI Testbed & Live Server Roadmap + +## Step-by-Step Implementation Plan + +### Phase 1: Foundation ✅ + +**Basic poker engine** +- Fast LUT hand evaluator (5, 6, and 7 cards) ✅ +- Game state management ✅ +- Game Info and Player Info traits ✅ +- Event reporting and action scheme ✅ +- Basic game rules (NL/FL/PL) ✅ + +**Configuration system** +- TOML configuration structures ✅ +- Bot parameter configuration ✅ +- Game type configurations (Cash/SNG/MTT, buyins, blinds, hands per level, etc.) ✅ +- Simulation parameters (hands/tournaments count, buyin thresholds for bots, etc.) ✅ + +### Phase 2: Simulation Testbed +**Goal:** Mass simulation capability + +**Testbed core** +- Tournament/cash game runners +- Parallel simulation engine +- Result collection and analysis +- Bot interface with early folding capability +- Bot performance metrics & visualization +- Simulation and game factories +- Configuration-driven experiment runner + +--- + +### Phase 3: Bot Framework +**Goal:** All bot types with playability enhancement + +**Bot core implementations** +- Generic helper functions for strategies +- Modular Strategy per stage and game situation +- Rule-based bot with configurable thresholds – port from Java +- Nash equilibrium calculator for SNG endgame – port from Java +- Statistical tracking bot – port from Java +- Logging of decision-making process + +**Advanced bots** +- GPU acceleration for mass rollouts +- Hand-reading opponent modeling (possibly with GPU integration) +- Flop playability precomputation & score integration +- Basic reinforcement learning skeleton +- Hybrid bot combining multiple approaches +- Factory for bot and sub-strategy composition +- Genetic algorithm for bot evolution (via configuration files) + +--- + +### Phase 4: "Unit Test" Framework +**Goal:** Bot behavior can be tested against predefined hands + +**Hand Replayer** +- Poker Tracker style hands +- Action comparison to expected outcome +- Selection of hands from different sources (books) +- Action likelihoods (multiple replays) + +--- + +### Phase 5: Live Server Infrastructure +**Goal:** Real-time bot deployment + +**Server core** +- Network protocol design (TBD: WebSocket, JSON, MQ, etc.) +- Actions (incl. early fold and table selection), events (incl. table switches and available tables) +- Client registration and management +- Game state synchronization +- Action dispatch system +- Event logging for analysis of problems + +**Client adapter system** +- Client factory for different poker environments +- Screen / protocol scraping adapters +- Table selection logic +- Configuration-driven game selection + +--- + +### Phase 6: Integration & Polish +**Goal:** Production-ready system + +**Advanced features** +- Real-time strategy adaptation +- Multi-table coordination +- Bankroll management +- Risk adjustment based on results + +**Optimization and deployment** +- Performance profiling and optimization +- Error handling and recovery +- Documentation and examples +- Deployment scripts and configuration \ No newline at end of file diff --git a/docs/VISION.md b/docs/VISION.md new file mode 100644 index 0000000..2e30f07 --- /dev/null +++ b/docs/VISION.md @@ -0,0 +1,46 @@ +# Poker AI Testbed & Live Server Vision + +## Overall Goal + +Create a **comprehensive poker AI ecosystem** with two main components: + +- **Massive Simulation Testbed** – For developing and testing bot strategies across all game types (NL/FL/PL, Cash/SNG/MTT) with configurable parameters +- **Live Action Server** – For deploying bots in real poker environments with automatic table selection + +--- + +## Core Architecture Components + +### 1. Simulation Testbed + +- **Configurable bots** (Rule-based, Nash EQ, Statistical, Hand-reading, Reinforcement Learning) +- **Mass parallel simulations** with GPU acceleration (e.g., for turn and river rollouts) +- **TOML-driven configuration** for all parameters of bots and simulations +- **Comprehensive result analysis** and bot evolution + +### 2. Live Action Server + +- **Generic protocol** for client communication +- **Early folding capability** (bots can fold without being polled) +- **Multiple client adapters** for different poker sites +- **Automated table selection** based on game configuration +- **Real-time strategy adaptation** + +### 3. Bot Concepts + +- **Modular Strategy** per stage and game situation – e.g., low stack in a tournament, preflop vs. postflop. + Plugable sub-strategies and decision methods (first applicable, random, most aggressive, least aggressive, etc.) +- **Generic helper functions** for strategies like ICM, ppot, not, hand strength, against one and multiple opponents +- **Incremental, asynchronous rollout** of turn and river on flop and river on turn +- **Flop Playability score** for preflop based on precalculated rollouts for all possible flops and starting hands +- **Opponent hand prediction and statistics**, for instance in Poker Tracker format +- **Real-time strategy adaptation**, e.g., reinforcement learning + +--- + +## Expected Outcomes + +- **Research Platform:** Systematic testing of poker AI strategies +- **Live Deployment:** Bots that can play on real poker sites +- **Strategy Insights:** Understanding what works in different game conditions +- **Bot Evolution:** Self-improving systems through simulation and adaptation \ No newline at end of file diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 4b91a3c..0000000 --- a/docs/api.md +++ /dev/null @@ -1,389 +0,0 @@ -# API Documentation - -## Core Types - -### `LookupHandEvaluator` - -The main hand evaluation engine that uses precomputed lookup tables for O(1) hand evaluation. - -#### Constructor - -```rust -pub fn new() -> io::Result -``` - -Creates a new evaluator instance. On first use, generates evaluation tables if they don't exist. - -**Returns:** -- `Ok(LookupHandEvaluator)` - Successfully initialized evaluator -- `Err(io::Error)` - Failed to load or generate tables - -#### Methods - -##### `rank_hand(&self, hand: &Hand) -> u32` - -Evaluates any poker hand (5, 6, or 7 cards) and returns its rank value. - -```rust -let rank = evaluator.rank_hand(&hand); -``` - -**Parameters:** -- `hand: &Hand` - Poker hand containing 5-7 cards - -**Returns:** -- `u32` - Hand rank (higher values = stronger hands) - -##### `rank_hand5(&self, hand: &Hand) -> u32` - -Optimized evaluation for exactly 5-card hands. - -```rust -let rank = evaluator.rank_hand5(&hand); -``` - -**Parameters:** -- `hand: &Hand` - Poker hand containing exactly 5 cards - -**Returns:** -- `u32` - Hand rank value - -##### `rank_hand6(&self, cards: &[u32]) -> u32` - -Evaluates a 6-card hand by finding the best 5-card combination. - -```rust -let rank = evaluator.rank_hand6(&card_indices); -``` - -**Parameters:** -- `cards: &[u32]` - Array of 6 card indices - -**Returns:** -- `u32` - Best possible 5-card hand rank - -##### `rank_hand7(&self, cards: &[u32]) -> u32` - -Evaluates a 7-card hand by finding the best 5-card combination. - -```rust -let rank = evaluator.rank_hand7(&card_indices); -``` - -**Parameters:** -- `cards: &[u32]` - Array of 7 card indices - -**Returns:** -- `u32` - Best possible 5-card hand rank - -### `HandEval` Trait - -Standardized interface for poker hand evaluators. - -#### Constants - -```rust -const HAND_CATEGORY_SHIFT: u32 = 24; -const HAND_CATEGORY_MASK: u32 = 0xF << Self::HAND_CATEGORY_SHIFT; -const VALUE_MASK: u32 = 0x000FFFFF; -``` - -#### Methods - -##### `rank_hand(&self, hand: &Hand) -> u32` - -Evaluates a poker hand of any supported size. - -##### `rank_hand7(&self, cards: &[Card; 7]) -> u32` - -Evaluates exactly 7 cards. - -##### `hand_description(&self, hand_value: u32) -> String` - -Returns human-readable description of a hand rank. - -##### `hand_description_hand(&self, hand: &Hand) -> String` - -Describes a hand by evaluating it first. - -##### `hand_description7(&self, cards: &[Card; 7]) -> String` - -Describes the best 5-card hand from 7 cards. - -## Supporting Types - -### `Hand` - -Collection of cards that can be evaluated. - -#### Key Methods - -```rust -// Construction -let mut hand = Hand::new(); -hand.add_card(card).unwrap(); - -// Information -let size = hand.size(); -let is_valid = hand.size() >= 5; - -// String representation -println!("{}", hand.to_string()); // "As Ks Qs Js Ts" -``` - -### `Card` - -Individual playing card representation. - -#### Construction - -```rust -// From string notation -let card = Card::from_string("As").unwrap(); -let card = Card::from_string("Kh").unwrap(); - -// String representation -assert_eq!(card.to_string(), "As"); -``` - -#### Properties - -```rust -let rank = card.rank(); // Card rank (0-12) -let suit = card.suit(); // Card suit (0-3) -let index = card.index(); // Unique card index (0-51) -``` - -## Hand Ranking Values - -### Category Extraction - -```rust -// Extract hand category (bits 24-27) -let category = (hand_value & HandEval::HAND_CATEGORY_MASK) >> HandEval::HAND_CATEGORY_SHIFT; - -// Extract relative value (bits 0-23) -let value = hand_value & HandEval::VALUE_MASK; -``` - -### Hand Categories - -| Category | Value | Description | -|----------|-------|-------------| -| Straight Flush | 9 | Five cards in sequence, same suit | -| Four of a Kind | 8 | Four cards of same rank | -| Full House | 7 | Three of a kind + pair | -| Flush | 6 | Five cards, same suit | -| Straight | 5 | Five cards in sequence | -| Three of a Kind | 4 | Three cards of same rank | -| Two Pair | 3 | Two different pairs | -| One Pair | 2 | Two cards of same rank | -| High Card | 1 | No matching cards | - -### Rank Value Ranges - -| Hand Type | Rank Range | Count | -|-----------|------------|-------| -| Straight Flush | 368,793,253 - 371,292,191 | 40 | -| Four of a Kind | 184,396,629 - 368,793,252 | 624 | -| Full House | 92,198,315 - 184,396,628 | 3,744 | -| Flush | 46,099,158 - 92,198,314 | 5,108 | -| Straight | 23,049,579 - 46,099,157 | 10,200 | -| Three of a Kind | 11,524,790 - 23,049,578 | 54,912 | -| Two Pair | 5,762,395 - 11,524,789 | 123,552 | -| One Pair | 2,881,198 - 5,762,394 | 1,098,240 | -| High Card | 1 - 2,881,197 | 1,302,540 | - -## Performance Characteristics - -### Time Complexity - -| Operation | Time Complexity | Notes | -|-----------|----------------|-------| -| 5-Card evaluation | O(1) | Direct table lookup | -| 6-Card evaluation | O(1) | 6 table lookups | -| 7-Card evaluation | O(1) | 21 table lookups | -| Table generation | O(N) | N = 32M combinations | - -### Memory Usage - -| Component | Size | Description | -|-----------|------|-------------| -| Hand ranks table | ~128 MB | Complete evaluation table | -| Working tables | ~2.4 MB | Generation workspace | -| Code size | ~50 KB | Compiled binary | - -### Cache Performance - -The lookup table design provides excellent cache performance: - -- **Sequential Access**: Table generation processes data sequentially -- **Compact Encoding**: Cards encoded in 8 bits each -- **Prefetch Friendly**: Linear table layout enables hardware prefetching -- **Low Branch Misprediction**: Perfect hash eliminates conditional logic - -## Error Handling - -### Common Error Cases - -```rust -// Invalid hand size -let rank = evaluator.rank_hand(&hand); // Returns 0 for < 5 cards - -// Card parsing errors -let card = Card::from_string("XX"); // Returns Err - -// Table loading errors -let evaluator = LookupHandEvaluator::new(); // May fail if disk full -``` - -### Error Types - -- `io::Error` - File system errors during table loading/generation -- `CardParseError` - Invalid card string format -- `HandFullError` - Attempting to add cards to full hand - -## Thread Safety - -The `LookupHandEvaluator` is designed for read-only usage after construction: - -```rust -// Safe: Read-only evaluator -let evaluator = LookupHandEvaluator::new().unwrap(); - -// Multiple threads can use the same evaluator -let rank1 = evaluator.rank_hand(&hand1); -let rank2 = evaluator.rank_hand(&hand2); - -// NOT safe: Modifying evaluator during use -// (evaluator is not Sync + Send for mutation) -``` - -## Integration Examples - -### Texas Hold'em Engine - -```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::{hand::Hand, card::Card}; - -struct HoldemEngine { - evaluator: LookupHandEvaluator, -} - -impl HoldemEngine { - fn evaluate_board(&self, hole_cards: &[Card; 2], board: &[Card; 5]) -> u32 { - let mut all_cards = Vec::new(); - all_cards.extend_from_slice(hole_cards); - all_cards.extend_from_slice(board); - - let mut hand = Hand::new(); - for card in all_cards { - hand.add_card(card).unwrap(); - } - - self.evaluator.rank_hand(&hand) - } -} -``` - -### Hand Range Analysis - -```rust -use poker_api::hand_evaluator::LookupHandEvaluator; - -struct RangeAnalyzer { - evaluator: LookupHandEvaluator, -} - -impl RangeAnalyzer { - fn compare_hands(&self, hand1: &Hand, hand2: &Hand) -> std::cmp::Ordering { - let rank1 = self.evaluator.rank_hand(hand1); - let rank2 = self.evaluator.rank_hand(hand2); - rank1.cmp(&rank2) - } -} -``` - -## Testing Integration - -### Unit Tests - -```rust -#[cfg(test)] -mod tests { - use super::*; - use poker_api::hand_evaluator::LookupHandEvaluator; - - #[test] - fn test_royal_flush() { - let evaluator = LookupHandEvaluator::new().unwrap(); - let mut hand = Hand::new(); - // Add A,K,Q,J,10 of same suit - let rank = evaluator.rank_hand(&hand); - assert!(rank > 368_793_253); // Royal flush range - } -} -``` - -### Benchmark Tests - -```rust -#[cfg(test)] -mod benchmarks { - use super::*; - use std::time::Instant; - - #[test] - fn benchmark_evaluation() { - let evaluator = LookupHandEvaluator::new().unwrap(); - let hand = create_test_hand(); - - let start = Instant::now(); - for _ in 0..1_000_000 { - let _rank = evaluator.rank_hand(&hand); - } - let duration = start.elapsed(); - - println!("1M evaluations in {:?}", duration); - } -} -``` - -## Advanced Configuration - -### Custom Table Paths - -The evaluator uses default paths, but this can be customized by modifying the source: - -```rust -// In hand_evaluator.rs -const FILE_NAME: &'static str = "custom/path/HandRanks.dat"; -``` - -### Memory Management - -For systems with limited memory, consider: - -```rust -// Use Box<[u32]> for heap allocation control -let hand_ranks: Box<[u32]> = vec![0; SIZE].into_boxed_slice(); - -// Monitor memory usage -println!("Table memory usage: {} MB", - std::mem::size_of_val(&*hand_ranks) / 1_048_576); -``` - -### Performance Tuning - -For maximum performance: - -```rust -// Use release mode -cargo build --release - -// Consider SIMD optimizations for batch evaluation -// (requires custom implementation) -``` - -This API documentation covers the complete public interface for poker hand evaluation. For implementation details, see the source code comments and the developer guide. \ No newline at end of file diff --git a/docs/developer_guide.md b/docs/developer_guide.md deleted file mode 100644 index e54e15e..0000000 --- a/docs/developer_guide.md +++ /dev/null @@ -1,484 +0,0 @@ -# Developer Guide - -## Architecture Overview - -The Poker Hand Evaluator implements the **Meerkat algorithm** for perfect hash-based poker hand evaluation. This section covers the internal architecture, algorithms, and design decisions. - -## System Architecture - -### Core Components - -``` -┌─────────────────────────────────────────────────────────┐ -│ LookupHandEvaluator │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ Hand Ranking Table │ │ -│ │ (32M+ entries) │ │ -│ └─────────────────────────────────────────────────┘ │ -└─────────────────────┬───────────────────────────────────┘ - │ Uses - ▼ -┌─────────────────────────────────────────────────────────┐ -│ StateTableGenerator │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ Algorithm Modules │ │ -│ │ • Flushes • Products • Unique • Values │ │ -│ └─────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ -``` - -### Data Flow - -1. **Table Generation** (one-time): - ``` - Card Combinations → Perfect Hash → Hand Evaluation → Rank Storage - ``` - -2. **Hand Evaluation** (runtime): - ``` - Input Hand → Perfect Hash → Table Lookup → Rank Value - ``` - -## Algorithm Details - -### Perfect Hash Function - -The core innovation is a perfect hash function that maps every possible card combination to a unique index: - -```rust -fn perfect_hash(cards: &[u32]) -> usize { - let mut hash = 53u32; // Initial value - - for &card in cards { - let index = card.index(); - hash = TABLE[(hash + index + 1) as usize] as u8; - } - - hash as usize -} -``` - -**Key Properties:** -- **Perfect**: No collisions for valid inputs -- **Minimal**: Uses minimal memory for given input size -- **Fast**: O(1) computation time -- **Deterministic**: Same input always produces same output - -### Card Encoding - -Each card is encoded using multiple techniques: - -```rust -struct EncodedCard { - prime: u32, // Prime number for rank (2, 3, 5, 7, 11, ...) - rank_bits: u32, // Bit position for rank identification - suit_bits: u32, // Bit mask for suit detection - meta_bits: u32, // Additional metadata bits -} -``` - -**Prime Encoding:** -```rust -const PRIMES: [u32; 13] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]; -let prime = PRIMES[rank]; // Unique prime for each rank -``` - -### Hand Evaluation Strategy - -#### 5-Card Hands - -Direct evaluation using specialized lookup tables: - -```rust -fn evaluate_5card(cards: [u32; 5]) -> u32 { - // 1. Check for flush - if is_flush(cards) { - return FLUSH_TABLE[flush_index(cards)]; - } - - // 2. Check for unique hands (straight flush, quads, etc.) - let unique_index = unique_hash(cards); - if let Some(rank) = UNIQUE_TABLE.get(unique_index) { - return rank; - } - - // 3. Standard hand evaluation - let product_index = prime_product(cards); - VALUES_TABLE[product_index] -} -``` - -#### 6-7 Card Hands - -Brute force evaluation of all 5-card combinations: - -```rust -fn evaluate_7card(cards: [u32; 7]) -> u32 { - let mut best_rank = 0; - - // Generate all C(7,5) = 21 combinations - for combination in combinations(cards, 5) { - let rank = evaluate_5card(combination); - best_rank = best_rank.max(rank); - } - - best_rank -} -``` - -## Implementation Details - -### Table Generation Process - -#### Phase 1: Card Combination Enumeration - -```rust -const TABLE_SIZE: usize = 612_978; // All possible combinations - -for key in 0..TABLE_SIZE { - let cards = decode_key_to_cards(key); - - // Validate card combination - if is_valid_combination(cards) { - let rank = evaluate_hand(cards); - table[key] = rank; - } -} -``` - -#### Phase 2: Perfect Hash Construction - -The perfect hash is constructed by: - -1. **Initial Mapping**: Map card combinations to initial hash values -2. **Conflict Resolution**: Resolve collisions using secondary tables -3. **Optimization**: Minimize table size while maintaining perfection - -#### Phase 3: Table Serialization - -```rust -fn save_tables(table: &[u32]) -> Result<(), io::Error> { - let file = File::create("math/HandRanks.dat")?; - let mut writer = BufWriter::new(file); - - for &rank in table { - writer.write_all(&rank.to_ne_bytes())?; - } - - Ok(()) -} -``` - -### Memory Layout - -#### Runtime Table Structure - -``` -┌─────────────────────────────────────────────────────────┐ -│ HandRanks.dat │ -├─────────────────────────────────────────────────────────┤ -│ Entry 0: [u32] Rank for combination 0 │ -│ Entry 1: [u32] Rank for combination 1 │ -│ ... │ -│ Entry N: [u32] Rank for combination N │ -└─────────────────────────────────────────────────────────┘ - ↑ - │ - 32,487,834 entries × 4 bytes = 129,951,336 bytes -``` - -#### Generation Working Tables - -```rust -struct WorkingTables { - // Main ranking table during generation - hand_ranks: Box<[u32; 612_978]>, - - // Specialized evaluation tables - flushes: FlushesTable, - products: ProductsTable, - unique: UniqueTable, - values: ValuesTable, -} -``` - -## Performance Optimizations - -### SIMD Opportunities - -Current implementation is scalar, but offers SIMD optimization potential: - -```rust -// Potential SIMD optimization (future enhancement) -#[cfg(target_feature = "avx2")] -fn evaluate_hands_simd(hands: &[Hand]) -> Vec { - // Process multiple hands in parallel using AVX instructions - // Each hand evaluation becomes a vector operation -} -``` - -### Memory Access Patterns - -The current design optimizes for: - -- **Sequential Generation**: Tables generated in linear order -- **Random Access Evaluation**: O(1) lookup for any hand -- **Cache Efficiency**: Compact data structures for better cache utilization - -### Algorithmic Optimizations - -#### Hand-Specific Algorithms - -Different hand types use specialized evaluation methods: - -```rust -impl HandEvaluator { - fn evaluate_straight_flush(cards: [u32; 5]) -> u32 { - // Optimized straight flush detection - let ranks = extract_ranks(cards); - if is_consecutive(ranks) && is_same_suit(cards) { - calculate_straight_flush_rank(ranks[0]) - } else { - 0 // Not a straight flush - } - } - - fn evaluate_four_of_a_kind(cards: [u32; 5]) -> u32 { - // Optimized quad detection using prime products - let product = calculate_prime_product(cards); - QUAD_TABLE[product] - } -} -``` - -## Testing Strategy - -### Unit Tests - -```rust -#[cfg(test)] -mod unit_tests { - use super::*; - - #[test] - fn test_royal_flush() { - let hand = create_royal_flush(); - let rank = evaluator.rank_hand(&hand); - assert!(is_royal_flush_rank(rank)); - } - - #[test] - fn test_perfect_hash() { - // Verify no hash collisions - let mut seen = HashSet::new(); - for combination in all_possible_combinations() { - let hash = perfect_hash(combination); - assert!(!seen.contains(&hash)); - seen.insert(hash); - } - } -} -``` - -### Integration Tests - -```rust -#[cfg(test)] -mod integration_tests { - use super::*; - - #[test] - fn test_vs_reference_implementation() { - let evaluator = LookupHandEvaluator::new().unwrap(); - - // Test against known reference implementation - for _ in 0..1_000_000 { - let hand = generate_random_hand(); - let our_rank = evaluator.rank_hand(&hand); - let ref_rank = reference_evaluate(&hand); - - assert_eq!(our_rank, ref_rank); - } - } -} -``` - -### Performance Benchmarks - -```rust -#[cfg(test)] -mod benchmarks { - use super::*; - use std::time::Instant; - - #[test] - fn benchmark_hand_evaluation() { - let evaluator = LookupHandEvaluator::new().unwrap(); - let hands = generate_test_hands(1_000_000); - - let start = Instant::now(); - for hand in hands { - let _rank = evaluator.rank_hand(&hand); - } - let duration = start.elapsed(); - - println!("1M evaluations in {:?}", duration); - println!("Average: {:?} per hand", duration / 1_000_000); - } -} -``` - -## Development Workflow - -### Code Organization - -``` -src/ -├── api/ # Public API definitions -│ ├── hand_eval.rs # HandEval trait -│ ├── card.rs # Card struct -│ └── hand.rs # Hand struct -├── hand_evaluator.rs # Main evaluator implementation -├── evaluator_generator/ # Table generation algorithms -│ ├── mod.rs -│ ├── state_table_generator.rs -│ ├── flushes.rs -│ ├── products.rs -│ ├── unique.rs -│ └── values.rs -└── lib.rs # Library root -``` - -### Adding New Features - -#### New Hand Type Support - -1. **Define evaluation algorithm** in `evaluator_generator/` -2. **Add lookup tables** for the new hand type -3. **Update `eval_5hand`** to use new tables -4. **Add tests** for the new hand type -5. **Update documentation** - -#### Performance Improvements - -1. **Profile** current performance -2. **Identify bottlenecks** (CPU, memory, cache) -3. **Implement optimization** with benchmarks -4. **Verify correctness** with existing tests -5. **Update performance documentation** - -### Debugging and Development Tools - -#### Debug Table Generation - -```rust -fn debug_table_generation() { - let mut generator = StateTableGenerator::new(); - - // Enable debug output - generator.set_debug_mode(true); - - // Generate with detailed logging - generator.generate_tables(); - - // Inspect specific entries - println!("Entry 12345: {}", generator.hand_ranks[12345]); -} -``` - -#### Hand Evaluation Tracing - -```rust -fn trace_hand_evaluation(hand: &Hand) -> EvaluationTrace { - let mut trace = EvaluationTrace::new(); - - // Step through evaluation with logging - trace.log("Starting evaluation"); - let rank = evaluator.rank_hand_traced(&hand, &mut trace); - trace.log(&format!("Final rank: {}", rank)); - - trace -} -``` - -## Mathematical Background - -### Combinatorics - -#### Card Combination Space - -| Cards | Combinations | Calculation | -|-------|--------------|-------------| -| 5-Card | 2,598,960 | C(52,5) | -| 6-Card | 20,358,520 | C(52,6) | -| 7-Card | 133,784,560 | C(52,7) | - -#### Table Size Calculation - -``` -Working Table: C(52,5) + C(52,6) + C(52,7) = 156,741,540 entries -Final Table: 32,487,834 unique combinations (after deduplication) -``` - -### Hash Function Design - -The perfect hash function uses: - -1. **Initial Value**: 53 (empirically chosen) -2. **Recursive Application**: Each card updates the hash -3. **Table Lookup**: Final mapping through ranking table - -```rust -fn perfect_hash(cards: &[u32]) -> u32 { - let mut hash = 53; - - for &card in cards { - let card_index = card.index(); - hash = TABLE[(hash + card_index + 1) as usize] as u8; - } - - hash as u32 -} -``` - -## Future Enhancements - -### Potential Improvements - -1. **SIMD Optimization**: Vectorize hand evaluation for batch processing -2. **Memory Mapping**: Use memory-mapped files for table loading -3. **Compression**: Reduce table size using compression techniques -4. **Hardware Acceleration**: GPU-accelerated evaluation for massive scale -5. **Distributed Generation**: Parallel table generation across multiple machines - -### Research Directions - -1. **Alternative Hash Functions**: Explore more efficient perfect hash constructions -2. **Machine Learning**: Use ML to optimize evaluation algorithms -3. **Hardware Design**: Custom ASIC for poker hand evaluation -4. **Algorithmic Improvements**: Reduce computational complexity further - -## Contributing - -### Development Setup - -1. **Clone repository** -2. **Install Rust**: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` -3. **Run tests**: `cargo test` -4. **Generate documentation**: `cargo doc --open` - -### Code Style - -- Use `rustfmt` for consistent formatting -- Add comprehensive documentation comments -- Include unit tests for new functionality -- Follow existing error handling patterns - -### Performance Guidelines - -- Profile before optimizing -- Maintain algorithm correctness -- Add benchmarks for performance regressions -- Document performance characteristics - -This developer guide provides the technical foundation for understanding and extending the poker hand evaluation system. For API usage, see the user guide. For detailed reference, see the API documentation. \ No newline at end of file diff --git a/docs/user_guide.md b/docs/user_guide.md deleted file mode 100644 index b80f932..0000000 --- a/docs/user_guide.md +++ /dev/null @@ -1,441 +0,0 @@ -# User Guide - -## Introduction - -This guide explains how to use the Poker Hand Evaluator library for evaluating poker hands in your applications. Whether you're building a poker game, analysis tool, or AI bot, this library provides the fast and accurate hand evaluation you need. - -## Quick Start - -### Basic Setup - -1. **Add dependency** to your `Cargo.toml`: -```toml -[dependencies] -poker-api = "0.1.0" -``` - -2. **Import and use** in your code: -```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::hand::Hand; - -fn main() -> Result<(), Box> { - // Create the evaluator (generates tables on first run) - let evaluator = LookupHandEvaluator::new()?; - - // Create a poker hand - let mut hand = Hand::new(); - // ... add cards - - // Evaluate the hand - let rank = evaluator.rank_hand(&hand); - println!("Hand strength: {}", rank); - - Ok(()) -} -``` - -### First Run Experience - -On the first run, you'll see: -``` -Evaluation tables do not exist, generating them... -Generating state table... -Hand evaluation tables saved to math/HandRanks.dat -Evaluation tables loaded. -``` - -This generates the lookup tables (~1-2 seconds) and saves them for future use. - -## Working with Cards - -### Card Creation - -Cards can be created from string notation: - -```rust -use poker_api::api::card::Card; - -// Face cards -let ace_spades = Card::from_string("As")?; -let king_hearts = Card::from_string("Kh")?; - -// Number cards -let five_clubs = Card::from_string("5c")?; -let ten_diamonds = Card::from_string("Td")?; - -// All suits: s (spades), h (hearts), d (diamonds), c (clubs) -// All ranks: A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, 2 -``` - -### Card Properties - -```rust -let card = Card::from_string("As")?; - -println!("Rank: {}", card.rank()); // 12 (Ace) -println!("Suit: {}", card.suit()); // 0 (Spades) -println!("Index: {}", card.index()); // 51 (unique card ID) -println!("String: {}", card.to_string()); // "As" -``` - -## Working with Hands - -### Creating Hands - -```rust -use poker_api::api::hand::Hand; -use poker_api::api::card::Card; - -// Create empty hand -let mut hand = Hand::new(); - -// Add cards one by one -hand.add_card(Card::from_string("As")?).unwrap(); -hand.add_card(Card::from_string("Ks")?).unwrap(); -hand.add_card(Card::from_string("Qs")?).unwrap(); -hand.add_card(Card::from_string("Js")?).unwrap(); -hand.add_card(Card::from_string("Ts")?).unwrap(); - -println!("Hand size: {}", hand.size()); // 5 -println!("Hand: {}", hand.to_string()); // "As Ks Qs Js Ts" -``` - -### Hand Evaluation - -```rust -// 5-card hand evaluation -let rank = evaluator.rank_hand(&hand); -println!("Royal flush rank: {}", rank); - -// 6-card hand (finds best 5-card combination) -let mut six_card_hand = Hand::new(); -six_card_hand.add_card(Card::from_string("As")?).unwrap(); -six_card_hand.add_card(Card::from_string("Ks")?).unwrap(); -// ... add 4 more cards - -let rank = evaluator.rank_hand(&six_card_hand); - -// 7-card hand (Texas Hold'em) -let mut seven_card_hand = Hand::new(); -// ... add 7 cards -let rank = evaluator.rank_hand(&seven_card_hand); -``` - -## Understanding Hand Ranks - -### Rank Values - -Higher rank values indicate stronger hands: - -```rust -// Compare hand strengths -let hand1_rank = evaluator.rank_hand(&hand1); -let hand2_rank = evaluator.rank_hand(&hand2); - -if hand1_rank > hand2_rank { - println!("Hand 1 is stronger"); -} else if hand1_rank < hand2_rank { - println!("Hand 2 is stronger"); -} else { - println!("Hands are equal"); -} -``` - -### Hand Categories - -Extract hand category from rank: - -```rust -use poker_api::api::hand_eval::HandEval; - -fn get_hand_category(rank: u32) -> u32 { - (rank & HandEval::HAND_CATEGORY_MASK) >> HandEval::HAND_CATEGORY_SHIFT -} - -let category = get_hand_category(rank); -match category { - 9 => println!("Straight Flush"), - 8 => println!("Four of a Kind"), - 7 => println!("Full House"), - 6 => println!("Flush"), - 5 => println!("Straight"), - 4 => println!("Three of a Kind"), - 3 => println!("Two Pair"), - 2 => println!("One Pair"), - 1 => println!("High Card"), - _ => println!("Unknown"), -} -``` - -## Texas Hold'em Example - -Here's a complete example of evaluating Texas Hold'em hands: - -```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::hand::Hand; -use poker_api::api::card::Card; - -struct PokerGame { - evaluator: LookupHandEvaluator, -} - -impl PokerGame { - fn new() -> Result> { - Ok(Self { - evaluator: LookupHandEvaluator::new()?, - }) - } - - fn evaluate_texas_holdem( - &self, - hole_cards: &[Card; 2], - board: &[Card; 5] - ) -> u32 { - let mut hand = Hand::new(); - - // Add hole cards - hand.add_card(hole_cards[0]).unwrap(); - hand.add_card(hole_cards[1]).unwrap(); - - // Add community cards - for &card in board { - hand.add_card(card).unwrap(); - } - - self.evaluator.rank_hand(&hand) - } -} - -fn main() -> Result<(), Box> { - let game = PokerGame::new()?; - - // Example: Royal Flush - let hole_cards = [ - Card::from_string("As").unwrap(), - Card::from_string("Ks").unwrap(), - ]; - - let board = [ - Card::from_string("Qs").unwrap(), - Card::from_string("Js").unwrap(), - Card::from_string("Ts").unwrap(), - Card::from_string("2d").unwrap(), - Card::from_string("3c").unwrap(), - ]; - - let rank = game.evaluate_texas_holdem(&hole_cards, &board); - println!("Royal flush rank: {}", rank); - - Ok(()) -} -``` - -## Performance Optimization - -### Table Preloading - -For applications that need immediate response: - -```rust -// Warm up the evaluator before use -fn preload_evaluator() -> LookupHandEvaluator { - println!("Loading poker evaluator..."); - let start = std::time::Instant::now(); - - let evaluator = LookupHandEvaluator::new().unwrap(); - - let duration = start.elapsed(); - println!("Evaluator ready in {:?}", duration); - - evaluator -} -``` - -### Batch Evaluation - -For evaluating many hands: - -```rust -fn evaluate_multiple_hands(evaluator: &LookupHandEvaluator, hands: &[Hand]) -> Vec { - hands.iter() - .map(|hand| evaluator.rank_hand(hand)) - .collect() -} -``` - -## Error Handling - -### Common Errors - -```rust -use poker_api::api::card::Card; - -// Invalid card strings -let invalid = Card::from_string("XX"); // Err -let invalid = Card::from_string("53"); // Err - -// Hand capacity exceeded -let mut hand = Hand::new(); -// Adding more than 7 cards will fail - -// Table loading failures -let evaluator = LookupHandEvaluator::new(); -// May fail if disk is full or permissions are insufficient -``` - -### Proper Error Handling - -```rust -use std::io; - -fn create_evaluator() -> Result { - match LookupHandEvaluator::new() { - Ok(evaluator) => { - println!("Evaluator created successfully"); - Ok(evaluator) - } - Err(e) => { - eprintln!("Failed to create evaluator: {}", e); - Err(e) - } - } -} -``` - -## Best Practices - -### 1. Reuse Evaluators - -```rust -// Good: Reuse the same evaluator -struct GameEngine { - evaluator: LookupHandEvaluator, -} - -impl GameEngine { - fn new() -> Result { - Ok(Self { - evaluator: LookupHandEvaluator::new()?, - }) - } - - fn evaluate_hand(&self, hand: &Hand) -> u32 { - self.evaluator.rank_hand(hand) - } -} -``` - -### 2. Validate Input Data - -```rust -fn safe_evaluate_hand(evaluator: &LookupHandEvaluator, hand: &Hand) -> Option { - if hand.size() >= 5 && hand.size() <= 7 { - Some(evaluator.rank_hand(hand)) - } else { - None - } -} -``` - -### 3. Handle Edge Cases - -```rust -// Check for invalid hands -let rank = evaluator.rank_hand(&hand); -if rank == 0 { - println!("Invalid hand: must have 5-7 cards"); -} -``` - -## Integration Examples - -### Poker Bot - -```rust -use poker_api::hand_evaluator::LookupHandEvaluator; - -struct PokerBot { - evaluator: LookupHandEvaluator, -} - -impl PokerBot { - fn should_raise(&self, my_hand: &Hand, pot_odds: f32) -> bool { - let hand_strength = self.evaluator.rank_hand(my_hand); - - // Simple strategy: raise with strong hands - match hand_strength { - rank if rank > 300_000_000 => true, // Very strong - rank if rank > 100_000_000 => pot_odds > 0.1, // Medium strength - _ => false, // Weak hands - } - } -} -``` - -### Hand Range Analysis - -```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::hand::Hand; - -struct RangeAnalyzer { - evaluator: LookupHandEvaluator, -} - -impl RangeAnalyzer { - fn analyze_range(&self, hands: &[Hand]) -> HandStrengthStats { - let ranks: Vec = hands.iter() - .map(|hand| self.evaluator.rank_hand(hand)) - .collect(); - - HandStrengthStats { - average_rank: ranks.iter().sum::() / ranks.len() as u32, - median_rank: self.calculate_median(&ranks), - strongest_hand: ranks.iter().max().copied().unwrap_or(0), - weakest_hand: ranks.iter().min().copied().unwrap_or(0), - } - } -} -``` - -## Troubleshooting - -### Common Issues - -**Problem**: "Evaluation tables do not exist, generating them..." every time - -**Solution**: Ensure write permissions in the current directory, or specify a custom path - -**Problem**: Slow first startup - -**Solution**: Expected behavior - table generation takes 1-2 seconds - -**Problem**: High memory usage - -**Solution**: The 128MB lookup table is required for performance. Consider if this fits your deployment constraints - -**Problem**: Card parsing fails - -**Solution**: Verify card strings use correct format: rank + suit (e.g., "As", "Kh", "Td") - -### Performance Issues - -If evaluation seems slow: - -1. **Check build mode**: Use `cargo build --release` -2. **Verify table loading**: Ensure tables are generated and cached -3. **Monitor memory**: Ensure sufficient RAM for table storage -4. **Consider batching**: Evaluate multiple hands together when possible - -## Support - -For additional help: - -1. Check the [API Documentation](api.md) for detailed reference -2. Review test files for usage examples -3. Open issues on GitHub for bugs or feature requests -4. Study the source code comments for implementation details - -This user guide covers the most common use cases. For advanced usage or customization, see the developer guide and source code documentation. \ No newline at end of file diff --git a/holdem_core/Cargo.toml b/holdem_core/Cargo.toml new file mode 100644 index 0000000..0b19ddb --- /dev/null +++ b/holdem_core/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "holdem_core" +version = "0.1.0" +edition = "2021" +authors = ["Jan-Peter v. Hunnius"] +description = "Poker library for bot testing frameworks" +readme = "README.md" +license = "MIT" +repository = "https://github.com/jhunnius/rusty_marvin" +documentation = "https://docs.rs/holdem_core" +homepage = "https://github.com/your-org/holdem_core" +keywords = ["poker", "bot-testing", "texas-holdem", "testbed"] +categories = ["games", "api-bindings", "mathematics"] +exclude = ["docs/*", "target/*", "*.md"] + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +rand = "0.9.0" +toml = "0.9.8" + +[dev-dependencies] +proptest = "1.0" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/core/README.md b/holdem_core/README.md similarity index 90% rename from core/README.md rename to holdem_core/README.md index 6f7f0cd..a060178 100644 --- a/core/README.md +++ b/holdem_core/README.md @@ -4,6 +4,12 @@ The Poker API Core Library (`poker-api`) is a high-performance poker hand evaluation library based on the Meerkat API algorithm. This library provides lightning-fast poker hand strength evaluation using precomputed lookup tables and perfect hashing. +## Recent Updates + +- **Input Validation**: Added comprehensive validation to `Card::new()` ensuring rank (0-12) and suit (0-3) are within valid ranges +- **Enhanced Documentation**: All public methods now include working code examples and panic condition documentation +- **Error Handling**: Improved error handling throughout the codebase with proper `Result` types + ## Features - **Blazing Fast**: O(1) hand evaluation using perfect hash lookup tables @@ -91,9 +97,9 @@ Hand strength is calculated using: ### Basic Hand Evaluation ```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::hand::Hand; -use poker_api::api::card::Card; +use holdem_core::hand_evaluator::LookupHandEvaluator; +use holdem_core::api::hand::Hand; +use holdem_core::api::card::Card; // Create evaluator (generates tables on first use) let evaluator = LookupHandEvaluator::new().unwrap(); @@ -114,8 +120,8 @@ println!("Hand rank: {}", rank); ### 7-Card Hand Evaluation (Texas Hold'em) ```rust -use poker_api::hand_evaluator::LookupHandEvaluator; -use poker_api::api::card::Card; +use holdem_core::hand_evaluator::LookupHandEvaluator; +use holdem_core::api::card::Card; let evaluator = LookupHandEvaluator::new().unwrap(); @@ -137,7 +143,7 @@ println!("Best 5-card hand rank: {}", rank); ### Custom Table Generation ```rust -use poker_api::evaluator_generator::state_table_generator::StateTableGenerator; +use holdem_core::evaluator_generator::state_table_generator::StateTableGenerator; let mut generator = StateTableGenerator::new(); generator.generate_tables(); // ~1-2 seconds diff --git a/holdem_core/src/board.rs b/holdem_core/src/board.rs new file mode 100644 index 0000000..7467000 --- /dev/null +++ b/holdem_core/src/board.rs @@ -0,0 +1,1383 @@ +//! # Board Module +//! +//! This module provides the `Board` struct for representing community cards +//! in Texas Hold'em poker. The Board manages the progression through betting +//! streets (flop, turn, river) and ensures valid card sequencing. +//! +//! ## Board Representation +//! +//! A Board consists of up to 5 community cards revealed progressively: +//! - **Flop**: 3 cards revealed simultaneously +//! - **Turn**: 1 additional card (4th total) +//! - **River**: 1 additional card (5th total) +//! +//! ## Examples +//! +//! ### Creating and Progressing a Board +//! +//! ```rust +//! use holdem_core::{Board, Card}; +//! use std::str::FromStr; +//! +//! let mut board = Board::new(); +//! +//! // Deal flop +//! let flop_cards = vec![ +//! Card::from_str("As").unwrap(), +//! Card::from_str("Kd").unwrap(), +//! Card::from_str("Qh").unwrap(), +//! ]; +//! board.deal_flop(flop_cards).unwrap(); +//! +//! // Deal turn +//! let turn_card = Card::from_str("Jc").unwrap(); +//! board.deal_turn(turn_card).unwrap(); +//! +//! // Deal river +//! let river_card = Card::from_str("Ts").unwrap(); +//! board.deal_river(river_card).unwrap(); +//! ``` +//! +//! ### Board State and Cards +//! +//! ```rust +//! use holdem_core::{Board, Street}; +//! +//! let board = Board::new(); +//! assert_eq!(board.street(), Street::Preflop); +//! assert_eq!(board.visible_cards().len(), 0); +//! ``` + +use crate::card::Card; +use crate::errors::PokerError; +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Represents the current betting street in Texas Hold'em +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Street { + /// No community cards dealt yet + Preflop, + /// Flop cards dealt (3 cards) + Flop, + /// Turn card dealt (4 cards total) + Turn, + /// River card dealt (5 cards total) + River, +} + +impl fmt::Display for Street { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Street::Preflop => write!(f, "Preflop"), + Street::Flop => write!(f, "Flop"), + Street::Turn => write!(f, "Turn"), + Street::River => write!(f, "River"), + } + } +} + +/// Represents the community cards (board) in Texas Hold'em poker +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Board { + /// The community cards, in deal order + cards: Vec, + /// Current betting street + street: Street, +} + +impl Board { + /// Creates a new empty board at preflop + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::board::Board; + /// + /// let board = Board::new(); + /// assert!(board.is_empty()); + /// assert_eq!(board.street().to_string(), "Preflop"); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn new() -> Self { + Self { + cards: Vec::new(), + street: Street::Preflop, + } + } + + /// Returns the current betting street + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::board::Board; + /// + /// let board = Board::new(); + /// assert_eq!(board.street().to_string(), "Preflop"); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn street(&self) -> Street { + self.street + } + + /// Returns all cards currently visible on the board + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::board::Board; + /// + /// let board = Board::new(); + /// let cards = board.visible_cards(); + /// assert_eq!(cards.len(), 0); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn visible_cards(&self) -> &[Card] { + &self.cards + } + + /// Returns the number of visible cards + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::board::Board; + /// + /// let board = Board::new(); + /// assert_eq!(board.len(), 0); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn len(&self) -> usize { + self.cards.len() + } + + /// Returns true if no cards have been dealt + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::board::Board; + /// + /// let board = Board::new(); + /// assert!(board.is_empty()); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn is_empty(&self) -> bool { + self.cards.is_empty() + } + + /// Deals the flop (3 cards), advancing from preflop to flop + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{board::Board, Card}; + /// use std::str::FromStr; + /// + /// let mut board = Board::new(); + /// let flop_cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// ]; + /// board.deal_flop(flop_cards).unwrap(); + /// assert_eq!(board.len(), 3); + /// ``` + /// + /// # Panics + /// + /// This method does not panic, but returns an error if called at the wrong street or with invalid cards. + pub fn deal_flop(&mut self, cards: Vec) -> Result<(), PokerError> { + if self.street != Street::Preflop { + return Err(PokerError::CannotDealFromStreet { + current_street: self.street.to_string(), + }); + } + if cards.len() != 3 { + return Err(PokerError::FlopMustBeThreeCards { + actual: cards.len(), + }); + } + + // Check for duplicates within flop and existing cards + self.check_duplicates(&cards)?; + + self.cards.extend(cards); + self.street = Street::Flop; + Ok(()) + } + + /// Deals the turn card, advancing from flop to turn + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{board::Board, Card}; + /// use std::str::FromStr; + /// + /// let mut board = Board::new(); + /// // Deal flop first + /// let flop_cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// ]; + /// board.deal_flop(flop_cards).unwrap(); + /// let turn_card = Card::from_str("Js").unwrap(); + /// board.deal_turn(turn_card).unwrap(); + /// assert_eq!(board.len(), 4); + /// ``` + /// + /// # Panics + /// + /// This method does not panic, but returns an error if called at the wrong street. + pub fn deal_turn(&mut self, card: Card) -> Result<(), PokerError> { + if self.street != Street::Flop { + return Err(PokerError::CannotDealFromStreet { + current_street: self.street.to_string(), + }); + } + if self.cards.len() != 3 { + return Err(PokerError::MustHaveThreeCardsForTurn { + actual: self.cards.len(), + }); + } + + // Check for duplicates + if self.cards.contains(&card) { + return Err(PokerError::DuplicateWithExistingBoardCard(card)); + } + + self.cards.push(card); + self.street = Street::Turn; + Ok(()) + } + + /// Deals the river card, advancing from turn to river + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{board::Board, Card}; + /// use std::str::FromStr; + /// + /// let mut board = Board::new(); + /// // Deal flop and turn first + /// let flop_cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// ]; + /// board.deal_flop(flop_cards).unwrap(); + /// let turn_card = Card::from_str("Js").unwrap(); + /// board.deal_turn(turn_card).unwrap(); + /// let river_card = Card::from_str("Tc").unwrap(); + /// board.deal_river(river_card).unwrap(); + /// assert_eq!(board.len(), 5); + /// ``` + /// + /// # Panics + /// + /// This method does not panic, but returns an error if called at the wrong street. + pub fn deal_river(&mut self, card: Card) -> Result<(), PokerError> { + if self.street != Street::Turn { + return Err(PokerError::CannotDealFromStreet { + current_street: self.street.to_string(), + }); + } + if self.cards.len() != 4 { + return Err(PokerError::MustHaveFourCardsForRiver { + actual: self.cards.len(), + }); + } + + // Check for duplicates + if self.cards.contains(&card) { + return Err(PokerError::DuplicateWithExistingBoardCard(card)); + } + + self.cards.push(card); + self.street = Street::River; + Ok(()) + } + + /// Builder pattern method to deal the flop (3 cards) using method chaining + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Board, Card, PokerError}; + /// use std::str::FromStr; + /// + /// let board = Board::new() + /// .with_flop([ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// ])? + /// .with_turn(Card::from_str("Js").unwrap())? + /// .with_river(Card::from_str("Ts").unwrap())?; + /// # Ok::<(), PokerError>(()) + /// ``` + pub fn with_flop(mut self, cards: [Card; 3]) -> Result { + self.deal_flop(Vec::from(cards))?; + Ok(self) + } + + /// Builder pattern method to deal the turn card using method chaining + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Board, Card, PokerError}; + /// use std::str::FromStr; + /// + /// let board = Board::new() + /// .with_flop([ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// ])? + /// .with_turn(Card::from_str("Js").unwrap())?; + /// # Ok::<(), PokerError>(()) + /// ``` + pub fn with_turn(mut self, card: Card) -> Result { + self.deal_turn(card)?; + Ok(self) + } + + /// Builder pattern method to deal the river card using method chaining + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Board, Card, PokerError}; + /// use std::str::FromStr; + /// + /// let board = Board::new() + /// .with_flop([ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// ])? + /// .with_turn(Card::from_str("Js").unwrap())? + /// .with_river(Card::from_str("Ts").unwrap())?; + /// # Ok::<(), PokerError>(()) + /// ``` + pub fn with_river(mut self, card: Card) -> Result { + self.deal_river(card)?; + Ok(self) + } + + /// Returns cards visible at the specified street + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{board::{Board, Street}, Card}; + /// use std::str::FromStr; + /// + /// let mut board = Board::new(); + /// let flop_cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// ]; + /// board.deal_flop(flop_cards).unwrap(); + /// + /// let flop_cards = board.cards_at_street(Street::Flop); + /// assert_eq!(flop_cards.len(), 3); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn cards_at_street(&self, street: Street) -> &[Card] { + match street { + Street::Preflop => &[], + Street::Flop => &self.cards[..3.min(self.cards.len())], + Street::Turn => &self.cards[..4.min(self.cards.len())], + Street::River => &self.cards, + } + } + + /// Checks for duplicate cards within the provided cards and existing board cards + /// + /// This method ensures that no card is dealt twice on the board, which would be + /// invalid in poker. It checks both duplicates within the new cards being dealt + /// and duplicates between new cards and existing board cards. + /// + /// # Arguments + /// * `new_cards` - The cards being dealt + /// + /// # Returns + /// * `Ok(())` if no duplicates found + /// * `Err(PokerError)` with error if duplicates detected + fn check_duplicates(&self, new_cards: &[Card]) -> Result<(), PokerError> { + // Check duplicates within new cards + for (i, &card1) in new_cards.iter().enumerate() { + for &card2 in &new_cards[i + 1..] { + if card1 == card2 { + return Err(PokerError::DuplicateCardsInDeal); + } + } + } + + // Check duplicates with existing board cards + for &new_card in new_cards { + if self.cards.contains(&new_card) { + return Err(PokerError::DuplicateWithExistingBoardCard(new_card)); + } + } + + Ok(()) + } +} + +impl Default for Board { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Display for Board { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.cards.is_empty() { + return write!(f, "Board: [empty] ({})", self.street); + } + + write!(f, "Board: [")?; + for (i, card) in self.cards.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + write!(f, "{}", card)?; + } + write!(f, "] ({})", self.street) + } +} + +impl IntoIterator for Board { + type Item = Card; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.cards.into_iter() + } +} + +impl<'a> IntoIterator for &'a Board { + type Item = &'a Card; + type IntoIter = std::slice::Iter<'a, Card>; + + fn into_iter(self) -> Self::IntoIter { + self.cards.iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::card::Card; + use crate::hand::Hand; + + #[test] + fn test_board_creation() { + let board = Board::new(); + assert_eq!(board.street(), Street::Preflop); + assert_eq!(board.len(), 0); + assert!(board.is_empty()); + assert_eq!(board.visible_cards().len(), 0); + } + + #[test] + fn test_board_default() { + let board = Board::default(); + assert_eq!(board.street(), Street::Preflop); + assert_eq!(board.len(), 0); + assert!(board.is_empty()); + } + + #[test] + fn test_deal_flop_valid() { + let mut board = Board::new(); + + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + + board.deal_flop(flop_cards.clone()).unwrap(); + + assert_eq!(board.street(), Street::Flop); + assert_eq!(board.len(), 3); + assert!(!board.is_empty()); + assert_eq!(board.visible_cards(), flop_cards.as_slice()); + } + + #[test] + fn test_deal_flop_invalid_street() { + let mut board = Board::new(); + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + + // Deal flop + board.deal_flop(flop_cards.clone()).unwrap(); + + // Try to deal flop again + assert!(board.deal_flop(flop_cards).is_err()); + } + + #[test] + fn test_deal_flop_wrong_card_count() { + let mut board = Board::new(); + + // Too few cards + let too_few = vec![Card::new(12, 3).unwrap()]; + assert!(board.deal_flop(too_few).is_err()); + + // Too many cards + let too_many = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 2).unwrap(), + ]; + assert!(board.deal_flop(too_many).is_err()); + } + + #[test] + fn test_deal_flop_duplicates() { + let mut board = Board::new(); + + let duplicate_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(12, 3).unwrap(), // Duplicate + Card::new(10, 1).unwrap(), + ]; + + assert!(board.deal_flop(duplicate_cards).is_err()); + } + + #[test] + fn test_deal_turn_valid() { + let mut board = Board::new(); + + // First deal flop + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards).unwrap(); + + // Then deal turn + let turn_card = Card::new(9, 2).unwrap(); + board.deal_turn(turn_card).unwrap(); + + assert_eq!(board.street(), Street::Turn); + assert_eq!(board.len(), 4); + assert_eq!(board.visible_cards().len(), 4); + assert_eq!(board.visible_cards()[3], turn_card); + } + + #[test] + fn test_deal_turn_invalid_street() { + let mut board = Board::new(); + let turn_card = Card::new(9, 2).unwrap(); + + // Try to deal turn before flop + assert!(board.deal_turn(turn_card).is_err()); + } + + #[test] + fn test_deal_turn_duplicate() { + let mut board = Board::new(); + + // Deal flop + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards).unwrap(); + + // Try to deal duplicate turn card + let duplicate_turn = Card::new(12, 3).unwrap(); + assert!(board.deal_turn(duplicate_turn).is_err()); + } + + #[test] + fn test_deal_river_valid() { + let mut board = Board::new(); + + // Deal flop + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards).unwrap(); + + // Deal turn + let turn_card = Card::new(9, 2).unwrap(); + board.deal_turn(turn_card).unwrap(); + + // Deal river + let river_card = Card::new(8, 0).unwrap(); + board.deal_river(river_card).unwrap(); + + assert_eq!(board.street(), Street::River); + assert_eq!(board.len(), 5); + assert_eq!(board.visible_cards().len(), 5); + assert_eq!(board.visible_cards()[4], river_card); + } + + #[test] + fn test_deal_river_invalid_street() { + let mut board = Board::new(); + let river_card = Card::new(8, 0).unwrap(); + + // Try to deal river before turn + assert!(board.deal_river(river_card).is_err()); + } + + #[test] + fn test_deal_river_duplicate() { + let mut board = Board::new(); + + // Deal flop and turn + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards).unwrap(); + board.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + + // Try to deal duplicate river card + let duplicate_river = Card::new(12, 3).unwrap(); + assert!(board.deal_river(duplicate_river).is_err()); + } + + #[test] + fn test_cards_at_street() { + let mut board = Board::new(); + + // Preflop + assert_eq!(board.cards_at_street(Street::Preflop).len(), 0); + assert_eq!(board.cards_at_street(Street::Flop).len(), 0); + assert_eq!(board.cards_at_street(Street::Turn).len(), 0); + assert_eq!(board.cards_at_street(Street::River).len(), 0); + + // Deal flop + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards.clone()).unwrap(); + + assert_eq!(board.cards_at_street(Street::Preflop).len(), 0); + assert_eq!(board.cards_at_street(Street::Flop), flop_cards.as_slice()); + assert_eq!(board.cards_at_street(Street::Turn), flop_cards.as_slice()); + assert_eq!(board.cards_at_street(Street::River), flop_cards.as_slice()); + + // Deal turn + let turn_card = Card::new(9, 2).unwrap(); + board.deal_turn(turn_card).unwrap(); + + let expected_turn_cards = &[flop_cards[0], flop_cards[1], flop_cards[2], turn_card]; + assert_eq!(board.cards_at_street(Street::Preflop).len(), 0); + assert_eq!(board.cards_at_street(Street::Flop), flop_cards.as_slice()); + assert_eq!(board.cards_at_street(Street::Turn), expected_turn_cards); + assert_eq!(board.cards_at_street(Street::River), expected_turn_cards); + + // Deal river + let river_card = Card::new(8, 0).unwrap(); + board.deal_river(river_card).unwrap(); + + let expected_river_cards = &[ + flop_cards[0], + flop_cards[1], + flop_cards[2], + turn_card, + river_card, + ]; + assert_eq!(board.cards_at_street(Street::Preflop).len(), 0); + assert_eq!(board.cards_at_street(Street::Flop), flop_cards.as_slice()); + assert_eq!(board.cards_at_street(Street::Turn), expected_turn_cards); + assert_eq!(board.cards_at_street(Street::River), expected_river_cards); + } + + #[test] + fn test_combine_with_hand() { + let mut board = Board::new(); + + // Deal some cards + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards).unwrap(); + + // Create hole cards + let hole_cards = crate::hole_cards::HoleCards::new( + Card::new(9, 2).unwrap(), // Jack of Clubs + Card::new(8, 0).unwrap(), // Ten of Hearts + ) + .unwrap(); + + // Create complete hand from hole cards and board + let hand = Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); + + // Verify hand has correct number of cards + assert_eq!(hand.len, 5); // 2 hole cards + 3 board cards + + // Check that hole cards are included + assert!(hand.cards.iter().any(|&c| c == hole_cards.cards[0])); + assert!(hand.cards.iter().any(|&c| c == hole_cards.cards[1])); + + // Check that board cards are included + for card in &board.cards { + assert!(hand.cards.iter().any(|&c| c == *card)); + } + } + + #[test] + fn test_board_display() { + let mut board = Board::new(); + + // Empty board + assert_eq!(format!("{}", board), "Board: [empty] (Preflop)"); + + // With cards + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards).unwrap(); + + let display = format!("{}", board); + assert!(display.contains("Board: [")); + assert!(display.contains("As")); + assert!(display.contains("Kh")); + assert!(display.contains("Qd")); + assert!(display.contains("] (Flop)")); + } + + #[test] + fn test_street_display() { + let test_cases = vec![ + (Street::Preflop, "Preflop"), + (Street::Flop, "Flop"), + (Street::Turn, "Turn"), + (Street::River, "River"), + ]; + + for (street, expected) in test_cases { + assert_eq!(format!("{}", street), expected); + } + } + + #[test] + fn test_street_ordering() { + assert!(Street::Preflop < Street::Flop); + assert!(Street::Flop < Street::Turn); + assert!(Street::Turn < Street::River); + assert_eq!(Street::River, Street::River); + } + + #[test] + fn test_board_serialization() { + let mut board = Board::new(); + + // Deal some cards + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards).unwrap(); + + // Test JSON serialization + let json = serde_json::to_string(&board).unwrap(); + let deserialized: Board = serde_json::from_str(&json).unwrap(); + assert_eq!(board, deserialized); + + // Test TOML serialization + let toml = toml::to_string(&board).unwrap(); + let deserialized: Board = toml::from_str(&toml).unwrap(); + assert_eq!(board, deserialized); + } + + #[test] + fn test_board_iteration() { + let mut board = Board::new(); + + // Deal some cards + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop_cards.clone()).unwrap(); + + // Test owned iteration + let cards: Vec = board.into_iter().collect(); + assert_eq!(cards, flop_cards); + + // Test borrowed iteration + let board = Board { + cards: flop_cards.clone(), + street: Street::Flop, + }; + let mut iter = board.cards.iter(); + assert_eq!(iter.next(), Some(&flop_cards[0])); + assert_eq!(iter.next(), Some(&flop_cards[1])); + assert_eq!(iter.next(), Some(&flop_cards[2])); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_board_equality() { + let mut board1 = Board::new(); + let mut board2 = Board::new(); + + // Both empty + assert_eq!(board1, board2); + + // Deal same cards to both + let flop_cards = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board1.deal_flop(flop_cards.clone()).unwrap(); + board2.deal_flop(flop_cards).unwrap(); + + assert_eq!(board1, board2); + + // Different streets + board1.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + assert_ne!(board1, board2); + } + + #[test] + fn test_complete_board_progression() { + let mut board = Board::new(); + + // Start at preflop + assert_eq!(board.street(), Street::Preflop); + assert_eq!(board.len(), 0); + + // Deal flop + let flop = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop).unwrap(); + assert_eq!(board.street(), Street::Flop); + assert_eq!(board.len(), 3); + + // Deal turn + let turn = Card::new(9, 2).unwrap(); + board.deal_turn(turn).unwrap(); + assert_eq!(board.street(), Street::Turn); + assert_eq!(board.len(), 4); + + // Deal river + let river = Card::new(8, 0).unwrap(); + board.deal_river(river).unwrap(); + assert_eq!(board.street(), Street::River); + assert_eq!(board.len(), 5); + + // Verify all cards are present + let visible = board.visible_cards(); + assert_eq!(visible.len(), 5); + assert_eq!(visible[0], Card::new(12, 3).unwrap()); + assert_eq!(visible[1], Card::new(11, 0).unwrap()); + assert_eq!(visible[2], Card::new(10, 1).unwrap()); + assert_eq!(visible[3], Card::new(9, 2).unwrap()); + assert_eq!(visible[4], Card::new(8, 0).unwrap()); + } + + #[test] + fn test_performance_board_operations() { + use std::time::Instant; + + let start = Instant::now(); + let mut boards = Vec::new(); + + // Create and progress 1,000 boards + for _i in 0..1000 { + let mut board = Board::new(); + + let flop = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop).unwrap(); + + let turn = Card::new(9, 2).unwrap(); + board.deal_turn(turn).unwrap(); + + let river = Card::new(8, 0).unwrap(); + board.deal_river(river).unwrap(); + + boards.push(board); + } + + let duration = start.elapsed(); + assert_eq!(boards.len(), 1000); + + // Should be fast (< 50ms typically) + assert!( + duration.as_millis() < 200, + "Board operations took too long: {:?}", + duration + ); + } + + #[test] + fn test_performance_card_access() { + use std::time::Instant; + + let mut board = Board::new(); + let flop = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop).unwrap(); + board.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + board.deal_river(Card::new(8, 0).unwrap()).unwrap(); + + let start = Instant::now(); + + // Perform many card access operations + for _ in 0..100_000 { + let _ = board.len(); + let _ = board.is_empty(); + let _ = board.visible_cards(); + let _ = board.cards_at_street(Street::Flop); + let _ = board.cards_at_street(Street::Turn); + let _ = board.cards_at_street(Street::River); + } + + let duration = start.elapsed(); + + // Should be very fast (< 50ms typically) + assert!( + duration.as_millis() < 50, + "Card access operations took too long: {:?}", + duration + ); + } + + #[test] + fn test_board_street_progression_edge_cases() { + let mut board = Board::new(); + + // Test that we can't skip streets + assert!(board.deal_turn(Card::new(9, 2).unwrap()).is_err()); + assert!(board.deal_river(Card::new(8, 0).unwrap()).is_err()); + + // Deal flop + let flop = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop).unwrap(); + + // Still can't deal river + assert!(board.deal_river(Card::new(8, 0).unwrap()).is_err()); + + // Deal turn + board.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + + // Now can deal river + board.deal_river(Card::new(8, 0).unwrap()).unwrap(); + + // Can't deal more cards + assert!(board.deal_turn(Card::new(7, 1).unwrap()).is_err()); + assert!(board.deal_river(Card::new(6, 2).unwrap()).is_err()); + } + + #[test] + fn test_board_card_validation_edge_cases() { + let mut board = Board::new(); + + // Test that invalid cards cannot be created + assert!(Card::new(13, 0).is_err()); // Invalid rank + assert!(Card::new(14, 1).is_err()); // Invalid rank + assert!(Card::new(15, 2).is_err()); // Invalid rank + assert!(Card::new(9, 4).is_err()); // Invalid suit + assert!(Card::new(8, 5).is_err()); // Invalid suit + + // Test dealing with valid cards + let flop = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + ]; + board.deal_flop(flop).unwrap(); + assert_eq!(board.len(), 3); + + board.deal_turn(Card::new(9, 3).unwrap()).unwrap(); + assert_eq!(board.len(), 4); + + board.deal_river(Card::new(8, 0).unwrap()).unwrap(); + assert_eq!(board.len(), 5); + } + + #[test] + fn test_board_duplicate_detection_comprehensive() { + let mut board = Board::new(); + + // Deal flop + let flop = vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]; + board.deal_flop(flop).unwrap(); + + // Try turn card that duplicates flop + assert!(board.deal_turn(Card::new(12, 3).unwrap()).is_err()); + assert!(board.deal_turn(Card::new(11, 0).unwrap()).is_err()); + assert!(board.deal_turn(Card::new(10, 1).unwrap()).is_err()); + + // Valid turn + board.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + + // Try river card that duplicates any previous + assert!(board.deal_river(Card::new(12, 3).unwrap()).is_err()); + assert!(board.deal_river(Card::new(11, 0).unwrap()).is_err()); + assert!(board.deal_river(Card::new(10, 1).unwrap()).is_err()); + assert!(board.deal_river(Card::new(9, 2).unwrap()).is_err()); + + // Valid river + board.deal_river(Card::new(8, 0).unwrap()).unwrap(); + } + + #[test] + fn test_board_serialization_edge_cases() { + // Test empty board + let empty_board = Board::new(); + let json = serde_json::to_string(&empty_board).unwrap(); + let deserialized: Board = serde_json::from_str(&json).unwrap(); + assert_eq!(empty_board, deserialized); + + // Test board with all streets + let mut full_board = Board::new(); + full_board + .deal_flop(vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]) + .unwrap(); + full_board.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + full_board.deal_river(Card::new(8, 0).unwrap()).unwrap(); + + let json = serde_json::to_string(&full_board).unwrap(); + let deserialized: Board = serde_json::from_str(&json).unwrap(); + assert_eq!(full_board, deserialized); + + // Test TOML serialization + let toml = toml::to_string(&full_board).unwrap(); + let deserialized: Board = toml::from_str(&toml).unwrap(); + assert_eq!(full_board, deserialized); + } + + #[test] + fn test_board_iteration_comprehensive() { + // Test iteration with different board states + let states = vec![ + (Street::Preflop, vec![]), + ( + Street::Flop, + vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ], + ), + ( + Street::Turn, + vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 2).unwrap(), + ], + ), + ( + Street::River, + vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 2).unwrap(), + Card::new(8, 0).unwrap(), + ], + ), + ]; + + for (street, expected_cards) in states { + let board = Board { + cards: expected_cards.clone(), + street, + }; + + // Test owned iteration + let collected: Vec = board.into_iter().collect(); + assert_eq!(collected, expected_cards); + + // Test borrowed iteration + let board = Board { + cards: expected_cards.clone(), + street, + }; + let collected_ref: Vec<&Card> = (&board).into_iter().collect(); + assert_eq!(collected_ref.len(), expected_cards.len()); + for (i, &card) in collected_ref.iter().enumerate() { + assert_eq!(card, &expected_cards[i]); + } + } + } + + #[test] + fn test_board_display_comprehensive() { + // Test all streets + let test_cases = vec![ + (Street::Preflop, "Board: [empty] (Preflop)"), + (Street::Flop, "Board: [As Kh Qd] (Flop)"), + (Street::Turn, "Board: [As Kh Qd Jc] (Turn)"), + (Street::River, "Board: [As Kh Qd Jc Th] (River)"), + ]; + + for (street, expected_display) in test_cases { + let cards = match street { + Street::Preflop => vec![], + Street::Flop => vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ], + Street::Turn => vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 2).unwrap(), + ], + Street::River => vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 2).unwrap(), + Card::new(8, 0).unwrap(), + ], + }; + + let board = Board { cards, street }; + assert_eq!(format!("{}", board), expected_display); + } + } + + #[test] + fn test_board_hashing() { + use std::collections::HashSet; + + let mut set = HashSet::new(); + + // Create same board multiple times + let mut board1 = Board::new(); + board1 + .deal_flop(vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]) + .unwrap(); + + let mut board2 = Board::new(); + board2 + .deal_flop(vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]) + .unwrap(); + + // Same boards should hash the same + assert_eq!(board1, board2); + assert!(set.insert(board1.clone())); + assert!(!set.insert(board2.clone())); // Should not insert duplicate + + // Different boards + let mut board3 = Board::new(); + board3 + .deal_flop(vec![ + Card::new(7, 0).unwrap(), + Card::new(6, 1).unwrap(), + Card::new(5, 2).unwrap(), + ]) + .unwrap(); + assert!(set.insert(board3)); + + // Different streets + let mut board4 = board2.clone(); + board4.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + assert!(set.insert(board4)); + + assert_eq!(set.len(), 3); + } + + #[test] + fn test_board_boundary_conditions() { + // Test maximum valid board (5 cards) + let mut board = Board::new(); + board + .deal_flop(vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]) + .unwrap(); + board.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + board.deal_river(Card::new(8, 0).unwrap()).unwrap(); + assert_eq!(board.len(), 5); + assert_eq!(board.street(), Street::River); + + // Test minimum valid board (0 cards) + let empty = Board::new(); + assert_eq!(empty.len(), 0); + assert_eq!(empty.street(), Street::Preflop); + assert!(empty.is_empty()); + + // Test that cards_at_street handles partial boards correctly + let mut partial_board = Board::new(); + partial_board + .deal_flop(vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]) + .unwrap(); + + // Even though we're at flop, asking for turn/river should return flop cards + assert_eq!(partial_board.cards_at_street(Street::Turn).len(), 3); + assert_eq!(partial_board.cards_at_street(Street::River).len(), 3); + } + + #[test] + fn test_board_invalid_operations() { + let mut board = Board::new(); + + // Try to deal flop with wrong number of cards + assert!(board.deal_flop(vec![]).is_err()); + assert!(board.deal_flop(vec![Card::new(12, 3).unwrap()]).is_err()); + assert!(board + .deal_flop(vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 2).unwrap() + ]) + .is_err()); + + // Deal valid flop + board + .deal_flop(vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]) + .unwrap(); + + // Try to deal turn when not exactly 3 cards (shouldn't happen in practice) + // But test the validation + let mut invalid_board = Board { + cards: vec![Card::new(12, 3).unwrap(), Card::new(11, 0).unwrap()], // Only 2 cards + street: Street::Flop, // Incorrect state + }; + assert!(invalid_board.deal_turn(Card::new(9, 2).unwrap()).is_err()); + + // Try to deal river when not exactly 4 cards + let mut invalid_board2 = Board { + cards: vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 2).unwrap(), + Card::new(8, 0).unwrap(), + ], // 5 cards + street: Street::Turn, // Incorrect state + }; + assert!(invalid_board2.deal_river(Card::new(7, 1).unwrap()).is_err()); + } + + #[test] + fn test_street_serialization() { + // Test that Street can be serialized/deserialized + for &street in &[Street::Preflop, Street::Flop, Street::Turn, Street::River] { + let json = serde_json::to_string(&street).unwrap(); + let deserialized: Street = serde_json::from_str(&json).unwrap(); + assert_eq!(street, deserialized); + + // Note: TOML serialization for enums may not be supported in all versions + // JSON serialization is sufficient for testing serialization functionality + } + } + + #[test] + fn test_board_clone_and_equality() { + let mut original = Board::new(); + original + .deal_flop(vec![ + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + ]) + .unwrap(); + original.deal_turn(Card::new(9, 2).unwrap()).unwrap(); + + let cloned = original.clone(); + assert_eq!(original, cloned); + assert_eq!(original.street(), cloned.street()); + assert_eq!(original.visible_cards(), cloned.visible_cards()); + + // Modify clone and verify they're different + let mut modified = cloned.clone(); + modified.deal_river(Card::new(8, 0).unwrap()).unwrap(); + assert_ne!(original, modified); + } +} diff --git a/holdem_core/src/card.rs b/holdem_core/src/card.rs new file mode 100644 index 0000000..66d76a0 --- /dev/null +++ b/holdem_core/src/card.rs @@ -0,0 +1,1172 @@ +//! # Card Module +//! +//! This module provides the core `Card` and `Deck` structs for representing playing cards +//! in the poker AI system. Uses zero-based u8 enums for optimal performance and memory efficiency. +//! +//! ## Card Representation +//! +//! Cards are represented with zero-based u8 values: +//! - **Rank**: 0=Two, 1=Three, ..., 12=Ace +//! - **Suit**: 0=Hearts, 1=Diamonds, 2=Clubs, 3=Spades +//! +//! ## Examples +//! +//! ### Creating Cards +//! +//! ```rust +//! use holdem_core::Card; +//! use std::str::FromStr; +//! +//! // Create from string notation +//! let ace_hearts = Card::from_str("Ah").unwrap(); +//! let king_diamonds = Card::from_str("Kd").unwrap(); +//! let seven_clubs = Card::from_str("7c").unwrap(); +//! +//! // Create from rank and suit values +//! let card = Card::new(12, 0).unwrap(); // Ace of Hearts +//! ``` +//! +//! ### Card Information +//! +//! ```rust +//! use holdem_core::Card; +//! use std::str::FromStr; +//! +//! let card = Card::from_str("As").unwrap(); +//! println!("Rank: {}", card.rank()); // 12 (Ace) +//! println!("Suit: {}", card.suit()); // 3 (Spades) +//! println!("Display: {}", card); // "As" +//! ``` +//! +//! ## Design Decisions +//! +//! - **Zero-based u8 enums**: Maximum performance with minimal memory usage +//! - **Memory Efficient**: 2 bytes per card (1 byte rank + 1 byte suit) +//! - **Performance Optimized**: Copy semantics and efficient comparisons +//! - **Serialization Ready**: Full serde support for TOML and network communication +//! - **Standard Ordering**: Cards ordered by rank then suit for efficient sorting +//! +//! ## Performance Characteristics +//! +//! - **Memory**: 2 bytes per card +//! - **Comparison/Sorting**: O(1) u8 comparisons +//! - **Hashing**: O(1) hash operations +//! - **String Conversion**: O(1) table lookups +//! - **Serialization**: Efficient binary/network formats via serde +//! +//! ## Future Optimization: Bit-Packed Cards +//! +//! For high-performance hand evaluation, consider using `PackedCard`: +//! ```rust +//! use holdem_core::card::PackedCard; +//! +//! // 8-bit representation: 6 bits rank + 2 bits suit +//! let packed = PackedCard::new(12, 3).unwrap(); // Ace of Spades +//! assert_eq!(packed.rank(), 12); +//! assert_eq!(packed.suit(), 3); +//! ``` +//! +//! **Benefits of PackedCard**: +//! - **50% less memory** (1 byte vs 2 bytes) +//! - **Faster operations** via bit manipulation +//! - **Better cache performance** with smaller memory footprint +//! - **Efficient for hand evaluation algorithms** + +use crate::errors::PokerError; +use serde::{de::Error, Deserialize, Deserializer, Serialize}; +use std::cmp::Ordering; +use std::fmt; +use std::str::FromStr; + +/// Represents a playing card with zero-based rank and suit values +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] +pub struct Card { + /// The card's rank (0=Two to 12=Ace) + pub rank: u8, + /// The card's suit (0=Hearts, 1=Diamonds, 2=Clubs, 3=Spades) + pub suit: u8, +} + +impl<'de> Deserialize<'de> for Card { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct CardData { + rank: u8, + suit: u8, + } + + let data = CardData::deserialize(deserializer)?; + Card::new(data.rank, data.suit).map_err(D::Error::custom) + } +} + +impl Card { + /// Creates a new card from rank and suit values + /// + /// # Arguments + /// + /// * `rank` - The card's rank (0=Two to 12=Ace) + /// * `suit` - The card's suit (0=Hearts, 1=Diamonds, 2=Clubs, 3=Spades) + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Card; + /// + /// let ace_spades = Card::new(12, 3).unwrap(); + /// assert_eq!(ace_spades.rank(), 12); + /// assert_eq!(ace_spades.suit(), 3); + /// ``` + /// + /// # Panics + /// + /// This method does not panic, but returns an error if rank or suit are invalid. + pub fn new(rank: u8, suit: u8) -> Result { + if rank > 12 { + return Err(PokerError::InvalidCardRank { rank }); + } + if suit > 3 { + return Err(PokerError::InvalidCardSuit { suit }); + } + Ok(Self { rank, suit }) + } + + /// Returns the card's rank value (0=Two to 12=Ace) + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Card; + /// use std::str::FromStr; + /// + /// let card = Card::from_str("As").unwrap(); + /// assert_eq!(card.rank(), 12); // Ace + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn rank(&self) -> u8 { + self.rank + } + + /// Returns the card's suit value (0=Hearts, 1=Diamonds, 2=Clubs, 3=Spades) + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Card; + /// use std::str::FromStr; + /// + /// let card = Card::from_str("As").unwrap(); + /// assert_eq!(card.suit(), 3); // Spades + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn suit(&self) -> u8 { + self.suit + } + + /// Returns the rank as a character ('2'-'A') + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Card; + /// use std::str::FromStr; + /// + /// let card = Card::from_str("As").unwrap(); + /// assert_eq!(card.rank_char(), 'A'); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn rank_char(&self) -> char { + match self.rank { + 0 => '2', + 1 => '3', + 2 => '4', + 3 => '5', + 4 => '6', + 5 => '7', + 6 => '8', + 7 => '9', + 8 => 'T', + 9 => 'J', + 10 => 'Q', + 11 => 'K', + 12 => 'A', + _ => '?', + } + } + + /// Returns the suit as a character ('h', 'd', 'c', 's') + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Card; + /// use std::str::FromStr; + /// + /// let card = Card::from_str("As").unwrap(); + /// assert_eq!(card.suit_char(), 's'); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn suit_char(&self) -> char { + match self.suit { + 0 => 'h', + 1 => 'd', + 2 => 'c', + 3 => 's', + _ => '?', + } + } + + /// Converts a rank character to its u8 value + /// Returns None if the character is invalid + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Card; + /// + /// assert_eq!(Card::rank_from_char('A'), Some(12)); + /// assert_eq!(Card::rank_from_char('2'), Some(0)); + /// assert_eq!(Card::rank_from_char('X'), None); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn rank_from_char(c: char) -> Option { + match c { + '2' => Some(0), + '3' => Some(1), + '4' => Some(2), + '5' => Some(3), + '6' => Some(4), + '7' => Some(5), + '8' => Some(6), + '9' => Some(7), + 'T' => Some(8), + 'J' => Some(9), + 'Q' => Some(10), + 'K' => Some(11), + 'A' => Some(12), + _ => None, + } + } + + /// Converts a rank u8 value to its character representation + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Card; + /// + /// assert_eq!(Card::rank_to_char(12), 'A'); + /// assert_eq!(Card::rank_to_char(0), '2'); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn rank_to_char(rank: u8) -> char { + match rank { + 0 => '2', + 1 => '3', + 2 => '4', + 3 => '5', + 4 => '6', + 5 => '7', + 6 => '8', + 7 => '9', + 8 => 'T', + 9 => 'J', + 10 => 'Q', + 11 => 'K', + 12 => 'A', + _ => '?', + } + } + + /// Converts a suit u8 value to its character representation + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Card; + /// + /// assert_eq!(Card::suit_to_char(3), 's'); + /// assert_eq!(Card::suit_to_char(0), 'h'); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn suit_to_char(suit: u8) -> char { + match suit { + 0 => 'h', + 1 => 'd', + 2 => 'c', + 3 => 's', + _ => '?', + } + } +} + +impl FromStr for Card { + type Err = PokerError; + + fn from_str(s: &str) -> Result { + if s.len() != 2 { + return Err(PokerError::InvalidCardString { + string: s.to_string(), + }); + } + + let rank_char = s.chars().nth(0).unwrap(); + let suit_char = s.chars().nth(1).unwrap(); + + let rank = match rank_char { + '2' => 0, + '3' => 1, + '4' => 2, + '5' => 3, + '6' => 4, + '7' => 5, + '8' => 6, + '9' => 7, + 'T' => 8, + 'J' => 9, + 'Q' => 10, + 'K' => 11, + 'A' => 12, + _ => { + return Err(PokerError::InvalidRankCharacter { + character: rank_char, + }) + } + }; + + let suit = match suit_char { + 'h' => 0, + 'd' => 1, + 'c' => 2, + 's' => 3, + _ => { + return Err(PokerError::InvalidSuitCharacter { + character: suit_char, + }) + } + }; + + Card::new(rank, suit) + } +} + +impl fmt::Display for Card { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.rank_char(), self.suit_char()) + } +} + +impl PartialOrd for Card { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Card { + fn cmp(&self, other: &Self) -> Ordering { + self.rank + .cmp(&other.rank) + .then(Self::suit_order(self.suit).cmp(&Self::suit_order(other.suit))) + } +} + +impl Card { + /// Returns the ordering value for suits (higher value = higher suit) + fn suit_order(suit: u8) -> u8 { + match suit { + 3 => 4, // Spades highest + 0 => 3, // Hearts + 1 => 2, // Diamonds + 2 => 1, // Clubs lowest + _ => 0, + } + } +} + +/// Bit-packed card representation for maximum performance in hand evaluation +/// +/// Layout: 6 bits rank (0-63) + 2 bits suit (0-3) = 8 bits total +/// - Bits 0-5: rank (0=Two, 1=Three, ..., 12=Ace) +/// - Bits 6-7: suit (0=Hearts, 1=Diamonds, 2=Clubs, 3=Spades) +/// +/// **Performance Benefits**: +/// - **50% less memory** (1 byte vs 2 bytes per card) +/// - **Faster comparisons** via direct integer operations +/// - **Better cache performance** with smaller memory footprint +/// - **Efficient bit manipulation** for hand evaluation algorithms +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] +pub struct PackedCard(u8); + +impl PackedCard { + /// Bit mask for extracting rank (lower 6 bits) + const RANK_MASK: u8 = 0b0011_1111; + /// Bit mask for extracting suit (upper 2 bits) + const SUIT_MASK: u8 = 0b1100_0000; + /// Number of bits to shift suit value + const SUIT_SHIFT: u8 = 6; + + /// Creates a new packed card from rank and suit values + /// + /// # Arguments + /// + /// * `rank` - The card's rank (0=Two to 12=Ace) + /// * `suit` - The card's suit (0=Hearts, 1=Diamonds, 2=Clubs, 3=Spades) + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::card::PackedCard; + /// + /// let ace_spades = PackedCard::new(12, 3).unwrap(); + /// assert_eq!(ace_spades.rank(), 12); + /// assert_eq!(ace_spades.suit(), 3); + /// ``` + pub fn new(rank: u8, suit: u8) -> Result { + if rank > 12 { + return Err(PokerError::InvalidCardRank { rank }); + } + if suit > 3 { + return Err(PokerError::InvalidCardSuit { suit }); + } + + // Pack: rank in lower 6 bits, suit in upper 2 bits + let packed = (suit << Self::SUIT_SHIFT) | rank; + Ok(Self(packed)) + } + + /// Creates a packed card from a regular Card + pub fn from_card(card: &Card) -> Self { + Self::new(card.rank(), card.suit()).unwrap() + } + + /// Converts to a regular Card + pub fn to_card(self) -> Card { + Card::new(self.rank(), self.suit()).unwrap() + } + + /// Returns the card's rank value (0=Two to 12=Ace) + pub fn rank(self) -> u8 { + self.0 & Self::RANK_MASK + } + + /// Returns the card's suit value (0=Hearts, 1=Diamonds, 2=Clubs, 3=Spades) + pub fn suit(self) -> u8 { + (self.0 & Self::SUIT_MASK) >> Self::SUIT_SHIFT + } + + /// Returns the rank as a character ('2'-'A') + pub fn rank_char(self) -> char { + Card::rank_to_char(self.rank()) + } + + /// Returns the suit as a character ('h', 'd', 'c', 's') + pub fn suit_char(self) -> char { + Card::suit_to_char(self.suit()) + } + + /// Returns the raw packed byte value + pub fn as_u8(self) -> u8 { + self.0 + } + + /// Creates a packed card from a raw byte value + /// Note: Does not validate that the value represents a valid card + pub fn from_u8(value: u8) -> Self { + Self(value) + } +} + +impl From for PackedCard { + fn from(card: Card) -> Self { + Self::from_card(&card) + } +} + +impl From for Card { + fn from(packed: PackedCard) -> Self { + packed.to_card() + } +} + +impl fmt::Display for PackedCard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.rank_char(), self.suit_char()) + } +} + +impl PartialOrd for PackedCard { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PackedCard { + fn cmp(&self, other: &Self) -> Ordering { + // Compare by rank first, then suit + self.rank() + .cmp(&other.rank()) + .then_with(|| Self::suit_order(self.suit()).cmp(&Self::suit_order(other.suit()))) + } +} + +impl PackedCard { + /// Returns the ordering value for suits (higher value = higher suit) + fn suit_order(suit: u8) -> u8 { + match suit { + 3 => 4, // Spades highest + 0 => 3, // Hearts + 1 => 2, // Diamonds + 2 => 1, // Clubs lowest + _ => 0, + } + } +} + +impl FromStr for PackedCard { + type Err = PokerError; + + fn from_str(s: &str) -> Result { + let card: Card = s.parse()?; + Ok(Self::from_card(&card)) + } +} + +impl<'de> Deserialize<'de> for PackedCard { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = u8::deserialize(deserializer)?; + Ok(PackedCard::from_u8(value)) + } +} + +#[cfg(test)] +mod packed_card_tests { + use super::*; + + #[test] + fn test_packed_card_creation() { + let packed = PackedCard::new(12, 3).unwrap(); // Ace of Spades + assert_eq!(packed.rank(), 12); + assert_eq!(packed.suit(), 3); + assert_eq!(packed.as_u8(), 0b1100_1100); // suit=3 (1100), rank=12 (1100) + } + + #[test] + fn test_packed_card_conversion() { + let original = Card::new(12, 3).unwrap(); + let packed = PackedCard::from_card(&original); + let back_to_card = packed.to_card(); + + assert_eq!(original, back_to_card); + assert_eq!(packed.rank(), original.rank()); + assert_eq!(packed.suit(), original.suit()); + } + + #[test] + fn test_packed_card_display() { + let packed = PackedCard::new(12, 3).unwrap(); + assert_eq!(format!("{}", packed), "As"); + + let packed = PackedCard::new(0, 0).unwrap(); + assert_eq!(format!("{}", packed), "2h"); + } + + #[test] + fn test_packed_card_ordering() { + let ace_spades = PackedCard::new(12, 3).unwrap(); + let king_spades = PackedCard::new(11, 3).unwrap(); + let ace_hearts = PackedCard::new(12, 0).unwrap(); + + assert!(ace_spades > king_spades); // Higher rank + assert!(ace_spades > ace_hearts); // Higher suit (Spades > Hearts) + } + + #[test] + fn test_packed_card_from_str() { + let packed: PackedCard = "As".parse().unwrap(); + assert_eq!(packed.rank(), 12); + assert_eq!(packed.suit(), 3); + + let packed: PackedCard = "2h".parse().unwrap(); + assert_eq!(packed.rank(), 0); + assert_eq!(packed.suit(), 0); + } + + #[test] + fn test_packed_card_memory_usage() { + use std::mem; + + assert_eq!(mem::size_of::(), 2); + assert_eq!(mem::size_of::(), 1); + + // Test that we can fit more packed cards in the same memory + let cards: Vec = (0..52) + .map(|i| Card::new((i % 13) as u8, (i % 4) as u8).unwrap()) + .collect(); + let packed_cards: Vec = + cards.iter().map(|c| PackedCard::from_card(c)).collect(); + + // Packed cards should use roughly half the memory + assert!(mem::size_of_val(&*packed_cards) * 2 >= mem::size_of_val(&*cards)); + } + + #[test] + fn test_packed_card_all_values() { + // Test all 52 possible cards + for rank in 0..13 { + for suit in 0..4 { + let packed = PackedCard::new(rank, suit).unwrap(); + assert_eq!(packed.rank(), rank); + assert_eq!(packed.suit(), suit); + + // Test round-trip conversion + let card = packed.to_card(); + assert_eq!(card.rank(), rank); + assert_eq!(card.suit(), suit); + } + } + } + + #[test] + fn test_packed_card_invalid_creation() { + assert!(PackedCard::new(13, 0).is_err()); // Invalid rank + assert!(PackedCard::new(12, 4).is_err()); // Invalid suit + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::deck::Deck; + use std::collections::HashSet; + + #[test] + fn test_card_creation() { + // Test creating cards with new() + let card = Card::new(12, 3).unwrap(); // Ace of Spades + assert_eq!(card.rank, 12); + assert_eq!(card.suit, 3); + + // Test creating all possible cards + for rank in 0..13 { + for suit in 0..4 { + let card = Card::new(rank, suit).unwrap(); + assert_eq!(card.rank(), rank); + assert_eq!(card.suit(), suit); + } + } + } + + #[test] + fn test_card_from_str_valid() { + // Test all valid card strings + let test_cases = vec![ + ("2c", 0, 2), // Two of Clubs + ("3d", 1, 1), // Three of Diamonds + ("4h", 2, 0), // Four of Hearts + ("5s", 3, 3), // Five of Spades + ("Ts", 8, 3), // Ten of Spades + ("Jc", 9, 2), // Jack of Clubs + ("Qd", 10, 1), // Queen of Diamonds + ("Kh", 11, 0), // King of Hearts + ("As", 12, 3), // Ace of Spades + ]; + + for (str_repr, expected_rank, expected_suit) in test_cases { + let card = Card::from_str(str_repr).unwrap(); + assert_eq!(card.rank, expected_rank); + assert_eq!(card.suit, expected_suit); + } + } + + #[test] + fn test_card_from_str_invalid() { + // Test invalid string lengths + assert!(Card::from_str("").is_err()); + assert!(Card::from_str("A").is_err()); + assert!(Card::from_str("Asc").is_err()); + assert!(Card::from_str("Asd").is_err()); + + // Test invalid rank characters + assert!(Card::from_str("1s").is_err()); + assert!(Card::from_str("Zs").is_err()); + assert!(Card::from_str("@s").is_err()); + + // Test invalid suit characters + assert!(Card::from_str("Az").is_err()); + assert!(Card::from_str("A1").is_err()); + assert!(Card::from_str("A ").is_err()); + } + + #[test] + fn test_card_display() { + let test_cases = vec![ + (Card::new(12, 3).unwrap(), "As"), // Ace of Spades + (Card::new(11, 0).unwrap(), "Kh"), // King of Hearts + (Card::new(10, 1).unwrap(), "Qd"), // Queen of Diamonds + (Card::new(9, 2).unwrap(), "Jc"), // Jack of Clubs + (Card::new(8, 3).unwrap(), "Ts"), // Ten of Spades + (Card::new(0, 0).unwrap(), "2h"), // Two of Hearts + ]; + + for (card, expected_str) in test_cases { + assert_eq!(format!("{}", card), expected_str); + } + } + + #[test] + fn test_card_equality() { + let card1 = Card::new(12, 3).unwrap(); // Ace of Spades + let card2 = Card::new(12, 3).unwrap(); // Ace of Spades + let card3 = Card::new(11, 3).unwrap(); // King of Spades + let card4 = Card::new(12, 0).unwrap(); // Ace of Hearts + + // Same cards should be equal + assert_eq!(card1, card2); + + // Different rank should not be equal + assert_ne!(card1, card3); + + // Different suit should not be equal + assert_ne!(card1, card4); + } + + #[test] + fn test_card_ordering() { + // Test rank ordering (higher rank first) + assert!(Card::new(12, 3).unwrap() > Card::new(11, 3).unwrap()); // Ace > King + assert!(Card::new(11, 3).unwrap() > Card::new(10, 3).unwrap()); // King > Queen + assert!(Card::new(0, 3).unwrap() < Card::new(1, 3).unwrap()); // Two < Three + + // Test suit ordering when ranks are equal (higher suit first) + assert!(Card::new(12, 3).unwrap() > Card::new(12, 0).unwrap()); // Spades > Hearts + assert!(Card::new(12, 0).unwrap() > Card::new(12, 1).unwrap()); // Hearts > Diamonds + assert!(Card::new(12, 1).unwrap() > Card::new(12, 2).unwrap()); // Diamonds > Clubs + } + + #[test] + fn test_card_hashing() { + let mut set = HashSet::new(); + + // Add all 52 cards + for rank in 0..13 { + for suit in 0..4 { + let card = Card::new(rank, suit).unwrap(); + assert!(set.insert(card), "Duplicate hash for card: {}", card); + } + } + + // Should have 52 unique cards + assert_eq!(set.len(), 52); + } + + #[test] + fn test_serialization() { + let card = Card::new(12, 3).unwrap(); // Ace of Spades + + // Test JSON serialization + let json = serde_json::to_string(&card).unwrap(); + let deserialized: Card = serde_json::from_str(&json).unwrap(); + assert_eq!(card, deserialized); + + // Test TOML serialization + let toml = toml::to_string(&card).unwrap(); + let deserialized: Card = toml::from_str(&toml).unwrap(); + assert_eq!(card, deserialized); + } + + #[test] + fn test_card_roundtrip_string_conversion() { + // Test that from_str -> to_string -> from_str works + let original_strings = vec![ + "2c", "3d", "4h", "5s", "6c", "7d", "8h", "9s", "Tc", "Jd", "Qh", "Ks", "Ac", + ]; + + for &str_repr in &original_strings { + let card = Card::from_str(str_repr).unwrap(); + let back_to_string = format!("{}", card); + assert_eq!(back_to_string, str_repr); + + // And parse again + let card2 = Card::from_str(&back_to_string).unwrap(); + assert_eq!(card, card2); + } + } + + #[test] + fn test_all_cards_unique() { + let mut cards = Vec::new(); + + // Generate all 52 cards + for rank in 0..13 { + for suit in 0..4 { + cards.push(Card::new(rank, suit).unwrap()); + } + } + + assert_eq!(cards.len(), 52); + + // Check all are unique + for i in 0..cards.len() { + for j in (i + 1)..cards.len() { + assert_ne!( + cards[i], cards[j], + "Cards {} and {} should be different", + cards[i], cards[j] + ); + } + } + } + + #[test] + fn test_deck_new() { + let deck = Deck::new(); + assert_eq!(deck.remaining(), 52); + assert!(!deck.is_empty()); + + // Check all cards are present + let cards = deck.cards(); + let mut card_set = HashSet::new(); + for &card in cards { + assert!(card_set.insert(card), "Duplicate card in deck: {}", card); + } + assert_eq!(card_set.len(), 52); + } + + #[test] + fn test_deck_shuffle() { + let mut deck1 = Deck::new(); + let deck2 = Deck::new(); + + // Decks should be identical initially + assert_eq!(deck1.cards(), deck2.cards()); + + // Shuffle one deck + use rand::SeedableRng; + let mut rng = rand::rngs::StdRng::from_seed([1; 32]); + deck1.shuffle(&mut rng); + + // After shuffle, order should be different (with very high probability) + assert_ne!(deck1.cards(), deck2.cards()); + assert_eq!(deck1.remaining(), 52); + assert_eq!(deck2.remaining(), 52); + } + + #[test] + fn test_deck_deal_one() { + let mut deck = Deck::new(); + + // Deal one card + let card = deck.deal_one().unwrap(); + assert_eq!(deck.remaining(), 51); + + // Deal another + let card2 = deck.deal_one().unwrap(); + assert_eq!(deck.remaining(), 50); + + // Cards should be different + assert_ne!(card, card2); + } + + #[test] + fn test_deck_deal_multiple() { + let mut deck = Deck::new(); + + // Deal 5 cards + let cards = deck.deal(5); + assert_eq!(cards.len(), 5); + assert_eq!(deck.remaining(), 47); + + // All cards should be unique + let mut card_set = HashSet::new(); + for &card in &cards { + assert!(card_set.insert(card), "Duplicate card dealt: {}", card); + } + } + + #[test] + fn test_deck_deal_empty() { + let mut deck = Deck::new(); + + // Deal all cards + let all_cards = deck.deal(52); + assert_eq!(all_cards.len(), 52); + assert_eq!(deck.remaining(), 0); + assert!(deck.is_empty()); + + // Try to deal one more + assert!(deck.deal_one().is_none()); + + // Try to deal multiple more + let empty_deal = deck.deal(5); + assert!(empty_deal.is_empty()); + } + + #[test] + fn test_deck_serialization() { + let deck = Deck::new(); + + // Test JSON serialization + let json = serde_json::to_string(&deck).unwrap(); + let deserialized: Deck = serde_json::from_str(&json).unwrap(); + assert_eq!(deck.cards(), deserialized.cards()); + + // Test TOML serialization + let toml = toml::to_string(&deck).unwrap(); + let deserialized: Deck = toml::from_str(&toml).unwrap(); + assert_eq!(deck.cards(), deserialized.cards()); + } + + #[test] + fn test_performance_card_creation() { + use std::time::Instant; + + let start = Instant::now(); + let mut cards = Vec::new(); + + // Create 10,000 cards + for _ in 0..10_000 { + for rank in 10..13 { + // Ace, King, Queen + for suit in 0..4 { + cards.push(Card::new(rank, suit).unwrap()); + } + } + } + + let duration = start.elapsed(); + assert_eq!(cards.len(), 120_000); + + // Should be very fast (< 10ms typically) + assert!( + duration.as_millis() < 100, + "Card creation took too long: {:?}", + duration + ); + } + + #[test] + fn test_performance_string_conversion() { + use std::time::Instant; + + let cards: Vec = (0..1000) + .map(|i| { + let rank = (i % 13) as u8; + let suit = (i % 4) as u8; + Card::new(rank, suit).unwrap() + }) + .collect(); + + let start = Instant::now(); + + // Convert all to strings + let strings: Vec = cards.iter().map(|c| format!("{}", c)).collect(); + + let duration = start.elapsed(); + assert_eq!(strings.len(), 1000); + + // Should be very fast (< 5ms typically) + assert!( + duration.as_millis() < 50, + "String conversion took too long: {:?}", + duration + ); + } + + #[test] + fn test_card_serialization_edge_cases() { + // Test serialization with edge case cards + let ace_spades = Card::new(12, 3).unwrap(); + let two_hearts = Card::new(0, 0).unwrap(); + + // JSON roundtrip + let json = serde_json::to_string(&ace_spades).unwrap(); + let deserialized: Card = serde_json::from_str(&json).unwrap(); + assert_eq!(ace_spades, deserialized); + + let json = serde_json::to_string(&two_hearts).unwrap(); + let deserialized: Card = serde_json::from_str(&json).unwrap(); + assert_eq!(two_hearts, deserialized); + + // TOML roundtrip + let toml = toml::to_string(&ace_spades).unwrap(); + let deserialized: Card = toml::from_str(&toml).unwrap(); + assert_eq!(ace_spades, deserialized); + + // Test invalid JSON (should fail gracefully) + let invalid_json = r#"{"rank": 13, "suit": 0}"#; // Invalid rank + assert!(serde_json::from_str::(invalid_json).is_err()); + + let invalid_json = r#"{"rank": 0, "suit": 4}"#; // Invalid suit + assert!(serde_json::from_str::(invalid_json).is_err()); + } + + #[test] + fn test_card_comparison_properties() { + // Test transitivity + let ace_spades = Card::new(12, 3).unwrap(); + let king_spades = Card::new(11, 3).unwrap(); + let queen_spades = Card::new(10, 3).unwrap(); + + assert!(ace_spades > king_spades); + assert!(king_spades > queen_spades); + assert!(ace_spades > queen_spades); + + // Test antisymmetry + assert!(!(ace_spades < ace_spades)); + assert!(!(ace_spades > ace_spades)); + assert_eq!(ace_spades, ace_spades); + + // Test suit ordering with same rank + let ace_spades = Card::new(12, 3).unwrap(); + let ace_hearts = Card::new(12, 0).unwrap(); + let ace_diamonds = Card::new(12, 1).unwrap(); + let ace_clubs = Card::new(12, 2).unwrap(); + + assert!(ace_spades > ace_hearts); + assert!(ace_hearts > ace_diamonds); + assert!(ace_diamonds > ace_clubs); + } + + #[test] + fn test_card_hash_properties() { + use std::collections::HashSet; + + // Test that equal cards have equal hashes + let card1 = Card::new(12, 3).unwrap(); + let card2 = Card::new(12, 3).unwrap(); + assert_eq!(card1, card2); + + let hash1 = { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + card1.hash(&mut hasher); + hasher.finish() + }; + + let hash2 = { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + card2.hash(&mut hasher); + hasher.finish() + }; + + assert_eq!(hash1, hash2); + + // Test hash distribution (no collisions for all 52 cards) + let mut hashes = HashSet::new(); + for rank in 0..13 { + for suit in 0..4 { + let card = Card::new(rank, suit).unwrap(); + let hash = { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + card.hash(&mut hasher); + hasher.finish() + }; + assert!( + hashes.insert(hash), + "Hash collision detected for card: {}", + card + ); + } + } + assert_eq!(hashes.len(), 52); + } + + #[test] + fn test_card_string_conversion_edge_cases() { + // Test all possible rank/suit combinations + for rank in 0..13 { + for suit in 0..4 { + let card = Card::new(rank, suit).unwrap(); + let string_repr = format!("{}", card); + assert_eq!(string_repr.len(), 2); + + // Should be able to parse back + let parsed = Card::from_str(&string_repr).unwrap(); + assert_eq!(card, parsed); + } + } + + // Test rank_char and suit_char functions + assert_eq!(Card::new(12, 3).unwrap().rank_char(), 'A'); + assert_eq!(Card::new(0, 0).unwrap().rank_char(), '2'); + assert_eq!(Card::new(12, 3).unwrap().suit_char(), 's'); + assert_eq!(Card::new(0, 0).unwrap().suit_char(), 'h'); + + // Test rank_from_char and suit_to_char + assert_eq!(Card::rank_from_char('A'), Some(12)); + assert_eq!(Card::rank_from_char('2'), Some(0)); + assert_eq!(Card::rank_from_char('X'), None); // Invalid + + // Test rank_to_char and suit_to_char + assert_eq!(Card::rank_to_char(12), 'A'); + assert_eq!(Card::rank_to_char(0), '2'); + assert_eq!(Card::suit_to_char(3), 's'); + assert_eq!(Card::suit_to_char(0), 'h'); + } + + #[test] + fn test_card_boundary_values() { + // Test maximum valid values + let max_card = Card::new(12, 3).unwrap(); // Ace of Spades + assert_eq!(max_card.rank(), 12); + assert_eq!(max_card.suit(), 3); + + // Test minimum valid values + let min_card = Card::new(0, 0).unwrap(); // Two of Hearts + assert_eq!(min_card.rank(), 0); + assert_eq!(min_card.suit(), 0); + + // Test all boundary combinations + let boundaries = [ + (0, 0), + (0, 3), + (12, 0), + (12, 3), + (6, 1), + (7, 2), // Middle values + ]; + + for (rank, suit) in boundaries.iter() { + let card = Card::new(*rank, *suit).unwrap(); + assert_eq!(card.rank(), *rank); + assert_eq!(card.suit(), *suit); + + // Should serialize/deserialize correctly + let json = serde_json::to_string(&card).unwrap(); + let deserialized: Card = serde_json::from_str(&json).unwrap(); + assert_eq!(card, deserialized); + } + } +} diff --git a/holdem_core/src/deck.rs b/holdem_core/src/deck.rs new file mode 100644 index 0000000..262fa7e --- /dev/null +++ b/holdem_core/src/deck.rs @@ -0,0 +1,495 @@ +//! # Deck Module +//! +//! This module provides the `Deck` struct for representing a deck of playing cards +//! in the poker AI system. Supports shuffling, dealing, and serialization. +//! +//! ## Deck Representation +//! +//! A Deck contains a vector of cards not yet dealt, starting with all 52 cards. +//! +//! ## Examples +//! +//! ### Creating and Using a Deck +//! +//! ```rust +//! use holdem_core::{Deck, Card}; +//! +//! let mut deck = Deck::new(); +//! assert_eq!(deck.remaining(), 52); +//! +//! // Shuffle the deck +//! use rand::SeedableRng; +//! let mut rng = rand::rngs::StdRng::from_seed([1; 32]); +//! deck.shuffle(&mut rng); +//! +//! // Deal some cards +//! let hole_cards = deck.deal(2); +//! let flop = deck.deal(3); +//! let turn = deck.deal(1); +//! let river = deck.deal(1); +//! +//! assert_eq!(hole_cards.len(), 2); +//! assert_eq!(flop.len(), 3); +//! assert_eq!(turn.len(), 1); +//! assert_eq!(river.len(), 1); +//! assert_eq!(deck.remaining(), 52 - 7); +//! ``` +//! +//! ## Design Decisions +//! +//! - **Full Deck**: Always starts with 52 cards in standard poker order +//! - **Efficient Dealing**: Pop from end for O(1) deal operations +//! - **Serialization Ready**: Full serde support for persistence +//! - **Flexible Shuffling**: Uses rand crate for high-quality randomization + +use crate::card::Card; +use serde::{Deserialize, Serialize}; + +/// Represents a deck of cards not yet dealt +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Deck { + cards: Vec, +} + +impl Deck { + /// Creates a new full deck of 52 cards + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Deck; + /// + /// let deck = Deck::new(); + /// assert_eq!(deck.remaining(), 52); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn new() -> Self { + let mut cards = Vec::with_capacity(52); + for suit in 0..4 { + for rank in 0..13 { + cards.push(Card::new(rank, suit).unwrap()); + } + } + Self { cards } + } + + /// Shuffles the deck using the provided random number generator + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Deck; + /// use rand::SeedableRng; + /// + /// let mut deck = Deck::new(); + /// let mut rng = rand::rngs::StdRng::from_seed([1; 32]); + /// deck.shuffle(&mut rng); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn shuffle(&mut self, rng: &mut R) { + use rand::seq::SliceRandom; + self.cards.shuffle(rng); + } + + /// Deals a single card from the top of the deck + /// + /// Returns `None` if the deck is empty. + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Deck; + /// + /// let mut deck = Deck::new(); + /// let card = deck.deal_one().unwrap(); + /// assert_eq!(deck.remaining(), 51); + /// ``` + /// + /// # Panics + /// + /// This method does not panic, but returns `None` if the deck is empty. + pub fn deal_one(&mut self) -> Option { + self.cards.pop() + } + + /// Deals multiple cards from the top of the deck + /// + /// Returns a vector containing up to `count` cards. If fewer than `count` cards + /// remain in the deck, returns all remaining cards. + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Deck; + /// + /// let mut deck = Deck::new(); + /// let cards = deck.deal(5); + /// assert_eq!(cards.len(), 5); + /// assert_eq!(deck.remaining(), 47); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn deal(&mut self, count: usize) -> Vec { + let mut dealt = Vec::new(); + for _ in 0..count { + if let Some(card) = self.deal_one() { + dealt.push(card); + } else { + break; + } + } + dealt + } + + /// Returns the number of cards remaining in the deck + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Deck; + /// + /// let mut deck = Deck::new(); + /// assert_eq!(deck.remaining(), 52); + /// + /// deck.deal(5); + /// assert_eq!(deck.remaining(), 47); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn remaining(&self) -> usize { + self.cards.len() + } + + /// Returns true if the deck is empty + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Deck; + /// + /// let mut deck = Deck::new(); + /// assert!(!deck.is_empty()); + /// + /// // Deal all cards + /// deck.deal(52); + /// assert!(deck.is_empty()); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn is_empty(&self) -> bool { + self.cards.is_empty() + } + + /// Returns a reference to the remaining cards + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Deck; + /// + /// let deck = Deck::new(); + /// let cards = deck.cards(); + /// assert_eq!(cards.len(), 52); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn cards(&self) -> &[Card] { + &self.cards + } +} + +impl Default for Deck { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + + #[test] + fn test_deck_new() { + let deck = Deck::new(); + assert_eq!(deck.remaining(), 52); + assert!(!deck.is_empty()); + + // Check all cards are present + let cards = deck.cards(); + let mut card_set = HashSet::new(); + for &card in cards { + assert!(card_set.insert(card), "Duplicate card in deck: {}", card); + } + assert_eq!(card_set.len(), 52); + } + + #[test] + fn test_deck_shuffle() { + let mut deck1 = Deck::new(); + let deck2 = Deck::new(); + + // Decks should be identical initially + assert_eq!(deck1.cards(), deck2.cards()); + + // Shuffle one deck + use rand::SeedableRng; + let mut rng = rand::rngs::StdRng::from_seed([1; 32]); + deck1.shuffle(&mut rng); + + // After shuffle, order should be different (with very high probability) + assert_ne!(deck1.cards(), deck2.cards()); + assert_eq!(deck1.remaining(), 52); + assert_eq!(deck2.remaining(), 52); + } + + #[test] + fn test_deck_deal_one() { + let mut deck = Deck::new(); + + // Deal one card + let card = deck.deal_one().unwrap(); + assert_eq!(deck.remaining(), 51); + + // Deal another + let card2 = deck.deal_one().unwrap(); + assert_eq!(deck.remaining(), 50); + + // Cards should be different + assert_ne!(card, card2); + } + + #[test] + fn test_deck_deal_multiple() { + let mut deck = Deck::new(); + + // Deal 5 cards + let cards = deck.deal(5); + assert_eq!(cards.len(), 5); + assert_eq!(deck.remaining(), 47); + + // All cards should be unique + let mut card_set = HashSet::new(); + for &card in &cards { + assert!(card_set.insert(card), "Duplicate card dealt: {}", card); + } + } + + #[test] + fn test_deck_deal_empty() { + let mut deck = Deck::new(); + + // Deal all cards + let all_cards = deck.deal(52); + assert_eq!(all_cards.len(), 52); + assert_eq!(deck.remaining(), 0); + assert!(deck.is_empty()); + + // Try to deal one more + assert!(deck.deal_one().is_none()); + + // Try to deal multiple more + let empty_deal = deck.deal(5); + assert!(empty_deal.is_empty()); + } + + #[test] + fn test_deck_serialization() { + let deck = Deck::new(); + + // Test JSON serialization + let json = serde_json::to_string(&deck).unwrap(); + let deserialized: Deck = serde_json::from_str(&json).unwrap(); + assert_eq!(deck.cards(), deserialized.cards()); + + // Test TOML serialization + let toml = toml::to_string(&deck).unwrap(); + let deserialized: Deck = toml::from_str(&toml).unwrap(); + assert_eq!(deck.cards(), deserialized.cards()); + } + + #[test] + fn test_deck_shuffle_randomness() { + use rand::SeedableRng; + + let mut deck = Deck::new(); + let original_order: Vec = deck.cards().to_vec(); + + // Shuffle with different seeds and verify randomness + let mut rng1 = rand::rngs::StdRng::from_seed([1; 32]); + deck.shuffle(&mut rng1); + let shuffled1 = deck.cards().to_vec(); + + // Reset deck + deck = Deck::new(); + let mut rng2 = rand::rngs::StdRng::from_seed([2; 32]); + deck.shuffle(&mut rng2); + let shuffled2 = deck.cards().to_vec(); + + // Different seeds should produce different shuffles + assert_ne!(shuffled1, shuffled2); + assert_ne!(shuffled1, original_order); + assert_ne!(shuffled2, original_order); + + // All cards should still be present + let mut set1 = HashSet::new(); + let mut set2 = HashSet::new(); + for &card in &shuffled1 { + set1.insert(card); + } + for &card in &shuffled2 { + set2.insert(card); + } + assert_eq!(set1.len(), 52); + assert_eq!(set2.len(), 52); + } + + #[test] + fn test_deck_deal_edge_cases() { + let mut deck = Deck::new(); + + // Deal more cards than available + let cards = deck.deal(60); + assert_eq!(cards.len(), 52); + assert_eq!(deck.remaining(), 0); + assert!(deck.is_empty()); + + // Try dealing from empty deck + let empty_deal = deck.deal(5); + assert!(empty_deal.is_empty()); + + // Deal one from empty deck + assert!(deck.deal_one().is_none()); + } + + #[test] + fn test_deck_remaining_cards() { + let mut deck = Deck::new(); + assert_eq!(deck.remaining(), 52); + + // Deal various amounts and check remaining + deck.deal(5); + assert_eq!(deck.remaining(), 47); + + deck.deal_one(); + assert_eq!(deck.remaining(), 46); + + deck.deal(10); + assert_eq!(deck.remaining(), 36); + + // Deal all remaining + deck.deal(36); + assert_eq!(deck.remaining(), 0); + assert!(deck.is_empty()); + } + + #[test] + fn test_deck_deal_zero() { + let mut deck = Deck::new(); + + // Deal zero cards + let empty_deal = deck.deal(0); + assert!(empty_deal.is_empty()); + assert_eq!(deck.remaining(), 52); + } + + #[test] + fn test_deck_default() { + let deck = Deck::default(); + assert_eq!(deck.remaining(), 52); + assert!(!deck.is_empty()); + } + + #[test] + fn test_deck_clone() { + let mut deck1 = Deck::new(); + deck1.deal(5); + + let deck2 = deck1.clone(); + assert_eq!(deck1.remaining(), deck2.remaining()); + assert_eq!(deck1.cards(), deck2.cards()); + } + + #[test] + fn test_deck_serialization_partial() { + let mut deck = Deck::new(); + deck.deal(10); // Remove 10 cards + + // Serialize partial deck + let json = serde_json::to_string(&deck).unwrap(); + let deserialized: Deck = serde_json::from_str(&json).unwrap(); + + assert_eq!(deck.remaining(), deserialized.remaining()); + assert_eq!(deck.cards(), deserialized.cards()); + + // Should be able to continue dealing from deserialized deck + let mut deserialized = deserialized; + let more_cards = deserialized.deal(5); + assert_eq!(more_cards.len(), 5); + } + + #[test] + fn test_deck_deal_uniqueness() { + let mut deck = Deck::new(); + + // Deal all cards and ensure uniqueness + let mut all_cards = Vec::new(); + while let Some(card) = deck.deal_one() { + all_cards.push(card); + } + + assert_eq!(all_cards.len(), 52); + + let mut card_set = HashSet::new(); + for card in all_cards { + assert!(card_set.insert(card), "Duplicate card found: {}", card); + } + assert_eq!(card_set.len(), 52); + } + + #[test] + fn test_deck_performance() { + use std::time::Instant; + + // Test creation performance + let start = Instant::now(); + for _ in 0..1000 { + let _deck = Deck::new(); + } + let creation_time = start.elapsed(); + + // Test dealing performance + let mut deck = Deck::new(); + let start = Instant::now(); + for _ in 0..52 { + deck.deal_one(); + } + let dealing_time = start.elapsed(); + + // Should be very fast + assert!( + creation_time.as_millis() < 100, + "Deck creation too slow: {:?}", + creation_time + ); + assert!( + dealing_time.as_millis() < 10, + "Dealing too slow: {:?}", + dealing_time + ); + } +} diff --git a/holdem_core/src/errors.rs b/holdem_core/src/errors.rs new file mode 100644 index 0000000..dd71a99 --- /dev/null +++ b/holdem_core/src/errors.rs @@ -0,0 +1,202 @@ +//! # Poker Error Types +//! +//! This module defines comprehensive error types for the poker library, +//! replacing string-based errors with structured, type-safe error handling. + +use crate::card::Card; +use std::fmt; + +/// Comprehensive error type for all poker-related operations +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PokerError { + /// Invalid card rank (must be 0-12) + InvalidCardRank { rank: u8 }, + /// Invalid card suit (must be 0-3) + InvalidCardSuit { suit: u8 }, + /// Invalid card string format + InvalidCardString { string: String }, + /// Invalid rank character in card string + InvalidRankCharacter { character: char }, + /// Invalid suit character in card string + InvalidSuitCharacter { character: char }, + /// Duplicate card found + DuplicateCard(Card), + /// Invalid hand size (must be 0-7 cards) + InvalidHandSize { size: usize }, + /// Invalid hole cards notation length + InvalidHoleCardsNotationLength { length: usize }, + /// Invalid suitedness indicator (must be 's' or 'o') + InvalidSuitednessIndicator { indicator: char }, + /// Pairs cannot have suitedness indicators + PairsCannotHaveSuitedness, + /// Non-pair hole cards must specify suitedness + NonPairMustSpecifySuitedness, + /// Invalid street transition in board dealing + InvalidStreetTransition, + /// Flop must consist of exactly 3 cards + FlopMustBeThreeCards { actual: usize }, + /// Must have exactly 3 cards before dealing turn + MustHaveThreeCardsForTurn { actual: usize }, + /// Must have exactly 4 cards before dealing river + MustHaveFourCardsForRiver { actual: usize }, + /// Cannot deal from current street + CannotDealFromStreet { current_street: String }, + /// Combined hole cards and board exceed 7 cards + CombinedCardsExceedLimit { total: usize }, + /// Duplicate cards in new deal + DuplicateCardsInDeal, + /// New card duplicates existing board card + DuplicateWithExistingBoardCard(Card), +} + +impl fmt::Display for PokerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PokerError::InvalidCardRank { rank } => { + write!(f, "Invalid card rank: {}. Rank must be 0-12", rank) + } + PokerError::InvalidCardSuit { suit } => { + write!(f, "Invalid card suit: {}. Suit must be 0-3", suit) + } + PokerError::InvalidCardString { string } => { + write!( + f, + "Invalid card string: '{}'. Must be exactly 2 characters (rank + suit)", + string + ) + } + PokerError::InvalidRankCharacter { character } => { + write!( + f, + "Invalid rank character: '{}'. Must be 2-9, T, J, Q, K, or A", + character + ) + } + PokerError::InvalidSuitCharacter { character } => { + write!( + f, + "Invalid suit character: '{}'. Must be h, d, c, or s", + character + ) + } + PokerError::DuplicateCard(card) => { + write!(f, "Duplicate card found: {}", card) + } + PokerError::InvalidHandSize { size } => { + write!( + f, + "Invalid hand size: {}. Hand must contain 0-7 cards", + size + ) + } + PokerError::InvalidHoleCardsNotationLength { length } => { + write!( + f, + "Invalid hole cards notation length: {}. Must be 2 or 3 characters", + length + ) + } + PokerError::InvalidSuitednessIndicator { indicator } => { + write!( + f, + "Invalid suitedness indicator: '{}'. Must be 's' or 'o'", + indicator + ) + } + PokerError::PairsCannotHaveSuitedness => { + write!(f, "Pairs cannot have suitedness indicators") + } + PokerError::NonPairMustSpecifySuitedness => { + write!( + f, + "Non-pair hole cards must specify suitedness with 's' or 'o'" + ) + } + PokerError::InvalidStreetTransition => { + write!(f, "Invalid street transition") + } + PokerError::FlopMustBeThreeCards { actual } => { + write!(f, "Flop must consist of exactly 3 cards, got {}", actual) + } + PokerError::MustHaveThreeCardsForTurn { actual } => { + write!( + f, + "Must have exactly 3 cards before dealing turn, got {}", + actual + ) + } + PokerError::MustHaveFourCardsForRiver { actual } => { + write!( + f, + "Must have exactly 4 cards before dealing river, got {}", + actual + ) + } + PokerError::CannotDealFromStreet { current_street } => { + write!(f, "Cannot deal from street: {}", current_street) + } + PokerError::CombinedCardsExceedLimit { total } => { + write!( + f, + "Combined hole cards and board must result in at most 7 cards, got {}", + total + ) + } + PokerError::DuplicateCardsInDeal => { + write!(f, "Duplicate cards in new deal") + } + PokerError::DuplicateWithExistingBoardCard(card) => { + write!(f, "New card duplicates existing board card: {}", card) + } + } + } +} + +impl std::error::Error for PokerError {} + +/// Convert PokerError to String for backward compatibility +impl From for String { + fn from(error: PokerError) -> String { + error.to_string() + } +} + +/// Convert &str to PokerError for backward compatibility +impl From<&str> for PokerError { + fn from(s: &str) -> PokerError { + PokerError::InvalidCardString { + string: s.to_string(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::card::Card; + + #[test] + fn test_error_display() { + let error = PokerError::InvalidCardRank { rank: 13 }; + assert_eq!( + error.to_string(), + "Invalid card rank: 13. Rank must be 0-12" + ); + + let error = PokerError::InvalidCardSuit { suit: 4 }; + assert_eq!(error.to_string(), "Invalid card suit: 4. Suit must be 0-3"); + + let error = PokerError::DuplicateCard(Card::new(12, 3).unwrap()); + assert!(error.to_string().contains("Duplicate card found")); + } + + #[test] + fn test_error_conversion() { + let error = PokerError::InvalidCardRank { rank: 13 }; + let string: String = error.into(); + assert!(string.contains("Invalid card rank")); + + let error: PokerError = "test string".into(); + assert!(matches!(error, PokerError::InvalidCardString { .. })); + } +} diff --git a/holdem_core/src/hand.rs b/holdem_core/src/hand.rs new file mode 100644 index 0000000..dc29d95 --- /dev/null +++ b/holdem_core/src/hand.rs @@ -0,0 +1,1130 @@ +//! # Hand Module +//! +//! This module provides the `Hand` struct for representing a complete poker hand +//! consisting of 5-7 cards for evaluation in Texas Hold'em poker. The Hand struct +//! supports evaluation of the best 5-card poker hand from available cards. +//! +//! ## Hand Representation +//! +//! A Hand consists of 0-7 distinct cards representing the complete hand available +//! to a player (hole cards + board cards). +//! +//! ## Examples +//! +//! ### Creating Hands +//! +//! ```rust +//! use holdem_core::{Hand, HoleCards, Board, Card}; +//! use std::str::FromStr; +//! +//! // Create from hole cards and board +//! let hole_cards = HoleCards::from_notation("AKs").unwrap(); +//! let mut board = Board::new(); +//! // ... add board cards +//! let hand = Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); +//! +//! // Create from cards directly +//! let cards = vec![ +//! Card::from_str("As").unwrap(), +//! Card::from_str("Kd").unwrap(), +//! Card::from_str("Qh").unwrap(), +//! Card::from_str("Js").unwrap(), +//! Card::from_str("Tc").unwrap(), +//! ]; +//! let hand = Hand::new(cards).unwrap(); +//! ``` +//! +//! ### Hand Evaluation +//! +//! ```rust +//! use holdem_core::{Hand, Card}; +//! use std::str::FromStr; +//! +//! let cards = vec![ +//! Card::from_str("As").unwrap(), +//! Card::from_str("Kd").unwrap(), +//! Card::from_str("Qh").unwrap(), +//! Card::from_str("Js").unwrap(), +//! Card::from_str("Tc").unwrap(), +//! ]; +//! let hand = Hand::new(cards).unwrap(); +//! let strength = hand.strength(); // Placeholder for future evaluator +//! let best_five = hand.best_five_cards(); // Get best 5-card hand +//! ``` + +use crate::card::Card; +use crate::errors::PokerError; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fmt; +use std::str::FromStr; + +/// Represents a complete poker hand consisting of 5-7 cards for evaluation +/// This includes hole cards combined with board cards for hand strength calculation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Hand { + /// All cards in the hand (5-7 cards), sorted by rank descending + pub cards: [Card; 7], + /// Number of valid cards (5-7) + pub len: usize, +} + +impl Hand { + /// Creates a new hand from a vector of 0-7 distinct cards + /// + /// Cards are sorted in rank-descending order for consistent evaluation. + /// Validates that all cards are distinct and the total count doesn't exceed 7. + /// + /// # Arguments + /// * `cards` - A vector of distinct cards (0-7 cards) + /// + /// # Returns + /// * `Ok(Hand)` - A valid hand with sorted cards + /// * `Err(PokerError)` - Error if validation fails + /// + /// # Panics + /// + /// This method does not panic, but returns an error if cards are invalid or duplicated. + /// + /// # Examples + /// ``` + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// assert_eq!(hand.len, 3); + /// ``` + pub fn new(cards: Vec) -> Result { + let len = cards.len(); + if len > 7 { + return Err(PokerError::InvalidHandSize { size: len }); + } + + // Check for duplicates + let mut unique_cards = std::collections::HashSet::new(); + for &card in &cards { + if !unique_cards.insert(card) { + return Err(PokerError::DuplicateCard(card)); + } + } + + let mut sorted_cards = cards; + // Sort cards with higher rank first, then higher suit + sorted_cards.sort_by(|a, b| b.cmp(a)); + + let mut hand_cards = [Card::new(0, 0).unwrap(); 7]; // dummy initialization + for (i, card) in sorted_cards.into_iter().enumerate() { + hand_cards[i] = card; + } + + Ok(Self { + cards: hand_cards, + len, + }) + } + + /// Creates a hand from hole cards and board cards + /// + /// Combines a player's private hole cards with the public board cards to form + /// a complete hand for evaluation. The resulting hand will have 2-7 cards total. + /// + /// # Arguments + /// * `hole_cards` - The player's two private cards + /// * `board` - The current board state with community cards + /// + /// # Returns + /// * `Ok(Hand)` - A valid hand combining hole cards and board cards + /// * `Err(PokerError)` - Error if the combination would exceed 7 cards + /// + /// # Panics + /// + /// This method does not panic, but returns an error if the combination would exceed 7 cards. + /// + /// # Examples + /// ``` + /// use holdem_core::{HoleCards, Board, Hand, Card}; + /// use std::str::FromStr; + /// + /// let hole_cards = HoleCards::from_notation("AKs").unwrap(); + /// let mut board = Board::new(); + /// board.deal_flop(vec![ + /// Card::from_str("Qh").unwrap(), + /// Card::from_str("Js").unwrap(), + /// Card::from_str("Tc").unwrap(), + /// ]).unwrap(); + /// + /// let hand = Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); + /// assert_eq!(hand.len, 5); + /// ``` + pub fn from_hole_cards_and_board( + hole_cards: &crate::hole_cards::HoleCards, + board: &crate::board::Board, + ) -> Result { + let mut all_cards = Vec::new(); + + // Add hole cards + all_cards.extend_from_slice(&hole_cards.cards); + + // Add board cards + all_cards.extend_from_slice(board.visible_cards()); + + if all_cards.len() > 7 { + return Err(PokerError::CombinedCardsExceedLimit { + total: all_cards.len(), + }); + } + + Self::new(all_cards) + } + + /// Convenience constructor to create a hand from a slice of cards + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// Card::from_str("Js").unwrap(), + /// Card::from_str("Ts").unwrap(), + /// ]; + /// let hand = Hand::from_cards(&cards).unwrap(); + /// assert_eq!(hand.len, 5); + /// ``` + pub fn from_cards(cards: &[Card]) -> Result { + Self::new(cards.to_vec()) + } + + /// Convenience constructor to create a hand from poker notation + /// + /// Supports formats like "As Ks Qs Js Ts" for a 5-card hand. + /// Cards are separated by whitespace. + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::Hand; + /// + /// let hand = Hand::from_notation("As Ks Qs Js Ts").unwrap(); + /// assert_eq!(hand.len, 5); + /// + /// let hand = Hand::from_notation("Ah Kh Qh Jh").unwrap(); + /// assert_eq!(hand.len, 4); + /// ``` + pub fn from_notation(notation: &str) -> Result { + let card_strings: Vec<&str> = notation.split_whitespace().collect(); + if card_strings.is_empty() { + return Self::new(Vec::new()); + } + + let mut cards = Vec::new(); + for card_str in card_strings { + if card_str.is_empty() { + continue; + } + let card = Card::from_str(card_str)?; + cards.push(card); + } + + Self::new(cards) + } + + /// Returns the cards as a slice (only valid cards) + /// + /// Returns only the valid cards in the hand, excluding any unused elements + /// in the internal array. Cards are sorted in rank-descending order. + /// + /// # Returns + /// A slice containing the valid cards in the hand + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// let card_slice = hand.cards(); + /// assert_eq!(card_slice.len(), 2); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + /// + /// # Examples + /// ``` + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// let card_slice = hand.cards(); + /// assert_eq!(card_slice.len(), 2); + /// ``` + pub fn cards(&self) -> &[Card] { + &self.cards[0..self.len] + } + + /// Returns the cards as a slice (alias for cards()) + /// + /// Convenience method that provides the same functionality as `cards()`. + /// + /// # Returns + /// A slice containing the valid cards in the hand + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// let card_slice = hand.as_slice(); + /// assert_eq!(card_slice.len(), 2); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn as_slice(&self) -> &[Card] { + self.cards() + } + + /// Returns an iterator over the valid cards + /// + /// Provides an iterator that yields references to each valid card in the hand, + /// in rank-descending order. + /// + /// # Returns + /// An iterator over the valid cards in the hand + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// + /// for card in hand.iter() { + /// println!("Card: {}", card); + /// } + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + /// + /// # Examples + /// ``` + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// + /// for card in hand.iter() { + /// println!("Card: {}", card); + /// } + /// ``` + pub fn iter(&self) -> std::slice::Iter { + self.cards[0..self.len].iter() + } + + /// Placeholder for hand strength evaluation (to be implemented with fast evaluator) + /// + /// Returns a placeholder strength value. In the future, this will integrate with + /// a high-performance hand evaluation system using lookup tables for instant + /// poker hand ranking. + /// + /// # Returns + /// Currently returns 0 (placeholder). Future implementation will return + /// a strength value where higher values represent stronger hands. + /// + /// # Note + /// This is a temporary implementation. The actual hand evaluation will be + /// implemented in Phase 1 with optimized lookup tables. + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// Card::from_str("Js").unwrap(), + /// Card::from_str("Tc").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// let strength = hand.strength(); // Placeholder for future evaluator + /// assert_eq!(strength, 0); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn strength(&self) -> u32 { + // TODO: Integrate with high-performance hand evaluation system in Phase 1 + // For now, return a placeholder value + 0 + } + + /// Returns the best 5-card hand from the available cards + /// + /// For hands with 5 or fewer cards, returns all cards. + /// For hands with 6-7 cards, returns the first 5 cards (sorted). + /// In the future, this will implement proper poker hand selection algorithms. + /// + /// # Returns + /// An array of 5 cards representing the best poker hand + /// + /// # Note + /// This is a placeholder implementation. The actual algorithm will select + /// the optimal 5-card combination from 6-7 cards for maximum poker strength. + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// Card::from_str("Js").unwrap(), + /// Card::from_str("Tc").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// let best_five = hand.best_five_cards(); + /// assert_eq!(best_five.len(), 5); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + /// + /// # Examples + /// ``` + /// use holdem_core::{Hand, Card}; + /// use std::str::FromStr; + /// + /// let cards = vec![ + /// Card::from_str("As").unwrap(), + /// Card::from_str("Kd").unwrap(), + /// Card::from_str("Qh").unwrap(), + /// Card::from_str("Js").unwrap(), + /// Card::from_str("Tc").unwrap(), + /// ]; + /// let hand = Hand::new(cards).unwrap(); + /// let best_five = hand.best_five_cards(); + /// assert_eq!(best_five.len(), 5); + /// ``` + pub fn best_five_cards(&self) -> [Card; 5] { + // TODO: Implement proper 5-card selection algorithm + // For now, return first 5 cards (sorted) + let mut best = [Card::new(0, 0).unwrap(); 5]; + for i in 0..5 { + best[i] = self.cards[i]; + } + best + } +} + +impl fmt::Display for Hand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Hand({} cards: ", self.len)?; + for (i, card) in self.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", card)?; + } + write!(f, ")") + } +} + +impl PartialOrd for Hand { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Hand { + fn cmp(&self, other: &Self) -> Ordering { + // Compare by hand strength (higher strength is better) + // TODO: Replace with proper poker hand ranking when evaluator is integrated + self.strength().cmp(&other.strength()).reverse() // Reverse because higher strength should be greater + } +} + +impl IntoIterator for Hand { + type Item = Card; + type IntoIter = std::iter::Take>; + + fn into_iter(self) -> Self::IntoIter { + self.cards.into_iter().take(self.len) + } +} + +impl<'a> IntoIterator for &'a Hand { + type Item = &'a Card; + type IntoIter = std::iter::Take>; + + fn into_iter(self) -> Self::IntoIter { + self.cards.iter().take(self.len) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + + #[test] + fn test_hand_creation_valid() { + let cards = vec![ + Card::new(12, 2).unwrap(), // Ace of Spades + Card::new(11, 0).unwrap(), // King of Hearts + Card::new(10, 1).unwrap(), // Queen of Diamonds + Card::new(9, 3).unwrap(), // Jack of Clubs + Card::new(8, 2).unwrap(), // Ten of Spades + ]; + let hand = Hand::new(cards).unwrap(); + assert_eq!(hand.len, 5); + assert_eq!(hand.cards()[0], Card::new(12, 2).unwrap()); + assert_eq!(hand.cards()[4], Card::new(8, 2).unwrap()); + } + + #[test] + fn test_hand_creation_invalid_length() { + let cards = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + Card::new(7, 0).unwrap(), + Card::new(6, 1).unwrap(), + Card::new(5, 3).unwrap(), // 8 cards + ]; + assert!(Hand::new(cards).is_err()); + } + + #[test] + fn test_hand_creation_empty() { + let cards = vec![]; + let hand = Hand::new(cards).unwrap(); + assert_eq!(hand.len, 0); + } + + #[test] + fn test_hand_creation_duplicate_cards() { + let cards = vec![ + Card::new(12, 2).unwrap(), + Card::new(12, 2).unwrap(), // Duplicate + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]; + assert!(Hand::new(cards).is_err()); + } + + #[test] + fn test_hand_from_hole_cards_and_board() { + use crate::board::Board; + use crate::hole_cards::HoleCards; + + let hole_cards = + HoleCards::new(Card::new(12, 2).unwrap(), Card::new(11, 0).unwrap()).unwrap(); + let mut board = Board::new(); + + // Add flop + board + .deal_flop(vec![ + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]) + .unwrap(); + + let hand = Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); + assert_eq!(hand.len, 5); + assert_eq!(hand.cards()[0], Card::new(12, 2).unwrap()); // Ace first (sorted) + assert_eq!(hand.cards()[1], Card::new(11, 0).unwrap()); // King + assert_eq!(hand.cards()[2], Card::new(10, 1).unwrap()); // Queen + assert_eq!(hand.cards()[3], Card::new(9, 3).unwrap()); // Jack + assert_eq!(hand.cards()[4], Card::new(8, 2).unwrap()); // Ten + } + + #[test] + fn test_hand_strength_placeholder() { + let cards = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]; + let hand = Hand::new(cards).unwrap(); + assert_eq!(hand.strength(), 0); // Placeholder value + } + + #[test] + fn test_hand_best_five_cards() { + let cards = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + Card::new(7, 0).unwrap(), + Card::new(6, 1).unwrap(), + ]; + let hand = Hand::new(cards).unwrap(); + let best_five = hand.best_five_cards(); + assert_eq!(best_five.len(), 5); + // Should be sorted highest first + assert_eq!(best_five[0], Card::new(12, 2).unwrap()); + assert_eq!(best_five[4], Card::new(8, 2).unwrap()); + } + + #[test] + fn test_hand_comparison() { + let cards1 = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]; + let hand1 = Hand::new(cards1).unwrap(); + + let cards2 = vec![ + Card::new(11, 2).unwrap(), + Card::new(10, 0).unwrap(), + Card::new(9, 1).unwrap(), + Card::new(8, 3).unwrap(), + Card::new(7, 2).unwrap(), + ]; + let hand2 = Hand::new(cards2).unwrap(); + + // For now, all hands have same strength (0), so they're equal + assert_eq!(hand1.cmp(&hand2), std::cmp::Ordering::Equal); + } + + #[test] + fn test_hand_serialization() { + let cards = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]; + let hand = Hand::new(cards).unwrap(); + + // Test JSON serialization + let json = serde_json::to_string(&hand).unwrap(); + let deserialized: Hand = serde_json::from_str(&json).unwrap(); + assert_eq!(hand, deserialized); + + // Test TOML serialization + let toml = toml::to_string(&hand).unwrap(); + let deserialized: Hand = toml::from_str(&toml).unwrap(); + assert_eq!(hand, deserialized); + } + + #[test] + fn test_hand_iteration() { + let cards = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]; + let hand = Hand::new(cards).unwrap(); + + // Test owned iteration + let collected: Vec = hand.into_iter().collect(); + assert_eq!(collected.len(), 5); + + // Test borrowed iteration + let hand = Hand::new(vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]) + .unwrap(); + let mut iter = hand.iter(); + for _ in 0..5 { + assert!(iter.next().is_some()); + } + assert!(iter.next().is_none()); + } + + #[test] + fn test_hand_display() { + let cards = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]; + let hand = Hand::new(cards).unwrap(); + let display = format!("{}", hand); + println!("Display: {}", display); + assert!(display.starts_with("Hand(5 cards: ")); + assert!(display.contains("Ac")); + assert!(display.contains("Kh")); + assert!(display.contains("Qd")); + assert!(display.contains("Js")); + assert!(display.contains("Tc")); + } + + #[test] + fn test_hand_hashing() { + let mut set = HashSet::new(); + + let cards1 = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 2).unwrap(), + ]; + let hand1 = Hand::new(cards1).unwrap(); + + let cards2 = vec![ + Card::new(11, 2).unwrap(), + Card::new(10, 0).unwrap(), + Card::new(9, 1).unwrap(), + Card::new(8, 3).unwrap(), + Card::new(7, 2).unwrap(), + ]; + let hand2 = Hand::new(cards2).unwrap(); + + assert!(set.insert(hand1)); + assert!(set.insert(hand2)); + assert_eq!(set.len(), 2); + } + + #[test] + fn test_performance_hand_creation() { + use std::time::Instant; + + let start = Instant::now(); + let mut hands = Vec::new(); + + // Create 10,000 hands + for i in 0..10_000 { + let cards = vec![ + Card::new(12, 2).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 1).unwrap(), + Card::new(9, 3).unwrap(), + Card::new((7 - (i % 5)) as u8, 2).unwrap(), // Decreasing rank to avoid duplicates + ]; + hands.push(Hand::new(cards).unwrap()); + } + + let duration = start.elapsed(); + assert_eq!(hands.len(), 10_000); + + // Should be very fast (< 100ms typically) + assert!( + duration.as_millis() < 500, + "Hand creation took too long: {:?}", + duration + ); + } + + #[test] + fn test_hand_construction_various_lengths() { + // Test 5 cards + let cards5 = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + ]; + let hand5 = Hand::new(cards5).unwrap(); + assert_eq!(hand5.len, 5); + + // Test 6 cards + let cards6 = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + Card::new(7, 1).unwrap(), + ]; + let hand6 = Hand::new(cards6).unwrap(); + assert_eq!(hand6.len, 6); + + // Test 7 cards + let cards7 = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + Card::new(7, 1).unwrap(), + Card::new(6, 2).unwrap(), + ]; + let hand7 = Hand::new(cards7).unwrap(); + assert_eq!(hand7.len, 7); + } + + #[test] + fn test_hand_construction_edge_cases() { + // Test that invalid cards cannot be created + assert!(Card::new(13, 0).is_err()); // Invalid rank + assert!(Card::new(12, 4).is_err()); // Invalid suit + + // Test with all same suit + let cards = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 0).unwrap(), + Card::new(10, 0).unwrap(), + Card::new(9, 0).unwrap(), + Card::new(8, 0).unwrap(), + ]; + let hand = Hand::new(cards).unwrap(); + assert_eq!(hand.len, 5); + + // Test with all same rank (impossible in poker, but test duplicate detection) + let cards = vec![ + Card::new(12, 0).unwrap(), + Card::new(12, 1).unwrap(), + Card::new(12, 2).unwrap(), + Card::new(12, 3).unwrap(), + Card::new(11, 0).unwrap(), + ]; + let hand = Hand::new(cards).unwrap(); // These are distinct cards + assert_eq!(hand.len, 5); + } + + #[test] + fn test_hand_construction_duplicate_detection() { + // Test exact duplicate cards + let cards = vec![ + Card::new(12, 0).unwrap(), + Card::new(12, 0).unwrap(), // Exact duplicate + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + ]; + assert!(Hand::new(cards).is_err()); + + // Test multiple duplicates + let cards = vec![ + Card::new(12, 0).unwrap(), + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + ]; + assert!(Hand::new(cards).is_err()); + } + + #[test] + fn test_hand_strength_placeholder_comprehensive() { + // Test various hand sizes + for len in 5..=7 { + let mut cards = Vec::new(); + for i in 0..len { + cards.push(Card::new((12 - i) as u8, (i % 4) as u8).unwrap()); + } + let hand = Hand::new(cards).unwrap(); + assert_eq!( + hand.strength(), + 0, + "Strength should be placeholder 0 for {} cards", + len + ); + } + } + + #[test] + fn test_hand_best_five_cards_comprehensive() { + // Test with 5 cards (should return all) + let cards5 = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + ]; + let hand5 = Hand::new(cards5).unwrap(); + let best5 = hand5.best_five_cards(); + assert_eq!( + best5, + [ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap() + ] + ); + + // Test with 6 cards (should return first 5 sorted) + let cards6 = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + Card::new(7, 1).unwrap(), + ]; + let hand6 = Hand::new(cards6).unwrap(); + let best6 = hand6.best_five_cards(); + assert_eq!( + best6, + [ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap() + ] + ); + + // Test with 7 cards + let cards7 = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + Card::new(7, 1).unwrap(), + Card::new(6, 2).unwrap(), + ]; + let hand7 = Hand::new(cards7).unwrap(); + let best7 = hand7.best_five_cards(); + assert_eq!( + best7, + [ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap() + ] + ); + } + + #[test] + fn test_hand_comparison_placeholder() { + // Since strength is placeholder (0), hands with same strength should compare as equal + let cards = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + ]; + let hand1 = Hand::new(cards.clone()).unwrap(); + let hand2 = Hand::new(cards).unwrap(); + + assert_eq!(hand1.cmp(&hand2), Ordering::Equal); + assert_eq!(hand1, hand2); // Same cards + } + + #[test] + fn test_hand_serialization_edge_cases() { + // Test empty hand + let empty_hand = Hand::new(vec![]).unwrap(); + let json = serde_json::to_string(&empty_hand).unwrap(); + let deserialized: Hand = serde_json::from_str(&json).unwrap(); + assert_eq!(empty_hand, deserialized); + + // Test hand with 7 cards + let cards7 = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + Card::new(7, 1).unwrap(), + Card::new(6, 2).unwrap(), + ]; + let hand7 = Hand::new(cards7).unwrap(); + let json = serde_json::to_string(&hand7).unwrap(); + let deserialized: Hand = serde_json::from_str(&json).unwrap(); + assert_eq!(hand7, deserialized); + + // Test TOML with various cards + let toml = toml::to_string(&hand7).unwrap(); + let deserialized: Hand = toml::from_str(&toml).unwrap(); + assert_eq!(hand7, deserialized); + } + + #[test] + fn test_hand_iteration_comprehensive() { + // Test iteration with different lengths + for len in 0..=7 { + let mut cards = Vec::new(); + for i in 0..len { + cards.push(Card::new((12 - i) as u8, (i % 4) as u8).unwrap()); + } + let hand = Hand::new(cards).unwrap(); + + // Test owned iteration + let collected: Vec = hand.into_iter().collect(); + assert_eq!(collected.len(), len); + + // Test borrowed iteration + let hand = Hand::new( + (0..len) + .map(|i| Card::new((12 - i) as u8, (i % 4) as u8).unwrap()) + .collect(), + ) + .unwrap(); + let mut count = 0; + for _ in &hand { + count += 1; + } + assert_eq!(count, len); + } + } + + #[test] + fn test_hand_display_comprehensive() { + // Test empty hand + let empty = Hand::new(vec![]).unwrap(); + assert_eq!(format!("{}", empty), "Hand(0 cards: )"); + + // Test single card + let single = Hand::new(vec![Card::new(12, 0).unwrap()]).unwrap(); + assert_eq!(format!("{}", single), "Hand(1 cards: Ah)"); + + // Test multiple cards + let cards = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + ]; + let hand = Hand::new(cards).unwrap(); + let display = format!("{}", hand); + assert!(display.starts_with("Hand(3 cards: ")); + assert!(display.contains("Ah")); + assert!(display.contains("Kd")); + assert!(display.contains("Qc")); + assert!(display.ends_with(")")); + } + + #[test] + fn test_hand_hashing_consistency() { + let mut set = HashSet::new(); + + // Create same hand multiple times + let cards = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(8, 0).unwrap(), + ]; + + let hand1 = Hand::new(cards.clone()).unwrap(); + let hand2 = Hand::new(cards).unwrap(); + + // Same hands should hash the same + assert_eq!(hand1, hand2); + // Note: In practice, we'd use a proper hash function, but for test we check equality + + assert!(set.insert(hand1)); + assert!(!set.insert(hand2)); // Should not insert duplicate + assert_eq!(set.len(), 1); + + // Different hands + let diff_cards = vec![ + Card::new(7, 0).unwrap(), + Card::new(6, 1).unwrap(), + Card::new(5, 2).unwrap(), + Card::new(4, 3).unwrap(), + Card::new(3, 0).unwrap(), + ]; + let hand3 = Hand::new(diff_cards).unwrap(); + assert!(set.insert(hand3)); + assert_eq!(set.len(), 2); + } + + #[test] + fn test_hand_boundary_conditions() { + // Test maximum valid length + let cards7 = (0..7) + .map(|i| Card::new((12 - i) as u8, (i % 4) as u8).unwrap()) + .collect(); + let hand = Hand::new(cards7).unwrap(); + assert_eq!(hand.len, 7); + + // Test minimum valid length (0) + let hand_empty = Hand::new(vec![]).unwrap(); + assert_eq!(hand_empty.len, 0); + + // Test that cards beyond len are not accessed + let hand = Hand::new(vec![Card::new(12, 0).unwrap()]).unwrap(); + assert_eq!(hand.len, 1); + assert_eq!(hand.cards()[0], Card::new(12, 0).unwrap()); + // The remaining cards in the array are uninitialized but shouldn't be accessed + } + + #[test] + fn test_hand_invalid_inputs() { + // Test more than 7 cards + let too_many = (0..8) + .map(|i| Card::new((12 - i) as u8, (i % 4) as u8).unwrap()) + .collect(); + assert!(Hand::new(too_many).is_err()); + + // Test duplicate detection with complex duplicates + let cards = vec![ + Card::new(12, 0).unwrap(), + Card::new(11, 1).unwrap(), + Card::new(10, 2).unwrap(), + Card::new(9, 3).unwrap(), + Card::new(12, 0).unwrap(), // Duplicate + ]; + assert!(Hand::new(cards).is_err()); + } +} diff --git a/holdem_core/src/hole_cards.rs b/holdem_core/src/hole_cards.rs new file mode 100644 index 0000000..19afc39 --- /dev/null +++ b/holdem_core/src/hole_cards.rs @@ -0,0 +1,746 @@ +//! # Hole Cards Module +//! +//! This module provides the `HoleCards` struct for representing a player's two private cards +//! in poker. Hole cards are distinct from complete hands and provide specialized methods +//! for analyzing preflop hand strength, suitedness, connectivity, and standard notation. +//! +//! ## Hole Cards Structure +//! +//! A `HoleCards` contains: +//! - An array of exactly two `Card` instances +//! - Methods for analyzing card relationships and properties +//! - Standard poker notation generation +//! - Efficient comparison and hashing +//! +//! ## Key Features +//! +//! - **Validation**: Ensures no duplicate cards during construction +//! - **Properties**: Pair detection, suitedness, connectivity analysis +//! - **Notation**: Standard poker abbreviations ("AKs", "QQ", "T9o") +//! - **Serialization**: Full serde support for persistence +//! - **Performance**: Optimized comparison and hashing for lookups +//! - **Access**: Methods for retrieving individual cards +//! +//! ## Examples +//! +//! ### Creating and Analyzing Hole Cards +//! +//! ```rust +//! use holdem_core::hole_cards::HoleCards; +//! use holdem_core::card::Card; +//! +//! // Create hole cards from strings +//! let hole_cards = HoleCards::from_notation("AKs").unwrap(); +//! +//! // Check properties +//! assert!(hole_cards.is_suited()); +//! assert!(!hole_cards.is_pair()); +//! assert_eq!(hole_cards.connectivity(), 0); // No gap between A and K +//! +//! // Get standard notation +//! assert_eq!(hole_cards.notation(), "AKs"); +//! ``` +//! +//! ### Working with Individual Cards +//! +//! ```rust +//! use holdem_core::hole_cards::HoleCards; +//! +//! let hole_cards = HoleCards::from_notation("QQ").unwrap(); +//! +//! // Access individual cards +//! let card1 = hole_cards.first_card(); +//! let card2 = hole_cards.second_card(); +//! +//! // Both cards are Queens +//! assert_eq!(card1.rank(), card2.rank()); +//! assert!(hole_cards.is_pair()); +//! ``` +//! +//! ## Design Decisions +//! +//! - **Fixed Size**: Always exactly two cards for hole cards +//! - **Validation**: Constructor validates no duplicates +//! - **Ordering**: Cards stored in rank-descending order for consistency +//! - **Notation**: Follows standard poker conventions (high-low, s/o for suited/offsuit) +//! - **Performance**: Implements Hash and efficient comparison +//! - **Safety**: Comprehensive error handling and validation + +use crate::card::Card; +use crate::errors::PokerError; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fmt; + +/// Represents a player's two private hole cards in poker +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct HoleCards { + /// The two cards, stored in rank-descending order + pub cards: [Card; 2], +} + +impl HoleCards { + /// Create new hole cards from two individual cards + /// + /// # Errors + /// Returns an error if the cards are identical (same rank and suit) + /// + /// # Examples + /// ``` + /// use holdem_core::hole_cards::HoleCards; + /// use holdem_core::card::Card; + /// use std::str::FromStr; + /// + /// let card1 = Card::from_str("As").unwrap(); + /// let card2 = Card::from_str("Ks").unwrap(); + /// let hole_cards = HoleCards::new(card1, card2).unwrap(); + /// ``` + /// + /// # Panics + /// + /// This method does not panic, but returns an error if cards are identical. + pub fn new(card1: Card, card2: Card) -> Result { + if card1 == card2 { + return Err(PokerError::DuplicateCard(card1)); + } + + // Store cards in rank-descending order + let (high, low) = if card1.rank() >= card2.rank() { + (card1, card2) + } else { + (card2, card1) + }; + + Ok(Self { cards: [high, low] }) + } + + /// Create hole cards from standard poker notation + /// + /// # Supported Formats + /// - "AKs" - suited Ace-King + /// - "QQ" - pocket Queens + /// - "T9o" - offsuit Ten-Nine + /// + /// # Errors + /// Returns an error for invalid notation or duplicate ranks with same suit + /// + /// # Examples + /// ``` + /// use holdem_core::hole_cards::HoleCards; + /// + /// let pocket_aces = HoleCards::from_notation("AA").unwrap(); + /// let pocket_kings = HoleCards::from_notation("KK").unwrap(); + /// let offsuit_jack_ten = HoleCards::from_notation("JTo").unwrap(); + /// ``` + /// + /// # Panics + /// + /// This method does not panic, but returns an error for invalid notation. + pub fn from_notation(s: &str) -> Result { + if s.len() < 2 || s.len() > 3 { + return Err(PokerError::InvalidHoleCardsNotationLength { length: s.len() }); + } + + let chars: Vec = s.chars().collect(); + + // Parse ranks + let rank1 = Card::rank_from_char(chars[0]).ok_or(PokerError::InvalidRankCharacter { + character: chars[0], + })?; + let rank2 = Card::rank_from_char(chars[1]).ok_or(PokerError::InvalidRankCharacter { + character: chars[1], + })?; + + // Determine suitedness + let suited = if s.len() == 3 { + // Pairs cannot have suitedness indicators + if rank1 == rank2 { + return Err(PokerError::PairsCannotHaveSuitedness); + } + match chars[2] { + 's' => true, + 'o' => false, + _ => { + return Err(PokerError::InvalidSuitednessIndicator { + indicator: chars[2], + }) + } + } + } else { + // For pairs, always use different suits + if rank1 != rank2 { + return Err(PokerError::NonPairMustSpecifySuitedness); + } + false // pairs are neither suited nor offsuit in notation + }; + + // Create cards - for suited hands, use spades; for offsuit, different suits + let suit1 = 3; // Spades + let suit2 = if suited { 3 } else { 2 }; // Spades or hearts + + let card1 = Card::new(rank1, suit1).unwrap(); + let card2 = Card::new(rank2, suit2).unwrap(); + + Self::new(card1, card2) + } + + /// Get the first card (higher rank) + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{HoleCards, Card}; + /// + /// let hole_cards = HoleCards::new(Card::new(12, 0).unwrap(), Card::new(11, 1).unwrap()).unwrap(); + /// let first = hole_cards.first_card(); + /// assert_eq!(first.rank(), 12); // Ace + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn first_card(&self) -> Card { + self.cards[0] + } + + /// Get the second card (lower rank) + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::{HoleCards, Card}; + /// + /// let hole_cards = HoleCards::new(Card::new(12, 0).unwrap(), Card::new(11, 1).unwrap()).unwrap(); + /// let second = hole_cards.second_card(); + /// assert_eq!(second.rank(), 11); // King + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn second_card(&self) -> Card { + self.cards[1] + } + + /// Check if the hole cards form a pocket pair + /// + /// # Examples + /// ``` + /// use holdem_core::hole_cards::HoleCards; + /// + /// let pocket_aces = HoleCards::from_notation("AA").unwrap(); + /// assert!(pocket_aces.is_pair()); + /// + /// let suited_king_queen = HoleCards::from_notation("KQs").unwrap(); + /// assert!(!suited_king_queen.is_pair()); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn is_pair(&self) -> bool { + self.cards[0].rank() == self.cards[1].rank() + } + + /// Check if both cards have the same suit (suited) + /// + /// # Examples + /// ``` + /// use holdem_core::hole_cards::HoleCards; + /// + /// let suited_ace_king = HoleCards::from_notation("AKs").unwrap(); + /// assert!(suited_ace_king.is_suited()); + /// + /// let offsuit_queen_jack = HoleCards::from_notation("QJo").unwrap(); + /// assert!(!offsuit_queen_jack.is_suited()); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn is_suited(&self) -> bool { + self.cards[0].suit() == self.cards[1].suit() + } + + /// Calculate the connectivity between the two cards + /// + /// Returns the number of ranks between the cards (0 = connected, 1 = one gap, etc.) + /// For pairs, returns 0 (perfectly connected). + /// + /// # Examples + /// ``` + /// use holdem_core::hole_cards::HoleCards; + /// + /// let connected = HoleCards::from_notation("KQs").unwrap(); + /// assert_eq!(connected.connectivity(), 0); // K and Q are adjacent + /// + /// let one_gapper = HoleCards::from_notation("J9s").unwrap(); + /// assert_eq!(one_gapper.connectivity(), 1); // J and 9 have one rank between (T) + /// + /// let pocket_pair = HoleCards::from_notation("77").unwrap(); + /// assert_eq!(pocket_pair.connectivity(), 0); // Pairs are perfectly connected + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn connectivity(&self) -> u8 { + if self.is_pair() { + 0 + } else { + let rank1 = self.cards[0].rank() as i8; + let rank2 = self.cards[1].rank() as i8; + (rank1 - rank2 - 1) as u8 + } + } + + /// Generate standard poker notation for the hole cards + /// + /// # Format + /// - Pairs: "AA", "KK", "22" + /// - Suited hands: "AKs", "QJs", "T9s" + /// - Offsuit hands: "AKo", "QJo", "T9o" + /// + /// # Examples + /// ``` + /// use holdem_core::hole_cards::HoleCards; + /// + /// let pocket_aces = HoleCards::from_notation("AA").unwrap(); + /// assert_eq!(pocket_aces.notation(), "AA"); + /// + /// let suited_king_queen = HoleCards::from_notation("KQs").unwrap(); + /// assert_eq!(suited_king_queen.notation(), "KQs"); + /// + /// let offsuit_jack_ten = HoleCards::from_notation("JTo").unwrap(); + /// assert_eq!(offsuit_jack_ten.notation(), "JTo"); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn notation(&self) -> String { + let rank1_char = Card::rank_to_char(self.cards[0].rank()); + let rank2_char = Card::rank_to_char(self.cards[1].rank()); + + if self.is_pair() { + format!("{}{}", rank1_char, rank2_char) + } else if self.is_suited() { + format!("{}{}s", rank1_char, rank2_char) + } else { + format!("{}{}o", rank1_char, rank2_char) + } + } + + /// Get a display-friendly string representation + /// + /// # Examples + /// + /// ```rust + /// use holdem_core::hole_cards::HoleCards; + /// + /// let aks = HoleCards::from_notation("AKs").unwrap(); + /// let string_repr = aks.to_string(); + /// assert!(string_repr.contains("A")); + /// assert!(string_repr.contains("K")); + /// ``` + /// + /// # Panics + /// + /// This method does not panic. + pub fn to_string(&self) -> String { + format!( + "{} {}", + self.cards[0].to_string(), + self.cards[1].to_string() + ) + } +} + +impl fmt::Display for HoleCards { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.notation()) + } +} + +impl PartialOrd for HoleCards { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HoleCards { + fn cmp(&self, other: &Self) -> Ordering { + // Compare by first card rank, then second card rank, then suitedness + match self.cards[0].rank().cmp(&other.cards[0].rank()) { + Ordering::Equal => match self.cards[1].rank().cmp(&other.cards[1].rank()) { + Ordering::Equal => self.is_suited().cmp(&other.is_suited()), + ord => ord, + }, + ord => ord, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn test_hole_cards_creation() { + let card1 = Card::from_str("As").unwrap(); + let card2 = Card::from_str("Ks").unwrap(); + let hole_cards = HoleCards::new(card1, card2).unwrap(); + + assert_eq!(hole_cards.first_card(), card1); + assert_eq!(hole_cards.second_card(), card2); + } + + #[test] + fn test_duplicate_cards_error() { + let card = Card::from_str("As").unwrap(); + let result = HoleCards::new(card, card); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), PokerError::DuplicateCard(_))); + } + + #[test] + fn test_from_notation_pairs() { + let aa = HoleCards::from_notation("AA").unwrap(); + assert!(aa.is_pair()); + assert_eq!(aa.notation(), "AA"); + + let kk = HoleCards::from_notation("KK").unwrap(); + assert!(kk.is_pair()); + assert_eq!(kk.notation(), "KK"); + } + + #[test] + fn test_from_notation_suited() { + let aks = HoleCards::from_notation("AKs").unwrap(); + assert!(aks.is_suited()); + assert!(!aks.is_pair()); + assert_eq!(aks.notation(), "AKs"); + assert_eq!(aks.connectivity(), 0); + } + + #[test] + fn test_from_notation_offsuit() { + let ako = HoleCards::from_notation("AKo").unwrap(); + assert!(!ako.is_suited()); + assert!(!ako.is_pair()); + assert_eq!(ako.notation(), "AKo"); + assert_eq!(ako.connectivity(), 0); + } + + #[test] + fn test_connectivity() { + let connected = HoleCards::from_notation("KQs").unwrap(); + assert_eq!(connected.connectivity(), 0); + + let one_gapper = HoleCards::from_notation("J9s").unwrap(); + assert_eq!(one_gapper.connectivity(), 1); + + let two_gapper = HoleCards::from_notation("K8s").unwrap(); + assert_eq!(two_gapper.connectivity(), 4); // K to 8 has Q, J, T, 9 between - 4 gaps + + let pair = HoleCards::from_notation("77").unwrap(); + assert_eq!(pair.connectivity(), 0); + } + + #[test] + fn test_invalid_notation() { + assert!(HoleCards::from_notation("").is_err()); + assert!(HoleCards::from_notation("A").is_err()); + assert!(HoleCards::from_notation("AKx").is_err()); + assert!(HoleCards::from_notation("AK").is_err()); // Missing suitedness for non-pair + assert!(HoleCards::from_notation("1K").is_err()); // Invalid rank + } + + #[test] + fn test_ordering() { + let aks = HoleCards::from_notation("AKs").unwrap(); + let ako = HoleCards::from_notation("AKo").unwrap(); + let aqs = HoleCards::from_notation("AQs").unwrap(); + + assert!(aks > ako); // Suited beats offsuit for same ranks + assert!(aqs < aks); // AK beats AQ + } + + #[test] + fn test_display() { + let aks = HoleCards::from_notation("AKs").unwrap(); + assert_eq!(format!("{}", aks), "AKs"); + + let qq = HoleCards::from_notation("QQ").unwrap(); + assert_eq!(format!("{}", qq), "QQ"); + } + + #[test] + fn test_serialization() { + let hole_cards = HoleCards::from_notation("AKs").unwrap(); + let serialized = serde_json::to_string(&hole_cards).unwrap(); + let deserialized: HoleCards = serde_json::from_str(&serialized).unwrap(); + assert_eq!(hole_cards, deserialized); + } + + #[test] + fn test_hole_cards_validation_edge_cases() { + // Test with same rank different suits (should work) + let card1 = Card::new(12, 3).unwrap(); // Ace of spades + let card2 = Card::new(12, 0).unwrap(); // Ace of hearts + let hole_cards = HoleCards::new(card1, card2).unwrap(); + assert!(hole_cards.is_pair()); + + // Test with different ranks same suit (should work) + let card1 = Card::new(12, 3).unwrap(); // Ace of spades + let card2 = Card::new(11, 3).unwrap(); // King of spades + let hole_cards = HoleCards::new(card1, card2).unwrap(); + assert!(hole_cards.is_suited()); + + // Test duplicate cards (should fail) + let card = Card::new(12, 3).unwrap(); + assert!(HoleCards::new(card, card).is_err()); + } + + #[test] + fn test_hole_cards_properties_comprehensive() { + // Test all possible pair combinations + for rank in 0..13 { + let notation = format!("{}{}", Card::rank_to_char(rank), Card::rank_to_char(rank)); + let hole_cards = HoleCards::from_notation(¬ation).unwrap(); + assert!(hole_cards.is_pair()); + assert!(!hole_cards.is_suited()); // Pairs are neither suited nor offsuit + assert_eq!(hole_cards.connectivity(), 0); + assert_eq!(hole_cards.notation(), notation); + } + + // Test suited hands + let suited_hands = [ + "AKs", "AQs", "AJs", "ATs", "A9s", "A8s", "A7s", "A6s", "A5s", "A4s", "A3s", "A2s", + "KQs", "KJs", "KTs", "K9s", "K8s", "K7s", "K6s", "K5s", "K4s", "K3s", "K2s", "QJs", + "QTs", "Q9s", "Q8s", "Q7s", "Q6s", "Q5s", "Q4s", "Q3s", "Q2s", + ]; + + for hand in suited_hands.iter() { + let hole_cards = HoleCards::from_notation(hand).unwrap(); + assert!(hole_cards.is_suited()); + assert!(!hole_cards.is_pair()); + assert_eq!(hole_cards.notation(), *hand); + } + + // Test offsuit hands + let offsuit_hands = [ + "AKo", "AQo", "AJo", "ATo", "A9o", "A8o", "A7o", "A6o", "A5o", "A4o", "A3o", "A2o", + ]; + + for hand in offsuit_hands.iter() { + let hole_cards = HoleCards::from_notation(hand).unwrap(); + assert!(!hole_cards.is_suited()); + assert!(!hole_cards.is_pair()); + assert_eq!(hole_cards.notation(), *hand); + } + } + + #[test] + fn test_hole_cards_connectivity_edge_cases() { + // Test maximum gap (A2) + let a2s = HoleCards::from_notation("A2s").unwrap(); + assert_eq!(a2s.connectivity(), 11); // A to 2 has K,Q,J,T,9,8,7,6,5,4,3 between - 11 gaps + + // Test minimum gap (AK, KQ, etc.) + let aks = HoleCards::from_notation("AKs").unwrap(); + assert_eq!(aks.connectivity(), 0); + + let kqs = HoleCards::from_notation("KQs").unwrap(); + assert_eq!(kqs.connectivity(), 0); + + // Test various gaps + let j9s = HoleCards::from_notation("J9s").unwrap(); + assert_eq!(j9s.connectivity(), 1); // J to 9 has T between + + let t7s = HoleCards::from_notation("T7s").unwrap(); + assert_eq!(t7s.connectivity(), 2); // T to 7 has 9,8 between + + let k8s = HoleCards::from_notation("K8s").unwrap(); + assert_eq!(k8s.connectivity(), 4); // K to 8 has Q,J,T,9 between + } + + #[test] + fn test_hole_cards_notation_comprehensive() { + // Test all possible notations generate correctly + let test_cases = [ + ("AA", "AA"), + ("AKs", "AKs"), + ("AKo", "AKo"), + ("22", "22"), + ("T9s", "T9s"), + ("J8o", "J8o"), + ]; + + for (input, expected) in test_cases.iter() { + let hole_cards = HoleCards::from_notation(input).unwrap(); + assert_eq!(hole_cards.notation(), *expected); + } + + // Test round-trip: notation -> HoleCards -> notation + let all_notations = [ + "AA", "KK", "QQ", "JJ", "TT", "99", "88", "77", "66", "55", "44", "33", "22", "AKs", + "AQs", "AJs", "ATs", "A9s", "A8s", "A7s", "A6s", "A5s", "A4s", "A3s", "A2s", "AKo", + "AQo", "AJo", "ATo", "A9o", "A8o", "A7o", "A6o", "A5o", "A4o", "A3o", "A2o", "KQs", + "KJs", "KTs", "K9s", "K8s", "K7s", "K6s", "K5s", "K4s", "K3s", "K2s", "QJs", "QTs", + "Q9s", "Q8s", "Q7s", "Q6s", "Q5s", "Q4s", "Q3s", "Q2s", "JTs", "J9s", "J8s", "J7s", + "J6s", "J5s", "J4s", "J3s", "J2s", "T9s", "T8s", "T7s", "T6s", "T5s", "T4s", "T3s", + "T2s", "98s", "97s", "96s", "95s", "94s", "93s", "92s", "87s", "86s", "85s", "84s", + "83s", "82s", "76s", "75s", "74s", "73s", "72s", "65s", "64s", "63s", "62s", "54s", + "53s", "52s", "43s", "42s", "32s", + ]; + + for notation in all_notations.iter() { + let hole_cards = HoleCards::from_notation(notation).unwrap(); + assert_eq!(hole_cards.notation(), *notation); + } + } + + #[test] + fn test_hole_cards_notation_invalid() { + // Test various invalid notations + let invalid_cases = [ + "", "A", "ABCD", "AKx", "AK", "AAs", "AAo", "AK", "1K", "AK1", "AKz", "ak", "AKS", + "ako", "A K s", "AKss", "AA s", + ]; + + for invalid in invalid_cases.iter() { + assert!( + HoleCards::from_notation(invalid).is_err(), + "Expected '{}' to be invalid", + invalid + ); + } + + // Test non-pair without suitedness indicator + assert!(HoleCards::from_notation("AK").is_err()); + assert!(HoleCards::from_notation("QJ").is_err()); + } + + #[test] + fn test_hole_cards_ordering_comprehensive() { + // Test that ordering is consistent and transitive + let hands = [ + HoleCards::from_notation("AA").unwrap(), + HoleCards::from_notation("AKs").unwrap(), + HoleCards::from_notation("AKo").unwrap(), + HoleCards::from_notation("AQs").unwrap(), + HoleCards::from_notation("AQo").unwrap(), + HoleCards::from_notation("22").unwrap(), + ]; + + // Test transitivity + for i in 0..hands.len() { + for j in 0..hands.len() { + for k in 0..hands.len() { + let cmp_ij = hands[i].cmp(&hands[j]); + let cmp_jk = hands[j].cmp(&hands[k]); + let cmp_ik = hands[i].cmp(&hands[k]); + + // If i >= j and j >= k, then i >= k + if cmp_ij != Ordering::Less && cmp_jk != Ordering::Less { + assert!( + cmp_ik != Ordering::Less, + "Transitivity violated: {} >= {} >= {} but {} < {}", + hands[i], + hands[j], + hands[k], + hands[i], + hands[k] + ); + } + } + } + } + + // Test specific ordering expectations + assert!(HoleCards::from_notation("AA").unwrap() > HoleCards::from_notation("AKs").unwrap()); + assert!( + HoleCards::from_notation("AKs").unwrap() > HoleCards::from_notation("AKo").unwrap() + ); + assert!( + HoleCards::from_notation("AKs").unwrap() > HoleCards::from_notation("AQs").unwrap() + ); + } + + #[test] + fn test_hole_cards_serialization_comprehensive() { + // Test JSON serialization round-trip for various hands + let test_hands = ["AA", "AKs", "AKo", "22", "T9s", "J8o"]; + + for hand in test_hands.iter() { + let hole_cards = HoleCards::from_notation(hand).unwrap(); + + // JSON round-trip + let json = serde_json::to_string(&hole_cards).unwrap(); + let deserialized: HoleCards = serde_json::from_str(&json).unwrap(); + assert_eq!(hole_cards, deserialized); + + // TOML round-trip + let toml = toml::to_string(&hole_cards).unwrap(); + let deserialized: HoleCards = toml::from_str(&toml).unwrap(); + assert_eq!(hole_cards, deserialized); + } + + // Test serialization of edge cases + let max_gap = HoleCards::from_notation("A2s").unwrap(); + let json = serde_json::to_string(&max_gap).unwrap(); + let deserialized: HoleCards = serde_json::from_str(&json).unwrap(); + assert_eq!(max_gap, deserialized); + } + + #[test] + fn test_hole_cards_hash_consistency() { + use std::collections::HashSet; + + // Test that equal hole cards have equal hashes + let hc1 = HoleCards::from_notation("AKs").unwrap(); + let hc2 = HoleCards::from_notation("AKs").unwrap(); + assert_eq!(hc1, hc2); + + // Test hash distribution (no major collisions for common hands) + let mut hashes = HashSet::new(); + let common_hands = ["AA", "AKs", "AKo", "AQs", "AQo", "AJs", "AJo"]; + + for hand in common_hands.iter() { + let hc = HoleCards::from_notation(hand).unwrap(); + let hash = { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + hc.hash(&mut hasher); + hasher.finish() + }; + hashes.insert(hash); + } + + // Should have no collisions for these distinct hands + assert_eq!(hashes.len(), common_hands.len()); + } + + #[test] + fn test_hole_cards_to_string() { + let aks = HoleCards::from_notation("AKs").unwrap(); + let string_repr = aks.to_string(); + assert!(string_repr.contains("A")); + assert!(string_repr.contains("K")); + assert!(string_repr.len() > 3); // Should be more than just "AKs" + } + + #[test] + fn test_hole_cards_card_access() { + let aks = HoleCards::from_notation("AKs").unwrap(); + let first = aks.first_card(); + let second = aks.second_card(); + + assert_eq!(first.rank(), 12); // Ace + assert_eq!(second.rank(), 11); // King + assert_eq!(first.suit(), second.suit()); // Both spades + } +} diff --git a/holdem_core/src/lib.rs b/holdem_core/src/lib.rs new file mode 100644 index 0000000..12f03c2 --- /dev/null +++ b/holdem_core/src/lib.rs @@ -0,0 +1,77 @@ +//! # Poker Core Library +//! +//! Core data structures for poker AI systems. This library provides the fundamental +//! representations for cards, decks, hole cards, hands, and community boards used +//! in poker games, optimized for performance with zero-based enumerations. +//! +//! ## Quick Start +//! +//! ```rust +//! use holdem_core::{Card, Deck, HoleCards, Hand, Board}; +//! use std::str::FromStr; +//! +//! // Create a card +//! let card = Card::from_str("As").unwrap(); +//! +//! // Create hole cards +//! let hole_cards = HoleCards::from_notation("AKs").unwrap(); +//! +//! // Create a board using the fluent builder pattern +//! let board = Board::new() +//! .with_flop([ +//! Card::from_str("Kd").unwrap(), +//! Card::from_str("Qh").unwrap(), +//! Card::from_str("Jc").unwrap(), +//! ]) +//! .unwrap() +//! .with_turn(Card::from_str("Ts").unwrap()).unwrap() +//! .with_river(Card::from_str("9h").unwrap()).unwrap(); +//! +//! // Create a complete hand +//! let hand = Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); +//! +//! // Or create hands from notation strings +//! let hand = Hand::from_notation("As Ks Qs Js Ts").unwrap(); +//! ``` +//! +//! ## Features +//! +//! - **Performance Optimized**: Zero-based enums for fast bit manipulation and lookup tables +//! - **Memory Efficient**: Compact data structures for mass simulations +//! - **Serialization Ready**: TOML and JSON support for configuration and networking +//! - **Texas Hold'em Support**: Complete representation of hole cards, boards, and hands +//! - **Type Safe**: Strong typing prevents invalid poker states + +/// Core poker card representation with zero-based rank/suit enums +pub mod card; + +/// Deck of cards representation with shuffle and deal functionality +pub mod deck; + +/// Complete poker hand representation for 5-7 card evaluation +pub mod hand; + +/// Hole cards representation for player's private cards +pub mod hole_cards; + +/// Community cards (board) representation with betting street management +pub mod board; + +/// Comprehensive error types for poker operations +pub mod errors; + +/// Re-export holdem_core types for convenience +pub use board::Board; +pub use card::Card; +pub use deck::Deck; +pub use hand::Hand; +pub use hole_cards::HoleCards; + +/// Re-export Street enum for convenience +pub use board::Street; + +/// Re-export error types for convenience +pub use errors::PokerError; + +#[cfg(test)] +mod tests {} diff --git a/holdem_core/tests/integration_tests.rs b/holdem_core/tests/integration_tests.rs new file mode 100644 index 0000000..a117e1d --- /dev/null +++ b/holdem_core/tests/integration_tests.rs @@ -0,0 +1,400 @@ +//! Integration tests for poker data structures +//! +//! These tests verify that the holdem_core poker structures work together correctly, +//! including serialization round-trips and performance benchmarks. + +use holdem_core::{board, card, deck, hand, hole_cards}; +use std::time::Instant; + +#[test] +fn test_integration_hand_from_hole_cards_and_board() { + // Test creating hands from various hole card and board combinations + + // Test with preflop (no board cards) + let hole_cards = hole_cards::HoleCards::new( + card::Card::new(12, 0).unwrap(), // As + card::Card::new(11, 1).unwrap(), // Kh + ) + .unwrap(); + let empty_board = board::Board::new(); + let preflop_hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &empty_board).unwrap(); + assert_eq!(preflop_hand.len, 2); + assert_eq!(preflop_hand.cards()[0], card::Card::new(12, 0).unwrap()); + assert_eq!(preflop_hand.cards()[1], card::Card::new(11, 1).unwrap()); + + // Test with flop + let mut flop_board = board::Board::new(); + flop_board + .deal_flop(vec![ + card::Card::new(10, 2).unwrap(), // Qd + card::Card::new(9, 3).unwrap(), // Js + card::Card::new(8, 0).unwrap(), // Tc + ]) + .unwrap(); + let flop_hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &flop_board).unwrap(); + assert_eq!(flop_hand.len, 5); + assert_eq!(flop_hand.cards()[0], card::Card::new(12, 0).unwrap()); // As + assert_eq!(flop_hand.cards()[1], card::Card::new(11, 1).unwrap()); // Kh + assert_eq!(flop_hand.cards()[2], card::Card::new(10, 2).unwrap()); // Qd + assert_eq!(flop_hand.cards()[3], card::Card::new(9, 3).unwrap()); // Js + assert_eq!(flop_hand.cards()[4], card::Card::new(8, 0).unwrap()); // Tc + + // Test with turn + let mut turn_board = flop_board.clone(); + turn_board + .deal_turn(card::Card::new(7, 1).unwrap()) + .unwrap(); // 9h + let turn_hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &turn_board).unwrap(); + assert_eq!(turn_hand.len, 6); + + // Test with river + let mut river_board = turn_board.clone(); + river_board + .deal_river(card::Card::new(6, 2).unwrap()) + .unwrap(); // 8d + let river_hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &river_board).unwrap(); + assert_eq!(river_hand.len, 7); +} + +#[test] +fn test_integration_hand_from_hole_cards_and_board_edge_cases() { + // Test edge cases for hand creation from hole cards and board + + // Test with pocket aces and board + let pocket_aces = hole_cards::HoleCards::new( + card::Card::new(12, 0).unwrap(), // As + card::Card::new(12, 1).unwrap(), // Ah + ) + .unwrap(); + let mut board = board::Board::new(); + board + .deal_flop(vec![ + card::Card::new(10, 2).unwrap(), // Qd + card::Card::new(9, 3).unwrap(), // Js + card::Card::new(8, 0).unwrap(), // Tc + ]) + .unwrap(); + let hand = hand::Hand::from_hole_cards_and_board(&pocket_aces, &board).unwrap(); + assert_eq!(hand.len, 5); + + // Test with suited connectors + let suited_connectors = hole_cards::HoleCards::new( + card::Card::new(11, 0).unwrap(), // Ks + card::Card::new(10, 0).unwrap(), // Qs + ) + .unwrap(); + let hand = hand::Hand::from_hole_cards_and_board(&suited_connectors, &board).unwrap(); + assert_eq!(hand.len, 5); + + // Test with empty board (should work) + let empty_board = board::Board::new(); + let hand = hand::Hand::from_hole_cards_and_board(&pocket_aces, &empty_board).unwrap(); + assert_eq!(hand.len, 2); +} + +#[test] +fn test_integration_serialization_round_trips() { + // Test that all structures can be serialized and deserialized correctly + + // Test Card serialization round-trip + let original_card = card::Card::new(12, 3).unwrap(); // Ace of Spades + let json = serde_json::to_string(&original_card).unwrap(); + let deserialized_card: card::Card = serde_json::from_str(&json).unwrap(); + assert_eq!(original_card, deserialized_card); + + // Test HoleCards serialization round-trip + let original_hole_cards = hole_cards::HoleCards::new( + card::Card::new(12, 0).unwrap(), + card::Card::new(11, 1).unwrap(), + ) + .unwrap(); + let json = serde_json::to_string(&original_hole_cards).unwrap(); + let deserialized_hole_cards: hole_cards::HoleCards = serde_json::from_str(&json).unwrap(); + assert_eq!(original_hole_cards, deserialized_hole_cards); + + // Test Board serialization round-trip + let mut original_board = board::Board::new(); + original_board + .deal_flop(vec![ + card::Card::new(10, 2).unwrap(), + card::Card::new(9, 3).unwrap(), + card::Card::new(8, 0).unwrap(), + ]) + .unwrap(); + original_board + .deal_turn(card::Card::new(7, 1).unwrap()) + .unwrap(); + original_board + .deal_river(card::Card::new(6, 2).unwrap()) + .unwrap(); + + let json = serde_json::to_string(&original_board).unwrap(); + let deserialized_board: board::Board = serde_json::from_str(&json).unwrap(); + assert_eq!(original_board, deserialized_board); + + // Test Hand serialization round-trip + let original_hand = + hand::Hand::from_hole_cards_and_board(&original_hole_cards, &original_board).unwrap(); + let json = serde_json::to_string(&original_hand).unwrap(); + let deserialized_hand: hand::Hand = serde_json::from_str(&json).unwrap(); + assert_eq!(original_hand, deserialized_hand); + + // Test Deck serialization round-trip + let original_deck = deck::Deck::new(); + let json = serde_json::to_string(&original_deck).unwrap(); + let deserialized_deck: deck::Deck = serde_json::from_str(&json).unwrap(); + assert_eq!(original_deck.cards(), deserialized_deck.cards()); +} + +#[test] +fn test_integration_serialization_round_trips_comprehensive() { + // Test serialization round-trips with various data formats and edge cases + + // Test with TOML serialization + let card = card::Card::new(12, 3).unwrap(); + let toml = toml::to_string(&card).unwrap(); + let deserialized: card::Card = toml::from_str(&toml).unwrap(); + assert_eq!(card, deserialized); + + // Test complex hand serialization + let hole_cards = hole_cards::HoleCards::new( + card::Card::new(12, 0).unwrap(), + card::Card::new(11, 1).unwrap(), + ) + .unwrap(); + let mut board = board::Board::new(); + board + .deal_flop(vec![ + card::Card::new(10, 2).unwrap(), + card::Card::new(9, 3).unwrap(), + card::Card::new(8, 0).unwrap(), + ]) + .unwrap(); + + let hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); + + // JSON round-trip + let json = serde_json::to_string(&hand).unwrap(); + let deserialized: hand::Hand = serde_json::from_str(&json).unwrap(); + assert_eq!(hand, deserialized); + + // TOML round-trip + let toml = toml::to_string(&hand).unwrap(); + let deserialized: hand::Hand = toml::from_str(&toml).unwrap(); + assert_eq!(hand, deserialized); + + // Test empty structures + let empty_hand = hand::Hand::new(vec![]).unwrap(); + let json = serde_json::to_string(&empty_hand).unwrap(); + let deserialized: hand::Hand = serde_json::from_str(&json).unwrap(); + assert_eq!(empty_hand, deserialized); +} + +#[test] +fn test_integration_performance_benchmarks() { + // Performance benchmarks for common operations + + let iterations = 1000; + + // Benchmark hand creation from hole cards and board + let hole_cards = hole_cards::HoleCards::new( + card::Card::new(12, 0).unwrap(), + card::Card::new(11, 1).unwrap(), + ) + .unwrap(); + let mut board = board::Board::new(); + board + .deal_flop(vec![ + card::Card::new(10, 2).unwrap(), + card::Card::new(9, 3).unwrap(), + card::Card::new(8, 0).unwrap(), + ]) + .unwrap(); + + let start = Instant::now(); + for _ in 0..iterations { + let _hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); + } + let duration = start.elapsed(); + println!("Hand creation ({} iterations): {:?}", iterations, duration); + assert!( + duration.as_millis() < 100, + "Hand creation too slow: {:?}", + duration + ); + + // Benchmark serialization round-trips + let hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); + let start = Instant::now(); + for _ in 0..iterations { + let json = serde_json::to_string(&hand).unwrap(); + let _: hand::Hand = serde_json::from_str(&json).unwrap(); + } + let duration = start.elapsed(); + println!( + "Serialization round-trip ({} iterations): {:?}", + iterations, duration + ); + assert!( + duration.as_millis() < 500, + "Serialization too slow: {:?}", + duration + ); + + // Benchmark card creation and operations + let start = Instant::now(); + let mut cards = Vec::new(); + for i in 0..iterations { + cards.push(card::Card::new((i % 13) as u8, (i % 4) as u8)); + } + let duration = start.elapsed(); + println!("Card creation ({} iterations): {:?}", iterations, duration); + assert!( + duration.as_millis() < 50, + "Card creation too slow: {:?}", + duration + ); + + // Benchmark deck operations + let start = Instant::now(); + for _ in 0..(iterations / 10) { + // Fewer iterations for deck operations + let mut deck = deck::Deck::new(); + let _ = deck.shuffle(&mut rand::rng()); + let _ = deck.deal(5); + } + let duration = start.elapsed(); + println!( + "Deck shuffle and deal ({} iterations): {:?}", + iterations / 10, + duration + ); + assert!( + duration.as_millis() < 200, + "Deck operations too slow: {:?}", + duration + ); +} + +#[test] +fn test_integration_complex_scenarios() { + // Test complex real-world scenarios + + // Test multiple hands from same board + let mut board = board::Board::new(); + board + .deal_flop(vec![ + card::Card::new(12, 2).unwrap(), // Ad + card::Card::new(11, 3).unwrap(), // Ks + card::Card::new(10, 0).unwrap(), // Qc + ]) + .unwrap(); + board.deal_turn(card::Card::new(9, 1).unwrap()).unwrap(); // Jh + board.deal_river(card::Card::new(8, 2).unwrap()).unwrap(); // Td + + let hole_cards1 = hole_cards::HoleCards::new( + card::Card::new(12, 0).unwrap(), // As + card::Card::new(12, 1).unwrap(), // Ah + ) + .unwrap(); // Pocket aces + + let hole_cards2 = hole_cards::HoleCards::new( + card::Card::new(11, 0).unwrap(), // Kc + card::Card::new(11, 1).unwrap(), // Kh + ) + .unwrap(); // Pocket kings + + let hand1 = hand::Hand::from_hole_cards_and_board(&hole_cards1, &board).unwrap(); + let hand2 = hand::Hand::from_hole_cards_and_board(&hole_cards2, &board).unwrap(); + + assert_eq!(hand1.len, 7); + assert_eq!(hand2.len, 7); + + // Both hands should contain all board cards plus their hole cards + assert!(hand1.cards().contains(&card::Card::new(12, 0).unwrap())); // As in hand1 + assert!(hand1.cards().contains(&card::Card::new(12, 1).unwrap())); // Ah in hand1 + assert!(hand2.cards().contains(&card::Card::new(11, 0).unwrap())); // Kc in hand2 + assert!(hand2.cards().contains(&card::Card::new(11, 1).unwrap())); // Kh in hand2 + + // Both hands should contain all board cards + for card in board.visible_cards() { + assert!(hand1.cards().contains(card)); + assert!(hand2.cards().contains(card)); + } +} + +#[test] +fn test_integration_error_handling() { + // Test error handling in integration scenarios + + // Test duplicate cards between hole cards and board + let hole_cards = hole_cards::HoleCards::new( + card::Card::new(12, 0).unwrap(), // As + card::Card::new(11, 1).unwrap(), // Kh + ) + .unwrap(); + + let mut board = board::Board::new(); + board + .deal_flop(vec![ + card::Card::new(12, 0).unwrap(), // Duplicate As + card::Card::new(9, 3).unwrap(), + card::Card::new(8, 0).unwrap(), + ]) + .unwrap(); + + // This should fail due to duplicate cards + assert!(hand::Hand::from_hole_cards_and_board(&hole_cards, &board).is_err()); + + // Test serialization of invalid states (should still work) + let invalid_hand = hand::Hand::new(vec![ + card::Card::new(12, 0).unwrap(), + card::Card::new(12, 0).unwrap(), // Duplicate + ]); + assert!(invalid_hand.is_err()); +} + +#[test] +fn test_integration_data_consistency() { + // Test that data remains consistent across operations + + // Create a complex scenario and verify all data stays consistent + let hole_cards = hole_cards::HoleCards::new( + card::Card::new(12, 0).unwrap(), + card::Card::new(11, 1).unwrap(), + ) + .unwrap(); + + let mut board = board::Board::new(); + let flop_cards = vec![ + card::Card::new(10, 2).unwrap(), + card::Card::new(9, 3).unwrap(), + card::Card::new(8, 0).unwrap(), + ]; + board.deal_flop(flop_cards.clone()).unwrap(); + + let hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap(); + + // Verify hand contains exactly the expected cards + let expected_cards = vec![ + card::Card::new(12, 0).unwrap(), // hole + card::Card::new(11, 1).unwrap(), // hole + card::Card::new(10, 2).unwrap(), // flop + card::Card::new(9, 3).unwrap(), // flop + card::Card::new(8, 0).unwrap(), // flop + ]; + + assert_eq!(hand.len, 5); + for expected in expected_cards { + assert!(hand.cards().contains(&expected)); + } + + // Test serialization preserves data + let json = serde_json::to_string(&hand).unwrap(); + let deserialized: hand::Hand = serde_json::from_str(&json).unwrap(); + assert_eq!(hand, deserialized); + assert_eq!(hand.len, deserialized.len); + for i in 0..hand.len { + assert_eq!(hand.cards()[i], deserialized.cards()[i]); + } +} diff --git a/holdem_core/tests/property_tests.rs b/holdem_core/tests/property_tests.rs new file mode 100644 index 0000000..ca53a04 --- /dev/null +++ b/holdem_core/tests/property_tests.rs @@ -0,0 +1,484 @@ +//! Property-based tests for poker data structures +//! +//! These tests verify fundamental properties of poker structures like cards, +//! hands, and decks through comprehensive testing of edge cases and invariants. + +use holdem_core::{card::Card, deck::Deck, hand::Hand}; +use std::collections::HashSet; +use std::str::FromStr; + +#[cfg(test)] +mod card_properties { + use super::*; + + #[test] + fn test_card_properties_comprehensive() { + // Test all possible card combinations for key properties + for rank in 0..=12 { + for suit in 0..=3 { + let card = Card::new(rank, suit).unwrap(); + + // Property: Valid cards are always within valid rank/suit bounds + assert!(card.rank() <= 12); + assert!(card.suit() <= 3); + + // Property: Card creation is deterministic (same inputs = same card) + let card2 = Card::new(rank, suit).unwrap(); + assert_eq!(card, card2); + assert_eq!(card.rank(), rank); + assert_eq!(card.suit(), suit); + + // Property: String parsing round-trips correctly + let string_repr = format!("{}", card); + let parsed = Card::from_str(&string_repr).unwrap(); + assert_eq!(card, parsed); + + // Property: Card serialization round-trips correctly + let json = serde_json::to_string(&card).unwrap(); + let deserialized: Card = serde_json::from_str(&json).unwrap(); + assert_eq!(card, deserialized); + + let toml = toml::to_string(&card).unwrap(); + let deserialized: Card = toml::from_str(&toml).unwrap(); + assert_eq!(card, deserialized); + } + } + } + + #[test] + fn test_card_ordering_properties() { + // Test card ordering with various combinations + let test_cases = vec![ + ((12, 3), (11, 3)), // Ace > King, same suit + ((12, 3), (12, 0)), // Ace spades > Ace hearts (Spades=4 > Hearts=3) + ((12, 0), (12, 1)), // Ace hearts > Ace diamonds (Hearts=3 > Diamonds=2) + ((12, 1), (12, 2)), // Ace diamonds > Ace clubs (Diamonds=2 > Clubs=1) + ((5, 3), (5, 0)), // Five spades > Five hearts + ((5, 0), (5, 1)), // Five hearts > Five diamonds + ((5, 1), (5, 2)), // Five diamonds > Five clubs + ]; + + for ((rank1, suit1), (rank2, suit2)) in test_cases { + let card1 = Card::new(rank1, suit1).unwrap(); + let card2 = Card::new(rank2, suit2).unwrap(); + + // Property: Cards are properly ordered (higher rank first, then higher suit) + if rank1 != rank2 { + if rank1 > rank2 { + assert!( + card1 > card2, + "Card {:?} should be > Card {:?}", + card1, + card2 + ); + } else { + assert!( + card1 < card2, + "Card {:?} should be < Card {:?}", + card1, + card2 + ); + } + } else { + // If ranks are equal, higher suit order should be greater + // Suit order: Spades(4) > Hearts(3) > Diamonds(2) > Clubs(1) + let suit_order1 = match suit1 { + 3 => 4, // Spades + 0 => 3, // Hearts + 1 => 2, // Diamonds + 2 => 1, // Clubs + _ => 0, + }; + let suit_order2 = match suit2 { + 3 => 4, // Spades + 0 => 3, // Hearts + 1 => 2, // Diamonds + 2 => 1, // Clubs + _ => 0, + }; + + if suit_order1 > suit_order2 { + assert!( + card1 > card2, + "Card {:?} should be > Card {:?}", + card1, + card2 + ); + } else { + assert!( + card1 < card2, + "Card {:?} should be < Card {:?}", + card1, + card2 + ); + } + } + } + } + + #[test] + fn test_all_cards_unique_property() { + let mut seen = HashSet::new(); + + // Property: All 52 possible cards are unique + for rank in 0..=12 { + for suit in 0..=3 { + let card = Card::new(rank, suit).unwrap(); + assert!(seen.insert(card), "Card {} was not unique", card); + } + } + + assert_eq!(seen.len(), 52, "Should have exactly 52 unique cards"); + } +} + +#[cfg(test)] +mod hand_properties { + use super::*; + + #[test] + fn test_hand_properties_comprehensive() { + // Test hands of various sizes for key properties + for size in 0..=7 { + if size < 2 { + continue; // Skip very small hands for some tests + } + + // Generate distinct cards for this hand size + let mut cards = Vec::new(); + let mut card_set = HashSet::new(); + + for i in 0..size { + let rank = (i % 13) as u8; + let suit = (i % 4) as u8; + let card = Card::new(rank, suit).unwrap(); + + // Ensure uniqueness + if card_set.insert(card) { + cards.push(card); + } + + if cards.len() == size { + break; + } + } + + if cards.len() == size { + let hand = Hand::new(cards.clone()).unwrap(); + + // Property: Hands never contain duplicate cards + let hand_cards = hand.cards(); + for i in 0..hand_cards.len() { + for j in (i + 1)..hand_cards.len() { + assert_ne!(hand_cards[i], hand_cards[j]); + } + } + + // Property: Hands are always sorted correctly (rank descending) + for i in 1..hand_cards.len() { + assert!(hand_cards[i].rank() <= hand_cards[i - 1].rank()); + } + + // Property: All cards in hand are valid cards + for card in hand.iter() { + assert!(card.rank() <= 12); + assert!(card.suit() <= 3); + } + + // Property: Hand serialization round-trips correctly + let json = serde_json::to_string(&hand).unwrap(); + let deserialized: Hand = serde_json::from_str(&json).unwrap(); + assert_eq!(hand, deserialized); + + let toml = toml::to_string(&hand).unwrap(); + let deserialized: Hand = toml::from_str(&toml).unwrap(); + assert_eq!(hand, deserialized); + + // Property: Hand length matches actual card count + assert_eq!(hand.len, cards.len()); + assert_eq!(hand_cards.len(), cards.len()); + + // Property: Best five cards returns exactly 5 cards (if hand large enough) + if hand.len >= 5 { + let best_five = hand.best_five_cards(); + assert_eq!(best_five.len(), 5); + + // Property: Best five cards are subset of hand cards + for &card in &best_five { + assert!(hand_cards.contains(&card)); + } + } + } + } + } + + #[test] + fn test_hand_length_properties() { + // Property: Hand length is always <= 7 + for size in 0..=10 { + let mut cards = Vec::new(); + for i in 0..size { + let rank = (i % 13) as u8; + let suit = (i % 4) as u8; + if let Ok(card) = Card::new(rank, suit) { + cards.push(card); + } + } + + if size <= 7 { + let hand = Hand::new(cards); + assert!(hand.is_ok(), "Hand of size {} should be valid", size); + } else { + let hand = Hand::new(cards); + assert!(hand.is_err(), "Hand of size {} should be invalid", size); + } + } + } + + #[test] + fn test_hand_display_properties() { + // Test various hand sizes for display format consistency + for size in 0..=7 { + let mut cards = Vec::new(); + for i in 0..size { + let rank = (i % 13) as u8; + let suit = (i % 4) as u8; + if let Ok(card) = Card::new(rank, suit) { + cards.push(card); + } + } + + if let Ok(hand) = Hand::new(cards) { + let display = format!("{}", hand); + + // Property: Hand display format is consistent + assert!(display.starts_with("Hand(")); + assert!(display.ends_with(")")); + assert!(display.contains("cards:")); + } + } + } +} + +#[cfg(test)] +mod deck_properties { + use super::*; + + #[test] + fn test_deck_properties_comprehensive() { + let deck = Deck::new(); + + // Property: Fresh decks always contain exactly 52 cards + assert_eq!(deck.remaining(), 52); + assert!(!deck.is_empty()); + + // Property: All cards in deck are unique + let cards = deck.cards(); + let mut seen = HashSet::new(); + for &card in cards { + assert!(seen.insert(card), "Duplicate card in deck: {}", card); + } + assert_eq!(seen.len(), 52); + + // Property: All cards in deck are valid cards + for &card in cards { + assert!(card.rank() <= 12); + assert!(card.suit() <= 3); + } + + // Property: Deck serialization round-trips correctly + let json = serde_json::to_string(&deck).unwrap(); + let deserialized: Deck = serde_json::from_str(&json).unwrap(); + assert_eq!(deck.cards(), deserialized.cards()); + + let toml = toml::to_string(&deck).unwrap(); + let deserialized: Deck = toml::from_str(&toml).unwrap(); + assert_eq!(deck.cards(), deserialized.cards()); + } + + #[test] + fn test_deck_dealing_properties() { + let mut deck = Deck::new(); + let initial_remaining = deck.remaining(); + + // Property: Dealing cards reduces remaining count + if initial_remaining > 0 { + let dealt = deck.deal(1); + assert_eq!(dealt.len(), 1); + assert_eq!(deck.remaining(), initial_remaining - 1); + } + + // Property: Dealing more cards than available returns empty + let mut empty_deck = Deck::new(); + while empty_deck.remaining() > 0 { + empty_deck.deal(1); + } + + assert_eq!(empty_deck.remaining(), 0); + let dealt_none = empty_deck.deal(1); + assert!(dealt_none.is_empty()); + } +} + +#[cfg(test)] +mod integration_properties { + use super::*; + + #[test] + fn test_hand_from_hole_cards_and_board_properties() { + // Test various combinations of hole cards and board cards + for num_hole in 2..=2 { + for num_board in 0..=5 { + let mut hole_cards = Vec::new(); + let mut board_cards = Vec::new(); + let mut all_cards_set = HashSet::new(); + + // Generate unique hole cards + for i in 0..num_hole { + let mut attempts = 0; + loop { + let rank = (i % 13) as u8; + let suit = (i % 4) as u8; + let card = Card::new(rank, suit).unwrap(); + if all_cards_set.insert(card) { + hole_cards.push(card); + break; + } + attempts += 1; + if attempts > 50 { + break; + } + } + } + + // Generate unique board cards + for i in 0..num_board { + let mut attempts = 0; + loop { + let rank = ((i + 2) % 13) as u8; + let suit = ((i + 1) % 4) as u8; + let card = Card::new(rank, suit).unwrap(); + if all_cards_set.insert(card) { + board_cards.push(card); + break; + } + attempts += 1; + if attempts > 50 { + break; + } + } + } + + if hole_cards.len() == num_hole && board_cards.len() == num_board { + use holdem_core::{board::Board, hole_cards::HoleCards}; + + let hole_cards_struct = HoleCards::new(hole_cards[0], hole_cards[1]).unwrap(); + let mut board = Board::new(); + + if board_cards.len() >= 3 { + // For testing purposes, just add the first 3 cards as flop + board + .deal_flop(board_cards.iter().take(3).cloned().collect()) + .unwrap(); + } + + // Property: Hand creation from hole cards and board never exceeds 7 cards + let hand = Hand::from_hole_cards_and_board(&hole_cards_struct, &board); + assert!(hand.is_ok()); + + let hand = hand.unwrap(); + assert!(hand.len <= 7); + + // The hand should contain hole cards + any successfully added board cards + let expected_min_len = num_hole; // At least hole cards + let expected_max_len = num_hole + num_board; // At most hole cards + all board cards + assert!(hand.len >= expected_min_len); + assert!(hand.len <= expected_max_len); + } + } + } + } + + #[test] + fn test_hand_notation_parsing_properties() { + // Test hand notation parsing with various valid combinations + for size in 2..=7 { + let mut cards = Vec::new(); + let mut card_set = HashSet::new(); + + for i in 0..size { + let mut attempts = 0; + loop { + let rank = (i % 13) as u8; + let suit = (i % 4) as u8; + let card = Card::new(rank, suit).unwrap(); + if card_set.insert(card) { + cards.push(card); + break; + } + attempts += 1; + if attempts > 50 { + break; + } + } + } + + if cards.len() == size { + let mut notation_parts = Vec::new(); + for card in &cards { + notation_parts.push(format!("{}", card)); + } + let notation = notation_parts.join(" "); + + // Property: Hand notation parsing works for valid hands + let parsed = Hand::from_notation(¬ation); + if size <= 7 { + assert!(parsed.is_ok(), "Failed to parse notation: {}", notation); + let parsed_hand = parsed.unwrap(); + assert_eq!(parsed_hand.len, cards.len()); + } else { + assert!( + parsed.is_err(), + "Should fail to parse hand with {} cards", + size + ); + } + } + } + } + + #[test] + fn test_hand_strength_properties() { + // Test that hand strength is deterministic + for size in 5..=7 { + let mut cards = Vec::new(); + let mut card_set = HashSet::new(); + + for i in 0..size { + let mut attempts = 0; + loop { + let rank = (i % 13) as u8; + let suit = (i % 4) as u8; + let card = Card::new(rank, suit).unwrap(); + if card_set.insert(card) { + cards.push(card); + break; + } + attempts += 1; + if attempts > 50 { + break; + } + } + } + + if cards.len() == size { + // Property: Hand strength is deterministic (same hand = same strength) + let hand1 = Hand::new(cards.clone()).unwrap(); + let hand2 = Hand::new(cards).unwrap(); + + // For now, strength is always 0 (placeholder), but should be consistent + assert_eq!(hand1.strength(), hand2.strength()); + } + } + } +} diff --git a/simulator/README.md b/simulator/README.md deleted file mode 100644 index cdc1211..0000000 --- a/simulator/README.md +++ /dev/null @@ -1,281 +0,0 @@ -# Poker Simulator - -## Overview - -The `simulator/` directory contains tools and frameworks for simulating poker games, testing strategies, and analyzing poker scenarios. These tools enable comprehensive testing and development of poker bots and strategies in controlled environments. - -## Components - -### Texas Hold'em Simulator (`src/texas_holdem/`) - -Core simulation engine for Texas Hold'em poker games. - -#### Features -- **Complete Game Simulation**: Full Texas Hold'em game lifecycle -- **Multiple Players**: Support for 2-10 players per simulation -- **Configurable Rules**: Customizable game rules and parameters -- **Hand Range Support**: Define specific hand ranges for testing -- **Equity Calculation**: Accurate pot odds and equity computation -- **Statistical Analysis**: Comprehensive results and analytics - -#### Simulation Capabilities -- **Hand Range Analysis**: Test bots against specific hand ranges -- **Monte Carlo Simulation**: Run thousands of scenarios for statistical validity -- **Tournament Simulation**: Multi-table tournament scenarios -- **Cash Game Simulation**: Ring game format with varying stack sizes -- **Heads-Up Simulation**: One-on-one bot testing and analysis - -## Usage - -### Basic Simulation Setup - -```rust -use poker_api::api::card::Card; -use poker_api::api::hand::Hand; - -// Create a simulation scenario -let mut simulation = TexasHoldemSimulation::new(); - -// Configure players -simulation.add_player("Bot1", 1000.0); -simulation.add_player("Bot2", 1000.0); - -// Set up hand range for testing -simulation.set_player_range(0, "AA,KK,QQ,AK".to_string()); - -// Run simulation -let results = simulation.run(1000); // Run 1000 hands - -println!("Results: {:?}", results); -``` - -### Advanced Simulation - -```rust -// Configure detailed simulation parameters -let config = SimulationConfig { - num_players: 6, - starting_stack: 1000.0, - blind_levels: vec![ - BlindLevel { small_blind: 10.0, big_blind: 20.0, duration_minutes: 10 }, - BlindLevel { small_blind: 15.0, big_blind: 30.0, duration_minutes: 10 }, - ], - hand_range: "22+,A2+,K2+,Q2+,J2+,T2+,92+,82+,72+,62+,52+,42+,32+".to_string(), -}; - -let mut tournament = TournamentSimulation::new(config); -tournament.run(); -``` - -## Analysis Tools - -### Hand Analysis - -```rust -// Analyze specific hand scenarios -let analyzer = HandAnalyzer::new(); - -let hole_cards = vec!["As", "Ks"]; -let board = vec!["Td", "Jc", "Qh"]; - -let analysis = analyzer.analyze_hand(hole_cards, board); -println!("Hand strength: {}", analysis.strength); -println!("Equity vs random: {}", analysis.equity_vs_random); -``` - -### Range Analysis - -```rust -// Analyze range vs range scenarios -let range_analyzer = RangeAnalyzer::new(); - -let range1 = "AA,AK, AQ".to_string(); // Premium hands -let range2 = "22+,A2+,K2+".to_string(); // Wide range - -let results = range_analyzer.analyze_ranges(range1, range2, board); -println!("Range1 equity: {:.2}%", results.range1_equity); -println!("Range2 equity: {:.2}%", results.range2_equity); -``` - -## Testing Framework - -### Bot Testing - -```rust -use poker_api::simulator::testing::BotTester; - -// Create bot tester -let mut tester = BotTester::new(); - -// Add bots to test -tester.add_bot("AggressiveBot", Box::new(AggressiveBot::new())); -tester.add_bot("TightBot", Box::new(TightBot::new())); - -// Define test scenarios -let scenarios = vec![ - TestScenario::heads_up(1000), - TestScenario::six_max(500), - TestScenario::full_ring(1000), -]; - -// Run comprehensive tests -let results = tester.run_scenarios(scenarios); -tester.generate_report("bot_test_results.html"); -``` - -### Strategy Testing - -```rust -// Test specific strategies -let strategy_tester = StrategyTester::new(); - -let strategies = vec![ - Strategy::new("Tight Aggressive", config_tight_aggressive), - Strategy::new("Loose Passive", config_loose_passive), - Strategy::new("Balanced", config_balanced), -]; - -let results = strategy_tester.test_strategies(strategies, 10000); -println!("Strategy comparison: {:?}", results); -``` - -## Performance - -### Simulation Speed -- **Simple Hand**: ~1ms per hand simulation -- **Complex Analysis**: ~10ms per hand with detailed analysis -- **Monte Carlo**: ~100ms per 1000 scenarios -- **Tournament**: ~1s per 100-hand tournament - -### Memory Usage -- **Base Simulation**: ~1MB per active simulation -- **Hand History**: ~10KB per 1000 hands stored -- **Analysis Data**: ~50MB per comprehensive analysis - -## Configuration - -### Simulation Parameters - -```toml -[simulation] -num_players = 6 -starting_stack = 1000 -num_hands = 10000 -save_hand_history = true - -[blinds] -small_blind = 10.0 -big_blind = 20.0 -ante = 0.0 - -[ranges] -player1 = "AA,KK,QQ,AK" -player2 = "22+,A2+,K2+,Q2+,J2+" -player3 = "random" - -[analysis] -detailed_equity = true -save_distributions = true -confidence_interval = 0.95 -``` - -### Custom Scenarios - -```rust -// Create custom test scenarios -let scenario = SimulationScenario { - name: "Premium vs Fish".to_string(), - players: vec![ - PlayerConfig { name: "Pro", range: "AA,KK,QQ,AK,AQ".to_string(), stack: 2000 }, - PlayerConfig { name: "Fish", range: "22+,A2+,K2+".to_string(), stack: 500 }, - ], - board_texture: BoardTexture::Dry, // Flop texture for analysis - num_simulations: 10000, -}; -``` - -## Output and Reporting - -### Results Format - -```rust -pub struct SimulationResults { - pub total_hands: usize, - pub player_results: Vec, - pub hand_distribution: HashMap, - pub equity_distribution: Vec, - pub confidence_intervals: Vec<(f64, f64)>, -} -``` - -### Report Generation - -```rust -// Generate comprehensive reports -let reporter = SimulationReporter::new(); -reporter.add_results(results); -reporter.generate_html_report("simulation_report.html"); -reporter.generate_csv_summary("summary.csv"); -``` - -## Integration - -The simulator integrates with: -- **Core API**: Uses hand evaluation and card management -- **Bot Framework**: Tests bot implementations in controlled environments -- **Analysis Tools**: Provides data for strategy analysis and improvement -- **Log System**: Integrates with logging and monitoring tools - -## Development - -### Extending the Simulator - -#### Adding New Game Types -```rust -// Implement new poker variant -pub struct OmahaSimulation { - // Omaha-specific logic -} - -impl PokerSimulation for OmahaSimulation { - fn simulate_hand(&mut self) -> HandResult { - // Omaha simulation logic - } -} -``` - -#### Custom Analysis Modules -```rust -// Add custom analysis -pub struct BluffAnalyzer { - // Bluff detection logic -} - -impl HandAnalyzer for BluffAnalyzer { - fn analyze(&self, hand: &Hand) -> AnalysisResult { - // Custom analysis logic - } -} -``` - -## Use Cases - -### Bot Development -- Test bot strategies against known ranges -- Identify leaks in bot decision-making -- Optimize bot parameters and configurations - -### Strategy Research -- Analyze range vs range scenarios -- Study board texture effects -- Research optimal bet sizing - -### Game Theory -- Equilibrium analysis for different stack sizes -- Nash equilibrium calculations -- Exploitability analysis - -### Performance Testing -- Load testing for poker platforms -- Scalability analysis -- Performance benchmarking \ No newline at end of file