use super::*;
use rbp_cards::*;
use rbp_core::*;
use std::ops::Not;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Game {
pot: Chips,
board: Board,
seats: [Seat; N],
dealer: Position,
ticker: Position,
}
impl Default for Game {
fn default() -> Self {
let mut deck = Deck::new();
Self {
pot: 0,
board: Board::empty(),
seats: [(); N]
.map(|_| deck.hole())
.map(|h| (h, STACK))
.map(Seat::from),
dealer: 0usize,
ticker: 0usize,
}
}
}
impl Game {
pub fn root() -> Self {
let mut game = Self::default();
game.act(game.posts());
game.act(game.posts());
game
}
pub fn wipe(mut self, hole: Hole) -> Self {
for seat in self.seats.iter_mut() {
seat.reset_cards(hole);
}
self
}
pub fn assume(mut self, hero: Turn, hole: Hole) -> Self {
self.seats
.iter_mut()
.enumerate()
.filter(|(i, _)| Turn::Choice(*i) != hero)
.for_each(|(_, seat)| seat.reset_cards(hole));
self
}
pub fn ffwd(mut self, target: Street) -> Self {
while self.street() < target {
match self.turn() {
Turn::Terminal => panic!("reached terminal before target street"),
Turn::Chance => {
let action = self.reveal();
self.act(action);
}
Turn::Choice(_) => {
let action = self.passive();
self.act(action);
}
}
}
debug_assert_eq!(self.street(), target, "overshot target street");
self
}
}
impl Game {
pub fn n(&self) -> usize {
self.seats.len()
}
pub fn pot(&self) -> Chips {
self.pot
}
pub fn seats(&self) -> [Seat; N] {
self.seats
}
pub fn board(&self) -> Board {
self.board
}
pub fn turn(&self) -> Turn {
if self.must_stop() {
Turn::Terminal
} else if self.must_deal() {
Turn::Chance
} else {
Turn::Choice(self.actor_idx())
}
}
pub fn actor(&self) -> &Seat {
self.actor_ref()
}
pub fn sweat(&self) -> Observation {
Observation::from((
Hand::from(self.actor().cards()), Hand::from(self.board()), ))
}
pub fn dealer(&self) -> Turn {
Turn::Choice(self.dealer)
}
pub fn street(&self) -> Street {
self.board.street()
}
}
impl Game {
pub fn consume(&mut self, action: Action) -> Self {
self.act(action);
self.clone()
}
pub fn apply(&self, action: Action) -> Self {
self.try_apply(action).expect("valid action")
}
pub fn try_apply(&self, action: Action) -> anyhow::Result<Self> {
if !self.is_allowed(&action) {
return Err(anyhow::anyhow!(
"illegal action {:?} in state {:?}",
action,
self.turn()
));
}
let mut child = self.clone();
child.act(action);
Ok(child)
}
pub fn legal(&self) -> Vec<Action> {
if self.must_stop() {
return vec![];
}
if self.must_deal() {
return vec![self.reveal()];
}
if self.must_post() {
return vec![self.posts()];
}
let mut options = Vec::new();
if self.may_raise() {
options.push(self.raise());
}
if self.may_shove() {
options.push(self.shove());
}
if self.may_call() {
options.push(self.calls());
}
if self.may_fold() {
options.push(self.folds());
}
if self.may_check() {
options.push(self.check());
}
debug_assert!(options.len() > 0);
options
}
pub fn is_allowed(&self, action: &Action) -> bool {
match action {
Action::Raise(raise) => {
self.may_raise()
&& self.must_stop().not()
&& self.must_deal().not()
&& raise.clone() >= self.to_raise()
&& raise.clone() <= self.to_shove() - 1
}
Action::Draw(cards) => {
self.must_deal()
&& self.must_stop().not()
&& cards.clone().all(|c| self.deck().contains(&c))
&& cards.count() == self.board().street().next().n_revealed()
}
other => self.legal().contains(other),
}
}
}
impl Game {
pub fn continuation(mut self) -> Option<Self> {
debug_assert!(self.turn() == Turn::Terminal);
self.settlements()
.iter()
.zip(self.seats())
.all(|(s, seat)| seat.stack() + s.pnl().reward() >= Game::bblind())
.then(|| {
self.give_chips();
self.wipe_board();
self.wipe_seats();
self.move_button();
self.act(self.posts());
self.act(self.posts());
self
})
}
fn give_chips(&mut self) {
for (_, (settlement, seat)) in self
.settlements()
.iter()
.zip(self.seats.iter_mut())
.enumerate()
.inspect(|(i, (x, s))| log::trace!("{} {} {:>7} {}", i, s.cards(), s.stack(), x.won()))
{
seat.win(settlement.pnl().reward());
}
self.pot = 0;
}
fn wipe_board(&mut self) {
debug_assert!(self.pot() == 0);
self.board.clear();
}
fn wipe_seats(&mut self) {
debug_assert!(self.pot() == 0);
debug_assert!(self.street() == Street::Pref);
let mut deck = Deck::new();
for seat in self.seats.iter_mut() {
seat.reset_state(State::Betting);
seat.reset_cards(deck.hole());
seat.reset_stake();
seat.reset_spent();
}
}
fn move_button(&mut self) {
debug_assert!(self.pot() == 0);
debug_assert!(self.seats.len() == self.n());
debug_assert!(self.street() == Street::Pref);
self.dealer = self.dealer + 1;
self.dealer = self.dealer % self.n();
self.ticker = 0;
}
}
impl Game {
fn act(&mut self, a: Action) {
debug_assert!(self.is_allowed(&a));
match a {
Action::Check => {
self.next_player();
}
Action::Fold => {
self.fold();
self.next_player();
}
Action::Call(chips)
| Action::Blind(chips)
| Action::Raise(chips)
| Action::Shove(chips) => {
self.bet(chips);
self.next_player();
}
Action::Draw(cards) => {
self.show(cards);
self.next_player();
self.next_street();
}
}
}
fn bet(&mut self, bet: Chips) {
debug_assert!(self.actor_ref().stack() >= bet);
self.pot += bet;
self.actor_mut().bet(bet);
if self.actor_ref().stack() == 0 {
self.allin();
}
}
fn allin(&mut self) {
self.actor_mut().reset_state(State::Shoving);
}
fn fold(&mut self) {
self.actor_mut().reset_state(State::Folding);
}
fn show(&mut self, hand: Hand) {
self.ticker = 0;
self.board.add(hand);
}
}
impl Game {
fn next_street(&mut self) {
for seat in self.seats.iter_mut() {
seat.reset_stake();
}
}
fn next_player(&mut self) {
if !self.is_everyone_alright() {
loop {
self.ticker += 1;
match self.actor_ref().state() {
State::Betting => break,
State::Folding => continue,
State::Shoving => continue,
}
}
}
}
}
impl Game {
pub fn must_stop(&self) -> bool {
if self.street() == Street::Rive {
self.is_everyone_alright()
} else {
self.is_everyone_folding()
}
}
pub fn must_deal(&self) -> bool {
self.street() != Street::Rive && self.is_everyone_alright()
}
pub fn must_post(&self) -> bool {
self.street() == Street::Pref && self.pot() < Self::sblind() + Self::bblind()
}
fn is_everyone_alright(&self) -> bool {
self.is_everyone_calling() || self.is_everyone_folding() || self.is_everyone_shoving()
}
fn is_everyone_calling(&self) -> bool {
self.is_everyone_touched() && self.is_everyone_matched()
}
fn is_everyone_touched(&self) -> bool {
self.ticker > self.n() + if self.street() == Street::Pref { 1 } else { 0 }
}
fn is_everyone_matched(&self) -> bool {
let stake = self.stakes();
self.seats
.iter()
.filter(|s| s.state() == State::Betting)
.all(|s| s.stake() == stake)
}
fn is_everyone_shoving(&self) -> bool {
self.seats
.iter()
.filter(|s| s.state() != State::Folding)
.all(|s| s.state() == State::Shoving)
}
fn is_everyone_folding(&self) -> bool {
self.seats
.iter()
.filter(|s| s.state() != State::Folding)
.count()
== 1
}
pub fn may_fold(&self) -> bool {
matches!(self.turn(), Turn::Choice(_)) && self.to_call() > 0
}
pub fn may_call(&self) -> bool {
matches!(self.turn(), Turn::Choice(_))
&& self.may_fold()
&& self.to_call() < self.to_shove()
}
pub fn may_check(&self) -> bool {
matches!(self.turn(), Turn::Choice(_)) && self.stakes() == self.actor_ref().stake()
}
pub fn may_raise(&self) -> bool {
matches!(self.turn(), Turn::Choice(_)) && self.to_raise() < self.to_shove()
}
pub fn may_shove(&self) -> bool {
matches!(self.turn(), Turn::Choice(_)) && self.to_shove() > 0
}
}
impl Game {
pub fn to_call(&self) -> Chips {
self.stakes() - self.actor_ref().stake()
}
pub fn to_post(&self) -> Chips {
debug_assert!(self.street() == Street::Pref);
if self.actor_idx() == self.dealer {
Self::sblind().min(self.actor_ref().stack())
} else {
Self::bblind().min(self.actor_ref().stack())
}
}
pub fn to_shove(&self) -> Chips {
self.actor_ref().stack()
}
pub fn to_raise(&self) -> Chips {
let (most_large_stake, next_large_stake) = self
.seats
.iter()
.filter(|s| s.state() != State::Folding)
.map(|s| s.stake())
.fold((0, 0), |(most, next), stake| {
if stake > most {
(stake, most)
} else if stake > next {
(most, stake)
} else {
(most, next)
}
});
let relative_raise = most_large_stake - self.actor().stake();
let marginal_raise = most_large_stake - next_large_stake;
let required_raise = std::cmp::max(marginal_raise, Self::bblind());
relative_raise + required_raise
}
pub fn raise(&self) -> Action {
Action::Raise(self.to_raise())
}
pub fn shove(&self) -> Action {
Action::Shove(self.to_shove())
}
pub fn calls(&self) -> Action {
Action::Call(self.to_call())
}
pub fn posts(&self) -> Action {
Action::Blind(self.to_post())
}
pub fn folds(&self) -> Action {
Action::Fold
}
pub fn check(&self) -> Action {
Action::Check
}
pub fn passive(&self) -> Action {
if self.may_check() {
Action::Check
} else {
Action::Fold
}
}
pub fn reveal(&self) -> Action {
Action::Draw(self.deck().deal(self.street()))
}
}
impl Game {
pub fn settlements(&self) -> Vec<Settlement> {
debug_assert!(self.must_stop(), "non terminal game state:\n{}", self);
Showdown::from(self.ledger()).settle()
}
pub fn is_showdown(&self) -> bool {
self.seats.iter().filter(|s| s.state().is_active()).count() > 1
}
fn ledger(&self) -> Vec<Settlement> {
self.seats
.iter()
.enumerate()
.map(|(position, _)| self.settlement(position))
.collect()
}
fn settlement(&self, position: usize) -> Settlement {
let seat = &self.seats[position];
let strength = Strength::from(Hand::add(
Hand::from(seat.cards()),
Hand::from(self.board()),
));
Settlement::from((seat.spent(), seat.state(), strength))
}
}
impl Game {
pub fn draw(&self) -> Hand {
self.deck().deal(self.street())
}
pub fn deck(&self) -> Deck {
let mut removed = Hand::from(self.board);
for seat in self.seats.iter() {
removed = Hand::or(removed, Hand::from(seat.cards()));
}
Deck::from(removed.complement())
}
}
impl Game {
fn actor_idx(&self) -> Position {
(self.dealer + self.ticker) % self.n()
}
fn actor_ref(&self) -> &Seat {
self.seats
.get(self.actor_idx())
.expect("index should be in bounds bc modulo")
}
fn actor_mut(&mut self) -> &mut Seat {
let index = self.actor_idx();
self.seats
.get_mut(index)
.expect("index should be in bounds bc modulo")
}
}
impl Game {
pub fn total(&self) -> Chips {
self.pot() + self.seats().iter().map(|s| s.stack()).sum::<Chips>()
}
pub fn effective(&self) -> Chips {
self.seats.iter().map(|s| s.stack()).min().unwrap_or(0)
}
pub fn spr(&self) -> f32 {
match self.pot() {
0 => 0.0,
p => self.effective() as f32 / p as f32,
}
}
fn stakes(&self) -> Chips {
self.seats
.iter()
.map(|s| s.stake())
.max()
.expect("non-empty seats")
}
#[allow(dead_code)]
fn is_opening(&self) -> bool {
self.street() == Street::Pref && self.pot() == Self::sblind() + Self::bblind()
}
}
impl Game {
pub const fn blinds() -> [Action; 2] {
[Action::Blind(Self::sblind()), Action::Blind(Self::bblind())]
}
pub const fn bblind() -> Chips {
rbp_core::B_BLIND
}
pub const fn sblind() -> Chips {
rbp_core::S_BLIND
}
}
impl Game {
pub fn choices(&self, depth: usize) -> Path {
self.legal()
.into_iter()
.flat_map(|action| self.unfold(depth, action))
.collect()
}
fn unfold(&self, depth: usize, action: Action) -> Vec<Edge> {
match action {
Action::Raise(_) => Edge::raises(self.street(), depth),
_ => vec![Edge::from(action)],
}
}
pub fn actionize(&self, edge: Edge) -> Action {
match edge {
Edge::Fold => Action::Fold,
Edge::Draw => self.reveal(),
Edge::Call => Action::Call(self.to_call()),
Edge::Check => Action::Check,
Edge::Shove => Action::Shove(self.to_shove()),
Edge::Open(n) => Action::Raise(n * rbp_core::B_BLIND),
Edge::Raise(_) => Action::Raise(edge.into_chips(self.pot())),
}
}
pub fn edgify(&self, action: Action, depth: usize) -> Edge {
match action {
Action::Fold => Edge::Fold,
Action::Check => Edge::Check,
Action::Draw(_) => Edge::Draw,
Action::Call(_) => Edge::Call,
Action::Blind(_) => Edge::Call,
Action::Shove(_) => Edge::Shove,
Action::Raise(chips) => self.snap_to_edge(chips, depth),
}
}
fn snap_to_edge(&self, chips: Chips, depth: usize) -> Edge {
let edges = Edge::raises(self.street(), depth);
edges
.into_iter()
.min_by_key(|e| (e.into_chips(self.pot()) as i32 - chips as i32).abs())
.unwrap_or(Edge::Shove)
}
pub fn snap(&self, action: Action) -> Action {
match action {
Action::Raise(x) if x >= self.to_shove() => self.snap(self.shove()), Action::Raise(_) if !self.may_raise() => self.snap(self.shove()), Action::Raise(x) if x < self.to_raise() => self.raise(), Action::Raise(x) => Action::Raise(x), Action::Shove(_) if self.may_shove() => self.shove(), Action::Shove(_) if self.may_call() => self.calls(), Action::Shove(_) => self.passive(), Action::Call(_) if self.may_call() => self.calls(), Action::Call(_) if self.may_shove() => self.shove(), Action::Call(_) => self.passive(), Action::Check if self.may_check() => Action::Check, Action::Check if self.may_call() => self.calls(), Action::Check => self.folds(), Action::Fold if self.may_fold() => Action::Fold, Action::Fold => Action::Check, Action::Draw(_) | Action::Blind(_) => action,
}
}
}
impl std::fmt::Display for Game {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
for seat in self.seats.iter() {
writeln!(
f,
"{:>3} {:>3} {:<6}",
seat.state(),
seat.cards(),
seat.stack()
)?;
}
writeln!(f, "Pot {}", self.pot())?;
writeln!(f, "Board {}", self.board())?;
Ok(())
}
}
pub struct Perpetual(Game);
impl Perpetual {
pub fn new(game: Game) -> Self {
Self(game)
}
}
impl Iterator for Perpetual {
type Item = Action;
fn next(&mut self) -> Option<Self::Item> {
loop {
let actions = self.0.legal();
if !actions.is_empty() {
let action = actions[rand::random_range(0..actions.len())];
self.0 = self.0.apply(action);
return Some(action);
}
self.0 = self.0.clone().continuation().unwrap_or_else(Game::root);
}
}
}
pub struct Hands(Game);
impl Hands {
pub fn new(game: Game) -> Self {
Self(game)
}
}
impl Iterator for Hands {
type Item = Game;
fn next(&mut self) -> Option<Self::Item> {
while !self.0.must_stop() {
let actions = self.0.legal();
let action = actions[rand::random_range(0..actions.len())];
self.0 = self.0.apply(action);
}
let terminal = self.0.clone();
self.0 = self.0.clone().continuation()?;
Some(terminal)
}
}
pub struct Session(Game);
impl Session {
pub fn new(game: Game) -> Self {
Self(game)
}
}
impl Iterator for Session {
type Item = Action;
fn next(&mut self) -> Option<Self::Item> {
loop {
let actions = self.0.legal();
if !actions.is_empty() {
let action = actions[rand::random_range(0..actions.len())];
self.0 = self.0.apply(action);
return Some(action);
}
self.0 = self.0.clone().continuation()?;
}
}
}
impl Game {
pub fn perpetual(self) -> Perpetual {
Perpetual::new(self)
}
pub fn hands(self) -> Hands {
Hands::new(self)
}
pub fn session(self) -> Session {
Session::new(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root() {
let game = Game::root();
assert_eq!(game.board().street(), Street::Pref);
assert_eq!(game.actor().state(), State::Betting);
assert_eq!(game.pot(), Game::sblind() + Game::bblind());
assert_eq!(game.turn(), Turn::Choice(game.dealer)); }
#[test]
fn everyone_folds_pref() {
let game = Game::root();
let game = game.apply(Action::Fold);
assert!(game.is_everyone_folding() == true);
assert!(game.is_everyone_alright() == true);
assert!(game.is_everyone_calling() == false);
assert!(game.must_deal() == true); assert!(game.must_stop() == true);
}
#[test]
fn everyone_folds_flop() {
let game = Game::root();
let flop = game.deck().deal(Street::Pref);
let game = game.apply(Action::Call(1));
let game = game.apply(Action::Check);
let game = game.apply(Action::Draw(flop));
let game = game.apply(Action::Raise(10));
let game = game.apply(Action::Fold);
assert!(game.is_everyone_folding() == true);
assert!(game.is_everyone_alright() == true);
assert!(game.is_everyone_calling() == false);
assert!(game.must_deal() == true); assert!(game.must_stop() == true);
}
#[test]
fn history_of_checks() {
let game = Game::root();
assert!(game.board().street() == Street::Pref);
assert!(game.pot() == 3);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false);
assert!(game.is_everyone_alright() == false);
assert!(game.is_everyone_calling() == false);
assert!(game.is_everyone_touched() == false);
assert!(game.is_everyone_matched() == false);
let game = game.apply(Action::Call(1));
assert!(game.board().street() == Street::Pref);
assert!(game.pot() == 4); assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false);
assert!(game.is_everyone_alright() == false);
assert!(game.is_everyone_calling() == false);
assert!(game.is_everyone_touched() == false);
assert!(game.is_everyone_matched() == true);
let game = game.apply(Action::Check);
assert!(game.board().street() == Street::Pref);
assert!(game.pot() == 4);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == true); assert!(game.is_everyone_alright() == true); assert!(game.is_everyone_calling() == true); assert!(game.is_everyone_touched() == true); assert!(game.is_everyone_matched() == true);
let flop = game.deck().deal(game.board().street());
let game = game.apply(Action::Draw(flop));
assert!(game.board().street() == Street::Flop); assert!(game.pot() == 4);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false); assert!(game.is_everyone_alright() == false); assert!(game.is_everyone_calling() == false); assert!(game.is_everyone_touched() == false); assert!(game.is_everyone_matched() == true);
let game = game.apply(Action::Check);
assert!(game.board().street() == Street::Flop);
assert!(game.pot() == 4);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false);
assert!(game.is_everyone_alright() == false);
assert!(game.is_everyone_calling() == false);
assert!(game.is_everyone_touched() == false);
assert!(game.is_everyone_matched() == true);
let game = game.apply(Action::Check);
assert!(game.board().street() == Street::Flop);
assert!(game.pot() == 4);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == true); assert!(game.is_everyone_alright() == true); assert!(game.is_everyone_calling() == true); assert!(game.is_everyone_touched() == true); assert!(game.is_everyone_matched() == true);
let turn = game.deck().deal(game.board().street());
let game = game.apply(Action::Draw(turn));
assert!(game.board().street() == Street::Turn);
assert!(game.pot() == 4);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false); assert!(game.is_everyone_alright() == false); assert!(game.is_everyone_calling() == false); assert!(game.is_everyone_touched() == false); assert!(game.is_everyone_matched() == true);
let game = game.apply(Action::Check);
assert!(game.board().street() == Street::Turn);
assert!(game.pot() == 4);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false);
assert!(game.is_everyone_alright() == false);
assert!(game.is_everyone_calling() == false);
assert!(game.is_everyone_touched() == false);
assert!(game.is_everyone_matched() == true);
let game = game.apply(Action::Raise(4));
assert!(game.board().street() == Street::Turn);
assert!(game.pot() == 8);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false);
assert!(game.is_everyone_alright() == false);
assert!(game.is_everyone_calling() == false);
assert!(game.is_everyone_touched() == true); assert!(game.is_everyone_matched() == false);
let game = game.apply(Action::Call(4));
assert!(game.board().street() == Street::Turn);
assert!(game.pot() == 12); assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == true); assert!(game.is_everyone_alright() == true); assert!(game.is_everyone_calling() == true); assert!(game.is_everyone_touched() == true);
assert!(game.is_everyone_matched() == true);
let rive = game.deck().deal(game.board().street());
let game = game.apply(Action::Draw(rive));
assert!(game.board().street() == Street::Rive); assert!(game.pot() == 12);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false); assert!(game.is_everyone_alright() == false); assert!(game.is_everyone_calling() == false); assert!(game.is_everyone_touched() == false); assert!(game.is_everyone_matched() == true);
let game = game.apply(Action::Check);
assert!(game.board().street() == Street::Rive);
assert!(game.pot() == 12);
assert!(game.must_post() == false);
assert!(game.must_stop() == false);
assert!(game.must_deal() == false);
assert!(game.is_everyone_alright() == false);
assert!(game.is_everyone_calling() == false);
assert!(game.is_everyone_touched() == false);
assert!(game.is_everyone_matched() == true);
let game = game.apply(Action::Check);
assert!(game.board().street() == Street::Rive);
assert!(game.pot() == 12);
assert!(game.must_post() == false);
assert!(game.must_stop() == true); assert!(game.must_deal() == false);
assert!(game.is_everyone_alright() == true); assert!(game.is_everyone_calling() == true); assert!(game.is_everyone_touched() == true); assert!(game.is_everyone_matched() == true); }
#[test]
fn next_after_fold() {
let game = Game::root().apply(Action::Fold);
assert!(game.must_stop());
let next = game.continuation().expect("can continue");
assert_eq!(next.street(), Street::Pref);
assert_eq!(next.pot(), Game::sblind() + Game::bblind());
assert_eq!(next.board(), Board::empty());
assert_eq!(next.dealer, 1); assert_eq!(next.turn(), Turn::Choice(1)); assert!(!next.is_everyone_touched());
}
#[test]
fn dealer_rotation() {
let game = Game::root();
assert_eq!(game.dealer, 0);
let game = game.apply(Action::Fold).continuation().unwrap();
assert_eq!(game.dealer, 1);
let game = game.apply(Action::Fold).continuation().unwrap();
assert_eq!(game.dealer, 0); let game = game.apply(Action::Fold).continuation().unwrap();
assert_eq!(game.dealer, 1);
}
#[test]
fn ticker_reset_on_next() {
let g0 = Game::root();
let g1 = g0.apply(Action::Fold).continuation().unwrap();
let g2 = g1.apply(Action::Fold).continuation().unwrap();
assert_eq!(g0.ticker, g1.ticker);
assert_eq!(g1.ticker, g2.ticker);
assert_eq!(g0.ticker, 2); }
#[test]
fn touched_with_rotated_dealer() {
let game = Game::root().apply(Action::Fold).continuation().unwrap();
assert_eq!(game.dealer, 1);
assert!(!game.is_everyone_touched()); let game = game.apply(Action::Call(1));
assert!(!game.is_everyone_touched()); let game = game.apply(Action::Check);
assert!(game.is_everyone_touched()); assert!(game.must_deal());
}
#[test]
fn full_hand_rotated_dealer() {
let game = Game::root().apply(Action::Fold).continuation().unwrap();
assert_eq!(game.dealer, 1);
let game = game.apply(Action::Call(1)).apply(Action::Check);
assert!(game.must_deal());
let flop = game.deck().deal(Street::Pref);
let game = game.apply(Action::Draw(flop));
assert_eq!(game.street(), Street::Flop);
assert_eq!(game.turn(), Turn::Choice(0)); assert!(!game.is_everyone_touched());
let game = game.apply(Action::Check).apply(Action::Check);
assert!(game.is_everyone_touched());
assert!(game.must_deal());
}
#[test]
fn five_hands_sequence() {
let mut game = Game::root();
for i in 0..5 {
assert_eq!(game.dealer, i % 2);
assert_eq!(game.pot(), Game::sblind() + Game::bblind());
assert_eq!(game.street(), Street::Pref);
assert!(!game.is_everyone_touched());
assert_eq!(game.turn(), Turn::Choice(game.dealer));
game = game.apply(Action::Fold).continuation().unwrap();
}
}
#[test]
fn symmetric_preflop_action() {
let g0 = Game::root();
assert_eq!(g0.dealer, 0);
let g0 = g0.apply(Action::Call(1));
assert!(!g0.is_everyone_touched());
let g0 = g0.apply(Action::Check);
assert!(g0.is_everyone_touched());
assert!(g0.must_deal());
let g1 = Game::root().apply(Action::Fold).continuation().unwrap();
assert_eq!(g1.dealer, 1);
let g1 = g1.apply(Action::Call(1));
assert!(!g1.is_everyone_touched());
let g1 = g1.apply(Action::Check);
assert!(g1.is_everyone_touched());
assert!(g1.must_deal());
}
#[test]
fn flop_actor_both_dealers() {
let g0 = Game::root().apply(Action::Call(1)).apply(Action::Check);
let flop = g0.deck().deal(Street::Pref);
let g0 = g0.apply(Action::Draw(flop));
assert_eq!(g0.turn(), Turn::Choice(1)); let g1 = Game::root()
.apply(Action::Fold)
.continuation()
.unwrap()
.apply(Action::Call(1))
.apply(Action::Check);
let flop = g1.deck().deal(Street::Pref);
let g1 = g1.apply(Action::Draw(flop));
assert_eq!(g1.turn(), Turn::Choice(0)); }
#[test]
fn allin_showdown() {
let game = Game::root();
let shove = game.to_shove(); let game = game.apply(Action::Shove(shove));
let shove = game.to_shove();
let game = game.apply(Action::Shove(shove));
assert!(game.is_everyone_shoving());
assert!(game.must_stop() || game.must_deal());
}
#[test]
fn allin_fold() {
let game = Game::root();
let shove = game.to_shove();
let game = game.apply(Action::Shove(shove)).apply(Action::Fold);
assert!(game.must_stop());
assert!(game.is_everyone_folding());
}
#[test]
fn raise_reraise() {
let g0 = Game::root();
let r1 = g0.to_raise();
let g1 = g0.apply(Action::Raise(r1));
let r2 = g1.to_raise();
let g2 = g1.apply(Action::Raise(r2));
assert!(!g2.must_deal()); assert!(!g2.is_everyone_alright()); assert_eq!(g2.turn(), Turn::Choice(0)); assert!(g2.may_raise() || g2.may_call()); }
#[test]
fn stacks_after_fold() {
let game = Game::root().apply(Action::Fold);
assert!(game.must_stop());
let settlements = game.settlements();
assert_eq!(settlements[0].pnl().reward(), 0); assert_eq!(settlements[1].pnl().reward(), 3); assert_eq!(settlements[0].won(), -1); assert_eq!(settlements[1].won(), 1); }
#[test]
fn stacks_after_flop_bet_fold() {
let game = Game::root().apply(Action::Call(1)).apply(Action::Check);
let flop = game.deck().deal(Street::Pref);
let game = game.apply(Action::Draw(flop));
let raise = game.to_raise();
let game = game.apply(Action::Raise(raise));
let game = game.apply(Action::Fold);
assert!(game.must_stop());
let settlements = game.settlements();
assert_eq!(settlements[0].pnl().reward(), 0); assert!(settlements[1].pnl().reward() > 0); assert_eq!(settlements[0].won(), -2); }
#[test]
fn multi_hand_with_betting() {
let g0 = Game::root();
let g0 = g0.apply(Action::Call(1)).apply(Action::Check);
let flop = g0.deck().deal(Street::Pref);
let g0 = g0.apply(Action::Draw(flop));
let raise = g0.to_raise();
let g0 = g0.apply(Action::Raise(raise)).apply(Action::Fold);
let g1 = g0.continuation().unwrap();
assert_eq!(g1.dealer, 1);
let r1 = g1.to_raise();
let g1 = g1.apply(Action::Raise(r1));
let c1 = g1.to_call();
let g1 = g1.apply(Action::Call(c1));
let flop = g1.deck().deal(Street::Pref);
let g1 = g1.apply(Action::Draw(flop));
let raise = g1.to_raise();
let g1 = g1.apply(Action::Raise(raise)).apply(Action::Fold);
let g2 = g1.continuation().unwrap();
assert_eq!(g2.dealer, 0);
assert_eq!(g2.pot(), 3);
}
#[test]
fn legal_preflop_options() {
let game = Game::root();
let legal = game.legal();
assert!(legal.contains(&Action::Fold));
assert!(legal.contains(&Action::Call(1)));
assert!(legal.iter().any(|a| matches!(a, Action::Raise(_))));
assert!(legal.iter().any(|a| matches!(a, Action::Shove(_))));
assert!(!legal.contains(&Action::Check)); }
#[test]
fn legal_bb_can_check() {
let game = Game::root().apply(Action::Call(1));
let legal = game.legal();
assert!(legal.contains(&Action::Check));
assert!(!legal.contains(&Action::Fold)); }
#[test]
fn legal_flop_options() {
let game = Game::root().apply(Action::Call(1)).apply(Action::Check);
let flop = game.deck().deal(Street::Pref);
let game = game.apply(Action::Draw(flop));
let legal = game.legal();
assert!(legal.contains(&Action::Check));
assert!(legal.iter().any(|a| matches!(a, Action::Raise(_))));
assert!(!legal.contains(&Action::Fold)); }
#[test]
fn terminal_river_showdown() {
let mut game = Game::root().apply(Action::Call(1)).apply(Action::Check);
for street in [Street::Pref, Street::Flop, Street::Turn] {
let cards = game.deck().deal(street);
game = game
.apply(Action::Draw(cards))
.apply(Action::Check)
.apply(Action::Check);
}
assert_eq!(game.street(), Street::Rive);
assert!(game.must_stop());
assert!(!game.must_deal());
}
#[test]
fn ten_hands_alternation() {
let mut game = Game::root();
for i in 0..10 {
assert_eq!(game.dealer, i % 2);
assert_eq!(game.turn(), Turn::Choice(game.dealer));
game = game.apply(Action::Fold).continuation().unwrap();
}
}
#[test]
fn min_raise_size() {
let game = Game::root();
assert_eq!(game.to_raise(), 3);
let game = game.apply(Action::Raise(3));
assert_eq!(game.to_raise(), 4);
}
#[test]
fn pot_tracking() {
let game = Game::root();
assert_eq!(game.pot(), 3);
let game = game.apply(Action::Call(1));
assert_eq!(game.pot(), 4);
let game = game.apply(Action::Raise(4));
assert_eq!(game.pot(), 8);
let game = game.apply(Action::Call(4));
assert_eq!(game.pot(), 12);
}
#[test]
fn bust_prevents_next() {
let game = Game::root();
let shove = game.to_shove();
let game = game.apply(Action::Shove(shove));
let shove = game.to_shove();
let game = game.apply(Action::Shove(shove));
let mut game = game;
while !game.must_stop() {
if game.must_deal() {
let cards = game.deck().deal(game.street());
game = game.apply(Action::Draw(cards));
}
}
let rewards: Vec<_> = game
.settlements()
.iter()
.map(|s| s.pnl().reward())
.collect();
assert!(rewards.contains(&0) && rewards.contains(&200));
}
#[test]
fn actor_idx_wrapping() {
let game = Game::root();
assert_eq!(game.actor_idx(), 0); let game = game.apply(Action::Call(1));
assert_eq!(game.actor_idx(), 1); let game = game.apply(Action::Check);
assert_eq!((game.dealer + game.ticker) % game.n(), 0); }
#[test]
fn snap_legal_unchanged() {
let game = Game::root();
game.legal()
.iter()
.inspect(|&&action| assert_eq!(game.snap(action), action))
.count();
}
#[test]
fn snap_raise_to_shove_too_large() {
let game = Game::root();
let shove = game.to_shove();
assert_eq!(game.snap(Action::Raise(Chips::MAX)), game.shove());
assert_eq!(game.snap(Action::Raise(shove)), game.shove());
}
#[test]
fn snap_raise_to_minim_too_small() {
let game = Game::root();
let minraise = game.to_raise();
assert_eq!(game.snap(Action::Raise(1)), Action::Raise(minraise));
assert_eq!(game.snap(Action::Raise(0)), Action::Raise(minraise));
}
#[test]
fn snap_fold_to_check_not_facing_bet() {
let game = Game::root().apply(Action::Call(1));
assert!(!game.may_fold());
assert!(game.may_check());
assert_eq!(game.snap(Action::Fold), Action::Check);
}
#[test]
fn snap_check_to_call_facing_bet() {
let game = Game::root();
assert!(!game.may_check());
assert!(game.may_call());
assert_eq!(game.snap(Action::Check), game.calls());
}
}