3
\$\begingroup\$

I've noticed I'm not the first to post this topic. As a matter of fact, I have taken some inspiration from Victor Nguyen Bao's post to help structure my code. Here's the link to his code: Structured blackjack game in Python 3

The other source of my code is from Anthony Tapias. https://medium.com/@anthonytapias/build-a-deck-of-cards-with-oo-python-c41913a744d3

I began to learn Python during my intro to computer science class in fall, but I felt I wasn't being challenged enough. I wanted to learn more, but at the same time, not overwhelm myself.

I found many resources thankfully, and my textbook Python Crash Course 2nd Edition has a few things going for it. We stopped right at functions. I wanted to learn more about classes, and felt overwhelmed with Alien Invasion. So, I took a step back and tried to do something more simple like Blackjack.

It turns out that this actually was quite a bit of a challenge for me! Debugging was rather tricky, but it helped to use print statements to make sure certain lines of code were running as intended.

The major issue I am having with my code right now is when the player goes bust, the dealer shouldn't draw anymore cards, and instead should reveal their current hand, display that the player lost, and prompt the player if they wanted to play another game.

The other issue I seem to be having is styling my code to meet PEP8 standards. I've tried to stay consistent with the way I named my classes. Although, these classes did come from another source, the only class I created was the Dealer.

I definitely feel I could clean this code up by reorganizing some lines of code, renaming certain methods, and using better identifiers. I'm hoping to get a healthy amount of constructive criticism. I'm also hoping to be able to create another game from scratch without referencing any other code in the future. What do you think?

blackjack_v1.py

from dealer import Dealer
from card import Card
from deck import Deck
#I commented out certain lines of code for debugging purposes.
class Blackjack:
    """Overall class to manage game assets and behavior."""

    def __init__(self):
        """Initialize the game, and create resources."""
        self.games = 0
        self.main_game_loop = True
        self.player = Player()
        self.dealer = Dealer()
        self.first_turn_new_game = False
        self.turns = False

    def run(self):
        """Begins the main loop to run the game."""
        while self.main_game_loop == True:
            deck = Deck()
            deck.shuffle()             
            self.deal_cards(deck)
            print("Your Hand:")
            self.player.blackjack_check()
            self.player.showHand()
            print("Dealer's hand:")
            self.dealer.blackjack_check()
            self.compare_first_hand()
            if(self.first_turn_new_game == True):
                self.another_game()
            else:
                self.player.turn(deck)
                self.dealer.dealer_turn(deck)
                self.compare_hands()
                self.another_game()

    def deal_cards(self, deck):
        """Deals cards to both player and dealer."""
        for i in range(0, 2):
            self.player.draw(deck)
            self.dealer.draw(deck)
        self.player.ace_check()
        self.dealer.ace_check()

    def compare_first_hand(self):
        """Compares the first hand between players."""
       # print(f"The truth value of player.blackjack is currently {self.player.blackjack == True}")
        #print(f"The turth value of dealer.blackjack is currently {self.dealer.blackjack == True}")
        if (self.player.blackjack == True) and (self.dealer.blackjack == True):
            self.dealer.showHand()
            self.player.ties += 1
            self.dealer.ties += 1
            print("The game is a tie!")           
            self.first_turn_new_game = True
        elif (self.player.blackjack == True) and (self.dealer.blackjack == False):
            self.dealer.showHand()
            self.player.wins += 1
            self.dealer.losses += 1
            print("You win!")
            #print(f"The truth value of player.blackjack should be True. It is {self.player.blackjack == True}")
            #print(f"The turth value of dealer.blackjack should be False. It is {self.dealer.blackjack == True}")
            self.first_turn_new_game = True
        elif (self.player.blackjack == False) and (self.dealer.blackjack == True):
            self.dealer.showHand()
            self.player.losses += 1
            self.dealer.wins += 1
            print("You lost!")
            #print(f"The truth value of player.blackjack should be False. It is {self.player.blackjack == True}")
            #print(f"The turth value of dealer.blackjack should be True. It is {self.dealer.blackjack == True}")
            self.first_turn_new_game = True
            #print(f"first_turn_new_game should be true. It is {self.first_turn_new_game == True}")
        else:
            self.turns = True
            #print(f"Turns should be  True. It is {self.turns == True}")
            self.dealer.show_initial_hand()

    def compare_hands(self):
        """Compares hand between players."""
        if (self.player.blackjack == True) and (self.dealer.blackjack == True):
            self.player.ties += 1
            self.dealer.ties += 1
            print("The game is a tie!")
        elif (self.player.blackjack == True) and (self.dealer.bust == False) and (self.dealer.blackjack == False):
            self.player.wins += 1
            self.dealer.losses += 1
            print("You win!")
        elif (self.player.bust == True) and (self.dealer.blackjack == True):
            self.player.losses += 1
            self.dealer.wins += 1
            print("You lost!")
        elif (self.player.bust == False) and (self.player.blackjack == False) and (self.dealer.blackjack == True):
            self.player.losses += 1
            self.dealer.wins += 1
            print("You lost!")
        elif (self.player.bust == True):
            self.player.losses += 1
            self.dealer.wins += 1
            print("You lost!")
        elif (self.dealer.bust == True) and (self.player.bust == False) and (self.player.blackjack == False):
            self.player.wins += 1
            self.dealer.losses += 1
            print("You win!")
        else:
            player_hand = []
            dealer_hand = []
            for card in self.player.hand:
                player_hand.append(card.face_value)
            for card in self.dealer.hand:
                dealer_hand.append(card.face_value)
            if sum(player_hand) > sum(dealer_hand):
                self.player.wins += 1
                self.dealer.losses += 1
                print("You win!")
            elif sum(player_hand) == sum(dealer_hand):
                self.player.ties += 1
                self.dealer.ties += 1
                print("The game is a tie!")           
            elif sum(player_hand) < sum(dealer_hand):
                self.player.losses += 1
                self.dealer.wins += 1
                print("You lost!")

    def reset_variables(self):
        """Resets all variables to set up for the next game."""
        for p in [self.player, self.dealer]:
            p.hand = []
            #print("The length of {} hand is {}".format(p, len(p.hand)))
            p.blackjack = False
            #print("The truth value of blackjack for {} should be False. This statement is {}".format(p, p.blackjack == False))
            p.bust = False
            #print("The truth value of bust for {} should be False. This statement is {}".format(p, p.bust == False))
            p.stand = False
            #print("The truth value of stand for {} should be False. This statement is {}".format(p, p.stand == False))
        self.first_turn_new_game = False
        #print("The truth value of first_turn_new_game for {} should be False. This statement is {}".format(self, self.first_turn_new_game == False))
        self.turns = False
        #print("The truth value of turns should be False. This statement is {}".format(self.turns == False))

    def final_results(self):
        """Display the overall end results across all games."""
        print("Final results:")
        print("Your wins: {} | Your losses: {} Ties {}".format(self.player.wins, self.player.losses, self.player.ties))
        print("Thank you for playing!")

    def another_game(self):
        """Asks user if they would like to play again. Resets game."""
        while(True):
            play_again_yes_options = ["yes", "YES", "Yes", "Y", "y"]
            play_again_no_options = ["no", "NO", "No", "N", "n"]
            play_again = input("Would you like to play again? Yes or No: ")
            if play_again in play_again_yes_options:
                self.reset_variables()
                break
            elif play_again in play_again_no_options:
                self.final_results()
                self.main_game_loop = False
                break
            else:
                print(f"{play_again} is not a valid response.")
if __name__ == '__main__':
    game = Blackjack()
    game.run()

dealer.py

from player import Player
class Dealer(Player):
    """Model of dealer AI."""

    def __init__(self):
        """Initialize attributes of the parent class"""
        super().__init__()

    def show_initial_hand(self):
        """Shows first hand."""
        self.hand[0].show_card()
        print(f"The value of the Dealer's face-up card is: {self.hand[0].face_value}")

    def dealer_turn(self, deck):
        """Dealer's actions during the game."""
        check_list_17 = []
        for card in self.hand:
            check_list_17.append(card.face_value)
        while sum(check_list_17) < 17:
            self.draw(deck)
            self.ace_check()
            self.blackjack_check()
            check_list_17.append(self.hand[-1].face_value)
        print("Dealer hand: ")
        self.showHand()

player.py

class Player:
    """Model of a user."""

    def __init__(self):
        """Initializes attributes of a player."""
        self.hand = []
        self.wins = 0
        self.losses = 0
        self.ties = 0
        self.blackjack = False
        self.bust = False
        self.stand = False

    def turn(self, deck):
        """Player's initial hand."""
        hit_options = ["H", "h", "HIT", "Hit", "hit", "Hit me", "hit me"]
        stand_options = ["S", "s", "STAND", "Stand", "stand"]
        while(self.stand == False) and (self.bust == False) and (self.blackjack == False):
            hit_or_stand = input("Hit or Stand?: \n")
            if hit_or_stand in hit_options:
                self.draw(deck)
                self.ace_check()
                self.blackjack_check()
                print("Your current hand is:")
                self.showHand()
            elif hit_or_stand in stand_options:
                self.stand = True
                break
            else:
                print(f"'{hit_or_stand}' is not a valid response.")

    def draw(self, deck):
        """Draws card and appends the card to hand."""
        self.hand.append(deck.drawCard())

    def showHand(self):
        for card in self.hand:
            card.show_card()
        player_hand_total_check = []
        for card in self.hand:
            player_hand_total_check.append(card.face_value)
        PLAYER_HAND_TOTAL = sum(player_hand_total_check)
        print(f"The total of the hand is: {PLAYER_HAND_TOTAL}")

    def ace_check(self):
        """Check's for ace in hand and evaluates to appropriate face value."""
        hand_ace_check_list = []
        number_of_cards_in_hand = len(self.hand)
        for card in self.hand:
            hand_ace_check_list.append(card.face_value)
        hand_check_TOTAL = sum(hand_ace_check_list)
        if 11 in hand_ace_check_list and hand_check_TOTAL > 21:
            count = 0
            for card in self.hand:
                if card.face_value == 11:
                    card.face_value = 1
                if hand_ace_check_list[count] == 11:
                    hand_ace_check_list[count] = 1
                if sum(hand_ace_check_list) <= 21:
                    break
                count += 1

    def blackjack_check(self):
        """Check's hand for blackjack or bust."""
        hand_list = []
        for card in self.hand:
            hand_list.append(card.face_value)
        if sum(hand_list) == 21:
            self.blackjack = True
        elif sum(hand_list) > 21:
            self.bust = True

card.py

class Card:
    """Model of a playing card."""

    def __init__(self, rank, face_value, suit):
        """Initializes attributes of a card."""
    
        self.rank = rank
        self.face_value = face_value
        self.suit = suit

    def show_card(self):
        """Reveals the rank and suit of card."""
        if self.rank == 1:
            print("{} of {}, valued at {}".format("Ace", self.suit, self.face_value))
        elif self.rank == 11:
            print("{} of {}".format("Jack", self.suit))
        elif self.rank == 12:
            print("{} of {}".format("Queen", self.suit))
        elif self.rank == 13:
            print("{} of {}".format("King", self.suit))
        else:
            print("{} of {}".format(self.rank, self.suit))

deck.py

from random import randint
from card import Card
class Deck:
    """Model of a deck of 1-8 decks of playing cards, with 52 cards per deck."""

    def __init__(self):
        """Initializes attributes of a deck."""
        self.cards = []
        self.build()

    def build(self):
        """Creates a deck of cards."""
        face_cards_ranks = [11, 12, 13]
        for i in range(0,8):
            for s in ["Clubs", "Diamonds", "Spades", "Hearts"]:
                for r in range(1, 14):
                    if r == 1:
                        face = 11
                        self.cards.append(Card(r, face, s))
                    elif r in face_cards_ranks:
                        face = 10
                        self.cards.append(Card(r, face, s))
                    else:
                        face = r
                        self.cards.append(Card(r, face, s))

    def shuffle(self):
        """Shuffles cards using the Fisher Yates shuffle"""
        for i in range(len(self.cards) - 1, 0, -1):
            r = randint(0, i)
            self.cards[i], self.cards[r] = self.cards[r], self.cards[i]

    def drawCard(self):
        """Draws a card from the deck"""
        return self.cards.pop()

    def show(self):
        """Shows every card in the deck."""
        for c in self.cards:
            c.show_card()
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You did a good job partitioning the code into files and classes. Many of the classes, functions and variables have meaningful names, and many of the docstrings are helpful.

Here are some adjustments for you to consider, mainly for coding style.

Usage

You made it easy to run the code, and following the input prompts is simple.

I recommend adding a blank line to the output to visually separate one game from the next. In the another_game function, you can add a print:

        if play_again in play_again_yes_options:
            self.reset_variables()
            print()
            break

Another recommendation is to add some indentation in the output. For example, in show_card, change:

        print("{} of {}".format("King", self.suit))

to:

        print("    {} of {}".format("King", self.suit))

This is how the new output would look:

Your Hand:
    5 of Clubs
    Queen of Hearts
The total of the hand is: 15
Dealer's hand:
    3 of Hearts
The value of the Dealer's face-up card is: 3

Input

It is great that the code allows me to be lazy and just type h instead of Hit.

You could simplify the code in the turn function while preserving this behavior by just seeing if the user input words starting with h or s. This would eliminate a couple variables:

def turn(self, deck):
    """Player's initial hand."""
    while(self.stand == False) and (self.bust == False) and (self.blackjack == False):
        hit_or_stand = input("Hit or Stand?: \n")
        if hit_or_stand[0].lower() == 'h':
            self.draw(deck)
            self.ace_check()
            self.blackjack_check()
            print("Your current hand is:")
            self.showHand()
        elif hit_or_stand[0].lower() == 's':

Commented code

blackjack_v1.py has many commented-out lines of code, and these create a lot of clutter. Just delete them.

Unused code

This code in player.py is not used and can be deleted:

    number_of_cards_in_hand = len(self.hand)

DRY

This line is repeated 3 times, in each branch of the if/else:

self.cards.append(Card(r, face, s))

It can be moved outside of the if/else:

            for r in range(1, 14):
                if r == 1:
                    face = 11
                elif r in face_cards_ranks:
                    face = 10
                else:
                    face = r
                self.cards.append(Card(r, face, s))

f strings

You use f strings in some places in the code, but .format() in other places. Use f strings wherever you can to simplify the code. For example, in card.py, change:

        print("{} of {}".format(self.rank, self.suit))

to:

        print(f"{self.rank} of {self.suit}")

Naming

Use more consistent naming for your function names. The PEP 8 style guide recommends:

  • drawCard should be draw_card
  • showHand should be show_hand

build would be more meaningful as create_deck

In deck.py, variable s would be mode descriptive as suit and r as rank.

Docstrings

The docstrings for your __init__ functions are not very descriptive:

def __init__(self):
    """Initializes attributes of a player."""

You can delete them or make them more meaningful.

Lint check

pylint identified several of the issues mentioned above.

\$\endgroup\$

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.