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>
62 lines
2.3 KiB
Swift
62 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
protocol GameRules: Sendable {
|
|
var variant: GameVariant { get }
|
|
|
|
/// Deal cards from a shuffled deck into the initial board layout.
|
|
func deal(deck: [Card]) -> GameSnapshot
|
|
|
|
/// Check if moving the given cards from source to destination is valid.
|
|
func canMove(cards: [Card], from: CardLocation, to: CardLocation, state: GameSnapshot) -> Bool
|
|
|
|
/// Return all valid destinations for the given cards from a source location.
|
|
func validDestinations(for cards: [Card], from: CardLocation, state: GameSnapshot) -> [CardLocation]
|
|
|
|
/// Draw card(s) from the stock pile.
|
|
func drawFromStock(state: inout GameSnapshot, drawCount: Int) -> MoveAction?
|
|
|
|
/// Calculate the score change for a given move.
|
|
func scoreForMove(from: CardLocation, to: CardLocation) -> Int
|
|
|
|
/// Check if the game is won.
|
|
func isWon(state: GameSnapshot) -> Bool
|
|
|
|
/// Check if auto-complete is possible (all remaining cards are face-up and ordered).
|
|
func canAutoComplete(state: GameSnapshot) -> Bool
|
|
|
|
/// Find available hints, ordered by priority (1 = highest).
|
|
func findHints(state: GameSnapshot, settings: DifficultySettings) -> [HintResult]
|
|
|
|
/// Check if a card can be stacked onto another card on a tableau.
|
|
func canStackOnTableau(card: Card, onto target: Card) -> Bool
|
|
|
|
/// Check if a group of cards can be picked up from a location.
|
|
func canPickUp(cards: [Card], from: CardLocation, state: GameSnapshot) -> Bool
|
|
}
|
|
|
|
extension GameRules {
|
|
func validDestinations(for cards: [Card], from: CardLocation, state: GameSnapshot) -> [CardLocation] {
|
|
var destinations: [CardLocation] = []
|
|
|
|
for i in 0..<state.foundations.count {
|
|
if canMove(cards: cards, from: from, to: .foundation(i), state: state) {
|
|
destinations.append(.foundation(i))
|
|
}
|
|
}
|
|
|
|
for i in 0..<state.tableaus.count {
|
|
if canMove(cards: cards, from: from, to: .tableau(i), state: state) {
|
|
destinations.append(.tableau(i))
|
|
}
|
|
}
|
|
|
|
for i in 0..<state.freeCells.count {
|
|
if canMove(cards: cards, from: from, to: .freeCell(i), state: state) {
|
|
destinations.append(.freeCell(i))
|
|
}
|
|
}
|
|
|
|
return destinations
|
|
}
|
|
}
|