274 lines
8.5 KiB
Swift
274 lines
8.5 KiB
Swift
//
|
|
// GameSettings.swift
|
|
// Blackjack
|
|
//
|
|
// User-configurable game settings including Blackjack rule variations.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
import CasinoKit
|
|
|
|
/// Blackjack rule variation presets.
|
|
enum BlackjackStyle: String, CaseIterable, Identifiable {
|
|
case vegas = "vegas" // Vegas Strip rules
|
|
case atlantic = "atlantic" // Atlantic City rules
|
|
case european = "european" // European no-hole-card
|
|
case custom = "custom"
|
|
|
|
var id: String { rawValue }
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .vegas: return String(localized: "Vegas Strip")
|
|
case .atlantic: return String(localized: "Atlantic City")
|
|
case .european: return String(localized: "European")
|
|
case .custom: return String(localized: "Custom")
|
|
}
|
|
}
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .vegas: return String(localized: "Dealer stands on soft 17, double after split, 3:2 blackjack")
|
|
case .atlantic: return String(localized: "Dealer stands on soft 17, late surrender, 8 decks")
|
|
case .european: return String(localized: "No hole card, dealer stands on soft 17, no surrender")
|
|
case .custom: return String(localized: "Customize all rules")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Number of decks in the shoe.
|
|
enum DeckCount: Int, CaseIterable, Identifiable {
|
|
case one = 1
|
|
case two = 2
|
|
case four = 4
|
|
case six = 6
|
|
case eight = 8
|
|
|
|
var id: Int { rawValue }
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .one: return "1 Deck"
|
|
case .two: return "2 Decks"
|
|
case .four: return "4 Decks"
|
|
case .six: return "6 Decks"
|
|
case .eight: return "8 Decks"
|
|
}
|
|
}
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .one: return String(localized: "Single deck, higher variance")
|
|
case .two: return String(localized: "Lower house edge")
|
|
case .four: return String(localized: "Common shoe game")
|
|
case .six: return String(localized: "Standard casino")
|
|
case .eight: return String(localized: "Maximum penetration")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Observable settings class for Blackjack configuration.
|
|
@Observable
|
|
@MainActor
|
|
final class GameSettings {
|
|
// MARK: - Game Style
|
|
|
|
/// The preset rule variation.
|
|
var gameStyle: BlackjackStyle = .vegas {
|
|
didSet {
|
|
applyStylePreset()
|
|
save()
|
|
}
|
|
}
|
|
|
|
// MARK: - Rule Options
|
|
|
|
/// Number of decks in the shoe.
|
|
var deckCount: DeckCount = .six { didSet { save() } }
|
|
|
|
/// Whether dealer hits on soft 17.
|
|
var dealerHitsSoft17: Bool = false { didSet { save() } }
|
|
|
|
/// Whether player can double after split.
|
|
var doubleAfterSplit: Bool = true { didSet { save() } }
|
|
|
|
/// Whether player can re-split aces.
|
|
var resplitAces: Bool = false { didSet { save() } }
|
|
|
|
/// Whether late surrender is allowed.
|
|
var lateSurrender: Bool = true { didSet { save() } }
|
|
|
|
/// Whether European no-hole-card rule is used (dealer gets second card after player acts).
|
|
var noHoleCard: Bool = false { didSet { save() } }
|
|
|
|
/// Whether insurance is offered.
|
|
var insuranceAllowed: Bool = true { didSet { save() } }
|
|
|
|
/// Blackjack payout ratio (1.5 = 3:2, 1.2 = 6:5)
|
|
var blackjackPayout: Double = 1.5 { didSet { save() } }
|
|
|
|
// MARK: - Betting Limits
|
|
|
|
/// The table limits preset.
|
|
var tableLimits: TableLimits = .low { didSet { save() } }
|
|
|
|
/// Minimum bet amount.
|
|
var minBet: Int { tableLimits.minBet }
|
|
|
|
/// Maximum bet amount.
|
|
var maxBet: Int { tableLimits.maxBet }
|
|
|
|
// MARK: - Starting Balance
|
|
|
|
/// The starting balance for new games.
|
|
var startingBalance: Int = 10_000 { didSet { save() } }
|
|
|
|
// MARK: - Animation Settings
|
|
|
|
/// Whether to show dealing animations.
|
|
var showAnimations: Bool = true { didSet { save() } }
|
|
|
|
/// Speed of card dealing (1.0 = normal)
|
|
var dealingSpeed: Double = 1.0 { didSet { save() } }
|
|
|
|
// MARK: - Display Settings
|
|
|
|
/// Whether to show the cards remaining indicator.
|
|
var showCardsRemaining: Bool = true { didSet { save() } }
|
|
|
|
/// Whether to show hand history.
|
|
var showHistory: Bool = true { didSet { save() } }
|
|
|
|
/// Whether to show dealer hints (suggested action).
|
|
var showHints: Bool = true { didSet { save() } }
|
|
|
|
/// Whether to show the running card count (Hi-Lo system).
|
|
var showCardCount: Bool = false { didSet { save() } }
|
|
|
|
// MARK: - Sound Settings
|
|
|
|
/// Whether sound effects are enabled.
|
|
var soundEnabled: Bool = true { didSet { save() } }
|
|
|
|
/// Whether haptic feedback is enabled.
|
|
var hapticsEnabled: Bool = true { didSet { save() } }
|
|
|
|
/// Volume level for sound effects.
|
|
var soundVolume: Float = 1.0 { didSet { save() } }
|
|
|
|
// MARK: - Initialization
|
|
|
|
init() {
|
|
self.persistence = CloudSyncManager<BlackjackSettingsData>()
|
|
load()
|
|
applyStylePreset()
|
|
}
|
|
|
|
// MARK: - Style Presets
|
|
|
|
private func applyStylePreset() {
|
|
guard gameStyle != .custom else { return }
|
|
|
|
switch gameStyle {
|
|
case .vegas:
|
|
deckCount = .six
|
|
dealerHitsSoft17 = false
|
|
doubleAfterSplit = true
|
|
resplitAces = false
|
|
lateSurrender = false
|
|
noHoleCard = false // American: dealer gets hole card upfront
|
|
blackjackPayout = 1.5
|
|
|
|
case .atlantic:
|
|
deckCount = .eight
|
|
dealerHitsSoft17 = false
|
|
doubleAfterSplit = true
|
|
resplitAces = true
|
|
lateSurrender = true
|
|
noHoleCard = false // American: dealer gets hole card upfront
|
|
blackjackPayout = 1.5
|
|
|
|
case .european:
|
|
deckCount = .six
|
|
dealerHitsSoft17 = false
|
|
doubleAfterSplit = true
|
|
resplitAces = false
|
|
lateSurrender = false
|
|
noHoleCard = true // European: dealer gets second card after player acts
|
|
blackjackPayout = 1.5
|
|
|
|
case .custom:
|
|
break
|
|
}
|
|
}
|
|
|
|
// MARK: - Persistence
|
|
|
|
private let persistence: CloudSyncManager<BlackjackSettingsData>
|
|
|
|
var iCloudAvailable: Bool {
|
|
FileManager.default.ubiquityIdentityToken != nil
|
|
}
|
|
|
|
func load() {
|
|
let data = persistence.load()
|
|
|
|
if let style = BlackjackStyle(rawValue: data.gameStyle) {
|
|
self.gameStyle = style
|
|
}
|
|
if let count = DeckCount(rawValue: data.deckCount) {
|
|
self.deckCount = count
|
|
}
|
|
if let limits = TableLimits(rawValue: data.tableLimits) {
|
|
self.tableLimits = limits
|
|
}
|
|
|
|
self.startingBalance = data.startingBalance
|
|
self.dealerHitsSoft17 = data.dealerHitsSoft17
|
|
self.doubleAfterSplit = data.doubleAfterSplit
|
|
self.resplitAces = data.resplitAces
|
|
self.lateSurrender = data.lateSurrender
|
|
self.noHoleCard = data.noHoleCard
|
|
self.blackjackPayout = data.blackjackPayout
|
|
self.insuranceAllowed = data.insuranceAllowed
|
|
self.showAnimations = data.showAnimations
|
|
self.dealingSpeed = data.dealingSpeed
|
|
self.showCardsRemaining = data.showCardsRemaining
|
|
self.showHistory = data.showHistory
|
|
self.showHints = data.showHints
|
|
self.showCardCount = data.showCardCount
|
|
self.soundEnabled = data.soundEnabled
|
|
self.hapticsEnabled = data.hapticsEnabled
|
|
self.soundVolume = data.soundVolume
|
|
}
|
|
|
|
func save() {
|
|
let data = BlackjackSettingsData(
|
|
lastModified: Date(),
|
|
gameStyle: gameStyle.rawValue,
|
|
deckCount: deckCount.rawValue,
|
|
tableLimits: tableLimits.rawValue,
|
|
startingBalance: startingBalance,
|
|
dealerHitsSoft17: dealerHitsSoft17,
|
|
doubleAfterSplit: doubleAfterSplit,
|
|
resplitAces: resplitAces,
|
|
lateSurrender: lateSurrender,
|
|
noHoleCard: noHoleCard,
|
|
blackjackPayout: blackjackPayout,
|
|
insuranceAllowed: insuranceAllowed,
|
|
showAnimations: showAnimations,
|
|
dealingSpeed: dealingSpeed,
|
|
showCardsRemaining: showCardsRemaining,
|
|
showHistory: showHistory,
|
|
showHints: showHints,
|
|
showCardCount: showCardCount,
|
|
soundEnabled: soundEnabled,
|
|
hapticsEnabled: hapticsEnabled,
|
|
soundVolume: soundVolume
|
|
)
|
|
persistence.save(data)
|
|
}
|
|
}
|
|
|