feat: add incremental trie lookup API and Deck::without_cards
- Add TrieCursor struct for partial Supersonic LUT trie traversal - Add init_lookup/advance_lookup/finish_* methods for 5/6/7-card incremental eval - Add raw_init/raw_advance/raw_finish_* zero-cost API (no depth tracking) - Add Deck::without_cards() for constructing remaining decks - Re-export TrieCursor from evaluator module - Add comprehensive tests for all new APIs - Update .gitignore for docs/REFERENCES.md
This commit is contained in:
parent
912445d992
commit
156904f2bf
4 changed files with 455 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -11,3 +11,6 @@ Thumbs.db
|
||||||
|
|
||||||
# Proptest regressions (auto-generated)
|
# Proptest regressions (auto-generated)
|
||||||
proptest-regressions/
|
proptest-regressions/
|
||||||
|
|
||||||
|
# Local reference file (not tracked)
|
||||||
|
docs/REFERENCES.md
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,42 @@ impl Deck {
|
||||||
pub fn cards(&self) -> &[Card] {
|
pub fn cards(&self) -> &[Card] {
|
||||||
&self.cards
|
&self.cards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new deck excluding the specified cards.
|
||||||
|
///
|
||||||
|
/// Useful for constructing a "remaining deck" that omits known cards
|
||||||
|
/// (hero's hole cards, board cards) — typically resulting in a 45–47 card
|
||||||
|
/// deck for enumeration-based equity calculations.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use holdem_core::{Deck, Card};
|
||||||
|
/// use std::str::FromStr;
|
||||||
|
///
|
||||||
|
/// let deck = Deck::new();
|
||||||
|
/// let excluded = vec![
|
||||||
|
/// Card::from_str("As").unwrap(),
|
||||||
|
/// Card::from_str("Kh").unwrap(),
|
||||||
|
/// ];
|
||||||
|
/// let remaining = deck.without_cards(&excluded);
|
||||||
|
/// assert_eq!(remaining.remaining(), 50);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This method does not panic.
|
||||||
|
pub fn without_cards(&self, excluded: &[Card]) -> Deck {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
let exclude_set: HashSet<Card> = excluded.iter().copied().collect();
|
||||||
|
let cards: Vec<Card> = self
|
||||||
|
.cards
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|c| !exclude_set.contains(c))
|
||||||
|
.collect();
|
||||||
|
Deck { cards }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Deck {
|
impl Default for Deck {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,9 @@ pub use evaluator::{Evaluator, HandRank, HandValue};
|
||||||
// Re-export math-specific types
|
// Re-export math-specific types
|
||||||
pub use tables::JumpTable;
|
pub use tables::JumpTable;
|
||||||
|
|
||||||
|
// Re-export Supersonic incremental lookup types
|
||||||
|
pub use supersonic::TrieCursor;
|
||||||
|
|
||||||
// Module-level constants
|
// Module-level constants
|
||||||
pub const MAX_CARDS_IN_HAND: usize = 7;
|
pub const MAX_CARDS_IN_HAND: usize = 7;
|
||||||
pub const TOTAL_CARDS_IN_DECK: usize = 52;
|
pub const TOTAL_CARDS_IN_DECK: usize = 52;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,49 @@ const PAGE_SIZE: usize = 53;
|
||||||
/// Initial trie root index (page 1 base entry).
|
/// Initial trie root index (page 1 base entry).
|
||||||
const INITIAL_STATE: usize = PAGE_SIZE;
|
const INITIAL_STATE: usize = PAGE_SIZE;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// TrieCursor — opaque partial trie traversal state
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Opaque trie cursor — represents a partial traversal of the Supersonic LUT trie.
|
||||||
|
///
|
||||||
|
/// Created by [`SupersonicEvaluator::init_lookup()`] and advanced by
|
||||||
|
/// [`SupersonicEvaluator::advance_lookup()`]. Cheaply `Copy`able (it's just two integers).
|
||||||
|
///
|
||||||
|
/// # Example: Incremental equity rollout
|
||||||
|
/// ```ignore
|
||||||
|
/// // Advance through hole + flop ONCE
|
||||||
|
/// let base = SupersonicEvaluator::init_lookup();
|
||||||
|
/// let base = eval.advance_lookup_slice(base, hole_cards);
|
||||||
|
/// let base = eval.advance_lookup_slice(base, flop_cards);
|
||||||
|
/// // base.depth() == 5
|
||||||
|
///
|
||||||
|
/// // For each turn card, resume from base
|
||||||
|
/// for turn in remaining_deck.iter() {
|
||||||
|
/// let after_turn = eval.advance_lookup(base, turn);
|
||||||
|
/// // after_turn.depth() == 6
|
||||||
|
/// for river in remaining_deck.iter() {
|
||||||
|
/// let after_river = eval.advance_lookup(after_turn, river);
|
||||||
|
/// let value = eval.finish_7_card(after_river);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct TrieCursor {
|
||||||
|
/// Internal trie pointer — index into the `hand_ranks` table.
|
||||||
|
p: usize,
|
||||||
|
/// Number of cards that have been advanced through.
|
||||||
|
depth: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrieCursor {
|
||||||
|
/// Returns the number of cards advanced through so far.
|
||||||
|
#[inline]
|
||||||
|
pub fn depth(&self) -> u8 {
|
||||||
|
self.depth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// SupersonicEvaluator
|
// SupersonicEvaluator
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -338,6 +381,141 @@ impl SupersonicEvaluator {
|
||||||
pub fn hand_ranks(&self) -> &[u32] {
|
pub fn hand_ranks(&self) -> &[u32] {
|
||||||
&self.hand_ranks
|
&self.hand_ranks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Incremental / partial lookup API
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Create a new trie cursor at the root (before any cards).
|
||||||
|
///
|
||||||
|
/// Equivalent to the Java `int p = 53;` initialization.
|
||||||
|
#[inline]
|
||||||
|
pub fn init_lookup() -> TrieCursor {
|
||||||
|
TrieCursor {
|
||||||
|
p: INITIAL_STATE,
|
||||||
|
depth: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance the trie cursor by one card.
|
||||||
|
///
|
||||||
|
/// Returns a **new** cursor; the original is not modified.
|
||||||
|
/// This is a single array lookup: `hand_ranks[p + card_index + 1]`.
|
||||||
|
#[inline]
|
||||||
|
pub fn advance_lookup(&self, cursor: TrieCursor, card: &Card) -> TrieCursor {
|
||||||
|
let idx = card_index(card);
|
||||||
|
let next = cursor.p + idx + 1;
|
||||||
|
debug_assert!(
|
||||||
|
next < self.hand_ranks.len(),
|
||||||
|
"Supersonic trie index out of bounds: next={}, len={}",
|
||||||
|
next,
|
||||||
|
self.hand_ranks.len()
|
||||||
|
);
|
||||||
|
let new_p = self.hand_ranks[next] as usize;
|
||||||
|
debug_assert!(
|
||||||
|
new_p < self.hand_ranks.len() || cursor.depth + 1 == 7,
|
||||||
|
"Supersonic trie pointer out of bounds: p={}, len={}, depth={}",
|
||||||
|
new_p,
|
||||||
|
self.hand_ranks.len(),
|
||||||
|
cursor.depth + 1
|
||||||
|
);
|
||||||
|
TrieCursor {
|
||||||
|
p: new_p,
|
||||||
|
depth: cursor.depth + 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance the trie cursor by multiple cards (convenience method).
|
||||||
|
#[inline]
|
||||||
|
pub fn advance_lookup_slice(&self, cursor: TrieCursor, cards: &[Card]) -> TrieCursor {
|
||||||
|
let mut c = cursor;
|
||||||
|
for card in cards {
|
||||||
|
c = self.advance_lookup(c, card);
|
||||||
|
}
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish a 5-card partial evaluation.
|
||||||
|
///
|
||||||
|
/// Performs the extra `hand_ranks[p]` dereference needed for < 7 cards.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Debug builds panic if `cursor.depth() != 5`.
|
||||||
|
#[inline]
|
||||||
|
pub fn finish_5_card(&self, cursor: TrieCursor) -> HandValue {
|
||||||
|
debug_assert_eq!(cursor.depth, 5, "finish_5_card requires depth 5, got {}", cursor.depth);
|
||||||
|
debug_assert!(cursor.p < self.hand_ranks.len(), "cursor p out of bounds");
|
||||||
|
packed_to_hand_value(self.hand_ranks[cursor.p])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish a 6-card partial evaluation.
|
||||||
|
///
|
||||||
|
/// Performs the extra `hand_ranks[p]` dereference needed for < 7 cards.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Debug builds panic if `cursor.depth() != 6`.
|
||||||
|
#[inline]
|
||||||
|
pub fn finish_6_card(&self, cursor: TrieCursor) -> HandValue {
|
||||||
|
debug_assert_eq!(cursor.depth, 6, "finish_6_card requires depth 6, got {}", cursor.depth);
|
||||||
|
debug_assert!(cursor.p < self.hand_ranks.len(), "cursor p out of bounds");
|
||||||
|
packed_to_hand_value(self.hand_ranks[cursor.p])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish a 7-card partial evaluation.
|
||||||
|
///
|
||||||
|
/// The cursor value IS the packed hand rank (no extra dereference).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Debug builds panic if `cursor.depth() != 7`.
|
||||||
|
#[inline]
|
||||||
|
pub fn finish_7_card(&self, cursor: TrieCursor) -> HandValue {
|
||||||
|
debug_assert_eq!(cursor.depth, 7, "finish_7_card requires depth 7, got {}", cursor.depth);
|
||||||
|
packed_to_hand_value(cursor.p as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish a partial evaluation, auto-detecting the depth.
|
||||||
|
///
|
||||||
|
/// Returns `None` if depth is not 5, 6, or 7.
|
||||||
|
#[inline]
|
||||||
|
pub fn finish_lookup(&self, cursor: TrieCursor) -> Option<HandValue> {
|
||||||
|
match cursor.depth {
|
||||||
|
5 => Some(self.finish_5_card(cursor)),
|
||||||
|
6 => Some(self.finish_6_card(cursor)),
|
||||||
|
7 => Some(self.finish_7_card(cursor)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Raw incremental API (zero-cost, no depth tracking)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Raw initial state — just the constant `53`.
|
||||||
|
///
|
||||||
|
/// Use with [`raw_advance`](Self::raw_advance) and
|
||||||
|
/// [`raw_finish_partial`](Self::raw_finish_partial) / [`raw_finish_7`](Self::raw_finish_7).
|
||||||
|
#[inline]
|
||||||
|
pub const fn raw_init() -> usize {
|
||||||
|
INITIAL_STATE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance a raw trie pointer by one card.
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_advance(&self, p: usize, card: &Card) -> usize {
|
||||||
|
self.hand_ranks[p + card_index(card) + 1] as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish for < 7 cards (extra `hand_ranks[p]` dereference).
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_finish_partial(&self, p: usize) -> HandValue {
|
||||||
|
packed_to_hand_value(self.hand_ranks[p])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish for exactly 7 cards (`p` is the packed hand rank).
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_finish_7(&self, p: usize) -> HandValue {
|
||||||
|
packed_to_hand_value(p as u32)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -882,4 +1060,239 @@ mod tests {
|
||||||
let suit = (idx % 4) as u8;
|
let suit = (idx % 4) as u8;
|
||||||
Card::new(rank, suit).expect("valid card")
|
Card::new(rank, suit).expect("valid card")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Incremental / partial lookup API tests
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trie_cursor_init() {
|
||||||
|
let cursor = SupersonicEvaluator::init_lookup();
|
||||||
|
assert_eq!(cursor.depth(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_incremental_5_card() {
|
||||||
|
let ss = match supersonic() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
eprintln!("Skipping test_incremental_5_card: Supersonic table unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test several 5-card hands
|
||||||
|
let hands: Vec<[Card; 5]> = vec![
|
||||||
|
// Royal Flush: As Ks Qs Js Ts
|
||||||
|
[card(12, 3), card(11, 3), card(10, 3), card(9, 3), card(8, 3)],
|
||||||
|
// Straight: Tc 9d 8s 7h 6c
|
||||||
|
[card(8, 2), card(7, 1), card(6, 3), card(5, 0), card(4, 2)],
|
||||||
|
// Two Pair: As Ah 4d 4c 9s
|
||||||
|
[card(12, 3), card(12, 0), card(2, 1), card(2, 2), card(7, 3)],
|
||||||
|
// High Card: Ad 9c 7s 4h 2c
|
||||||
|
[card(12, 1), card(7, 2), card(5, 3), card(2, 0), card(0, 2)],
|
||||||
|
// Full House: Ks Kh Kd 7c 7s
|
||||||
|
[card(11, 3), card(11, 0), card(11, 1), card(5, 2), card(5, 3)],
|
||||||
|
];
|
||||||
|
|
||||||
|
for hand in hands {
|
||||||
|
let batch = ss.evaluate_5_card(&hand);
|
||||||
|
|
||||||
|
// Incremental: one card at a time
|
||||||
|
let mut cursor = SupersonicEvaluator::init_lookup();
|
||||||
|
for card in &hand {
|
||||||
|
cursor = ss.advance_lookup(cursor, card);
|
||||||
|
}
|
||||||
|
assert_eq!(cursor.depth(), 5);
|
||||||
|
let incremental = ss.finish_5_card(cursor);
|
||||||
|
assert_eq!(batch, incremental, "5-card mismatch for hand {:?}", hand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_incremental_6_card() {
|
||||||
|
let ss = match supersonic() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
eprintln!("Skipping test_incremental_6_card: Supersonic table unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let hands: Vec<[Card; 6]> = vec![
|
||||||
|
// AA + KK + 2s 3s
|
||||||
|
[card(12, 0), card(12, 1), card(11, 2), card(11, 3), card(0, 3), card(1, 3)],
|
||||||
|
// Flush draw + pair: Ah Th 8h 5h 2h Ad
|
||||||
|
[card(12, 0), card(10, 0), card(8, 0), card(5, 0), card(2, 0), card(12, 1)],
|
||||||
|
// Straight: Tc 9d 8s 7h 6c 5d
|
||||||
|
[card(8, 2), card(7, 1), card(6, 3), card(5, 0), card(4, 2), card(3, 1)],
|
||||||
|
];
|
||||||
|
|
||||||
|
for hand in hands {
|
||||||
|
let batch = ss.evaluate_6_card(&hand);
|
||||||
|
|
||||||
|
// Incremental: one card at a time
|
||||||
|
let mut cursor = SupersonicEvaluator::init_lookup();
|
||||||
|
for card in &hand {
|
||||||
|
cursor = ss.advance_lookup(cursor, card);
|
||||||
|
}
|
||||||
|
assert_eq!(cursor.depth(), 6);
|
||||||
|
let incremental = ss.finish_6_card(cursor);
|
||||||
|
assert_eq!(batch, incremental, "6-card mismatch for hand {:?}", hand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_incremental_7_card() {
|
||||||
|
let ss = match supersonic() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
eprintln!("Skipping test_incremental_7_card: Supersonic table unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let hands: Vec<[Card; 7]> = vec![
|
||||||
|
// AA + KK + 5h 5d 3c
|
||||||
|
[card(12, 0), card(12, 1), card(11, 2), card(11, 3), card(5, 0), card(5, 1), card(3, 2)],
|
||||||
|
// Flush: Ah Th + 8h 5h 2h + Kd Tc
|
||||||
|
[card(12, 0), card(10, 0), card(8, 0), card(5, 0), card(2, 0), card(11, 1), card(9, 2)],
|
||||||
|
// Royal Flush on board: As Ks Qs Js Ts + 2h 3h
|
||||||
|
[card(12, 3), card(11, 3), card(10, 3), card(9, 3), card(8, 3), card(0, 0), card(1, 0)],
|
||||||
|
];
|
||||||
|
|
||||||
|
for hand in hands {
|
||||||
|
let batch = ss.evaluate_7_card(&hand);
|
||||||
|
|
||||||
|
// Incremental: one card at a time
|
||||||
|
let mut cursor = SupersonicEvaluator::init_lookup();
|
||||||
|
for card in &hand {
|
||||||
|
cursor = ss.advance_lookup(cursor, card);
|
||||||
|
}
|
||||||
|
assert_eq!(cursor.depth(), 7);
|
||||||
|
let incremental = ss.finish_7_card(cursor);
|
||||||
|
assert_eq!(batch, incremental, "7-card mismatch for hand {:?}", hand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_incremental_slice_advance() {
|
||||||
|
let ss = match supersonic() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
eprintln!("Skipping test_incremental_slice_advance: Supersonic table unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 7-card hand: Ah Ad Ks Kc 5h 5d 3c
|
||||||
|
let hand: [Card; 7] = [
|
||||||
|
card(12, 0), card(12, 1), card(11, 2), card(11, 3),
|
||||||
|
card(5, 0), card(5, 1), card(3, 2),
|
||||||
|
];
|
||||||
|
let batch = ss.evaluate_7_card(&hand);
|
||||||
|
|
||||||
|
// Advance in slices: 2 + 3 + 2
|
||||||
|
let cursor = SupersonicEvaluator::init_lookup();
|
||||||
|
assert_eq!(cursor.depth(), 0);
|
||||||
|
|
||||||
|
let cursor = ss.advance_lookup_slice(cursor, &hand[0..2]);
|
||||||
|
assert_eq!(cursor.depth(), 2);
|
||||||
|
|
||||||
|
let cursor = ss.advance_lookup_slice(cursor, &hand[2..5]);
|
||||||
|
assert_eq!(cursor.depth(), 5);
|
||||||
|
|
||||||
|
let cursor = ss.advance_lookup_slice(cursor, &hand[5..7]);
|
||||||
|
assert_eq!(cursor.depth(), 7);
|
||||||
|
|
||||||
|
let incremental = ss.finish_7_card(cursor);
|
||||||
|
assert_eq!(batch, incremental);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw_api() {
|
||||||
|
let ss = match supersonic() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
eprintln!("Skipping test_raw_api: Supersonic table unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5-card hand: Straight Tc 9d 8s 7h 6c
|
||||||
|
let hand5: [Card; 5] = [card(8, 2), card(7, 1), card(6, 3), card(5, 0), card(4, 2)];
|
||||||
|
let batch5 = ss.evaluate_5_card(&hand5);
|
||||||
|
|
||||||
|
let mut p = SupersonicEvaluator::raw_init();
|
||||||
|
for card in &hand5 {
|
||||||
|
p = ss.raw_advance(p, card);
|
||||||
|
}
|
||||||
|
let raw5 = ss.raw_finish_partial(p);
|
||||||
|
assert_eq!(batch5, raw5);
|
||||||
|
|
||||||
|
// 7-card hand: AA + KK + 5h 5d 3c
|
||||||
|
let hand7: [Card; 7] = [
|
||||||
|
card(12, 0), card(12, 1), card(11, 2), card(11, 3),
|
||||||
|
card(5, 0), card(5, 1), card(3, 2),
|
||||||
|
];
|
||||||
|
let batch7 = ss.evaluate_7_card(&hand7);
|
||||||
|
|
||||||
|
let mut p = SupersonicEvaluator::raw_init();
|
||||||
|
for card in &hand7 {
|
||||||
|
p = ss.raw_advance(p, card);
|
||||||
|
}
|
||||||
|
let raw7 = ss.raw_finish_7(p);
|
||||||
|
assert_eq!(batch7, raw7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_finish_lookup_auto_detect() {
|
||||||
|
let ss = match supersonic() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
eprintln!("Skipping test_finish_lookup_auto_detect: Supersonic table unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5-card hand
|
||||||
|
let hand5: [Card; 5] = [card(12, 3), card(11, 3), card(10, 3), card(9, 3), card(8, 3)];
|
||||||
|
let batch5 = ss.evaluate_5_card(&hand5);
|
||||||
|
let mut cursor = SupersonicEvaluator::init_lookup();
|
||||||
|
for card in &hand5 {
|
||||||
|
cursor = ss.advance_lookup(cursor, card);
|
||||||
|
}
|
||||||
|
assert_eq!(ss.finish_lookup(cursor), Some(batch5));
|
||||||
|
|
||||||
|
// 6-card hand
|
||||||
|
let hand6: [Card; 6] = [
|
||||||
|
card(12, 0), card(12, 1), card(11, 2), card(11, 3), card(0, 3), card(1, 3),
|
||||||
|
];
|
||||||
|
let batch6 = ss.evaluate_6_card(&hand6);
|
||||||
|
let mut cursor = SupersonicEvaluator::init_lookup();
|
||||||
|
for card in &hand6 {
|
||||||
|
cursor = ss.advance_lookup(cursor, card);
|
||||||
|
}
|
||||||
|
assert_eq!(ss.finish_lookup(cursor), Some(batch6));
|
||||||
|
|
||||||
|
// 7-card hand
|
||||||
|
let hand7: [Card; 7] = [
|
||||||
|
card(12, 0), card(12, 1), card(11, 2), card(11, 3),
|
||||||
|
card(5, 0), card(5, 1), card(3, 2),
|
||||||
|
];
|
||||||
|
let batch7 = ss.evaluate_7_card(&hand7);
|
||||||
|
let mut cursor = SupersonicEvaluator::init_lookup();
|
||||||
|
for card in &hand7 {
|
||||||
|
cursor = ss.advance_lookup(cursor, card);
|
||||||
|
}
|
||||||
|
assert_eq!(ss.finish_lookup(cursor), Some(batch7));
|
||||||
|
|
||||||
|
// Invalid depths return None
|
||||||
|
let cursor0 = SupersonicEvaluator::init_lookup();
|
||||||
|
assert_eq!(ss.finish_lookup(cursor0), None);
|
||||||
|
|
||||||
|
let cursor1 = ss.advance_lookup(SupersonicEvaluator::init_lookup(), &card(12, 0));
|
||||||
|
assert_eq!(cursor1.depth(), 1);
|
||||||
|
assert_eq!(ss.finish_lookup(cursor1), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue