126 lines
3.4 KiB
Swift
126 lines
3.4 KiB
Swift
//
|
|
// MemoryGame.swift
|
|
// Memorize
|
|
//
|
|
// Created by ching on 2023/2/12.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// model
|
|
struct MemoryGame<CardContent> where CardContent: Equatable {
|
|
private(set) var cards: [Card]
|
|
private var indexOfTheOneAndOnlyFaceUpCard: Int? {
|
|
get { cards.indices.filter { cards[$0].isFaceUp }.oneAndOnly }
|
|
set { cards.indices.forEach { cards[$0].isFaceUp = ($0 == newValue) } }
|
|
}
|
|
|
|
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
|
|
}
|
|
cards[chosenIndex].isFaceUp.toggle()
|
|
} else {
|
|
indexOfTheOneAndOnlyFaceUpCard = chosenIndex
|
|
}
|
|
}
|
|
}
|
|
|
|
mutating func shuffle() {
|
|
cards.shuffle()
|
|
}
|
|
|
|
func index(of card: Card) -> Int? {
|
|
for index in 0 ..< cards.count {
|
|
if cards[index].id == card.id {
|
|
return index
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
init(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
|
|
cards = []
|
|
|
|
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 = false {
|
|
didSet {
|
|
if isFaceUp {
|
|
startUsingBonusTime()
|
|
} else {
|
|
stopUsingBonusTime()
|
|
}
|
|
}
|
|
}
|
|
|
|
var isMatched = false {
|
|
didSet {
|
|
stopUsingBonusTime()
|
|
}
|
|
}
|
|
|
|
let content: CardContent
|
|
let id: Int
|
|
|
|
var bonusTimeLimit = const.DrawingConstants.bonusTimeLimit
|
|
|
|
private var faceUpTime: TimeInterval {
|
|
if let lastFaceUpDate = lastFaceUpDate {
|
|
return pastFaceUpTime + Date().timeIntervalSince(lastFaceUpDate)
|
|
} else {
|
|
return pastFaceUpTime
|
|
}
|
|
}
|
|
|
|
var lastFaceUpDate: Date?
|
|
var pastFaceUpTime: TimeInterval = 0
|
|
var bonusTimeRemaining: TimeInterval {
|
|
max(0, bonusTimeLimit - faceUpTime)
|
|
}
|
|
|
|
var bonusRemaining: Double {
|
|
(bonusTimeLimit > 0 && bonusTimeRemaining > 0) ? bonusTimeRemaining / bonusTimeLimit : 0
|
|
}
|
|
|
|
var hasEarnedBonus: Bool {
|
|
isMatched && bonusTimeRemaining > 0
|
|
}
|
|
|
|
var isConsumingBonusTime: Bool {
|
|
isFaceUp && !isMatched && bonusTimeRemaining > 0
|
|
}
|
|
|
|
private mutating func startUsingBonusTime() {
|
|
if isConsumingBonusTime, lastFaceUpDate == nil {
|
|
lastFaceUpDate = Date()
|
|
}
|
|
}
|
|
|
|
private mutating func stopUsingBonusTime() {
|
|
pastFaceUpTime = faceUpTime
|
|
lastFaceUpDate = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Array {
|
|
var oneAndOnly: Element? {
|
|
if count == 1 {
|
|
return first
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|