Categories
Programming

CS193p Spring 2021 Lecture 4 & Assignment 2

After lecture 4

A lot of this stuff still isn’t making a ton of sense to me. I really struggled with how to init the theme for assignment 2. The key was optionals. How to do it came to me in the shower. I am writing this post after doing the changes from Lecture 4, Assignment 2, and Lecture 5 so I don’t have a whole lot specifically around lecture 4.

Code

The viewModel is now hooked up to both the View and the Model. This MVVM stuff is clicking for me, thankfully.

MemoryGame.swift – includes scoring:

//
//  MemoryGame.swift
//  Memorize
//
//  Created by Austin on 5/28/21.
//  austinsnerdythings.com

import Foundation

// model
struct MemoryGame<CardContent> where CardContent: Equatable {
    private(set) var cards: Array<Card>
    private var indexOfTheOneAndOnlyFaceUpCard: Int?
    private(set) var score = 0
    
    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 {
                cards[chosenIndex].hasBeenSeenThisManyTimes += 1
                cards[potentialMatchIndex].hasBeenSeenThisManyTimes += 1
                if cards[chosenIndex].content == cards[potentialMatchIndex].content {
                    // match
                    cards[chosenIndex].isMatched = true
                    cards[potentialMatchIndex].isMatched = true
                    score += 2
                } else if cards[chosenIndex].hasBeenSeenThisManyTimes > 1 ||
							cards[potentialMatchIndex].hasBeenSeenThisManyTimes > 1 {
					// mismatch
					 score -= 1
				}
                indexOfTheOneAndOnlyFaceUpCard = nil
            } else {
                for index in cards.indices {
                    cards[index].isFaceUp = false
                }
                indexOfTheOneAndOnlyFaceUpCard = chosenIndex
            }
            
            cards[chosenIndex].isFaceUp.toggle()
        }
        print("\(cards)")
    }
    
    init(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
        cards = Array<Card>()
        // add number of pairs of cards x 2 cards to card array
        for pairIndex in 0..<numberOfPairsOfCards {
            let content: CardContent = createCardContent(pairIndex)
            cards.append(Card(content: content, id: pairIndex*2))
            cards.append(Card(content: content, id: pairIndex*2+1))

        }
        cards.shuffle()
    }
    
    struct Card: Identifiable {
        var isFaceUp: Bool = false
        var isMatched: Bool = false
        var content: CardContent
        var id: Int
        var hasBeenSeenThisManyTimes: Int = 0
    }
}

EmojiMemoryGame.swift – we’ve moved the theme stuff into its own struct/file

//
//  EmojiMemoryGame.swift
//  Memorize
//
//  Created by Austin on 5/28/21.
//  austinsnerdythings.com

import SwiftUI

// viewModel
class EmojiMemoryGame: ObservableObject {
    @Published private var gameModel: MemoryGame<String>
    private(set) var theme: Theme
    
    static func createMemoryGame(theme: Theme) -> MemoryGame<String> {
        let emojis: Array<String> = theme.emojis.shuffled()
		var cardsToShow = theme.numberOfPairsOfCards ?? Int.random(in: 3...theme.emojis.count)
		if cardsToShow > theme.emojis.count {
			cardsToShow = theme.emojis.count
		}
        return MemoryGame<String>(numberOfPairsOfCards: cardsToShow) { pairIndex in
            emojis[pairIndex]
        }
    }
    
	init(startingTheme: Theme? = nil)
    {
		let selectedTheme = startingTheme ?? themes.randomElement()!
		self.theme = selectedTheme
		gameModel = EmojiMemoryGame.createMemoryGame(theme: selectedTheme)
    }

    var cards: Array<MemoryGame<String>.Card> {
        return gameModel.cards
    }
	
	var score: Int {
		return gameModel.score
	}
    
    // MARK: - INTENTS
    func choose(_ card: MemoryGame<String>.Card) {
        gameModel.choose(card)
    }
    
    func startNewGame() {
        let newTheme = themes.randomElement()!
		self.theme = newTheme
		gameModel = EmojiMemoryGame.createMemoryGame(theme: newTheme)
    }
}

MemorizeApp.swift – added the viewModel argument to the init here

//
//  MemorizeApp.swift
//  Memorize
//
//  Created by Austin on 5/25/21.
//  austinsnerdythings.com

import SwiftUI

@main
struct MemorizeApp: App {
    let game = EmojiMemoryGame()
    
    var body: some Scene {
        WindowGroup {
            ContentView(viewModel: game)
        }
    }
}

Theme.swift

//
//  Theme.swift
//  Memorize
//
//  Created by Austin on 6/7/21.
//

import Foundation
import SwiftUI

//    struct Theme: Identifiable {
struct Theme {
    var name: String
    var emojis: [String]
    var numberOfPairsOfCards: Int?
    var baseColor: Color
}

let themes: [Theme] = [
	Theme(name: "vehicles",
		  emojis: ["?","?","?","?","?","?","?","?","?","?","?","?","?","✈️","?","?","?","?","?","?","?","?","?","?"],
		  baseColor: Color.red),
	Theme(name: "fruits",
		  emojis: ["?","?","?","?","?","?","?","?","?","?","?","?"],
		  baseColor: Color.yellow),
	Theme(name: "animals",
		  emojis: ["?","?","?","?","?","?","?","?","?","?","?","?"],
		  numberOfPairsOfCards: 20,
		  baseColor: Color.blue)

]

ContentView.swift

//
//  ContentView.swift
//  Memorize - Stanford CS193p, Spring 2021
//  After assignment 1
//
//  Created by Austin from austinsnerdythings.com on 5/27/21.
//

import SwiftUI

// view
struct ContentView: View {
    @ObservedObject var viewModel: EmojiMemoryGame
    
    var body: some View {
        VStack {
			HStack {
				Text("Memorize!").font(.largeTitle)
				Spacer()
				HStack {
					VStack {
						Text(viewModel.theme.name).font(.title)
						Text("Score: \(viewModel.score)")
					}
					Button("New Game") {
						viewModel.startNewGame()
					}
				}
				
			}
            ScrollView {
                LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]){
                    ForEach(viewModel.cards[0..<viewModel.cards.count]) { card in
                        CardView(card: card)
                            .aspectRatio(2/3, contentMode: .fit)
                            .onTapGesture {
                                viewModel.choose(card)
                            }
                    }
                }
            }
            .foregroundColor(viewModel.theme.baseColor)
            .font(.largeTitle)
            .padding(.horizontal)
        }
    }
}

struct CardView: View {
    let card: MemoryGame<String>.Card
    var body: some View {
        ZStack {
            let shape = RoundedRectangle(cornerRadius: 20)
            if card.isFaceUp {
                shape.fill().foregroundColor(.white)
                shape.strokeBorder(lineWidth: 3)
                Text(card.content).font(.largeTitle)
            } else if card.isMatched {
                shape.opacity(0)
            } else {
                shape.fill()
            }
        }
    
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let game = EmojiMemoryGame()
        ContentView(viewModel: game)
            .preferredColorScheme(.light)
        ContentView(viewModel: game)
            .preferredColorScheme(.dark)
    }
}

References

I gained some inspiration (and cleared up a lot of confusion) from two GitHub repos:

Conclusion

Still a slog. Still learning. CS193p Spring 2021 Lecture 4 is probably where I would start wondering if I should drop the class if I was a Stanford student. The stuff from lecture 5 (post coming up) where Professor took 20 lines and shrunk it to 2 is still a bit much for me. He says it improves readability. It does, but stuffing everything into a single line does hinder debugging.

2 replies on “CS193p Spring 2021 Lecture 4 & Assignment 2”

Thank you for sharing. Lecture 4 is exactly where I realized I need to slow down and digest. I’m taking this course with only knowledge of python, so lots to take in. Declaring variables and structures is different Than the functional programming I do in python. Your solution helps me to organize my thoughts. But I will be reviewing the info in these lectures for a while before moving forward.

I agree that doing homework assignment 2 after lecture 4 is like trying to swallow an elephant. The problem is, no hints or directions are given about how to create and switch between multiple screens. I don’t have time to make doing these assignments major research projects. I would imagine that the students at Stanford taking this class did a lot of knowledge sharing within the class and took advantage of the professors office hours to get this homework assignment done. I eventually broke down and looked into swift you are is navigation view and navigation link to do the job by their standard meant.

In Austin’s conclusion, I agree about the dubious advantages of shrinking syntax down so much. I suppose after you get used to it that it’s useful. But until then, when a new Swift programmer looks at these cryptic statements, it’s hard to understand what the heck is going on. But that’s life, so I will just learn to deal with it. Thanks for posting this Austin.

Leave a Reply

Your email address will not be published. Required fields are marked *