title: Lab 10: Object-oriented programming, part II [TOC] ### 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. 0. Begin by downloading the following file: [card.py](../files/labs/lab10/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 an `int` (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' | | | +-----+ | +----------------+ 1. Locate the constructor that we've provided for `Card` objects. Notice how it uses the `type()` function to determine if the value passed in for `rank` is an `int`. If it is, it simply stores that integer value in the `rank` attribute (`self.rank`). Otherwise, it assumes that the 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. Run `card.py` in IDLE, and enter these statements in the Shell: :::python >>> 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 for `suit` 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 for `suit` is a string of one or more letters. Once you have made the necessary changes, re-run `card.py` and test the new version: :::python >>> c2 = Card('queen', 'diamonds') >>> c2.suit 'D' 2. 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: :::python >>> 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 the `Card` class:*** :::python 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'`. ***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: :::python >>> c2 = Card('Q', 'D') >>> c2 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`. 3. Examine the method called `get_value()` in `card.py`. It returns the value of the called `Card` object. More specifically: * if a `Card` object has a numeric `rank`, it returns the `rank` * otherwise, it returns a value of 10 This follows the common convention of giving all face cards a value of 10. For example: :::python >>> 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: :::python >>> c3 = Card('A', 'S') >>> c3 Ace of Spades >>> c3.get_value() 1 ### 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. 0. Begin by downloading the following files: * [hand.py](../files/labs/lab10/hand.py) * [lab10_client.py](../files/labs/lab10/lab10_client.py) Open both files in IDLE. 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. 1. After reviewing and understanding the `Hand` class, ***add client code*** to `lab10_client.py` to accomplish the tasks listed below. 1. 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` 2. Create a single `Hand` object and assign it to a variable `h1`. 3. 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`. 4. Print `h1` as follows: :::python print('first hand:', h1) Run `lab10_client.py`, and you should see the following output: :::text first hand: [7 of Hearts, Ace of Diamonds] 2. As part of the `Hand` class, add a method called `num_cards()` that returns the number of cards in the called `Hand` 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: :::python print('number of cards:', h1.num_cards()) Rerun the program, and you should now see: :::text first hand: [7 of Hearts, Ace of Diamonds] number of cards: 2 3. 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`). 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: :::python print('total value:', h1.get_value()) Rerun the program, and you should now see: :::text first hand: [7 of Hearts, Ace of Diamonds] number of cards: 2 total value: 8 4. 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: :::python 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: :::text 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. 1. 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! 2. 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: :::python 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. 3. To test your new subclass, cut and paste the following client code into the bottom of your `lab10_client.py` file: :::python 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: :::text 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: 1. Login to Apollo, using the link in the left-hand navigation bar. You will need to use your Kerberos user name and password. 2. Find the appropriate lab section on the main page and click *Upload files.* 3. 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. 4. Click the *Upload* button at the bottom of the page. 5. 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. 6. 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.***