Blackjack card game with hit and stand.
I wasn't allowed to use functions outside of classes in the code.
I had some difficulty with __str__
dunder method on Dealer
and Player
classes (I got weird solutions for the time being)
I would appreciate any feedback on how to improve readability and make the code more efficient.
Code
#Blackjack
#imports
import random
import time
from IPython.display import clear_output
#dictionaries
ranks = ['Ace', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Jack', 'Queen', 'King']
suits = ['Diamonds', 'Spades', 'Clubs', 'Hearts']
values = {'Ace':1, 'Two':2, 'Three':3, 'Four':4, 'Five':5, 'Six':6, 'Seven':7, 'Eight':8, 'Nine':9, 'Ten':10, 'Jack':10, 'Queen':10, 'King':10}
#card class
class Card():
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit
self.value = values[rank]
def __str__(self):
return f'{self.rank} of {self.suit}'
#deck class
class Deck():
def __init__(self):
self.deck_cards = []
for rank in ranks:
for suit in suits:
self.deck_cards.append(Card(rank,suit))
def shuffle(self):
random.shuffle(self.deck_cards)
def dealCards(self):
return self.deck_cards.pop(0)
def __str__(self):
for item in self.deck_cards:
print(item)
return 'Joker'
#hand class
class Player():
def __init__(self):
self.hand = []
def addCard(self, card):
self.hand.append(card)
def handSum(self):
card_sum = 0
for card in self.hand:
card_sum += card.value
return card_sum
def __str__(self):
print('------------\nPlayer Cards\n------------')
for item in self.hand:
print(item)
print(f"{player_name}'s card sum: {player1.handSum()}")
return('')
#chip class
class Chips():
def __init__(self, player_name):
self.player_name = player_name
self.chip_count = 3
def betChip(self,num):
self.chip_count -= num
return num
def addChip(self,num):
self.chip_count += num
def subtractChip(self,num):
self.chip_count -= num
def __str__(self):
if self.chip_count == 1:
return f'{self.player_name} has {self.chip_count} chip!'
else:
return f'{self.player_name} has {self.chip_count} chips!'
#dealer class
class Dealer(Player):
def __str__(self):
print('------------\nDealer Cards\n------------')
for item in self.hand:
if item == self.hand[-1]:
pass
else:
print(item)
print('**********')
return str('')
#variable initialization
player_name = input('What is your name?: ')
running = False
game_on = True
gameDeck = Deck()
gameDeck.shuffle()
player1 = Player()
player1_chips = Chips(player_name)
dealer = Dealer()
#game loop
while game_on:
if player1_chips.chip_count == 0:
time.sleep(3)
clear_output()
print('You ran out of chips\nThanks for playing')
break
else:
pass
gameDeck = Deck()
gameDeck.shuffle()
player1 = Player()
dealer = Dealer()
ready_game = input('Ready for a round of BlackJack?(Y/N) ')
if ready_game == 'Y' or ready_game == 'y':
clear_output()
running = True
elif ready_game == 'N' or ready_game == 'n':
clear_output()
print('Thanks for playing!')
break
else:
clear_output()
print("Please input 'Y' or 'N'")
continue
while running:
#placing bets
bet = input(f'You have {player1_chips.chip_count} chips. How much would you like to bet? ')
clear_output()
if bet.isdigit() == True:
bet = int(bet)
if bet > player1_chips.chip_count:
print('Your bet is above your total chip count!')
time.sleep(1)
clear_output()
continue
else:
print(f'You have bet {bet} chips')
time.sleep(1)
clear_output()
else:
print('Your bet is not a number!')
time.sleep(1)
clear_output()
continue
#card initialization
print(f'Distributing card to {player_name}...')
player1.addCard(gameDeck.dealCards())
time.sleep(.85)
clear_output()
print('Distributing card to Dealer...')
dealer.addCard(gameDeck.dealCards())
time.sleep(.85)
clear_output()
print(f'Distributing card to {player_name}...')
player1.addCard(gameDeck.dealCards())
time.sleep(.85)
clear_output()
print('Distributing card to Dealer...')
dealer.addCard(gameDeck.dealCards())
time.sleep(.85)
clear_output()
print('Cards distributed')
clear_output()
print(player1)
print(dealer)
#check for 21:
if player1.handSum() == 21:
print(f'You win and get {bet * 1.5} chips')
player1_chips.addChip(bet * 1.5)
time.sleep(2)
clear_output()
running = False
else:
pass
temp_var = True
#gameplay
while temp_var:
choice = input('Hit(H) or Stand(S)? ')
if choice == 'H' or choice == 'h':
clear_output()
print(f'Distributing card to {player_name}...')
player1.addCard(gameDeck.dealCards())
time.sleep(1)
temp_var = False
elif choice == 'S' or choice == 's':
temp_var = False
clear_output()
else:
clear_output()
print("Please input 'H' or 'S'")
time.sleep(.5)
continue
#printing updated hands
print('Dealer will reveal their cards')
time.sleep(1.3)
clear_output()
print(player1)
print('')
print('------------\nDealer Cards\n------------')
[print(item) for item in dealer.hand[0::]]
print(f"Dealer's card sum: {dealer.handSum()}")
print(' \n \n ')
time.sleep(.9)
#no more user input/game continues
if player1.handSum() == 21:
if dealer.handSum() == 21:
print('You have tied with the dealer')
time.sleep(1)
print('You won no chips. Your total remains the same')
break
else:
print('You win!')
time.sleep(.9)
player1_chips.addChip(bet)
print(f'You have won {bet} chips. Bringing your total up to {player1_chips.chip_count} chips')
break
elif player1.handSum() > 21:
print('You have BUSTED\nYou Lose!')
time.sleep(1)
player1_chips.subtractChip(bet)
print(f'You lost {bet} chips. Bringing your total down to {player1_chips.chip_count} chips')
break
elif dealer.handSum() <= 16:
dealer.addCard(gameDeck.dealCards())
print('Dealer is adding a card to their hand as they have a 16 or less....')
time.sleep(3)
clear_output()
if dealer.handSum() > 21:
#printing hands
##################################################
print(player1)
print('------------\nDealer Cards\n------------')
[print(item) for item in dealer.hand[0::]]
print(f"Dealer's card sum: {dealer.handSum()}")
time.sleep(1)
##################################################
print('You win! Dealer Busts!')
time.sleep(.9)
player1_chips.addChip(bet)
print(f'You have won {bet} chips. Bringing your total up to {player1_chips.chip_count} chips')
break
elif dealer.handSum() > player1.handSum():
#printing hands
##################################################
print(player1)
print('------------\nDealer Cards\n------------')
[print(item) for item in dealer.hand[0::]]
print(f"Dealer's card sum: {dealer.handSum()}")
time.sleep(1)
##################################################
print('You Lose! Dealer got a higher sum of cards!')
time.sleep(.9)
player1_chips.subtractChip(bet)
print(f'You lost {bet} chips. Bringing your total down to {player1_chips.chip_count} chips')
break
else:
if dealer.handSum() > player1.handSum():
print('You Lose! Dealer got a higher sum of cards!')
time.sleep(1)
player1_chips.subtractChip(bet)
print(f'You lost {bet} chips. Bringing your total down to {player1_chips.chip_count} chips')
break
else:
print('You win! Dealer got a lesser sum of cards!')
time.sleep(1)
player1_chips.addChip(bet)
print(f'You have won {bet} chips. Bringing your total up to {player1_chips.chip_count} chips')
break
-
1\$\begingroup\$ Is this homework? (in other words, is it your prof that restricted your library use?) \$\endgroup\$Reinderien– Reinderien2022年10月10日 23:06:26 +00:00Commented Oct 10, 2022 at 23:06
-
2\$\begingroup\$ No not homework. Im doing this for my self using udemy. The objective was understanding OOP better, as to why I was restricted in what I was allowed to use. \$\endgroup\$Beginner– Beginner2022年10月11日 04:41:01 +00:00Commented Oct 11, 2022 at 4:41
2 Answers 2
Don't print in __str__
Your Card
class does things correctly:
>>> card = Card("Ace", "Spades")
>>> s = str(card)
>>> print(s)
Ace of Spades
Notice that str(card)
doesn't print anything. It just silently returns the string for the card.
Where this is useful is the print()
function. It automatically turns everything passed to it into a string. This means you don't have to str(card)
yourself; it is automatic.
>>> print(card)
Ace of Spaces
In contrast, stringifying a dealer produces strange results. First of all, it has a side effect of printing. Second, it returns an empty string!
>>> dealer = Dealer()
>>> s = str(dealer)
------------
Dealer Cards
------------
**********
>>> print(repr(s))
''
This is not the correct behaviour. s = str(something)
should never cause any side effects, including printing. It must simply return a string.
Not referencing self
>>> player_name = "Anna"
>>> player1 = Player()
>>> player1.addCard(Card("Ace", "Spades"))
>>> player_name = "Bob"
>>> player2 = Player()
>>> player2.addCard(Card("Two", "Clubs"))
>>> player_name = "Chris"
>>> print(player1, player2)
What do you think the output is, and why?
After you've tried it out, can you explain why both players appear to have the same name, and the same score despite having a different card?
The section title gives it away, I suppose. You're using player1.handSum()
, not self.handSum()
. You should also be storing the player's name inside the Player
object, and using self.player_name
.
Hidden Dealer's Card
for item in self.hand:
if item == self.hand[-1]:
pass
else:
print(item)
Here, you loop through all the cards in the dealer's hand, compare them with the last card, and if different, they get printed out.
This is too much code, and may cause a subtle bug down the road.
First, the subtle bug: Imagine upgrading the program to use a shoe with 5 decks. Since the Seven of Diamonds will exist 5 times, the dealer's hand could be two Seven of Diamonds. The loop runs with the first card, and compares it with the last card. Are they the same card? Will it print the first card? Maybe not, depending on exactly how you've implemented things.
We can avoid the problem all together, by looping over all but the last card, eliminating the need to compare the current card with the last one:
for item in self.hand[:-1]:
print(item)
The magic here is self.hand[:-1]
, which is a sliced copy of the self.hand
, starting at the beginning and stopping just before the end. Since the sliced copy doesn't include last item, we don't have to test if we've reached it and handle it separately anymore.
PEP 8
The Style Guide for Python Code has many recommendations that all Python programmers should follow. It includes things like naming conventions. addCard
is a member function and should be written in snake_case
, instead of as a bumpyWord
.
There is much more to review feedback to give here, but I'm out of time for now.
-
\$\begingroup\$ Appreciate the feedback, will take your advice into account moving forward \$\endgroup\$Beginner– Beginner2022年10月11日 19:48:11 +00:00Commented Oct 11, 2022 at 19:48
-
\$\begingroup\$ @AJNeufeld Don't you mean
lowerCamelCase
insetad ofbumpyWord
? \$\endgroup\$Otakuwu– Otakuwu2024年11月07日 01:04:34 +00:00Commented Nov 7, 2024 at 1:04 -
\$\begingroup\$ @Daemons The linked document actually refers to them (now?) as
mixedCase
, but seems to mean the same aslowerCamelCase
andbumpyWord
... and are eschewed by PEP 8 in favour ofsnake_case
. \$\endgroup\$AJNeufeld– AJNeufeld2024年11月07日 15:33:56 +00:00Commented Nov 7, 2024 at 15:33
In addition to the comments in the other answer, here are some more suggestions.
Comments
Comments such as the following are unnecessary because they are redundant with the code:
#dealer class
class Dealer(Player):
Instead of comments like that, add a doctring to summarize the code.
DRY
The following 2 return
lines are identical except for one character:
return f'{self.player_name} has {self.chip_count} chip!'
else:
return f'{self.player_name} has {self.chip_count} chips!'
It is great that you are paying such attention to detail in making your message grammatically correct based on the number of chips. However, you could eliminate the repetition with a variable:
def __str__(self):
suffix = 's' if self.chip_count > 1 else ''
return f'{self.player_name} has {self.chip_count} chip{suffix}!'
When I run the code, I sometimes see this output:
You have bet 1 chips
In keeping with the grammar theme, it should print "chip", not "chips".
You could use a similar technique (suffix
variable) on the following code:
print(f'You have bet {bet} chips')
It might make sense to create a function to handle the number of chips.
In the following code, you repeatedly compare against the same variable:
ready_game = input('Ready for a round of BlackJack?(Y/N) ')
if ready_game == 'Y' or ready_game == 'y':
clear_output()
running = True
elif ready_game == 'N' or ready_game == 'n':
It is great that you accept case-insensitive input. You can simplify the code by converting the input to lowercase immediately:
ready_game = input('Ready for a round of BlackJack?(Y/N) ').lower()
if ready_game == 'y':
clear_output()
running = True
elif ready_game == 'n':
Indentation
There is some inconsistent indentation, such as:
def __init__(self, player_name):
self.player_name = player_name
self.chip_count = 1
In most of your code, you use 4-space, but here you have 8-space. The black program can be used to automatically use consistent indentation.
Simpler
There are a couple places where the usage of pass
seems unnecessary. For example:
if player1_chips.chip_count == 0:
time.sleep(3)
clear_output()
print('You ran out of chips\nThanks for playing')
break
else:
pass
I think you can simplify the code by deleting these 2 lines:
else:
pass
Documentation
Add a docstring to the top of the code to summarize its purpose:
"""
Blackjack card game with hit and stand.
"""
-
1\$\begingroup\$ Adding on to the part about the purposes of comments - generally, you should use comments to explain the why of what you're doing, not the what (unless the code is super long (in which case a docstring would be better anyway)). \$\endgroup\$Otakuwu– Otakuwu2024年11月07日 01:07:24 +00:00Commented Nov 7, 2024 at 1:07
-
\$\begingroup\$
chip{suffix}
doesn't translate very well to languages whose plurals are formed in different ways, or where other parts of the sentence need to agree grammatically with the number. \$\endgroup\$Toby Speight– Toby Speight2024年11月07日 08:45:11 +00:00Commented Nov 7, 2024 at 8:45 -
2\$\begingroup\$ @TobySpeight: I agree that this is not a general solution. It was intended as an incremental improvement on the OP code, mainly for purposes of eliminating redundant code. The main point was DRY, as opposed to "here's the best general way to pluralize". \$\endgroup\$toolic– toolic2024年11月07日 12:04:22 +00:00Commented Nov 7, 2024 at 12:04
Explore related questions
See similar questions with these tags.