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:
- @pd95 – https://github.com/pd95/CS193p-Memorize/tree/main-2020-spring/Memorize
- @sk-ruban – https://github.com/sk-ruban/CS193p
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.