Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
889e91a8ca
commit
98d72d0db8
@ -485,7 +485,11 @@ final class GameState {
|
||||
evaluateSideBets()
|
||||
|
||||
// Check for insurance offer (only in American style with hole card)
|
||||
if !settings.noHoleCard, let upCard = dealerUpCard, engine.shouldOfferInsurance(dealerUpCard: upCard) {
|
||||
// Skip if user has opted out of insurance prompts
|
||||
if !settings.noHoleCard,
|
||||
!settings.neverAskInsurance,
|
||||
let upCard = dealerUpCard,
|
||||
engine.shouldOfferInsurance(dealerUpCard: upCard) {
|
||||
currentPhase = .insurance
|
||||
return
|
||||
}
|
||||
@ -567,6 +571,12 @@ final class GameState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Declines insurance and sets the "never ask again" preference.
|
||||
func neverAskInsurance() {
|
||||
settings.neverAskInsurance = true
|
||||
declineInsurance()
|
||||
}
|
||||
|
||||
// MARK: - Player Actions
|
||||
|
||||
/// Player hits (takes another card).
|
||||
|
||||
@ -105,6 +105,9 @@ final class GameSettings {
|
||||
/// Whether insurance is offered.
|
||||
var insuranceAllowed: Bool = true { didSet { save() } }
|
||||
|
||||
/// Whether to skip the insurance prompt and auto-decline.
|
||||
var neverAskInsurance: Bool = false { didSet { save() } }
|
||||
|
||||
/// Blackjack payout ratio (1.5 = 3:2, 1.2 = 6:5)
|
||||
var blackjackPayout: Double = 1.5 { didSet { save() } }
|
||||
|
||||
@ -237,6 +240,7 @@ final class GameSettings {
|
||||
self.noHoleCard = data.noHoleCard
|
||||
self.blackjackPayout = data.blackjackPayout
|
||||
self.insuranceAllowed = data.insuranceAllowed
|
||||
self.neverAskInsurance = data.neverAskInsurance
|
||||
self.sideBetsEnabled = data.sideBetsEnabled
|
||||
self.showAnimations = data.showAnimations
|
||||
self.dealingSpeed = data.dealingSpeed
|
||||
@ -263,6 +267,7 @@ final class GameSettings {
|
||||
noHoleCard: noHoleCard,
|
||||
blackjackPayout: blackjackPayout,
|
||||
insuranceAllowed: insuranceAllowed,
|
||||
neverAskInsurance: neverAskInsurance,
|
||||
sideBetsEnabled: sideBetsEnabled,
|
||||
showAnimations: showAnimations,
|
||||
dealingSpeed: dealingSpeed,
|
||||
@ -290,6 +295,7 @@ final class GameSettings {
|
||||
noHoleCard = false
|
||||
blackjackPayout = 1.5
|
||||
insuranceAllowed = true
|
||||
neverAskInsurance = false
|
||||
sideBetsEnabled = false
|
||||
showAnimations = true
|
||||
dealingSpeed = 1.0
|
||||
|
||||
@ -1057,6 +1057,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Auto-decline when dealer shows Ace" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Auto-decline when dealer shows Ace"
|
||||
}
|
||||
},
|
||||
"es-MX" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Rechazar automáticamente cuando el crupier muestra As"
|
||||
}
|
||||
},
|
||||
"fr-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Refuser automatiquement quand le croupier montre un As"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Baccarat" : {
|
||||
"comment" : "The name of a casino game.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
@ -2625,6 +2647,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Don't Ask" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Don't Ask"
|
||||
}
|
||||
},
|
||||
"es-MX" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No preguntar"
|
||||
}
|
||||
},
|
||||
"fr-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ne pas demander"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Double" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -5800,6 +5844,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Skip Insurance Prompt" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Skip Insurance Prompt"
|
||||
}
|
||||
},
|
||||
"es-MX" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Omitir aviso de seguro"
|
||||
}
|
||||
},
|
||||
"fr-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ignorer l'invitation d'assurance"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Some games: Dealer hits on 'soft 17' (Ace + 6)." : {
|
||||
"comment" : "Description of a rule where the dealer must hit on a 'soft 17' (Ace + 6) in some blackjack games.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
|
||||
@ -66,6 +66,7 @@ struct BlackjackSettingsData: PersistableGameData {
|
||||
noHoleCard: false,
|
||||
blackjackPayout: 1.5,
|
||||
insuranceAllowed: true,
|
||||
neverAskInsurance: false,
|
||||
sideBetsEnabled: false,
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
@ -90,6 +91,7 @@ struct BlackjackSettingsData: PersistableGameData {
|
||||
var noHoleCard: Bool
|
||||
var blackjackPayout: Double
|
||||
var insuranceAllowed: Bool
|
||||
var neverAskInsurance: Bool
|
||||
var sideBetsEnabled: Bool
|
||||
var showAnimations: Bool
|
||||
var dealingSpeed: Double
|
||||
|
||||
@ -121,10 +121,15 @@ enum Design {
|
||||
// MARK: - Card Deal Animation
|
||||
|
||||
enum DealAnimation {
|
||||
/// Horizontal offset for card deal (from upper-right, simulating shoe)
|
||||
static let offsetX: CGFloat = 150
|
||||
/// Vertical offset for card deal (from above the table)
|
||||
static let offsetY: CGFloat = -200
|
||||
/// Horizontal offset for dealer cards (shoe is nearby, less horizontal travel)
|
||||
static let dealerOffsetX: CGFloat = 120
|
||||
/// Vertical offset for dealer cards (small since near top)
|
||||
static let dealerOffsetY: CGFloat = -80
|
||||
|
||||
/// Horizontal offset for player cards (shoe is far away, more horizontal travel)
|
||||
static let playerOffsetX: CGFloat = 180
|
||||
/// Vertical offset for player cards (large since far from top)
|
||||
static let playerOffsetY: CGFloat = -350
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -186,7 +186,8 @@ struct GameTableView: View {
|
||||
betAmount: state.currentBet / 2,
|
||||
balance: state.balance,
|
||||
onTake: { Task { await state.takeInsurance() } },
|
||||
onDecline: { state.declineInsurance() }
|
||||
onDecline: { state.declineInsurance() },
|
||||
onNeverAsk: { state.neverAskInsurance() }
|
||||
)
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
|
||||
@ -146,6 +146,15 @@ struct SettingsView: View {
|
||||
isOn: $settings.showCardsRemaining,
|
||||
accentColor: accent
|
||||
)
|
||||
|
||||
Divider().background(Color.white.opacity(Design.Opacity.hint))
|
||||
|
||||
SettingsToggle(
|
||||
title: String(localized: "Skip Insurance Prompt"),
|
||||
subtitle: String(localized: "Auto-decline when dealer shows Ace"),
|
||||
isOn: $settings.neverAskInsurance,
|
||||
accentColor: accent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -91,6 +91,8 @@ struct BlackjackTableView: View {
|
||||
hand: state.dealerHand,
|
||||
showHoleCard: state.shouldShowDealerHoleCard,
|
||||
showCardCount: showCardCount,
|
||||
showAnimations: state.settings.showAnimations,
|
||||
dealingSpeed: state.settings.dealingSpeed,
|
||||
cardWidth: cardWidth,
|
||||
cardSpacing: cardSpacing
|
||||
)
|
||||
@ -121,6 +123,8 @@ struct BlackjackTableView: View {
|
||||
activeHandIndex: state.activeHandIndex,
|
||||
isPlayerTurn: state.isPlayerTurn,
|
||||
showCardCount: showCardCount,
|
||||
showAnimations: state.settings.showAnimations,
|
||||
dealingSpeed: state.settings.dealingSpeed,
|
||||
cardWidth: cardWidth,
|
||||
cardSpacing: cardSpacing,
|
||||
currentHint: state.currentHint,
|
||||
|
||||
@ -12,12 +12,19 @@ struct DealerHandView: View {
|
||||
let hand: BlackjackHand
|
||||
let showHoleCard: Bool
|
||||
let showCardCount: Bool
|
||||
let showAnimations: Bool
|
||||
let dealingSpeed: Double
|
||||
let cardWidth: CGFloat
|
||||
let cardSpacing: CGFloat
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||
@ScaledMetric(relativeTo: .headline) private var badgeHeight: CGFloat = CasinoDesign.Size.valueBadge
|
||||
|
||||
/// Scaled animation duration based on dealing speed.
|
||||
private var animationDuration: Double {
|
||||
Design.Animation.springDuration * dealingSpeed
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
@ -62,12 +69,14 @@ struct DealerHandView: View {
|
||||
}
|
||||
.zIndex(Double(index))
|
||||
.transition(
|
||||
.asymmetric(
|
||||
insertion: .offset(x: Design.DealAnimation.offsetX, y: Design.DealAnimation.offsetY)
|
||||
showAnimations
|
||||
? .asymmetric(
|
||||
insertion: .offset(x: Design.DealAnimation.dealerOffsetX, y: Design.DealAnimation.dealerOffsetY)
|
||||
.combined(with: .opacity)
|
||||
.combined(with: .scale(scale: Design.Scale.slightShrink)),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
)
|
||||
: .identity
|
||||
)
|
||||
}
|
||||
|
||||
@ -78,7 +87,12 @@ struct DealerHandView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.animation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce), value: hand.cards.count)
|
||||
.animation(
|
||||
showAnimations
|
||||
? .spring(duration: animationDuration, bounce: Design.Animation.springBounce)
|
||||
: .none,
|
||||
value: hand.cards.count
|
||||
)
|
||||
.overlay(alignment: .bottom) {
|
||||
// Result badge - overlayed so it doesn't add height to the view
|
||||
if let result = hand.cards.count >= 2 && showHoleCard ? handResultText : nil {
|
||||
@ -136,6 +150,8 @@ struct DealerHandView: View {
|
||||
hand: BlackjackHand(),
|
||||
showHoleCard: false,
|
||||
showCardCount: false,
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
cardWidth: 60,
|
||||
cardSpacing: -20
|
||||
)
|
||||
@ -152,6 +168,8 @@ struct DealerHandView: View {
|
||||
]),
|
||||
showHoleCard: false,
|
||||
showCardCount: false,
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
cardWidth: 60,
|
||||
cardSpacing: -20
|
||||
)
|
||||
@ -168,6 +186,8 @@ struct DealerHandView: View {
|
||||
]),
|
||||
showHoleCard: true,
|
||||
showCardCount: true,
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
cardWidth: 60,
|
||||
cardSpacing: -20
|
||||
)
|
||||
|
||||
@ -13,6 +13,7 @@ struct InsurancePopupView: View {
|
||||
let balance: Int
|
||||
let onTake: () -> Void
|
||||
let onDecline: () -> Void
|
||||
let onNeverAsk: () -> Void
|
||||
|
||||
@State private var showContent = false
|
||||
|
||||
@ -47,6 +48,7 @@ struct InsurancePopupView: View {
|
||||
.padding(.bottom, Design.Spacing.small)
|
||||
|
||||
// Buttons
|
||||
VStack(spacing: Design.Spacing.medium) {
|
||||
HStack(spacing: Design.Spacing.large) {
|
||||
// Decline button
|
||||
Button(action: onDecline) {
|
||||
@ -82,6 +84,16 @@ struct InsurancePopupView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't Ask Again button
|
||||
Button(action: onNeverAsk) {
|
||||
Text(String(localized: "Don't Ask"))
|
||||
.font(.system(size: Design.BaseFontSize.small, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
.padding(.vertical, Design.Spacing.small)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(Design.Spacing.xLarge)
|
||||
.background(
|
||||
@ -113,7 +125,8 @@ struct InsurancePopupView: View {
|
||||
betAmount: 500,
|
||||
balance: 4500,
|
||||
onTake: {},
|
||||
onDecline: {}
|
||||
onDecline: {},
|
||||
onNeverAsk: {}
|
||||
)
|
||||
}
|
||||
|
||||
@ -122,7 +135,8 @@ struct InsurancePopupView: View {
|
||||
betAmount: 500,
|
||||
balance: 200,
|
||||
onTake: {},
|
||||
onDecline: {}
|
||||
onDecline: {},
|
||||
onNeverAsk: {}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,8 @@ struct PlayerHandsView: View {
|
||||
let activeHandIndex: Int
|
||||
let isPlayerTurn: Bool
|
||||
let showCardCount: Bool
|
||||
let showAnimations: Bool
|
||||
let dealingSpeed: Double
|
||||
let cardWidth: CGFloat
|
||||
let cardSpacing: CGFloat
|
||||
|
||||
@ -43,6 +45,8 @@ struct PlayerHandsView: View {
|
||||
hand: hand,
|
||||
isActive: isActiveHand,
|
||||
showCardCount: showCardCount,
|
||||
showAnimations: showAnimations,
|
||||
dealingSpeed: dealingSpeed,
|
||||
// Hand numbers: rightmost (index 0) is Hand 1, played first
|
||||
handNumber: hands.count > 1 ? index + 1 : nil,
|
||||
cardWidth: cardWidth,
|
||||
@ -95,6 +99,8 @@ struct PlayerHandView: View {
|
||||
let hand: BlackjackHand
|
||||
let isActive: Bool
|
||||
let showCardCount: Bool
|
||||
let showAnimations: Bool
|
||||
let dealingSpeed: Double
|
||||
let handNumber: Int?
|
||||
let cardWidth: CGFloat
|
||||
let cardSpacing: CGFloat
|
||||
@ -105,6 +111,11 @@ struct PlayerHandView: View {
|
||||
/// Whether the hint toast should be visible.
|
||||
let showHintToast: Bool
|
||||
|
||||
/// Scaled animation duration based on dealing speed.
|
||||
private var animationDuration: Double {
|
||||
Design.Animation.springDuration * dealingSpeed
|
||||
}
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||
@ScaledMetric(relativeTo: .caption) private var handNumberSize: CGFloat = Design.Size.handNumberFontSize
|
||||
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.handIconSize
|
||||
@ -132,17 +143,24 @@ struct PlayerHandView: View {
|
||||
}
|
||||
.zIndex(Double(index))
|
||||
.transition(
|
||||
.asymmetric(
|
||||
insertion: .offset(x: Design.DealAnimation.offsetX, y: Design.DealAnimation.offsetY)
|
||||
showAnimations
|
||||
? .asymmetric(
|
||||
insertion: .offset(x: Design.DealAnimation.playerOffsetX, y: Design.DealAnimation.playerOffsetY)
|
||||
.combined(with: .opacity)
|
||||
.combined(with: .scale(scale: Design.Scale.slightShrink)),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
)
|
||||
: .identity
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.animation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce), value: hand.cards.count)
|
||||
.animation(
|
||||
showAnimations
|
||||
? .spring(duration: animationDuration, bounce: Design.Animation.springBounce)
|
||||
: .none,
|
||||
value: hand.cards.count
|
||||
)
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
@ -246,6 +264,8 @@ struct PlayerHandView: View {
|
||||
activeHandIndex: 0,
|
||||
isPlayerTurn: true,
|
||||
showCardCount: false,
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
cardWidth: 60,
|
||||
cardSpacing: -20,
|
||||
currentHint: nil,
|
||||
@ -265,6 +285,8 @@ struct PlayerHandView: View {
|
||||
activeHandIndex: 0,
|
||||
isPlayerTurn: true,
|
||||
showCardCount: false,
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
cardWidth: 60,
|
||||
cardSpacing: -20,
|
||||
currentHint: "Hit",
|
||||
@ -298,6 +320,8 @@ struct PlayerHandView: View {
|
||||
activeHandIndex: 1,
|
||||
isPlayerTurn: true,
|
||||
showCardCount: true,
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
cardWidth: 60,
|
||||
cardSpacing: -20,
|
||||
currentHint: "Stand",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user