use rand::Rng;
use crate::arena::{GameState, action::AgentAction, game_state::Round};
use crate::core::{Card, Deck, PlayerBitSet, Rank, Rankable, SevenCardAccum, Suit, Value};
const MAX_CONTENDERS: usize = 16;
const DECK_LEN: usize = 52;
pub(super) fn fast_forward_apply_action(gs: &mut GameState, action: &AgentAction) {
let call_current_bet = |gs: &mut GameState| {
let _ = gs.do_bet(gs.current_round_bet(), false);
};
match action {
AgentAction::Fold => gs.fold(),
AgentAction::Call => call_current_bet(gs),
AgentAction::Bet(amount) => {
if gs.do_bet(*amount, false).is_err() {
call_current_bet(gs);
}
}
AgentAction::AllIn => {
let idx = gs.to_act_idx();
let target = gs.stacks[idx] + gs.current_round_player_bet(idx);
if gs.do_bet(target, false).is_err() {
call_current_bet(gs);
}
}
}
}
pub(super) fn fast_forward_run_to_showdown<R: Rng>(gs: &mut GameState, rng: &mut R) {
let mut deck = fast_forward_remaining_deck(gs);
loop {
let contenders = gs.player_active.count() + gs.player_all_in.count();
if contenders <= 1 {
return;
}
match gs.round {
Round::Showdown | Round::Complete => return,
Round::Starting | Round::Ante | Round::DealPreflop => gs.advance_round(),
Round::DealFlop => {
fast_forward_deal_community_cards(gs, &mut deck, 3, rng);
gs.advance_round();
}
Round::DealTurn | Round::DealRiver => {
fast_forward_deal_community_cards(gs, &mut deck, 1, rng);
gs.advance_round();
}
Round::Preflop | Round::Flop | Round::Turn | Round::River => {
fast_forward_everyone_calls(gs);
gs.advance_round();
}
}
}
}
fn fast_forward_everyone_calls(gs: &mut GameState) {
for _ in 0..gs.num_players {
if gs.round_data.num_players_need_action() == 0 {
break;
}
let to_match = gs.current_round_bet();
if gs.do_bet(to_match, false).is_err() {
let _ = gs.do_bet(0.0, false);
}
}
}
fn fast_forward_remaining_deck(gs: &GameState) -> Deck {
let mut deck = Deck::default();
for hand in &gs.hands {
for card in hand.iter() {
deck.remove(&card);
}
}
deck
}
fn fast_forward_deal_community_cards<R: Rng>(
gs: &mut GameState,
deck: &mut Deck,
num_cards: usize,
rng: &mut R,
) {
for _ in 0..num_cards {
let Some(card) = deck.deal(rng) else { return };
gs.board.push(card);
for hand in gs.hands.iter_mut() {
hand.insert(card);
}
}
}
pub(super) fn fast_forward_distribute_pot(gs: &mut GameState) {
let contenders = gs.player_active | gs.player_all_in;
let count = contenders.count();
if count == 0 {
return;
}
let pot = gs.total_pot;
if pot <= 0.0 {
return;
}
if count == 1 {
let winner = contenders.ones().next().unwrap();
gs.award(winner, pot);
gs.total_pot = 0.0;
return;
}
let winners = find_winners(&contenders, &gs.hands);
let split = pot / winners.count() as f32;
for idx in winners.ones() {
gs.award(idx, split);
}
gs.total_pot = 0.0;
}
fn find_winners(contenders: &PlayerBitSet, hands: &[crate::core::Hand]) -> PlayerBitSet {
let mut best_rank = None;
let mut winners = PlayerBitSet::default();
for idx in contenders.ones() {
let rank = hands[idx].rank();
match best_rank {
None => {
best_rank = Some(rank);
winners.enable(idx);
}
Some(current) if rank > current => {
best_rank = Some(rank);
winners = PlayerBitSet::default();
winners.enable(idx);
}
Some(current) if rank == current => winners.enable(idx),
_ => {}
}
}
winners
}
pub(super) fn fast_forward_advance_betting(gs: &mut GameState) {
for _ in 0..8 {
match gs.round {
Round::DealFlop | Round::DealTurn | Round::DealRiver => return,
Round::Showdown | Round::Complete => return,
Round::Starting | Round::Ante | Round::DealPreflop => gs.advance_round(),
Round::Preflop | Round::Flop | Round::Turn | Round::River => {
fast_forward_everyone_calls(gs);
gs.advance_round();
}
}
}
}
fn fast_forward_uncontested_reward(
gs: &GameState,
contenders: PlayerBitSet,
player_idx: usize,
) -> Option<f32> {
match contenders.count() {
0 => Some(gs.player_reward(player_idx)),
1 => {
let winner = contenders.ones().next().unwrap();
let winnings = if winner == player_idx {
gs.total_pot
} else {
0.0
};
Some(gs.stacks[player_idx] + winnings - gs.starting_stacks[player_idx])
}
_ => None,
}
}
pub(super) fn fast_forward_enumerate_showdowns(
gs: &GameState,
player_idx: usize,
cards_needed: usize,
) -> f32 {
let contenders = gs.player_active | gs.player_all_in;
if let Some(reward) = fast_forward_uncontested_reward(gs, contenders, player_idx) {
return reward;
}
let pot = gs.total_pot;
if pot <= 0.0 {
return gs.player_reward(player_idx);
}
if cards_needed == 0 {
return evaluate_showdown_reward(gs, &contenders, pot, player_idx);
}
let deck = fast_forward_remaining_deck(gs);
let mut card_buf = [Card::new(Value::Two, Suit::Spade); DECK_LEN];
let mut rn = 0;
for c in deck.iter() {
card_buf[rn] = c;
rn += 1;
}
let remaining = &card_buf[..rn];
let starting_stack = gs.starting_stacks[player_idx];
let remaining_stack = gs.stacks[player_idx];
let mut total_reward = 0.0f64;
let mut count = 0u32;
let mut acc_buf = [(0usize, SevenCardAccum::new()); MAX_CONTENDERS];
let base = contender_accums(gs, &contenders, &mut acc_buf);
if cards_needed == 1 {
for &card in remaining {
let reward = combo_reward::<1>(base, player_idx, pot, [card]);
total_reward += f64::from(remaining_stack + reward - starting_stack);
count += 1;
}
} else {
for i in 0..remaining.len() {
for j in (i + 1)..remaining.len() {
let reward = combo_reward::<2>(base, player_idx, pot, [remaining[i], remaining[j]]);
total_reward += f64::from(remaining_stack + reward - starting_stack);
count += 1;
}
}
}
(total_reward / f64::from(count)) as f32
}
pub(super) const FLOP_SAMPLES: usize = 3;
pub(super) fn fast_forward_sample_flop_enumerate_runout<R: Rng>(
gs: &GameState,
player_idx: usize,
rng: &mut R,
) -> f32 {
fast_forward_sample_flop_enumerate_runout_n(gs, player_idx, rng, FLOP_SAMPLES)
}
pub(super) fn fast_forward_sample_flop_enumerate_runout_n<R: Rng>(
gs: &GameState,
player_idx: usize,
rng: &mut R,
num_samples: usize,
) -> f32 {
let contenders = gs.player_active | gs.player_all_in;
if let Some(reward) = fast_forward_uncontested_reward(gs, contenders, player_idx) {
return reward;
}
let pot = gs.total_pot;
if pot <= 0.0 {
return gs.player_reward(player_idx);
}
let mut deck = fast_forward_remaining_deck(gs);
let starting_stack = gs.starting_stacks[player_idx];
let remaining_stack = gs.stacks[player_idx];
let mut total_reward = 0.0f64;
let mut total_count = 0u64;
let mut acc_buf = [(0usize, SevenCardAccum::new()); MAX_CONTENDERS];
let mut card_buf = [Card::new(Value::Two, Suit::Spade); DECK_LEN];
for _ in 0..num_samples {
let mut flop = [Card::new(Value::Two, Suit::Spade); 3];
let mut fc = 0;
while fc < 3 {
match deck.deal(rng) {
Some(c) => {
flop[fc] = c;
fc += 1;
}
None => break,
}
}
if fc < 3 {
break;
}
let mut n = 0;
for idx in contenders.ones() {
let mut acc = SevenCardAccum::new();
for c in gs.hands[idx].iter() {
acc.add(c);
}
for &c in &flop {
acc.add(c);
}
acc_buf[n] = (idx, acc);
n += 1;
}
let base = &acc_buf[..n];
let mut rn = 0;
for c in deck.iter() {
card_buf[rn] = c;
rn += 1;
}
let remaining = &card_buf[..rn];
for i in 0..remaining.len() {
for j in (i + 1)..remaining.len() {
let reward = combo_reward::<2>(base, player_idx, pot, [remaining[i], remaining[j]]);
total_reward += f64::from(remaining_stack + reward - starting_stack);
total_count += 1;
}
}
for &card in &flop {
deck.insert(card);
}
}
if total_count == 0 {
return gs.player_reward(player_idx);
}
(total_reward / total_count as f64) as f32
}
fn contender_accums<'b>(
gs: &GameState,
contenders: &PlayerBitSet,
buf: &'b mut [(usize, SevenCardAccum); MAX_CONTENDERS],
) -> &'b [(usize, SevenCardAccum)] {
let mut n = 0;
for idx in contenders.ones() {
let mut acc = SevenCardAccum::new();
for c in gs.hands[idx].iter() {
acc.add(c);
}
buf[n] = (idx, acc);
n += 1;
}
&buf[..n]
}
#[inline]
fn combo_reward<const N: usize>(
base: &[(usize, SevenCardAccum)],
player_idx: usize,
pot: f32,
extra: [crate::core::Card; N],
) -> f32 {
let mut best: Option<Rank> = None;
let mut win_count = 0u32;
let mut player_wins = false;
for &(idx, base_acc) in base {
let mut acc = base_acc;
for card in extra {
acc.add(card);
}
let rank = acc.rank();
match best {
Some(b) if rank < b => {}
Some(b) if rank == b => {
win_count += 1;
player_wins |= idx == player_idx;
}
_ => {
best = Some(rank);
win_count = 1;
player_wins = idx == player_idx;
}
}
}
if player_wins {
pot / win_count as f32
} else {
0.0
}
}
fn evaluate_showdown_reward(
gs: &GameState,
contenders: &PlayerBitSet,
pot: f32,
player_idx: usize,
) -> f32 {
let mut acc_buf = [(0usize, SevenCardAccum::new()); MAX_CONTENDERS];
let base = contender_accums(gs, contenders, &mut acc_buf);
let reward = combo_reward::<0>(base, player_idx, pot, []);
gs.stacks[player_idx] + reward - gs.starting_stacks[player_idx]
}