Holdem basic core structures from scratch.

This commit is contained in:
Jan-Peter von Hunnius 2025-10-10 19:48:24 +02:00
parent f02c35bef3
commit e94bf42dce
53 changed files with 6316 additions and 11906 deletions

19
docs/ACHIEVEMENTS.md Normal file
View 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
View 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
View 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

View file

@ -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.

View file

@ -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.

View file

@ -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.