484 lines
No EOL
14 KiB
Markdown
484 lines
No EOL
14 KiB
Markdown
# 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. |