First steps with Swift: Representing cards in a deck

Earlier I talked about the poker hands kata to practice our coding. Today we’re going to start easy.
Here is the first line in the rules:

A poker deck contains 52 cards – each card has a suit which is one of clubs, diamonds, hearts, or spades (denoted C, D, H, and S in the input data). Each card also has a value which is one of 2, 3, 4, 5, 6, 7, 8, 9, 10, jack, queen, king, ace (denoted 2, 3, 4, 5, 6, 7, 8, 9, T, J, Q, K, A). For scoring purposes, the suits are unordered while the values are ordered as given above, with 2 being the lowest and ace the highest value.

I’ve annotated the rules, and put the main concepts in bold. These are Deck, Card, Suit and Value. So how can we represent these? A Deck obviously consists of Cards, and each Card always has both a Suit and a Value.

Cards

Lets talk about Cards first! We have 52 unique cards, and thus we note that there is a limit to the amount different values a Suit and a Value can have. We only have four possible Suits, and of each Suit we only have thirteen different Values. We could have each Suit and Value represented by an Integer. Say for Suit we have the numbers 0-3. What happens if we accidentally create a card with a Suit outside this range? Is it then invalid, or are we going to modulo to make it work? We would need a lot of extra code to deal with these edge cases, just to make our code reliable. Lucky we don’t have to, as Swift provides us with a concept in which we can limit the amount of values to exactly the amount we need, using Enums.

Enums in Swift are, opposed to in Objective-C, one of the core concepts (or first class citizens as we call them), next to classes, structs and functions. While Integer can represent any number from 0 to 18.446.744.073.709.551.615, enums can have only as much different values as defined by the enum cases. This is useful, because we then don’t have to care if a certain value is invalid or not, or how to deal with such cases otherwise. Having a finite number of possible values makes Suit and Value ideal enum candidates.

Lets start off first by creating a Single View project, and don’t forget to add the test target. Since it’s a kata we follow the rules of TDD, and thus we start by writing our first failing test:

We create a Card which is initialised by a Suit and a Rank, and we test if the Suit is what we expect, being a Diamond. We run the test and it fails, because we haven’t implemented anything yet. So what do we need to implement this? Lets start off by creating a Card:

So do we have everything we need? Lets run the test again. It fails, because we still haven’t defined Suit and Rank. Lets do that as well:

Lets try our test again. It passes! Awesome.

Deck

Lets continue building on this, here is our new failing test in which we create an empty deck. We expect the number of cards to be 0.

To make it work, we need to create a deck, and have a computed property totalNumberOfCards that returns 0. Let’s create it. Computed properties don’t hold any value, they’re always computed and thus have a body. They’re actually very similar to a function/method. There are no rules for when you should use a computed property and when a function. My tip: as soon as there is some complicated logic going on, you might just want to have a function. That conveys that its not just a property, and that there is more to it than just returning a value.
Also note that computed properties are always ‘var’. We’ve only used ‘let’ until now, so I need to explain this further. ‘let’ means that the value or variable cannot change, i.e. mutate. ‘var’ means variable (obviously), so it is either computed or can be changed. We’ll see some more of ‘var’ later.

Great! The tests pass again. Note that we only did the most limited change possible to make the test work. This is important to realise.
Of course a deck should also hold cards, so let’s make that work as well. Let’s add a test for that:

We need to change a few things to make it work. First of all, we will need an initialiser that takes an array of cards. Second, we need to store the array of cards and let totalNumberOfCards return the amount of cards in the array. Our Deck is going to look like this:

Pass!
Finally, we want to have a deck of 52 cards. It is going to be a bit of a hassle to create every card in the code manually, so lets write a convenience method for it. First the test:

Then the implementation. To make it easier to instantiate all the different cards we need, we need something convenient. What if we could create a loop? Unfortunately we cannot just ask the enum to loop over all of its cases. If only they could be Ints instead, then it would be easy right? Swift has a way to accomplish that. We can back an enum by an Int as such:

We do the same for Rank, and we make sure that Two = 2, so that all the following cases have the correct value. Ace will have value 14. That also makes us check off the rule that the values are ordered as given above, with 2 being the lowest and ace the highest value.

The addition of the Int backing also gives us a failable initialiser for the enum: init?(rawValue: Int). Initialising a Suit with a rawValue gives us either a valid Suit or nil, which is captured in a concept called Optional. Actually Optional is an enum by itself and conforms to ExpressibleByNilLiteral, so that .none returns nil:

On to our Deck class, we add a class func singleDeck(), which creates an array of cards, and fills them using the values we’ve just set on our Int backed enums. We also need to change our Deck from a struct to a class because of this:

Green!
Remember we talked about how ‘var’ allows for the variable to change? Here’s a perfect local example. We initialise an empty cards array and by looping over it we add new cards to the array, therefore mutating the cards array.
There is a rule to using var though; we only make a property or variable var, if we actually mutate. The compiler is really great and will help in this by informing you that you’ve used a ‘var’ but no mutation took place, and that you should be using a ‘let’ instead.

Thats it for now!
Next time, we’ll go into drawing cards from the deck!

Leave a Reply