From ee26fcd4e8ab0417e3c91d35ec55423b21bd7a28 Mon Sep 17 00:00:00 2001 From: Charles Kawczynski Date: Thu, 31 Aug 2023 08:56:57 -0700 Subject: [PATCH] Use vector cards instead of tuple cards wip --- src/game.jl | 13 +++++++------ src/player_type.jl | 10 +++++++--- src/recreate.jl | 20 ++++++++------------ src/table.jl | 27 +++++++++++++++------------ src/terminal/ascii_card.jl | 9 +++++++-- src/terminal/ui.jl | 2 +- src/transactions.jl | 17 ++++++++++------- test/Project.toml | 1 + test/runtests.jl | 3 +++ test/table.jl | 11 +++++++---- test/tournament.jl | 16 ++++++++++++++++ 11 files changed, 82 insertions(+), 47 deletions(-) create mode 100644 test/tournament.jl diff --git a/src/game.jl b/src/game.jl index 04a6fd88..9fcddfe8 100644 --- a/src/game.jl +++ b/src/game.jl @@ -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!" @@ -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 @@ -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", @@ -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 diff --git a/src/player_type.jl b/src/player_type.jl index 783c154d..7e77d627 100644 --- a/src/player_type.jl +++ b/src/player_type.jl @@ -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 @@ -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 @@ -85,7 +85,7 @@ function Player(strategy, seat_number = -1, cards = (nothing,nothing); bank_roll args = ( strategy, seat_number, - cards, + Card[cards...], Chips(bank_roll), Chips(game_profit), action_required, @@ -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 diff --git a/src/recreate.jl b/src/recreate.jl index ed8e34b8..928aeebd 100644 --- a/src/recreate.jl +++ b/src/recreate.jl @@ -3,35 +3,31 @@ 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)) + player.cards = [SB.sample!(table.deck), SB.sample!(table.deck)] return nothing end function resample_cards!(game::Game, player::Player) diff --git a/src/table.jl b/src/table.jl index c84b6ec3..1f19e30a 100644 --- a/src/table.jl +++ b/src/table.jl @@ -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 @@ -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(), @@ -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...], blinds, pot, round, @@ -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 @@ -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) @@ -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 diff --git a/src/terminal/ascii_card.jl b/src/terminal/ascii_card.jl index 29f731de..ce09a6e6 100644 --- a/src/terminal/ascii_card.jl +++ b/src/terminal/ascii_card.jl @@ -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 @@ -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 diff --git a/src/terminal/ui.jl b/src/terminal/ui.jl index 1a3a6a18..c825b7ae 100644 --- a/src/terminal/ui.jl +++ b/src/terminal/ui.jl @@ -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 diff --git a/src/transactions.jl b/src/transactions.jl index e236f2a1..99962297 100644 --- a/src/transactions.jl +++ b/src/transactions.jl @@ -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 """ @@ -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 diff --git a/test/Project.toml b/test/Project.toml index 2ce7b351..cb17a87e 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -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" diff --git a/test/runtests.jl b/test/runtests.jl index ba13fe91..7384f2c0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 diff --git a/test/table.jl b/test/table.jl index 66eba7c8..43282d22 100644 --- a/test/table.jl +++ b/test/table.jl @@ -1,6 +1,8 @@ using Test using PlayingCards import Logging +import StatsBase +const SB = StatsBase using TexasHoldem using TexasHoldem: seat_number const TH = TexasHoldem @@ -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 diff --git a/test/tournament.jl b/test/tournament.jl new file mode 100644 index 00000000..a2d267e7 --- /dev/null +++ b/test/tournament.jl @@ -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