Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
fa016047c2
commit
e8e0a68c24
@ -610,8 +610,14 @@ final class GameState {
|
||||
bankerHadPair = false
|
||||
betResults = []
|
||||
|
||||
// Deal initial cards
|
||||
// Change to dealing phase - triggers layout animation (horizontal to vertical)
|
||||
currentPhase = .dealingInitial
|
||||
|
||||
// Wait for layout animation to complete before dealing cards
|
||||
if settings.showAnimations {
|
||||
try? await Task.sleep(for: .seconds(1))
|
||||
}
|
||||
|
||||
let initialCards = engine.dealInitialCards()
|
||||
|
||||
// Check if animations are enabled
|
||||
|
||||
@ -17,6 +17,9 @@ struct GameTableView: View {
|
||||
@State private var showRules = false
|
||||
@State private var showStats = false
|
||||
|
||||
/// Screen size for card sizing (measured from TableBackgroundView)
|
||||
@State private var screenSize: CGSize = CGSize(width: 375, height: 667)
|
||||
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
|
||||
@ -76,8 +79,13 @@ struct GameTableView: View {
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// Table background (from CasinoKit)
|
||||
// Table background - measures screen size for card sizing
|
||||
TableBackgroundView()
|
||||
.onGeometryChange(for: CGSize.self) { proxy in
|
||||
proxy.size
|
||||
} action: { size in
|
||||
screenSize = size
|
||||
}
|
||||
|
||||
// Main content
|
||||
mainContent(geometry: geometry)
|
||||
@ -192,7 +200,8 @@ struct GameTableView: View {
|
||||
showAnimations: settings.showAnimations,
|
||||
dealingSpeed: settings.dealingSpeed,
|
||||
bettedOnPlayer: state.bettedOnPlayer,
|
||||
isDealing: isDealing
|
||||
isDealing: isDealing,
|
||||
screenSize: screenSize
|
||||
)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
@ -289,7 +298,8 @@ struct GameTableView: View {
|
||||
showAnimations: settings.showAnimations,
|
||||
dealingSpeed: settings.dealingSpeed,
|
||||
bettedOnPlayer: state.bettedOnPlayer,
|
||||
isDealing: isDealing
|
||||
isDealing: isDealing,
|
||||
screenSize: screenSize
|
||||
)
|
||||
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
|
||||
@ -3,8 +3,7 @@
|
||||
// Baccarat
|
||||
//
|
||||
// The cards display area showing both Player and Banker hands.
|
||||
// Defaults to side-by-side during betting, animates to vertical during dealing
|
||||
// with the betted hand on bottom.
|
||||
// Animates from side-by-side (betting) to vertical stack (dealing).
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
@ -12,7 +11,7 @@ import CasinoKit
|
||||
|
||||
/// The cards display area showing both hands.
|
||||
/// - Betting phase: Horizontal side-by-side layout (Player | Banker)
|
||||
/// - Dealing/Result phases: Vertical layout with betted hand on bottom
|
||||
/// - Dealing phase: Vertical stack with betted hand on bottom
|
||||
struct CardsDisplayArea: View {
|
||||
let playerCards: [Card]
|
||||
let bankerCards: [Card]
|
||||
@ -26,39 +25,48 @@ struct CardsDisplayArea: View {
|
||||
let showAnimations: Bool
|
||||
let dealingSpeed: Double
|
||||
/// Which main bet is placed - nil if no main bet, true if Player, false if Banker.
|
||||
/// Determines layout ordering in vertical mode: betted hand appears on bottom.
|
||||
let bettedOnPlayer: Bool?
|
||||
/// Whether the game is in dealing/result phase (vertical layout) or betting phase (horizontal).
|
||||
let isDealing: Bool
|
||||
/// Full screen size for calculating card width in dealing mode.
|
||||
let screenSize: CGSize
|
||||
|
||||
// MARK: - State
|
||||
|
||||
@State private var containerWidth: CGFloat = 300
|
||||
@Namespace private var animation
|
||||
|
||||
// MARK: - Environment
|
||||
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
/// Whether we're on a large screen (iPad)
|
||||
private var isLargeScreen: Bool {
|
||||
horizontalSizeClass == .regular
|
||||
}
|
||||
|
||||
// Use global debug flag from Design constants
|
||||
/// Whether we're in landscape mode
|
||||
private var isLandscape: Bool {
|
||||
verticalSizeClass == .compact || (isLargeScreen && screenSize.width > screenSize.height)
|
||||
}
|
||||
|
||||
private var showDebugBorders: Bool { Design.showDebugBorders }
|
||||
|
||||
/// Label font size - larger on iPad
|
||||
private var labelFontSize: CGFloat {
|
||||
isLargeScreen ? 18 : Design.Size.labelFontSize
|
||||
}
|
||||
|
||||
/// Minimum height for label row - larger on iPad
|
||||
private var labelRowMinHeight: CGFloat {
|
||||
isLargeScreen ? 40 : Design.Size.labelRowHeight
|
||||
}
|
||||
|
||||
/// Whether Player hand should be on bottom in vertical mode.
|
||||
private var playerOnBottom: Bool {
|
||||
bettedOnPlayer ?? true
|
||||
}
|
||||
|
||||
/// Spacing between hands
|
||||
private var handsSpacing: CGFloat {
|
||||
if isDealing {
|
||||
@ -68,13 +76,35 @@ struct CardsDisplayArea: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Card section width - larger in vertical mode (more horizontal space)
|
||||
// MARK: - Card Width Calculations
|
||||
|
||||
/// Card section width for horizontal (betting) mode
|
||||
private var horizontalHandWidth: CGFloat {
|
||||
let spacing = isLargeScreen ? Design.Spacing.xxxLarge : Design.Spacing.large
|
||||
return max(100, (containerWidth - spacing) / 2)
|
||||
}
|
||||
|
||||
/// Card section width for vertical (dealing) mode - matches Blackjack sizing
|
||||
/// Uses screen height as base (like Blackjack), which is smaller in landscape
|
||||
private var verticalHandWidth: CGFloat {
|
||||
// Use screen height (smaller dimension in landscape = smaller cards)
|
||||
let height = screenSize.height
|
||||
guard height > 100 else { return horizontalHandWidth }
|
||||
|
||||
// Blackjack uses 0.18, but in landscape we may need slightly smaller
|
||||
let percentage: CGFloat = isLandscape ? 0.14 : 0.18
|
||||
let cardWidth = height * percentage
|
||||
|
||||
// CompactHandView: cardWidth = containerWidth / divisor
|
||||
let overlapRatio: CGFloat = -0.45
|
||||
let maxCards: CGFloat = 3
|
||||
let divisor = 1 + (maxCards - 1) * (1 + overlapRatio)
|
||||
return cardWidth * divisor
|
||||
}
|
||||
|
||||
/// Current hand section width based on mode
|
||||
private var handSectionWidth: CGFloat {
|
||||
if isDealing {
|
||||
return containerWidth * 0.7
|
||||
} else {
|
||||
return (containerWidth - handsSpacing) / 2
|
||||
}
|
||||
isDealing ? verticalHandWidth : horizontalHandWidth
|
||||
}
|
||||
|
||||
// MARK: - Accessibility
|
||||
@ -111,49 +141,35 @@ struct CardsDisplayArea: View {
|
||||
return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue)
|
||||
}
|
||||
|
||||
/// Whether Player hand should be on bottom (true) or top (false) in vertical mode.
|
||||
/// Defaults to Player on bottom if no bet placed.
|
||||
private var playerOnBottom: Bool {
|
||||
bettedOnPlayer ?? true
|
||||
}
|
||||
|
||||
/// The layout to use - HStack for horizontal, VStack for vertical
|
||||
private var layout: AnyLayout {
|
||||
isDealing ? AnyLayout(VStackLayout(spacing: handsSpacing)) : AnyLayout(HStackLayout(spacing: handsSpacing))
|
||||
}
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
layout {
|
||||
// First position: Player in horizontal, or top hand in vertical
|
||||
if isDealing && !playerOnBottom {
|
||||
// Vertical mode, player on top
|
||||
playerHandSection(width: handSectionWidth)
|
||||
.debugBorder(showDebugBorders, color: .blue, label: "Player")
|
||||
} else if isDealing && playerOnBottom {
|
||||
// Vertical mode, banker on top
|
||||
bankerHandSection(width: handSectionWidth)
|
||||
.debugBorder(showDebugBorders, color: .red, label: "Banker")
|
||||
// Use different layouts but keep view identity with matchedGeometryEffect
|
||||
Group {
|
||||
if isDealing {
|
||||
// Vertical layout
|
||||
VStack(spacing: handsSpacing) {
|
||||
// Top position
|
||||
if playerOnBottom {
|
||||
bankerHandSection(width: handSectionWidth)
|
||||
.matchedGeometryEffect(id: "banker", in: animation)
|
||||
playerHandSection(width: handSectionWidth)
|
||||
.matchedGeometryEffect(id: "player", in: animation)
|
||||
} else {
|
||||
playerHandSection(width: handSectionWidth)
|
||||
.matchedGeometryEffect(id: "player", in: animation)
|
||||
bankerHandSection(width: handSectionWidth)
|
||||
.matchedGeometryEffect(id: "banker", in: animation)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Horizontal mode - player always on left
|
||||
playerHandSection(width: handSectionWidth)
|
||||
.debugBorder(showDebugBorders, color: .blue, label: "Player")
|
||||
}
|
||||
|
||||
// Second position: Banker in horizontal, or bottom hand in vertical
|
||||
if isDealing && !playerOnBottom {
|
||||
// Vertical mode, banker on bottom
|
||||
bankerHandSection(width: handSectionWidth)
|
||||
.debugBorder(showDebugBorders, color: .red, label: "Banker")
|
||||
} else if isDealing && playerOnBottom {
|
||||
// Vertical mode, player on bottom
|
||||
playerHandSection(width: handSectionWidth)
|
||||
.debugBorder(showDebugBorders, color: .blue, label: "Player")
|
||||
} else {
|
||||
// Horizontal mode - banker always on right
|
||||
bankerHandSection(width: handSectionWidth)
|
||||
.debugBorder(showDebugBorders, color: .red, label: "Banker")
|
||||
// Horizontal layout - Player left, Banker right
|
||||
HStack(spacing: handsSpacing) {
|
||||
playerHandSection(width: handSectionWidth)
|
||||
.matchedGeometryEffect(id: "player", in: animation)
|
||||
bankerHandSection(width: handSectionWidth)
|
||||
.matchedGeometryEffect(id: "banker", in: animation)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
@ -176,15 +192,14 @@ struct CardsDisplayArea: View {
|
||||
.accessibilityHidden(true)
|
||||
)
|
||||
.debugBorder(showDebugBorders, color: .mint, label: "HandsContainer")
|
||||
.animation(.spring(duration: 0.5, bounce: 0.2), value: isDealing)
|
||||
.animation(.spring(duration: 0.4, bounce: 0.15), value: playerOnBottom)
|
||||
.animation(.spring(duration: 0.6, bounce: 0.2), value: isDealing)
|
||||
.animation(.spring(duration: 0.5, bounce: 0.15), value: playerOnBottom)
|
||||
}
|
||||
|
||||
// MARK: - Private Views
|
||||
|
||||
private func playerHandSection(width: CGFloat) -> some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Label with value
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Text("PLAYER")
|
||||
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
|
||||
@ -196,7 +211,6 @@ struct CardsDisplayArea: View {
|
||||
}
|
||||
.frame(minHeight: labelRowMinHeight)
|
||||
|
||||
// Cards
|
||||
CompactHandView(
|
||||
cards: playerCards,
|
||||
cardsFaceUp: playerCardsFaceUp,
|
||||
@ -214,7 +228,6 @@ struct CardsDisplayArea: View {
|
||||
|
||||
private func bankerHandSection(width: CGFloat) -> some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Label with value
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Text("BANKER")
|
||||
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
|
||||
@ -226,7 +239,6 @@ struct CardsDisplayArea: View {
|
||||
}
|
||||
.frame(minHeight: labelRowMinHeight)
|
||||
|
||||
// Cards
|
||||
CompactHandView(
|
||||
cards: bankerCards,
|
||||
cardsFaceUp: bankerCardsFaceUp,
|
||||
@ -261,7 +273,8 @@ struct CardsDisplayArea: View {
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
bettedOnPlayer: nil,
|
||||
isDealing: false
|
||||
isDealing: false,
|
||||
screenSize: CGSize(width: 400, height: 800)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -288,7 +301,8 @@ struct CardsDisplayArea: View {
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
bettedOnPlayer: true,
|
||||
isDealing: true
|
||||
isDealing: true,
|
||||
screenSize: CGSize(width: 400, height: 800)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -317,7 +331,8 @@ struct CardsDisplayArea: View {
|
||||
showAnimations: true,
|
||||
dealingSpeed: 1.0,
|
||||
bettedOnPlayer: false,
|
||||
isDealing: true
|
||||
isDealing: true,
|
||||
screenSize: CGSize(width: 400, height: 800)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user