// // 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 /// Fixed height for the hint area to prevent layout shifts private let hintAreaHeight: CGFloat = 44 // Use global debug flag from Design constants private var showDebugBorders: Bool { Design.showDebugBorders } var body: some View { VStack(spacing: Design.Spacing.small) { // Dealer area DealerHandView( hand: state.dealerHand, showHoleCard: shouldShowDealerHoleCard, showCardCount: showCardCount, cardWidth: cardWidth, cardSpacing: cardSpacing ) .debugBorder(showDebugBorders, color: .red, label: "Dealer") // Flexible space between dealer and player (minimum 60pt) Spacer(minLength: 60) .debugBorder(showDebugBorders, color: .yellow, label: "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) .debugBorder(showDebugBorders, color: .green, label: "Player") } // Betting zone (when betting) if state.currentPhase == .betting { Spacer() .debugBorder(showDebugBorders, color: .yellow, label: "Spacer2") BettingZoneView( betAmount: state.currentBet, minBet: state.settings.minBet, maxBet: state.settings.maxBet, onTap: onPlaceBet ) .transition(.scale.combined(with: .opacity)) .debugBorder(showDebugBorders, color: .blue, label: "BetZone") // Betting hint based on count (only when card counting enabled) if showCardCount, let bettingHint = bettingHint { BettingHintView(hint: bettingHint, trueCount: state.engine.trueCount) .transition(.opacity) .debugBorder(showDebugBorders, color: .purple, label: "BetHint") } } else { // Fixed-height hint area to prevent layout shifts during player turn ZStack { if state.settings.showHints && isPlayerTurn, let hint = currentHint { HintView(hint: hint) .transition(.opacity) } } .frame(height: hintAreaHeight) .debugBorder(showDebugBorders, color: .orange, label: "HintArea") } } .padding(.horizontal, Design.Spacing.large) .padding(.vertical, Design.Spacing.medium) .debugBorder(showDebugBorders, color: .white, label: "TableView") .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) } }