// // BettingZoneView.swift // Blackjack // // The betting area where players place their bets. // import SwiftUI import CasinoKit /// Main betting zone view - adapts layout based on whether side bets are enabled. /// Follows Baccarat's betting pattern exactly. struct BettingZoneView: View { @Bindable var state: GameState let selectedChip: ChipDenomination @ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize @ScaledMetric(relativeTo: .caption) private var detailFontSize: CGFloat = Design.Size.handNumberFontSize @ScaledMetric(relativeTo: .body) private var chipSize: CGFloat = Design.Size.bettingChipSize @ScaledMetric(relativeTo: .body) private var zoneHeight: CGFloat = CasinoDesign.Size.bettingZoneHeight // MARK: - Computed Properties (matches Baccarat's pattern) /// Whether a bet can be added to main bet (matches Baccarat's canAddBet) private var canAddMainBet: Bool { state.canPlaceBet && state.balance >= selectedChip.rawValue && state.canAddToMainBet(amount: selectedChip.rawValue) } /// Whether a bet can be added to Perfect Pairs private var canAddPerfectPairs: Bool { state.canPlaceBet && state.balance >= selectedChip.rawValue && state.canAddToSideBet(type: .perfectPairs, amount: selectedChip.rawValue) } /// Whether a bet can be added to 21+3 private var canAddTwentyOnePlusThree: Bool { state.canPlaceBet && state.balance >= selectedChip.rawValue && state.canAddToSideBet(type: .twentyOnePlusThree, amount: selectedChip.rawValue) } private var isMainAtMax: Bool { state.currentBet >= state.settings.maxBet } private var isPPAtMax: Bool { state.perfectPairsBet >= state.settings.maxBet } private var is21PlusThreeAtMax: Bool { state.twentyOnePlusThreeBet >= state.settings.maxBet } var body: some View { if state.settings.sideBetsEnabled { // Horizontal layout: PP | Main Bet | 21+3 HStack(spacing: Design.Spacing.small) { // Perfect Pairs SideBetZoneView( betType: .perfectPairs, betAmount: state.perfectPairsBet, isEnabled: canAddPerfectPairs, isAtMax: isPPAtMax, onTap: { state.placeSideBet(type: .perfectPairs, amount: selectedChip.rawValue) } ) .frame(width: Design.Size.sideBetZoneWidth) // Main bet (center, takes remaining space) mainBetZone // 21+3 SideBetZoneView( betType: .twentyOnePlusThree, betAmount: state.twentyOnePlusThreeBet, isEnabled: canAddTwentyOnePlusThree, isAtMax: is21PlusThreeAtMax, onTap: { state.placeSideBet(type: .twentyOnePlusThree, amount: selectedChip.rawValue) } ) .frame(width: Design.Size.sideBetZoneWidth) } .frame(height: zoneHeight) } else { // Simple layout: just main bet mainBetZone .frame(height: zoneHeight) } } private var mainBetZone: some View { Button { if canAddMainBet { state.placeBet(amount: selectedChip.rawValue) } } label: { ZStack { // Background RoundedRectangle(cornerRadius: Design.CornerRadius.large) .fill(Color.BettingZone.main) .overlay( RoundedRectangle(cornerRadius: Design.CornerRadius.large) .strokeBorder(Color.BettingZone.mainBorder, lineWidth: Design.LineWidth.medium) ) // Content if state.currentBet > 0 { // Show chip with amount (scaled) ChipOnTableView(amount: state.currentBet, showMax: isMainAtMax, size: chipSize) } else { // Empty state VStack(spacing: Design.Spacing.small) { Text(String(localized: "TAP TO BET")) .font(.system(size: labelFontSize, weight: .bold)) .foregroundStyle(.white.opacity(Design.Opacity.medium)) HStack(spacing: Design.Spacing.medium) { Text(String(localized: "Min: $\(state.settings.minBet)")) .font(.system(size: detailFontSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.light)) Text(String(localized: "Max: $\(state.settings.maxBet.formatted())")) .font(.system(size: detailFontSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.light)) } } } } .frame(maxWidth: .infinity) } .buttonStyle(.plain) .accessibilityLabel(state.currentBet > 0 ? "$\(state.currentBet) bet" + (isMainAtMax ? ", maximum" : "") : "Place bet") .accessibilityHint("Double tap to add chips") } } // MARK: - Design Constants Extension extension Design.Size { /// Width of side bet zones static let sideBetZoneWidth: CGFloat = 70 } // MARK: - Previews #Preview("Empty - No Side Bets") { ZStack { Color.Table.felt.ignoresSafeArea() BettingZoneView( state: GameState(settings: GameSettings()), selectedChip: .hundred ) .padding() } } #Preview("With Side Bets") { ZStack { Color.Table.felt.ignoresSafeArea() BettingZoneView( state: { let settings = GameSettings() settings.sideBetsEnabled = true let state = GameState(settings: settings) state.placeBet(amount: 100) return state }(), selectedChip: .twentyFive ) .padding() } } #Preview("All Bets Placed") { ZStack { Color.Table.felt.ignoresSafeArea() BettingZoneView( state: { let settings = GameSettings() settings.sideBetsEnabled = true let state = GameState(settings: settings) state.placeBet(amount: 250) state.placeSideBet(type: .perfectPairs, amount: 25) state.placeSideBet(type: .twentyOnePlusThree, amount: 50) return state }(), selectedChip: .hundred ) .padding() } }