This is a simple CLI Blackjack game in python3. I saw that there were others already posted here and tried to implement some of their solutions and logic where I understood it. My game is a little different in that I have a "probability mode". This shows probabilities of bust or blackjack if the user takes another hit. It also shows probabilities of the dealer's hidden card revealing a blackjack (no need for bust probability for dealer because you can't bust on just 2 cards).
There are three classes used as a way to store variables that I want to access throughout the program. This avoids having to pass in arguments all the time. In my opinion this makes for more easily understandable code, but I'm not very experienced so I don't know if this is best practices. Or if I should use more classes... or expand on these and not have so many functions in the global space?
I'm also wondering about my use of try-except in the ask_for_bet() function. Is this the correct usage? Should I use it elsewhere?
import random
import time
from decimal import Decimal
import sys
class Money:
def __init__(self, bank, bet):
self._bank = bank
self._bet = bet
# Getter/setter for player bank
@property
def bank(self):
return round(Decimal(self._bank), 2)
@bank.setter
def bank(self, amount):
self._bank = amount
# Getter/setter for bet
@property
def bet(self):
return round(Decimal(self._bet), 2)
@bet.setter
def bet(self, amount):
self._bet = amount
m = Money(100, 0)
class Prob_mode:
def __init__(self, value):
self._value = value
# Getter/setter for probability mode
@property
def value(self):
return self._value
@value.setter
def value(self, boolean):
self._value = boolean
p = Prob_mode(False)
class Cards:
def __init__(self, dealer_total, player_total):
self._deck = []
self._player_hand = []
self._dealer_hand = []
self._dealer_total = dealer_total
self._player_total = player_total
# Getter/setter for dealer_total
@property
def dealer_total(self):
return self._dealer_total
@dealer_total.setter
def dealer_total(self, amount):
self._dealer_total = amount
# Getter/setter for player_total
@property
def player_total(self):
return self._player_total
@player_total.setter
def player_total(self, amount):
self._player_total = amount
c = Cards(0, 0)
#### Card handling functions ####
def draw_card():
popped_card = c.deck.pop(random.randint(0, len(c.deck) - 1))
return popped_card
def initial_deal():
for _ in range(0, 2):
c.player_hand.append(draw_card())
c.dealer_hand.append(draw_card())
hand_value(c.dealer_hand, 'dealer')
hand_value(c.player_hand, 'player')
def cardify_full_hand(hand):
cardified_hand = []
for i in range(0, (len(hand))):
card = f"|{hand[i]}|"
cardified_hand.append(card)
return ' '.join(cardified_hand)
def cardify_dealer_initial():
card1 = f"|{c.dealer_hand[0]}|"
card2 = "|≈|"
cardified_hand = [card1, card2]
return ' '.join(cardified_hand)
def hand_value(hand, who=None):
sum_of_hand = 0
ace_count = hand.count('A')
face_count = hand.count('K') + hand.count('Q') + hand.count('J')
for card in hand:
if (type(card) == int):
sum_of_hand += card
sum_of_hand += 10 * face_count
if (ace_count > 0):
if (sum_of_hand >= 11):
sum_of_hand += ace_count
else:
sum_of_hand += (11 + ace_count - 1)
if who == "dealer":
c.dealer_total = sum_of_hand
elif who == "player":
c.player_total = sum_of_hand
else:
return sum_of_hand
#### Display functions ####
def line():
sleepy_print('-----------------------------------------------')
def sleepy_print(string):
time.sleep(.5)
print(string)
time.sleep(.5)
def error_msg():
sleepy_print("\n\t!*** Invalid choice, try again ***!\n")
def update_total():
sleepy_print(f"\nNow you have ${m.bank}\n")
# Bankrupcy test
if (m.bank < 5):
input("\n\tYou don't have enough money. Hit ENTER to restart game\n")
m.bank = 100
menu()
else:
player_choice = input('ENTER for new game or "M" to go back to menu\n').upper()
if (player_choice == ""):
start_game()
elif (player_choice == 'M'):
time.sleep(.5)
menu()
else:
error_msg()
update_total()
def display_hands_before_flip():
line()
dealer_display = f"\nDealer hand = {cardify_dealer_initial()}"
player_display = f"\nPlayer hand = {cardify_full_hand(c.player_hand)}"
if p.value:
dealer_display += f" --------probability--> {blackjack_prob_msg(hand_value([c.dealer_hand[0]]))}"
player_display += f" --------probability--> {blackjack_prob_msg(c.player_total)} {bust_prob_msg(c.player_total)}"
print(dealer_display + '\n')
print(f" * The bet is ${m.bet} *")
print(player_display + '\n')
line()
def display_hands_after_flip():
line()
print("\nDealer's draw...")
time.sleep(.5)
print(f"\nDealer hand = {cardify_full_hand(c.dealer_hand)}")
print(f"Total = {c.dealer_total}\n")
print(f"Player hand = {cardify_full_hand(c.player_hand)}")
print(f"Total = {c.player_total}\n")
#### Probability functions ####
def deck_list_to_nums():
deck_of_all_nums = []
for card in c.deck:
if ((card == 'K') | (card == 'Q') | (card == 'J')):
deck_of_all_nums.append(10)
elif (card == 'A'):
deck_of_all_nums.append(1)
else:
deck_of_all_nums.append(card)
return deck_of_all_nums
def bust_prob_msg(hand_total):
danger_card_count = 0
if hand_total >= 12:
for i in deck_list_to_nums():
if ((hand_total + i) > 21):
danger_card_count += 1
percent = Decimal(danger_card_count/len(c.deck)) * 100
return f"Bust = %{round(percent, 2)}"
def blackjack_prob_msg(hand_total):
a_count = c.deck.count('A')
bj_card_count = 0
if (hand_total == 10):
bj_card_count = a_count
elif (hand_total >= 11):
for i in deck_list_to_nums():
if ((hand_total + i) == 21):
bj_card_count += 1
percent = Decimal(bj_card_count/len(c.deck)) * 100
return f"Blackjack = %{round(percent, 2)}"
#### Outcomes ####
def player_win():
if (c.player_total == 21):
blackjack()
else:
if (c.dealer_total > 21):
print("\t\t Dealer BUSTED...\n")
m.bank += m.bet * 2
sleepy_print(" ++You win!++\n")
def blackjack():
time.sleep(.5)
print(" $ $ $ $ $ $ $ $")
time.sleep(.05)
print(" $ $ BLACKJACK $ $")
time.sleep(.05)
print(" $ $ 1.5x $ $")
time.sleep(.05)
print(" $ Win! $")
time.sleep(.05)
print(" $ $ $")
time.sleep(.05)
print(" $ $")
time.sleep(.05)
print(" $")
m.bank += (m.bet * Decimal(2.5))
def dealer_win():
sleepy_print(" --Dealer won--\n")
def tie(both_bust=False):
if both_bust:
print(("\t You and the dealer both BUSTED...\n"))
elif (c.player_total == 21):
print(("\t You and the dealer both got Blackjack...\n"))
sleepy_print(" ~~It's a tie~~\n")
m.bank += m.bet
#### Main gameplay and run functions ####
def ask_for_bet():
print(f"\nMax bet = ${m.bank}")
add_bet = input("Hit ENTER to bet the minimum (5ドル.00) or input higher amount: $")
if (add_bet == ""):
m.bank = m.bank - 5
m.bet = 5
return
try:
add_bet = round(Decimal(add_bet), 2)
if (add_bet > m.bank):
sleepy_print("\n\t!*** You don't have enough money to afford that bet! Try again ***!")
ask_for_bet()
elif (add_bet < 5):
sleepy_print("\n\t!*** Bet is too low. Must meet the minimum ***!")
ask_for_bet()
else:
m.bet += add_bet
m.bank -= add_bet
except:
error_msg()
ask_for_bet()
def player_hit_stand():
# If blackjack
if (c.player_total == 21):
input("You have 21! Hit ENTER to see what the dealer has...")
return
player_choice = input('Do you want to hit ("H") or stand ("S")?\nEnter response: ').upper()
if (player_choice == 'H'):
c.player_hand.append(draw_card())
hand_value(c.player_hand, 'player')
# If bust
if (c.player_total > 21):
display_hands_before_flip()
input("You BUSTED! Hit ENTER to see what the dealer has...")
return
# If still in the game (player total < 21 )
else:
time.sleep(.5)
display_hands_before_flip()
player_hit_stand()
elif (player_choice == 'S'):
return
else:
error_msg()
player_hit_stand()
def dealer_hit_stand():
# Dealer hits until 17 or higher is reached
while (c.dealer_total < 17):
c.dealer_hand.append(draw_card())
hand_value(c.dealer_hand, 'dealer')
def who_wins():
difference = c.player_total - c.dealer_total
# Bust outcomes
if ((c.player_total > 21) | (c.dealer_total > 21)):
if ((c.player_total > 21) & (c.dealer_total > 21)):
tie(both_bust = True)
elif (c.player_total > 21):
dealer_win()
else:
player_win()
# All other outcomes
elif (difference == 0):
tie()
elif (difference > 0):
player_win()
else:
dealer_win()
def menu():
# Probability toggle
if p.value:
prob_indicator = 'on'
else:
prob_indicator = 'off'
# Options menu
print(f'\n\n\t Input "P" to toggle probability mode (currently: {prob_indicator})')
print('\t Input "X" to exit the game')
print('\n\t\t--Hit ENTER to deal a new hand--')
input_var = input().upper()
if input_var == "":
start_game()
elif input_var == 'P':
time.sleep(.5)
p.value = not p.value
menu()
elif input_var == 'X':
sleepy_print('\nExiting...\n')
sys.exit()
else:
error_msg()
menu()
def start_game():
# Empty hands, zero bet, and new deck at the start of each hand
c.dealer_hand = []
c.player_hand = []
m.bet = 0
c.deck = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A'] * 4
sleepy_print('\n\n****************** New Hand ******************')
# Play action
ask_for_bet()
initial_deal()
display_hands_before_flip()
player_hit_stand()
dealer_hit_stand()
display_hands_after_flip()
# Ouctomes
who_wins()
update_total()
menu()
```
1 Answer 1
The main problem with your code is that your methods are using the global variables c,p,m
.
Firstly, these variable names could use a little love, naming them cards, probabilities
and money
would be useful, but I'd go one step farther and try to find better class names for those three classes. I'd expect a class Cards
to represent a pack of cards, not the hands of two different players. The point is : When naming a class, ask yourself what that class represents and name it this way. Otherwise, it makes the code difficult to understand.
Second of all, I think you should either encapsulate this code in a class
where c,p,m
would be member variables (self.p, self.m, self.c
), which would make it clearer the variables have already been declared and are supposed to be used. Otherwise, you have methods that can't be called before the three global variables have been initialized and while this works in your current situation, if you were to move your code a little bit it might break, which is a pretty big code smell.
Apart from those two points, I think your code is great.