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
Cardthat will serve as a blueprint for a single playing card.Each
Cardobject 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
Cardobject representing the 4 of Clubs would look like in memory:+----------------+ | +-----+ | | rank | 4 | | | +-----+ | | +-----+ | | suit | 'C' | | | +-----+ | +----------------+
And here’s a picture of a
Cardobject representing the Queen of Diamonds:+----------------+ | +-----+ | | rank | 'Q' | | | +-----+ | | +-----+ | | suit | 'D' | | | +-----+ | +----------------+
- a
-
Locate the constructor that we’ve provided for
Cardobjects.Notice how it uses the
type()function to determine if the value passed in forrankis anint. If it is, it simply stores that integer value in therankattribute (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 thesuitattribute is a single upper-case character.Run
card.pyin 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 forsuitwithout changing it.Modify the constructor so that it ensures that the value stored in the
suitattribute is a single upper-case letter. You may assume that the value passed in forsuitis a string of one or more letters.Once you have made the necessary changes, re-run
card.pyand 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 aCardobject to a string. It simply constructs a string consisting of therankandsuit, separated by a space:>>> c2 = Card('Q', 'D') >>> c2 Q D
Although it makes sense to only store a single character for a
Cardobject’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 theCardclass: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_NAMESandSUIT_NAMESare 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_NAMESis 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_NAMESis 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-nameofsuit-name'. For example:>>> c2 = Card('Q', 'D') >>> c2 Queen of Diamonds
If the called object’s
rankis of typeint, you should simply convert it to a string, as we do in the initial__repr__. Otherwise, you should use theRANK_NAMESdictionary to look up the descriptive name associated with the object’srank.Similarly, you should use the
SUIT_NAMESdictionary 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 calledCardobject. More specifically:- if a
Cardobject 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 calledHandthat will serve as a blueprint for objects that represent a single hand of cards.Each
Handobject has a single attribute calledcardsthat is initially an empty list. AsCardobjects are added to theHandusing theadd_card()method, they are concentenated to the end of this list. -
After reviewing and understanding the
Handclass, add client code tolab10_client.pyto accomplish the tasks listed below.-
Create two
Cardobjects:- 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
Handobject and assign it to a variableh1. -
Use the
add_card()method to add both of theCardobjects toh1. You will need to call the method twice, once for eachCard. -
Print
h1as 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
Handclass, add a method callednum_cards()that returns the number of cards in the calledHandobject (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
Handclass, add a method calledget_value()that returns the total value of the cards in the calledHandobject (self).You will need to perform a cumulative computation, and you should use the
Cardversion of theget_value()method to determine the individual value of eachCardobject 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
Handclass, add a method calledhas_any()that takes a card rank as its only input, and that returnsTrueif there is at least one occurrence of aCardwith that rank in the calledHandobject (self), and that returnsFalseif there are no occurrences of that rank in theHand. (Note that the suits of theCardobjects 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 calledBlackjackHandthat is a subclass of theHandclass: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 theHandclass.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
BlackjackHandto be computed somewhat differently than the value of a regularHand. In particular, if aBlackjackHandhas 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 theBlackjackHandclass.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
Handmethods.) 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.pyfile: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.pyfile - your modified
hand.pyfile - your modified
lab10_client.pyfile
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.