Skip to content

Commit

Permalink
Add capability to goto player option
Browse files Browse the repository at this point in the history
  • Loading branch information
charleskawczynski committed Aug 10, 2023
1 parent 505a6f3 commit f78309e
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 75 deletions.
1 change: 1 addition & 0 deletions src/TexasHoldem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct Flop <: AbstractRound end
struct Turn <: AbstractRound end
struct River <: AbstractRound end

include("goto_player_option.jl")
include("player_type.jl")
include("players.jl")
include("transactions.jl")
Expand Down
197 changes: 131 additions & 66 deletions src/game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -120,116 +120,181 @@ function end_preflop_actions(table::Table, player::Player, ::PreFlop)
return all((cond1, cond2, cond3))
end

function act_generic!(game::Game, round::AbstractRound)
skip_pre_option(sf::StartFrom{StartOfGame}, _) = false
skip_option(sf::StartFrom{StartOfGame}, _) = false
skip_post_option(sf::StartFrom{StartOfGame}, _) = false

Check warning on line 125 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L125

Added line #L125 was not covered by tests

skip_pre_option(sf::StartFrom{GP}, player) where {GP <: PlayerOption} =
true
skip_option(sf::StartFrom{GP}, player) where {GP <: PlayerOption} =
seat_number(sf.game_point.player) seat_number(player)
skip_post_option(sf::StartFrom{GP}, player) where {GP <: PlayerOption} =

Check warning on line 131 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L131

Added line #L131 was not covered by tests
seat_number(sf.game_point.player) seat_number(player)

function act_generic!(game::Game, round::AbstractRound, sf::StartFrom)
table = game.table
players = table.players
logger = table.logger
table.winners.declared && return
set_round!(table, round)
print_round(table, round)
reset_round_bank_rolls!(game, round)

any_actions_required(game) || return
play_out_game(table) && return
set_play_out_game!(table)
@assert sf.game_point isa StartOfGame || sf.game_point isa PlayerOption
if sf.game_point isa StartOfGame
set_round!(table, round)
print_round(table, round)
reset_round_bank_rolls!(game, round)

any_actions_required(game) || return
play_out_game(table) && return
set_play_out_game!(table)
end
reached_game_point = false # to support when StartFrom is not StartOfGame
past_game_point = false
for (i, sn) in enumerate(circle(table, FirstToAct()))
player = players[sn]
@cdebug logger "Checking to see if it's $(name(player))'s turn to act"
@cdebug logger " not_playing(player) = $(not_playing(player))"
@cdebug logger " all_in(player) = $(all_in(player))"
not_playing(player) && continue # skip players not playing
set_preflop_blind_raise!(table, player, round, i)
if end_of_actions(table, player)
break
if reached_game_point || !skip_pre_option(sf, player)
@cdebug logger "Checking to see if it's $(name(player))'s turn to act"
@cdebug logger " not_playing(player) = $(not_playing(player))"
@cdebug logger " all_in(player) = $(all_in(player))"
not_playing(player) && continue # skip players not playing
set_preflop_blind_raise!(table, player, round, i)
if end_of_actions(table, player)
break
end
all_in(player) && continue
@cdebug logger "$(name(player))'s turn to act"
end
if reached_game_point || !skip_option(sf, player)
reached_game_point = true
if sf.game_point isa PlayerOption && !past_game_point
action = sf.game_point.action
past_game_point = true
else
action = player_option(game, player)
end
end
if reached_game_point || !skip_post_option(sf, player)
update_given_valid_action!(table, player, action)
table.winners.declared && break
end_preflop_actions(table, player, round) && break
end
all_in(player) && continue
@cdebug logger "$(name(player))'s turn to act"
player_option(game, player)
table.winners.declared && break
end_preflop_actions(table, player, round) && break
if i > n_max_actions(table)
error("Too many actions have occured, please open an issue.")
error("Too many actions have occurred, please open an issue.")
end
end
@cinfo logger "Betting is finished."
@assert all_bets_were_called(table)
end

function act!(game::Game, round::AbstractRound)
act_generic!(game, round)
skip_round(round::AbstractRound, sf::StartFrom) = skip_round(round, sf.game_point)
skip_round(round::AbstractRound, gp::StartOfGame) = false
skip_round(round::AbstractRound, po::PlayerOption) = skip_round(round, po.round)
skip_round(round::AbstractRound, ::AbstractRound) = false

# Skip to Preflop
# Nothing needed-- we automatically skip to this if `StartFrom` is not StartOfGame

# Skip to Flop
skip_round(round::PreFlop, ::Flop) = true

Check warning on line 196 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L196

Added line #L196 was not covered by tests

# Skip to Turn
skip_round(round::PreFlop, ::Turn) = true
skip_round(round::Flop, ::Turn) = true

Check warning on line 200 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L199-L200

Added lines #L199 - L200 were not covered by tests

# Skip to River
skip_round(round::PreFlop, ::River) = true
skip_round(round::Flop, ::River) = true
skip_round(round::Turn, ::River) = true

function act!(game::Game, round::AbstractRound, sf::StartFrom)
skip_round(round, sf) && return nothing
act_generic!(game, round, sf)
reset_round!(game.table)
end

metafmt(level, _module, group, id, file, line) =
Logging.default_metafmt(level, nothing, group, id, nothing, nothing)

"""
play!(game::Game)
play!(game::Game[, sf::StartFrom])
Play a game.
Optionally, users can pass in a `StartFrom`
option, to start from a game-point, specified
by `sf`.
"""
play!(game::Game) = deal_and_play!(game::Game)
play!(game::Game, sf::StartFrom = StartFrom(StartOfGame())) =
deal_and_play!(game::Game, sf)

"""
deal_and_play!(game::Game)
deal_and_play!(game::Game[, sf::StartFrom])
Deal and play a game.
Optionally, users can pass in a `StartFrom`
option, to start from a game-point, specified
by `sf`.
"""
function deal_and_play!(game::Game)
function deal_and_play!(game::Game, sf::StartFrom = StartFrom(StartOfGame()))
if game.table.logger isa DebugLogger
cl = Logging.ConsoleLogger(stderr,Logging.Debug; meta_formatter=metafmt)
Logging.with_logger(cl) do
_deal_and_play!(game)
_deal_and_play!(game, sf)
end
else
_deal_and_play!(game)
_deal_and_play!(game, sf)
end
end

function _deal_and_play!(game::Game)
function _deal_and_play!(game::Game, sf::StartFrom)
logger = game.table.logger
@cinfo logger "------ Playing game!"

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

local initial_brs
if sf.game_point isa PlayerOption
# Cannot (or, should not) play from a point
# at which a winner has been declared
@assert !winners.declared
end

@cdebug logger begin
initial_brs = deepcopy(bank_roll.(players))
end
initial_∑brs = sum(x->bank_roll(x), players)

@cinfo logger "Initial bank roll summary: $(bank_roll.(players))"
did = dealer_pidx(table)
sb = seat_number(small_blind(table))
bb = seat_number(big_blind(table))
f2a = seat_number(first_to_act(table))
@cinfo logger "Buttons (dealer, small, big, 1ˢᵗToAct): ($did, $sb, $bb, $f2a)"

@assert still_playing(dealer(table)) "The button must be placed on a non-folded player"
@assert still_playing(small_blind(table)) "The small blind button must be placed on a non-folded player"
@assert still_playing(big_blind(table)) "The big blind button must be placed on a non-folded player"
@assert still_playing(first_to_act(table)) "The first-to-act button must be placed on a non-folded player"
if sf.game_point isa StartOfGame
@cinfo logger "------ Playing game!"
set_active_status!(table)
initial_∑brs = sum(x->bank_roll(x), players)

@cinfo logger "Initial bank roll summary: $(bank_roll.(players))"

did = dealer_pidx(table)
sb = seat_number(small_blind(table))
bb = seat_number(big_blind(table))
f2a = seat_number(first_to_act(table))
@cinfo logger "Buttons (dealer, small, big, 1ˢᵗToAct): ($did, $sb, $bb, $f2a)"
@assert still_playing(dealer(table)) "The button must be placed on a non-folded player"
@assert still_playing(small_blind(table)) "The small blind button must be placed on a non-folded player"
@assert still_playing(big_blind(table)) "The big blind button must be placed on a non-folded player"
@assert still_playing(first_to_act(table)) "The first-to-act button must be placed on a non-folded player"
end

@assert dealer_pidx(table) small_blind_pidx(table) "The button and small blind cannot coincide"
@assert small_blind_pidx(table) big_blind_pidx(table) "The small and big blinds cannot coincide"
@assert big_blind_pidx(table) first_to_act_pidx(table) "The big blind and first to act cannot coincide"

reset!(table.transactions, players)

@assert all(p->cards(p) == nothing, players)
@assert cards(table) == nothing
reset_round_bank_rolls!(table) # round bank-rolls must account for blinds
deal!(table, blinds(table))
@assert cards(table) nothing

winners = table.winners
if sf.game_point isa StartOfGame
reset!(table.transactions, players)
@assert all(p->cards(p) == nothing, players)
@assert cards(table) == nothing
reset_round_bank_rolls!(table) # round bank-rolls must account for blinds
deal!(table, blinds(table))
@assert cards(table) nothing
end

winners.declared || act!(game, PreFlop()) # Pre-flop bet/check/raise
winners.declared || act!(game, Flop()) # Deal flop , then bet/check/raise
winners.declared || act!(game, Turn()) # Deal turn , then bet/check/raise
winners.declared || act!(game, River()) # Deal river, then bet/check/raise
winners.declared || act!(game, PreFlop(), sf) # Pre-flop bet/check/raise
winners.declared || act!(game, Flop(), sf) # Deal flop , then bet/check/raise
winners.declared || act!(game, Turn(), sf) # Deal turn , then bet/check/raise
winners.declared || act!(game, River(), sf) # Deal river, then bet/check/raise

distribute_winnings!(players, table.transactions, cards(table), logger)
winners.declared = true
Expand All @@ -240,12 +305,12 @@ function _deal_and_play!(game::Game)
@cdebug logger "initial_brs = $(initial_brs)"
@cdebug logger "bank_roll.(players_at_table(table)) = $(bank_roll.(players_at_table(table)))"

if !(logger isa ByPassLogger)
if !(initial_∑brs == sum(x->bank_roll(x), players_at_table(table)))
@cinfo logger "initial_∑brs=$initial_∑brs, brs=$(bank_roll.(players_at_table(table)))"
end
end
@assert initial_∑brs == sum(x->bank_roll(x), players_at_table(table)) # eventual assertion
# if !(logger isa ByPassLogger)
# if !(initial_∑brs == sum(x->bank_roll(x), players_at_table(table)))
# @cinfo logger "initial_∑brs=$initial_∑brs, brs=$(bank_roll.(players_at_table(table)))"
# end
# end
# @assert initial_∑brs == sum(x->bank_roll(x), players_at_table(table)) # eventual assertion
@assert sum(sp->amount(sp), table.transactions.side_pots) == 0

@cinfo logger "Final bank roll summary: $(bank_roll.(players))"
Expand Down
12 changes: 12 additions & 0 deletions src/goto_player_option.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
abstract type AbstractGamePoint end
struct StartOfGame <: AbstractGamePoint end
struct PlayerOption{P, R, A} <: AbstractGamePoint
player::P
round::R
action::A
end

struct StartFrom{GP}
game_point::GP
end

2 changes: 1 addition & 1 deletion src/player_options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ function player_option(game::Game, player::Player)
action = player_option(game, player, CheckRaiseFold())::Action
validate_action(action, CheckRaiseFold())
end
update_given_valid_action!(table, player, action)
return action
end

# By default, forward to `player_option` with
Expand Down
27 changes: 19 additions & 8 deletions src/transactions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -220,18 +220,29 @@ Base.@propagate_inbounds function sidepot_winnings(tm::TransactionManager, id::I
mapreduce(i->tm.side_pots[i].amt, +, 1:id; init=0)
end

function profit(player, tm)
if not_playing(player)
return -pot_investment(player)
else
n = length(tm.side_pots)
∑spw = sidepot_winnings(tm, n)
return ∑spw-pot_investment(player)
end
end

function distribute_winnings_1_player_left!(players, tm::TransactionManager, table_cards, logger)
@assert count(x->still_playing(x), players) == 1
n = length(tm.side_pots)
for (player, initial_br) in zip(players, tm.initial_brs)
player.game_profit = profit(player, tm)
end
for (player, initial_br) in zip(players, tm.initial_brs)
∑spw = sidepot_winnings(tm, n)
not_playing(player) && continue
amt_contributed = initial_br - bank_roll(player)
∑spw = sidepot_winnings(tm, n)
prof = ∑spw-amt_contributed
player.game_profit = prof
player.bank_roll += ∑spw
if !(∑spw == 0 && player.bank_roll == 0 && amt_contributed == 0)
@cinfo logger "$(name(player)) wins $(∑spw) ($(prof) profit) (all opponents folded)"
@cinfo logger "$(name(player)) wins $(∑spw) ($(profit(player, tm)) profit) (all opponents folded)"
end
@inbounds for j in 1:n
tm.side_pots[j].amt = 0 # empty out distributed winnings
Expand All @@ -255,7 +266,7 @@ end

#=largest pot investment, excluding player=#
function largest_pot_investment(player, players)
lpi = typeof(pot_investment(player))(0)
lpi = 0
for i in 1:length(players)
seat_number(players[i]) == seat_number(player) && continue
lpi = max(lpi, pot_investment(players[i]))
Expand Down Expand Up @@ -368,6 +379,9 @@ function distribute_winnings!(players, tm::TransactionManager, table_cards, logg
for (player, initial_br, player_winnings) in zip(players, tm.initial_brs, tm.side_pot_winnings)
∑spw = sum(player_winnings)
ssn = tm.unsorted_to_sorted_map[seat_number(player)]
amt_contributed = initial_br - bank_roll(player)
prof = profit(player, tm)
player.game_profit = prof
if ∑spw == 0 && active(player)
@cinfo logger begin
she = sorted_hand_evals[ssn]
Expand All @@ -378,8 +392,6 @@ function distribute_winnings!(players, tm::TransactionManager, table_cards, logg
end
else
@cdebug logger "$(name(player))'s side-pot wins: $(player_winnings)!"
amt_contributed = initial_br - bank_roll(player)
prof = ∑spw-amt_contributed
if amt_contributed == 0 && ∑spw == 0 && prof == 0 && !still_playing(player)
continue
end
Expand All @@ -389,7 +401,6 @@ function distribute_winnings!(players, tm::TransactionManager, table_cards, logg
bc = she.best_cards
"$(name(player)) wins $∑spw ($prof profit) with $bc ($hand_name)!"
end
player.game_profit = prof
player.bank_roll += ∑spw
end
end
Expand Down
Loading

0 comments on commit f78309e

Please sign in to comment.