149 lines
4.8 KiB
Swift
149 lines
4.8 KiB
Swift
//
|
|
// 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)
|
|
}
|
|
}
|