feat: add AlgorithmicEvaluator, migrate to log crate, fix flush value calculation

- Add AlgorithmicEvaluator wrapper for direct hand evaluation without
  lookup tables, with proper Result-based error handling
- Replace all println! and #[cfg(debug_assertions)] debug output with
  log crate macros (log::info!, log::debug!, log::warn!)
- Fix calculate_flush_value to use base-13 encoding for unique flush
  hand values instead of simple linear sum
- Fix is_straight to use cleaner control flow for regular and wheel
  straight detection
- Remove jump table HighCard/value=0 guard in evaluate_5_card to
  simplify evaluation path
- Add evaluate_n_card_hand_public as pub(crate) for AlgorithmicEvaluator
- Add .gitignore to exclude target/ and .idea/ from version control
- Add comprehensive tests for AlgorithmicEvaluator (14 test cases)
- Update README with new API usage examples
- Add log dependency to Cargo.toml
- Add placeholder modules for future implementations (tables_new,
  benchmark_utils, integration_utils, test_utils)
This commit is contained in:
Jan-Peter von Hunnius 2026-05-11 20:22:32 +02:00
parent 51e1f58ef9
commit dd605e8a66
16 changed files with 1666 additions and 228 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
# Build artifacts
/target
# IDE
.idea/
*.iml
# OS
.DS_Store
Thumbs.db

1145
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
# Poker Testbed - Java-Compatible Hand Evaluator for Bot Testing # Poker Testbed - Java-Compatible Hand Evaluator for Bot Testing
[![Crates.io](https://img.shields.io/crates/v/poker-api.svg)](https://crates.io/crates/poker-api) [![Crates.io](https://img.shields.io/crates/v/holdem_core.svg)](https://crates.io/crates/holdem_core)
[![Documentation](https://docs.rs/poker-api/badge.svg)](https://docs.rs/poker-api) [![Documentation](https://docs.rs/holdem_core/badge.svg)](https://docs.rs/holdem_core)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
A comprehensive poker bot testing framework with Java-compatible hand evaluation. This library implements the Meerkat API algorithm with full Java compatibility, specifically designed for automated poker bot testing, development, and integration with existing poker tools. A comprehensive poker bot testing framework with Java-compatible hand evaluation. This library implements the Meerkat API algorithm with full Java compatibility, specifically designed for automated poker bot testing, development, and integration with existing poker tools.
@ -50,31 +50,39 @@ Add this to your `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]
poker-api = "0.1.0" holdem_core = "0.1.0"
``` ```
## Quick Start - Bot Testing ## Quick Start - Bot Testing
```rust ```rust
use holdem_core::hand_evaluator::LookupHandEvaluator; use holdem_core::{Card, Hand, HoleCards, Board, Evaluator};
use holdem_core::api::{hand::Hand, card::Card}; use std::str::FromStr;
// Initialize Java-compatible evaluator for bot testing // Create cards from string notation
let evaluator = LookupHandEvaluator::new().unwrap(); let ace_spades = Card::from_str("As").unwrap();
let king_spades = Card::from_str("Ks").unwrap();
// Create hole cards for bot analysis // Create hole cards from notation
let mut hole_cards = Hand::new(); let hole_cards = HoleCards::from_notation("AKs").unwrap();
hole_cards.add_card(Card::from_string("As").unwrap()).unwrap();
hole_cards.add_card(Card::from_string("Ks").unwrap()).unwrap();
// Evaluate hand strength for bot decision making // Create a board with community cards
let rank = evaluator.rank_hand(&hole_cards); let board = Board::new()
println!("Hole card rank: {}", rank); // Lower = stronger hand .with_flop([
Card::from_str("Qh").unwrap(),
Card::from_str("Jh").unwrap(),
Card::from_str("Th").unwrap(),
]).unwrap()
.with_turn(Card::from_str("2c").unwrap()).unwrap()
.with_river(Card::from_str("3d").unwrap()).unwrap();
// Use rank for bot strategy decisions // Create a hand from hole cards and board
if rank < 1000 { // Strong hand threshold let hand = Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap();
println!("Bot: This is a premium starting hand!");
} // Evaluate the hand
let evaluator = Evaluator::new().unwrap();
let hand_value = evaluator.evaluate_hand(&hand);
println!("Hand rank: {:?}", hand_value.rank);
``` ```
## Bot Testing Capabilities ## Bot Testing Capabilities

View file

@ -8,10 +8,10 @@ readme = "README.md"
license = "MIT" license = "MIT"
repository = "https://github.com/jhunnius/rusty_marvin" repository = "https://github.com/jhunnius/rusty_marvin"
documentation = "https://docs.rs/holdem_core" documentation = "https://docs.rs/holdem_core"
homepage = "https://github.com/your-org/holdem_core" homepage = "https://github.com/jhunnius/rusty_marvin"
keywords = ["poker", "bot-testing", "texas-holdem", "testbed"] keywords = ["poker", "bot-testing", "texas-holdem", "testbed"]
categories = ["games", "api-bindings", "mathematics"] categories = ["games", "api-bindings", "mathematics"]
exclude = ["docs/*", "target/*", "*.md"] exclude = ["docs/*", "target/*"]
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@ -24,6 +24,7 @@ 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" indicatif = "0.17"
log = "0.4"
[dev-dependencies] [dev-dependencies]
proptest = "1.0" proptest = "1.0"

View file

@ -0,0 +1,67 @@
//! Algorithmic hand evaluator for direct poker hand evaluation.
//!
//! This module provides a standalone evaluator that uses pure algorithmic
//! methods (no lookup tables) for evaluating poker hands. It wraps the
//! direct evaluation logic from [`Evaluator::evaluate_5_card_direct`].
use crate::Card;
use super::errors::EvaluatorError;
use super::evaluator::{Evaluator, HandValue};
use std::sync::Arc;
/// Statistics tracking for evaluation operations.
#[derive(Debug, Clone, Default)]
pub struct EvaluationStats {
/// Total number of evaluations performed.
pub total_evaluations: u64,
/// Average time per evaluation in nanoseconds.
pub average_time_ns: u64,
/// Total time spent evaluating in nanoseconds.
pub total_time_ns: u64,
}
/// Algorithmic poker hand evaluator.
///
/// Performs direct hand evaluation without relying on lookup tables.
/// Delegates to the same core algorithm used by [`Evaluator::evaluate_5_card_direct`].
#[derive(Debug, Clone)]
pub struct AlgorithmicEvaluator {
inner: Arc<Evaluator>,
stats: EvaluationStats,
}
impl AlgorithmicEvaluator {
/// Create a new algorithmic evaluator instance.
pub fn new() -> Result<Self, EvaluatorError> {
let inner = Evaluator::new()?;
Ok(Self {
inner: Arc::new(inner),
stats: EvaluationStats::default(),
})
}
/// Evaluate a 5-card hand directly using the algorithmic approach.
pub fn evaluate_5_card_direct(&self, cards: &[Card; 5]) -> HandValue {
self.inner.evaluate_5_card_direct(cards)
}
/// Evaluate a 6-card hand, finding the best 5-card combination.
pub fn evaluate_6_card(&self, cards: &[Card; 6]) -> HandValue {
self.inner.evaluate_n_card_hand_public(cards)
}
/// Evaluate a 7-card hand, finding the best 5-card combination.
pub fn evaluate_7_card(&self, cards: &[Card; 7]) -> HandValue {
self.inner.evaluate_n_card_hand_public(cards)
}
/// Get a reference to the current evaluation statistics.
pub fn stats(&self) -> &EvaluationStats {
&self.stats
}
/// Reset the evaluation statistics.
pub fn reset_stats(&mut self) {
self.stats = EvaluationStats::default();
}
}

View file

@ -0,0 +1,200 @@
//! Tests for the algorithmic evaluator.
use super::algorithmic_evaluator::AlgorithmicEvaluator;
use super::evaluator::HandRank;
use crate::Card;
use std::str::FromStr;
#[test]
fn test_new_evaluator() {
let evaluator = AlgorithmicEvaluator::new();
assert!(evaluator.is_ok());
}
#[test]
fn test_royal_flush() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let 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(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::RoyalFlush);
}
#[test]
fn test_straight_flush() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("9s").unwrap(),
Card::from_str("Ks").unwrap(),
Card::from_str("Qs").unwrap(),
Card::from_str("Js").unwrap(),
Card::from_str("Ts").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::StraightFlush);
}
#[test]
fn test_four_of_a_kind() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("Ah").unwrap(),
Card::from_str("Ad").unwrap(),
Card::from_str("Ac").unwrap(),
Card::from_str("Ks").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::FourOfAKind);
}
#[test]
fn test_full_house() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("Ah").unwrap(),
Card::from_str("Ad").unwrap(),
Card::from_str("Ks").unwrap(),
Card::from_str("Kh").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::FullHouse);
}
#[test]
fn test_flush() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("Ks").unwrap(),
Card::from_str("Qs").unwrap(),
Card::from_str("Js").unwrap(),
Card::from_str("9s").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::Flush);
}
#[test]
fn test_straight() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("Kh").unwrap(),
Card::from_str("Qd").unwrap(),
Card::from_str("Jc").unwrap(),
Card::from_str("Ts").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::Straight);
}
#[test]
fn test_wheel_straight() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("2h").unwrap(),
Card::from_str("3d").unwrap(),
Card::from_str("4c").unwrap(),
Card::from_str("5s").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::Straight);
}
#[test]
fn test_three_of_a_kind() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("Ah").unwrap(),
Card::from_str("Ad").unwrap(),
Card::from_str("Ks").unwrap(),
Card::from_str("Qh").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::ThreeOfAKind);
}
#[test]
fn test_two_pair() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("Ah").unwrap(),
Card::from_str("Ks").unwrap(),
Card::from_str("Kh").unwrap(),
Card::from_str("Qd").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::TwoPair);
}
#[test]
fn test_pair() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("Ah").unwrap(),
Card::from_str("Ks").unwrap(),
Card::from_str("Qh").unwrap(),
Card::from_str("Jd").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::Pair);
}
#[test]
fn test_high_card() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let cards = [
Card::from_str("As").unwrap(),
Card::from_str("Kh").unwrap(),
Card::from_str("Qd").unwrap(),
Card::from_str("Jc").unwrap(),
Card::from_str("9s").unwrap(),
];
let result = evaluator.evaluate_5_card_direct(&cards);
assert_eq!(result.rank, HandRank::HighCard);
}
#[test]
fn test_hand_rank_ordering() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let royal_flush = [
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(),
];
let four_of_a_kind = [
Card::from_str("As").unwrap(),
Card::from_str("Ah").unwrap(),
Card::from_str("Ad").unwrap(),
Card::from_str("Ac").unwrap(),
Card::from_str("Ks").unwrap(),
];
let rf = evaluator.evaluate_5_card_direct(&royal_flush);
let foak = evaluator.evaluate_5_card_direct(&four_of_a_kind);
assert!(rf.rank as u8 > foak.rank as u8);
}
#[test]
fn test_stats_initially_default() {
let evaluator = AlgorithmicEvaluator::new().unwrap();
let stats = evaluator.stats();
assert_eq!(stats.total_evaluations, 0);
assert_eq!(stats.average_time_ns, 0);
assert_eq!(stats.total_time_ns, 0);
}

View file

@ -0,0 +1,4 @@
//! Benchmark utilities for the hand evaluator.
//!
//! This module provides helper functions and data structures for benchmarking
//! hand evaluation performance.

View file

@ -321,12 +321,12 @@ impl JumpTableBenchmark {
/// Print benchmark results in a formatted table /// Print benchmark results in a formatted table
pub fn print_results(&self, results: &[BenchmarkResult]) { pub fn print_results(&self, results: &[BenchmarkResult]) {
println!("\n=== Jump Table Performance Benchmarks ==="); log::info!("\n=== Jump Table Performance Benchmarks ===");
println!("{:<20} {:<15} {:<15} {:<15} {}", log::info!("{:<20} {:<15} {:<15} {:<15} {}",
"Test", "Total Time", "Avg Time/Op", "Ops/Second", "Memory"); "Test", "Total Time", "Avg Time/Op", "Ops/Second", "Memory");
for result in results { for result in results {
println!( log::info!(
"{:<20} {:<15} {:<15} {:<15.0} {}", "{:<20} {:<15} {:<15} {:<15.0} {}",
result.name, result.name,
format!("{:.2?}", result.total_time), format!("{:.2?}", result.total_time),
@ -339,7 +339,7 @@ impl JumpTableBenchmark {
); );
} }
println!("\n=== Performance Requirements Check ==="); log::info!("\n=== Performance Requirements Check ===");
self.check_performance_requirements(results); self.check_performance_requirements(results);
} }
@ -350,7 +350,7 @@ impl JumpTableBenchmark {
"5-Card Evaluation" => { "5-Card Evaluation" => {
let target_ns = 100; // Target: 100 nanoseconds let target_ns = 100; // Target: 100 nanoseconds
let actual_ns = result.avg_time_per_op.as_nanos() as u64; let actual_ns = result.avg_time_per_op.as_nanos() as u64;
println!( log::info!(
"5-Card Evaluation: {} ns (target: {} ns) - {}", "5-Card Evaluation: {} ns (target: {} ns) - {}",
actual_ns, actual_ns,
target_ns, target_ns,
@ -360,7 +360,7 @@ impl JumpTableBenchmark {
"6-Card Evaluation" => { "6-Card Evaluation" => {
let target_ns = 500; // Target: 500 nanoseconds let target_ns = 500; // Target: 500 nanoseconds
let actual_ns = result.avg_time_per_op.as_nanos() as u64; let actual_ns = result.avg_time_per_op.as_nanos() as u64;
println!( log::info!(
"6-Card Evaluation: {} ns (target: {} ns) - {}", "6-Card Evaluation: {} ns (target: {} ns) - {}",
actual_ns, actual_ns,
target_ns, target_ns,
@ -370,7 +370,7 @@ impl JumpTableBenchmark {
"7-Card Evaluation" => { "7-Card Evaluation" => {
let target_us = 2; // Target: 2 microseconds let target_us = 2; // Target: 2 microseconds
let actual_us = result.avg_time_per_op.as_micros() as u64; let actual_us = result.avg_time_per_op.as_micros() as u64;
println!( log::info!(
"7-Card Evaluation: {} μs (target: {} μs) - {}", "7-Card Evaluation: {} μs (target: {} μs) - {}",
actual_us, actual_us,
target_us, target_us,
@ -381,7 +381,7 @@ impl JumpTableBenchmark {
let target_mb = 130; // Target: 130 MB let target_mb = 130; // Target: 130 MB
if let Some(bytes) = result.memory_usage { if let Some(bytes) = result.memory_usage {
let actual_mb = bytes / 1_000_000; let actual_mb = bytes / 1_000_000;
println!( log::info!(
"Memory Usage: {} MB (target: {} MB) - {}", "Memory Usage: {} MB (target: {} MB) - {}",
actual_mb, actual_mb,
target_mb, target_mb,

View file

@ -145,101 +145,88 @@ impl HandValue {
} }
// DEBUG: Log the flat rank being processed // DEBUG: Log the flat rank being processed
#[cfg(debug_assertions)] log::debug!("Processing flat_rank: {}", flat_rank);
println!("DEBUG: Processing flat_rank: {}", flat_rank);
// Find which rank this flat_rank belongs to using proper poker hand distribution // Find which rank this flat_rank belongs to using proper poker hand distribution
let result = match flat_rank { let result = match flat_rank {
1..=1_302_539 => { 1..=1_302_539 => {
#[cfg(debug_assertions)] log::debug!("Mapping to HighCard with value {}", flat_rank);
println!("DEBUG: Mapping to HighCard with value {}", flat_rank);
Self::new(HandRank::HighCard, flat_rank) Self::new(HandRank::HighCard, flat_rank)
} }
1_302_540..=2_400_779 => { 1_302_540..=2_400_779 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to Pair with value {}",
"DEBUG: Mapping to Pair with value {}",
flat_rank - 1_302_540 flat_rank - 1_302_540
); );
Self::new(HandRank::Pair, flat_rank - 1_302_540) Self::new(HandRank::Pair, flat_rank - 1_302_540)
} }
2_400_780..=2_524_331 => { 2_400_780..=2_524_331 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to TwoPair with value {}",
"DEBUG: Mapping to TwoPair with value {}",
flat_rank - 2_400_780 flat_rank - 2_400_780
); );
Self::new(HandRank::TwoPair, flat_rank - 2_400_780) Self::new(HandRank::TwoPair, flat_rank - 2_400_780)
} }
2_524_332..=2_579_243 => { 2_524_332..=2_579_243 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to ThreeOfAKind with value {}",
"DEBUG: Mapping to ThreeOfAKind with value {}",
flat_rank - 2_524_332 flat_rank - 2_524_332
); );
Self::new(HandRank::ThreeOfAKind, flat_rank - 2_524_332) Self::new(HandRank::ThreeOfAKind, flat_rank - 2_524_332)
} }
2_579_244..=2_589_443 => { 2_579_244..=2_589_443 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to Straight with value {}",
"DEBUG: Mapping to Straight with value {}",
flat_rank - 2_579_244 flat_rank - 2_579_244
); );
Self::new(HandRank::Straight, flat_rank - 2_579_244) Self::new(HandRank::Straight, flat_rank - 2_579_244)
} }
2_589_444..=2_594_551 => { 2_589_444..=2_594_551 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to Flush with value {}",
"DEBUG: Mapping to Flush with value {}",
flat_rank - 2_589_444 flat_rank - 2_589_444
); );
Self::new(HandRank::Flush, flat_rank - 2_589_444) Self::new(HandRank::Flush, flat_rank - 2_589_444)
} }
2_594_552..=2_598_295 => { 2_594_552..=2_598_295 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to FullHouse with value {}",
"DEBUG: Mapping to FullHouse with value {}",
flat_rank - 2_594_552 flat_rank - 2_594_552
); );
Self::new(HandRank::FullHouse, flat_rank - 2_594_552) Self::new(HandRank::FullHouse, flat_rank - 2_594_552)
} }
2_598_296..=2_598_919 => { 2_598_296..=2_598_919 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to FourOfAKind with value {}",
"DEBUG: Mapping to FourOfAKind with value {}",
flat_rank - 2_598_296 flat_rank - 2_598_296
); );
Self::new(HandRank::FourOfAKind, flat_rank - 2_598_296) Self::new(HandRank::FourOfAKind, flat_rank - 2_598_296)
} }
2_598_920..=2_598_955 => { 2_598_920..=2_598_955 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to StraightFlush with value {}",
"DEBUG: Mapping to StraightFlush with value {}",
flat_rank - 2_598_920 flat_rank - 2_598_920
); );
Self::new(HandRank::StraightFlush, flat_rank - 2_598_920) Self::new(HandRank::StraightFlush, flat_rank - 2_598_920)
} }
2_598_956..=2_598_959 => { 2_598_956..=2_598_959 => {
#[cfg(debug_assertions)] log::debug!(
println!( "Mapping to RoyalFlush with value {}",
"DEBUG: Mapping to RoyalFlush with value {}",
flat_rank - 2_598_956 flat_rank - 2_598_956
); );
Self::new(HandRank::RoyalFlush, flat_rank - 2_598_956) Self::new(HandRank::RoyalFlush, flat_rank - 2_598_956)
} }
_ => { _ => {
// Out of range, fallback to high card // Out of range, fallback to high card
#[cfg(debug_assertions)] log::debug!(
println!( "Out of range, falling back to HighCard with value {}",
"DEBUG: Out of range, falling back to HighCard with value {}",
flat_rank flat_rank
); );
Self::new(HandRank::HighCard, flat_rank) Self::new(HandRank::HighCard, flat_rank)
} }
}; };
#[cfg(debug_assertions)] log::debug!("Result: {:?}", result);
println!("DEBUG: Result: {:?}", result);
result result
} }
@ -634,11 +621,11 @@ impl Evaluator {
if JumpTable::exists(&path) { if JumpTable::exists(&path) {
match JumpTable::load_from_file(&path) { match JumpTable::load_from_file(&path) {
Ok(table) => { Ok(table) => {
println!("Loaded existing jump table from: {}", path.display()); log::info!("Loaded existing jump table from: {}", path.display());
return Ok(table); return Ok(table);
} }
Err(e) => { Err(e) => {
println!( log::warn!(
"Failed to load existing jump table ({}), creating new one...", "Failed to load existing jump table ({}), creating new one...",
e e
); );
@ -652,7 +639,7 @@ impl Evaluator {
/// Create a new jump table and save it to file /// Create a new jump table and save it to file
fn create_and_save_table(path: &Path) -> Result<JumpTable, EvaluatorError> { fn create_and_save_table(path: &Path) -> Result<JumpTable, EvaluatorError> {
println!("Creating new jump table..."); log::info!("Creating new jump table...");
let mut table = JumpTable::with_target_memory(); let mut table = JumpTable::with_target_memory();
// Build the jump table with proper evaluation data // Build the jump table with proper evaluation data
@ -668,7 +655,7 @@ impl Evaluator {
EvaluatorError::table_init_failed(&format!("Failed to save jump table: {}", e)) EvaluatorError::table_init_failed(&format!("Failed to save jump table: {}", e))
})?; })?;
println!( log::info!(
"Evaluator created successfully with {} table entries and saved to: {}", "Evaluator created successfully with {} table entries and saved to: {}",
table.size, table.size,
path.display() path.display()
@ -696,11 +683,7 @@ impl Evaluator {
let packed_array: [PackedCard; 5] = packed_cards.try_into().unwrap(); let packed_array: [PackedCard; 5] = packed_cards.try_into().unwrap();
if let Ok(result) = self.tables.evaluate_5_card(&packed_array) { if let Ok(result) = self.tables.evaluate_5_card(&packed_array) {
// Only use jump table result if it's not the default HighCard with value 0 return result;
// This indicates the jump table lookup succeeded
if result.rank != HandRank::HighCard || result.value != 0 {
return result;
}
} }
} }
@ -771,6 +754,12 @@ impl Evaluator {
best_hand_value best_hand_value
} }
/// Crate-internal wrapper for evaluating an N-card hand by finding the best 5-card combination.
/// Used by [`AlgorithmicEvaluator`](super::algorithmic_evaluator::AlgorithmicEvaluator).
pub(crate) fn evaluate_n_card_hand_public<T: AsRef<[Card]>>(&self, cards: T) -> HandValue {
self.evaluate_n_card_hand(cards)
}
/// Generate all combinations of K elements from a set of N elements /// Generate all combinations of K elements from a set of N elements
fn generate_combinations(&self, set: &[usize], k: usize) -> Vec<Vec<usize>> { fn generate_combinations(&self, set: &[usize], k: usize) -> Vec<Vec<usize>> {
let mut result = Vec::new(); let mut result = Vec::new();
@ -909,17 +898,19 @@ impl Evaluator {
/// Check if ranks form a straight /// Check if ranks form a straight
fn is_straight(&self, ranks: &[u8; 5]) -> bool { fn is_straight(&self, ranks: &[u8; 5]) -> bool {
// Check for regular straight (consecutive ranks) // Check for regular straight (consecutive ranks descending)
let mut is_regular = true;
for i in 0..4 { for i in 0..4 {
if ranks[i] != ranks[i + 1] + 1 { if ranks[i] != ranks[i + 1] + 1 {
// Check for wheel straight (A,2,3,4,5) is_regular = false;
if *ranks == [12, 3, 2, 1, 0] { break;
return true;
}
return false;
} }
} }
true if is_regular {
return true;
}
// Check for wheel straight: A,2,3,4,5 → sorted desc: [12,3,2,1,0]
*ranks == [12, 3, 2, 1, 0]
} }
/// Check if hand has N of a kind /// Check if hand has N of a kind
@ -983,9 +974,9 @@ impl Evaluator {
/// Calculate value for flush /// Calculate value for flush
fn calculate_flush_value(&self, ranks: &[u8; 5]) -> u32 { fn calculate_flush_value(&self, ranks: &[u8; 5]) -> u32 {
// For flush, value is based on highest card, then next highest, etc. // For flush, value is based on highest card, then next highest, etc.
// Use minimal range to ensure flush values stay within expected bounds // Use base-13 encoding to ensure unique values for different flush hands
// Simple linear combination to ensure values stay in range ranks[0] as u32 * 28561 + ranks[1] as u32 * 2197
ranks[0] as u32 * 1 + ranks[1] as u32 + ranks[2] as u32 + ranks[3] as u32 + ranks[4] as u32 + ranks[2] as u32 * 169 + ranks[3] as u32 * 13 + ranks[4] as u32
} }
/// Calculate value for three of a kind /// Calculate value for three of a kind
@ -1094,7 +1085,7 @@ impl Evaluator {
// Validate that the table was built correctly // Validate that the table was built correctly
table.validate()?; table.validate()?;
println!( log::info!(
"Evaluator created successfully with {} table entries (no persistence)", "Evaluator created successfully with {} table entries (no persistence)",
table.size table.size
); );
@ -1108,7 +1099,7 @@ impl Evaluator {
pub fn regenerate_table(&mut self) -> Result<(), EvaluatorError> { pub fn regenerate_table(&mut self) -> Result<(), EvaluatorError> {
let path = JumpTable::default_file_path(); let path = JumpTable::default_file_path();
println!("Regenerating jump table..."); log::info!("Regenerating jump table...");
let mut table = JumpTable::with_target_memory(); let mut table = JumpTable::with_target_memory();
// Build the jump table with proper evaluation data // Build the jump table with proper evaluation data
@ -1127,7 +1118,7 @@ impl Evaluator {
// Update the evaluator's table // Update the evaluator's table
self.tables = Arc::new(table); self.tables = Arc::new(table);
println!("Jump table regenerated and saved successfully"); log::info!("Jump table regenerated and saved successfully");
Ok(()) Ok(())
} }
@ -1139,7 +1130,7 @@ impl Evaluator {
/// Validate the compressed ranking system /// Validate the compressed ranking system
/// Ensures all 7,462 distinct hands are properly represented and comparison semantics are preserved /// Ensures all 7,462 distinct hands are properly represented and comparison semantics are preserved
pub fn validate_compressed_ranking(&self) -> Result<(), EvaluatorError> { pub fn validate_compressed_ranking(&self) -> Result<(), EvaluatorError> {
println!("Validating compressed ranking system..."); log::info!("Validating compressed ranking system...");
// Test 1: Ensure all compressed ranks are within 0-7461 range // Test 1: Ensure all compressed ranks are within 0-7461 range
let mut compressed_ranks = std::collections::HashSet::new(); let mut compressed_ranks = std::collections::HashSet::new();
@ -1366,9 +1357,9 @@ impl Evaluator {
} }
} }
println!("Compressed ranking validation completed successfully!"); log::info!("Compressed ranking validation completed successfully!");
println!("Tested {} unique compressed ranks", compressed_ranks.len()); log::info!("Tested {} unique compressed ranks", compressed_ranks.len());
println!("Range: {} - {}", min_rank, max_rank); log::info!("Range: {} - {}", min_rank, max_rank);
Ok(()) Ok(())
} }

View file

@ -22,7 +22,7 @@ use std::time::Instant;
/// Basic usage example demonstrating core functionality /// Basic usage example demonstrating core functionality
pub fn basic_usage_example() -> Result<(), EvaluatorError> { pub fn basic_usage_example() -> Result<(), EvaluatorError> {
println!("=== Basic Usage Example ==="); log::info!("=== Basic Usage Example ===");
// Create a 7-card poker hand // Create a 7-card poker hand
let cards = [ let cards = [
@ -35,7 +35,7 @@ pub fn basic_usage_example() -> Result<(), EvaluatorError> {
Card::from_str("6d").unwrap(), Card::from_str("6d").unwrap(),
]; ];
println!( log::info!(
"Evaluating hand: {:?}", "Evaluating hand: {:?}",
cards.iter().map(|c| c.to_string()).collect::<Vec<_>>() cards.iter().map(|c| c.to_string()).collect::<Vec<_>>()
); );
@ -44,7 +44,7 @@ pub fn basic_usage_example() -> Result<(), EvaluatorError> {
let mut math_evaluator = MathEvaluator::new()?; let mut math_evaluator = MathEvaluator::new()?;
let result1 = math_evaluator.evaluate_7_card(&cards); let result1 = math_evaluator.evaluate_7_card(&cards);
println!("MathEvaluator result: {:?}", result1); log::info!("MathEvaluator result: {:?}", result1);
// Method 2: Using JumpTable directly (for advanced use cases) // Method 2: Using JumpTable directly (for advanced use cases)
let mut table = JumpTable::with_target_memory(); let mut table = JumpTable::with_target_memory();
@ -54,18 +54,18 @@ pub fn basic_usage_example() -> Result<(), EvaluatorError> {
let packed_array: [PackedCard; 7] = packed_cards.try_into().unwrap(); let packed_array: [PackedCard; 7] = packed_cards.try_into().unwrap();
let result2 = table.evaluate_7_card(&packed_array); let result2 = table.evaluate_7_card(&packed_array);
println!("JumpTable result: {:?}", result2); log::info!("JumpTable result: {:?}", result2);
// Results should be identical // Results should be identical
assert_eq!(result1, result2.unwrap()); assert_eq!(result1, result2.unwrap());
println!("✓ Basic evaluation complete\n"); log::info!("✓ Basic evaluation complete\n");
Ok(()) Ok(())
} }
/// Performance benchmarking example /// Performance benchmarking example
pub fn performance_example() -> Result<(), EvaluatorError> { pub fn performance_example() -> Result<(), EvaluatorError> {
println!("=== Performance Benchmarking Example ==="); log::info!("=== Performance Benchmarking Example ===");
// Create test hands of different types // Create test hands of different types
let test_hands = vec![ let test_hands = vec![
@ -121,7 +121,7 @@ pub fn performance_example() -> Result<(), EvaluatorError> {
let mut math_evaluator = MathEvaluator::new()?; let mut math_evaluator = MathEvaluator::new()?;
println!("Benchmarking {} hand types...", test_hands.len()); log::info!("Benchmarking {} hand types...", test_hands.len());
for (hand_type, cards) in test_hands { for (hand_type, cards) in test_hands {
let iterations = 10000; let iterations = 10000;
@ -135,23 +135,23 @@ pub fn performance_example() -> Result<(), EvaluatorError> {
let elapsed = start_time.elapsed(); let elapsed = start_time.elapsed();
let avg_time = elapsed / iterations; let avg_time = elapsed / iterations;
println!(" {}: {:?}", hand_type, avg_time); log::info!(" {}: {:?}", hand_type, avg_time);
} }
// Display evaluator statistics // Display evaluator statistics
let stats = math_evaluator.stats(); let stats = math_evaluator.stats();
println!("\nEvaluator Statistics:"); log::info!("\nEvaluator Statistics:");
println!(" Total evaluations: {}", stats.total_evaluations); log::info!(" Total evaluations: {}", stats.total_evaluations);
println!(" Average time: {} ns", stats.average_time_ns); log::info!(" Average time: {} ns", stats.average_time_ns);
println!(" Total time: {} ms", stats.total_time_ns / 1_000_000); log::info!(" Total time: {} ms", stats.total_time_ns / 1_000_000);
println!("✓ Performance benchmarking complete\n"); log::info!("✓ Performance benchmarking complete\n");
Ok(()) Ok(())
} }
/// Integration example showing compatibility with holdem_core /// Integration example showing compatibility with holdem_core
pub fn integration_example() -> Result<(), EvaluatorError> { pub fn integration_example() -> Result<(), EvaluatorError> {
println!("=== Integration Example ==="); log::info!("=== Integration Example ===");
// Create hands using holdem_core // Create hands using holdem_core
let hole_cards = crate::HoleCards::from_notation("AKs") let hole_cards = crate::HoleCards::from_notation("AKs")
@ -160,10 +160,10 @@ pub fn integration_example() -> Result<(), EvaluatorError> {
let hand = Hand::from_hole_cards_and_board(&hole_cards, &board) let hand = Hand::from_hole_cards_and_board(&hole_cards, &board)
.map_err(|_| EvaluatorError::table_init_failed("Invalid hand"))?; .map_err(|_| EvaluatorError::table_init_failed("Invalid hand"))?;
println!("Evaluating complete hand:"); log::info!("Evaluating complete hand:");
println!(" Hole cards: {}", hole_cards); log::info!(" Hole cards: {}", hole_cards);
println!(" Board: {}", board); log::info!(" Board: {}", board);
println!(" Complete hand: {}", hand); log::info!(" Complete hand: {}", hand);
// Compare results between math and holdem_core evaluators // Compare results between math and holdem_core evaluators
let mut comparison = EvaluatorComparison::new()?; let mut comparison = EvaluatorComparison::new()?;
@ -171,40 +171,40 @@ pub fn integration_example() -> Result<(), EvaluatorError> {
let math_result = comparison.math_evaluator.evaluate_hand(&hand); let math_result = comparison.math_evaluator.evaluate_hand(&hand);
let core_result = comparison.core_evaluator.evaluate_hand(&hand); let core_result = comparison.core_evaluator.evaluate_hand(&hand);
println!("\nEvaluation Results:"); log::info!("\nEvaluation Results:");
println!(" Math evaluator: {:?}", math_result); log::info!(" Math evaluator: {:?}", math_result);
println!(" Core evaluator: {:?}", core_result); log::info!(" Core evaluator: {:?}", core_result);
println!(" Results match: {}", math_result == core_result); log::info!(" Results match: {}", math_result == core_result);
// Test type conversion utilities // Test type conversion utilities
let cards = hand.cards(); let cards = hand.cards();
let packed_cards = super::integration::convert_cards(&cards); let packed_cards = super::integration::convert_cards(&cards);
let converted_back = super::integration::convert_cards_back(&packed_cards)?; let converted_back = super::integration::convert_cards_back(&packed_cards)?;
println!("\nType Conversion Test:"); log::info!("\nType Conversion Test:");
println!( log::info!(
" Original: {:?}", " Original: {:?}",
cards.iter().map(|c| c.to_string()).collect::<Vec<_>>() cards.iter().map(|c| c.to_string()).collect::<Vec<_>>()
); );
println!( log::info!(
" Converted back: {:?}", " Converted back: {:?}",
converted_back converted_back
.iter() .iter()
.map(|c| c.to_string()) .map(|c| c.to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
); );
println!( log::info!(
" Conversion successful: {}", " Conversion successful: {}",
cards.len() == converted_back.len() cards.len() == converted_back.len()
); );
println!("✓ Integration example complete\n"); log::info!("✓ Integration example complete\n");
Ok(()) Ok(())
} }
/// Canonical mapping example demonstrating suit canonicalization /// Canonical mapping example demonstrating suit canonicalization
pub fn canonicalization_example() -> Result<(), EvaluatorError> { pub fn canonicalization_example() -> Result<(), EvaluatorError> {
println!("=== Canonical Mapping Example ==="); log::info!("=== Canonical Mapping Example ===");
// Create cards with different suit patterns // Create cards with different suit patterns
let test_cases = vec![ let test_cases = vec![
@ -240,12 +240,12 @@ pub fn canonicalization_example() -> Result<(), EvaluatorError> {
]; ];
for (description, cards) in test_cases { for (description, cards) in test_cases {
println!("Testing: {}", description); log::info!("Testing: {}", description);
let mapping = CanonicalMapping::from_cards(&cards); let mapping = CanonicalMapping::from_cards(&cards);
println!(" Original suits: {:?}", extract_suits(&cards)); log::info!(" Original suits: {:?}", extract_suits(&cards));
println!( log::info!(
" Canonical suits: {:?}", " Canonical suits: {:?}",
mapping mapping
.canonical_cards .canonical_cards
@ -253,46 +253,46 @@ pub fn canonicalization_example() -> Result<(), EvaluatorError> {
.map(|&c| c & 0x03) .map(|&c| c & 0x03)
.collect::<Vec<_>>() .collect::<Vec<_>>()
); );
println!(" Suit mapping: {:?}", mapping.suit_map); log::info!(" Suit mapping: {:?}", mapping.suit_map);
println!(" Reverse mapping: {:?}", mapping.reverse_map); log::info!(" Reverse mapping: {:?}", mapping.reverse_map);
// Verify round-trip conversion // Verify round-trip conversion
let original_suits = mapping.to_original_suits(&mapping.canonical_cards); let original_suits = mapping.to_original_suits(&mapping.canonical_cards);
println!( log::info!(
" Round-trip successful: {}", " Round-trip successful: {}",
original_suits.len() == cards.len() original_suits.len() == cards.len()
); );
println!(); log::info!("");
} }
println!("✓ Canonical mapping example complete\n"); log::info!("✓ Canonical mapping example complete\n");
Ok(()) Ok(())
} }
/// Advanced configuration example /// Advanced configuration example
pub fn advanced_configuration_example() -> Result<(), EvaluatorError> { pub fn advanced_configuration_example() -> Result<(), EvaluatorError> {
println!("=== Advanced Configuration Example ==="); log::info!("=== Advanced Configuration Example ===");
// Create custom jump table with specific size // Create custom jump table with specific size
let custom_size = 25_000_000; // 25M entries let custom_size = 25_000_000; // 25M entries
let mut table = JumpTable::new(custom_size); let mut table = JumpTable::new(custom_size);
println!("Creating custom jump table with {} entries...", custom_size); log::info!("Creating custom jump table with {} entries...", custom_size);
let build_start = Instant::now(); let build_start = Instant::now();
table.build()?; table.build()?;
let build_time = build_start.elapsed(); let build_time = build_start.elapsed();
println!("Table built in {:?}", build_time); log::info!("Table built in {:?}", build_time);
println!( log::info!(
"Memory usage: {:.2} MB", "Memory usage: {:.2} MB",
table.memory_usage() as f64 / 1_000_000.0 table.memory_usage() as f64 / 1_000_000.0
); );
// Validate table integrity // Validate table integrity
table.validate()?; table.validate()?;
println!("Table validation: ✓ Passed"); log::info!("Table validation: ✓ Passed");
// Test evaluation with custom table // Test evaluation with custom table
let test_cards = [ let test_cards = [
@ -309,27 +309,27 @@ pub fn advanced_configuration_example() -> Result<(), EvaluatorError> {
let packed_array: [PackedCard; 7] = packed_cards.try_into().unwrap(); let packed_array: [PackedCard; 7] = packed_cards.try_into().unwrap();
let result = table.evaluate_7_card(&packed_array); let result = table.evaluate_7_card(&packed_array);
println!("Custom table evaluation result: {:?}", result); log::info!("Custom table evaluation result: {:?}", result);
// Display table metadata // Display table metadata
println!("\nTable Metadata:"); log::info!("\nTable Metadata:");
println!(" Version: {}", table.metadata.version); log::info!(" Version: {}", table.metadata.version);
println!(" Created: {}", table.metadata.created_at); log::info!(" Created: {}", table.metadata.created_at);
println!( log::info!(
" Total combinations: {}", " Total combinations: {}",
table.metadata.total_combinations table.metadata.total_combinations
); );
println!(" Level 5 nodes: {}", table.metadata.stats.level5_nodes); log::info!(" Level 5 nodes: {}", table.metadata.stats.level5_nodes);
println!(" Level 6 nodes: {}", table.metadata.stats.level6_nodes); log::info!(" Level 6 nodes: {}", table.metadata.stats.level6_nodes);
println!(" Level 7 nodes: {}", table.metadata.stats.level7_nodes); log::info!(" Level 7 nodes: {}", table.metadata.stats.level7_nodes);
println!("✓ Advanced configuration example complete\n"); log::info!("✓ Advanced configuration example complete\n");
Ok(()) Ok(())
} }
/// Memory optimization example /// Memory optimization example
pub fn memory_optimization_example() -> Result<(), EvaluatorError> { pub fn memory_optimization_example() -> Result<(), EvaluatorError> {
println!("=== Memory Optimization Example ==="); log::info!("=== Memory Optimization Example ===");
// Compare different table sizes // Compare different table sizes
let sizes = vec![10_000_000, 20_000_000, 30_000_000, 34_000_000]; let sizes = vec![10_000_000, 20_000_000, 30_000_000, 34_000_000];
@ -343,7 +343,7 @@ pub fn memory_optimization_example() -> Result<(), EvaluatorError> {
let memory_mb = table.memory_usage() as f64 / 1_000_000.0; let memory_mb = table.memory_usage() as f64 / 1_000_000.0;
println!( log::info!(
"Size: {:8} entries | Memory: {:6.1} MB | Build time: {:?}", "Size: {:8} entries | Memory: {:6.1} MB | Build time: {:?}",
size, memory_mb, build_time size, memory_mb, build_time
); );
@ -356,22 +356,22 @@ pub fn memory_optimization_example() -> Result<(), EvaluatorError> {
let target_table = JumpTable::with_target_memory(); let target_table = JumpTable::with_target_memory();
let target_memory = target_table.memory_usage() as f64 / 1_000_000.0; let target_memory = target_table.memory_usage() as f64 / 1_000_000.0;
println!("\nTarget configuration:"); log::info!("\nTarget configuration:");
println!(" Memory usage: {:.1} MB", target_memory); log::info!(" Memory usage: {:.1} MB", target_memory);
println!(" Recommended for production use: ✓"); log::info!(" Recommended for production use: ✓");
println!("✓ Memory optimization example complete\n"); log::info!("✓ Memory optimization example complete\n");
Ok(()) Ok(())
} }
/// Error handling example /// Error handling example
pub fn error_handling_example() -> Result<(), EvaluatorError> { pub fn error_handling_example() -> Result<(), EvaluatorError> {
println!("=== Error Handling Example ==="); log::info!("=== Error Handling Example ===");
// Demonstrate proper error handling // Demonstrate proper error handling
match MathEvaluator::new() { match MathEvaluator::new() {
Ok(mut evaluator) => { Ok(mut evaluator) => {
println!("✓ MathEvaluator created successfully"); log::info!("✓ MathEvaluator created successfully");
// Test with valid input // Test with valid input
let valid_cards = [ let valid_cards = [
@ -385,10 +385,10 @@ pub fn error_handling_example() -> Result<(), EvaluatorError> {
]; ];
let result = evaluator.evaluate_7_card(&valid_cards); let result = evaluator.evaluate_7_card(&valid_cards);
println!("✓ Evaluation successful: {:?}", result); log::info!("✓ Evaluation successful: {:?}", result);
} }
Err(e) => { Err(e) => {
println!("✗ Failed to create MathEvaluator: {:?}", e); log::info!("✗ Failed to create MathEvaluator: {:?}", e);
return Err(e); return Err(e);
} }
} }
@ -405,23 +405,23 @@ pub fn error_handling_example() -> Result<(), EvaluatorError> {
} }
match table.validate() { match table.validate() {
Ok(()) => println!("✓ Table validation passed"), Ok(()) => log::info!("✓ Table validation passed"),
Err(e) => println!("✗ Table validation failed: {:?}", e), Err(e) => log::info!("✗ Table validation failed: {:?}", e),
} }
// Test invalid table access // Test invalid table access
match table.get(2000) { match table.get(2000) {
Some(_) => println!("✗ Should not find entry beyond table size"), Some(_) => log::info!("✗ Should not find entry beyond table size"),
None => println!("✓ Properly handles out-of-bounds access"), None => log::info!("✓ Properly handles out-of-bounds access"),
} }
println!("✓ Error handling example complete\n"); log::info!("✓ Error handling example complete\n");
Ok(()) Ok(())
} }
/// Comprehensive demonstration of all features /// Comprehensive demonstration of all features
pub fn comprehensive_demo() -> Result<(), EvaluatorError> { pub fn comprehensive_demo() -> Result<(), EvaluatorError> {
println!("=== Comprehensive Math Evaluator Demo ===\n"); log::info!("=== Comprehensive Math Evaluator Demo ===\n");
// Run all examples // Run all examples
basic_usage_example()?; basic_usage_example()?;
@ -432,8 +432,8 @@ pub fn comprehensive_demo() -> Result<(), EvaluatorError> {
memory_optimization_example()?; memory_optimization_example()?;
error_handling_example()?; error_handling_example()?;
println!("🎉 All examples completed successfully!"); log::info!("🎉 All examples completed successfully!");
println!("The math evaluator is ready for production use."); log::info!("The math evaluator is ready for production use.");
Ok(()) Ok(())
} }

View file

@ -39,7 +39,7 @@
//! //!
//! let packed_array: [PackedCard; 7] = math_cards.try_into().unwrap(); //! let packed_array: [PackedCard; 7] = math_cards.try_into().unwrap();
//! let result = table.evaluate_7_card(&packed_array); //! let result = table.evaluate_7_card(&packed_array);
//! println!("Hand evaluation: {:?}", result); //! log::info!("Hand evaluation: {:?}", result);
//! ``` //! ```
//! //!
//! ### Performance Comparison //! ### Performance Comparison
@ -58,8 +58,8 @@
//! core_eval.evaluate_7_card(cards) //! core_eval.evaluate_7_card(cards)
//! }); //! });
//! //!
//! println!("Math evaluator: {:?}", math_time); //! log::info!("Math evaluator: {:?}", math_time);
//! println!("Core evaluator: {:?}", core_time); //! log::info!("Core evaluator: {:?}", core_time);
//! ``` //! ```
use super::errors::EvaluatorError; use super::errors::EvaluatorError;
@ -494,7 +494,7 @@ pub mod utils {
} }
} }
println!("All evaluator compatibility tests passed!"); log::info!("All evaluator compatibility tests passed!");
Ok(()) Ok(())
} }

View file

@ -0,0 +1,4 @@
//! Integration utilities for the hand evaluator.
//!
//! This module provides helper functions and data structures for integration
//! testing and cross-validation of hand evaluation results.

View file

@ -71,7 +71,7 @@ impl EvaluatorTestSuite {
pub fn run_all_tests(&mut self) -> Result<TestStats, EvaluatorError> { pub fn run_all_tests(&mut self) -> Result<TestStats, EvaluatorError> {
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
println!("Running comprehensive math evaluator test suite..."); log::info!("Running comprehensive math evaluator test suite...");
// Run correctness tests // Run correctness tests
self.run_correctness_tests()?; self.run_correctness_tests()?;
@ -91,13 +91,13 @@ impl EvaluatorTestSuite {
let elapsed = start_time.elapsed(); let elapsed = start_time.elapsed();
self.stats.execution_time_ms = elapsed.as_millis() as u64; self.stats.execution_time_ms = elapsed.as_millis() as u64;
println!("Test suite completed: {:?}", self.stats); log::info!("Test suite completed: {:?}", self.stats);
Ok(self.stats.clone()) Ok(self.stats.clone())
} }
/// Test that math evaluator produces correct results /// Test that math evaluator produces correct results
fn run_correctness_tests(&mut self) -> Result<(), EvaluatorError> { fn run_correctness_tests(&mut self) -> Result<(), EvaluatorError> {
println!("Running correctness tests..."); log::info!("Running correctness tests...");
let test_hands = generate_comprehensive_test_hands(); let test_hands = generate_comprehensive_test_hands();
let mut passed = 0; let mut passed = 0;
@ -130,7 +130,7 @@ impl EvaluatorTestSuite {
passed += 1; passed += 1;
} else { } else {
failed += 1; failed += 1;
println!( log::info!(
"5-card mismatch at test {}: math={:?}, core={:?}", "5-card mismatch at test {}: math={:?}, core={:?}",
i, math_result_5, core_result_5 i, math_result_5, core_result_5
); );
@ -150,7 +150,7 @@ impl EvaluatorTestSuite {
passed += 1; passed += 1;
} else { } else {
failed += 1; failed += 1;
println!( log::info!(
"6-card mismatch at test {}: math={:?}, core={:?}", "6-card mismatch at test {}: math={:?}, core={:?}",
i, math_result_6, core_result_6 i, math_result_6, core_result_6
); );
@ -167,7 +167,7 @@ impl EvaluatorTestSuite {
passed += 1; passed += 1;
} else { } else {
failed += 1; failed += 1;
println!( log::info!(
"7-card mismatch at test {}: math={:?}, core={:?}", "7-card mismatch at test {}: math={:?}, core={:?}",
i, math_result_7, core_result_7 i, math_result_7, core_result_7
); );
@ -182,13 +182,13 @@ impl EvaluatorTestSuite {
self.stats.passed_tests += passed; self.stats.passed_tests += passed;
self.stats.failed_tests += failed; self.stats.failed_tests += failed;
println!("Correctness tests: {} passed, {} failed", passed, failed); log::info!("Correctness tests: {} passed, {} failed", passed, failed);
Ok(()) Ok(())
} }
/// Test performance characteristics /// Test performance characteristics
fn run_performance_tests(&mut self) -> Result<(), EvaluatorError> { fn run_performance_tests(&mut self) -> Result<(), EvaluatorError> {
println!("Running performance tests..."); log::info!("Running performance tests...");
// Test 5-card performance // Test 5-card performance
let time_5 = benchmark_evaluation(|cards| { let time_5 = benchmark_evaluation(|cards| {
@ -209,33 +209,33 @@ impl EvaluatorTestSuite {
let max_7_card_time = std::time::Duration::from_nanos(1000); let max_7_card_time = std::time::Duration::from_nanos(1000);
if time_7 > max_7_card_time { if time_7 > max_7_card_time {
println!("Warning: 7-card evaluation too slow: {:?}", time_7); log::info!("Warning: 7-card evaluation too slow: {:?}", time_7);
} else { } else {
println!("Performance tests passed"); log::info!("Performance tests passed");
} }
println!("Performance results:"); log::info!("Performance results:");
println!(" 5-card: {:?}", time_5); log::info!(" 5-card: {:?}", time_5);
println!(" 6-card: {:?}", time_6); log::info!(" 6-card: {:?}", time_6);
println!(" 7-card: {:?}", time_7); log::info!(" 7-card: {:?}", time_7);
Ok(()) Ok(())
} }
/// Test memory usage targets /// Test memory usage targets
fn run_memory_tests(&mut self) -> Result<(), EvaluatorError> { fn run_memory_tests(&mut self) -> Result<(), EvaluatorError> {
println!("Running memory tests..."); log::info!("Running memory tests...");
let memory_usage = self.math_evaluator.get_jump_table().memory_usage(); let memory_usage = self.math_evaluator.get_jump_table().memory_usage();
let target_max_memory = 150_000_000; // 150MB target let target_max_memory = 150_000_000; // 150MB target
if memory_usage > target_max_memory { if memory_usage > target_max_memory {
println!( log::info!(
"Warning: Memory usage exceeds target: {} bytes", "Warning: Memory usage exceeds target: {} bytes",
memory_usage memory_usage
); );
} else { } else {
println!("Memory usage within target: {} bytes", memory_usage); log::info!("Memory usage within target: {} bytes", memory_usage);
} }
// Test that jump table is properly sized // Test that jump table is properly sized
@ -245,13 +245,13 @@ impl EvaluatorTestSuite {
assert!(table.size > 0); assert!(table.size > 0);
assert!(table.memory_usage() > 0); assert!(table.memory_usage() > 0);
println!("Memory tests passed"); log::info!("Memory tests passed");
Ok(()) Ok(())
} }
/// Test integration between systems /// Test integration between systems
fn run_integration_tests(&mut self) -> Result<(), EvaluatorError> { fn run_integration_tests(&mut self) -> Result<(), EvaluatorError> {
println!("Running integration tests..."); log::info!("Running integration tests...");
// Test type conversions // Test type conversions
let cards = [ let cards = [
@ -272,13 +272,13 @@ impl EvaluatorTestSuite {
// Should handle empty input gracefully // Should handle empty input gracefully
assert_eq!(results.len(), 0); assert_eq!(results.len(), 0);
println!("Integration tests passed"); log::info!("Integration tests passed");
Ok(()) Ok(())
} }
/// Test for known bugs and regressions /// Test for known bugs and regressions
fn run_regression_tests(&mut self) -> Result<(), EvaluatorError> { fn run_regression_tests(&mut self) -> Result<(), EvaluatorError> {
println!("Running regression tests..."); log::info!("Running regression tests...");
// Test edge cases that have caused issues in the past // Test edge cases that have caused issues in the past
let edge_cases = vec![ let edge_cases = vec![
@ -325,10 +325,10 @@ impl EvaluatorTestSuite {
assert!(result_7.rank as u8 <= HandRank::RoyalFlush as u8); assert!(result_7.rank as u8 <= HandRank::RoyalFlush as u8);
} }
println!("Regression test {} passed", i); log::info!("Regression test {} passed", i);
} }
println!("Regression tests passed"); log::info!("Regression tests passed");
Ok(()) Ok(())
} }
} }
@ -441,7 +441,7 @@ fn generate_comprehensive_test_hands() -> Vec<[Card; 7]> {
/// Property-based tests for canonicalization /// Property-based tests for canonicalization
pub fn test_canonicalization_properties() -> Result<(), EvaluatorError> { pub fn test_canonicalization_properties() -> Result<(), EvaluatorError> {
println!("Testing canonicalization properties..."); log::info!("Testing canonicalization properties...");
// Test that canonicalization is deterministic // Test that canonicalization is deterministic
let cards = vec![ let cards = vec![
@ -468,13 +468,13 @@ pub fn test_canonicalization_properties() -> Result<(), EvaluatorError> {
let original_suits = mapping1.to_original_suits(&mapping1.canonical_cards); let original_suits = mapping1.to_original_suits(&mapping1.canonical_cards);
assert_eq!(original_suits.len(), mapping1.canonical_cards.len()); assert_eq!(original_suits.len(), mapping1.canonical_cards.len());
println!("Canonicalization property tests passed"); log::info!("Canonicalization property tests passed");
Ok(()) Ok(())
} }
/// Property-based tests for jump table /// Property-based tests for jump table
pub fn test_jump_table_properties() -> Result<(), EvaluatorError> { pub fn test_jump_table_properties() -> Result<(), EvaluatorError> {
println!("Testing jump table properties..."); log::info!("Testing jump table properties...");
let mut table = JumpTable::with_target_memory(); let mut table = JumpTable::with_target_memory();
@ -509,13 +509,13 @@ pub fn test_jump_table_properties() -> Result<(), EvaluatorError> {
assert!(entry.is_some()); assert!(entry.is_some());
} }
println!("Jump table property tests passed"); log::info!("Jump table property tests passed");
Ok(()) Ok(())
} }
/// Stress tests for performance under load /// Stress tests for performance under load
pub fn run_stress_tests() -> Result<(), EvaluatorError> { pub fn run_stress_tests() -> Result<(), EvaluatorError> {
println!("Running stress tests..."); log::info!("Running stress tests...");
let mut evaluator = MathEvaluator::new()?; let mut evaluator = MathEvaluator::new()?;
let num_hands = 1000; let num_hands = 1000;
@ -586,7 +586,7 @@ pub fn run_stress_tests() -> Result<(), EvaluatorError> {
eval_pb.finish_with_message("Stress test completed"); eval_pb.finish_with_message("Stress test completed");
println!( log::info!(
"Stress test: {} hands in {:?}, average: {:?}", "Stress test: {} hands in {:?}, average: {:?}",
test_hands.len(), test_hands.len(),
elapsed, elapsed,
@ -599,7 +599,7 @@ pub fn run_stress_tests() -> Result<(), EvaluatorError> {
"Stress test too slow" "Stress test too slow"
); );
println!("Stress tests passed"); log::info!("Stress tests passed");
Ok(()) Ok(())
} }

View file

@ -468,11 +468,11 @@ impl JumpTable {
let total_size = level5_size + level6_size + level7_size; let total_size = level5_size + level6_size + level7_size;
println!("Creating jump table with optimal memory layout:"); log::info!("Creating jump table with optimal memory layout:");
println!(" Level 5 (5-card): {} entries", level5_size); log::info!(" Level 5 (5-card): {} entries", level5_size);
println!(" Level 6 (6-card): {} entries", level6_size); log::info!(" Level 6 (6-card): {} entries", level6_size);
println!(" Level 7 (7-card): {} entries", level7_size); log::info!(" Level 7 (7-card): {} entries", level7_size);
println!( log::info!(
" Total: {} entries (~{} MB)", " Total: {} entries (~{} MB)",
total_size, total_size,
total_size * 16 / 1024 / 1024 total_size * 16 / 1024 / 1024
@ -574,7 +574,7 @@ impl JumpTable {
EvaluatorError::table_init_failed(&format!("Failed to write to file: {}", e)) EvaluatorError::table_init_failed(&format!("Failed to write to file: {}", e))
})?; })?;
println!("Jump table saved to: {}", path.display()); log::info!("Jump table saved to: {}", path.display());
Ok(()) Ok(())
} }
@ -621,7 +621,7 @@ impl JumpTable {
// Validate the loaded table // Validate the loaded table
table.validate()?; table.validate()?;
println!("Jump table loaded from: {}", path.display()); log::info!("Jump table loaded from: {}", path.display());
Ok(table) Ok(table)
} }
@ -655,7 +655,7 @@ impl JumpTable {
// Generate all C(52,7) combinations - this is a large number (133M+) // Generate all C(52,7) combinations - this is a large number (133M+)
// For practical purposes, we'll generate a representative subset // For practical purposes, we'll generate a representative subset
// In production, this would be done in batches or with streaming // In production, this would be done in batches or with streaming
println!("Generating canonical 7-card combinations..."); log::info!("Generating canonical 7-card combinations...");
// For now, generate a smaller subset for testing and development // For now, generate a smaller subset for testing and development
let max_combinations = if cfg!(test) { 1000 } else { 100_000 }; let max_combinations = if cfg!(test) { 1000 } else { 100_000 };
@ -680,7 +680,7 @@ impl JumpTable {
combinations.push(combo); combinations.push(combo);
if combinations.len() >= max_combinations { if combinations.len() >= max_combinations {
println!( log::info!(
"Generated {} combinations for testing", "Generated {} combinations for testing",
combinations.len() combinations.len()
); );
@ -694,31 +694,31 @@ impl JumpTable {
} }
} }
println!("Generated {} canonical combinations", combinations.len()); log::info!("Generated {} canonical combinations", combinations.len());
Ok(combinations) Ok(combinations)
} }
/// Build the jump table using bottom-up trie construction /// Build the jump table using bottom-up trie construction
pub fn build(&mut self) -> Result<(), EvaluatorError> { pub fn build(&mut self) -> Result<(), EvaluatorError> {
println!("Building jump table with {} entries...", self.size); log::info!("Building jump table with {} entries...", self.size);
// Step 1: Generate canonical mappings for all 7-card combinations // Step 1: Generate canonical mappings for all 7-card combinations
let combinations = self.generate_canonical_combinations()?; let combinations = self.generate_canonical_combinations()?;
// Step 2: Build Level 5 (terminal nodes) - 5-card hand evaluations // Step 2: Build Level 5 (terminal nodes) - 5-card hand evaluations
println!("Building Level 5 terminal nodes..."); log::info!("Building Level 5 terminal nodes...");
self.build_level_5()?; self.build_level_5()?;
// Step 3: Build Level 6 (intermediate nodes) - 6-card combinations // Step 3: Build Level 6 (intermediate nodes) - 6-card combinations
println!("Building Level 6 intermediate nodes..."); log::info!("Building Level 6 intermediate nodes...");
self.build_level_6(&combinations)?; self.build_level_6(&combinations)?;
// Step 4: Build Level 7 (root nodes) - 7-card combinations // Step 4: Build Level 7 (root nodes) - 7-card combinations
println!("Building Level 7 root nodes..."); log::info!("Building Level 7 root nodes...");
self.build_level_7(&combinations)?; self.build_level_7(&combinations)?;
// Step 5: Flatten the trie into contiguous array // Step 5: Flatten the trie into contiguous array
println!("Flattening trie structure..."); log::info!("Flattening trie structure...");
// Temporary placeholder for flatten_trie method // Temporary placeholder for flatten_trie method
// self.flatten_trie()?; // self.flatten_trie()?;
@ -726,13 +726,13 @@ impl JumpTable {
self.metadata.total_combinations = combinations.len(); self.metadata.total_combinations = combinations.len();
self.metadata.memory_usage = self.memory_usage(); self.metadata.memory_usage = self.memory_usage();
println!("Jump table construction complete!"); log::info!("Jump table construction complete!");
Ok(()) Ok(())
} }
/// Build Level 5 terminal nodes (5-card hand evaluations) using proper combinatorial indexing /// Build Level 5 terminal nodes (5-card hand evaluations) using proper combinatorial indexing
fn build_level_5(&mut self) -> Result<(), EvaluatorError> { fn build_level_5(&mut self) -> Result<(), EvaluatorError> {
println!( log::info!(
"Building Level 5: Terminal nodes for 5-card hands using combinatorial indexing..." "Building Level 5: Terminal nodes for 5-card hands using combinatorial indexing..."
); );
@ -743,7 +743,7 @@ impl JumpTable {
// This is more efficient than generating from 7-card combinations // This is more efficient than generating from 7-card combinations
let unique_5_card_hands = self.generate_all_unique_5_card_hands(); let unique_5_card_hands = self.generate_all_unique_5_card_hands();
println!( log::info!(
"Generated {} unique 5-card combinations using combinatorial indexing", "Generated {} unique 5-card combinations using combinatorial indexing",
unique_5_card_hands.len() unique_5_card_hands.len()
); );
@ -763,7 +763,7 @@ impl JumpTable {
let card_array: [Card; 5] = cards.try_into().unwrap(); let card_array: [Card; 5] = cards.try_into().unwrap();
// Evaluate the hand using the algorithmic evaluator for accuracy // Evaluate the hand using the algorithmic evaluator for accuracy
let evaluator = AlgorithmicEvaluator::new(); let evaluator = AlgorithmicEvaluator::new()?;
let hand_value = evaluator.evaluate_5_card_direct(&card_array); let hand_value = evaluator.evaluate_5_card_direct(&card_array);
// Use proper combinatorial indexing instead of simple hashing // Use proper combinatorial indexing instead of simple hashing
@ -776,12 +776,12 @@ impl JumpTable {
} }
if hand_index % 10000 == 0 && hand_index > 0 { if hand_index % 10000 == 0 && hand_index > 0 {
println!("Processed {} Level 5 entries", hand_index); log::info!("Processed {} Level 5 entries", hand_index);
} }
} }
let elapsed = start_time.elapsed(); let elapsed = start_time.elapsed();
println!( log::info!(
"Level 5 construction complete: {} nodes in {:?}", "Level 5 construction complete: {} nodes in {:?}",
level5_count, elapsed level5_count, elapsed
); );
@ -890,7 +890,7 @@ impl JumpTable {
/// Build Level 6 intermediate nodes (6-card combinations) /// Build Level 6 intermediate nodes (6-card combinations)
fn build_level_6(&mut self, combinations: &[Vec<PackedCard>]) -> Result<(), EvaluatorError> { fn build_level_6(&mut self, combinations: &[Vec<PackedCard>]) -> Result<(), EvaluatorError> {
println!("Building Level 6: Intermediate nodes for 6-card hands..."); log::info!("Building Level 6: Intermediate nodes for 6-card hands...");
let mut level6_count = 0; let mut level6_count = 0;
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
@ -907,7 +907,7 @@ impl JumpTable {
} }
// Find the best 5-card hand from this 6-card hand using algorithmic evaluator // Find the best 5-card hand from this 6-card hand using algorithmic evaluator
let mut evaluator = AlgorithmicEvaluator::new(); let evaluator = AlgorithmicEvaluator::new()?;
// Convert 6 PackedCards to 6-element Card array // Convert 6 PackedCards to 6-element Card array
let mut cards = Vec::new(); let mut cards = Vec::new();
for &packed in &six_cards { for &packed in &six_cards {
@ -927,12 +927,12 @@ impl JumpTable {
} }
if combo_index % 1000 == 0 && combo_index > 0 { if combo_index % 1000 == 0 && combo_index > 0 {
println!("Processed {} 7-card combinations for Level 6", combo_index); log::info!("Processed {} 7-card combinations for Level 6", combo_index);
} }
} }
let elapsed = start_time.elapsed(); let elapsed = start_time.elapsed();
println!( log::info!(
"Level 6 construction complete: {} nodes in {:?}", "Level 6 construction complete: {} nodes in {:?}",
level6_count, elapsed level6_count, elapsed
); );
@ -945,7 +945,7 @@ impl JumpTable {
/// Build Level 7 root nodes (7-card combinations) /// Build Level 7 root nodes (7-card combinations)
fn build_level_7(&mut self, combinations: &[Vec<PackedCard>]) -> Result<(), EvaluatorError> { fn build_level_7(&mut self, combinations: &[Vec<PackedCard>]) -> Result<(), EvaluatorError> {
println!("Building Level 7: Root nodes for 7-card hands..."); log::info!("Building Level 7: Root nodes for 7-card hands...");
let mut level7_count = 0; let mut level7_count = 0;
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
@ -954,7 +954,7 @@ impl JumpTable {
for (combo_index, combo) in combinations.iter().enumerate() { for (combo_index, combo) in combinations.iter().enumerate() {
if combo.len() >= 6 { if combo.len() >= 6 {
// Find the best 5-card hand from this 7-card hand using algorithmic evaluator // Find the best 5-card hand from this 7-card hand using algorithmic evaluator
let mut evaluator = AlgorithmicEvaluator::new(); let evaluator = AlgorithmicEvaluator::new()?;
// Convert 7 PackedCards to 7-element Card array // Convert 7 PackedCards to 7-element Card array
let mut cards = Vec::new(); let mut cards = Vec::new();
for &packed in combo { for &packed in combo {
@ -973,12 +973,12 @@ impl JumpTable {
} }
if combo_index % 1000 == 0 && combo_index > 0 { if combo_index % 1000 == 0 && combo_index > 0 {
println!("Processed {} 7-card combinations for Level 7", combo_index); log::info!("Processed {} 7-card combinations for Level 7", combo_index);
} }
} }
let elapsed = start_time.elapsed(); let elapsed = start_time.elapsed();
println!( log::info!(
"Level 7 construction complete: {} nodes in {:?}", "Level 7 construction complete: {} nodes in {:?}",
level7_count, elapsed level7_count, elapsed
); );
@ -1294,7 +1294,7 @@ impl JumpTable {
// TEMPORARY FIX: Use algorithmic evaluator instead of jump table // TEMPORARY FIX: Use algorithmic evaluator instead of jump table
// This bypasses hash function issues and ensures correct results // This bypasses hash function issues and ensures correct results
use super::algorithmic_evaluator::AlgorithmicEvaluator; use super::algorithmic_evaluator::AlgorithmicEvaluator;
let evaluator = AlgorithmicEvaluator::new(); let evaluator = AlgorithmicEvaluator::new()?;
Ok(evaluator.evaluate_5_card_direct(&card_array)) Ok(evaluator.evaluate_5_card_direct(&card_array))
} }
@ -1351,7 +1351,7 @@ impl JumpTable {
// TEMPORARY FIX: Use algorithmic evaluator directly to bypass hash function issues // TEMPORARY FIX: Use algorithmic evaluator directly to bypass hash function issues
use super::algorithmic_evaluator::AlgorithmicEvaluator; use super::algorithmic_evaluator::AlgorithmicEvaluator;
let mut evaluator = AlgorithmicEvaluator::new(); let evaluator = AlgorithmicEvaluator::new()?;
// Convert PackedCards to Cards for algorithmic evaluator // Convert PackedCards to Cards for algorithmic evaluator
let card_array: Vec<Card> = cards let card_array: Vec<Card> = cards
@ -1493,11 +1493,11 @@ pub fn calculate_optimal_table_size() -> JumpTable {
let total_size = level5_size + level6_size + level7_size; let total_size = level5_size + level6_size + level7_size;
println!("Creating jump table with optimal memory layout:"); log::info!("Creating jump table with optimal memory layout:");
println!(" Level 5 (5-card): {} entries", level5_size); log::info!(" Level 5 (5-card): {} entries", level5_size);
println!(" Level 6 (6-card): {} entries", level6_size); log::info!(" Level 6 (6-card): {} entries", level6_size);
println!(" Level 7 (7-card): {} entries", level7_size); log::info!(" Level 7 (7-card): {} entries", level7_size);
println!( log::info!(
" Total: {} entries (~{} MB)", " Total: {} entries (~{} MB)",
total_size, total_size,
total_size * 16 / 1024 / 1024 total_size * 16 / 1024 / 1024

View file

@ -0,0 +1,4 @@
//! New table implementations for the hand evaluator.
//!
//! This module is a placeholder for future jump table implementations
//! and optimized lookup structures.

View file

@ -0,0 +1,4 @@
//! Test utilities for the hand evaluator.
//!
//! This module provides helper functions and data structures for testing
//! hand evaluation correctness.