184 lines
6.0 KiB
Swift
184 lines
6.0 KiB
Swift
//
|
|
// GameTableView.swift
|
|
// Blackjack
|
|
//
|
|
// Main game container view.
|
|
//
|
|
|
|
import SwiftUI
|
|
import CasinoKit
|
|
|
|
struct GameTableView: View {
|
|
@State private var settings = GameSettings()
|
|
@State private var gameState: GameState?
|
|
@State private var selectedChip: ChipDenomination = .twentyFive
|
|
|
|
// MARK: - Sheet State
|
|
|
|
@State private var showSettings = false
|
|
@State private var showRules = false
|
|
@State private var showStats = false
|
|
|
|
// MARK: - Environment
|
|
|
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
|
|
|
/// Whether we're on iPad
|
|
private var isIPad: Bool {
|
|
horizontalSizeClass == .regular
|
|
}
|
|
|
|
/// Maximum content width based on device
|
|
private var maxContentWidth: CGFloat {
|
|
if isIPad {
|
|
return verticalSizeClass == .compact
|
|
? Design.Size.maxContentWidthLandscape
|
|
: Design.Size.maxContentWidthPortrait
|
|
}
|
|
return .infinity
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let state = gameState {
|
|
mainGameView(state: state)
|
|
} else {
|
|
ProgressView()
|
|
.task {
|
|
gameState = GameState(settings: settings)
|
|
}
|
|
}
|
|
}
|
|
.sheet(isPresented: $showSettings) {
|
|
SettingsView(settings: settings, gameState: gameState)
|
|
}
|
|
.sheet(isPresented: $showRules) {
|
|
RulesHelpView()
|
|
}
|
|
.sheet(isPresented: $showStats) {
|
|
if let state = gameState {
|
|
StatisticsSheetView(state: state)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Main Game View
|
|
|
|
@ViewBuilder
|
|
private func mainGameView(state: GameState) -> some View {
|
|
ZStack {
|
|
// Background
|
|
TableBackgroundView()
|
|
|
|
VStack(spacing: 0) {
|
|
// Top bar
|
|
TopBarView(
|
|
balance: state.balance,
|
|
secondaryInfo: settings.showCardsRemaining ? "\(state.engine.cardsRemaining)" : nil,
|
|
secondaryIcon: settings.showCardsRemaining ? "rectangle.portrait.on.rectangle.portrait.fill" : nil,
|
|
onReset: { state.resetGame() },
|
|
onSettings: { showSettings = true },
|
|
onHelp: { showRules = true },
|
|
onStats: { showStats = true }
|
|
)
|
|
.frame(maxWidth: maxContentWidth)
|
|
|
|
// Card count display (when enabled)
|
|
if settings.showCardCount {
|
|
CardCountView(
|
|
runningCount: state.engine.runningCount,
|
|
trueCount: state.engine.trueCount
|
|
)
|
|
.frame(maxWidth: maxContentWidth)
|
|
}
|
|
|
|
// Reshuffle notification
|
|
if state.showReshuffleNotification {
|
|
ReshuffleNotificationView(showCardCount: settings.showCardCount)
|
|
.frame(maxWidth: maxContentWidth)
|
|
.transition(.move(edge: .top).combined(with: .opacity))
|
|
}
|
|
|
|
// Table layout
|
|
BlackjackTableView(
|
|
state: state,
|
|
onPlaceBet: { placeBet(state: state) }
|
|
)
|
|
.frame(maxWidth: maxContentWidth)
|
|
|
|
Spacer()
|
|
|
|
// Chip selector - only shown during betting phase
|
|
if state.currentPhase == .betting {
|
|
ChipSelectorView(
|
|
selectedChip: $selectedChip,
|
|
balance: state.balance,
|
|
currentBet: state.currentBet,
|
|
maxBet: state.settings.maxBet
|
|
)
|
|
.frame(maxWidth: maxContentWidth)
|
|
.padding(.bottom, Design.Spacing.small)
|
|
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
|
}
|
|
|
|
// Action buttons
|
|
ActionButtonsView(state: state)
|
|
.frame(maxWidth: maxContentWidth)
|
|
.padding(.bottom, Design.Spacing.medium)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
|
|
// Insurance popup overlay (covers entire screen)
|
|
if state.currentPhase == .insurance {
|
|
InsurancePopupView(
|
|
betAmount: state.currentBet / 2,
|
|
balance: state.balance,
|
|
onTake: { Task { await state.takeInsurance() } },
|
|
onDecline: { state.declineInsurance() }
|
|
)
|
|
.transition(.opacity.combined(with: .scale(scale: 0.9)))
|
|
}
|
|
|
|
// Result banner overlay
|
|
if state.showResultBanner, let result = state.lastRoundResult {
|
|
ResultBannerView(
|
|
result: result,
|
|
currentBalance: state.balance,
|
|
minBet: state.settings.minBet,
|
|
onNewRound: { state.newRound() },
|
|
onPlayAgain: { state.resetGame() }
|
|
)
|
|
}
|
|
|
|
// Confetti for wins (matching Baccarat pattern)
|
|
if state.showResultBanner && (state.lastRoundResult?.totalWinnings ?? 0) > 0 {
|
|
ConfettiView()
|
|
}
|
|
|
|
// Game over
|
|
if state.isGameOver && !state.showResultBanner {
|
|
GameOverView(
|
|
roundsPlayed: state.roundsPlayed,
|
|
onPlayAgain: { state.resetGame() }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Betting
|
|
|
|
private func placeBet(state: GameState) {
|
|
state.placeBet(amount: selectedChip.rawValue)
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
GameTableView()
|
|
}
|
|
|