XCode-Claude-Workflow/SoliCards/Services/SoundManager.swift
idev2025 0f989f5c86 feat: SoliCards v1.2.0 — native SwiftUI solitaire for iOS, iPadOS, macOS
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>
2026-04-14 07:33:52 -04:00

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 {}