// // BlackjackTableView.swift // Blackjack // // The main table layout showing dealer and player hands. // import SwiftUI import CasinoKit struct BlackjackTableView: View { @Bindable var state: GameState let onPlaceBet: () -> Void /// Whether to show Hi-Lo card count values on cards. var showCardCount: Bool { state.settings.showCardCount } // MARK: - Scaled Metrics @ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.BaseFontSize.medium @ScaledMetric(relativeTo: .title) private var valueFontSize: CGFloat = Design.BaseFontSize.xLarge @ScaledMetric(relativeTo: .caption) private var hintFontSize: CGFloat = Design.BaseFontSize.small // MARK: - Layout private let cardWidth: CGFloat = Design.Size.cardWidth private let cardSpacing: CGFloat = Design.Size.cardOverlap var body: some View { VStack(spacing: Design.Spacing.large) { // Dealer area DealerHandView( hand: state.dealerHand, showHoleCard: shouldShowDealerHoleCard, showCardCount: showCardCount, cardWidth: cardWidth, cardSpacing: cardSpacing ) Spacer() // Player hands area - only show when there are cards dealt if state.playerHands.first?.cards.isEmpty == false { PlayerHandsView( hands: state.playerHands, activeHandIndex: state.activeHandIndex, isPlayerTurn: isPlayerTurn, showCardCount: showCardCount, cardWidth: cardWidth, cardSpacing: cardSpacing ) .transition(.opacity) } // Betting zone (when betting) if state.currentPhase == .betting { BettingZoneView( betAmount: state.currentBet, minBet: state.settings.minBet, maxBet: state.settings.maxBet, onTap: onPlaceBet ) .transition(.scale.combined(with: .opacity)) // Betting hint based on count (only when card counting enabled) if showCardCount, let bettingHint = bettingHint { BettingHintView(hint: bettingHint, trueCount: state.engine.trueCount) .transition(.opacity) } } // Hint (when enabled and player turn) if state.settings.showHints && isPlayerTurn, let hint = currentHint { HintView(hint: hint) .transition(.opacity) } } .padding(.horizontal, Design.Spacing.large) .padding(.vertical, Design.Spacing.medium) .animation(.spring(duration: Design.Animation.springDuration), value: state.currentPhase) } // MARK: - Computed Properties private var shouldShowDealerHoleCard: Bool { switch state.currentPhase { case .dealerTurn, .roundComplete: return true default: return false } } private var isPlayerTurn: Bool { if case .playerTurn = state.currentPhase { return true } return false } private var currentHint: String? { guard let hand = state.activeHand, let upCard = state.dealerUpCard else { return nil } // Use count-adjusted hints when card counting is enabled if showCardCount { return state.engine.getCountAdjustedHint(playerHand: hand, dealerUpCard: upCard) } return state.engine.getHint(playerHand: hand, dealerUpCard: upCard) } /// Betting recommendation based on the true count. private var bettingHint: String? { let tc = Int(state.engine.trueCount.rounded()) // Betting spread recommendations based on true count switch tc { case ...(-2): return String(localized: "Bet minimum or sit out") case -1: return String(localized: "Bet minimum") case 0: return String(localized: "Bet minimum (neutral)") case 1: return String(localized: "Bet 2x minimum") case 2: return String(localized: "Bet 4x minimum") case 3: return String(localized: "Bet 6x minimum") case 4: return String(localized: "Bet 8x minimum") case 5...: return String(localized: "Bet maximum!") default: return nil } } } // MARK: - Previews #Preview { ZStack { Color.Table.felt.ignoresSafeArea() Text("Use GameTableView for full preview") .foregroundStyle(.white) } }