Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-25 08:32:07 -06:00
parent 8d222317a0
commit 100eb83422
7 changed files with 118 additions and 94 deletions

View File

@ -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

View File

@ -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,

View File

@ -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,8 +141,16 @@ struct CompactHandView: View {
} }
} }
} }
.frame(height: cardHeight) .frame(height: cardHeight + (isWinner ? Design.Spacing.small * 2 : 0))
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
}
// 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) .padding(.vertical, isWinner ? Design.Spacing.small : 0)
.background(winnerBorder) .background(winnerBorder)
.overlay(alignment: .bottom) { .overlay(alignment: .bottom) {
@ -140,8 +158,6 @@ struct CompactHandView: View {
} }
} }
// MARK: - Private Views
private var cardsContent: some View { private var cardsContent: some View {
HStack(spacing: effectiveSpacing) { HStack(spacing: effectiveSpacing) {
if cards.isEmpty { if cards.isEmpty {

View File

@ -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)
) )
} }

View File

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

View File

@ -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)

View File

@ -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)) }