diff --git a/Blackjack/Blackjack/Engine/GameState.swift b/Blackjack/Blackjack/Engine/GameState.swift index 5632127..889e3d3 100644 --- a/Blackjack/Blackjack/Engine/GameState.swift +++ b/Blackjack/Blackjack/Engine/GameState.swift @@ -74,6 +74,9 @@ final class GameState { /// Index of the hand currently being played. private(set) var activeHandIndex: Int = 0 + /// Whether an action is currently being processed (prevents double-tap issues). + private(set) var isProcessingAction: Bool = false + /// The active player hand. var activeHand: BlackjackHand? { guard activeHandIndex < playerHands.count else { return nil } @@ -184,18 +187,21 @@ final class GameState { /// Whether the current hand can hit. var canHit: Bool { + guard !isProcessingAction else { return false } guard case .playerTurn = currentPhase else { return false } return activeHand?.canHit ?? false } /// Whether the current hand can stand. var canStand: Bool { + guard !isProcessingAction else { return false } guard case .playerTurn = currentPhase else { return false } return !(activeHand?.isBusted ?? true) } /// Whether the current hand can double. var canDouble: Bool { + guard !isProcessingAction else { return false } guard case .playerTurn = currentPhase else { return false } guard let hand = activeHand else { return false } return engine.canDoubleDown(hand: hand, balance: balance) @@ -203,6 +209,7 @@ final class GameState { /// Whether the current hand can split. var canSplit: Bool { + guard !isProcessingAction else { return false } guard case .playerTurn = currentPhase else { return false } guard let hand = activeHand else { return false } let splitCount = playerHands.count - 1 @@ -211,6 +218,7 @@ final class GameState { /// Whether the player can surrender. var canSurrender: Bool { + guard !isProcessingAction else { return false } guard case .playerTurn = currentPhase else { return false } guard let hand = activeHand else { return false } return engine.canSurrender(hand: hand) @@ -445,11 +453,18 @@ final class GameState { // Ensure enough cards for a full hand - reshuffle if needed if !engine.canDealNewHand { engine.reshuffle() - showReshuffleNotification = true + // Show notification with animation + withAnimation(.spring(duration: Design.Animation.springDuration)) { + showReshuffleNotification = true + } + + // Auto-dismiss after 2 seconds Task { try? await Task.sleep(for: .seconds(2)) - showReshuffleNotification = false + withAnimation(.spring(duration: Design.Animation.springDuration)) { + showReshuffleNotification = false + } } } @@ -582,6 +597,9 @@ final class GameState { /// Player hits (takes another card). func hit() async { guard canHit else { return } + isProcessingAction = true + defer { isProcessingAction = false } + guard let card = engine.dealCard() else { return } playerHands[activeHandIndex].cards.append(card) @@ -600,6 +618,8 @@ final class GameState { /// Player stands. func stand() async { guard canStand else { return } + isProcessingAction = true + defer { isProcessingAction = false } playerHands[activeHandIndex].isStanding = true await moveToNextHand() @@ -608,6 +628,8 @@ final class GameState { /// Player doubles down. func doubleDown() async { guard canDouble else { return } + isProcessingAction = true + defer { isProcessingAction = false } let additionalBet = playerHands[activeHandIndex].bet balance -= additionalBet @@ -632,6 +654,8 @@ final class GameState { /// Player splits the hand. func split() async { guard canSplit else { return } + isProcessingAction = true + defer { isProcessingAction = false } let originalHand = playerHands[activeHandIndex] let splitCard = originalHand.cards[1] @@ -676,6 +700,8 @@ final class GameState { /// Player surrenders. func surrender() async { guard canSurrender else { return } + isProcessingAction = true + defer { isProcessingAction = false } playerHands[activeHandIndex].result = .surrender await completeRound() @@ -964,12 +990,19 @@ final class GameState { // Check if shoe needs reshuffling if engine.needsReshuffle { engine.reshuffle() - showReshuffleNotification = true - // Auto-dismiss after a delay + // Show notification after delay so it appears after result banner is dismissed Task { try? await Task.sleep(for: .seconds(2)) - showReshuffleNotification = false + withAnimation(.spring(duration: Design.Animation.springDuration)) { + showReshuffleNotification = true + } + + // Auto-dismiss after showing for 2 seconds + try? await Task.sleep(for: .seconds(2)) + withAnimation(.spring(duration: Design.Animation.springDuration)) { + showReshuffleNotification = false + } } } } diff --git a/Blackjack/Blackjack/Views/Game/GameTableView.swift b/Blackjack/Blackjack/Views/Game/GameTableView.swift index b63175e..df5fa91 100644 --- a/Blackjack/Blackjack/Views/Game/GameTableView.swift +++ b/Blackjack/Blackjack/Views/Game/GameTableView.swift @@ -136,13 +136,6 @@ struct GameTableView: View { .frame(maxWidth: maxContentWidth) .debugBorder(showDebugBorders, color: .cyan, label: "TopBar") - // Reshuffle notification - if state.showReshuffleNotification { - ReshuffleNotificationView(showCardCount: settings.showCardCount) - .frame(maxWidth: maxContentWidth) - .transition(.move(edge: .top).combined(with: .opacity)) - } - // Table layout BlackjackTableView( state: state, @@ -178,6 +171,13 @@ struct GameTableView: View { Design.debugLog("🔄 Phase changed: \(oldPhase) → \(newPhase)") } + // Reshuffle notification overlay (centered, floating) + if state.showReshuffleNotification { + ReshuffleNotificationView(showCardCount: settings.showCardCount) + .transition(.scale.combined(with: .opacity)) + .zIndex(50) + } + // Insurance popup overlay (covers entire screen) if state.currentPhase == .insurance { Color.clear