CasinoGames/CasinoKit/Sources/CasinoKit/Models/CasinoGameState.swift

164 lines
4.7 KiB
Swift

//
// CasinoGameState.swift
// CasinoKit
//
// Protocol defining common state management patterns for casino games.
// Extends SessionManagedGame with additional shared functionality.
//
import Foundation
/// Protocol for casino game state classes that manage game flow,
/// session tracking, and common behaviors.
///
/// This extends `SessionManagedGame` to add additional requirements
/// that are common across all casino games in this app.
@MainActor
public protocol CasinoGameState: SessionManagedGame {
/// The settings object conforming to GameSettingsProtocol.
associatedtype GameSettingsType: GameSettingsProtocol
/// Game-specific settings.
var settings: GameSettingsType { get }
/// The shared sound manager.
var soundManager: SoundManager { get }
/// Onboarding state for first-time users.
var onboarding: OnboardingState { get }
/// Resets the game to starting conditions (balance, cards, etc.)
/// without clearing session history.
func resetGame()
/// Clears all data including session history.
func clearAllData()
/// Aggregated statistics from all sessions.
var aggregatedStats: AggregatedSessionStats { get }
}
// MARK: - Default Implementations
public extension CasinoGameState {
/// Default implementation uses shared SoundManager.
var soundManager: SoundManager { SoundManager.shared }
/// Default aggregated stats computed from session history.
var aggregatedStats: AggregatedSessionStats {
sessionHistory.aggregatedStats()
}
/// Play a sound effect if enabled.
func playSound(_ sound: GameSound) {
soundManager.play(sound)
}
/// Play light haptic feedback if enabled.
func hapticLight() {
soundManager.hapticLight()
}
/// Play medium haptic feedback if enabled.
func hapticMedium() {
soundManager.hapticMedium()
}
/// Play success haptic feedback if enabled.
func hapticSuccess() {
soundManager.hapticSuccess()
}
/// Play error haptic feedback if enabled.
func hapticError() {
soundManager.hapticError()
}
/// Convenience: Check if we should play sounds.
var isSoundEnabled: Bool {
settings.soundEnabled && soundManager.soundEnabled
}
/// Convenience: Check if we should play haptics.
var isHapticsEnabled: Bool {
settings.hapticsEnabled && soundManager.hapticsEnabled
}
}
// MARK: - Balance Management Extensions
public extension CasinoGameState {
/// Whether the player is out of chips (balance is 0 or below minimum bet).
var isBroke: Bool {
balance < settings.minBet
}
/// Whether the player can afford a bet of the given amount.
func canAffordBet(_ amount: Int) -> Bool {
balance >= amount
}
/// The maximum bet the player can currently place.
var maxAffordableBet: Int {
min(balance, settings.maxBet)
}
}
// MARK: - Game Flow Extensions
public extension CasinoGameState {
/// Ends the current session due to running out of chips.
func endSessionDueToBroke() {
if currentSession != nil {
endCurrentSession(reason: .brokeOut)
}
}
/// Checks if the player is broke and handles session ending if needed.
/// Returns true if the player is broke.
@discardableResult
func checkBrokeStatus() -> Bool {
if isBroke {
endSessionDueToBroke()
return true
}
return false
}
}
// MARK: - Game Reset Extensions
public extension CasinoGameState {
/// Default implementation of resetGame that properly handles session ending.
/// Games can override this but should call the helper methods in the correct order.
///
/// The correct order is:
/// 1. End current session (captures actual ending balance)
/// 2. Reset balance to starting balance
/// 3. Reset game-specific state (via resetForNewSession)
/// 4. Start new session with fresh balance
/// 5. Save data
func performResetGame() {
// 1. End current session FIRST while balance still shows actual state
if currentSession != nil {
endCurrentSession(reason: .manualEnd)
}
// 2. Reset balance to starting balance
balance = startingBalance
// 3. Let game reset its specific state (reshuffle deck, clear history, etc.)
resetForNewSession()
// 4. Create new session with fresh balance
currentSession = GameSession<Stats>(
gameStyle: currentGameStyle,
startingBalance: balance
)
// 5. Save
saveGameData()
}
}