14
\$\begingroup\$

Before you can do any sort of card game, you must first write some code to define your deck of cards.

One thing I've noticed in looking at some of Apple's Swift interfaces is that they very much so like to use extensions. This is probably good. In the big picture long term, it's a really good way of allowing ever-expanding classes while actually keeping individual source code files quite tidy.

I also wanted to try something out with Swift enums and iterating through every value in them...


The Basics

enum Suit

enum Suit: String {
    case Clubs = "Clubs"
    case Diamonds = "Diamonds"
    case Hearts = "Hearts"
    case Spades = "Spades"
    case __EXHAUST = ""
}

enum Rank

enum Rank: String {
    case Ace = "Ace"
    case Two = "Two"
    case Three = "Three"
    case Four = "Four"
    case Five = "Five"
    case Six = "Six"
    case Seven = "Seven"
    case Eight = "Eight"
    case Nine = "Nine"
    case Ten = "Ten"
    case Jack = "Jack"
    case Queen = "Queen"
    case King = "King"
    case __EXHAUST = ""
}

struct Card

struct Card {
    let suit: Suit
    let rank: Rank
}

The Extensions

Suit: FowardIndexType

extension Suit: ForwardIndexType {
    func successor() -> Suit {
        switch self {
        case .Clubs: return .Diamonds
        case .Diamonds: return .Hearts
        case .Hearts: return .Spades
        case .Spades: return .__EXHAUST
        case .__EXHAUST: return .__EXHAUST
        }
    }
}

Rank: ForwardIndexType

extension Rank: ForwardIndexType {
    func successor() -> Rank {
        switch self {
        case .Ace: return .Two
        case .Two: return .Three
        case .Three: return .Four
        case .Four: return .Five
        case .Five: return .Six
        case .Six: return .Seven
        case .Seven: return .Eight
        case .Eight: return .Nine
        case .Nine: return .Ten
        case .Ten: return .Jack
        case .Jack: return .Queen
        case .Queen: return .King
        case .King: return .__EXHAUST
        case .__EXHAUST: return .__EXHAUST
        }
    }
}

Card: Printable, DebugPrintable

extension Card: Printable, DebugPrintable {
    var description: String {
        get {
            return self.rank.rawValue + " of " + self.suit.rawValue
        }
    }
    var debugDescription: String {
        get {
            return self.rank.rawValue + " of " + self.suit.rawValue
        }
    }
}

Array

extension Array {
    mutating func shuffle() {
        for i in 0..<(count - 1) {
            let j = Int(arc4random_uniform(UInt32(count - i))) + i
            swap(&self[i], &self[j])
        }
    }
}

Int

extension Int {
    func repeat(function: () -> ()) {
        for _ in 1...self {
            function()
        }
    }
}

Example Usage

Constructing the Deck

var deck: [Card] = Array<Card>()
for suit in Suit.Clubs...Suit.Spades {
    for rank in Rank.Ace...Rank.King {
        let newCard = Card(suit: suit, rank: rank)
        deck.append(newCard)
    }
}

Shuffling the Deck

3.repeat({deck.shuffle()})

Printing the entire Deck

for card in deck {
    println(card)
}

Conforming to the ForwardIndexType protocol is what allows the ... and ..< syntax for loops. The unfortunate downside is that for an enum, we must include one extra value in order to actually iterate through the entire enumeration, per this StackOverflow question. This seems like a bug that will hopefully be fixed.

I think this is a good foundation for a deck of playing cards in Swift.

Including Jokers might prove difficult. I'm not sure whether it could be done very from this structure. And working with extensions means that as we add additional functionality to a card or the enums, we can do it cleanly in new source code files without mucking around with this file.

\$\endgroup\$
5
\$\begingroup\$

Bearing in mind that I don't know much Swift...

Your Rank enum could save you a lot of typing if you simply made it a plain old autoincrementing integer enumeration:

enum Rank: Int {
    case Ace = 1, Two, Three, Four, Five, Six, Seven,
    Eight, Nine, Ten, Jack, Queen, King, __EXHAUST
}

extension Rank: ForwardIndexType {
    func successor() -> Rank {
        switch self {
        case .__EXHAUST: return .__EXHAUST
        default: return Rank(rawValue: self.rawValue + 1)!
        }
    }
}

extension Rank: Printable, DebugPrintable {
    var description: String {
        get {
            switch (self) {
            case .Ace: return "Ace"
            case .Jack: return "Jack"
            case .Queen: return "Queen"
            case .King: return "King"
            default: return self.rawValue.description
            }
        }
    }
    var debugDescription: String {
        get { return self.description }
    }
}

for i in Rank.Ace ... .King {
    print(i)
}

When testing out this code, please notice that the Printable protocol doesn't work in Playgrounds at the moment.

Also, of course, shuffling the deck 3 times doesn't make the order of the cards any "more random" than shuffling the deck once. But you probably know that, since you went to the bother of implementing copying a correct shuffling algorithm.

To add jokers, the most "C-like" approach would be to add Suit.Joker and Rank.Joker and leave Card alone. The most "functional-programming" approach would be to change Card from a struct to an enum with a special case for jokers:

enum Card {
    case Regular(Rank, Suit)
    case Joker
}

var c = Card.Regular(.Ace, .Spades)
var j = Card.Joker
\$\endgroup\$
  • \$\begingroup\$ Did you test this? I believe changing the enum from String backed to Int backed and implementing the description method as you have done will print Arabic numerals rather than plain-English number words. I do like the Card enum and the way you handle Jokers though. \$\endgroup\$ – nhgrif Mar 12 '15 at 10:39
  • \$\begingroup\$ Yes, of course it'll print Arabic numerals, but I would guess that most people who'd be interpreting the output of this program will have at least a passing familiarity with Arabic numerals, so it'll be okay. :) \$\endgroup\$ – Quuxplusone Mar 12 '15 at 23:01
  • \$\begingroup\$ Sure, but the point of the String-backed enum is that I can print them in plain-English, which is how I intend to print them. \$\endgroup\$ – nhgrif Mar 12 '15 at 23:02
  • \$\begingroup\$ Is there an issue with implementing ForwardIndexType on Extensions. I get a architecture error. \$\endgroup\$ – ericgu Mar 13 '15 at 18:50
  • \$\begingroup\$ @ericgu, you'll have to provide more information (or ask a question of your own). All I can say is that the above code worked fine for me in a Playground targeting OSX, in Xcode 6.1.1 (6A2008a). Modulo the Printable bug, that is. :) \$\endgroup\$ – Quuxplusone Mar 14 '15 at 18:24
2
\$\begingroup\$

This answer is written in the spirit of New answers on old questions using new language features: The Swift language has developed considerably from Swift 1 (in 2014) to the current version Swift 5. Many new features were added in the meantime, and that allows to simplify the provided code.

I will also use the current naming conventions of Swift:

Names of types and protocols are UpperCamelCase. Everything else is lowerCamelCase.

Iterating over all enumeration values

The first (and main) point is that with the implementation of SE-0194 Derived Collection of Enum Cases in Swift 4.2, “simple” enumerations (i.e.: enumerations without associated values) can conform to the CaseIterable protocol:

enum Suit: String, CaseIterable {
    case clubs = "Clubs"
    case diamonds = "Diamonds"
    case hearts = "Hearts"
    case spades = "Spades"
}

That is an “opt-in” feature: You have to declare the protocol conformance, but then the compiler synthesizes the required code. And now iterating over all possible values becomes as simple as

for suit in Suit.allCases {
    // ...
}

Defining a sentinel case (__EXHAUST) and a successor() method is no longer needed. If we define enum Rank in the same fashion then the complete deck can be created with

var deck: [Card] = []
for suit in Suit.allCases {
    for rank in Rank.allCases {
        let newCard = Card(suit: suit, rank: rank)
        deck.append(newCard)
    }
}

Or by map()ing each suit and rank to a card and flattening the result:

let deck = Suit.allCases.flatMap { suit in
    Rank.allCases.map { rank in
        Card(suit: suit, rank: rank)
    }
}

Providing a textual representation

Printable and DebugPrintable have been renamed to CustomStringConvertible and CustomDebugStringConvertible. If – as in your code – both description and debugDescription produce the same result then it is sufficient to implement the CustomStringConvertible conformance, it will serve as a fallback for debugDescription. Also,

  • the get { ... } wrapper can be omitted in a read-only computed property,
  • the implicit self can be omitted.
extension Card: CustomStringConvertible {
    var description: String {
        return rank.rawValue + " of " + suit.rawValue
    }
}

Another option would be to use the default raw values of string-based enums

enum Suit: String, CaseIterable {
    case clubs
    case diamonds
    case hearts
    case spades
}

and capitalize the initial character in the description method:

extension Card: CustomStringConvertible {
    var description: String {
        return rank.rawValue.capitalized + " of " + self.suit.rawValue.capitalized
    }
}

Shuffling the deck

With the Random Unification in Swift 4.2, a shuffle() method has been added for mutable collections:

deck.shuffle()
// Or:
let newDeck = deck.shuffled()

Implementing a custom (Fisher-Yates) shuffle method is no longer necessary.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.