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>
73 lines
3.3 KiB
Swift
73 lines
3.3 KiB
Swift
import SwiftUI
|
|
|
|
struct RulesView: View {
|
|
let variant: GameVariant
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
switch variant {
|
|
case .klondike:
|
|
klondikeRules
|
|
case .spider:
|
|
spiderRules
|
|
case .freeCell:
|
|
freeCellRules
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
.navigationTitle("\(variant.displayName) Rules")
|
|
#if os(iOS)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
#endif
|
|
.toolbar {
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button("Done") { dismiss() }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var klondikeRules: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
ruleSection("Objective", "Move all 52 cards to the four foundation piles, building each from Ace to King in the same suit.")
|
|
ruleSection("Tableau", "Build downward in alternating colors (red on black, black on red). Only Kings can be placed on empty columns.")
|
|
ruleSection("Foundation", "Build upward by suit from Ace to King.")
|
|
ruleSection("Stock & Waste", "Draw cards from the stock pile. The number drawn depends on difficulty (1 or 3 cards).")
|
|
ruleSection("Moving Groups", "You can move a properly sequenced group of face-up cards as a unit.")
|
|
}
|
|
}
|
|
|
|
private var spiderRules: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
ruleSection("Objective", "Build eight complete King-to-Ace sequences of the same suit. Completed sequences are automatically removed to the foundation.")
|
|
ruleSection("Tableau", "Build downward regardless of suit. However, only same-suit sequences can be moved as a group.")
|
|
ruleSection("Stock", "Deal one card to each of the 10 columns. All columns must have at least one card before dealing.")
|
|
ruleSection("Completing", "When a complete K through A sequence of the same suit is formed on a tableau, it automatically moves to a foundation pile.")
|
|
}
|
|
}
|
|
|
|
private var freeCellRules: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
ruleSection("Objective", "Move all 52 cards to the four foundation piles, building each from Ace to King in the same suit.")
|
|
ruleSection("Tableau", "Build downward in alternating colors. Any card can go on an empty column.")
|
|
ruleSection("Free Cells", "Four temporary storage cells. Each holds one card at a time.")
|
|
ruleSection("Power Moves", "The number of cards you can move at once depends on empty free cells and empty columns: (1 + empty cells) \u{00D7} 2^(empty columns).")
|
|
ruleSection("Foundation", "Build upward by suit from Ace to King.")
|
|
}
|
|
}
|
|
|
|
private func ruleSection(_ title: String, _ body: String) -> some View {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(title)
|
|
.font(.headline)
|
|
Text(body)
|
|
.font(.body)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|