Vadim F.
Vadim F.

Reputation: 1051

Deck of card initialization using Enums

I am trying to initialize a deck of cards for the game Sets, which has 4 parameters for each card : color, shape, shade and number of shapes on the card.

My Card class looks as below :

enum CardColor : Int {
    case red = 1
    case purple = 2
    case green = 3
}

enum CardShape : Int {
    case line = 1
    case snake = 2
    case diamond = 3
}

enum CardShade : Int {
    case empty = 1
    case filled = 2
    case stripes = 3
}

class Card {

    var color : CardColor
    var shape : CardShape
    var shade : CardShade
    var number : Int
    var image : String

    var isTapped = false
    var isMatched = false


    init(color : CardColor , shape : CardShape, shade : CardShade , number : Int) {
        self.color = color
        self.shape = shape
        self.shade = shade
        self.number = number

        self.image = "\(color.rawValue)\(shape.rawValue)\(shade.rawValue)\(number)"

    }
}

I can't find a way to initialize a deck and I am currently using an array of Card that I made manually :

cards = [Card(color: .red, shape: .diamond, shade: .filled, number: 1)... 

and so on.

The cards looks like this :

enter image description here

Upvotes: 1

Views: 310

Answers (2)

Alexander
Alexander

Reputation: 63252

I would use a series of flatMaps to generate the full deck. I would also change a few other things:

  • Make Card a struct. Any card that is a single red empty line is the same as any other card that's a single red empty line. I.e. cards don't have their own identity separate from their value, thus a reference type (a class) wouldn't be useful.
  • Extract out the isTapped or isMatched fields from the Card struct. It doesn't make sense there. If you give a Set player a card and asked them what the isTapped value is, they would have no idea. It simply doesn't belong. I would make something like a CardSlot or CardView that keeps track of such UI state.
  • Changing image to imageName. If it were really an image, one would expect it to have type UIImage or NSImage, not String.
    • Change imageName to be a computed property. Now, all stored fields (color, shape, shade, parity) can be filled in by a member wise initializer, which the compiler will automatically synthesize for this struct.
  • Change the enums to be nested types of Card, to prevent the needless Card prefix in all their names.
  • Use more human readable names for your card images. Trust me, this will be a big help. It's much either to find green_line_filled_three.png than 3123.png
    • Now that the rawValues aren't needed to generate the image names, they can be removed. There's rarely ever a need to use Int raw values anymore. C interop is the only thing I can think of. For generating all enum values, you can use CaseIterable, instead of mapping init(rawValue: Int) over a range of Ints.
  • Change number: Int to arity: Card.arity, to prevent illegal values from ever popping up

And here's what that would look like:

struct Card {
    enum Color: CaseIterable { case red, purple, green }
    enum Shape: CaseIterable { case line, snake, diamond }
    enum Shade: CaseIterable { case empty, filled, striped }
    enum Arity: CaseIterable { case one, two, three }

    let color: Color
    let shape: Shape
    let shade: Shade
    let arity: Arity

    var imageName: String {
        return "\(color)\(shape)\(shade)\(arity)"
    }

    static func generateFullDeck() -> [Card] {
        return Color.allCases.flatMap { color in
            return Shape.allCases.flatMap { shape in
                return Shade.allCases.flatMap { shade in
                    return Arity.allCases.map { arity in
                        return Card(color: color, shape: shape, shade: shade, arity: arity)
                    }
                }
            }
        }
        /* 
        // Alternate implementation which prevents the "pyramid of doom" nesting,
        // But it's considerably more complex
        return Color.allCases.lazy.flatMap { color in
            Shape.allCases.map { shape in (color, shape)}
        }.flatMap { (color, shape) in
            Shade.allCases.map { shade in (color, shape, shade) }
        }.flatMap { (color, shape, shade) in
            Arity.allCases.map { arity in

            }
        })      
        */
    }
}


Card.generateFullDeck().forEach { print($0) }

Upvotes: 1

vacawama
vacawama

Reputation: 154583

For loops would work well here. Use 4 nested loops to loop through the attributes and create a new card inside of the inner loop:

Add CaseIterable to your enums to make it easy to access all of the cases:

enum CardColor : Int, CaseIterable {
    case red = 1
    case purple = 2
    case green = 3
}

Then:

// Array to hold the cards
var cards = [Card]()

for color in CardColor.allCases {
    for shape in CardShape.allCases {
        for shade in CardShade.allCases {
            for number in 1...3 {
                // Create a new card and append it to the array
                cards.append(Card(color: color, shape: shape, shade: shade, number: number))
            }
        }
    }
}

Upvotes: 2

Related Questions