use rand::Rng;
use crate::arena::GameState;
use crate::core::{Card, CardBitSet, Hand};
use super::OpponentRanges;
const MAX_RESAMPLE_RETRIES: usize = 16;
pub fn sample_world<R: Rng>(ranges: &OpponentRanges, base: &GameState, rng: &mut R) -> GameState {
let acting_idx = base.to_act_idx();
let mut gs = base.clone();
let board: Vec<Card> = base.board.iter().copied().collect();
let mut dead = CardBitSet::new();
for c in &board {
dead.insert(*c);
}
for seat in 0..base.num_players {
if seat == acting_idx || ranges.get(seat).is_none() {
for c in base.hands[seat].iter() {
dead.insert(c);
}
}
}
for seat in 0..base.num_players {
if seat == acting_idx {
continue;
}
let Some(dist) = ranges.get(seat) else {
continue;
};
let mut combo = None;
for _ in 0..MAX_RESAMPLE_RETRIES {
if let Some(c) = dist.sample(rng, &dead) {
combo = Some((c.lo, c.hi));
break;
}
}
let (lo, hi) = match combo.or_else(|| uniform_pair_from_deck(&dead, rng)) {
Some(pair) => pair,
None => {
for c in base.hands[seat].iter() {
dead.insert(c);
}
continue;
}
};
dead.insert(lo);
dead.insert(hi);
let mut hand = Hand::new();
hand.insert(lo);
hand.insert(hi);
for c in &board {
hand.insert(*c);
}
gs.hands[seat] = hand;
}
gs
}
fn uniform_pair_from_deck<R: Rng>(dead: &CardBitSet, rng: &mut R) -> Option<(Card, Card)> {
let mut live: CardBitSet = (0u8..52)
.map(Card::from)
.filter(|c| !dead.contains(*c))
.collect();
let first = live.sample_one(rng)?;
live.remove(first);
let second = live.sample_one(rng)?;
Some((first, second))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arena::GameStateBuilder;
use crate::arena::hand_estimator::{HandDistributionEstimator, KnownHandsEstimator};
use rand::SeedableRng;
use rand::rngs::StdRng;
fn two_player_state() -> GameState {
let mut gs = GameStateBuilder::default()
.num_players_with_stack(2, 100.0)
.big_blind(2.0)
.build()
.unwrap();
gs.hands[0] = Hand::new_with_cards(vec![Card::from(0), Card::from(1)]);
gs.hands[1] = Hand::new_with_cards(vec![Card::from(2), Card::from(3)]);
gs.round_data.to_act_idx = 0;
gs
}
#[tokio::test]
async fn known_hands_round_trips_every_hand() {
let gs = two_player_state();
let ranges = KnownHandsEstimator.estimate(&gs, None).await;
let mut rng = StdRng::seed_from_u64(42);
let world = sample_world(&ranges, &gs, &mut rng);
assert_eq!(world.hands[0], gs.hands[0]);
assert_eq!(world.hands[1], gs.hands[1]);
}
#[tokio::test]
async fn sampled_world_has_no_duplicate_cards() {
use crate::arena::hand_estimator::UniformRandomEstimator;
let gs = two_player_state();
let ranges = UniformRandomEstimator.estimate(&gs, None).await;
let mut rng = StdRng::seed_from_u64(99);
let world = sample_world(&ranges, &gs, &mut rng);
assert_eq!(world.hands[0], gs.hands[0]);
let mut seen = CardBitSet::new();
for c in world.hands[0].iter().chain(world.hands[1].iter()) {
assert!(!seen.contains(c), "duplicate card {c:?} across seats");
seen.insert(c);
}
}
}