Holdem basic core structures from scratch.
This commit is contained in:
parent
f02c35bef3
commit
e94bf42dce
53 changed files with 6316 additions and 11906 deletions
19
docs/ACHIEVEMENTS.md
Normal file
19
docs/ACHIEVEMENTS.md
Normal file
|
|
@ -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
|
||||
99
docs/ROADMAP.md
Normal file
99
docs/ROADMAP.md
Normal file
|
|
@ -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
|
||||
46
docs/VISION.md
Normal file
46
docs/VISION.md
Normal file
|
|
@ -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
|
||||
389
docs/api.md
389
docs/api.md
|
|
@ -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<Self>
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -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<u32> {
|
||||
// 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.
|
||||
|
|
@ -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<dyn std::error::Error>> {
|
||||
// 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<Self, Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<u32> {
|
||||
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<LookupHandEvaluator, io::Error> {
|
||||
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<Self, io::Error> {
|
||||
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<u32> {
|
||||
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<u32> = hands.iter()
|
||||
.map(|hand| self.evaluator.rank_hand(hand))
|
||||
.collect();
|
||||
|
||||
HandStrengthStats {
|
||||
average_rank: ranks.iter().sum::<u32>() / 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue