5
\$\begingroup\$

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()
```
200_success
146k22 gold badges190 silver badges479 bronze badges
asked May 4, 2019 at 8:24
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

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, probabilitiesand 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.

answered Aug 15, 2019 at 15:43
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.