Skip to content

Commit

Permalink
Merge #230
Browse files Browse the repository at this point in the history
230: Use vector cards instead of tuple cards r=charleskawczynski a=charleskawczynski

This PR changes the Table and Players `cards` member from `Union{..., Tuple{Card,...}}`to `Vector{Card}`. The main reason this is useful is because we do mutate these cards during games (e.g., during deal), so it's a bit more ergonomic to have them be vectors than tuples.

Co-authored-by: Charles Kawczynski <kawczynski.charles@gmail.com>
  • Loading branch information
bors[bot] and charleskawczynski committed Sep 3, 2023
2 parents f9e3fb4 + e2a0854 commit 9193644
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 47 deletions.
13 changes: 7 additions & 6 deletions src/game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ function _deal_and_play!(game::Game, sf::StartFrom)
end

if sf.game_point isa StartOfGame
for (pidx, player) in enumerate(players)
@inbounds for (pidx, player) in enumerate(players)
initial_brs[pidx] = bank_roll_chips(player)
end
@cinfo logger "------ Playing game!"
Expand All @@ -317,8 +317,8 @@ function _deal_and_play!(game::Game, sf::StartFrom)

if sf.game_point isa StartOfGame
reset!(table.transactions, players)
@assert all(p->cards(p) == (nothing,nothing), players)
@assert cards(table) == nothing
@assert all(p->!has_cards(p), players)
@assert all(c->c==joker, cards(table))
reset_round_bank_rolls!(table) # round bank-rolls must account for blinds
deal!(table, blinds(table))
@assert cards(table) nothing
Expand Down Expand Up @@ -351,12 +351,11 @@ function _deal_and_play!(game::Game, sf::StartFrom)
for (player, initial_br) in zip(players, initial_brs)
mpp = max_possible_profit(player, players, initial_brs)
prof = bank_roll_chips(player) - initial_br
br = map(x->bank_roll_chips(x), players)
# TODO: this is broken due to https://github.com/charleskawczynski/TexasHoldem.jl/issues/200
@assert prof mpp string("Over-winning occurred:\n",
" Player $(name(player))\n",
" Initial BRs $(initial_brs)\n",
" BRs $br\n",
" BRs $(map(x->bank_roll_chips(x), players))\n",
" profit $prof\n",
" profit.n $(prof.n)\n",
" cond $(prof.n mpp.n)\n",
Expand Down Expand Up @@ -403,7 +402,9 @@ function reset_game!(game::Game)
table = game.table
players = players_at_table(table)
for player in players
player.cards = (nothing,nothing)
@inbounds for j in 1:2
player.cards[j] = joker
end
player.pot_investment = 0
player.game_profit = Chips(0)
player.all_in = false
Expand Down
10 changes: 7 additions & 3 deletions src/player_type.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ for flow control logic.
mutable struct Player{S #=<: AbstractStrategy=#}
strategy::S
seat_number::Int
cards::NTuple{2,Union{Card,Nothing}}
cards::Vector{Card}
bank_roll::Chips
game_profit::Chips
action_required::Bool
Expand All @@ -70,7 +70,7 @@ function Base.show(io::IO, player::Player)
print(io, "$(name(player)): $(player.cards)")
end

function Player(strategy, seat_number = -1, cards = (nothing,nothing); bank_roll = 200)
function Player(strategy, seat_number = -1, cards = Card[joker, joker]; bank_roll = 200)
action_required = true
all_in = false
round_bank_roll = bank_roll
Expand All @@ -85,7 +85,7 @@ function Player(strategy, seat_number = -1, cards = (nothing,nothing); bank_roll
args = (
strategy,
seat_number,
cards,
Card[cards[1], cards[2]],
Chips(bank_roll),
Chips(game_profit),
action_required,
Expand Down Expand Up @@ -154,5 +154,9 @@ round_contribution(player::Player) = player.round_contribution
strategy(player::Player) = player.strategy
pot_eligible(player::Player) = !folded(player) && still_playing(player) && active(player)
pot_eligible(player::Nothing) = false
function has_cards(player::Player)
@assert length(cards(player)) == 2
return all(c->c == joker, cards(player)) ? false : true
end

notify_reward(player) = nothing
22 changes: 10 additions & 12 deletions src/recreate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,33 @@ function resample_unobserved_table_cards!(table::Table, round::PreFlop)
for c in table.cards
PlayingCards.restore!(table.deck, c)
end
table.cards = ntuple(i->SB.sample!(table.deck), 5)
@inbounds for j in 1:5
table.cards[j] = SB.sample!(table.deck)
end
return nothing
end
function resample_unobserved_table_cards!(table::Table, round::Flop)
@inbounds PlayingCards.restore!(table.deck, table.cards[4])
@inbounds PlayingCards.restore!(table.deck, table.cards[5])
@inbounds table.cards = (
table.cards[1:3]...,
SB.sample!(table.deck),
SB.sample!(table.deck),
)
@inbounds table.cards[4] = SB.sample!(table.deck)
@inbounds table.cards[5] = SB.sample!(table.deck)
return nothing
end
function resample_unobserved_table_cards!(table::Table, round::Turn)
@inbounds PlayingCards.restore!(table.deck, table.cards[5])
@inbounds table.cards = (
table.cards[1:4]...,
SB.sample!(table.deck),
)
@inbounds table.cards[5] = SB.sample!(table.deck)
return nothing
end
resample_unobserved_table_cards!(table::Table, round::River) = nothing

function resample_player_cards!(table::Table, player::Player)
@assert player.cards (nothing, nothing)
@assert has_cards(player)
for c in player.cards
PlayingCards.restore!(table.deck, c)
end
player.cards = (SB.sample!(table.deck), SB.sample!(table.deck))
@inbounds for j in 1:2
player.cards[j] = SB.sample!(table.deck)
end
return nothing
end
function resample_cards!(game::Game, player::Player)
Expand Down
27 changes: 15 additions & 12 deletions src/table.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ buttons(b::Buttons) = (
mutable struct Table{P<:Players, L, TM, B <: Blinds, D <: PlayingCards.AbstractDeck, G}
deck::D
players::P
cards::Union{Nothing,Tuple{<:Card,<:Card,<:Card,<:Card,<:Card}}
cards::Vector{Card}
blinds::B
pot::Int
round::AbstractRound
Expand Down Expand Up @@ -100,7 +100,7 @@ n_raises(i, n_players) = Int(floor(i/n_players))
Table(players; kwargs...) = Table(Players(players); kwargs...)
function Table(players::Players;
deck = PlayingCards.MaskedDeck(),
cards = nothing,
cards = Card[joker, joker, joker, joker, joker],
gui = PlainLogger(), # good for test/debugging, but not very fun
# gui = Terminal(), # fun, but not good for tests/debugging
blinds = Blinds(),
Expand All @@ -124,7 +124,7 @@ function Table(players::Players;
B = typeof(blinds)
return Table{P, L, TM, B, typeof(deck), typeof(gui)}(deck,
players,
cards,
Card[cards[1],cards[2],cards[3],cards[4],cards[5]],
blinds,
pot,
round,
Expand Down Expand Up @@ -175,27 +175,26 @@ function Base.show(io::IO, table::Table, include_type = true)
println(io, "Observed cards = $(observed_cards(table))")
end

get_table_cards!(deck::PlayingCards.MaskedDeck) = ntuple(_->SB.sample!(deck), Val(5))::Tuple{Card, Card, Card, Card, Card}
cards(table::Table) = table.cards

observed_cards(table::Table) = observed_cards(table, round(table))
observed_cards(table::Table, ::PreFlop) = ()
observed_cards(table::Table, ::PreFlop) = Card[]
observed_cards(table::Table, ::Flop) = table.cards[1:3]
observed_cards(table::Table, ::Turn) = table.cards[1:4]
observed_cards(table::Table, ::River) = table.cards

observed_cards_all(table::Table) = observed_cards_all(table, round(table))
observed_cards_all(table::Table, ::PreFlop) = ntuple(_->nothing, 5)
observed_cards_all(table::Table, ::Flop) = (table.cards[1:3]..., nothing, nothing)
observed_cards_all(table::Table, ::Turn) = (table.cards[1:4]..., nothing)
observed_cards_all(table::Table, ::PreFlop) = Card[joker,joker,joker,joker,joker]
observed_cards_all(table::Table, ::Flop) = Card[table.cards[1:3]..., joker, joker]
observed_cards_all(table::Table, ::Turn) = Card[table.cards[1:4]..., joker]
observed_cards_all(table::Table, ::River) = table.cards

# for testing
unobserved_cards(table::Table) = unobserved_cards(table, round(table))
unobserved_cards(table::Table, ::PreFlop) = table.cards
unobserved_cards(table::Table, ::Flop) = table.cards[4:5]
unobserved_cards(table::Table, ::Turn) = (table.cards[5],)
unobserved_cards(table::Table, ::River) = ()
unobserved_cards(table::Table, ::Turn) = Card[table.cards[5]]
unobserved_cards(table::Table, ::River) = Card[]

current_raise_amt(table::Table) = table.current_raise_amt
initial_round_raise_amt(table::Table) = table.initial_round_raise_amt
Expand Down Expand Up @@ -480,7 +479,9 @@ function deal!(table::Table, blinds::Blinds)

not_playing(player) && continue

player.cards = ntuple(_->SB.sample!(table.deck), 2)::Tuple{Card, Card}
for j in 1:2
player.cards[j] = SB.sample!(table.deck)
end

if is_small_blind(table, player) && bank_roll(player) blinds.small
contribute!(table, player, bank_roll(player), call_blinds)
Expand All @@ -501,7 +502,9 @@ function deal!(table::Table, blinds::Blinds)
end
end

table.cards = get_table_cards!(table.deck)::Tuple{Card,Card,Card,Card,Card}
@inbounds for j in 1:5
table.cards[j] = SB.sample!(table.deck)
end
@cinfo logger "Table cards dealt (face-down)."
@cdebug logger "Post-blinds bank roll summary: $(bank_roll.(players))"
end
Expand Down
9 changes: 7 additions & 2 deletions src/terminal/ascii_card.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ using PlayingCards
ASCII cards.
"""
# ascii_card(cards...; kwargs...) = ascii_card(cards; kwargs...)
function ascii_card(cards::NTuple;to_string=true, rbuffer=" ")
function ascii_card(cards; to_string=true, rbuffer=" ")
lines = map(enumerate(cards)) do (i, c)
_rbuffer = iseven(i) ? rbuffer : ""
if c isa Card
Expand Down Expand Up @@ -50,8 +50,13 @@ cards of type `card::Nothing`.
"""
function ascii_card_dealer(cards; to_string=true)
all_lines = map(_->"", 1:9)
kwargs = (;to_string=false, rbuffer="")
for card in cards
card_lines = ascii_card((card,); to_string=false, rbuffer="")
if card == joker
card_lines = ascii_card((nothing,); kwargs...)
else
card_lines = ascii_card((card,); kwargs...)
end
all_lines = [x * y for (x, y) in zip(all_lines, card_lines)]
end
card_str = to_string ? join(all_lines, "\n") : all_lines
Expand Down
2 changes: 1 addition & 1 deletion src/terminal/ui.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function update_gui(io::IO, table::Table, ::Terminal, pov_player)

visible_player_cards = map(players) do player
if hide_card(table.winners, pov_player, player)
(nothing, nothing)
[nothing, nothing]
else
cards(player)
end
Expand Down
17 changes: 10 additions & 7 deletions src/transactions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const joker = Card(0, 1)
Base.@kwdef mutable struct HandEval
hand_rank::Int = 1
hand_type::Symbol = :empty
best_cards::NTuple{5,Card} = ntuple(_->joker, 5)
best_cards::Vector{Card} = Card[joker,joker,joker,joker,joker]
end

"""
Expand Down Expand Up @@ -272,19 +272,22 @@ function distribute_winnings!(players, tm::TransactionManager, table_cards, logg
if !pot_eligible(player)
sorted_hand_evals[ssn].hand_rank = -1
sorted_hand_evals[ssn].hand_type = :empty
sorted_hand_evals[ssn].best_cards = ntuple(j->joker, 5)
@inbounds for i in 1:5
sorted_hand_evals[ssn].best_cards[i] = joker
end
else
pc = player.cards::Tuple{Card,Card}
tc = table_cards::Tuple{Card,Card,Card,Card,Card}
pc = player.cards
tc = table_cards
@inbounds all_cards = (pc[1], pc[2], tc[1], tc[2], tc[3], tc[4], tc[5])
if logger isa ByPassLogger
he = PHE.CompactHandEval((pc..., tc...))
he = PHE.CompactHandEval(all_cards)
sorted_hand_evals[ssn].hand_rank = PHE.hand_rank(he)
sorted_hand_evals[ssn].hand_type = PHE.hand_type(he)
else
he = PHE.FullHandEval((pc..., tc...))
he = PHE.FullHandEval(all_cards)
sorted_hand_evals[ssn].hand_rank = PHE.hand_rank(he)
sorted_hand_evals[ssn].hand_type = PHE.hand_type(he)
sorted_hand_evals[ssn].best_cards = PHE.best_cards(he)
sorted_hand_evals[ssn].best_cards .= Card[PHE.best_cards(he)...]
end
end
end
Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
TerminalRegressionTests = "98bfdc55-cc95-5876-a49a-74609291cbe0"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ end
@safetestset "play" begin
Δt = @elapsed include("play.jl"); @info "Completed tests for play in $Δt seconds"
end
@safetestset "tournament" begin
Δt = @elapsed include("tournament.jl"); @info "Completed tests for tournament in $Δt seconds"
end
@safetestset "fuzz_play" begin
Δt = @elapsed include("fuzz_play.jl"); @info "Completed tests for fuzz_play in $Δt seconds"
end
Expand Down
11 changes: 7 additions & 4 deletions test/table.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Test
using PlayingCards
import Logging
import StatsBase
const SB = StatsBase
using TexasHoldem
using TexasHoldem: seat_number
const TH = TexasHoldem
Expand All @@ -11,22 +13,23 @@ const TH = TexasHoldem
# we use StatsBase.sample! for efficiency, but shuffle! is convenient
shuffle!(deck)
blinds = TH.Blinds(1,2)
cards = TH.get_table_cards!(deck)
cards = map(x->SB.sample!(deck), 1:5)

table = TH.Table(players;deck=deck, cards=cards, logger=TH.ByPassLogger())
TH.deal!(table, blinds)

table.round = PreFlop()
@test TH.observed_cards(table) == ()
@test isempty(TH.observed_cards(table))
@test TH.unobserved_cards(table) == table.cards
table.round = Flop()
@test TH.observed_cards(table) == table.cards[1:3]
@test TH.unobserved_cards(table) == table.cards[4:5]
table.round = Turn()
@test TH.observed_cards(table) == table.cards[1:4]
@test TH.unobserved_cards(table) == (table.cards[5],)
@test TH.unobserved_cards(table) == [table.cards[5]]
table.round = River()
@test TH.observed_cards(table) == table.cards
@test TH.unobserved_cards(table) == ()
@test isempty(TH.unobserved_cards(table))
end

@testset "Table: Move button" begin
Expand Down
16 changes: 16 additions & 0 deletions test/tournament.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Test
using PlayingCards
using TexasHoldem
import TexasHoldem
import Random
const TH = TexasHoldem
import Random

include("tester_bots.jl")
QuietGame(args...; kwargs...) = Game(args...; kwargs..., logger=TH.ByPassLogger())

@testset "Tournament (BotCheckCall)" begin
players = ntuple(i->Player(BotCheckCall();bank_roll=6), 2)
game = QuietGame(players)
tournament!(game)
end

0 comments on commit 9193644

Please sign in to comment.