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)