Skip to content

Commit

Permalink
Merge #47
Browse files Browse the repository at this point in the history
47: Improve names, add some helper funcs, add some docs r=charleskawczynski a=charleskawczynski

Peel some things off of #46.

Co-authored-by: Charles Kawczynski <kawczynski.charles@gmail.com>
  • Loading branch information
bors[bot] and charleskawczynski committed May 10, 2021
2 parents ac8ab39 + 9b98a7c commit 7803f70
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 161 deletions.
79 changes: 77 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))))
```
4 changes: 2 additions & 2 deletions src/TexasHoldem.jl
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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])"
Expand Down
72 changes: 48 additions & 24 deletions src/player_actions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).
"""
Expand All @@ -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

Expand Down Expand Up @@ -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))
Expand All @@ -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))
64 changes: 32 additions & 32 deletions src/player_options.jl
Original file line number Diff line number Diff line change
@@ -1,44 +1,48 @@
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)
choice == -1 && error("Uncaught case")
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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand Down
Loading

0 comments on commit 7803f70

Please sign in to comment.