Simpler Poker Counter

This is a simple poker counter.

It lets players decide their actions and makes the currency calculations for them.

While adding a bit of console animation.

Can I achieve the same results with simpler code??

from string import ascii_lowercase

def get_player_names():
'''
Returns a List Of Playrer
Names Got From User Input
'''
i = 0
player_names = []

print('Enter Player Names...')
print('Press Enter To Indicate That You\'re Done')
while True:
player = input()
if player == '':
break

player_names.append(player)
i += 1

print('-'*40)
print(f'Player#{i} Is Set To: "{player}"')
print('-'*40)

return player_names

def list_sequence(sequence):
i = 0
lengths = [len(item) for item in sequence]

if type(sequence) is dict:
print('-'*40)
print('Current Currency:')
for key in sequence:
i += 1
proportional_space = ' ' * ( (max(lengths) - len(key)) + 2 )
print('-'*20)
print(f'{i})-{key}{proportional_space}: {sequence.get(key, 0)}')
print('-'*40)
else:
print('Current List Of Players:')
print('-'*26)
for item in sequence:
i += 1
print(f'Player#{i} ---> "{item}"')
print()
def start_currency(player_names):
player_currency_dict = \
{player: 100 for player in player_names}
#player_currency_dict[player_names[0]] = 1
return player_currency_dict

def list_player_statues(player_currency, player_statues):
i = 0
lengths = [len(player) for player in player_currency]
for player in player_currency:

i += 1
currency = player_currency[player]
state = players_statues[player]

#formating
proportional_length_factor= ( max(lengths) - len(player) ) + 1
proportional_space = ' ' * proportional_length_factor
remaining_space = 9 - len(player) - len(str(currency)) - proportional_length_factor - (3-len(str(currency)))
proportional_hyphen = '-'*remaining_space
#------------------------------------------------------------------------------------

print('_'*15 + '||' + '_'*25)
print(f'{i}.{player}{proportional_space}: {"0"*(3-len(str(currency)))}{currency} {proportional_hyphen} || {"-"*5}> {state}')
print('_'*40)

player_names = ['Dante', 'Sam', 'Kat', 'Nancy']
#player_names = get_player_names()
player_currency = start_currency(player_names)
players_statues = {player: 'Stand By' for player in player_names}

list_sequence(player_names)
print('Each Player Gets 100 Currency For The Game')
list_sequence(player_currency)

confirm = input('Press Enter To Continue...')
print()

player_bids = {player: 10 for player in player_names}
total_bid = 10 * len(player_names)
player_currency = {player: player_currency.get(player) -10 for player in player_names}
round_over = ''

while len(player_names) > 0:

for player in player_names:

player_folded = players_statues[player] == 'Folded'
player_is_out = players_statues[player] == 'Out'

if player_folded or player_is_out:
continue

players_statues[player] = 'Current Turn'

#next player
player_index = player_names.index(player) + 1
print(player_index)
if player_index >= len(player_names):
player_index = 0

next_player = player_names[player_index]
print(next_player)
players_statues[next_player] = 'Next Turn'
#------------------------------------------------

list_player_statues(player_currency, players_statues)

print()
print('-'*40)
print(f"{'~'*10} {player}'s Turn {'~'*10}")
print('-'*40)

#getting action for each player
while True:

action = input()
#print(player_bids[player])
#print(max(player_bids))
if action == 'ck' and player_bids[player] == max(player_bids.values()):
bid = 0
players_statues[player] = 'Check'
break
elif action == 'c':
bid = max(player_bids.values()) - player_bids[player]
players_statues[player] = 'Called'
break
elif action == 'r':
while True:
amount = input('Raise By?')
is_numeric = amount.isnumeric()
if is_numeric:
amount = int(amount)
bid = max(player_bids.values()) + amount - player_bids[player]
players_statues[player] = f'Raised By {amount}'
break
print()
break
elif action == 'f':
bid = 0
break
else:
bid = 0
if player_bids[player] != max(player_bids.values()):
print("You Cant't check since a player raised the bid")
else:
print('List Of Available Actions:')
print('"ck": Check \n"c": Call \n"r": Raise \n"f": Fold')

#----------------------------------------------------------------------
player_currency[player] -= bid
player_bids[player] += bid
total_bid += bid
print()
print()
print(f'Current Bid: {max(player_bids.values())}')
print(f'Current Round Total: {total_bid}')
print()
print()

list_player_statues(player_currency, players_statues)
round_over = input('Is The Round Over?')
if round_over in ['yes', 'y']:
winner = input('Who Won?')
player_currency[winner] += total_bid
total_bid = 0

player_bids = {player: 10 for player in player_names}
player_statues = {player: 'Stand By' for player in player_names}
list_player_statues(player_currency, players_statues)

$$$$


Tracking Players

There are a few instances where I see you are iterating over players in order to retrieve values from multiple dictionaries (player_currency_dict and player_statues). This is a hint that maybe that information should be stored in the same dictionary of players. I'd also keep the players' last bet in there as well:

# should look like this
players = {'name': [amount, state, last_bet], ...}


This way, it's really easy to keep track of those three pieces of information. To build this, you can swap out your get_player_names and start_currency functions for one simple function:

def get_player_dict(players):
"""
Simply return a dictionary that holds players as keys and
a list of [currency, state, last_bet]. This is a list so that you may
edit those values
"""
return {player: [100, 'Standby', 0] for player in players}


To get all of that information, you can use dict.items() in a loop like this:

# for keeping the list together
for name, status in players.items():
# do things

# for full unpacking
for name, (money, state, last_bet) in players.items():
# do things


Why keep the list together?

I want to use a list because I can modify it in-place. This will be more clear later, but as a short example, I can do something like:

sample = {'Steve': [100, 'Standby', 0]}

def bet_10(my_list):
my_list[0] -= 10
my_list[2] += 10

bet_10(sample['Steve'])
sample
{'Steve': [90, 'Standby', 10]}


Displaying Game State

There are some functions you define that focus on displaying information. While this is a good use-case for a function, some of the formatting can be trimmed down. First, if you are trying to fill a string with a value, you can use ljust or rjust to fill to the right or left, respectively:

'abc'.ljust(7)
'abc    '

'abc'.rjust(7)
'    abc'


Also, magic numbers are usually a code smell. If you need to adjust them, I'd suggest passing them as parameters to make things a bit more flexible:

def show_players(players, name=20, currency=10, state=10, bet=10):
"""
Pass the players dict and the padding for the four values,
or just use the defaults
"""
sts = (n.ljust(v) for n, v in zip(
("Name", "Currency", "State", "Bet"), (name, currency, state, bet))
)
header = "   {} | {} | {} | {}".format(*sts)
for i, (n, (c, s, l)) in enumerate(players.items(), start=1):
n = n.ljust(name)
c = str(c).ljust(currency)
s = s.ljust(state)
l = str(l).ljust(bet)
print(f"{i}. {n} | {c} | {s} | {l}")

# Example
show_players(players)
Name                 | Currency   | State      | Bet
______________________________________________________________
1. Dante                | 100        | Standby    | 0
______________________________________________________________
2. Sam                  | 100        | Standby    | 0
______________________________________________________________
3. Kat                  | 100        | Standby    | 0


I might prefer to show all of the info, that way, at the end of each turn I can display the state of the game and re-use this every time

The Game*

The core workings of your game execute in global scope and don't leverage a lot of functions. This makes changing code a bit more difficult. In pseudo-code, I would imagine this game would look something like:

# Each Round
loop {
# each iteration would be a turn
foreach player in  players{
# Each player that hasn't folded gets a chance to bet
case {
player -> Raise
player -> Fold
player -> Call
player -> Check
}
}

if all(bets match or all-in){
break loop
} else {
continue
}

}



Hopefully the notation isn't too wonky. This will be the basic structure for breaking down your main script.

Round Loop

You will want a loop to track each round of play. Each round will have a pool, which is the total money being bet by all players. It will also need a bid amount which you've noted already as the highest bet. You can just use another while loop to go through the players until an end condition is hit:

def play_round(players):
"""
Loop over players in players dictionary and play
a turn for each player. The win condition is defined by
one of two criteria:
For all players that have not folded they either:
A) are All-In
B) Have last_bet matching bid

The pool (total) of money gambled is returned

"""
bid, pool = 0, 0
while True:
for name, status in players.items():
show_players(players)

curr, state, last_bet = status
# skip over non-eligible players
if state == 'Fold' or state == 'Out':
continue

print(f"It is {name}'s turn!")
print(f"The current bid is {bid}")

# will need some sort of take_turn function
player_bet = take_turn(name, status, bid)

# add the appropriate bet to the pool of cash
pool += player_bet
# the bid is the max of the bid or the bet that was made
bid = max(bid, player_bet)

# Everyone's bet either matches current bid, is all-in, or has folded
if all(
curr==0 or last_bet==bid
for curr, state, last_bet in players.values()
if state != 'Fold'
):
print('All bets match, ending round')
break

# return the pool to outer scope
return pool


Player Turns

Each player will need a turn. This replaces the loop that gets each action (well, moves it more than replaces it) with a function. Really, there are four major actions a player can take: Check, Raise, Call, or Fold. Raise and Call will be pretty similar to one another, so that might be a function on its own. You can easily handle this with an if/else block:

def take_turn(name, status, bid):
"""
A player is allowed the option to do one of five things:
Raise -> Raise bid by integer amount
Call -> Bet the same as the bid
Fold -> Quit the hand
Check -> Bet nothing (only applicable if bid is 0)
All-In -> Bet everything they have

Return amt to be added to the bid pool
"""

# set opts as a dictionary for easy membership-testing
# of user-selected options
opts = {
'c': 'check',
'r': 'raise',
'b': 'call',
'a': 'all-in',
'f': 'fold'
}
curr, state, last_bet = status
if state == "All-In":
return 0

while True:
action = input(f"Which action would you choose? "
f"({', '.join(map(': '.join, opts.items()))})").strip().lower()

# This is a fast check to make sure inputs are valid
# Loop again if not in options dictionary
if action not in opts:
continue

# Otherwise, continue forward
elif action == 'c':
# If the player's last bet is not equal to the current one,
# they are unable to check.
if last_bet != bid:
print(f"Cannot check, bid was raised to {bid}")
continue

print(f"Player {name} has checked")
amt = 0

elif action == 'a':
print(f"Player {name} is going All-In")
amt = all_in(status)

elif action == 'f':
status[1] = 'Fold'
print(f"Player {name} has folded")
amt = 0

else:
try:
# We've abstracted Call and Raise into the same function
# for ease of use
amt = handle_action(name, status, action, bid)
except (ValueError, TypeError):
continue

return amt


Now, we know we will want to return the bartered amount to the round loop so the money can be added to the pool. Here we just need to keep track of the current bid and the player itself. Now, the way handle_action is written depends on how the betting code is written, but in general, it's a way to handle two similar cases. Both are detailed below.

Betting

We will want a function for players to bet amounts of money. Using the general form from the example bet_10 function, you could re-organize it into something like:

def player_bet(player, amount):
"""
use this to modify who bet what amount by deducting amount from the
first value in the list representing the player in-place:

>>> players = {'Steve': [100, 'Standby', 0]}
>>> player_bet(players['Steve'], 50)
>>> players
{'Steve': [50, 'Standby', 50]}

>>> player_bet(players['Steve'], 75)
ValueError
'Player does not have that amount to bet! Player has 50'
"""
curr, state, last_bet = player
if curr < amount:
raise ValueError(
f"Player does not have that amount to bet! Player has {curr}"
)
elif state == 'All-In':
raise TypeError("Player that is All-In cannot bet")

# decrement held money
player[0] -= amount
# while increasing the amount that was bet
player[2] += amount


Using the fact that we are passing in player by reference, we can edit the values in-place so that we don't have to return anything. I am raising an exception for not having enough money because I don't want players who may have mis-typed a number to accidentally go All-In. I also might want going all in to be a special case of betting for the purpose of being explicit. This could be a matter of taste, but an all_in function might look like:

def all_in(player):
"""
Take the amount that the player has left and
return it, this is just in case we aren't immediately
sure what the bet is going to be. Use a try-except so that
going all-in is a bit more explicit and not accidental

>>> players = {'Steve': [100, 'Standby', 0]}
>>> initial_bet = 150
>>> pool = 150
>>>
>>> try:
...     player = players['Steve']
...     player_bet(initial_bet)
... except ValueError:
...     pool += all_in(player)

>>> pool
250
>>> players
{'Steve': [0, 'All-In', 100]}
"""
curr, *_ = player
# set held funds to zero
player[0] -= curr
# add that to bet funds
player[2] += curr
# set status to All In
player[1] = 'All In'
return curr


I want to return currency because I don't want to have to worry about how much a player has when I call that function.

handle_action

Now we can roll Call and Raise into a function that I've called handle_action:

def handle_action(name, status, action, bid):
"""
A function to help keep take_turn function from being too difficult to read,
especially since Raise and Call are almost the same event
"""
curr, state, last_bet = status

# the if/elif statement here helps define two parameters
# amt and message, and raise any exceptions otherwise
if action == 'r':
try:
amt = int(
input("Raise by how much? ")
)
# int function raises a TypeError on bad input
except TypeError:
print("Please input an integer amount of money")
raise
else:
message = f"Player {name} has raised bet by {amt}"

elif action == 'b':
amt = bid - last_bet
message = f"Player {name} has called current bet of {bid}"

# Now, try the player_bet function since amt and message are set
# and return amt only if player_bet was called successfully
try:
player_bet(status, amt)
print(status)
print(message)
except ValueError as e:
# Player will need to explicitly select 'a' as an option
# to go all-in
print(e)
print(f"You do not have enough, if you want to go all-in, select 'a'")
raise
except TypeError as e:
print(e)
raise

return amt



Main Function

You usually want a main function to run your program. In this case, main will be the game loop that runs each round. I've put as many comments as I can to try to be clear, this is already a pretty long answer as is:

def main(players):

while True:
pool = play_round(players)

# Show results after round
show_players(players)
print("Round Over")

# If all remaining players folded for some reason, this will allow the program
# to handle that case and skip the while loop
is_winner = any(x not in ('Fold', 'Out') for _, x, _ in players.values())

while is_winner:
# I like to sanitize input by at least stripping off
# entered
winner = input("Who was the winner? ").strip()
try:
winner = players[winner]
# Prevent cheating! People who are out or folded cannot win...
if winner[1] in ('Fold', 'Out'):
print("That can't be! Must be a different winner...")
continue

# add the pool to the winner's available cash pile
winner[0] += pool
except KeyError:
# selected winner wasn't in the players dictionary
else:
break
else:
# if is_winner is False, the while loop will skip to
# this else statement
print("How on earth was there no winner??")

play_again = input('Play Again? [y/n] ').lower().strip() == 'y'
if not play_again:
print("Ok, goodbye")
sys.exit(0)

# Mark players with no money left as out
no_money_left = [name for name, (money, *_) in players.items() if not money]
if no_money_left:
l = '\n '.join(no_money_left)
print(f"The following players are Out: {l}")
for player in no_money_left:
# sets state in-place
players[player][1] = 'Out'

# reset previous bets and states to default values for players
# who are still in for the next round
for player, status in players.items():
_, state, _ = status
status[2] = 0
if state != 'Out':
status[1] = 'Standby'


if name == "main"

Usually, most programs will want a __name__ == "__main__" guard. This guard will only be True if the program is called like python poker.py but not if it is imported like from poker import main. Also, it might be helpful to pass the players names as sys args:

if __name__ == "__main__":
import sys

players = get_player_dict(sys.argv[1:])
if not players:
# since this is a cli-game anyways, might be helpful to display a
# use message if they called it with no players
usage()
sys.exit(0)

main(players)


Where usage might look like:

def usage():
print("Call this script by passing the names of players as")
print("sys-args like so:")
print(" python poker.py Alice James Steve Laura ")


Odds and Ends

Checking types of objects

It is more pythonic to use isinstance rather than type(obj) == <type>

d = {}

# do this
if isinstance(d, dict):

# not this
if type(d) == dict:


And is is a keyword to stay away from, as it doesn't do exactly what you think it does, even though in this case it gives True, that's not always the case. Take this for example:

a = []
b = []
a is b
False

# however
a == b
True


Checking for emptiness of a container

Checking to see if a dict, list, set, tuple, string, etc is empty should be done using its native __bool__ implementation like this:

d = {}

if not d:
# empty


Tracking an index in a loop

Any time you see this kind of code:

i = 0
for item in collection:
# do something
i += 1


It should be refactored to use enumerate:

for i, item in enumerate(collection):
# do something


Multi-Line Statements

Any time you have a statement that is going on for too long, you can use parentheses rather than \ to wrap a long line:

x = (1 + 2 + 3 + 4
+ 5 + 6 + 7)


I am not certain what you are asking, so I will mention three ideas about your code and hope it helps.

def list_sequence(sequence):


This is a tricky function, mostly because it is really:

def print_players_or_currency(player_list_or_currency_dict)


You should rarely need to look at the type of an object (with type() or isinstance()) and only when doing meta-programming. Make two functions.

Play with f-strings formatting

    for key in sequence:
i += 1
proportional_space = ' ' * ( (max(lengths) - len(key)) + 2 )
print('-'*20)
print(f'{i})-{key}{proportional_space}: {sequence.get(key, 0)}')


could be shorter:

for i, (key, value) in enumerate(sequence.items()):
print(f"{'':->20}\n{i+1:2}-{key:<{max(lengths)}}: {value}")


Always use a main() function

You have about a hundred lines at the top level. The problem is that the top level creates questions about global variables, ordering declarations and so on. It is far better to throw all hundred lines inside a main function and call it. While this might seem odd to insist on, remember that the Python interpreter just runs lines of code from top to bottom while keeping track of name spaces. This means that def foo(): .... is just a statement. The problem is that the top level code creates global variables, can only call functions if the def was seen first, and so on. Save confusion: put all your code inside a function and just have imports and a main()` at the top level.

• fixed, thank you. – Charles Merriam Jan 30 at 6:48