diff --git a/README.md b/README.md index ac600096..a15ce829 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,90 @@ [bors-img]: https://bors.tech/images/badge_small.svg [bors-url]: https://app.bors.tech/repositories/32732 -An experimental package for simulating No Limit Holdem Poker. We're not registered yet, so install with +An experimental package for simulating no-limit Texas Holdem Poker. We're not registered yet, so install with ```julia-repl (@v1.x) pkg> add https://github.com/charleskawczynski/TexasHoldem.jl ``` -And try it out with: +# Playing + +Games can be played with: ```julia using TexasHoldem play(configure_game()) ``` + +# Creating your own bot + +Four methods (variants of `player_option!`) need to be defined to create and play your own bot: + +```julia +using TexasHoldem +import TexasHoldem +TH = TexasHoldem + +struct MyBot <: AbstractAI end + +function TH.player_option!(game::Game, player::Player{MyBot}, ::AbstractGameState, ::CheckRaiseFold) + # options are: + # check!(game, player) + # raise!(game, player, amt::Float64) + # raise_all_in!(game, player) + # fold!(game, player) + if rand() < 0.5 + check!(game, player) + else + amt = Int(round(rand()*bank_roll(player), digits=0)) + amt = TH.bound_raise(game.table, player, amt) # to properly bound raise amount + raise_to!(game, player, amt) + end +end +function TH.player_option!(game::Game, player::Player{MyBot}, ::AbstractGameState, ::CallRaiseFold) + # options are: + # call!(game, player) + # raise!(game, player, amt::Float64) + # raise_all_in!(game, player) + # fold!(game, player) + if rand() < 0.5 + if rand() < 0.5 # Call + call!(game, player) + else # re-raise + amt = Int(round(rand()*bank_roll(player), digits=0)) + amt = TH.bound_raise(game.table, player, amt) # to properly bound raise amount + raise_to!(game, player, amt) + end + else + fold!(game, player) + end +end +function TH.player_option!(game::Game, player::Player{MyBot}, ::AbstractGameState, ::CallAllInFold) + # options are: + # call!(game, player) + # raise_all_in!(game, player) + # fold!(game, player) + if rand() < 0.5 + if rand() < 0.5 # Call + call!(game, player) + else # re-raise + raise_all_in!(game, player) + end + else + fold!(game, player) + end +end +function TH.player_option!(game::Game, player::Player{MyBot}, ::AbstractGameState, ::CallFold) + # options are: + # call!(game, player) + # fold!(game, player) + if rand() < 0.5 + call!(game, player) + else + fold!(game, player) + end +end + +# Heads-up against the MyBot! +play(Game((Player(Human(), 1), Player(MyBot(), 2)))) +``` \ No newline at end of file diff --git a/src/TexasHoldem.jl b/src/TexasHoldem.jl index eb67c2a0..2e9dd7c2 100644 --- a/src/TexasHoldem.jl +++ b/src/TexasHoldem.jl @@ -1,7 +1,7 @@ """ TexasHoldem -A no-limit hold-em simulator. +A no-limit Texas Holdem simulator. # Terminology - `game` a single "game", where players are dealt hands, @@ -19,7 +19,7 @@ using PokerHandEvaluator.HandTypes using UnPack using Printf -export PreFlop, Flop, Turn, River +export AbstractGameState, PreFlop, Flop, Turn, River abstract type AbstractGameState end struct PreFlop <: AbstractGameState end diff --git a/src/game.jl b/src/game.jl index 9d2aef48..43ab3c25 100644 --- a/src/game.jl +++ b/src/game.jl @@ -26,7 +26,7 @@ function Game(players::Tuple; n_player_cards = sum(map(x->cards(x)==nothing ? 0 : length(cards(x)), players)) - @assert 2 ≤ length(players) ≤ 10 + @assert 2 ≤ length(players) ≤ 10 "Invalid number of players" if length(deck) ≠ 52 # if the deck isn't full, then players should have been dealt cards. @@ -57,6 +57,7 @@ end players_at_table(game::Game) = players_at_table(game.table) blinds(game::Game) = blinds(game.table) any_actions_required(game::Game) = any_actions_required(game.table) +state(game::Game) = state(game.table) print_new_cards(table, state::PreFlop) = nothing print_new_cards(table, state::Flop) = @info "Flop: $(repeat(" ", 44)) $(table.cards[1:3])" diff --git a/src/player_actions.jl b/src/player_actions.jl index 83ce49fb..5e93d08e 100644 --- a/src/player_actions.jl +++ b/src/player_actions.jl @@ -44,12 +44,19 @@ end ##### Call ##### +function call_amount(table::Table, player::Player) + cra = table.current_raise_amt + prc = player.round_contribution + cra ≈ 0 && (@assert prc ≈ 0) + call_amt = cra - prc + @debug "cra = $cra, prc = $prc, call_amt = $call_amt" + return call_amt +end + call!(game::Game, player::Player) = call!(game.table, player) function call!(table::Table, player::Player) - cra = table.current_raise_amt - pc = player.round_contribution - call_amt = cra - pc + call_amt = call_amount(table, player) if call_amt ≤ bank_roll(player) call_valid_amount!(table, player, call_amt) else @@ -87,6 +94,28 @@ function bound_raise(table::Table, player::Player, amt) return amt end +""" + valid_raise_bounds(table::Table, player::Player) + +A tuple of valid raise bounds. Note that +all-in is the only option if both elements +are equal. +""" +function valid_raise_bounds(table::Table, player::Player) + cra = table.current_raise_amt + rbr = round_bank_roll(player) + if cra ≈ 0 # initial raise + vrb = (blinds(table).small, rbr) + else # re-raise + if rbr > 2*cra + vrb = (2*cra, rbr) + else + vrb = (rbr, rbr) + end + end + return vrb +end + # TODO: add assertion that raise amount must be # greater than small blind (unless all-in). """ @@ -100,28 +129,19 @@ function valid_raise_amount(table::Table, player::Player, amt) @assert !(amt ≈ 0) @assert amt ≤ round_bank_roll(player) cra = table.current_raise_amt - pc = player.round_contribution - br = round_bank_roll(player) - if cra ≈ 0 # initial raise - rb = (blinds(table).small, br) # raise bounds - else # re-raise - if br > 2*cra - rb = (2*cra, br) # raise bounds - else - rb = (br, br) # raise bounds - end - end - # @assert amt_required_to_call > 0 # right? - @debug "Attempting to raise to \$$(amt), already contributed \$$(pc). Valid raise bounds: [\$$(rb[1]), \$$(rb[2])]" - if !(rb[1] ≤ amt ≤ rb[2] || amt ≈ rb[1] ≈ rb[2]) + prc = player.round_contribution + rbr = round_bank_roll(player) + vrb = valid_raise_bounds(table, player) + @debug "Attempting to raise to \$$(amt), already contributed \$$(prc). Valid raise bounds: [\$$(vrb[1]), \$$(vrb[2])]" + if !(vrb[1] ≤ amt ≤ vrb[2] || amt ≈ vrb[1] ≈ vrb[2]) @debug "cra = $cra" @debug "amt = $amt" - @debug "br = $br" - @debug "amt ≈ br = $(amt ≈ br)" - @debug "2*cra ≤ amt ≤ br = $(2*cra ≤ amt ≤ br)" + @debug "rbr = $rbr" + @debug "amt ≈ rbr = $(amt ≈ rbr)" + @debug "2*cra ≤ amt ≤ rbr = $(2*cra ≤ amt ≤ rbr)" end - @assert rb[1] ≤ amt ≤ rb[2] || amt ≈ rb[1] ≈ rb[2] - @assert amt - pc > 0 # contribution amount must be > 0! + @assert vrb[1] ≤ amt ≤ vrb[2] || amt ≈ vrb[1] ≈ vrb[2] + @assert amt - prc > 0 # contribution amount must be > 0! return amt end @@ -153,8 +173,8 @@ raise_to!(table::Table, player::Player, amt) = function raise_to_valid_raise_amount!(table::Table, player::Player, amt) @debug "$(name(player)) raising to $(amt)." - pc = player.round_contribution - contribute!(table, player, amt - pc, false) + prc = player.round_contribution + contribute!(table, player, amt - prc, false) table.current_raise_amt = amt push!(player.action_history, Raise(amt)) @@ -173,3 +193,7 @@ function raise_to_valid_raise_amount!(table::Table, player::Player, amt) @info "$(name(player)) raised to $(amt)." end end + +raise_all_in!(game::Game, player::Player) = raise_all_in!(game.table, player) +raise_all_in!(table::Table, player::Player) = + raise_to!(table, player, round_bank_roll(player)) diff --git a/src/player_options.jl b/src/player_options.jl index cf3df581..9923af4d 100644 --- a/src/player_options.jl +++ b/src/player_options.jl @@ -1,36 +1,40 @@ +export SitDownSitOut, + CheckRaiseFold, + CallRaiseFold, + CallAllInFold, + CallFold + abstract type PlayerOptions end struct CheckRaiseFold <: PlayerOptions end struct CallRaiseFold <: PlayerOptions end -struct CallAllInFold <: PlayerOptions end # TODO: maybe useful? +struct CallAllInFold <: PlayerOptions end struct CallFold <: PlayerOptions end -struct PayBlindSitOut <: PlayerOptions end +struct SitDownSitOut <: PlayerOptions end function player_option!(game::Game, player::Player) - cra = game.table.current_raise_amt - pc = player.round_contribution - cra ≈ 0 && (@assert pc ≈ 0) - call_amt = cra - pc - @debug "player_option! cra = $cra, pc = $pc, call_amt = $call_amt, !(call_amt ≈ 0) = $(!(call_amt ≈ 0))" + table = game.table + call_amt = call_amount(table, player) + game_state = state(table) if !(call_amt ≈ 0) - pc = player.round_contribution - amt_required_to_call = cra-pc # (i.e., call amount) - raise_possible = bank_roll(player) > amt_required_to_call + raise_possible = bank_roll(player) > call_amt @debug "raise_possible = $raise_possible" if raise_possible # all-in or fold - player_option!(game, player, CallRaiseFold()) + player_option!(game, player, game_state, CallRaiseFold()) else - player_option!(game, player, CallFold()) + player_option!(game, player, game_state, CallFold()) end else - player_option!(game, player, CheckRaiseFold()) + player_option!(game, player, game_state, CheckRaiseFold()) end end +player_option(player::Player, ::SitDownSitOut) = PayBlind() + ##### ##### Human player options (ask via prompts) ##### -function player_option(player::Player{Human}, ::PayBlindSitOut) +function player_option(player::Player{Human}, ::SitDownSitOut) options = ["Pay blind", "Sit out a hand"] menu = RadioMenu(options, pagesize=4) choice = request("$(name(player))'s turn to act:", menu) @@ -38,7 +42,7 @@ function player_option(player::Player{Human}, ::PayBlindSitOut) choice == 1 && return PayBlind() choice == 2 && return SitOut() end -function player_option!(game::Game, player::Player{Human}, ::CheckRaiseFold) +function player_option!(game::Game, player::Player{Human}, ::AbstractGameState, ::CheckRaiseFold) options = ["Check", "Raise", "Fold"] menu = RadioMenu(options, pagesize=4) choice = request("$(name(player))'s turn to act:", menu) @@ -47,7 +51,7 @@ function player_option!(game::Game, player::Player{Human}, ::CheckRaiseFold) choice == 2 && raise_to!(game, player, input_raise_amt(game.table, player)) choice == 3 && fold!(game, player) end -function player_option!(game::Game, player::Player{Human}, ::CallRaiseFold) +function player_option!(game::Game, player::Player{Human}, ::AbstractGameState, ::CallRaiseFold) options = ["Call", "Raise", "Fold"] menu = RadioMenu(options, pagesize=4) choice = request("$(name(player))'s turn to act:", menu) @@ -56,7 +60,7 @@ function player_option!(game::Game, player::Player{Human}, ::CallRaiseFold) choice == 2 && raise_to!(game, player, input_raise_amt(game.table, player)) choice == 3 && fold!(game, player) end -function player_option!(game::Game, player::Player{Human}, ::CallFold) +function player_option!(game::Game, player::Player{Human}, ::AbstractGameState, ::CallFold) options = ["Call", "Fold"] menu = RadioMenu(options, pagesize=4) choice = request("$(name(player))'s turn to act:", menu) @@ -89,27 +93,23 @@ end ##### BotSitOut -player_option(player::Player{BotSitOut}, ::PayBlindSitOut) = SitOut() # no other options needed +player_option(player::Player{BotSitOut}, ::SitDownSitOut) = SitOut() # no other options needed ##### BotCheckFold -player_option(player::Player{BotCheckFold}, ::PayBlindSitOut) = PayBlind() -player_option!(game::Game, player::Player{BotCheckFold}, ::CheckRaiseFold) = check!(game, player) -player_option!(game::Game, player::Player{BotCheckFold}, ::CallRaiseFold) = fold!(game, player) -player_option!(game::Game, player::Player{BotCheckFold}, ::CallFold) = fold!(game, player) +player_option!(game::Game, player::Player{BotCheckFold}, ::AbstractGameState, ::CheckRaiseFold) = check!(game, player) +player_option!(game::Game, player::Player{BotCheckFold}, ::AbstractGameState, ::CallRaiseFold) = fold!(game, player) +player_option!(game::Game, player::Player{BotCheckFold}, ::AbstractGameState, ::CallFold) = fold!(game, player) ##### BotCheckCall -player_option(player::Player{BotCheckCall}, ::PayBlindSitOut) = PayBlind() -player_option!(game::Game, player::Player{BotCheckCall}, ::CheckRaiseFold) = check!(game, player) -player_option!(game::Game, player::Player{BotCheckCall}, ::CallRaiseFold) = call!(game, player) -player_option!(game::Game, player::Player{BotCheckCall}, ::CallFold) = call!(game, player) - -##### BotRandom +player_option!(game::Game, player::Player{BotCheckCall}, ::AbstractGameState, ::CheckRaiseFold) = check!(game, player) +player_option!(game::Game, player::Player{BotCheckCall}, ::AbstractGameState, ::CallRaiseFold) = call!(game, player) +player_option!(game::Game, player::Player{BotCheckCall}, ::AbstractGameState, ::CallFold) = call!(game, player) -player_option(player::Player{BotRandom}, ::PayBlindSitOut) = PayBlind() +##### Bot5050 -function player_option!(game::Game, player::Player{BotRandom}, ::CheckRaiseFold) +function player_option!(game::Game, player::Player{Bot5050}, ::AbstractGameState, ::CheckRaiseFold) if rand() < 0.5 check!(game, player) else @@ -118,7 +118,7 @@ function player_option!(game::Game, player::Player{BotRandom}, ::CheckRaiseFold) raise_to!(game, player, amt) end end -function player_option!(game::Game, player::Player{BotRandom}, ::CallRaiseFold) +function player_option!(game::Game, player::Player{Bot5050}, ::AbstractGameState, ::CallRaiseFold) if rand() < 0.5 if rand() < 0.5 # Call call!(game, player) @@ -132,7 +132,7 @@ function player_option!(game::Game, player::Player{BotRandom}, ::CallRaiseFold) end end -function player_option!(game::Game, player::Player{BotRandom}, ::CallFold) +function player_option!(game::Game, player::Player{Bot5050}, ::AbstractGameState, ::CallFold) if rand() < 0.5 call!(game, player) else diff --git a/src/player_types.jl b/src/player_types.jl index b2e022eb..81e8cc5f 100644 --- a/src/player_types.jl +++ b/src/player_types.jl @@ -2,21 +2,21 @@ ##### Player types ##### -export bank_roll +export Player, bank_roll export Human -export BotRandom, BotSitOut, BotCheckFold, BotCheckCall +export AbstractAI, Bot5050, BotSitOut, BotCheckFold, BotCheckCall abstract type AbstractLifeForm end struct Human <: AbstractLifeForm end abstract type AbstractAI <: AbstractLifeForm end -struct BotRandom <: AbstractAI end +struct Bot5050 <: AbstractAI end struct BotSitOut <: AbstractAI end struct BotCheckFold <: AbstractAI end struct BotCheckCall <: AbstractAI end -ai_to_use() = BotRandom() +ai_to_use() = Bot5050() mutable struct Player{LF} life_form::LF @@ -83,3 +83,5 @@ all_in(player::Player) = player.all_in action_required(player::Player) = player.action_required sat_out(player::Player) = player.sat_out round_bank_roll(player::Player) = player.round_bank_roll +pot_investment(player::Player) = player.pot_investment +round_contribution(player::Player) = player.round_contribution diff --git a/src/table.jl b/src/table.jl index 3a7a0513..21569e4a 100644 --- a/src/table.jl +++ b/src/table.jl @@ -30,17 +30,44 @@ function Base.show(io::IO, blinds::Blinds, include_type = true) println(io, "Blinds = (small=$(blinds.small),big=$(blinds.big))") end -Base.@kwdef mutable struct Table - deck::PlayingCards.Deck = ordered_deck() +mutable struct Table + deck::PlayingCards.Deck players::Tuple - cards::Union{Nothing,Tuple{<:Card,<:Card,<:Card,<:Card,<:Card}} = nothing - blinds::Blinds = Blinds() - pot::Float64 = Float64(0) - state::AbstractGameState = PreFlop() - button_id::Int = button_id() - current_raise_amt::Float64 = Float64(0) - transactions::TransactionManager = TransactionManager(players) - winners::Winners = Winners() + cards::Union{Nothing,Tuple{<:Card,<:Card,<:Card,<:Card,<:Card}} + blinds::Blinds + pot::Float64 + state::AbstractGameState + button_id::Int + current_raise_amt::Float64 + transactions::TransactionManager + winners::Winners +end + +function Table(; + players::Tuple, + deck = ordered_deck(), + cards = nothing, + blinds = Blinds(), + pot = Float64(0), + state = PreFlop(), + button_id = button_id(), + current_raise_amt = Float64(0), + transactions = nothing, + winners = Winners(), +) + if transactions == nothing + transactions = TransactionManager(players) + end + return Table(deck, + players, + cards, + blinds, + pot, + state, + button_id, + current_raise_amt, + transactions, + winners) end function Base.show(io::IO, table::Table, include_type = true) @@ -63,6 +90,7 @@ 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 +state(table::Table) = table.state players_at_table(table::Table) = table.players all_checked_or_folded(table::Table) = all(map(x -> folded(x) || checked(x), players_at_table(table))) all_all_in_or_folded(table::Table) = all(map(x -> folded(x) || all_in(x), players_at_table(table))) @@ -197,7 +225,7 @@ function deal!(table::Table, blinds::Blinds) players = players_at_table(table) shuffle!(table.deck) for (i, player) in enumerate(circle(table, SmallBlind())) - po = player_option(player, PayBlindSitOut()) + po = player_option(player, SitDownSitOut()) # TODO: move sit-out option to before deal! to allow earlier # error checking, and avoiding situations with too few players. if po isa SitOut @@ -206,22 +234,18 @@ function deal!(table::Table, blinds::Blinds) @info "$(name(player)) sat out a hand." check_for_winner!(table) elseif po isa PayBlind - if player.id == small_blind(table).id && bank_roll(player) ≤ blinds.small - player.cards = pop!(table.deck, 2) - player.all_in = true + player.cards = pop!(table.deck, 2) + if is_small_blind(table, player) && bank_roll(player) ≤ blinds.small @info "$(name(player)) paid the small blind (all-in) and dealt cards: $(player.cards)" contribute!(table, player, bank_roll(player)) - elseif player.id == big_blind(table).id && bank_roll(player) ≤ blinds.big - player.cards = pop!(table.deck, 2) - player.all_in = true + elseif is_big_blind(table, player) && bank_roll(player) ≤ blinds.big @info "$(name(player)) paid the big blind (all-in) and dealt cards: $(player.cards)" contribute!(table, player, bank_roll(player)) else - player.cards = pop!(table.deck, 2) - if player.id == small_blind(table).id + if is_small_blind(table, player) @info "$(name(player)) paid the small blind and dealt cards: $(player.cards)" contribute!(table, player, blinds.small) - elseif player.id == big_blind(table).id + elseif is_big_blind(table, player) @info "$(name(player)) paid the big blind and dealt cards: $(player.cards)" contribute!(table, player, blinds.big) else diff --git a/src/transactions.jl b/src/transactions.jl index 63fec2b3..ce447528 100644 --- a/src/transactions.jl +++ b/src/transactions.jl @@ -134,9 +134,6 @@ function contribute!(table, player, amt, call=false) amt_remaining -= amt_contrib amt_remaining ≈ 0 && break end - if !(amt_remaining ≈ 0) - @show amt_remaining - end @assert amt_remaining ≈ 0 # pots better be emptied if bank_roll(player) ≈ 0 # went all-in, set exactly. @@ -228,6 +225,9 @@ function distribute_winnings!(players, tm::TransactionManager, table_cards) end n_winners = length(winner_ids) @debug "winner_ids = $(winner_ids)" + @debug "length(players) = $(length(players))" + @debug "length(hand_evals_sorted) = $(length(hand_evals_sorted))" + @debug "length(tm.sorted_players) = $(length(tm.sorted_players))" for winner_id in winner_ids win_id = tm.sorted_players[winner_id].id winning_player = players[win_id] diff --git a/test/fuzz_play.jl b/test/fuzz_play.jl new file mode 100644 index 00000000..9aa09d9d --- /dev/null +++ b/test/fuzz_play.jl @@ -0,0 +1,16 @@ +using Test +using PlayingCards +using TexasHoldem +TH = TexasHoldem + +@testset "Game: Play (3 Bot5050's)" begin + for n in 1:n_fuzz + play(Game(ntuple(i->Player(Bot5050(), i), 3))) + end +end + +@testset "Game: Play (10 Bot5050's)" begin + for n in 1:n_fuzz_10_players + play(Game(ntuple(i->Player(Bot5050(), i), 10))) + end +end diff --git a/test/game.jl b/test/game.jl index ea23770d..5bade2b4 100644 --- a/test/game.jl +++ b/test/game.jl @@ -5,7 +5,7 @@ TH = TexasHoldem @testset "Game: show" begin players = ntuple(2) do i - TH.Player(BotRandom(), i) + Player(Bot5050(), i) end game = Game(players) sprint(show, game) @@ -18,7 +18,7 @@ end deck = ordered_deck() shuffle!(deck) players = ntuple(3) do i - TH.Player(BotRandom(), i, pop!(deck, 2)) + Player(Bot5050(), i, pop!(deck, 2)) end table = Table(;players=players,deck=deck,cards=pop!(deck, 5)) game = Game(players;deck=deck,table=table) @@ -26,7 +26,7 @@ end @testset "Game: contrived game" begin players = ntuple(3) do i - TH.Player(BotRandom(), i) + Player(Bot5050(), i) end game = Game(players) players = TH.players_at_table(game) @@ -50,7 +50,7 @@ end # All-in cases players = ntuple(3) do i - TH.Player(BotRandom(), i) + Player(Bot5050(), i) end game = Game(players) players = TH.players_at_table(game) diff --git a/test/play.jl b/test/play.jl index 92498234..542c8e90 100644 --- a/test/play.jl +++ b/test/play.jl @@ -1,36 +1,21 @@ using Test using PlayingCards using TexasHoldem -TH = TexasHoldem @testset "Game: Play (invalid number of players)" begin - @test_throws AssertionError Game(ntuple(i->TH.Player(BotRandom(), i), 1)) - @test_throws AssertionError Game(ntuple(i->TH.Player(BotRandom(), i), 11)) + @test_throws AssertionError("Invalid number of players") Game(ntuple(i->Player(Bot5050(), i), 1)) + @test_throws AssertionError("Invalid number of players") Game(ntuple(i->Player(Bot5050(), i), 11)) end @testset "Game: Play (BotCheckCall)" begin - play(Game(ntuple(i->TH.Player(BotCheckCall(), i), 3))) + play(Game(ntuple(i->Player(BotCheckCall(), i), 3))) end @testset "Game: Play (BotCheckFold)" begin - play(Game(ntuple(i->TH.Player(BotCheckFold(), i), 3))) + play(Game(ntuple(i->Player(BotCheckFold(), i), 3))) end @testset "Game: Play (BotSitOut)" begin - players = ( - TH.Player(BotSitOut(), 1), - TH.Player(BotRandom(), 2), - TH.Player(BotRandom(), 3), - ) + players = (Player(BotSitOut(), 1),Player(BotCheckCall(), 2),Player(BotCheckCall(), 3)) play(Game(players)) end - -@testset "Game: Play (3 BotRandom's)" begin - for n in 1:100 - play(Game(ntuple(i->TH.Player(BotRandom(), i), 3))) - end -end - -@testset "Game: Play (10 BotRandom's)" begin - play(Game(ntuple(i->TH.Player(BotRandom(), i), 10))) -end diff --git a/test/players.jl b/test/players.jl index acf8ec92..29f6074b 100644 --- a/test/players.jl +++ b/test/players.jl @@ -5,11 +5,11 @@ TH = TexasHoldem @testset "Players" begin players = ( - TH.Player(Human(), 1), - TH.Player(BotRandom(), 2), + Player(Human(), 1), + Player(Bot5050(), 2), ) @test TH.name(players[1]) == "Human[1]" - @test TH.name(players[2]) == "BotRandom[2]" + @test TH.name(players[2]) == "Bot5050[2]" @test TH.cards(players[1]) == players[1].cards @test TH.cards(players[2]) == players[2].cards end diff --git a/test/runtests.jl b/test/runtests.jl index 7ec35173..50315479 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,8 +9,13 @@ submodules = [ "table", "game", "play", + "fuzz_play", ] +local_run = isempty(get(ENV, "CI", "")) +n_fuzz = local_run ? 10 : 100 +n_fuzz_10_players = local_run ? 1 : 10 + tests_to_debug = ["play"] # tests_to_debug = submodules diff --git a/test/table.jl b/test/table.jl index 457206a7..2b33206a 100644 --- a/test/table.jl +++ b/test/table.jl @@ -5,7 +5,7 @@ using TexasHoldem TH = TexasHoldem @testset "Table: constructors / observed cards" begin - players = ntuple(i-> TH.Player(BotRandom(), i), 2) + players = ntuple(i-> Player(Bot5050(), i), 2) deck = ordered_deck() shuffle!(deck) blinds = TH.Blinds(1,2) @@ -24,7 +24,7 @@ TH = TexasHoldem end @testset "Table: Move button" begin - players = ntuple(i-> TH.Player(BotRandom(), i), 3) + players = ntuple(i-> Player(Bot5050(), i), 3) table = Table(;players = players) @test table.button_id == 1 move_button!(table) @@ -56,7 +56,7 @@ end end @testset "Table: player position" begin - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = TH.button_id()) @test TH.position(table, players[1], -5) == 1 @@ -88,7 +88,7 @@ end @test TH.button_id() == 1 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = TH.button_id()) TH.deal!(table, TH.blinds(table)) @@ -102,7 +102,7 @@ end @test state==length(players) # button_id = 2 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = 2) TH.deal!(table, TH.blinds(table)) state = 0 @@ -122,7 +122,7 @@ end @test TH.button_id() == 1 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = TH.button_id()) TH.deal!(table, TH.blinds(table)) @@ -140,7 +140,7 @@ end @test state==length(players) # button_id = 2 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = 2) TH.deal!(table, TH.blinds(table)) state = 0 @@ -160,7 +160,7 @@ end @test TH.button_id() == 1 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = TH.button_id()) TH.deal!(table, TH.blinds(table)) @@ -178,7 +178,7 @@ end @test state==length(players) # button_id = 2 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = 2) TH.deal!(table, TH.blinds(table)) state = 0 @@ -198,7 +198,7 @@ end @test TH.button_id() == 1 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = TH.button_id()) TH.deal!(table, TH.blinds(table)) @@ -216,7 +216,7 @@ end @test state==length(players) # button_id = 2 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = 2) TH.deal!(table, TH.blinds(table)) state = 0 @@ -235,7 +235,7 @@ end @testset "Table: iterate from player" begin @test TH.button_id() == 1 - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = TH.button_id()) TH.deal!(table, TH.blinds(table)) # button_id = 1 @@ -247,7 +247,7 @@ end end @test state==length(players) - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = 2) TH.deal!(table, TH.blinds(table)) # button_id = 2 @@ -259,7 +259,7 @@ end end @test state==length(players) - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = TH.button_id()) TH.deal!(table, TH.blinds(table)) # button_id = 1 @@ -275,7 +275,7 @@ end end @test state==length(players) - players = ntuple(i-> TH.Player(BotRandom(), i), 5) + players = ntuple(i-> Player(Bot5050(), i), 5) table = Table(;players = players, button_id = 2) TH.deal!(table, TH.blinds(table)) # button_id = 2 diff --git a/test/transactions.jl b/test/transactions.jl index ac54ed59..f3778d4b 100644 --- a/test/transactions.jl +++ b/test/transactions.jl @@ -1,14 +1,14 @@ using Test using PlayingCards -using TexasHoldem: Player, BotRandom, TransactionManager, button_id, Table +using TexasHoldem: Player, Bot5050, TransactionManager, button_id, Table TH = TexasHoldem @testset "TransactionManagers - Lowest bank roll goes all-in and wins it all" begin table_cards = (A♢, K♢, Q♢, 2♠, 3♠) players = ( - TH.Player(BotRandom(), 1, (A♠, A♣); bank_roll = 1*100), - TH.Player(BotRandom(), 2, (K♠, K♣); bank_roll = 2*100), - TH.Player(BotRandom(), 3, (Q♠, Q♣); bank_roll = 3*100), + Player(Bot5050(), 1, (A♠, A♣); bank_roll = 1*100), + Player(Bot5050(), 2, (K♠, K♣); bank_roll = 2*100), + Player(Bot5050(), 3, (Q♠, Q♣); bank_roll = 3*100), ) tm = TH.TransactionManager(players) table = Table(;players=players,cards=table_cards,transactions=tm) @@ -30,9 +30,9 @@ end @testset "TransactionManagers - Highest bank roll goes all-in and wins it all" begin table_cards = (A♢, K♢, Q♢, 2♠, 3♠) players = ( - TH.Player(BotRandom(), 1, (A♠, A♣); bank_roll = 3*100), - TH.Player(BotRandom(), 2, (K♠, K♣); bank_roll = 2*100), - TH.Player(BotRandom(), 3, (Q♠, Q♣); bank_roll = 1*100), + Player(Bot5050(), 1, (A♠, A♣); bank_roll = 3*100), + Player(Bot5050(), 2, (K♠, K♣); bank_roll = 2*100), + Player(Bot5050(), 3, (Q♠, Q♣); bank_roll = 1*100), ) tm = TH.TransactionManager(players) table = Table(;players=players,cards=table_cards,transactions=tm) @@ -53,9 +53,9 @@ end @testset "TransactionManagers - Lowest bank roll goes all-in and wins a split pot" begin table_cards = (A♢, K♢, Q♢, 2♠, 3♠) players = ( - TH.Player(BotRandom(), 1, (A♠, A♣); bank_roll = 1*100), - TH.Player(BotRandom(), 2, (K♠, K♣); bank_roll = 2*100), - TH.Player(BotRandom(), 3, (Q♠, Q♣); bank_roll = 3*100), + Player(Bot5050(), 1, (A♠, A♣); bank_roll = 1*100), + Player(Bot5050(), 2, (K♠, K♣); bank_roll = 2*100), + Player(Bot5050(), 3, (Q♠, Q♣); bank_roll = 3*100), ) tm = TH.TransactionManager(players) table = Table(;players=players,cards=table_cards,transactions=tm) @@ -85,9 +85,9 @@ end @testset "TransactionManagers - Highest bank roll goes all-in and wins it all" begin table_cards = (A♢, K♢, Q♢, 2♠, 3♠) players = ( - TH.Player(BotRandom(), 1, (A♠, A♣); bank_roll = 3*100), - TH.Player(BotRandom(), 2, (K♠, K♣); bank_roll = 2*100), - TH.Player(BotRandom(), 3, (Q♠, Q♣); bank_roll = 1*100), + Player(Bot5050(), 1, (A♠, A♣); bank_roll = 3*100), + Player(Bot5050(), 2, (K♠, K♣); bank_roll = 2*100), + Player(Bot5050(), 3, (Q♠, Q♣); bank_roll = 1*100), ) tm = TH.TransactionManager(players) table = Table(;players=players,cards=table_cards,transactions=tm) @@ -117,12 +117,12 @@ end @testset "TransactionManagers - Semi-complicated split pot (shared winners)" begin table_cards = (T♢, Q♢, A♠, 8♠, 9♠) players = ( - TH.Player(BotRandom(), 1, (4♠, 5♣); bank_roll = 1*100), # bust - TH.Player(BotRandom(), 2, (K♠, K♣); bank_roll = 2*100), # win, split with player 3 - TH.Player(BotRandom(), 3, (K♡,K♢); bank_roll = 3*100), # win, split with player 2 - TH.Player(BotRandom(), 4, (2♡, 3♢); bank_roll = 4*100), # bust - TH.Player(BotRandom(), 5, (7♠, 7♣); bank_roll = 5*100), # 2nd to players 2 and 3, win remaining pot - TH.Player(BotRandom(), 6, (2♠, 3♣); bank_roll = 6*100), # lose, but not bust + Player(Bot5050(), 1, (4♠, 5♣); bank_roll = 1*100), # bust + Player(Bot5050(), 2, (K♠, K♣); bank_roll = 2*100), # win, split with player 3 + Player(Bot5050(), 3, (K♡,K♢); bank_roll = 3*100), # win, split with player 2 + Player(Bot5050(), 4, (2♡, 3♢); bank_roll = 4*100), # bust + Player(Bot5050(), 5, (7♠, 7♣); bank_roll = 5*100), # 2nd to players 2 and 3, win remaining pot + Player(Bot5050(), 6, (2♠, 3♣); bank_roll = 6*100), # lose, but not bust ) tm = TH.TransactionManager(players) table = Table(;players=players,cards=table_cards,transactions=tm) @@ -180,12 +180,12 @@ end @testset "TransactionManagers - Single round split pot (shared winners), with simple re-raises" begin table_cards = (T♢, Q♢, A♠, 8♠, 9♠) players = ( - TH.Player(BotRandom(), 1, (4♠, 5♣); bank_roll = 1*100), # bust - TH.Player(BotRandom(), 2, (K♠, K♣); bank_roll = 2*100), # win, split with player 3 - TH.Player(BotRandom(), 3, (K♡,K♢); bank_roll = 3*100), # win, split with player 2 - TH.Player(BotRandom(), 4, (2♡, 3♢); bank_roll = 4*100), # bust - TH.Player(BotRandom(), 5, (7♠, 7♣); bank_roll = 5*100), # 2nd to players 2 and 3, win remaining pot - TH.Player(BotRandom(), 6, (2♠, 3♣); bank_roll = 6*100), # lose, but not bust + Player(Bot5050(), 1, (4♠, 5♣); bank_roll = 1*100), # bust + Player(Bot5050(), 2, (K♠, K♣); bank_roll = 2*100), # win, split with player 3 + Player(Bot5050(), 3, (K♡,K♢); bank_roll = 3*100), # win, split with player 2 + Player(Bot5050(), 4, (2♡, 3♢); bank_roll = 4*100), # bust + Player(Bot5050(), 5, (7♠, 7♣); bank_roll = 5*100), # 2nd to players 2 and 3, win remaining pot + Player(Bot5050(), 6, (2♠, 3♣); bank_roll = 6*100), # lose, but not bust ) tm = TH.TransactionManager(players) table = Table(;players=players,cards=table_cards,transactions=tm) @@ -224,12 +224,12 @@ end @testset "TransactionManagers - Single round split pot (shared winners), with simple re-raises, reversed bank roll order" begin table_cards = (T♢, Q♢, A♠, 8♠, 9♠) players = ( - TH.Player(BotRandom(), 1, (2♠, 3♣); bank_roll = 6*100), # lose, but not bust - TH.Player(BotRandom(), 2, (7♠, 7♣); bank_roll = 5*100), # 2nd to players 2 and 3, win remaining pot - TH.Player(BotRandom(), 3, (2♡, 3♢); bank_roll = 4*100), # bust - TH.Player(BotRandom(), 4, (K♡,K♢); bank_roll = 3*100), # win, split with player 2 - TH.Player(BotRandom(), 5, (K♠, K♣); bank_roll = 2*100), # win, split with player 3 - TH.Player(BotRandom(), 6, (4♠, 5♣); bank_roll = 1*100), # bust + Player(Bot5050(), 1, (2♠, 3♣); bank_roll = 6*100), # lose, but not bust + Player(Bot5050(), 2, (7♠, 7♣); bank_roll = 5*100), # 2nd to players 2 and 3, win remaining pot + Player(Bot5050(), 3, (2♡, 3♢); bank_roll = 4*100), # bust + Player(Bot5050(), 4, (K♡,K♢); bank_roll = 3*100), # win, split with player 2 + Player(Bot5050(), 5, (K♠, K♣); bank_roll = 2*100), # win, split with player 3 + Player(Bot5050(), 6, (4♠, 5♣); bank_roll = 1*100), # bust ) tm = TH.TransactionManager(players) table = Table(;players=players,cards=table_cards,transactions=tm)