Fixed hand evaluator LUT generation
This commit is contained in:
parent
7819b1ee27
commit
51e1f58ef9
17 changed files with 2045 additions and 660 deletions
|
|
@ -23,9 +23,11 @@ sha2 = "0.10"
|
|||
tempfile = "3.23.0"
|
||||
once_cell = "1.19"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
indicatif = "0.17"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1.0"
|
||||
indicatif = "0.17"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
use super::errors::EvaluatorError;
|
||||
use super::evaluator::{HandRank, HandValue};
|
||||
use super::integration::{benchmark_evaluation, utils, EvaluatorComparison, MathEvaluator};
|
||||
use super::integration::{EvaluatorComparison, MathEvaluator};
|
||||
use super::tables::{CanonicalMapping, JumpTable};
|
||||
use crate::card::PackedCard;
|
||||
use crate::{Card, Hand};
|
||||
|
|
@ -250,7 +250,7 @@ pub fn canonicalization_example() -> Result<(), EvaluatorError> {
|
|||
mapping
|
||||
.canonical_cards
|
||||
.iter()
|
||||
.map(|&c| (c & 0x03))
|
||||
.map(|&c| c & 0x03)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
println!(" Suit mapping: {:?}", mapping.suit_map);
|
||||
|
|
@ -444,14 +444,14 @@ fn extract_suits(cards: &[PackedCard]) -> Vec<u8> {
|
|||
}
|
||||
|
||||
/// Helper function to extract ranks from cards
|
||||
fn extract_ranks(cards: &[PackedCard]) -> Vec<u8> {
|
||||
cards.iter().map(|card| card.rank()).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn extract_ranks(cards: &[PackedCard]) -> Vec<u8> {
|
||||
cards.iter().map(|card| card.rank()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_usage_example() {
|
||||
assert!(basic_usage_example().is_ok());
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
use super::errors::EvaluatorError;
|
||||
use super::evaluator::{HandRank, HandValue};
|
||||
use super::tables::{CanonicalMapping, JumpTable, JumpTableEntry};
|
||||
use super::tables::JumpTable;
|
||||
use crate::card::PackedCard;
|
||||
use crate::{Card, Hand};
|
||||
use std::time::{Duration, Instant};
|
||||
|
|
@ -215,7 +215,7 @@ impl MathEvaluator {
|
|||
pub fn convert_cards(cards: &[Card]) -> Vec<PackedCard> {
|
||||
cards
|
||||
.iter()
|
||||
.map(|card| PackedCard::from_card(card))
|
||||
.map(|card| PackedCard::new(card.rank(), card.suit()).unwrap())
|
||||
.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] {
|
||||
let mut result = [PackedCard::new(0, 0).unwrap(); N];
|
||||
for (i, card) in cards.iter().enumerate() {
|
||||
result[i] = PackedCard::from_card(card);
|
||||
result[i] = PackedCard::new(card.rank(), card.suit()).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
|
@ -392,13 +392,13 @@ impl EvaluatorComparison {
|
|||
let mut core_times = Vec::new();
|
||||
|
||||
// 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));
|
||||
math_times.push(math_time);
|
||||
}
|
||||
|
||||
// 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));
|
||||
core_times.push(core_time);
|
||||
}
|
||||
|
|
@ -473,8 +473,6 @@ pub mod utils {
|
|||
|
||||
/// Validate that math evaluator produces same results as holdem_core
|
||||
pub fn validate_evaluator_compatibility() -> Result<(), EvaluatorError> {
|
||||
use std::str::FromStr;
|
||||
|
||||
let comparison = EvaluatorComparison::new()?;
|
||||
|
||||
// Test a few representative hands
|
||||
|
|
@ -647,19 +645,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_benchmark_function() {
|
||||
use std::str::FromStr;
|
||||
|
||||
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| {
|
||||
let elapsed = benchmark_evaluation(|_cards| {
|
||||
// Simple evaluation for testing
|
||||
HandValue::new(HandRank::HighCard, 0)
|
||||
});
|
||||
|
|
@ -672,7 +658,7 @@ mod tests {
|
|||
let comparison = EvaluatorComparison::new();
|
||||
assert!(comparison.is_ok());
|
||||
|
||||
let mut comparison = EvaluatorComparison::new().unwrap();
|
||||
let comparison = EvaluatorComparison::new().unwrap();
|
||||
let results = comparison.compare_evaluations(&[]);
|
||||
|
||||
// Should handle empty hand list gracefully
|
||||
|
|
|
|||
|
|
@ -13,16 +13,25 @@
|
|||
//! - **`property_tests`**: Property-based testing for evaluation correctness
|
||||
//! - **`examples`**: Usage examples and performance benchmarks
|
||||
|
||||
pub mod algorithmic_evaluator;
|
||||
pub mod benchmark_utils;
|
||||
pub mod errors;
|
||||
pub mod evaluator;
|
||||
pub mod examples;
|
||||
pub mod file_io;
|
||||
pub mod integration;
|
||||
pub mod integration_utils;
|
||||
pub mod property_tests;
|
||||
pub mod singleton;
|
||||
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
|
||||
pub use algorithmic_evaluator::{AlgorithmicEvaluator, EvaluationStats};
|
||||
pub use errors::EvaluatorError;
|
||||
pub use evaluator::{Evaluator, HandRank, HandValue};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@
|
|||
//! 4. **Coverage**: Ensure comprehensive testing of all code paths
|
||||
|
||||
use super::errors::EvaluatorError;
|
||||
use super::evaluator::{HandRank, HandValue};
|
||||
use super::integration::{benchmark_evaluation, utils, EvaluatorComparison, MathEvaluator};
|
||||
use super::evaluator::HandRank;
|
||||
use super::integration::{benchmark_evaluation, EvaluatorComparison, MathEvaluator};
|
||||
use super::tables::{CanonicalMapping, JumpTable};
|
||||
use crate::card::PackedCard;
|
||||
use crate::{Card, Hand};
|
||||
use crate::Card;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Comprehensive test suite for the math evaluator system
|
||||
|
|
@ -103,6 +103,19 @@ impl EvaluatorTestSuite {
|
|||
let mut passed = 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() {
|
||||
// Test 5-card evaluation
|
||||
let math_result_5 = self
|
||||
|
|
@ -112,6 +125,7 @@ impl EvaluatorTestSuite {
|
|||
.core_evaluator
|
||||
.evaluate_5_card(&cards[..5].try_into().unwrap());
|
||||
|
||||
pb.inc(1);
|
||||
if math_result_5 == core_result_5 {
|
||||
passed += 1;
|
||||
} else {
|
||||
|
|
@ -131,6 +145,7 @@ impl EvaluatorTestSuite {
|
|||
.core_evaluator
|
||||
.evaluate_6_card(&cards[..6].try_into().unwrap());
|
||||
|
||||
pb.inc(1);
|
||||
if math_result_6 == core_result_6 {
|
||||
passed += 1;
|
||||
} else {
|
||||
|
|
@ -147,6 +162,7 @@ impl EvaluatorTestSuite {
|
|||
let math_result_7 = self.math_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 {
|
||||
passed += 1;
|
||||
} else {
|
||||
|
|
@ -161,6 +177,8 @@ impl EvaluatorTestSuite {
|
|||
self.stats.total_tests += 3;
|
||||
}
|
||||
|
||||
pb.finish_with_message("Correctness tests completed");
|
||||
|
||||
self.stats.passed_tests += passed;
|
||||
self.stats.failed_tests += failed;
|
||||
|
||||
|
|
@ -172,16 +190,6 @@ impl EvaluatorTestSuite {
|
|||
fn run_performance_tests(&mut self) -> Result<(), EvaluatorError> {
|
||||
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
|
||||
let time_5 = benchmark_evaluation(|cards| {
|
||||
self.math_evaluator
|
||||
|
|
@ -259,7 +267,6 @@ impl EvaluatorTestSuite {
|
|||
|
||||
// Test evaluator comparison utility
|
||||
let comparison = EvaluatorComparison::new()?;
|
||||
let test_hands = utils::generate_test_hands();
|
||||
let results = comparison.compare_evaluations(&[]);
|
||||
|
||||
// Should handle empty input gracefully
|
||||
|
|
@ -301,21 +308,21 @@ impl EvaluatorTestSuite {
|
|||
let result_5 = self
|
||||
.math_evaluator
|
||||
.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 {
|
||||
let result_6 = self
|
||||
.math_evaluator
|
||||
.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 {
|
||||
let result_7 = self
|
||||
.math_evaluator
|
||||
.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);
|
||||
|
|
@ -480,6 +487,7 @@ pub fn test_jump_table_properties() -> Result<(), EvaluatorError> {
|
|||
// Test memory usage is within bounds
|
||||
let memory_usage = table.memory_usage();
|
||||
let max_memory = 200_000_000; // 200MB absolute max
|
||||
let target_memory = 130_000_000; // ~130MB target for Cactus Kev's algorithm
|
||||
|
||||
assert!(
|
||||
memory_usage <= max_memory,
|
||||
|
|
@ -487,6 +495,14 @@ pub fn test_jump_table_properties() -> Result<(), EvaluatorError> {
|
|||
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
|
||||
for i in 0..100 {
|
||||
let entry = table.get(i);
|
||||
|
|
@ -502,10 +518,23 @@ pub fn run_stress_tests() -> Result<(), EvaluatorError> {
|
|||
println!("Running stress tests...");
|
||||
|
||||
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
|
||||
let mut test_hands = Vec::new();
|
||||
for i in 0..1000 {
|
||||
for i in 0..num_hands {
|
||||
let rank1 = (i * 7) % 13;
|
||||
let suit1 = (i * 7) % 4;
|
||||
let rank2 = (i * 11) % 13;
|
||||
|
|
@ -526,18 +555,37 @@ pub fn run_stress_tests() -> Result<(), EvaluatorError> {
|
|||
];
|
||||
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
|
||||
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);
|
||||
|
||||
// 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 avg_time = elapsed / test_hands.len() as u32;
|
||||
|
||||
eval_pb.finish_with_message("Stress test completed");
|
||||
|
||||
println!(
|
||||
"Stress test: {} hands in {:?}, average: {:?}",
|
||||
test_hands.len(),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -355,7 +355,7 @@ impl Hand {
|
|||
/// 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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ pub use board::Street;
|
|||
/// Re-export error types for convenience
|
||||
pub use errors::PokerError;
|
||||
|
||||
/// Re-export algorithmic evaluator for direct evaluation
|
||||
pub use evaluator::algorithmic_evaluator::{AlgorithmicEvaluator, EvaluationStats};
|
||||
/// Re-export evaluator types for convenience
|
||||
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,
|
||||
// ensuring compatibility and correct interaction with the holdem_core evaluator.
|
||||
|
||||
use holdem_core::card::PackedCard;
|
||||
use holdem_core::evaluator::integration::{EvaluatorComparison, MathEvaluator};
|
||||
use holdem_core::evaluator::tables::JumpTable;
|
||||
use holdem_core::evaluator::{HandRank, HandValue};
|
||||
use holdem_core::{Card, Hand};
|
||||
use holdem_core::evaluator::integration::MathEvaluator;
|
||||
use holdem_core::Card;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// 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, 0), (12, 1)), // Ace hearts > Ace diamonds (Hearts=3 > Diamonds=2)
|
||||
((12, 1), (12, 2)), // Ace diamonds > Ace clubs (Diamonds=2 > Clubs=1)
|
||||
((5, 3), (5, 0)), // Five spades > Five hearts
|
||||
((5, 0), (5, 1)), // Five hearts > Five diamonds
|
||||
((5, 1), (5, 2)), // Five diamonds > Five clubs
|
||||
((3, 3), (3, 0)), // Five spades > Five hearts
|
||||
((3, 0), (3, 1)), // Five hearts > Five diamonds
|
||||
((3, 1), (3, 2)), // Five diamonds > Five clubs
|
||||
];
|
||||
|
||||
for ((rank1, suit1), (rank2, suit2)) in test_cases {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue