- Model real-world objects by subclassing
NSObject
. - Get more practice creating properties, methods, and initializers.
- Learn to override the
description
property for customized use in debugging.
In this lab, you're going to get further practice subclassing from NSObject
by creating a model of a playing card (FISCard
) and a model of a playing card deck (FISCardDeck
). While this assignment does little to use the functionality of the data models that you'll be setting up, this lab is a prerequisite for certain labs later in the course that will rely on these data models to create a card game (such as Blackjack).
Whenever we send objects to an NSLog()
to be printed in the console, what that handy function actually does is read the description
string property of that object. You won't see this property listed in the Apple Reference Docs for any of the classes that you're used to working with, however. This is because description
is actually a property on NSObject
which means that almost all objects inherit it. Many of them, such as NSArray
, override it to customize how they are logged. As a part of this lab, you'll do the same for your custom classes.
Open the OOP-Cards-Model.xcworkspace
file. You'll notice that the project is empty except for the FISAppDelegate
class and two tests files.
-
Create the class files that the tests are expecting:
FISCard
andFISCardDeck
. -
Prepare the
FISCard
class. It should have:
- two class methods,
validSuits
which returns anNSArray
, andvalidRanks
which returns anNSArray
;
- four public
readonly
properties in the.h
(remember to make them privatelyreadwrite
in the.m
file, so we can change them in there),- an
NSString
property calledsuit
, - an
NSString
property calledrank
, - an
NSString
property calledcardLabel
, and - an
NSUInteger
property calledcardValue
; and
- an
- a designated initializer that accepts arguments for the
suit
andrank
string properties.
- Prepare the
FISCardDeck
class. It should have:
- two public properties:
- an
NSMutableArray
calledremainingCards
, and - an
NSMutableArray
calleddealtCards
;
- an
- four public methods:
drawNextCard
which returns aFISCard
(you'll need to importFISCard.h
for this),resetDeck
which provides no return,gatherDealtCards
which provides no return, andshuffleRemainingCards
which provides no return.
- Define all of the declared methods to default implementations so that the test build will succeed. Run the testing suite to check its initial failures.
-
Write the implementation for the
validSuits
class method to return an array containing the four unicode characters for card suits ( ♠ ♥ ♣ ♦ ) inside strings. -
Write the implementation for the
validRanks
class method to return an array containing a string representation of the thirteen card ranks from Ace to King.
Hint: Use digits to represent the numbered cards and abbreviate the face cards to "A", "J", "Q", "K". In this implementation, the Ace should be ordered as a low card. -
Write the implementation for the designated initializer (you should have named it
initWithSuit:rank:
). Set the ivars for_suit
and_rank
to their associated arguments. -
Assign
_cardLabel
and_cardValue
in your initializer as follows:_cardLabel
should include thesuit
andrank
properties together in such a way that the Queen of Spades will show♥Q
and the Ten of Diamonds will show♦10
._cardValue
will be a little more complex. It will need to be1
for an Ace, the face value of the numbered cards (i.e.2
for a Two and10
for a Ten), and10
for the face cards (i.e. Jack, Queen, and King).
Hint: If the ranks are stored in an array, can you detect the index of a rank in that array? Then you could somehow use the index value to determine the card's value. Remember, if the Ace is first in the array, its index will be zero.
Advanced: This is a case where Enums are most appropriate to use, however we don't cover them in the curriculum so we've taken a different route.
-
Override the default initializer
init
to call the designated initializer while passing in@"!"
as thesuit
argument and@"N"
as therank
argument. -
Override the implementation for the
description
method (i.e. the getter for thedescription
property). Likeinit
,description
should present itself as an option to autocomplete. This is because the superclassNSObject
has declared both of these methods publicly. The implementation for the override ofdescription
is simple: return the string value stored in thecardLabel
property. -
Navigate to the
FISAppDelegate.m
file and use theapplication:didFinishLaunchingWithOptions:
method as a playground. Import theFISCard.h
header file to give yourself access to the class. Manually create a defaultFISCard
object andNSLog()
itsdescription
property. Run the scheme⌘
R
(not the tests!) and you should see!N
print neatly to the debug console. -
Verify that all of the tests for
FISCard
pass before moving on. TheFISCardDeck
class depends on the theFISCard
class to work properly.
-
To keep things organized, write a private method that will be used to generate the fifty-two cards in a standard deck. Think of an appropriate name based on its intended behavior. Make it a
void
method (it will assign directly toremainingCards
) and leave its implementation empty for now. -
Overwrite the default initializer (
init
). Have it initialize the_remainingCards
and_dealtCards
ivars to newNSMutableArray
s. At the end of theif (self)
block, insert a call to your card generator helper method that you just declared in step 1. Finish the rest of the initializer and run the tests to check that the array properties are getting initialized. -
Write the implementation for your card generator helper method. Think about how you can use the two arrays that you saved in the
FISCard
class methodsvalidSuits
andvalidRanks
to dynamically create one unique card of each suit and rank combination. Add each card to theremainingCards
array.
Hint: You'll need to use a loop within a loop. -
Write the implementation for the
drawNextCard
method. It should return a card at one end of the array (it is up to you to choose between making this either the first object or the last object in the array). However, this method should also remove that card from theremainingCards
array, and also add it to thedealtCards
array. This way, our instance ofFISCardDeck
will always keep track of all fifty-two cards, even after drawing them. -
You'll notice that there's a test which attempts to draw a fifty-third card from our fifty-two card deck. Add a protection against this behavior at the beginning of the
drawNextCard
method toreturn nil
if there are no cards in theremainingCards
array.
Top-tip: Add anNSLog()
along with this protection to print a message that the deck is empty. -
Write the implementation for the
resetDeck
method to call thegatherDealtCards
method and then theshuffleRemainingCards
method. That's it, however the tests forresetDeck
will fail until those other method implementations are completed.
Advanced: This method is the intended interface for interacting with the deck. There is not a "testable" option in Objective-C so the smaller methods must be made fully public in order to be visible to the tests. -
Write the implementation for the
gatherDealtCards
method. It should add the cards in thedealtCards
array back into theremainingCards
array and leave thedealtCards
array empty. -
Write the implementation for the
shuffleRemainingCards
method. Randomizing a collection is a difficult challenge and there are several ways to approach it. One approach would be to take the following steps:
- "pick up" the deck by making a
mutableCopy
of theremainingCards
array and emptying theremainingCards
array, - begin a loop limited by the total number of cards to be shuffled (in most cases this will be fifty-two, but can you guarantee that?),
- randomly draw a card out of the copied mutable array and insert it into the
remainingCards
array (make sure to remove it from the copied array).
Top-tip: To get a random integer, use thearc4random_uniform()
C function which specifically takes auint32_t
parameter. To silence the warning generated when passing it anNSUInteger
variable, cast the argument variable to anuint32_t
.
- Override the
description
method to return a customized string. You should return the count of theremainingCards
array, and continue by appending thecard.description
of each card in theremainingCards
array.
Hint: This is a great time to use anNSMutableString
.
Hint: You can hardcode a newline character with\n
and indentations with spaces.
Consider the following example when building yourdescription
method:
2015-10-01 16:22:30.422 OOP-Cards-Model[6488:163010] FISCardDeck
count: 52
cards:
♠A ♠2 ♠3 ♠4 ♠5 ♠6 ♠7 ♠8 ♠9 ♠10 ♠J ♠Q ♠K
♥A ♥2 ♥3 ♥4 ♥5 ♥6 ♥7 ♥8 ♥9 ♥10 ♥J ♥Q ♥K
♣A ♣2 ♣3 ♣4 ♣5 ♣6 ♣7 ♣8 ♣9 ♣10 ♣J ♣Q ♣K
♦A ♦2 ♦3 ♦4 ♦5 ♦6 ♦7 ♦8 ♦9 ♦10 ♦J ♦Q ♦K
Because of the randomization, the tests are only checking that it contains the substrings @"count"
and @"cards"
, so it's generally up to you to make this something useful.
10 — Navigate to the FISAppDelegate.m
file and import FISCardDeck.h
. In the application:didFinishLaunchingWithOptions:
method, create a new FISCardDeck
variable and NSLog()
its description
property. Play around with the methods you wrote, printing the description to watch the deck change.
Advanced: Try playing a few draws of War with yourself if you like, but don't get bogged down in a thorough implementation of the game.
View OOP Cards Model on Learn.co and start learning to code for free.