Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add capability to goto player option #196

Merged
merged 2 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TexasHoldem"
uuid = "6cef90fc-eb55-4a2a-97d0-7ecce2b738fe"
authors = ["Charles Kawczynski <kawczynski.charles@gmail.com>"]
version = "0.3.1"
version = "0.3.2"

[deps]
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Expand Down
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
198 changes: 134 additions & 64 deletions src/game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -120,116 +120,184 @@
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
# need a bool so that we pick the recreated
# action (in `sf.game_point.action`) once,
# and then continue with `player_option`.
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 199 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L199

Added line #L199 was not covered by tests

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

Check warning on line 203 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L202-L203

Added lines #L202 - L203 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 +308,14 @@
@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)))"
if sf.game_point isa StartOfGame
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)))"

Check warning on line 314 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L313-L314

Added lines #L313 - L314 were not covered by tests
end
end
@assert initial_∑brs == sum(x->bank_roll(x), players_at_table(table)) # eventual assertion
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
Loading