Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
8d222317a0
commit
100eb83422
@ -48,9 +48,12 @@ enum Design {
|
|||||||
static let mainBetRowHeight: CGFloat = 50
|
static let mainBetRowHeight: CGFloat = 50
|
||||||
static let bonusZoneWidth: CGFloat = 80
|
static let bonusZoneWidth: CGFloat = 80
|
||||||
|
|
||||||
// Labels
|
// Labels (matches Blackjack for consistency)
|
||||||
static let labelFontSize: CGFloat = 14
|
static let labelFontSize: CGFloat = 14
|
||||||
static let labelRowHeight: CGFloat = 30
|
static let labelRowHeight: CGFloat = 30
|
||||||
|
static let handLabelFontSize: CGFloat = 14
|
||||||
|
static let handNumberFontSize: CGFloat = 12
|
||||||
|
static let handIconSize: CGFloat = 18
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
static let bettingButtonsContainerHeight: CGFloat = 70
|
static let bettingButtonsContainerHeight: CGFloat = 70
|
||||||
|
|||||||
@ -62,13 +62,13 @@ struct CardsDisplayArea: View {
|
|||||||
|
|
||||||
private var showDebugBorders: Bool { Design.showDebugBorders }
|
private var showDebugBorders: Bool { Design.showDebugBorders }
|
||||||
|
|
||||||
private var labelFontSize: CGFloat {
|
// MARK: - Scaled Metrics (Dynamic Type)
|
||||||
isLargeScreen ? 18 : Design.Size.labelFontSize
|
// These match Blackjack's HandLabelView for consistency
|
||||||
}
|
|
||||||
|
|
||||||
private var labelRowMinHeight: CGFloat {
|
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||||
isLargeScreen ? 40 : Design.Size.labelRowHeight
|
@ScaledMetric(relativeTo: .headline) private var badgeHeight: CGFloat = CasinoDesign.Size.valueBadge
|
||||||
}
|
@ScaledMetric(relativeTo: .caption) private var betFontSize: CGFloat = Design.Size.handNumberFontSize
|
||||||
|
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.handIconSize
|
||||||
|
|
||||||
/// Whether Player hand should be on bottom in vertical mode.
|
/// Whether Player hand should be on bottom in vertical mode.
|
||||||
private var playerOnBottom: Bool {
|
private var playerOnBottom: Bool {
|
||||||
@ -225,15 +225,16 @@ struct CardsDisplayArea: View {
|
|||||||
// MARK: - Private Views
|
// MARK: - Private Views
|
||||||
|
|
||||||
/// Bet amount display shown below the bottom hand during dealing.
|
/// Bet amount display shown below the bottom hand during dealing.
|
||||||
|
/// Matches Blackjack's PlayerHandView bet display styling.
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var betAmountDisplay: some View {
|
private var betAmountDisplay: some View {
|
||||||
if totalBetAmount > 0 {
|
if totalBetAmount > 0 {
|
||||||
HStack(spacing: Design.Spacing.xSmall) {
|
HStack(spacing: Design.Spacing.xSmall) {
|
||||||
Image(systemName: "dollarsign.circle.fill")
|
Image(systemName: "dollarsign.circle.fill")
|
||||||
.font(.system(size: Design.BaseFontSize.xLarge))
|
.font(.system(size: iconSize))
|
||||||
.foregroundStyle(.yellow)
|
.foregroundStyle(.yellow)
|
||||||
Text("\(totalBetAmount)")
|
Text("\(totalBetAmount)")
|
||||||
.font(.system(size: Design.BaseFontSize.medium, weight: .bold, design: .rounded))
|
.font(.system(size: betFontSize, weight: .bold, design: .rounded))
|
||||||
.foregroundStyle(.yellow)
|
.foregroundStyle(.yellow)
|
||||||
}
|
}
|
||||||
.padding(.top, Design.Spacing.medium)
|
.padding(.top, Design.Spacing.medium)
|
||||||
@ -265,7 +266,8 @@ struct CardsDisplayArea: View {
|
|||||||
.animation(nil, value: visibleValue) // No animation when value changes
|
.animation(nil, value: visibleValue) // No animation when value changes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(minHeight: labelRowMinHeight)
|
.fixedSize() // Prevent the label from being constrained/truncated
|
||||||
|
.frame(minHeight: badgeHeight)
|
||||||
|
|
||||||
CompactHandView(
|
CompactHandView(
|
||||||
cards: playerCards,
|
cards: playerCards,
|
||||||
@ -316,7 +318,8 @@ struct CardsDisplayArea: View {
|
|||||||
.animation(nil, value: visibleValue) // No animation when value changes
|
.animation(nil, value: visibleValue) // No animation when value changes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(minHeight: labelRowMinHeight)
|
.fixedSize() // Prevent the label from being constrained/truncated
|
||||||
|
.frame(minHeight: badgeHeight)
|
||||||
|
|
||||||
CompactHandView(
|
CompactHandView(
|
||||||
cards: bankerCards,
|
cards: bankerCards,
|
||||||
|
|||||||
@ -86,6 +86,18 @@ struct CompactHandView: View {
|
|||||||
cards.isEmpty ? placeholderSpacing : cardSpacing
|
cards.isEmpty ? placeholderSpacing : cardSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The actual width of the cards content (for winner border sizing)
|
||||||
|
private var cardsContentWidth: CGFloat {
|
||||||
|
if cards.isEmpty {
|
||||||
|
// 2 placeholders with spacing
|
||||||
|
return (2 * placeholderWidth) + placeholderSpacing
|
||||||
|
} else {
|
||||||
|
// N cards with (N-1) spacings
|
||||||
|
let cardCount = CGFloat(cards.count)
|
||||||
|
return (cardCount * cardWidth) + ((cardCount - 1) * cardSpacing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -94,15 +106,13 @@ struct CompactHandView: View {
|
|||||||
|
|
||||||
if isLargeScreen || !isDealing {
|
if isLargeScreen || !isDealing {
|
||||||
// iPad or betting phase: Simple centered layout - no scrolling needed
|
// iPad or betting phase: Simple centered layout - no scrolling needed
|
||||||
cardsContent
|
cardsContentWithBorder
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
|
||||||
.frame(width: availableWidth, alignment: .center)
|
.frame(width: availableWidth, alignment: .center)
|
||||||
} else {
|
} else {
|
||||||
// iPhone dealing phase: Use ScrollView for 3rd card
|
// iPhone dealing phase: Use ScrollView for 3rd card
|
||||||
ScrollViewReader { proxy in
|
ScrollViewReader { proxy in
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
cardsContent
|
cardsContentWithBorder
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
|
||||||
.frame(minWidth: availableWidth, alignment: .center)
|
.frame(minWidth: availableWidth, alignment: .center)
|
||||||
.id("cards_container")
|
.id("cards_container")
|
||||||
}
|
}
|
||||||
@ -131,17 +141,23 @@ struct CompactHandView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: cardHeight)
|
.frame(height: cardHeight + (isWinner ? Design.Spacing.small * 2 : 0))
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.vertical, isWinner ? Design.Spacing.small : 0)
|
|
||||||
.background(winnerBorder)
|
|
||||||
.overlay(alignment: .bottom) {
|
|
||||||
winBadge
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Views
|
// MARK: - Private Views
|
||||||
|
|
||||||
|
/// Cards content wrapped with the winner border sized to fit the actual cards
|
||||||
|
private var cardsContentWithBorder: some View {
|
||||||
|
cardsContent
|
||||||
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
|
.padding(.vertical, isWinner ? Design.Spacing.small : 0)
|
||||||
|
.background(winnerBorder)
|
||||||
|
.overlay(alignment: .bottom) {
|
||||||
|
winBadge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var cardsContent: some View {
|
private var cardsContent: some View {
|
||||||
HStack(spacing: effectiveSpacing) {
|
HStack(spacing: effectiveSpacing) {
|
||||||
if cards.isEmpty {
|
if cards.isEmpty {
|
||||||
|
|||||||
@ -2,40 +2,25 @@
|
|||||||
// HandValueBadge.swift
|
// HandValueBadge.swift
|
||||||
// Baccarat
|
// Baccarat
|
||||||
//
|
//
|
||||||
// A circular badge displaying the hand value.
|
// A capsule badge displaying the hand value.
|
||||||
|
// Matches CasinoKit's ValueBadge styling for consistency with Blackjack.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CasinoKit
|
import CasinoKit
|
||||||
|
|
||||||
/// A small circular badge showing the hand value.
|
/// A capsule badge showing the hand value.
|
||||||
|
/// Uses the same styling as CasinoKit's ValueBadge for consistency across games.
|
||||||
struct HandValueBadge: View {
|
struct HandValueBadge: View {
|
||||||
let value: Int
|
let value: Int
|
||||||
let color: Color
|
let color: Color
|
||||||
|
|
||||||
// MARK: - Environment
|
// MARK: - Scaled Metrics (Dynamic Type)
|
||||||
|
// These match CasinoKit's ValueBadge exactly
|
||||||
|
|
||||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
@ScaledMetric(relativeTo: .headline) private var valueFontSize: CGFloat = 15
|
||||||
|
@ScaledMetric(relativeTo: .headline) private var badgeHeight: CGFloat = CasinoDesign.Size.valueBadge
|
||||||
// MARK: - Computed Properties
|
@ScaledMetric(relativeTo: .headline) private var badgePadding: CGFloat = 8
|
||||||
|
|
||||||
/// Whether we're on a large screen (iPad)
|
|
||||||
private var isLargeScreen: Bool {
|
|
||||||
horizontalSizeClass == .regular
|
|
||||||
}
|
|
||||||
|
|
||||||
@ScaledMetric(relativeTo: .headline) private var baseValueFontSize: CGFloat = 15
|
|
||||||
@ScaledMetric(relativeTo: .headline) private var baseBadgeSize: CGFloat = 26
|
|
||||||
|
|
||||||
/// Font size - larger on iPad
|
|
||||||
private var valueFontSize: CGFloat {
|
|
||||||
isLargeScreen ? baseValueFontSize * 1.5 : baseValueFontSize
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Badge size - larger on iPad
|
|
||||||
private var badgeSize: CGFloat {
|
|
||||||
isLargeScreen ? baseBadgeSize * 1.5 : baseBadgeSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
@ -43,9 +28,12 @@ struct HandValueBadge: View {
|
|||||||
Text("\(value)")
|
Text("\(value)")
|
||||||
.font(.system(size: valueFontSize, weight: .black, design: .rounded))
|
.font(.system(size: valueFontSize, weight: .black, design: .rounded))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.frame(width: badgeSize, height: badgeSize)
|
.lineLimit(1)
|
||||||
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
|
.padding(.horizontal, badgePadding)
|
||||||
|
.frame(minWidth: badgeHeight, minHeight: badgeHeight)
|
||||||
.background(
|
.background(
|
||||||
Circle()
|
Capsule()
|
||||||
.fill(color)
|
.fill(color)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,9 +29,6 @@ struct InteractiveCardView: View {
|
|||||||
isWaiting && !isFaceUp && isBottomHand && revealStyle != .auto && !isInteracting
|
isWaiting && !isFaceUp && isBottomHand && revealStyle != .auto && !isInteracting
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Animation state for the pulsing glow
|
|
||||||
@State private var glowPulse = false
|
|
||||||
|
|
||||||
/// Tracks if we just completed a squeeze reveal (to skip flip animation)
|
/// Tracks if we just completed a squeeze reveal (to skip flip animation)
|
||||||
@State private var squeezeJustCompleted = false
|
@State private var squeezeJustCompleted = false
|
||||||
|
|
||||||
@ -44,21 +41,18 @@ struct InteractiveCardView: View {
|
|||||||
// Glow layer - completely separate from the card, sits underneath
|
// Glow layer - completely separate from the card, sits underneath
|
||||||
GlowBackgroundView(
|
GlowBackgroundView(
|
||||||
isActive: showGlow,
|
isActive: showGlow,
|
||||||
glowPulse: glowPulse,
|
shouldPulse: showGlow,
|
||||||
cardWidth: cardWidth
|
cardWidth: cardWidth
|
||||||
)
|
)
|
||||||
|
// Force recreation on size change to avoid stale dimensions during rotation
|
||||||
|
.id("glow-\(Int(cardWidth))")
|
||||||
|
|
||||||
// Card layer - completely independent, no glow modifiers
|
// Card layer - completely independent, no glow modifiers
|
||||||
cardContent
|
cardContent
|
||||||
}
|
}
|
||||||
.onAppear {
|
// Explicit frame ensures card and glow stay synced during size changes
|
||||||
if showGlow {
|
.frame(width: cardWidth, height: cardHeight)
|
||||||
glowPulse = true
|
.animation(.easeOut(duration: 0.3), value: cardWidth)
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: showGlow) { _, newValue in
|
|
||||||
glowPulse = newValue
|
|
||||||
}
|
|
||||||
.onChange(of: isWaiting) { _, newValue in
|
.onChange(of: isWaiting) { _, newValue in
|
||||||
// Reset interaction state when this card is no longer waiting
|
// Reset interaction state when this card is no longer waiting
|
||||||
if !newValue {
|
if !newValue {
|
||||||
@ -154,9 +148,12 @@ private struct VerticalFlipCardView: View {
|
|||||||
/// Completely separate from the card view to avoid any state changes affecting the card.
|
/// Completely separate from the card view to avoid any state changes affecting the card.
|
||||||
private struct GlowBackgroundView: View {
|
private struct GlowBackgroundView: View {
|
||||||
let isActive: Bool
|
let isActive: Bool
|
||||||
let glowPulse: Bool
|
let shouldPulse: Bool
|
||||||
let cardWidth: CGFloat
|
let cardWidth: CGFloat
|
||||||
|
|
||||||
|
/// Internal animation state - starts false so animation triggers on appear
|
||||||
|
@State private var animatingPulse = false
|
||||||
|
|
||||||
/// Corner radius matching CasinoKit's CardView
|
/// Corner radius matching CasinoKit's CardView
|
||||||
private var cornerRadius: CGFloat {
|
private var cornerRadius: CGFloat {
|
||||||
cardWidth * 0.08
|
cardWidth * 0.08
|
||||||
@ -170,15 +167,15 @@ private struct GlowBackgroundView: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
// Outer glow strokes (largest to smallest for layered glow)
|
// Outer glow strokes (largest to smallest for layered glow)
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.stroke(Color.orange.opacity(glowPulse ? 0.6 : 0.2), lineWidth: glowPulse ? 20 : 10)
|
.stroke(Color.orange.opacity(animatingPulse ? 0.6 : 0.2), lineWidth: animatingPulse ? 20 : 10)
|
||||||
.blur(radius: 10)
|
.blur(radius: 10)
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.stroke(Color.yellow.opacity(glowPulse ? 0.8 : 0.3), lineWidth: glowPulse ? 12 : 6)
|
.stroke(Color.yellow.opacity(animatingPulse ? 0.8 : 0.3), lineWidth: animatingPulse ? 12 : 6)
|
||||||
.blur(radius: 6)
|
.blur(radius: 6)
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.stroke(Color.yellow.opacity(glowPulse ? 1.0 : 0.5), lineWidth: glowPulse ? 6 : 3)
|
.stroke(Color.yellow.opacity(animatingPulse ? 1.0 : 0.5), lineWidth: animatingPulse ? 6 : 3)
|
||||||
.blur(radius: 3)
|
.blur(radius: 3)
|
||||||
|
|
||||||
// Bright animated border (sharp, on top)
|
// Bright animated border (sharp, on top)
|
||||||
@ -189,16 +186,25 @@ private struct GlowBackgroundView: View {
|
|||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
),
|
),
|
||||||
lineWidth: glowPulse ? 4 : 3
|
lineWidth: animatingPulse ? 4 : 3
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.frame(width: cardWidth, height: cardHeight)
|
.frame(width: cardWidth, height: cardHeight)
|
||||||
.scaleEffect(isActive && glowPulse ? 1.02 : 1.0)
|
.scaleEffect(isActive && animatingPulse ? 1.02 : 1.0)
|
||||||
.opacity(isActive ? 1.0 : 0.0)
|
.opacity(isActive ? 1.0 : 0.0)
|
||||||
.animation(.easeOut(duration: 0.15), value: isActive)
|
.animation(.easeOut(duration: 0.15), value: isActive)
|
||||||
.animation(
|
.animation(
|
||||||
isActive ? .easeInOut(duration: 0.7).repeatForever(autoreverses: true) : .easeOut(duration: 0.15),
|
isActive ? .easeInOut(duration: 0.7).repeatForever(autoreverses: true) : .easeOut(duration: 0.15),
|
||||||
value: glowPulse
|
value: animatingPulse
|
||||||
)
|
)
|
||||||
|
.onAppear {
|
||||||
|
// Start animation after view appears - this ensures the repeating animation triggers
|
||||||
|
if shouldPulse {
|
||||||
|
animatingPulse = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: shouldPulse) { _, newValue in
|
||||||
|
animatingPulse = newValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,8 @@ struct PageCurlView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
PageCurlRepresentable(
|
PageCurlRepresentable(
|
||||||
currentIndex: $currentIndex,
|
currentIndex: $currentIndex,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
onReveal: onReveal,
|
onReveal: onReveal,
|
||||||
onInteractionStarted: onInteractionStarted,
|
onInteractionStarted: onInteractionStarted,
|
||||||
pages: [
|
pages: [
|
||||||
@ -48,12 +50,15 @@ struct PageCurlView: View {
|
|||||||
)
|
)
|
||||||
])
|
])
|
||||||
.frame(width: width, height: height)
|
.frame(width: width, height: height)
|
||||||
.id("page-curl-\(card.id)")
|
// Use both card ID and size to force recreation on rotation
|
||||||
|
.id("page-curl-\(card.id)-\(Int(width))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct PageCurlRepresentable: UIViewControllerRepresentable {
|
private struct PageCurlRepresentable: UIViewControllerRepresentable {
|
||||||
@Binding var currentIndex: Int
|
@Binding var currentIndex: Int
|
||||||
|
let width: CGFloat
|
||||||
|
let height: CGFloat
|
||||||
let onReveal: () -> Void
|
let onReveal: () -> Void
|
||||||
let onInteractionStarted: (() -> Void)?
|
let onInteractionStarted: (() -> Void)?
|
||||||
let pages: [AnyView]
|
let pages: [AnyView]
|
||||||
@ -84,7 +89,10 @@ private struct PageCurlRepresentable: UIViewControllerRepresentable {
|
|||||||
return pageVC
|
return pageVC
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ pageVC: UIPageViewController, context: Context) { }
|
func updateUIViewController(_ pageVC: UIPageViewController, context: Context) {
|
||||||
|
// Update the page view controller's preferred content size when dimensions change
|
||||||
|
pageVC.preferredContentSize = CGSize(width: width, height: height)
|
||||||
|
}
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
func makeCoordinator() -> Coordinator {
|
||||||
Coordinator(self)
|
Coordinator(self)
|
||||||
|
|||||||
@ -435,7 +435,7 @@ final class GameState: CasinoGameState {
|
|||||||
|
|
||||||
currentBet += amount
|
currentBet += amount
|
||||||
balance -= amount
|
balance -= amount
|
||||||
sound.play(.chipPlace)
|
sound.playChipPlace()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Places a side bet (Perfect Pairs or 21+3).
|
/// Places a side bet (Perfect Pairs or 21+3).
|
||||||
@ -452,7 +452,7 @@ final class GameState: CasinoGameState {
|
|||||||
twentyOnePlusThreeBet += amount
|
twentyOnePlusThreeBet += amount
|
||||||
}
|
}
|
||||||
balance -= amount
|
balance -= amount
|
||||||
sound.play(.chipPlace)
|
sound.playChipPlace()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears all bets (main and side bets).
|
/// Clears all bets (main and side bets).
|
||||||
@ -461,7 +461,7 @@ final class GameState: CasinoGameState {
|
|||||||
currentBet = 0
|
currentBet = 0
|
||||||
perfectPairsBet = 0
|
perfectPairsBet = 0
|
||||||
twentyOnePlusThreeBet = 0
|
twentyOnePlusThreeBet = 0
|
||||||
sound.play(.chipPlace)
|
sound.playClearBets()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total amount bet (main + side bets).
|
/// Total amount bet (main + side bets).
|
||||||
@ -538,7 +538,7 @@ final class GameState: CasinoGameState {
|
|||||||
} else {
|
} else {
|
||||||
dealerHand.cards.append(card)
|
dealerHand.cards.append(card)
|
||||||
}
|
}
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
|
|
||||||
// Wait for card to appear on screen
|
// Wait for card to appear on screen
|
||||||
if cardAppearDelay > 0 {
|
if cardAppearDelay > 0 {
|
||||||
@ -593,7 +593,7 @@ final class GameState: CasinoGameState {
|
|||||||
|
|
||||||
if playerBJ || dealerBJ {
|
if playerBJ || dealerBJ {
|
||||||
// Reveal dealer card
|
// Reveal dealer card
|
||||||
sound.play(.cardFlip)
|
sound.playCardFlip()
|
||||||
|
|
||||||
if playerBJ && dealerBJ {
|
if playerBJ && dealerBJ {
|
||||||
// Push
|
// Push
|
||||||
@ -625,11 +625,11 @@ final class GameState: CasinoGameState {
|
|||||||
|
|
||||||
insuranceBet = insuranceAmount
|
insuranceBet = insuranceAmount
|
||||||
balance -= insuranceAmount
|
balance -= insuranceAmount
|
||||||
sound.play(.chipPlace)
|
sound.playChipPlace()
|
||||||
|
|
||||||
// Check dealer blackjack
|
// Check dealer blackjack
|
||||||
if dealerHand.isBlackjack {
|
if dealerHand.isBlackjack {
|
||||||
sound.play(.cardFlip)
|
sound.playCardFlip()
|
||||||
// Insurance wins
|
// Insurance wins
|
||||||
let payout = insuranceBet * 3
|
let payout = insuranceBet * 3
|
||||||
balance += payout
|
balance += payout
|
||||||
@ -672,7 +672,7 @@ final class GameState: CasinoGameState {
|
|||||||
guard let card = engine.dealCard() else { return }
|
guard let card = engine.dealCard() else { return }
|
||||||
|
|
||||||
playerHands[activeHandIndex].cards.append(card)
|
playerHands[activeHandIndex].cards.append(card)
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
|
|
||||||
// Animation timing
|
// Animation timing
|
||||||
let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
|
let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
|
||||||
@ -733,12 +733,12 @@ final class GameState: CasinoGameState {
|
|||||||
let additionalBet = playerHands[activeHandIndex].bet
|
let additionalBet = playerHands[activeHandIndex].bet
|
||||||
balance -= additionalBet
|
balance -= additionalBet
|
||||||
playerHands[activeHandIndex].isDoubledDown = true
|
playerHands[activeHandIndex].isDoubledDown = true
|
||||||
sound.play(.chipPlace)
|
sound.playChipPlace()
|
||||||
|
|
||||||
// Deal one card and stand
|
// Deal one card and stand
|
||||||
if let card = engine.dealCard() {
|
if let card = engine.dealCard() {
|
||||||
playerHands[activeHandIndex].cards.append(card)
|
playerHands[activeHandIndex].cards.append(card)
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
|
|
||||||
// Animation timing
|
// Animation timing
|
||||||
let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
|
let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
|
||||||
@ -792,7 +792,7 @@ final class GameState: CasinoGameState {
|
|||||||
|
|
||||||
// Deduct bet for second hand
|
// Deduct bet for second hand
|
||||||
balance -= originalHand.bet
|
balance -= originalHand.bet
|
||||||
sound.play(.chipPlace)
|
sound.playChipPlace()
|
||||||
|
|
||||||
// Replace original with split hands first (so visible counts are tracked correctly)
|
// Replace original with split hands first (so visible counts are tracked correctly)
|
||||||
playerHands.remove(at: activeHandIndex)
|
playerHands.remove(at: activeHandIndex)
|
||||||
@ -818,7 +818,7 @@ final class GameState: CasinoGameState {
|
|||||||
// Deal one card to each hand (with full animation timing for each)
|
// Deal one card to each hand (with full animation timing for each)
|
||||||
if let card1 = engine.dealCard() {
|
if let card1 = engine.dealCard() {
|
||||||
playerHands[activeHandIndex].cards.append(card1)
|
playerHands[activeHandIndex].cards.append(card1)
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
|
|
||||||
if cardAppearDelay > 0 {
|
if cardAppearDelay > 0 {
|
||||||
try? await Task.sleep(for: .seconds(cardAppearDelay))
|
try? await Task.sleep(for: .seconds(cardAppearDelay))
|
||||||
@ -831,7 +831,7 @@ final class GameState: CasinoGameState {
|
|||||||
|
|
||||||
if let card2 = engine.dealCard() {
|
if let card2 = engine.dealCard() {
|
||||||
playerHands[activeHandIndex + 1].cards.append(card2)
|
playerHands[activeHandIndex + 1].cards.append(card2)
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
|
|
||||||
if cardAppearDelay > 0 {
|
if cardAppearDelay > 0 {
|
||||||
try? await Task.sleep(for: .seconds(cardAppearDelay))
|
try? await Task.sleep(for: .seconds(cardAppearDelay))
|
||||||
@ -911,7 +911,7 @@ final class GameState: CasinoGameState {
|
|||||||
dealerHand.cards.append(card)
|
dealerHand.cards.append(card)
|
||||||
// Mark card as visible immediately - face is visible as soon as card appears
|
// Mark card as visible immediately - face is visible as soon as card appears
|
||||||
dealerVisibleCardCount += 1
|
dealerVisibleCardCount += 1
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
|
|
||||||
// Wait for animation to complete before checking blackjack
|
// Wait for animation to complete before checking blackjack
|
||||||
if delay > 0 {
|
if delay > 0 {
|
||||||
@ -938,7 +938,7 @@ final class GameState: CasinoGameState {
|
|||||||
} else {
|
} else {
|
||||||
// American style: reveal hole card (card is already in hand)
|
// American style: reveal hole card (card is already in hand)
|
||||||
// The flip animation shows the card face at the midpoint (90° rotation)
|
// The flip animation shows the card face at the midpoint (90° rotation)
|
||||||
sound.play(.cardFlip)
|
sound.playCardFlip()
|
||||||
|
|
||||||
// Wait until card face becomes visible (halfway through flip)
|
// Wait until card face becomes visible (halfway through flip)
|
||||||
if flipMidpointDelay > 0 {
|
if flipMidpointDelay > 0 {
|
||||||
@ -962,7 +962,7 @@ final class GameState: CasinoGameState {
|
|||||||
dealerHand.cards.append(card)
|
dealerHand.cards.append(card)
|
||||||
// Mark card as visible immediately - face is visible as soon as card appears
|
// Mark card as visible immediately - face is visible as soon as card appears
|
||||||
dealerVisibleCardCount += 1
|
dealerVisibleCardCount += 1
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
|
|
||||||
// Wait for animation to complete before drawing next card
|
// Wait for animation to complete before drawing next card
|
||||||
if delay > 0 {
|
if delay > 0 {
|
||||||
@ -1006,7 +1006,7 @@ final class GameState: CasinoGameState {
|
|||||||
let ppWon = perfectPairsResult?.isWin ?? false
|
let ppWon = perfectPairsResult?.isWin ?? false
|
||||||
let topWon = twentyOnePlusThreeResult?.isWin ?? false
|
let topWon = twentyOnePlusThreeResult?.isWin ?? false
|
||||||
if ppWon || topWon {
|
if ppWon || topWon {
|
||||||
sound.play(.win)
|
sound.playWin()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-hide toasts after delay
|
// Auto-hide toasts after delay
|
||||||
@ -1186,13 +1186,13 @@ final class GameState: CasinoGameState {
|
|||||||
// Save game data to iCloud
|
// Save game data to iCloud
|
||||||
saveGameData()
|
saveGameData()
|
||||||
|
|
||||||
// Play appropriate sound
|
// Play appropriate sound with haptic feedback
|
||||||
if roundWinnings > 0 {
|
if roundWinnings > 0 {
|
||||||
sound.play(.win)
|
sound.playWin()
|
||||||
} else if roundWinnings < 0 {
|
} else if roundWinnings < 0 {
|
||||||
sound.play(.lose)
|
sound.playLose()
|
||||||
} else {
|
} else {
|
||||||
sound.play(.push)
|
sound.playPush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset bet for next round
|
// Reset bet for next round
|
||||||
@ -1249,7 +1249,7 @@ final class GameState: CasinoGameState {
|
|||||||
lastRoundResult = nil
|
lastRoundResult = nil
|
||||||
currentPhase = .betting
|
currentPhase = .betting
|
||||||
|
|
||||||
sound.play(.newRound)
|
sound.playNewRound()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SessionManagedGame Implementation
|
// MARK: - SessionManagedGame Implementation
|
||||||
@ -1366,28 +1366,28 @@ final class GameState: CasinoGameState {
|
|||||||
|
|
||||||
// Deal player card 1
|
// Deal player card 1
|
||||||
playerHands[0].cards.append(card1)
|
playerHands[0].cards.append(card1)
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
if cardAppearDelay > 0 { try? await Task.sleep(for: .seconds(cardAppearDelay)) }
|
if cardAppearDelay > 0 { try? await Task.sleep(for: .seconds(cardAppearDelay)) }
|
||||||
playerHandsVisibleCardCount[0] += 1
|
playerHandsVisibleCardCount[0] += 1
|
||||||
if remainingDelay > 0 { try? await Task.sleep(for: .seconds(remainingDelay)) }
|
if remainingDelay > 0 { try? await Task.sleep(for: .seconds(remainingDelay)) }
|
||||||
|
|
||||||
// Deal dealer card 1 (face up)
|
// Deal dealer card 1 (face up)
|
||||||
dealerHand.cards.append(dealerCard1)
|
dealerHand.cards.append(dealerCard1)
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
if cardAppearDelay > 0 { try? await Task.sleep(for: .seconds(cardAppearDelay)) }
|
if cardAppearDelay > 0 { try? await Task.sleep(for: .seconds(cardAppearDelay)) }
|
||||||
dealerVisibleCardCount += 1
|
dealerVisibleCardCount += 1
|
||||||
if remainingDelay > 0 { try? await Task.sleep(for: .seconds(remainingDelay)) }
|
if remainingDelay > 0 { try? await Task.sleep(for: .seconds(remainingDelay)) }
|
||||||
|
|
||||||
// Deal player card 2 (matching rank for split)
|
// Deal player card 2 (matching rank for split)
|
||||||
playerHands[0].cards.append(card2)
|
playerHands[0].cards.append(card2)
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
if cardAppearDelay > 0 { try? await Task.sleep(for: .seconds(cardAppearDelay)) }
|
if cardAppearDelay > 0 { try? await Task.sleep(for: .seconds(cardAppearDelay)) }
|
||||||
playerHandsVisibleCardCount[0] += 1
|
playerHandsVisibleCardCount[0] += 1
|
||||||
if remainingDelay > 0 { try? await Task.sleep(for: .seconds(remainingDelay)) }
|
if remainingDelay > 0 { try? await Task.sleep(for: .seconds(remainingDelay)) }
|
||||||
|
|
||||||
// Deal dealer hole card (face down)
|
// Deal dealer hole card (face down)
|
||||||
dealerHand.cards.append(dealerCard2)
|
dealerHand.cards.append(dealerCard2)
|
||||||
sound.play(.cardDeal)
|
sound.playCardDeal()
|
||||||
if cardAppearDelay > 0 { try? await Task.sleep(for: .seconds(cardAppearDelay)) }
|
if cardAppearDelay > 0 { try? await Task.sleep(for: .seconds(cardAppearDelay)) }
|
||||||
dealerVisibleCardCount += 1
|
dealerVisibleCardCount += 1
|
||||||
if remainingDelay > 0 { try? await Task.sleep(for: .seconds(remainingDelay)) }
|
if remainingDelay > 0 { try? await Task.sleep(for: .seconds(remainingDelay)) }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user