3
\$\begingroup\$

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
toolic
14.4k5 gold badges29 silver badges201 bronze badges
asked Oct 10, 2022 at 19:21
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Is this homework? (in other words, is it your prof that restricted your library use?) \$\endgroup\$ Commented 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\$ Commented Oct 11, 2022 at 4:41

2 Answers 2

6
\$\begingroup\$

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.

answered Oct 11, 2022 at 4:52
\$\endgroup\$
3
  • \$\begingroup\$ Appreciate the feedback, will take your advice into account moving forward \$\endgroup\$ Commented Oct 11, 2022 at 19:48
  • \$\begingroup\$ @AJNeufeld Don't you mean lowerCamelCase insetad of bumpyWord? \$\endgroup\$ Commented Nov 7, 2024 at 1:04
  • \$\begingroup\$ @Daemons The linked document actually refers to them (now?) as mixedCase, but seems to mean the same as lowerCamelCase and bumpyWord ... and are eschewed by PEP 8 in favour of snake_case. \$\endgroup\$ Commented Nov 7, 2024 at 15:33
2
\$\begingroup\$

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.
"""
answered Nov 3, 2024 at 11:27
\$\endgroup\$
3
  • 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\$ Commented 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\$ Commented 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\$ Commented Nov 7, 2024 at 12:04

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.