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
: ACard
class for playing card-based games. -
deck.py
: ADeck
class that represents a deck of cards. -
blackjack_hand.py
: ABlackjackHand
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=10def 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 calllen()
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 calllen()
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