Old version
This is the CS 111 site as it appeared on December 20, 2022.
Create a subfolder called lab11
within your
cs111
folder, and put all of the files for this lab in that
folder.
Card
objectsTo implement a card game, one type of object that would be useful is an object that represents an individual card from a deck of cards.
Begin by downloading
card.py,
saving it in your lab11
folder.
Open the file in Spyder. You’ll see that we’ve begun to define a class
called Card
that will serve as a blueprint for a single playing card.
Each Card
object has two attributes:
rank
, which is either an int
(for numeric cards) or a
single upper-case letter (for Aces and for face cards like Queens)suit
, which is stored as a single upper-case letter
('C'
for Clubs, 'S'
for Spades, 'H'
for Hearts,
and 'D'
for Diamonds)For example, here’s one way to picture what a Card
object representing
the 4 of Clubs would look like in memory:
+----------------+ | +-----+ | | rank | 4 | | | +-----+ | | +-----+ | | suit | 'C' | | | +-----+ | +----------------+
And here’s a picture of a Card
object representing the Queen of
Diamonds:
+----------------+ | +-----+ | | rank | 'Q' | | | +-----+ | | +-----+ | | suit | 'D' | | | +-----+ | +----------------+
Locate the constructor that we’ve provided for Card
objects.
Notice how it uses the type()
function to determine what to do
with the value passed in for rank
:
If the value of rank
is an int
, it simply stores that
integer value in the rank
attribute (self.rank
).
Otherwise, it assumes that the value passed in for rank
is a
string, and it uses string indexing and the upper()
method
to ensure that the value stored in the suit
attribute is a
single upper-case character.
The constructor also converts the value passed in for suit
(which we assume is a string) to a single upper-case letter.
Run card.py
in Spyder, and enter these statements in the console:
>>> c1 = Card(4, 'C') >>> c1.rank result: 4 >>> c1.suit result: 'C' >>> c2 = Card('queen', 'diamonds') >>> c2.rank result: 'Q' >>> c2.suit result: 'D'
We’ve also given you an initial version of a __repr__
method
for converting a Card
object to a string. It simply constructs
a string consisting of the rank
and suit
, separated by a space:
>>> c2 = Card('Q', 'D') >>> c2 Q D
Although it makes sense to only store a single character for a Card
object’s rank and suit, we’d like the string representation of the
of the object to be more descriptive.
If you look at the top of the file, you’ll see that we’ve given you the following lines of code:
RANK_NAMES = {'A': 'Ace', 'J': 'Jack', 'Q': 'Queen', 'K': 'King'} SUIT_NAMES = {'C': 'Clubs', 'S': 'Spades', 'H': 'Hearts', 'D': 'Diamonds'}
Since we’re putting these lines in the global scope, the variables
RANK_NAMES
and SUIT_NAMES
are global variables (variables that
can be accessed from anywhere in the file), and we’re capitalizing
their names to indicate that fact.
Both of these variables represent dictionaries:
RANK_NAMES
is a dictionary that connects each non-numeric rank
to a more descriptive string for that rank. For example,
it connects the rank 'A'
to the string 'Ace'
.
SUIT_NAMES
is a dictionary that connects each suit character
to a more descriptive string for that suit. For example,
it connects the suit 'C'
to the string 'Clubs'
.
Remember that a dictionary is a set of key-value pairs, and that you can use the key as an index to obtain the value associated with the key. For example:
>>> RANK_NAMES['A'] result: 'Ace' >>> SUIT_NAMES['C'] result: 'Clubs'
Modify the __repr__
method so that it uses these
dictionaries to create and return a string of the form
'
rank-name of
suit-name'
. For example:
>>> c2 = Card('Q', 'D') >>> c2 result: Queen of Diamonds
If the called object’s rank
is of type int
, you should simply
convert it to a string, as we do in the initial __repr__
. Otherwise,
you should use the RANK_NAMES
dictionary to look up the descriptive
name associated with the object’s rank
.
Similarly, you should use the SUIT_NAMES
dictionary to look up the
descriptive name associated with the object’s suit
.
Examine the method called get_value()
in card.py
.
It returns the value of the called Card
object. More specifically:
Card
object has a numeric rank
, it returns the rank
This follows the common convention of giving all face cards a value of 10.
For example:
>>> c1 = Card(4, 'C') >>> c1.get_value() result: 4 >>> c2 = Card('queen', 'diamonds') >>> c2.get_value() result: 10
The current version of get_value()
also gives Aces a value of 10.
Modify the method so that it gives Aces a value of 1 instead:
>>> c3 = Card('A', 'S') >>> c3 result: Ace of Spades >>> c3.get_value() result: 1
Hand
objectsNext, we’ll consider another type of object that would be useful when implementing a card game.
Begin by downloading the following files:
Make sure to save both files in your lab11
folder, and open
them in Spyder.
In hand.py
, we’ve begun to define a class
called Hand
that will serve as a blueprint for objects
that represent a single hand of cards.
Each Hand
object has a single attribute called cards
that
is initially an empty list. As Card
objects are added to the
Hand
using the add_card()
method, they are concentenated to
the end of this list.
In addition, there is a method called num_cards
that can be used
for obtaining the number of cards in the called Hand
object.
After reviewing and understanding the Hand
class, add client
code to lab11_client.py
to accomplish the tasks listed
below.
Create two Card
objects:
c1
c2
Create a single Hand
object and assign it to a variable h1
.
Use the add_card()
method to add both of the Card
objects
to h1
. You will need to call the method twice, once for
each Card
.
Print h1
as follows:
print('first hand:', h1)
Print the string 'number of cards:'
, followed by the value
obtained by calling the num_cards()
method on h1
.
Run lab11_client.py
, and you should see the following output:
first hand: [7 of Hearts, Ace of Diamonds] number of cards: 2
As part of the Hand
class, add a method called get_value()
that returns the total value of the cards in the called Hand
object (self
). Make sure to indent it under the class header.
You will need to perform a cumulative computation, and you should
use the Card
version of the get_value()
method to determine
the individual value of each Card
object in the Hand
.
To test your new method, add the following line to the end of your client code:
print('total value:', h1.get_value())
Rerun the program, and you should now see:
first hand: [7 of Hearts, Ace of Diamonds] number of cards: 2 total value: 8
As part of the Hand
class, add a method called has_any()
that
takes a card rank as its only input, and that returns True
if
there is at least one occurrence of a Card
with that rank in the
called Hand
object (self
), and that returns False
if there are
no occurrences of that rank in the Hand
. (Note that the suits of
the Card
objects don’t matter in this method; we’re only looking
at their ranks.)
To test your new method, add the following lines to the end of your client code:
print('has at least one 7:', h1.has_any(7)) print('has at least one Ace:', h1.has_any('A')) print('has at least one Queen:', h1.has_any('Q'))
Rerun the program, and you should now see:
first hand: [7 of Hearts, Ace of Diamonds] number of cards: 2 total value: 8 has at least one 7: True has at least one Ace: True has at least one Queen: False
If we were implementing a game of Blackjack, we would need an object
for a hand of cards. Our existing Hand
class has most of the
functionality that we need, but we’d like the value of a hand to be
computed somewhat differently. Rather than defining a completely new
class, we’ll take advantage of inheritance to define a subclass of the
Hand
class.
In hand.py
, write a header for a class called BlackjackHand
that
is a subclass of the Hand
class:
class BlackjackHand(Hand):
The use of (Hand)
after the name of the new class causes the new
class to inherit all of the attributes and methods of the Hand
class.
Because we’re not adding any new attributes to objects of this class, we don’t even need to write a new constructor!
As discussed above, we’d like the value of a BlackjackHand
to be computed somewhat differently than the value of a regular Hand
.
In particular, if a BlackjackHand
has one or more Aces, we want
to give one of the Aces a value of 11 unless doing so would lead the
hand to have a total value that is greater than 21.
Override the inherited get_value()
method by writing a new version of
get_value()
that is part of the BlackjackHand
class.
The new method should start by determining the standard value of
the hand. You can do so by calling the inherited version of
get_value()
, and because that inherited method is being
overriden, you will need to use super()
to access it:
value = super().get_value()
Once you have this value, determine if the called object has any
Aces in it. (Hint: Use one of the other inherited Hand
methods.)
If it does, determine whether you can afford to count one
of the Aces as being worth 11 instead of 1, and adjust the value
as needed. Finally, return the value of the hand.
To test your new subclass, cut and paste the following client code
into the bottom of your lab11_client.py
file:
print() h2 = BlackjackHand() h2.add_card(c1) h2.add_card(c2) print('second hand:', h2) print('number of cards:', h2.num_cards()) print('total value:', h2.get_value()) print() print('adding a card to h2...') c3 = Card(4, 'C') h2.add_card(c3) print('updated second hand:', h2) print('number of cards:', h2.num_cards()) print('total value:', h2.get_value())
If your new class is working correctly, you’ll see the following new lines of output:
second hand: [7 of Hearts, Ace of Diamonds] number of cards: 2 total value: 18 adding a card to h2... updated second hand: [7 of Hearts, Ace of Diamonds, 4 of Clubs] number of cards: 3 total value: 12
Note that the second hand (which starts out with the same cards as as the first hand), originally has a value of 18, since the Ace is counted as 11. However, once we add a third card to it, its value becomes 12; counting the Ace as 11 would bring the total value of the hand over 21, so we count the Ace as 1 instead.
Last updated on January 6, 2023.