Old version
This is the CS 111 site as it appeared on May 10, 2018.
Lab 10: Object-oriented programming, part II
Task 0: complete a class for Card
objects
To 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 the following file: card.py
Open it in IDLE. 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:- a
rank
, which is either anint
(for numeric cards) or a single upper-case letter (for Aces and for face cards like Queens) - a
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' | | | +-----+ | +----------------+
- a
-
Locate the constructor that we’ve provided for
Card
objects.Notice how it uses the
type()
function to determine if the value passed in forrank
is anint
. If it is, it simply stores that integer value in therank
attribute (self.rank
). Otherwise, it assumes that the rank is a string, and it uses string indexing and theupper()
method to ensure that the value stored in thesuit
attribute is a single upper-case character.Run
card.py
in IDLE, and enter these statements in the Shell:>>> c1 = Card(4, 'C') >>> c1.rank 4 >>> c1.suit 'C' >>> c2 = Card('queen', 'diamonds') >>> c2.rank 'Q' >>> c2.suit 'diamonds'
Note that the constructor converted the rank provided for
c2
(the string'queen'
) to the single upper-case letter'Q'
. However, the constructor did not convert the suit'diamonds'
to a single upper-case letter. It simply stored the string provided forsuit
without changing it.Modify the constructor so that it ensures that the value stored in the
suit
attribute is a single upper-case letter. You may assume that the value passed in forsuit
is a string of one or more letters.Once you have made the necessary changes, re-run
card.py
and test the new version:>>> c2 = Card('queen', 'diamonds') >>> c2.suit 'D'
-
We’ve also given you an initial version of a
__repr__
method for converting aCard
object to a string. It simply constructs a string consisting of therank
andsuit
, 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.Begin by copying the following lines into
card.py
, putting them near the top of the file, before the header for theCard
class: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
andSUIT_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'
.
Modify the
__repr__
method so that it uses these dictionaries to create and return a string of the form'
rank-nameof
suit-name'
. For example:>>> c2 = Card('Q', 'D') >>> c2 Queen of Diamonds
If the called object’s
rank
is of typeint
, you should simply convert it to a string, as we do in the initial__repr__
. Otherwise, you should use theRANK_NAMES
dictionary to look up the descriptive name associated with the object’srank
.Similarly, you should use the
SUIT_NAMES
dictionary to look up the descriptive name associated with the object’ssuit
. -
-
Examine the method called
get_value()
incard.py
. It returns the value of the calledCard
object. More specifically:- if a
Card
object has a numericrank
, it returns therank
- otherwise, it returns a value of 10
This follows the common convention of giving all face cards a value of 10.
For example:
>>> c1 = Card(4, 'C') >>> c1.get_value() 4 >>> c2 = Card('queen', 'diamonds') >>> c2.get_value() 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 Ace of Spades >>> c3.get_value() 1
- if a
Task 1: use and complete a class for Hand
objects
Next, we’ll consider another type of object that would be useful when implementing a card game.
-
Begin by downloading the following files:
Open both files in IDLE.
In
hand.py
, we’ve begun to define a class calledHand
that will serve as a blueprint for objects that represent a single hand of cards.Each
Hand
object has a single attribute calledcards
that is initially an empty list. AsCard
objects are added to theHand
using theadd_card()
method, they are concentenated to the end of this list. -
After reviewing and understanding the
Hand
class, add client code tolab10_client.py
to accomplish the tasks listed below.-
Create two
Card
objects:- one for the 7 of Hearts, assigning it to a variable
c1
- one for the Ace of Diamonds, assigning it to a variable
c2
- one for the 7 of Hearts, assigning it to a variable
-
Create a single
Hand
object and assign it to a variableh1
. -
Use the
add_card()
method to add both of theCard
objects toh1
. You will need to call the method twice, once for eachCard
. -
Print
h1
as follows:print('first hand:', h1)
Run
lab10_client.py
, and you should see the following output:first hand: [7 of Hearts, Ace of Diamonds]
-
-
As part of the
Hand
class, add a method callednum_cards()
that returns the number of cards in the calledHand
object (self
). Make sure to indent the method under the class header.To test it, add the following line to the end of your client code:
print('number of cards:', h1.num_cards())
Rerun the program, and you should now see:
first hand: [7 of Hearts, Ace of Diamonds] number of cards: 2
-
As part of the
Hand
class, add a method calledget_value()
that returns the total value of the cards in the calledHand
object (self
).You will need to perform a cumulative computation, and you should use the
Card
version of theget_value()
method to determine the individual value of eachCard
object in theHand
.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 calledhas_any()
that takes a card rank as its only input, and that returnsTrue
if there is at least one occurrence of aCard
with that rank in the calledHand
object (self
), and that returnsFalse
if there are no occurrences of that rank in theHand
. (Note that the suits of theCard
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
Task 2: use inheritance to define a class for Blackjack hands
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 calledBlackjackHand
that is a subclass of theHand
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 theHand
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 regularHand
. In particular, if aBlackjackHand
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 ofget_value()
that is part of theBlackjackHand
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 usesuper()
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
lab10_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.
Task 3: submit your work
You should use Apollo to submit:
- your modified
card.py
file - your modified
hand.py
file - your modified
lab10_client.py
file
Don’t worry if you didn’t finish all of the tasks. You should just submit whatever work you were able to complete during lab.
Here are the steps:
- Login to Apollo, using the link in the left-hand navigation bar. You will need to use your Kerberos user name and password.
- Check to see that your BU username is at the top of the Apollo page. If it isn’t, click the Log out button and login again.
- Find the appropriate lab section on the main page and click Upload files.
- For each file that you want to submit, find the matching upload section for the file. Make sure that you use the right section for each file. You may upload any number of files at a time.
- Click the Upload button at the bottom of the page.
- Review the upload results. If Apollo reports any issues, return to the upload page by clicking the link at the top of the results page, and try the upload again, per Apollo’s advice.
- Once all of your files have been successfully uploaded, return to the upload page by clicking the link at the top of the results page. The upload page will show you when you uploaded each file, and it will give you a way to view or download the uploaded file. Click on the link for each file so that you can ensure that you submitted the correct file.