rusty_marvin/docs/developer_guide.md
Jan-Peter von Hunnius 5c271b279f Added Documentation
Added Test Cases
Added Comments
2025-10-02 18:33:31 +02:00

484 lines
No EOL
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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