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 30, 2023
2 parents 0dbc474 + 4ee13b0 commit 77c2a9d
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 26 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
18 changes: 13 additions & 5 deletions src/game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ any_actions_required(game::Game) = any_actions_required(game.table)
round(game::Game) = round(game.table)
move_buttons!(game) = move_buttons!(game.table)

print_round(table) = print_round(table, table.gui)

print_round(table, gui::Terminal) = nothing
print_round(table, gui::PlainLogger) = print_round(table, table.round)

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])"
Expand Down Expand Up @@ -141,7 +146,8 @@ 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)
print_round(table, round)
update_gui(table)
print_round(table)
reset_round_bank_rolls!(game, round)

any_actions_required(game) || return
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 Down Expand Up @@ -395,7 +402,7 @@ function reset_game!(game::Game)
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 +429,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) = pe = !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 debugging, but not very fun
gui = Terminal(),
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
60 changes: 60 additions & 0 deletions src/terminal/ascii_card.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import PlayingCards as PC
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
25 changes: 25 additions & 0 deletions src/terminal/ascii_player.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import PokerHandEvaluator as PHE

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
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

0 comments on commit 77c2a9d

Please sign in to comment.