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>
47 lines
1.3 KiB
Swift
47 lines
1.3 KiB
Swift
import AVFoundation
|
|
import Foundation
|
|
|
|
enum SoundEffect: String, Sendable {
|
|
case cardFlip = "card_flip"
|
|
case cardPlace = "card_place"
|
|
case error = "error"
|
|
case victory = "victory"
|
|
}
|
|
|
|
@Observable
|
|
final class SoundManager: @unchecked Sendable {
|
|
var isEnabled: Bool = true
|
|
private var players: [SoundEffect: AVAudioPlayer] = [:]
|
|
private let queue = DispatchQueue(label: "com.solicards.sound")
|
|
|
|
init() {
|
|
preload()
|
|
}
|
|
|
|
func play(_ effect: SoundEffect) async {
|
|
guard isEnabled else { return }
|
|
queue.async { [weak self] in
|
|
guard let player = self?.players[effect] else { return }
|
|
player.currentTime = 0
|
|
player.play()
|
|
}
|
|
}
|
|
|
|
private func preload() {
|
|
for effect in SoundEffect.allCases {
|
|
if let url = Bundle.main.url(forResource: effect.rawValue, withExtension: "caf",
|
|
subdirectory: "Sounds") {
|
|
do {
|
|
let player = try AVAudioPlayer(contentsOf: url)
|
|
player.prepareToPlay()
|
|
players[effect] = player
|
|
} catch {
|
|
// Sound will be unavailable — not a fatal error
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SoundEffect: CaseIterable {}
|