Skip to content

Commit

Permalink
Add printing of ascii objects
Browse files Browse the repository at this point in the history
  • Loading branch information
charleskawczynski committed Aug 31, 2023
1 parent 0dbc474 commit 37eb9c0
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 33 deletions.
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
22 changes: 15 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)

Check warning on line 35 in src/game.jl

View check run for this annotation

Codecov / codecov/patch

src/game.jl#L33-L35

Added lines #L33 - L35 were not covered by tests
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 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) = !folded(player) && still_playing(player) && active(player)
pot_eligible(player::Nothing) = false

Check warning on line 156 in src/player_type.jl

View check run for this annotation

Codecov / codecov/patch

src/player_type.jl#L156

Added line #L156 was not covered by tests

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

Check warning on line 191 in src/table.jl

View check run for this annotation

Codecov / codecov/patch

src/table.jl#L187-L191

Added lines #L187 - L191 were not covered by tests

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

Check warning on line 15 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L11-L15

Added lines #L11 - L15 were not covered by tests
# space = PC.rank(c)==10 ? "" : " "
space = " "

Check warning on line 17 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L17

Added line #L17 was not covered by tests
# 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

Check warning on line 29 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L19-L29

Added lines #L19 - L29 were not covered by tests
else
["┌─────────┐$_rbuffer", map(x->"│░░░░░░░░░│$_rbuffer", 1:7)..., "└─────────┘$_rbuffer"]

Check warning on line 31 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L31

Added line #L31 was not covered by tests
end
end
strs = map(1:9) do j
join(

Check warning on line 35 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L34-L35

Added lines #L34 - L35 were not covered by tests
map(1:length(cards)) do i
getindex(lines[i],j)

Check warning on line 37 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L37

Added line #L37 was not covered by tests
end,
""
)
end
return to_string ? join(strs, "\n") : strs

Check warning on line 42 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L42

Added line #L42 was not covered by tests
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)

Check warning on line 51 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L51

Added line #L51 was not covered by tests
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)]

Check warning on line 55 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L53-L55

Added lines #L53 - L55 were not covered by tests
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)

Check warning on line 60 in src/terminal/ascii_card.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_card.jl#L57-L60

Added lines #L57 - L60 were not covered by tests
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...))))"

Check warning on line 11 in src/terminal/ascii_player.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_player.jl#L4-L11

Added lines #L4 - L11 were not covered by tests
else
""

Check warning on line 13 in src/terminal/ascii_player.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_player.jl#L13

Added line #L13 was not covered by tests
end
info = [

Check warning on line 15 in src/terminal/ascii_player.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_player.jl#L15

Added line #L15 was not covered by tests
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

Check warning on line 25 in src/terminal/ascii_player.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/ascii_player.jl#L23-L25

Added lines #L23 - L25 were not covered by tests
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())

Check warning on line 67 in src/terminal/config_game.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/config_game.jl#L67

Added line #L67 was not covered by tests
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())

Check warning on line 80 in src/terminal/config_game.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/config_game.jl#L80

Added line #L80 was not covered by tests
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())

Check warning on line 90 in src/terminal/config_game.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/config_game.jl#L90

Added line #L90 was not covered by tests
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())

Check warning on line 102 in src/terminal/config_game.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/config_game.jl#L102

Added line #L102 was not covered by tests
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())

Check warning on line 130 in src/terminal/config_game.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/config_game.jl#L130

Added line #L130 was not covered by tests
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

Check warning on line 68 in src/terminal/human_player_options.jl

View check run for this annotation

Codecov / codecov/patch

src/terminal/human_player_options.jl#L60-L68

Added lines #L60 - L68 were not covered by tests
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 37eb9c0

Please sign in to comment.