Using if let... don't understand where data is coming from

129 Views Asked by At

I am following the Stanford CS193 SwiftUI iOS Development Course on YouTube and I am having a very difficult time comprehending how a certain piece of the code is working.

import Foundation

struct MemoryGame<CardContent> where CardContent: Equatable {
    private(set) var cards: Array<Card>
    
    **private var indexOfTheOneAndOnlyFaceUpCard: Int?**
    mutating func choose(_ card: Card) {
        if let chosenIndex = cards.firstIndex(where: { $0.id == card.id }),
           !cards[chosenIndex].isFaceUp,
           !cards[chosenIndex].isMatched
        {
            **if let potentialMatchIndex = indexOfTheOneAndOnlyFaceUpCard** {
                **if cards[chosenIndex].content == cards[potentialMatchIndex].content** {
                    cards[chosenIndex].isMatched = true
                    cards[potentialMatchIndex].isMatched = true
                }
                indexOfTheOneAndOnlyFaceUpCard = nil
            } else {
                for index in cards.indices {
                    cards[index].isFaceUp = false
                }
                indexOfTheOneAndOnlyFaceUpCard = chosenIndex
            }
            cards[chosenIndex].isFaceUp.toggle()
        }
        print("\(cards)")
    }

I have put asterisks around the lines I'm focusing on. I do not understand how cards[chosenIndex].content is being compared to cards[potentialMatchIndex].content, which stems from me not understanding how the if let potentialMatchIndex = indexOfTheOneAndOnlyFaceUpCard line is working. I believe I have a general understanding of optionals and how this code is running but I truly don't understand where the data/value is coming from for indexOfTheOneAndOnlyFaceUpCard. Here is the video for reference. He begins talking about these lines of code at 1:12:40. Thank you for any help in advance!

1

There are 1 best solutions below

2
On

In the memory game, a turn proceeds in two stages.

  1. First, one card is turned face up.

  2. Then, the user chooses another card. At that point, one of two things can happen: the new card is a match with the face up card or it isn't. But either way, that turn is over and all cards are now face down.

Thus, when the user chooses a card, if there is a face up card, we are in the middle of the turn, and we arrange things so there is no face up card; the turn is over.

But if there is no face up card, we are at the start of the turn, and we just turn the chosen card face up and wait for the second part of the turn.

So as far as the variable that tracks the face up card is concerned, the logic works like this:

var faceupCard : Card? = nil
if let card = faceupCard {
    faceupCard = nil
} else {
    faceupCard = chosenCard
}

So that's very clear, when pared down in that way: if the card is face up, make it not be face up; if it isn't face up, make it be face up.


The reason you are confused, in my opinion, is that this is a really bad way to write the code. The face-up-ness of a card is being used as a signal for whether we are in the middle of the turn or at the start of the turn. That's terrible programming. The way to signal what stage we are at is to have an enum whose job is to say what stage we are at! When you are programming you should say what you mean, not use a value in two different ways as is being done here (both track what card is face up and signal what stage of the turn we're on).


Another reason you might be confused is that everything has to be done in terms of indexes, because Card is a value type (struct). We cannot cycle through Cards, or point directly to the face up Card, or even ask a Card whether it is face up; we have to cycle thru indexes in our array of Cards, and track the index of the face up Card.