Complete native rewrite of the web-based SoliCards game as a SwiftUI multiplatform app targeting iOS 17+, iPadOS 17+, and macOS 14+. Three solitaire variants (Klondike, Spider, FreeCell) with full game rules, drag & drop, smart zoom layout, 6 themes, 4 difficulty levels, SwiftData persistence, VoiceOver accessibility, and 57 unit tests. Key features: - MVVM + Protocol-Oriented Strategy architecture - DragGesture with coordinate-space hit-testing (long press + drag) - Smart zoom: cards auto-size to fit screen based on deepest column - Landscape: 30% bigger cards with scrollable overflow (iOS) - macOS: 120pt card cap, 92% height buffer for window resizing - Auto-save, game resume, statistics tracking via SwiftData - Privacy manifest, app icon, String Catalog, zero dependencies Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
78 lines
1.7 KiB
Swift
78 lines
1.7 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
@Observable
|
|
final class GameState {
|
|
var tableaus: [[Card]]
|
|
var foundations: [[Card]]
|
|
var stock: [Card]
|
|
var waste: [Card]
|
|
var freeCells: [Card?]
|
|
|
|
var moves: Int = 0
|
|
var score: Int = 0
|
|
var phase: GamePhase = .notStarted
|
|
|
|
private(set) var history: [GameSnapshot] = []
|
|
private let maxHistorySize = 20
|
|
|
|
var canUndo: Bool { !history.isEmpty }
|
|
|
|
init() {
|
|
self.tableaus = []
|
|
self.foundations = []
|
|
self.stock = []
|
|
self.waste = []
|
|
self.freeCells = []
|
|
}
|
|
|
|
func snapshot() -> GameSnapshot {
|
|
GameSnapshot(
|
|
tableaus: tableaus,
|
|
foundations: foundations,
|
|
stock: stock,
|
|
waste: waste,
|
|
freeCells: freeCells,
|
|
moves: moves,
|
|
score: score
|
|
)
|
|
}
|
|
|
|
func restore(from snapshot: GameSnapshot) {
|
|
tableaus = snapshot.tableaus
|
|
foundations = snapshot.foundations
|
|
stock = snapshot.stock
|
|
waste = snapshot.waste
|
|
freeCells = snapshot.freeCells
|
|
moves = snapshot.moves
|
|
score = snapshot.score
|
|
}
|
|
|
|
func pushHistory() {
|
|
history.append(snapshot())
|
|
if history.count > maxHistorySize {
|
|
history.removeFirst()
|
|
}
|
|
}
|
|
|
|
func popHistory() -> GameSnapshot? {
|
|
history.popLast()
|
|
}
|
|
|
|
func clearHistory() {
|
|
history.removeAll()
|
|
}
|
|
|
|
func reset(from snapshot: GameSnapshot, variant: GameVariant) {
|
|
restore(from: snapshot)
|
|
phase = .playing
|
|
clearHistory()
|
|
|
|
if variant.hasFreeCells {
|
|
freeCells = Array(repeating: nil, count: variant.freeCellCount)
|
|
} else {
|
|
freeCells = []
|
|
}
|
|
}
|
|
}
|