Skip to content

Commit

Permalink
Merge #85
Browse files Browse the repository at this point in the history
85: Ensure raises are called, fix edge cases with player options r=charleskawczynski a=charleskawczynski

This PR
 - Adds an assertion to ensure raises are called (via `all_raises_were_called`)
 - Fixes an edge cases where players are incorrectly given options when they shouldn't have any. For example, consider a heads-up game when the small blind player calls small blind (and is then all-in), the big blind has paid the blind, and has no action to make. In this case, the big blind was incorrectly given the option to check/raise/fold, which lead to downstream errors.
 - Adding `!action_required(br_leader)` to `cond3` helped fix an edge case (maybe helped with the same one above?)

Co-authored-by: Charles Kawczynski <kawczynski.charles@gmail.com>
  • Loading branch information
bors[bot] and charleskawczynski committed Jun 13, 2021
2 parents 87ba8ba + 0d2f4b1 commit d7afe40
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 13 deletions.
60 changes: 52 additions & 8 deletions src/game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,60 @@ function end_of_actions(table::Table, player)
@debug " all_playing_checked(table) = $(all_playing_checked(table))"
@debug " all_all_in_or_checked(table) = $(all_all_in_or_checked(table))"
@debug " all_all_in_except_bank_roll_leader(table) = $(all_all_in_except_bank_roll_leader(table))"
# all_all_in_except_bank_roll_leader does not supersede
# all_playing_all_in, since it always returns false if
# there are multiple players (still playing) with the
# same (max) bank roll:
@debug " all_oppononents_all_in(table, player) = $(all_oppononents_all_in(table, player))"

case_1 = last_to_raise(player)
case_2 = all_playing_checked(table)
case_3 = all_playing_all_in(table)
case_4 = all_all_in_except_bank_roll_leader(table)
case_5 = all_all_in_or_checked(table)
case_6 = !any(action_required.(players))
@debug " cases = $((case_1, case_2, case_3, case_4, case_5, case_6))"
case_7 = all_oppononents_all_in(table, player) && paid_current_raise_amount(table, player)
@debug " cases = $((case_1, case_2, case_3, case_4, case_5, case_6, case_7))"

return any((case_1, case_2, case_3, case_4, case_5, case_6))
return any((case_1, case_2, case_3, case_4, case_5, case_6, case_7))
end

function last_player_to_raise(table::Table)
for player in players_at_table(table)
last_to_raise(player) && return player
end
return nothing
end

function all_raises_were_called(table::Table)
lptr = last_player_to_raise(table)
lptr===nothing && return true

players = players_at_table(table)
ltr = last_to_raise.(players)
@assert count(ltr) == 1
players_who_called = []
@debug "Checking if all raises were called"
@debug " table.winners.declared = $(table.winners.declared)"
arwc = false
conds = map(players) do player
cond1 = seat_number(player) == seat_number(lptr)
cond2 = not_playing(player)
cond3 = all_in(player)
cond4 = pot_investment(player) pot_investment(lptr)
(cond1, cond2, cond3, cond4)
end
@debug begin
conds_debug = map(players) do player
sn = seat_number(player)
cond1 = seat_number(player) == seat_number(lptr)
cond2 = not_playing(player)
cond3 = all_in(player)
cond4 = pot_investment(player) pot_investment(lptr)
(sn, cond1, cond2, cond3, cond4)
end
for cond in conds_debug
@debug "sn, cond = $(cond[1]), $(cond[2:end])"
end
@debug "snlptr = $(seat_number(lptr))"
end
return all(map(cond->any(cond), conds))
end

function act_generic!(game::Game, state::AbstractGameState)
Expand All @@ -114,6 +154,8 @@ function act_generic!(game::Game, state::AbstractGameState)
reset_round_bank_rolls!(game, state)

any_actions_required(game) || return
play_out_game(table) && return
set_play_out_game!(table)
for (i, player) in enumerate(circle(table, FirstToAct()))
@debug "Checking to see if it's $(name(player))'s turn to act"
@debug " not_playing(player) = $(not_playing(player))"
Expand All @@ -128,6 +170,7 @@ function act_generic!(game::Game, state::AbstractGameState)
player_option!(game, player)
table.winners.declared && break
end
@assert all_raises_were_called(table)
end

function act!(game::Game, state::AbstractGameState)
Expand All @@ -146,6 +189,7 @@ function play!(game::Game)
@info "------ Playing game!"

table = game.table
set_active_status!(table)
players = players_at_table(table)

initial_brs = deepcopy(bank_roll.(players))
Expand Down Expand Up @@ -215,7 +259,6 @@ end
function reset_game!(game::Game)
table = game.table
players = players_at_table(table)
set_active_status!(game.table)

game.table = Table(;
deck=ordered_deck(),
Expand All @@ -236,7 +279,8 @@ function reset_game!(game::Game)
player.round_contribution = 0
player.active = true
end
set_active_status!(game.table)
set_active_status!(table)
table.play_out_game = false
end

"""
Expand Down
8 changes: 5 additions & 3 deletions src/player_actions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,12 @@ function raise_to_valid_raise_amount!(table::Table, player::Player, amt::Real)
for opponent in players
seat_number(opponent) == seat_number(player) && continue
not_playing(opponent) && continue
all_in(opponent) && continue
opponent.action_required = true
opponent.checked = false # to avoid exiting on all_all_in_or_checked(table). TODO: there's got to be a cleaner way
if !all_in(opponent)
opponent.action_required = true
end
opponent.last_to_raise = false
# TODO: there's got to be a cleaner way
opponent.checked = false # to avoid exiting on all_all_in_or_checked(table).
end
if bank_roll(player) 0
if isempty(pbpai)
Expand Down
34 changes: 32 additions & 2 deletions src/table.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ mutable struct Table
current_raise_amt::Float64
transactions::TransactionManager
winners::Winners
play_out_game::Bool
end

buttons(table::Table) = table.buttons
Expand All @@ -77,6 +78,7 @@ function Table(;
current_raise_amt = Float64(0),
transactions = nothing,
winners = Winners(),
play_out_game = false,
)
if transactions == nothing
transactions = TransactionManager(players)
Expand All @@ -91,7 +93,8 @@ function Table(;
buttons,
current_raise_amt,
transactions,
winners)
winners,
play_out_game)
end

function Buttons(dealer_id, players::Tuple)
Expand Down Expand Up @@ -152,6 +155,8 @@ current_raise_amt(table::Table) = table.current_raise_amt

state(table::Table) = table.state

play_out_game(table::Table) = table.play_out_game

dealer_id(table::Table) = dealer_id(table.buttons)
small_blind_id(table::Table) = small_blind_id(table.buttons)
big_blind_id(table::Table) = big_blind_id(table.buttons)
Expand Down Expand Up @@ -200,6 +205,9 @@ function bank_roll_leader(table::Table)
return br_leader, multiple_leaders
end

paid_current_raise_amount(table::Table, player::Player) =
round_contribution(player) current_raise_amt(table)

# Can be true in exactly 2 cases:
# 1) Everyone (still playing) is all-in.
# 2) Everyone (still playing), except `player`, is all-in.
Expand All @@ -223,6 +231,7 @@ end
# everyone else does not, so nobody can respond to their raise
# (if they chose to do so). Therefore, we must "play out" the
# entire game with no further actions.
# Note that this method is intended to be used _during_ a round.
function all_all_in_except_bank_roll_leader(table::Table)
br_leader, multiple_leaders = bank_roll_leader(table)
players = players_at_table(table)
Expand All @@ -231,7 +240,28 @@ function all_all_in_except_bank_roll_leader(table::Table)
@assert !multiple_leaders # We have a single bank roll leader

return all(map(players) do player
not_playing(player) || all_in(player) || seat_number(player) == seat_number(br_leader)
cond1 = not_playing(player)
cond2 = all_in(player)
cond3 = seat_number(player) == seat_number(br_leader) && !action_required(br_leader)
any((cond1, cond2, cond3))
end)
end

# Note that this method is only valid before or after a round has ended.
function set_play_out_game!(table::Table)
br_leader, multiple_leaders = bank_roll_leader(table)
players = players_at_table(table)
if multiple_leaders
return all(map(player -> not_playing(player) || all_in(player), players))
end

@assert !multiple_leaders # We have a single bank roll leader

table.play_out_game = all(map(players) do player
cond1 = not_playing(player)
cond2 = all_in(player)
cond3 = seat_number(player) == seat_number(br_leader)
any((cond1, cond2, cond3))
end)
end

Expand Down
6 changes: 6 additions & 0 deletions test/fuzz_play.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ end
end
end

@testset "Game: tournament! (3 Bot5050's)" begin
for n in 1:n_fuzz_3_players
tournament!(Game(ntuple(i->Player(Bot5050(), i; bank_roll = 6), 3)))
end
end

@testset "Game: tournament! (10 Bot5050's)" begin
for n in 1:n_fuzz_10_players
tournament!(Game(ntuple(i->Player(Bot5050(), i; bank_roll = 6), 10)))
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ submodules = [
local_run = isempty(get(ENV, "CI", ""))
n_fuzz = local_run ? 10 : 100
n_fuzz_10_players = local_run ? 1 : 10
n_fuzz_3_players = local_run ? 10 : 20

tests_to_debug = ["play", "fuzz_play"]
# tests_to_debug = submodules
Expand Down

0 comments on commit d7afe40

Please sign in to comment.