Add a method to Card that creates a full deck of cards, with one card of each combination of rank and suit

人走茶凉 提交于 2019-11-28 20:42:27

Here's another way of doing it, this time only using techniques you would have learned up to that point*

First we define the possible ranks and suits, using the respective Rank and Suit enums defined previously.

Next we have the function iterate over each rank within each suit, creating a card for each, and finally returning an array of the cards.

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }

    func createDeck() -> Card[] {
        let ranks = [Rank.Ace, Rank.Two, Rank.Three, Rank.Four, Rank.Five, Rank.Six, Rank.Seven, Rank.Eight, Rank.Nine, Rank.Ten, Rank.Jack, Rank.Queen, Rank.King]
        let suits = [Suit.Spades, Suit.Hearts, Suit.Diamonds, Suit.Clubs]
        var deck = Card[]()
        for suit in suits {
            for rank in ranks {
                deck.append(Card(rank: rank, suit: suit))
            }
        }
        return deck
    }
}

(* with the notable exception that the tour hadn't explicitly explained how to append to arrays at that point)

A robust code answer will not to the use actual values (i.e., .Spades) from the enumerations when generating the deck, e.g., if a "Joker" is added later to the Rank enumeration (anywhere in the enumeration), the deck generation function should still work without change.

The design questions (what to return?, should the deck generation be a function of card?) are not really relevant to this tutorial, but it is likely that a Deck class would be preferable if any serious functionality is going to be built out further (e.g., shuffle). So for now, returning an Array from a function in the Card structure is all that is required.

The following code (as far as possible only using what has been described up to this point in the tutorial) defines a function in the Card Structure that loops through the Suit and Rank enumerations without needing to know any of the enumeration values and returns an Array:

static func deck() -> [Card] {
    var deck = [Card]()
    var suitCount = 1
    while let suit = Suit(rawValue: suitCount) {
        var rankCount = 1
        while let rank = Rank(rawValue: rankCount) {
            deck.append(Card(rank: rank, suit: suit))
            rankCount += 1
        }
        suitCount += 1
    }
    return deck
}

Invoke this with:

let deck = Card.deck()
var card3 = deck[3].simpleDescription()

Copy the function into the Card structure and try adding values to enums. Note the following:

  • how the number of times the loops are executed changes when adding to the enums
  • that both enum counters start at 1 (if not otherwise specified in the enumeration the first raw value is one)
  • unspecified array indexes start at 0 (e.g., deck[3] is actually the 4 of Spades)

The experiment asks for a method to Card. So I I declared the method as being static so that it acts on the struct and not an instance of it:

struct Card {
    var rank: Rank
    var suit: Suit

    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }

    static func deck() -> [Card] {
        var deck: [Card] = []
        for suit in [Suit.Spades, Suit.Hearts, Suit.Diamonds, Suit.Clubs] {
            for rank in 0...13 {
                if let unwrappedRank = Rank.fromRaw(rank) {
                    deck.append(Card(rank: unwrappedRank, suit: suit))
                }
            }
        }
        return deck
    }
}

To make use of it:

let deck = Card.deck()

Hope that helps.

A for loop is the way to go. I made a few adjustments in your base code. First, I added a type to your Suit enum.

enum Suit : Int

Then I added a class called Deck which is responsible for a deck of cards.

class Deck {
    var cards:Card[]

    init() {
        self.cards = Array<Card>()
    }

    func createDeck() {
        for suit in 0...Suit.Clubs.toRaw() {
            for rank in 1...Rank.King.toRaw() {
                self.cards += Card(rank: Rank.fromRaw(rank)!, suit: Suit.fromRaw(suit)!)
            }
        }
    }
}

func createDeck() loops through all possible playing cards and adds them to your deck.

I left everything like in Swift Tour, Suit is String and Rank is Int.

struct Card {
    var rank: Rank
    var suit: Suit

    func simpleDescription () -> String{
        return "The \(rank.simpleDescription()) of \suit.simpleDescription())"
}

    func createDeck() -> [Card] {
        var n = 1
        var deck = [Card]()
        let suits = [Suit.spades, Suit.hearts, Suit.diamonds, Suit.clubs]
        while let rank = Rank(rawValue: n) {
            for suit in suits {
                deck.append(Card(rank: rank, suit: suit))
            }
            n += 1
        }
        return deck
    }
}   
let card = Card (rank: Rank.ace, suit: Suit.spades)
let deck = card.createDeck()

First I'll tackle the easiest question: Where you put the code that creates the full deck is up to you, but I would advise you not to put it in Card, but rather create a Deck class and provide a convenience initializer to do it there.

That said, let's continue with the plan of adding it to the Card class. Unfortunately there is no way to just loop over all possible values of an Enum in the way you'd hope (though I'd love to be wrong about this!), but you can do this:

let first_card = Rank.Ace.toRaw() // == 1
let last_card = Rank.King.toRaw() // == 13

for raw_rank in first_card...last_card {
    let rank = Rank.fromRaw(raw_rank)!
}

Let's walk through this. An Enum assigns an underlying value to each case, and by writing case Ace = 1 you're setting it up to start counting from 1 (rather than 0, the default). The API provided by an Enum for accessing the underlying value is a toRaw() method on each Enum case (The Enum itself also provides it in the form of Rank.toRaw(Rank.Ace).

You can convert back from the raw value using the aptly named fromRaw() method (so Rank.fromRaw(1) would give us Ace) but there is a caveat: It returns an optional. The return type is Rank?, not Rank. In order to access the value you need to either check for nil, or force unwrap it.

Checking for nil:

if let rank = Rank.fromRaw(1) {
    // Do stuff with rank, which is now a plain old Rank
}
else {
    // handle nil
}

Force unwrap:

var rank: Rank = Rank.fromRaw(1)!

So to answer your question about the loops: Yes, that's the way to do it =P, and yes again about the array, though that's a design decision. It makes just as much sense to create a Deck class and return that instead.

Let's add the method using an extension. Extensions let you add functionality to an existing type. You can create an extension on a class, enum, or even a primitive type. pretty much anything.

extension Card {

    func createFullDeck() -> Card[] {
        var deck: Array<Card> = []
        for raw_rank in Rank.Ace.toRaw()...Rank.King.toRaw() {
            deck += [
                Card(rank:Rank.fromRaw(raw_rank)!, suit:.Spades),
                Card(rank:Rank.fromRaw(raw_rank)!, suit:.Hearts),
                Card(rank:Rank.fromRaw(raw_rank)!, suit:.Diamonds),
                Card(rank:Rank.fromRaw(raw_rank)!, suit:.Clubs),
            ]
        }
        return deck
    }

}

I read the answers above, but then I couldn't use the method ... unless it is a class method. So I added "static" before the 2 methods I added, and here is my proposal:

struct Card {
  var rank: Rank
  var suit: Suit

  func simpleDescription() -> String {
    return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
  }

  static func createDeck() -> Card[] {
    var deck = Card[]()
    for suit in [Suit.Spades, Suit.Clubs, Suit.Hearts, Suit.Diamonds] {
        for rankRawValue in 1...13 {
            let rank = Rank.fromRaw(rankRawValue)
            let card = Card(rank: rank!, suit: suit)
            deck += card
        }
    }
    return deck
  }

  static func printDeck(deck:Card[]) {
    for card in deck {
        println(card.simpleDescription())
    }
  }
}

let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

let deck = Card.createDeck()
Card.printDeck(deck)

But I agree, a "Deck" class would be a better option ...

Surprisingly, no one has yet had a stab at a functional implementation. Here goes:

extension Array {
  func flatten<T>() -> T[] {
    let xs = (self as Any) as Array<Array<T>>
    return xs.reduce(T[]()) { (x, acc) in x + acc }
  }
}

extension Card {
  static func fullDeck() -> Card[] {
    let rawRanks = Array(Rank.Ace.toRaw()...Rank.King.toRaw())
    let suits: Suit[] = [.Spades, .Hearts, .Diamonds, .Clubs]
    return (rawRanks.map {
      rawRank in suits.map {
        suit in Card(rank: Rank.fromRaw(rawRank)!, suit: suit)
        }
      }).flatten()
  }
}

Trying to avoid knowledge of the enum definition... It seems clumsy (I'm a beginner), and still needs the starting index: 0 for Suit, 1 for Rank.

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
    static func deck() -> [Card] {
        var deck = [Card]()
        var suitCount = 0
        while (Suit(rawValue: suitCount) != nil) {
            var rankCount = 1
            while (Rank(rawValue: rankCount) != nil) {
                deck.append(Card(rank: Rank(rawValue: rankCount)!, suit: Suit(rawValue: suitCount)!))
                rankCount++
            }
            suitCount++
        }
        return deck
    }
}
let deck = Card.deck()

I just started learning Swift also, and had this same problem. I too thought it was rather odd that the experiment was to create a method inside the Card structure to create a full deck of cards.

After looking at these answers, and reading the official Apple "The Swift Programming Language (Swift 2.1)" tour, I solved it like this:

struct Card {
    var rank: Rank
    var suit: Suit

    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }

    func createDeck() -> [Card] {
        let suits = [Suit.Spades, Suit.Hearts, Suit.Clubs, Suit.Diamonds]
        var deck = [Card]()

        for theSuit in suits {
            for theRank in Rank.Ace.rawValue...Rank.King.rawValue {
                deck.append(Card(rank: Rank(rawValue: theRank)!, suit: theSuit))
            }
        }

        return deck
    }
}

let aceOfHearts = Card(rank: .Ace, suit: .Hearts)
let deck = aceOfHearts.createDeck()

for card in deck {
    print("\(card.rank) of \(card.suit)")
}
mrabins

Here's the whole solution for Swift 3:

struct Card {

    var rank: Rank
    var suit: Suit

    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
    func createDeck() -> [Card] {
        let suits = [Suit.spades, Suit.hearts, Suit.clubs, Suit.diamonds]
        var deck = [Card]()

        for theSuit in suits {
            for theRank in Rank.Ace.rawValue...Rank.King.rawValue {
                deck.append(Card(rank: Rank(rawValue: theRank)!, suit: theSuit))
            }
        }
        return deck
    }
}

You can call it like this:

let aceOfHearts = Card(rank: .Ace, suit: .hearts)
let deck = aceOfHearts.createDeck()

Since all the examples above are imperative in nature and Swift is built with functional programming in mind, I took a more functional approach to solving the problem. Here's my full set of code:

My Rank enum (have to define an array with all the values because it's not possible to iterate over all the values of an enum for some reason)

enum Rank: Int, CustomStringConvertible {

    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king

    static let allRanks = [ace, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king]

    var description: String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}

Suit enum (added similar type of array)

enum Suit: String, CustomStringConvertible  {

    case spades = "♠︎"
    case hearts = "♥︎"
    case diamonds = "♦︎"
    case clubs = "♣︎"

    static let allSuits = [spades, hearts, diamonds, clubs]

    var description: String {
        switch self {
        default:
            return rawValue
        }
    }

}

...and finally the card:

struct Card: CustomStringConvertible {
    var rank: Rank
    var suit: Suit

    var description: String {
        return "\(rank)\(suit)"
    }

    static func createDeckOfCards() -> [Card] {
        return Suit.allSuits.reduce([]) {
            deck, suit in deck + Rank.allRanks.reduce([]) {
                cardsInSuit, rank in cardsInSuit + [Card(rank: rank, suit: suit)]
            }
        }
    }
}

print(Card.createDeckOfCards())

As an iOS dev I try to read this book/tutorial about once a year. This year, I thought I'd approach it as a novice developer, and see what I could do based on what information the tutorial had given up to this point. As https://stackoverflow.com/users/262455/jack-james noted they may not have taught .append yet. With that in mind, here is my answer

func fullDeck() -> [String] {
    var deckOfCards = [String]()
    let suits = [Suit.clubs, Suit.diamonds, Suit.hearts, Suit.spades]
    let ranks = [Rank.ace, Rank.two, Rank.three, Rank.four, Rank.five, Rank.six, Rank.seven, Rank.eight, Rank.nine, Rank.ten ,Rank.jack, Rank.queen, Rank.king]
    for suit in suits {
        for rank in ranks {
            let card = Card(rank: rank, suit: suit)
            deckOfCards.append(card.simpleDescription())
        }
    }
    print(deckOfCards)
    return deckOfCards
}

I agree with the guy above, a class would make more sense, because in this example you need to initialize a Card first in order to call this function...

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!