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
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Is this homework? (in other words, is it your prof that restricted your library use?) \$\endgroup\$
    – Reinderien
    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\$
    – Beginner
    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.

\$\endgroup\$
3
  • \$\begingroup\$ Appreciate the feedback, will take your advice into account moving forward \$\endgroup\$
    – Beginner
    Commented Oct 11, 2022 at 19:48
  • \$\begingroup\$ @AJNeufeld Don't you mean lowerCamelCase insetad of bumpyWord? \$\endgroup\$ Commented Nov 7 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\$
    – AJNeufeld
    Commented Nov 7 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.
"""
\$\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 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 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
    Commented Nov 7 at 12:04

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.