Fixed hand evaluator LUT generation
This commit is contained in:
parent
7819b1ee27
commit
51e1f58ef9
17 changed files with 2045 additions and 660 deletions
|
|
@ -17,7 +17,6 @@ A comprehensive poker bot testing framework with Java-compatible hand evaluation
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Java-Compatible**: Full compatibility with Java Meerkat API for seamless tool integration
|
|
||||||
- **Bot Testing Framework**: Specifically designed for automated poker bot testing and development
|
- **Bot Testing Framework**: Specifically designed for automated poker bot testing and development
|
||||||
- **Blazing Fast**: O(1) hand evaluation using perfect hash jump tables
|
- **Blazing Fast**: O(1) hand evaluation using perfect hash jump tables
|
||||||
- **Memory Efficient**: ~130MB for complete evaluation tables (79% reduction from 625MB)
|
- **Memory Efficient**: ~130MB for complete evaluation tables (79% reduction from 625MB)
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,5 @@
|
||||||
# Poker AI Testbed & Live Server Achievements
|
# Poker AI Testbed & Live Server Achievements
|
||||||
|
|
||||||
## Major Technical Achievements
|
|
||||||
|
|
||||||
### 🚀 Perfect Hash Jump Table Implementation ✅
|
|
||||||
|
|
||||||
**Revolutionary Memory Optimization**
|
|
||||||
- **79% Memory Reduction**: Reduced evaluation table size from 625MB to ~130MB
|
|
||||||
- **Advanced Algorithm**: Perfect hash jump table with suit canonicalization
|
|
||||||
- **Bottom-up Construction**: Cache-optimized trie construction for optimal performance
|
|
||||||
- **Zero Breaking Changes**: Full API compatibility maintained for existing users
|
|
||||||
|
|
||||||
**Technical Innovation Details**
|
|
||||||
- **Suit Canonicalization**: Lexicographically smallest suit permutation algorithm
|
|
||||||
- **Three-Level Trie Structure**: Level 5 (terminal), Level 6 (intermediate), Level 7 (root)
|
|
||||||
- **Memory Layout Optimization**: Sequential access patterns for CPU cache efficiency
|
|
||||||
- **Streaming Construction**: Handles large datasets without excessive memory allocation
|
|
||||||
|
|
||||||
**Performance Improvements**
|
|
||||||
- **Faster 6-Card Evaluation**: Improved evaluation speed for 6-card hands
|
|
||||||
- **Enhanced 7-Card Performance**: Better performance for 7-card hand evaluation
|
|
||||||
- **Cache-Friendly Access**: Optimized memory access patterns for modern CPUs
|
|
||||||
- **Reduced Memory Pressure**: Significantly lower memory footprint for large-scale simulations
|
|
||||||
|
|
||||||
## Completed Phases and Items
|
## Completed Phases and Items
|
||||||
|
|
||||||
### Phase 1: Foundation ✅
|
### Phase 1: Foundation ✅
|
||||||
|
|
@ -29,38 +7,12 @@
|
||||||
**Basic poker engine**
|
**Basic poker engine**
|
||||||
- Card/Hand/Board representations ✅
|
- Card/Hand/Board representations ✅
|
||||||
- Fast LUT hand evaluator (5, 6, and 7 cards) ✅
|
- 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.) ✅
|
|
||||||
|
|
||||||
## Success Metrics
|
## Success Metrics
|
||||||
|
|
||||||
- **Bot Performance:** Profitability across different game types
|
- **Bot Performance:** Profitability across different game types
|
||||||
- **Simulation Speed:** Ability to run millions of hands efficiently
|
- **Simulation Speed:** Ability to run millions of hands efficiently
|
||||||
- **Memory Efficiency:** 79% reduction in evaluation table memory usage
|
|
||||||
- **Live Reliability:** 99.9% uptime with correct action execution
|
- **Live Reliability:** 99.9% uptime with correct action execution
|
||||||
- **Flexibility:** Easy configuration for new sites and game types
|
- **Flexibility:** Easy configuration for new sites and game types
|
||||||
- **Technical Innovation:** State-of-the-art perfect hash jump table implementation
|
|
||||||
- **Evolution:** Continuous improvement through simulation results
|
- **Evolution:** Continuous improvement through simulation results
|
||||||
- **Monitoring:** Meaningful debug and performance logging and visualization
|
- **Monitoring:** Meaningful debug and performance logging and visualization
|
||||||
|
|
||||||
## Technical Specifications
|
|
||||||
|
|
||||||
### Memory Achievement Breakdown
|
|
||||||
- **Original Implementation**: 625MB for complete 7-card evaluation tables
|
|
||||||
- **New Implementation**: ~130MB for equivalent functionality (79% reduction)
|
|
||||||
- **Target Size**: Exactly 32-35 million u32 entries for optimal memory usage
|
|
||||||
- **Construction Time**: 2-3 minutes for complete table generation
|
|
||||||
|
|
||||||
### Performance Characteristics
|
|
||||||
- **5-Card Evaluation**: ~50-100 nanoseconds (O(1) lookup)
|
|
||||||
- **6-Card Evaluation**: ~200-300 nanoseconds (best 5-card selection)
|
|
||||||
- **7-Card Evaluation**: ~400-600 nanoseconds (best 5-card selection)
|
|
||||||
- **Hash Calculation**: ~20-30 nanoseconds (card canonicalization)
|
|
||||||
|
|
@ -2,27 +2,19 @@
|
||||||
|
|
||||||
## Step-by-Step Implementation Plan
|
## Step-by-Step Implementation Plan
|
||||||
|
|
||||||
### Phase 1: Foundation ✅
|
### Phase 1: Foundation
|
||||||
|
|
||||||
**Basic poker engine**
|
**Basic poker engine**
|
||||||
- Fast LUT hand evaluator (5, 6, and 7 cards) ✅
|
- Game state management
|
||||||
- Game state management ✅
|
- Game Info and Player Info traits
|
||||||
- Game Info and Player Info traits ✅
|
- Event reporting and action scheme
|
||||||
- Event reporting and action scheme ✅
|
- Basic game rules (NL/FL/PL)
|
||||||
- Basic game rules (NL/FL/PL) ✅
|
|
||||||
|
|
||||||
**Configuration system**
|
**Configuration system**
|
||||||
- TOML configuration structures ✅
|
- TOML configuration structures
|
||||||
- Bot parameter configuration ✅
|
- Bot parameter configuration
|
||||||
- Game type configurations (Cash/SNG/MTT, buyins, blinds, hands per level, etc.) ✅
|
- Game type configurations (Cash/SNG/MTT, buyins, blinds, hands per level, etc.)
|
||||||
- Simulation parameters (hands/tournaments count, buyin thresholds for bots, etc.) ✅
|
- Simulation parameters (hands/tournaments count, buyin thresholds for bots, etc.)
|
||||||
|
|
||||||
**🚀 Memory Optimization Achievement ✅**
|
|
||||||
- **Perfect Hash Jump Table**: Revolutionary 79% memory reduction (625MB → ~130MB)
|
|
||||||
- **Suit Canonicalization**: Advanced isomorphic hand reduction algorithm
|
|
||||||
- **Bottom-up Trie Construction**: Cache-optimized memory layout
|
|
||||||
- **Zero Breaking Changes**: Full API compatibility maintained
|
|
||||||
- **Performance Enhancement**: Faster evaluation for 6 and 7-card hands
|
|
||||||
|
|
||||||
### Phase 2: Simulation Testbed
|
### Phase 2: Simulation Testbed
|
||||||
**Goal:** Mass simulation capability
|
**Goal:** Mass simulation capability
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,11 @@ 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
|
- **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
|
- **Live Action Server** – For deploying bots in real poker environments with automatic table selection
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Core Architecture Components
|
## Core Architecture Components
|
||||||
|
|
||||||
### 1. Advanced Evaluation Engine
|
### 1. Simulation Testbed
|
||||||
|
|
||||||
- **Perfect Hash Jump Tables**: Revolutionary memory-efficient evaluation (~130MB vs 625MB)
|
|
||||||
- **Suit Canonicalization**: Advanced isomorphic hand reduction for optimal performance
|
|
||||||
- **Bottom-up Trie Construction**: Cache-optimized memory layout for CPU efficiency
|
|
||||||
- **Zero Breaking Changes**: Full backward compatibility with existing evaluation APIs
|
|
||||||
|
|
||||||
### 2. Simulation Testbed
|
|
||||||
|
|
||||||
- **Configurable bots** (Rule-based, Nash EQ, Statistical, Hand-reading, Reinforcement Learning)
|
- **Configurable bots** (Rule-based, Nash EQ, Statistical, Hand-reading, Reinforcement Learning)
|
||||||
- **Mass parallel simulations** with GPU acceleration (e.g., for turn and river rollouts)
|
- **Mass parallel simulations** with GPU acceleration (e.g., for turn and river rollouts)
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ sha2 = "0.10"
|
||||||
tempfile = "3.23.0"
|
tempfile = "3.23.0"
|
||||||
once_cell = "1.19"
|
once_cell = "1.19"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
indicatif = "0.17"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = "1.0"
|
proptest = "1.0"
|
||||||
|
indicatif = "0.17"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
use super::errors::EvaluatorError;
|
use super::errors::EvaluatorError;
|
||||||
use super::evaluator::{HandRank, HandValue};
|
use super::evaluator::{HandRank, HandValue};
|
||||||
use super::integration::{benchmark_evaluation, utils, EvaluatorComparison, MathEvaluator};
|
use super::integration::{EvaluatorComparison, MathEvaluator};
|
||||||
use super::tables::{CanonicalMapping, JumpTable};
|
use super::tables::{CanonicalMapping, JumpTable};
|
||||||
use crate::card::PackedCard;
|
use crate::card::PackedCard;
|
||||||
use crate::{Card, Hand};
|
use crate::{Card, Hand};
|
||||||
|
|
@ -250,7 +250,7 @@ pub fn canonicalization_example() -> Result<(), EvaluatorError> {
|
||||||
mapping
|
mapping
|
||||||
.canonical_cards
|
.canonical_cards
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&c| (c & 0x03))
|
.map(|&c| c & 0x03)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
println!(" Suit mapping: {:?}", mapping.suit_map);
|
println!(" Suit mapping: {:?}", mapping.suit_map);
|
||||||
|
|
@ -444,14 +444,14 @@ fn extract_suits(cards: &[PackedCard]) -> Vec<u8> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to extract ranks from cards
|
/// Helper function to extract ranks from cards
|
||||||
fn extract_ranks(cards: &[PackedCard]) -> Vec<u8> {
|
|
||||||
cards.iter().map(|card| card.rank()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn extract_ranks(cards: &[PackedCard]) -> Vec<u8> {
|
||||||
|
cards.iter().map(|card| card.rank()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_usage_example() {
|
fn test_basic_usage_example() {
|
||||||
assert!(basic_usage_example().is_ok());
|
assert!(basic_usage_example().is_ok());
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
|
|
||||||
use super::errors::EvaluatorError;
|
use super::errors::EvaluatorError;
|
||||||
use super::evaluator::{HandRank, HandValue};
|
use super::evaluator::{HandRank, HandValue};
|
||||||
use super::tables::{CanonicalMapping, JumpTable, JumpTableEntry};
|
use super::tables::JumpTable;
|
||||||
use crate::card::PackedCard;
|
use crate::card::PackedCard;
|
||||||
use crate::{Card, Hand};
|
use crate::{Card, Hand};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
@ -215,7 +215,7 @@ impl MathEvaluator {
|
||||||
pub fn convert_cards(cards: &[Card]) -> Vec<PackedCard> {
|
pub fn convert_cards(cards: &[Card]) -> Vec<PackedCard> {
|
||||||
cards
|
cards
|
||||||
.iter()
|
.iter()
|
||||||
.map(|card| PackedCard::from_card(card))
|
.map(|card| PackedCard::new(card.rank(), card.suit()).unwrap())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,7 +223,7 @@ pub fn convert_cards(cards: &[Card]) -> Vec<PackedCard> {
|
||||||
pub fn convert_cards_fixed<const N: usize>(cards: &[Card; N]) -> [PackedCard; N] {
|
pub fn convert_cards_fixed<const N: usize>(cards: &[Card; N]) -> [PackedCard; N] {
|
||||||
let mut result = [PackedCard::new(0, 0).unwrap(); N];
|
let mut result = [PackedCard::new(0, 0).unwrap(); N];
|
||||||
for (i, card) in cards.iter().enumerate() {
|
for (i, card) in cards.iter().enumerate() {
|
||||||
result[i] = PackedCard::from_card(card);
|
result[i] = PackedCard::new(card.rank(), card.suit()).unwrap();
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
@ -392,13 +392,13 @@ impl EvaluatorComparison {
|
||||||
let mut core_times = Vec::new();
|
let mut core_times = Vec::new();
|
||||||
|
|
||||||
// Benchmark math evaluator
|
// Benchmark math evaluator
|
||||||
for cards in &test_cases {
|
for _cards in &test_cases {
|
||||||
let math_time = benchmark_evaluation(|c| self.math_evaluator.evaluate_7_card(c));
|
let math_time = benchmark_evaluation(|c| self.math_evaluator.evaluate_7_card(c));
|
||||||
math_times.push(math_time);
|
math_times.push(math_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark core evaluator
|
// Benchmark core evaluator
|
||||||
for cards in &test_cases {
|
for _cards in &test_cases {
|
||||||
let core_time = benchmark_evaluation(|c| self.core_evaluator.evaluate_7_card(c));
|
let core_time = benchmark_evaluation(|c| self.core_evaluator.evaluate_7_card(c));
|
||||||
core_times.push(core_time);
|
core_times.push(core_time);
|
||||||
}
|
}
|
||||||
|
|
@ -473,8 +473,6 @@ pub mod utils {
|
||||||
|
|
||||||
/// Validate that math evaluator produces same results as holdem_core
|
/// Validate that math evaluator produces same results as holdem_core
|
||||||
pub fn validate_evaluator_compatibility() -> Result<(), EvaluatorError> {
|
pub fn validate_evaluator_compatibility() -> Result<(), EvaluatorError> {
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
let comparison = EvaluatorComparison::new()?;
|
let comparison = EvaluatorComparison::new()?;
|
||||||
|
|
||||||
// Test a few representative hands
|
// Test a few representative hands
|
||||||
|
|
@ -647,19 +645,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_benchmark_function() {
|
fn test_benchmark_function() {
|
||||||
use std::str::FromStr;
|
let elapsed = benchmark_evaluation(|_cards| {
|
||||||
|
|
||||||
let test_cards = [
|
|
||||||
Card::from_str("As").unwrap(),
|
|
||||||
Card::from_str("Ks").unwrap(),
|
|
||||||
Card::from_str("Qs").unwrap(),
|
|
||||||
Card::from_str("Js").unwrap(),
|
|
||||||
Card::from_str("Ts").unwrap(),
|
|
||||||
Card::from_str("7h").unwrap(),
|
|
||||||
Card::from_str("6d").unwrap(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let elapsed = benchmark_evaluation(|cards| {
|
|
||||||
// Simple evaluation for testing
|
// Simple evaluation for testing
|
||||||
HandValue::new(HandRank::HighCard, 0)
|
HandValue::new(HandRank::HighCard, 0)
|
||||||
});
|
});
|
||||||
|
|
@ -672,7 +658,7 @@ mod tests {
|
||||||
let comparison = EvaluatorComparison::new();
|
let comparison = EvaluatorComparison::new();
|
||||||
assert!(comparison.is_ok());
|
assert!(comparison.is_ok());
|
||||||
|
|
||||||
let mut comparison = EvaluatorComparison::new().unwrap();
|
let comparison = EvaluatorComparison::new().unwrap();
|
||||||
let results = comparison.compare_evaluations(&[]);
|
let results = comparison.compare_evaluations(&[]);
|
||||||
|
|
||||||
// Should handle empty hand list gracefully
|
// Should handle empty hand list gracefully
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,25 @@
|
||||||
//! - **`property_tests`**: Property-based testing for evaluation correctness
|
//! - **`property_tests`**: Property-based testing for evaluation correctness
|
||||||
//! - **`examples`**: Usage examples and performance benchmarks
|
//! - **`examples`**: Usage examples and performance benchmarks
|
||||||
|
|
||||||
|
pub mod algorithmic_evaluator;
|
||||||
|
pub mod benchmark_utils;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod evaluator;
|
pub mod evaluator;
|
||||||
pub mod examples;
|
pub mod examples;
|
||||||
pub mod file_io;
|
pub mod file_io;
|
||||||
pub mod integration;
|
pub mod integration;
|
||||||
|
pub mod integration_utils;
|
||||||
pub mod property_tests;
|
pub mod property_tests;
|
||||||
pub mod singleton;
|
pub mod singleton;
|
||||||
pub mod tables;
|
pub mod tables;
|
||||||
|
pub mod tables_new;
|
||||||
|
pub mod test_utils;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod algorithmic_evaluator_test;
|
||||||
|
|
||||||
// Re-export commonly used types from local modules
|
// Re-export commonly used types from local modules
|
||||||
|
pub use algorithmic_evaluator::{AlgorithmicEvaluator, EvaluationStats};
|
||||||
pub use errors::EvaluatorError;
|
pub use errors::EvaluatorError;
|
||||||
pub use evaluator::{Evaluator, HandRank, HandValue};
|
pub use evaluator::{Evaluator, HandRank, HandValue};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@
|
||||||
//! 4. **Coverage**: Ensure comprehensive testing of all code paths
|
//! 4. **Coverage**: Ensure comprehensive testing of all code paths
|
||||||
|
|
||||||
use super::errors::EvaluatorError;
|
use super::errors::EvaluatorError;
|
||||||
use super::evaluator::{HandRank, HandValue};
|
use super::evaluator::HandRank;
|
||||||
use super::integration::{benchmark_evaluation, utils, EvaluatorComparison, MathEvaluator};
|
use super::integration::{benchmark_evaluation, EvaluatorComparison, MathEvaluator};
|
||||||
use super::tables::{CanonicalMapping, JumpTable};
|
use super::tables::{CanonicalMapping, JumpTable};
|
||||||
use crate::card::PackedCard;
|
use crate::card::PackedCard;
|
||||||
use crate::{Card, Hand};
|
use crate::Card;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// Comprehensive test suite for the math evaluator system
|
/// Comprehensive test suite for the math evaluator system
|
||||||
|
|
@ -103,6 +103,19 @@ impl EvaluatorTestSuite {
|
||||||
let mut passed = 0;
|
let mut passed = 0;
|
||||||
let mut failed = 0;
|
let mut failed = 0;
|
||||||
|
|
||||||
|
// Create progress bar for correctness tests
|
||||||
|
let total_tests = test_hands.len() * 3; // 3 tests per hand (5, 6, 7 card)
|
||||||
|
let pb = indicatif::ProgressBar::new(total_tests as u64);
|
||||||
|
pb.set_style(
|
||||||
|
indicatif::ProgressStyle::default_bar()
|
||||||
|
.template(
|
||||||
|
"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("#>-"),
|
||||||
|
);
|
||||||
|
pb.set_message("Testing correctness");
|
||||||
|
|
||||||
for (i, cards) in test_hands.iter().enumerate() {
|
for (i, cards) in test_hands.iter().enumerate() {
|
||||||
// Test 5-card evaluation
|
// Test 5-card evaluation
|
||||||
let math_result_5 = self
|
let math_result_5 = self
|
||||||
|
|
@ -112,6 +125,7 @@ impl EvaluatorTestSuite {
|
||||||
.core_evaluator
|
.core_evaluator
|
||||||
.evaluate_5_card(&cards[..5].try_into().unwrap());
|
.evaluate_5_card(&cards[..5].try_into().unwrap());
|
||||||
|
|
||||||
|
pb.inc(1);
|
||||||
if math_result_5 == core_result_5 {
|
if math_result_5 == core_result_5 {
|
||||||
passed += 1;
|
passed += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -131,6 +145,7 @@ impl EvaluatorTestSuite {
|
||||||
.core_evaluator
|
.core_evaluator
|
||||||
.evaluate_6_card(&cards[..6].try_into().unwrap());
|
.evaluate_6_card(&cards[..6].try_into().unwrap());
|
||||||
|
|
||||||
|
pb.inc(1);
|
||||||
if math_result_6 == core_result_6 {
|
if math_result_6 == core_result_6 {
|
||||||
passed += 1;
|
passed += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -147,6 +162,7 @@ impl EvaluatorTestSuite {
|
||||||
let math_result_7 = self.math_evaluator.evaluate_7_card(cards);
|
let math_result_7 = self.math_evaluator.evaluate_7_card(cards);
|
||||||
let core_result_7 = self.core_evaluator.evaluate_7_card(cards);
|
let core_result_7 = self.core_evaluator.evaluate_7_card(cards);
|
||||||
|
|
||||||
|
pb.inc(1);
|
||||||
if math_result_7 == core_result_7 {
|
if math_result_7 == core_result_7 {
|
||||||
passed += 1;
|
passed += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -161,6 +177,8 @@ impl EvaluatorTestSuite {
|
||||||
self.stats.total_tests += 3;
|
self.stats.total_tests += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pb.finish_with_message("Correctness tests completed");
|
||||||
|
|
||||||
self.stats.passed_tests += passed;
|
self.stats.passed_tests += passed;
|
||||||
self.stats.failed_tests += failed;
|
self.stats.failed_tests += failed;
|
||||||
|
|
||||||
|
|
@ -172,16 +190,6 @@ impl EvaluatorTestSuite {
|
||||||
fn run_performance_tests(&mut self) -> Result<(), EvaluatorError> {
|
fn run_performance_tests(&mut self) -> Result<(), EvaluatorError> {
|
||||||
println!("Running performance tests...");
|
println!("Running performance tests...");
|
||||||
|
|
||||||
let test_cards = [
|
|
||||||
Card::from_str("As").unwrap(),
|
|
||||||
Card::from_str("Ks").unwrap(),
|
|
||||||
Card::from_str("Qs").unwrap(),
|
|
||||||
Card::from_str("Js").unwrap(),
|
|
||||||
Card::from_str("Ts").unwrap(),
|
|
||||||
Card::from_str("7h").unwrap(),
|
|
||||||
Card::from_str("6d").unwrap(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Test 5-card performance
|
// Test 5-card performance
|
||||||
let time_5 = benchmark_evaluation(|cards| {
|
let time_5 = benchmark_evaluation(|cards| {
|
||||||
self.math_evaluator
|
self.math_evaluator
|
||||||
|
|
@ -259,7 +267,6 @@ impl EvaluatorTestSuite {
|
||||||
|
|
||||||
// Test evaluator comparison utility
|
// Test evaluator comparison utility
|
||||||
let comparison = EvaluatorComparison::new()?;
|
let comparison = EvaluatorComparison::new()?;
|
||||||
let test_hands = utils::generate_test_hands();
|
|
||||||
let results = comparison.compare_evaluations(&[]);
|
let results = comparison.compare_evaluations(&[]);
|
||||||
|
|
||||||
// Should handle empty input gracefully
|
// Should handle empty input gracefully
|
||||||
|
|
@ -301,21 +308,21 @@ impl EvaluatorTestSuite {
|
||||||
let result_5 = self
|
let result_5 = self
|
||||||
.math_evaluator
|
.math_evaluator
|
||||||
.evaluate_5_card(&cards[..5].try_into().unwrap());
|
.evaluate_5_card(&cards[..5].try_into().unwrap());
|
||||||
assert!(result_5.rank as u8 <= HandRank::HighCard as u8);
|
assert!(result_5.rank as u8 <= HandRank::RoyalFlush as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
if cards.len() >= 6 {
|
if cards.len() >= 6 {
|
||||||
let result_6 = self
|
let result_6 = self
|
||||||
.math_evaluator
|
.math_evaluator
|
||||||
.evaluate_6_card(&cards[..6].try_into().unwrap());
|
.evaluate_6_card(&cards[..6].try_into().unwrap());
|
||||||
assert!(result_6.rank as u8 <= HandRank::HighCard as u8);
|
assert!(result_6.rank as u8 <= HandRank::RoyalFlush as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
if cards.len() >= 7 {
|
if cards.len() >= 7 {
|
||||||
let result_7 = self
|
let result_7 = self
|
||||||
.math_evaluator
|
.math_evaluator
|
||||||
.evaluate_7_card(&cards[..7].try_into().unwrap());
|
.evaluate_7_card(&cards[..7].try_into().unwrap());
|
||||||
assert!(result_7.rank as u8 <= HandRank::HighCard as u8);
|
assert!(result_7.rank as u8 <= HandRank::RoyalFlush as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Regression test {} passed", i);
|
println!("Regression test {} passed", i);
|
||||||
|
|
@ -480,6 +487,7 @@ pub fn test_jump_table_properties() -> Result<(), EvaluatorError> {
|
||||||
// Test memory usage is within bounds
|
// Test memory usage is within bounds
|
||||||
let memory_usage = table.memory_usage();
|
let memory_usage = table.memory_usage();
|
||||||
let max_memory = 200_000_000; // 200MB absolute max
|
let max_memory = 200_000_000; // 200MB absolute max
|
||||||
|
let target_memory = 130_000_000; // ~130MB target for Cactus Kev's algorithm
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
memory_usage <= max_memory,
|
memory_usage <= max_memory,
|
||||||
|
|
@ -487,6 +495,14 @@ pub fn test_jump_table_properties() -> Result<(), EvaluatorError> {
|
||||||
memory_usage
|
memory_usage
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Verify memory usage is close to target (~130MB)
|
||||||
|
assert!(
|
||||||
|
memory_usage < target_memory * 2, // Allow some tolerance but should be much less than 2x target
|
||||||
|
"Memory usage far exceeds target: {} (target ~{}MB)",
|
||||||
|
memory_usage,
|
||||||
|
target_memory / 1024 / 1024
|
||||||
|
);
|
||||||
|
|
||||||
// Test that all entries are properly initialized
|
// Test that all entries are properly initialized
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
let entry = table.get(i);
|
let entry = table.get(i);
|
||||||
|
|
@ -502,10 +518,23 @@ pub fn run_stress_tests() -> Result<(), EvaluatorError> {
|
||||||
println!("Running stress tests...");
|
println!("Running stress tests...");
|
||||||
|
|
||||||
let mut evaluator = MathEvaluator::new()?;
|
let mut evaluator = MathEvaluator::new()?;
|
||||||
|
let num_hands = 1000;
|
||||||
|
|
||||||
|
// Create progress bar for stress test setup
|
||||||
|
let setup_pb = indicatif::ProgressBar::new(num_hands);
|
||||||
|
setup_pb.set_style(
|
||||||
|
indicatif::ProgressStyle::default_bar()
|
||||||
|
.template(
|
||||||
|
"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("#>-"),
|
||||||
|
);
|
||||||
|
setup_pb.set_message("Generating test hands");
|
||||||
|
|
||||||
// Generate many test hands
|
// Generate many test hands
|
||||||
let mut test_hands = Vec::new();
|
let mut test_hands = Vec::new();
|
||||||
for i in 0..1000 {
|
for i in 0..num_hands {
|
||||||
let rank1 = (i * 7) % 13;
|
let rank1 = (i * 7) % 13;
|
||||||
let suit1 = (i * 7) % 4;
|
let suit1 = (i * 7) % 4;
|
||||||
let rank2 = (i * 11) % 13;
|
let rank2 = (i * 11) % 13;
|
||||||
|
|
@ -526,18 +555,37 @@ pub fn run_stress_tests() -> Result<(), EvaluatorError> {
|
||||||
];
|
];
|
||||||
test_hands.push(cards);
|
test_hands.push(cards);
|
||||||
}
|
}
|
||||||
|
setup_pb.inc(1);
|
||||||
}
|
}
|
||||||
|
setup_pb.finish_with_message("Test hands generated");
|
||||||
|
|
||||||
|
// Create progress bar for evaluation
|
||||||
|
let eval_pb = indicatif::ProgressBar::new(test_hands.len() as u64);
|
||||||
|
eval_pb.set_style(
|
||||||
|
indicatif::ProgressStyle::default_bar()
|
||||||
|
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) - {msg}")
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("#>-")
|
||||||
|
);
|
||||||
|
eval_pb.set_message("Evaluating hands");
|
||||||
|
|
||||||
// Evaluate all hands and measure performance
|
// Evaluate all hands and measure performance
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
for cards in &test_hands {
|
for (i, cards) in test_hands.iter().enumerate() {
|
||||||
let _result = evaluator.evaluate_7_card(cards);
|
let _result = evaluator.evaluate_7_card(cards);
|
||||||
|
|
||||||
|
// Update progress every 100 hands to minimize overhead
|
||||||
|
if i % 100 == 0 || i == test_hands.len() - 1 {
|
||||||
|
eval_pb.set_position((i + 1) as u64);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let elapsed = start_time.elapsed();
|
let elapsed = start_time.elapsed();
|
||||||
let avg_time = elapsed / test_hands.len() as u32;
|
let avg_time = elapsed / test_hands.len() as u32;
|
||||||
|
|
||||||
|
eval_pb.finish_with_message("Stress test completed");
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Stress test: {} hands in {:?}, average: {:?}",
|
"Stress test: {} hands in {:?}, average: {:?}",
|
||||||
test_hands.len(),
|
test_hands.len(),
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -355,7 +355,7 @@ impl Hand {
|
||||||
/// println!("Card: {}", card);
|
/// println!("Card: {}", card);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn iter(&self) -> std::slice::Iter<Card> {
|
pub fn iter(&self) -> std::slice::Iter<'_, Card> {
|
||||||
self.cards[0..self.len].iter()
|
self.cards[0..self.len].iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,8 @@ pub use board::Street;
|
||||||
/// Re-export error types for convenience
|
/// Re-export error types for convenience
|
||||||
pub use errors::PokerError;
|
pub use errors::PokerError;
|
||||||
|
|
||||||
|
/// Re-export algorithmic evaluator for direct evaluation
|
||||||
|
pub use evaluator::algorithmic_evaluator::{AlgorithmicEvaluator, EvaluationStats};
|
||||||
/// Re-export evaluator types for convenience
|
/// Re-export evaluator types for convenience
|
||||||
pub use evaluator::evaluator::{Evaluator, HandRank, HandValue};
|
pub use evaluator::evaluator::{Evaluator, HandRank, HandValue};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -403,11 +403,8 @@ fn test_integration_data_consistency() {
|
||||||
// This module provides integration tests for the advanced evaluator system,
|
// This module provides integration tests for the advanced evaluator system,
|
||||||
// ensuring compatibility and correct interaction with the holdem_core evaluator.
|
// ensuring compatibility and correct interaction with the holdem_core evaluator.
|
||||||
|
|
||||||
use holdem_core::card::PackedCard;
|
use holdem_core::evaluator::integration::MathEvaluator;
|
||||||
use holdem_core::evaluator::integration::{EvaluatorComparison, MathEvaluator};
|
use holdem_core::Card;
|
||||||
use holdem_core::evaluator::tables::JumpTable;
|
|
||||||
use holdem_core::evaluator::{HandRank, HandValue};
|
|
||||||
use holdem_core::{Card, Hand};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// Integration test suite for the math evaluator
|
/// Integration test suite for the math evaluator
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,9 @@ mod card_properties {
|
||||||
((12, 3), (12, 0)), // Ace spades > Ace hearts (Spades=4 > Hearts=3)
|
((12, 3), (12, 0)), // Ace spades > Ace hearts (Spades=4 > Hearts=3)
|
||||||
((12, 0), (12, 1)), // Ace hearts > Ace diamonds (Hearts=3 > Diamonds=2)
|
((12, 0), (12, 1)), // Ace hearts > Ace diamonds (Hearts=3 > Diamonds=2)
|
||||||
((12, 1), (12, 2)), // Ace diamonds > Ace clubs (Diamonds=2 > Clubs=1)
|
((12, 1), (12, 2)), // Ace diamonds > Ace clubs (Diamonds=2 > Clubs=1)
|
||||||
((5, 3), (5, 0)), // Five spades > Five hearts
|
((3, 3), (3, 0)), // Five spades > Five hearts
|
||||||
((5, 0), (5, 1)), // Five hearts > Five diamonds
|
((3, 0), (3, 1)), // Five hearts > Five diamonds
|
||||||
((5, 1), (5, 2)), // Five diamonds > Five clubs
|
((3, 1), (3, 2)), // Five diamonds > Five clubs
|
||||||
];
|
];
|
||||||
|
|
||||||
for ((rank1, suit1), (rank2, suit2)) in test_cases {
|
for ((rank1, suit1), (rank2, suit2)) in test_cases {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Seeds for failure cases proptest has generated in the past. It is
|
|
||||||
# automatically read and these particular cases re-run before any
|
|
||||||
# novel cases are generated.
|
|
||||||
#
|
|
||||||
# It is recommended to check this file in to source control so that
|
|
||||||
# everyone who runs the test benefits from these saved cases.
|
|
||||||
cc d0090c2206be394c0d408b3e3007d10014ef89cc0b289519b1a830b2c3626662 # shrinks to cards = [Card { rank: 4, suit: 0 }, Card { rank: 5, suit: 0 }, Card { rank: 2, suit: 1 }, Card { rank: 8, suit: 2 }, Card { rank: 1, suit: 3 }, Card { rank: 9, suit: 3 }]
|
|
||||||
cc ca376e60fdef26e0b7b9a3d96000de681d13a84e917ab55f95cbf536b720412c # shrinks to cards = [Card { rank: 5, suit: 0 }, Card { rank: 8, suit: 0 }, Card { rank: 12, suit: 1 }, Card { rank: 0, suit: 2 }, Card { rank: 7, suit: 2 }, Card { rank: 12, suit: 2 }, Card { rank: 7, suit: 3 }]
|
|
||||||
cc e8cbfce99bb2beeefb23c4cc317e22fb04bae3f8acd7a7edc81f7457fdb6e1fb # shrinks to hand = [Card { rank: 12, suit: 3 }, Card { rank: 11, suit: 3 }, Card { rank: 10, suit: 3 }, Card { rank: 9, suit: 3 }, Card { rank: 8, suit: 3 }]
|
|
||||||
cc d2513c8954856423773cdcdfd3a336a17f92a6984c9a4ef803b74d960678ec7f # shrinks to cards = [Card { rank: 8, suit: 0 }, Card { rank: 11, suit: 1 }, Card { rank: 2, suit: 2 }, Card { rank: 11, suit: 3 }, Card { rank: 12, suit: 3 }]
|
|
||||||
cc 3cb176da85ed776101a34339707b68f3bafa03ebd9f7fb495c36590e6a64c3f5 # shrinks to cards = [Card { rank: 7, suit: 1 }, Card { rank: 6, suit: 2 }, Card { rank: 11, suit: 2 }, Card { rank: 12, suit: 2 }, Card { rank: 11, suit: 3 }]
|
|
||||||
cc 487fd65fa6cee58dfe754c01cdff15913fea01e4b90b6c0bfc2d45823a587f90 # shrinks to hand = [Card { rank: 12, suit: 3 }, Card { rank: 11, suit: 3 }, Card { rank: 10, suit: 3 }, Card { rank: 9, suit: 3 }, Card { rank: 8, suit: 3 }]
|
|
||||||
cc 7e46fa2f7f3ecb235753efedbca0d196d025147d51fc16f863e965af0da32654 # shrinks to hand1 = [Card { rank: 12, suit: 3 }, Card { rank: 11, suit: 3 }, Card { rank: 10, suit: 3 }, Card { rank: 9, suit: 3 }, Card { rank: 8, suit: 3 }], hand2 = [Card { rank: 12, suit: 3 }, Card { rank: 11, suit: 3 }, Card { rank: 10, suit: 3 }, Card { rank: 9, suit: 3 }, Card { rank: 8, suit: 3 }]
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"rustc_fingerprint":9168241167641241758,"outputs":{"16495917692426387086":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\n","stderr":""},"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\jan\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.81.0 (eeb90cda1 2024-09-04)\nbinary: rustc\ncommit-hash: eeb90cda1969383f56a2637cbd3037bdf598841c\ncommit-date: 2024-09-04\nhost: x86_64-pc-windows-msvc\nrelease: 1.81.0\nLLVM version: 18.1.7\n","stderr":""}},"successes":{}}
|
{"rustc_fingerprint":12042185895431567242,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.90.0 (1159e78c4 2025-09-14)\nbinary: rustc\ncommit-hash: 1159e78c4747b02ef996e55082b704c09b970588\ncommit-date: 2025-09-14\nhost: x86_64-pc-windows-msvc\nrelease: 1.90.0\nLLVM version: 20.1.8\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\jan\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue