Skip to content

Commit

Permalink
Merge #227
Browse files Browse the repository at this point in the history
227: Add printing of ascii objects r=charleskawczynski a=charleskawczynski

Closes #198.

Co-authored-by: Charles Kawczynski <kawczynski.charles@gmail.com>
  • Loading branch information
bors[bot] and charleskawczynski authored Aug 31, 2023
2 parents 0dbc474 + f18594c commit f9e3fb4
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TexasHoldem"
uuid = "6cef90fc-eb55-4a2a-97d0-7ecce2b738fe"
authors = ["Charles Kawczynski <kawczynski.charles@gmail.com>"]
version = "0.4.2"
version = "0.4.3"

[deps]
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Expand Down
7 changes: 7 additions & 0 deletions src/TexasHoldem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export Chips

include("custom_logger.jl")

abstract type AbstractGUI end
struct PlainLogger <: AbstractGUI end # no gui
struct Terminal <: AbstractGUI end

abstract type AbstractRound end
struct PreFlop <: AbstractRound end
struct Flop <: AbstractRound end
Expand All @@ -40,6 +44,9 @@ include("player_actions.jl")
include("player_options.jl")
include(joinpath("terminal", "human_player_options.jl"))
include(joinpath("terminal", "config_game.jl"))
include(joinpath("terminal", "ascii_card.jl"))
include(joinpath("terminal", "ascii_player.jl"))
include(joinpath("terminal", "ui.jl"))
include("recreate.jl")

end # module
23 changes: 16 additions & 7 deletions src/game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@ any_actions_required(game::Game) = any_actions_required(game.table)
round(game::Game) = round(game.table)
move_buttons!(game) = move_buttons!(game.table)

function print_round(table, round)
table.gui isa Terminal && return nothing
print_round(table, round)
end

print_round(table, round::PreFlop) = @cinfo table.logger "Pre-flop!"
print_round(table, round::Flop) = @cinfo table.logger "Flop: $(repeat(" ", 44)) $(table.cards[1:3])"
print_round(table, round::Turn) = @cinfo table.logger "Turn: $(repeat(" ", 44)) $(table.cards[4])"
print_round(table, round::River) = @cinfo table.logger "River: $(repeat(" ", 43)) $(table.cards[5])"
print_round(table, round::Flop) = @cinfo table.logger "Flop: $(repeat(" ", 44)) $(table.cards[1:3])"
print_round(table, round::Turn) = @cinfo table.logger "Turn: $(repeat(" ", 44)) $(table.cards[4])"
print_round(table, round::River) = @cinfo table.logger "River: $(repeat(" ", 43)) $(table.cards[5])"

set_preflop_blind_raise!(table::Table, player, ::AbstractRound, i::Int) = nothing
function set_preflop_blind_raise!(table::Table, player::Player, ::PreFlop, i::Int)
Expand Down Expand Up @@ -141,6 +146,7 @@ function act_generic!(game::Game, round::AbstractRound, sf::StartFrom)
@assert sf.game_point isa StartOfGame || sf.game_point isa PlayerOption
if sf.game_point isa StartOfGame
set_round!(table, round)
update_gui(table)
print_round(table, round)
reset_round_bank_rolls!(game, round)

Expand Down Expand Up @@ -311,7 +317,7 @@ function _deal_and_play!(game::Game, sf::StartFrom)

if sf.game_point isa StartOfGame
reset!(table.transactions, players)
@assert all(p->cards(p) == nothing, players)
@assert all(p->cards(p) == (nothing,nothing), players)
@assert cards(table) == nothing
reset_round_bank_rolls!(table) # round bank-rolls must account for blinds
deal!(table, blinds(table))
Expand All @@ -326,6 +332,7 @@ function _deal_and_play!(game::Game, sf::StartFrom)
distribute_winnings!(players, table.transactions, cards(table), logger)
winners.declared = true

update_gui(table)
@cdebug logger "amounts.(table.transactions.side_pots) = $(amounts.(table.transactions.side_pots))"
@cdebug logger "initial_∑brs = $(initial_∑brs)"
@cdebug logger "sum(bank_roll.(players)) = $(sum(bank_roll.(players)))"
Expand Down Expand Up @@ -363,7 +370,7 @@ function _deal_and_play!(game::Game, sf::StartFrom)
end

@cinfo logger "------ Finished game!"
return winners
return any(x->quit_game(game, x), players)
end

function set_active_status!(table::Table)
Expand All @@ -390,12 +397,13 @@ function reset_game!(game::Game)
dealer_pidx=dealer_pidx(table),
blinds=table.blinds,
logger=logger,
gui=table.gui,
)
PlayingCards.reset!(game.table.deck)
table = game.table
players = players_at_table(table)
for player in players
player.cards = nothing
player.cards = (nothing,nothing)
player.pot_investment = 0
player.game_profit = Chips(0)
player.all_in = false
Expand All @@ -422,7 +430,8 @@ function tournament!(game::Game)
table = game.table
players = players_at_table(table)
while length(players) > 1
play!(game)
quit = play!(game)
quit && break
n_players_remaining = count(x->!(bank_roll(x) == 0), players)
if n_players_remaining 1
@cinfo logger "Victor emerges!"
Expand Down
6 changes: 4 additions & 2 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::Union{Nothing,Tuple{<:Card,<:Card}}
cards::NTuple{2,Union{Card,Nothing}}
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; bank_roll = 200)
function Player(strategy, seat_number = -1, cards = (nothing,nothing); bank_roll = 200)
action_required = true
all_in = false
round_bank_roll = bank_roll
Expand Down Expand Up @@ -152,5 +152,7 @@ inactive(player::Player) = !active(player)
pot_investment(player::Player) = player.pot_investment
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

notify_reward(player) = nothing
16 changes: 13 additions & 3 deletions src/table.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ buttons(b::Buttons) = (
b.first_to_act,
)

mutable struct Table{P<:Players, L, TM, B <: Blinds, D <: PlayingCards.AbstractDeck}
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}}
Expand All @@ -65,6 +65,7 @@ mutable struct Table{P<:Players, L, TM, B <: Blinds, D <: PlayingCards.AbstractD
play_out_game::Bool
n_max_actions::Int
logger::L
gui::G
end

buttons(table::Table) = table.buttons
Expand Down Expand Up @@ -100,6 +101,8 @@ Table(players; kwargs...) = Table(Players(players); kwargs...)
function Table(players::Players;
deck = PlayingCards.MaskedDeck(),
cards = nothing,
gui = PlainLogger(), # good for test/debugging, but not very fun
# gui = Terminal(), # fun, but not good for tests/debugging
blinds = Blinds(),
pot = 0,
round = PreFlop(),
Expand All @@ -119,7 +122,7 @@ function Table(players::Players;
L = typeof(logger)
TM = typeof(transactions)
B = typeof(blinds)
return Table{P, L, TM, B, typeof(deck)}(deck,
return Table{P, L, TM, B, typeof(deck), typeof(gui)}(deck,
players,
cards,
blinds,
Expand All @@ -132,7 +135,8 @@ function Table(players::Players;
winners,
play_out_game,
n_max_actions,
logger)
logger,
gui)
end

function Buttons(players::Players, dealer_pidx)
Expand Down Expand Up @@ -180,6 +184,12 @@ 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, ::River) = table.cards

# for testing
unobserved_cards(table::Table) = unobserved_cards(table, round(table))
unobserved_cards(table::Table, ::PreFlop) = table.cards
Expand Down
61 changes: 61 additions & 0 deletions src/terminal/ascii_card.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import PlayingCards
const PC = PlayingCards
using PlayingCards

"""
ascii_card(cards...; to_string=true)
ASCII cards.
"""
# ascii_card(cards...; kwargs...) = ascii_card(cards; kwargs...)
function ascii_card(cards::NTuple;to_string=true, rbuffer=" ")
lines = map(enumerate(cards)) do (i, c)
_rbuffer = iseven(i) ? rbuffer : ""
if c isa Card
r = PC.rank_string(PC.rank(c))
# space = PC.rank(c)==10 ? "" : " "
space = " "
# get the cards suit in two steps
s = string(PC.suit(c))
l1 = "┌─────────┐$_rbuffer"
l2 = "$r$space$_rbuffer"
l3 = "│ │$_rbuffer"
l4 = "│ │$_rbuffer"
l5 = "$s$_rbuffer"
l6 = "│ │$_rbuffer"
l7 = "│ │$_rbuffer"
l8 = "$space$(r)$_rbuffer"
l9 = "└─────────┘$_rbuffer"
l1,l2,l3,l4,l5,l6,l7,l8,l9
else
["┌─────────┐$_rbuffer", map(x->"│░░░░░░░░░│$_rbuffer", 1:7)..., "└─────────┘$_rbuffer"]
end
end
strs = map(1:9) do j
join(
map(1:length(cards)) do i
getindex(lines[i],j)
end,
""
)
end
return to_string ? join(strs, "\n") : strs
end

"""
ascii_card_dealer(cards; to_string=true)
Similar to `ascii_card`, except that it hides
cards of type `card::Nothing`.
"""
function ascii_card_dealer(cards; to_string=true)
all_lines = map(_->"", 1:9)
for card in cards
card_lines = ascii_card((card,); to_string=false, rbuffer="")
all_lines = [x * y for (x, y) in zip(all_lines, card_lines)]
end
card_str = to_string ? join(all_lines, "\n") : all_lines
width = length(all_lines[1])
height = length(all_lines)
return (card_str, width, height)
end
26 changes: 26 additions & 0 deletions src/terminal/ascii_player.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import PokerHandEvaluator
const PHE = PokerHandEvaluator

function ascii_player(table, player, player_cards; to_string=false, rbuffer="")
showdown = table.winners.declared
tm = table.transactions
card_lines = ascii_card(player_cards; to_string, rbuffer)
width = length(card_lines[1])
net_winnings = showdown ? "Net winnings: $(profit(player, tm))" : ""
hand = if !isnothing(player_cards[1]) && showdown
"$(PHE.hand_type(PHE.CompactHandEval((player_cards..., table.cards...))))"
else
""
end
info = [
name(player),
"pot investment: $(pot_investment(player))",
"bank roll: $(bank_roll(player))",
"pot-eligible: $(pot_eligible(player) ? "yes" : "no")",
net_winnings,
hand,
]
lines = [i * repeat(" ", (width - length(i))) for i in info]
lines = vcat(lines, card_lines)
return to_string ? join(lines, "\n") : lines
end
10 changes: 5 additions & 5 deletions src/terminal/config_game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function configure_basic_heads_up_game()
Player(Human(), 1; bank_roll=bank_roll),
Player(Bot5050(), 2; bank_roll=bank_roll)
)
return Game(players; blinds=blinds)
return Game(players; blinds=blinds, gui=Terminal())
end

function configure_basic_1v4_game()
Expand All @@ -77,7 +77,7 @@ function configure_basic_1v4_game()
Player(Bot5050(), 4; bank_roll=bank_roll),
Player(Bot5050(), 5; bank_roll=bank_roll),
)
return Game(players; blinds=blinds)
return Game(players; blinds=blinds, gui=Terminal())
end

function configure_basic_2_bots_game()
Expand All @@ -87,7 +87,7 @@ function configure_basic_2_bots_game()
Player(Bot5050(), 1; bank_roll=bank_roll),
Player(Bot5050(), 2; bank_roll=bank_roll),
)
return Game(players; blinds=blinds)
return Game(players; blinds=blinds, gui=Terminal())
end

function configure_basic_4_bots_game()
Expand All @@ -99,7 +99,7 @@ function configure_basic_4_bots_game()
Player(Bot5050(), 3; bank_roll=bank_roll),
Player(Bot5050(), 4; bank_roll=bank_roll),
)
return Game(players; blinds=blinds)
return Game(players; blinds=blinds, gui=Terminal())
end

function configure_human_players(n_players)
Expand Down Expand Up @@ -127,7 +127,7 @@ function configure_custom_game()
end
end

return Game(players; blinds=blinds)
return Game(players; blinds=blinds, gui=Terminal())
end

function configure_game()
Expand Down
27 changes: 22 additions & 5 deletions src/terminal/human_player_options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@
##### Human player options (ask via prompts)
#####

function player_option(game::Game, player::Player{Human}, ::CheckRaiseFold, io::IO=stdin)
function player_option(game::Game, player::Player{Human}, ::CheckRaiseFold, ioin::IO=stdin)
table = game.table
update_gui(stdout, table, player)
vrr = valid_raise_range(table, player)
options = ["Check", "Raise [$(first(vrr)), $(last(vrr))]", "Fold"]
menu = RadioMenu(options, pagesize=4)
choice = request("$(name(player))'s turn to act:", menu)
choice == -1 && error("Uncaught case")
choice == 1 && return Check()
choice == 2 && return Raise(input_raise_amt(table, player, io))
choice == 2 && return Raise(input_raise_amt(table, player, ioin))
choice == 3 && return Fold()
end
function player_option(game::Game, player::Player{Human}, ::CallRaiseFold, io::IO=stdin)
function player_option(game::Game, player::Player{Human}, ::CallRaiseFold, ioin::IO=stdin)
table = game.table
update_gui(stdout, table, player)
vrr = valid_raise_range(table, player)
call_amt = call_amount(table, player)
blind_str = is_blind_call(table, player) ? " (blind)" : ""
Expand All @@ -23,11 +25,12 @@ function player_option(game::Game, player::Player{Human}, ::CallRaiseFold, io::I
choice = request("$(name(player))'s turn to act:", menu)
choice == -1 && error("Uncaught case")
choice == 1 && return Call(table, player)
choice == 2 && return Raise(input_raise_amt(table, player, io))
choice == 2 && return Raise(input_raise_amt(table, player, ioin))
choice == 3 && return Fold()
end
function player_option(game::Game, player::Player{Human}, ::CallAllInFold)
function player_option(game::Game, player::Player{Human}, ::CallAllInFold, ioin::IO=stdin)
table = game.table
update_gui(stdout, table, player)
call_amt = call_amount(table, player)
all_in_amt = round_bank_roll(player)
blind_str = is_blind_call(table, player) ? " (blind)" : ""
Expand All @@ -41,6 +44,7 @@ function player_option(game::Game, player::Player{Human}, ::CallAllInFold)
end
function player_option(game::Game, player::Player{Human}, ::CallFold)
table = game.table
update_gui(stdout, table, player)
call_amt = call_amount(table, player)
blind_str = is_blind_call(table, player) ? " (blind)" : ""
options = ["Call $(call_amt)$blind_str", "Fold"]
Expand All @@ -51,6 +55,19 @@ function player_option(game::Game, player::Player{Human}, ::CallFold)
choice == 2 && return Fold()
end

quit_game(game::Game, player::Player, ioin::IO=stdin) = false

function quit_game(game::Game, player::Player{Human}, ioin::IO=stdin)
table = game.table
update_gui(stdout, table, player)
options = ["Continue playing", "Quite game"]
menu = RadioMenu(options, pagesize=4)
choice = request("Continue or quit?", menu)
choice == -1 && error("Uncaught case")
choice == 1 && return false
choice == 2 && return true
end

# io only works for tests, but does not for user input
# so we have a switch for the test suite
use_input_io() = false
Expand Down
Loading

2 comments on commit f9e3fb4

@charleskawczynski
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/90528

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.3 -m "<description of version>" f9e3fb486b8062afe5bdedc6cf9e70fb9c9b4e78
git push origin v0.4.3

Please sign in to comment.