Added Documentation
Added Test Cases Added Comments
This commit is contained in:
parent
ac25a1caea
commit
5c271b279f
42 changed files with 5279 additions and 52 deletions
484
docs/developer_guide.md
Normal file
484
docs/developer_guide.md
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue