Week 13: More Classes and Objects

Week 13 Goals

  • Gain more practice writing classes

  • Import classes and use instances of one class to build another

Week 13 Files

  • card.py: A Card class for playing card-based games.

  • deck.py: A Deck class that represents a deck of cards.

  • blackjack_hand.py: A BlackjackHand class that represents one hand in a game of blackjack.

  • blackjack.py: Full implementation of a game of blackjack.

Recap of terminology and key concepts

  • A class is a template for a new type.

  • Every object is an instance of some class.

  • The data within a class is stored in instance variables (using self.<variablename>).

  • The operations within a class are called methods and always include self as their first parameter.

  • The __init__ method is the constructor. The constructor creates an instance of the class.

  • The __str__ method is called when to convert your object into a string. This method should return a string, not print.

  • getter methods allow you to access information within the object.

  • The syntax if __name__ == "__main__": at the bottom of the file that contains the class definition allows you to test the class while making sure that code doesn’t run when you import the class into another file.

Relationship Between Classes

One of the powerful things about object-oriented programming and being able to define our own custom classes is that we can aggregate them into more complex structures.

In this example, we’ll create a class Deck which represents a collection of Card objects.

First, here’s the definition of the Card class:

Card

  • Constructor: construct a new card

    • parameters: rank, suit (strings)

    • later we will try to enforce that users only provide legal values

      class Card:
          """
          Represents a playing card with a suit and a rank
          """
      
          def __init__(self, rank, suit):
              """
              Construct a new card
              Parameters: rank and suit should be strings
              """
              self.rank = rank
              self.suit = suit
  • String method:

    • return a string representing the card

    • use T instead of 10 so it looks nicer when printing

    • we can try to use emojis (import emoji)

          def __str__(self):
              """ Create a string representation of the card """
              suit = self.suit
              rank = self.rank
              if rank == '10':
                  rank = 'T'  # make the "10" 1-character wide in the display
              return "%s %s" % (rank, suit)
  • Getters:

    • get_rank, get_suit

    • get_value (some card games return values for each card) where A=1 and K,Q,J=10

          def get_rank(self):
              return self.rank
      
          def get_suit(self):
              return self.suit
      
          def get_value(self):
              """ Some card games assign values to each rank """
              if self.rank == 'A':
                  return 1
              elif self.rank in ['K', 'Q', 'J']:
                  return 10
              else:
                  return int(self.rank)

Testing the Card Class

Let’s test this Card class out in a main function that only gets called if we’re running this file directly.

Deck

Now we can create a class Deck that includes a list of Card objects as one of its instance variables. A Deck is going to contain all 52 of the possible cards.

  • data:

    • deck (list of Card objects)

  • methods:

    • shuffle

    • deal

    • reset_deck (put all the cards back in the Deck)

Since we’re going to make the Deck out of Card objects, we should import the Card class into our file.

from card import Card
from random import shuffle

class Deck:
    """ Represents a standard deck of 52 playing cards """

    def __init__(self):
        """ Calls method to create a list of all possible cards """
        self.reset_deck()

    def __str__(self):
        """ Returns a string representing the entire deck """
        result = ""
        for i in range(len(self.deck)):
            result += "%3s " % (self.deck[i])
            if (i+1) % 26 == 0:
                result += "\n"
        return result

    def __len__(self):
        """ The length of a Deck is the number of cards in the deck """
        return len(self.deck)

    def reset_deck(self):
        """ Resets the deck with all 52 possible cards """
        self.deck = []
        ranks = ['A','2','3','4','5','6','7','8','9','10','J','Q','K']
        suits = ['heart', 'club', 'diamond', 'spade']
        for suit in suits:
            for rank in ranks:
                self.deck.append(Card(rank, suit))

    def shuffle(self):
        """ Shuffles the order of the cards in the deck """
        shuffle(self.deck)

    def deal(self):
        """ Deals out the top card from the deck """
        return self.deck.pop(0)
  • The constructor has one line: self.reset_deck

    • This method will allow us to reset the deck.

  • reset_deck

    • Creates a new list of all 52 cards

    • Creates self.deck which is an empty list

    • Outer loop for each suit, inner loop for each rank

    • Create a new Card and append to self.deck

  • __str__

    • Build up a string with multiple lines (if necessary)

  • __len__

    • The __len__ method gets called when you call len() on an object

  • shuffle

    • Use the random library to shuffle our cards

  • deal

    • remove and return the first card using the list.pop method

Testing the Deck Class

Let’s test this Deck class out in a main function that only gets called if we’re running this file directly.

Blackjack

Our next goal is to use the Deck and Card classes to build a blackjack game.

Rules

  • Played at casinos and usually involves betting

  • Players play against the dealer

  • Each player, including the dealer, starts with 2 cards

  • A player can choose to draw or hold

  • The goal for every player is to get as high a score as possible while still being less than or equal to 21.

  • The score is based on the ranks of the cards in your hand

    • 2 is worth 2, 3 is worth 3, and so on

    • Face cards (J,Q,K) are worth 10

    • Ace is worth 1 or 11 depending on what is more beneficial to the player

  • If you get to 21 exactly, this is called "blackjack"

  • If you go over 21, you "bust" and lose

  • For our version of the game, the dealer has an algorithm that it follows:

    • If the dealer has fewer than 17 points, it must draw

    • If the dealer has 17 or more points, it must hold

We will play a simplified version with no betting.

We will play multiple rounds until the user chooses to quit.

BlackjackHand Class

We’ll start by defining a new class called BlackjackHand in the file blackjack_hand.py. This class will represent one hand in a game of blackjack. It should support the following methods:

  • __str__

    • Build up and return a string to produce a human-readable representation of this blackjack hand.

  • __len__

    • The __len__ method gets called when you call len() on an object. For a blackjack hand, it should return the number of cards in the hand.

  • get_name

    • Return the name of the hand’s player.

  • add_card

    • Add a card to the hand (the card to add is provided as a parameter).

  • get_value

    • Produce and return a total score (an integer) that this hand represents according to the rules above.

    • 2-10: value is 2-10

    • JQK: value is 10

    • A is 1 or 11

    • We want to return the highest value that (if possible) doesn’t exceed 21.

      • First we sum up all the cards assuming Ace is 11

      • As long as we have more than a score of 21 and some Aces we are still considering 11, consider one of the Aces as 1

Blackjack

Now we can finally implement the blackjack game: a conventional program with functions!

It uses the Deck class and the BlackjackHand class and it’s going to implement a blackjack game.

The general design of the game is:

  • Show instructions

  • Get player’s name

  • Create and shuffle a deck of cards

  • Continue until the user quits:

    • player takes their turn

      • deal two cards to the player

      • ask them to draw or hold and verify input

      • if player has more than 21, they lose and the dealer doesn’t have to go

    • dealer takes their turn

      • deal two cards to the dealer

      • if dealer has more than 21, they lose

      • If the dealer has fewer than 17 points, it must draw

      • If the dealer has 17 or more points, it must hold

    • increment appropriate player_wins or dealer_wins counter

    • print appropriate message ("Player wins!", "Dealer wins!")

    • ask if they want to quit

  • Show final stats