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>
29 lines
852 B
Swift
29 lines
852 B
Swift
import Foundation
|
|
|
|
extension Array where Element == Card {
|
|
/// Returns the suffix of face-up cards from the end of the array.
|
|
var faceUpSuffix: [Card] {
|
|
var result: [Card] = []
|
|
for card in reversed() {
|
|
guard card.isFaceUp else { break }
|
|
result.insert(card, at: 0)
|
|
}
|
|
return result
|
|
}
|
|
|
|
/// Returns the top card (last element), or nil if empty.
|
|
var topCard: Card? { last }
|
|
}
|
|
|
|
extension Array {
|
|
/// Returns the longest suffix where all elements satisfy the predicate.
|
|
func trailingSuffix(while predicate: (Element) -> Bool) -> ArraySlice<Element> {
|
|
var startIndex = endIndex
|
|
for index in indices.reversed() {
|
|
guard predicate(self[index]) else { break }
|
|
startIndex = index
|
|
}
|
|
return self[startIndex..<endIndex]
|
|
}
|
|
}
|