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
|
bankerHadPair = false
|
||||||
betResults = []
|
betResults = []
|
||||||
|
|
||||||
// Deal initial cards
|
// Change to dealing phase - triggers layout animation (horizontal to vertical)
|
||||||
currentPhase = .dealingInitial
|
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()
|
let initialCards = engine.dealInitialCards()
|
||||||
|
|
||||||
// Check if animations are enabled
|
// Check if animations are enabled
|
||||||
|
|||||||
@ -17,6 +17,9 @@ struct GameTableView: View {
|
|||||||
@State private var showRules = false
|
@State private var showRules = false
|
||||||
@State private var showStats = 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(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||||
|
|
||||||
@ -76,8 +79,13 @@ struct GameTableView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
ZStack {
|
ZStack {
|
||||||
// Table background (from CasinoKit)
|
// Table background - measures screen size for card sizing
|
||||||
TableBackgroundView()
|
TableBackgroundView()
|
||||||
|
.onGeometryChange(for: CGSize.self) { proxy in
|
||||||
|
proxy.size
|
||||||
|
} action: { size in
|
||||||
|
screenSize = size
|
||||||
|
}
|
||||||
|
|
||||||
// Main content
|
// Main content
|
||||||
mainContent(geometry: geometry)
|
mainContent(geometry: geometry)
|
||||||
@ -192,7 +200,8 @@ struct GameTableView: View {
|
|||||||
showAnimations: settings.showAnimations,
|
showAnimations: settings.showAnimations,
|
||||||
dealingSpeed: settings.dealingSpeed,
|
dealingSpeed: settings.dealingSpeed,
|
||||||
bettedOnPlayer: state.bettedOnPlayer,
|
bettedOnPlayer: state.bettedOnPlayer,
|
||||||
isDealing: isDealing
|
isDealing: isDealing,
|
||||||
|
screenSize: screenSize
|
||||||
)
|
)
|
||||||
.frame(maxWidth: maxContentWidth)
|
.frame(maxWidth: maxContentWidth)
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
@ -289,7 +298,8 @@ struct GameTableView: View {
|
|||||||
showAnimations: settings.showAnimations,
|
showAnimations: settings.showAnimations,
|
||||||
dealingSpeed: settings.dealingSpeed,
|
dealingSpeed: settings.dealingSpeed,
|
||||||
bettedOnPlayer: state.bettedOnPlayer,
|
bettedOnPlayer: state.bettedOnPlayer,
|
||||||
isDealing: isDealing
|
isDealing: isDealing,
|
||||||
|
screenSize: screenSize
|
||||||
)
|
)
|
||||||
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
// Baccarat
|
// Baccarat
|
||||||
//
|
//
|
||||||
// The cards display area showing both Player and Banker hands.
|
// The cards display area showing both Player and Banker hands.
|
||||||
// Defaults to side-by-side during betting, animates to vertical during dealing
|
// Animates from side-by-side (betting) to vertical stack (dealing).
|
||||||
// with the betted hand on bottom.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
@ -12,7 +11,7 @@ import CasinoKit
|
|||||||
|
|
||||||
/// The cards display area showing both hands.
|
/// The cards display area showing both hands.
|
||||||
/// - Betting phase: Horizontal side-by-side layout (Player | Banker)
|
/// - 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 {
|
struct CardsDisplayArea: View {
|
||||||
let playerCards: [Card]
|
let playerCards: [Card]
|
||||||
let bankerCards: [Card]
|
let bankerCards: [Card]
|
||||||
@ -26,39 +25,48 @@ struct CardsDisplayArea: View {
|
|||||||
let showAnimations: Bool
|
let showAnimations: Bool
|
||||||
let dealingSpeed: Double
|
let dealingSpeed: Double
|
||||||
/// Which main bet is placed - nil if no main bet, true if Player, false if Banker.
|
/// 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?
|
let bettedOnPlayer: Bool?
|
||||||
/// Whether the game is in dealing/result phase (vertical layout) or betting phase (horizontal).
|
/// Whether the game is in dealing/result phase (vertical layout) or betting phase (horizontal).
|
||||||
let isDealing: Bool
|
let isDealing: Bool
|
||||||
|
/// Full screen size for calculating card width in dealing mode.
|
||||||
|
let screenSize: CGSize
|
||||||
|
|
||||||
// MARK: - State
|
// MARK: - State
|
||||||
|
|
||||||
@State private var containerWidth: CGFloat = 300
|
@State private var containerWidth: CGFloat = 300
|
||||||
|
@Namespace private var animation
|
||||||
|
|
||||||
// MARK: - Environment
|
// MARK: - Environment
|
||||||
|
|
||||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
/// Whether we're on a large screen (iPad)
|
|
||||||
private var isLargeScreen: Bool {
|
private var isLargeScreen: Bool {
|
||||||
horizontalSizeClass == .regular
|
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 }
|
private var showDebugBorders: Bool { Design.showDebugBorders }
|
||||||
|
|
||||||
/// Label font size - larger on iPad
|
|
||||||
private var labelFontSize: CGFloat {
|
private var labelFontSize: CGFloat {
|
||||||
isLargeScreen ? 18 : Design.Size.labelFontSize
|
isLargeScreen ? 18 : Design.Size.labelFontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimum height for label row - larger on iPad
|
|
||||||
private var labelRowMinHeight: CGFloat {
|
private var labelRowMinHeight: CGFloat {
|
||||||
isLargeScreen ? 40 : Design.Size.labelRowHeight
|
isLargeScreen ? 40 : Design.Size.labelRowHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether Player hand should be on bottom in vertical mode.
|
||||||
|
private var playerOnBottom: Bool {
|
||||||
|
bettedOnPlayer ?? true
|
||||||
|
}
|
||||||
|
|
||||||
/// Spacing between hands
|
/// Spacing between hands
|
||||||
private var handsSpacing: CGFloat {
|
private var handsSpacing: CGFloat {
|
||||||
if isDealing {
|
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 {
|
private var handSectionWidth: CGFloat {
|
||||||
if isDealing {
|
isDealing ? verticalHandWidth : horizontalHandWidth
|
||||||
return containerWidth * 0.7
|
|
||||||
} else {
|
|
||||||
return (containerWidth - handsSpacing) / 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Accessibility
|
// MARK: - Accessibility
|
||||||
@ -111,49 +141,35 @@ struct CardsDisplayArea: View {
|
|||||||
return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue)
|
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
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
layout {
|
// Use different layouts but keep view identity with matchedGeometryEffect
|
||||||
// First position: Player in horizontal, or top hand in vertical
|
Group {
|
||||||
if isDealing && !playerOnBottom {
|
if isDealing {
|
||||||
// Vertical mode, player on top
|
// Vertical layout
|
||||||
playerHandSection(width: handSectionWidth)
|
VStack(spacing: handsSpacing) {
|
||||||
.debugBorder(showDebugBorders, color: .blue, label: "Player")
|
// Top position
|
||||||
} else if isDealing && playerOnBottom {
|
if playerOnBottom {
|
||||||
// Vertical mode, banker on top
|
bankerHandSection(width: handSectionWidth)
|
||||||
bankerHandSection(width: handSectionWidth)
|
.matchedGeometryEffect(id: "banker", in: animation)
|
||||||
.debugBorder(showDebugBorders, color: .red, label: "Banker")
|
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 {
|
} else {
|
||||||
// Horizontal mode - player always on left
|
// Horizontal layout - Player left, Banker right
|
||||||
playerHandSection(width: handSectionWidth)
|
HStack(spacing: handsSpacing) {
|
||||||
.debugBorder(showDebugBorders, color: .blue, label: "Player")
|
playerHandSection(width: handSectionWidth)
|
||||||
}
|
.matchedGeometryEffect(id: "player", in: animation)
|
||||||
|
bankerHandSection(width: handSectionWidth)
|
||||||
// Second position: Banker in horizontal, or bottom hand in vertical
|
.matchedGeometryEffect(id: "banker", in: animation)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
@ -176,15 +192,14 @@ struct CardsDisplayArea: View {
|
|||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
)
|
)
|
||||||
.debugBorder(showDebugBorders, color: .mint, label: "HandsContainer")
|
.debugBorder(showDebugBorders, color: .mint, label: "HandsContainer")
|
||||||
.animation(.spring(duration: 0.5, bounce: 0.2), value: isDealing)
|
.animation(.spring(duration: 0.6, bounce: 0.2), value: isDealing)
|
||||||
.animation(.spring(duration: 0.4, bounce: 0.15), value: playerOnBottom)
|
.animation(.spring(duration: 0.5, bounce: 0.15), value: playerOnBottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Views
|
// MARK: - Private Views
|
||||||
|
|
||||||
private func playerHandSection(width: CGFloat) -> some View {
|
private func playerHandSection(width: CGFloat) -> some View {
|
||||||
VStack(spacing: Design.Spacing.small) {
|
VStack(spacing: Design.Spacing.small) {
|
||||||
// Label with value
|
|
||||||
HStack(spacing: Design.Spacing.small) {
|
HStack(spacing: Design.Spacing.small) {
|
||||||
Text("PLAYER")
|
Text("PLAYER")
|
||||||
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
|
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
|
||||||
@ -196,7 +211,6 @@ struct CardsDisplayArea: View {
|
|||||||
}
|
}
|
||||||
.frame(minHeight: labelRowMinHeight)
|
.frame(minHeight: labelRowMinHeight)
|
||||||
|
|
||||||
// Cards
|
|
||||||
CompactHandView(
|
CompactHandView(
|
||||||
cards: playerCards,
|
cards: playerCards,
|
||||||
cardsFaceUp: playerCardsFaceUp,
|
cardsFaceUp: playerCardsFaceUp,
|
||||||
@ -214,7 +228,6 @@ struct CardsDisplayArea: View {
|
|||||||
|
|
||||||
private func bankerHandSection(width: CGFloat) -> some View {
|
private func bankerHandSection(width: CGFloat) -> some View {
|
||||||
VStack(spacing: Design.Spacing.small) {
|
VStack(spacing: Design.Spacing.small) {
|
||||||
// Label with value
|
|
||||||
HStack(spacing: Design.Spacing.small) {
|
HStack(spacing: Design.Spacing.small) {
|
||||||
Text("BANKER")
|
Text("BANKER")
|
||||||
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
|
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
|
||||||
@ -226,7 +239,6 @@ struct CardsDisplayArea: View {
|
|||||||
}
|
}
|
||||||
.frame(minHeight: labelRowMinHeight)
|
.frame(minHeight: labelRowMinHeight)
|
||||||
|
|
||||||
// Cards
|
|
||||||
CompactHandView(
|
CompactHandView(
|
||||||
cards: bankerCards,
|
cards: bankerCards,
|
||||||
cardsFaceUp: bankerCardsFaceUp,
|
cardsFaceUp: bankerCardsFaceUp,
|
||||||
@ -261,7 +273,8 @@ struct CardsDisplayArea: View {
|
|||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0,
|
||||||
bettedOnPlayer: nil,
|
bettedOnPlayer: nil,
|
||||||
isDealing: false
|
isDealing: false,
|
||||||
|
screenSize: CGSize(width: 400, height: 800)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,7 +301,8 @@ struct CardsDisplayArea: View {
|
|||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0,
|
||||||
bettedOnPlayer: true,
|
bettedOnPlayer: true,
|
||||||
isDealing: true
|
isDealing: true,
|
||||||
|
screenSize: CGSize(width: 400, height: 800)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,7 +331,8 @@ struct CardsDisplayArea: View {
|
|||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0,
|
||||||
bettedOnPlayer: false,
|
bettedOnPlayer: false,
|
||||||
isDealing: true
|
isDealing: true,
|
||||||
|
screenSize: CGSize(width: 400, height: 800)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user