Home > front end >  Accessing a stuct value within another stuct within another struct that is a generic in Swift
Accessing a stuct value within another stuct within another struct that is a generic in Swift

Time:01-01

I'm trying to create a card game with Swift. My model is a struct called SetGame that holds an array of cards of type struct Card where one variable in Card is another struct called content which is of type CardContent that I set to a struct called SetCard in the view model. It has the following variables: color, shading, numberOfShapes, and shape.

I can't seem to figure out how to access the cardContent variables such as shape and color inside my model.

When I type print(card1.content.shape) I get an error saying that "Value of type 'CardContent' has no member 'shape'"

When I print (card1) I get "Card(isSelected: true, isMatched: false, content: Set.SetCard(color: purple, numberOfShapes: 1, shape: "Diamond", shading: 0.1), id: 54)"

When I print(card1.content) I get the values I'm looking for "SetCard(color: purple, numberOfShapes: 1, shape: "Diamond", shading: 0.1)"

How do I access these values?

struct SetGame<CardContent> where CardContent: Equatable {
    private(set) var cards: Array<Card>
    private(set) var dealtCards: Array<Card>
    private(set) var score = 0
    private(set) var selectedCards = [Int]()
    
    init(numberOfPairOfCards: Int, creatrCardContent: (Int) -> CardContent) {
        cards = []
        dealtCards = [Card]()
        for pairIndex in 0..<numberOfPairOfCards {
            let content: CardContent = creatrCardContent(pairIndex)
            cards.append(Card(content: content, id: pairIndex))
        }
        cards.shuffle()
        for _ in 0..<12{
            if let dealtCard = cards.popLast(){
                self.dealtCards.append(dealtCard)
            }
        }
    }
    
    mutating func choose(_ card: Card){

        if let chosenIndex = dealtCards.firstIndex(where: {$0.id == card.id}) {
            if !dealtCards[chosenIndex].isSelected {
                selectedCards.append(dealtCards[chosenIndex].id)
            } else {
                selectedCards.removeLast()
            }
            dealtCards[chosenIndex].isSelected = !dealtCards[chosenIndex].isSelected
            print(selectedCards)
        }
        if selectedCards.count == 3 {
            print("Let's see if they match")
            print(doesMatch(cards: selectedCards))
        }
    }
    
    func doesMatch(cards: [Int]) -> Bool {
        // Get card content from id in cards array
        
        if let card1 = dealtCards.first(where: { $0.id == cards[0]}) {
            print(card1)
            print(card1.content)
            print(card1.content.shape)
        }

        return false
    }
    
    
    struct Card: Identifiable {
        var isSelected = false
        var isMatched = false
        let content: CardContent
        let id : Int
    }
}

struct SetCard : Equatable {
    let color : Color
    let numberOfShapes: Int
    let shape : String
    let shading : Double
}

class SetGameViewModel: ObservableObject {
    typealias Card = SetGame<SetCard>.Card
    @Published private var model: SetGame<SetCard>
    
    
    init() {
        self.model = SetGameViewModel.createGame()
    }
    
    var cards: Array<Card> {
        return model.cards
    }
    
    var dealtCards: Array<Card> {
        return model.dealtCards
    }
    
    
    static func createGame() -> SetGame<SetCard> {
        var cardDeck : [SetCard] = []
        let shapes = ["Diamond", "Squiggle", "Oval"]
        let colors = [Color.red, Color.green, Color.purple]
        let counts = [1,2,3]
        let shades = [0.1, 0.5, 1]
        for color in colors {
            for shape in shapes {
                for count in counts {
                    for shade in shades {
                        let card = SetCard.init(color: color, numberOfShapes: count, shape: shape, shading: shade)
                        cardDeck.append(card)
                    }
                }
            }
        }

        return SetGame<SetCard>(numberOfPairOfCards: cardDeck.count) {pairIndex in
            cardDeck[pairIndex]
        }
    }
    
    // Mark:= Intents(s)
    func choose(_ card: Card){
        model.choose(card)
    }
}

CodePudding user response:

You made content generic, so it's type is actually Equatable. This means you need to cast the content to its actual type SetCard, and now you can access its properties:

if let card1 = dealtCards.first(where: { $0.id == cards[0]}) {
    print(card1)
    print(card1.content)
    let content = card1.content as! SetCard
    print(content.shape)
}

CodePudding user response:

You have made SetGame generic with respect to the contents of the cards, so presumably, you don't want your game logic to depend on, or assume anything about, what's on those cards, right? The game should work with any kind of card content.

However, in your doesMatch method, you are attempting to do exactly that, by accessing the card content's shape property. You are assuming that whatever the card content used for this game has a shape property. What if I used this random Foo struct that I just made up as the card content instead?

struct Foo: Equatable {
    let x: Int
}

There is no shape property in Foo, is there? This is why there is an error.

Judging from the method's name, doesMatch, you don't actually need to access the card's shape or anything like that. Why not just compare the equality of the card contents to see if they match? You have constrained them to be Equatable, after all.

if let card1 = dealtCards.first(where: { $0.id == cards[0]}),
   let card2 = dealtCards.first(where: { $0.id == cards[1]}) {
    let firstTwoCardsMatch = card1.content == card2.content
}

If you have more complicated matching logic, you should write a Matchable protocol like this:

protocol Matchable {
    func doesMatch(other: Self) -> Bool
}

struct SetGame<CardContent> where CardContent: Equatable & Matchable {
    ...

    // in doesMatch, you can do:
    let firstTwoCardsMatch = card1.content.doesMatch(other: card2.content)
}

// write your matching logic in SetCard
struct SetCard : Equatable, Matchable {
    let color : Color
    let numberOfShapes: Int
    let shape : String
    let shading : Double
    
    func doesMatch(other: SetCard) -> Bool {
        ...
    }
}

If you desperately want to access the shape property there for some reason, you can introduce a new protocol that has a shape property:

protocol CardContentWithShape {
    var shape: String { get }
}

Then conform SetCard to it:

struct SetCard : Equatable, CardContentWithShape {

and constrain CardContent to it:

struct SetGame<CardContent> where CardContent: Equatable & CardContentWithShape {

Now CardContent can only be something that is both Equatable and CardContentWithShape, so it must have a shape property, so you will be able to do:

print(card1.content.shape)
  • Related