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 9, 2023
1 parent 505a6f3 commit dae85b2
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 61 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
178 changes: 117 additions & 61 deletions src/game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -120,116 +120,172 @@ function end_preflop_actions(table::Table, player::Player, ::PreFlop)
return all((cond1, cond2, cond3))
end

function act_generic!(game::Game, round::AbstractRound)
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
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 sf.game_point isa PlayerOption
# @debug logger "not player psn: $(seat_number(player))"
# @debug logger "not player gpsn: $(seat_number(sf.game_point.player))"
# if seat_number(sf.game_point.player) ≠ seat_number(player)
# # TODO: add check for infinite loop
# @debug logger "not player psn: $(seat_number(player))"
# @debug logger "not player gpsn: $(seat_number(sf.game_point.player))"
# continue
# else
# @debug logger "Success! Found dreamer"
# end
# end
if sf.game_point isa StartOfGame
@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)
end
if end_of_actions(table, player)
break
end
all_in(player) && continue
@cdebug logger "$(name(player))'s turn to act"
if sf.game_point isa StartOfGame
all_in(player) && continue
@cdebug logger "$(name(player))'s turn to act"
end
# end
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 187 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L187

Added line #L187 was not covered by tests

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

Check warning on line 191 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L190-L191

Added lines #L190 - L191 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 +296,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
11 changes: 11 additions & 0 deletions src/goto_player_option.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
abstract type AbstractGamePoint end
struct StartOfGame <: AbstractGamePoint end
struct PlayerOption{P, R} <: AbstractGamePoint
player::P
round::R
end

struct StartFrom{GP}
game_point::GP
end

64 changes: 64 additions & 0 deletions test/goto_player_option.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Test
using PlayingCards
using TexasHoldem
import TexasHoldem
const TH = TexasHoldem

include("tester_bots.jl")

##### RiverDreamer
mutable struct RiverDreamer <: AbstractStrategy
fixed::Bool
end

TH.player_option(game::Game, player::Player{RiverDreamer}, ::AGS, ::CheckRaiseFold) = Check()
TH.player_option(game::Game, player::Player{RiverDreamer}, ::AGS, ::CallRaiseFold) = Call(game, player)
TH.player_option(game::Game, player::Player{RiverDreamer}, ::AGS, ::CallAllInFold) = Call(game, player)
TH.player_option(game::Game, player::Player{RiverDreamer}, ::AGS, ::CallFold) = Call(game, player)

function TH.player_option(game::Game, player::Player{RiverDreamer}, round::River, option::CheckRaiseFold)
if player.strategy.fixed
Check()
else
player.strategy.fixed = true
options = (Check(), Raise(10), Raise(40), Fold())
rewards = map(options) do option
@info "Recreating game from $option"
rgame = TH.recreate_game(game, player)
sf = TH.StartFrom(TH.PlayerOption(player, round))
@info "Playing new game"
play!(rgame, sf)
@info "Finished new game"
@info "Computing pidx after new game"
pidx = findfirst(rgame.table.players) do p
TH.seat_number(p) == TH.seat_number(player)
end
@info "Finished computing pidx after new game"
@info "reward: $(rgame.table.players[pidx].game_profit)"
rgame.table.players[pidx].game_profit
end
@show rewards
return Check()
end
end
function TH.player_option(game::Game, player::Player{RiverDreamer}, round::River, option::CallRaiseFold)
@info "Recreating game from $option"
rgame = TH.recreate_game(game, player)
Call(game, player)
end
function TH.player_option(game::Game, player::Player{RiverDreamer}, round::River, option::CallAllInFold)
@info "Recreating game from $option"
rgame = TH.recreate_game(game, player)
Call(game, player)
end
function TH.player_option(game::Game, player::Player{RiverDreamer}, round::River, option::CallFold)
@info "Recreating game from $option"
rgame = TH.recreate_game(game, player)
Call(game, player)
end

@testset "Game: Play (Bot5050 vs RiverDreamer)" begin
fuzz_bots = ntuple(i->Player(Bot5050(), i), 3)
players = (fuzz_bots..., Player(RiverDreamer(false), length(fuzz_bots)+1))
play!(Game(players; logger=TH.DebugLogger()))
end
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ end
@safetestset "recreate" begin
Δt = @elapsed include("recreate.jl"); @info "Completed tests for recreate in $Δt seconds"
end
@safetestset "goto player option" begin
Δt = @elapsed include("goto_player_option.jl"); @info "Completed tests for goto_player_option in $Δt seconds"
end
@safetestset "play" begin
Δt = @elapsed include("play.jl"); @info "Completed tests for play in $Δt seconds"
end
Expand Down

0 comments on commit dae85b2

Please sign in to comment.