Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
78f7cb1544
commit
cac0af4ab3
@ -34,22 +34,9 @@
|
||||
EAD890CE2EF1E9CF006DBA80 /* BaccaratUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BaccaratUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
EAD891062EF1F51E006DBA80 /* Exceptions for "Baccarat" folder in "Baccarat" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Agents.md,
|
||||
);
|
||||
target = EAD890B62EF1E9CE006DBA80 /* Baccarat */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
EAD890B92EF1E9CE006DBA80 /* Baccarat */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
EAD891062EF1F51E006DBA80 /* Exceptions for "Baccarat" folder in "Baccarat" target */,
|
||||
);
|
||||
path = Baccarat;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
@ -29,15 +29,50 @@ enum Design {
|
||||
// MARK: - Baccarat-Specific Component Sizes
|
||||
|
||||
enum Size {
|
||||
// Cards - use CasinoDesign values
|
||||
// MARK: - Hand Scaling
|
||||
|
||||
/// Hand scaling factor for cards and related elements.
|
||||
/// 1.0 = original size, 1.5 = 50% larger, 2.0 = double size.
|
||||
/// Adjust this value to change card sizes across the app.
|
||||
static let handScale: CGFloat = 1.5
|
||||
|
||||
/// Scale multiplier for small screens (iPhone SE, etc).
|
||||
/// Applied instead of handScale on screens narrower than smallScreenThreshold.
|
||||
static let smallScreenScale: CGFloat = 1.2
|
||||
|
||||
/// Screen width threshold for small screen detection (iPhone SE is 375pt)
|
||||
static let smallScreenThreshold: CGFloat = 390
|
||||
|
||||
/// Additional scale multiplier for large screens (iPad).
|
||||
/// Applied on top of handScale when on regular size class.
|
||||
static let largeScreenMultiplier: CGFloat = 1.2
|
||||
|
||||
// Cards - base values from CasinoDesign
|
||||
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
|
||||
static let cardWidthMedium: CGFloat = CasinoDesign.Size.cardWidthMedium
|
||||
static let cardWidthLarge: CGFloat = CasinoDesign.Size.cardWidthLarge
|
||||
static let cardAspectRatio: CGFloat = CasinoDesign.Size.cardAspectRatio
|
||||
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap
|
||||
|
||||
// Baccarat table cards (smaller for compact layout)
|
||||
static let cardWidthTable: CGFloat = 45
|
||||
/// Base card width before scaling (for reference)
|
||||
private static let cardWidthTableBase: CGFloat = 45
|
||||
|
||||
/// Card overlap scaled with hand size (standard iPhone)
|
||||
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap * handScale
|
||||
|
||||
/// Card overlap for small screens
|
||||
static let cardOverlapSmall: CGFloat = CasinoDesign.Size.cardOverlap * smallScreenScale
|
||||
|
||||
// Baccarat table cards - scaled for better visibility (standard iPhone)
|
||||
static let cardWidthTable: CGFloat = cardWidthTableBase * handScale
|
||||
|
||||
/// Card width for small screens (iPhone SE, etc)
|
||||
static let cardWidthTableSmall: CGFloat = cardWidthTableBase * smallScreenScale
|
||||
|
||||
/// Card width for large screens (iPad) - applies additional multiplier
|
||||
static let cardWidthTableLarge: CGFloat = cardWidthTableBase * handScale * largeScreenMultiplier
|
||||
|
||||
/// Card overlap for large screens
|
||||
static let cardOverlapLarge: CGFloat = CasinoDesign.Size.cardOverlap * handScale * largeScreenMultiplier
|
||||
|
||||
// Chips - use CasinoDesign values
|
||||
static let chipSmall: CGFloat = CasinoDesign.Size.chipSmall
|
||||
|
||||
@ -52,38 +52,40 @@ struct GameTableView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Table background
|
||||
TableBackgroundView()
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// Table background
|
||||
TableBackgroundView()
|
||||
|
||||
// Main content
|
||||
VStack(spacing: 0) {
|
||||
// Top bar with balance and info
|
||||
TopBarView(
|
||||
balance: state.balance,
|
||||
cardsRemaining: state.engine.shoe.cardsRemaining,
|
||||
showCardsRemaining: settings.showCardsRemaining,
|
||||
onReset: { state.resetGame() },
|
||||
onSettings: { showSettings = true },
|
||||
onHelp: { showRules = true },
|
||||
onStats: { showStats = true }
|
||||
)
|
||||
// Main content
|
||||
VStack(spacing: 0) {
|
||||
// Top bar with balance and info
|
||||
TopBarView(
|
||||
balance: state.balance,
|
||||
cardsRemaining: state.engine.shoe.cardsRemaining,
|
||||
showCardsRemaining: settings.showCardsRemaining,
|
||||
onReset: { state.resetGame() },
|
||||
onSettings: { showSettings = true },
|
||||
onHelp: { showRules = true },
|
||||
onStats: { showStats = true }
|
||||
)
|
||||
|
||||
Spacer(minLength: Design.Spacing.xSmall)
|
||||
Spacer(minLength: Design.Spacing.xSmall)
|
||||
|
||||
// Cards display area - constrained width on iPad
|
||||
CardsDisplayArea(
|
||||
playerCards: state.visiblePlayerCards,
|
||||
bankerCards: state.visibleBankerCards,
|
||||
playerCardsFaceUp: state.playerCardsFaceUp,
|
||||
bankerCardsFaceUp: state.bankerCardsFaceUp,
|
||||
playerValue: state.playerHandValue,
|
||||
bankerValue: state.bankerHandValue,
|
||||
playerIsWinner: playerIsWinner,
|
||||
bankerIsWinner: bankerIsWinner,
|
||||
isTie: isTie
|
||||
)
|
||||
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
||||
// Cards display area - constrained width on iPad
|
||||
CardsDisplayArea(
|
||||
playerCards: state.visiblePlayerCards,
|
||||
bankerCards: state.visibleBankerCards,
|
||||
playerCardsFaceUp: state.playerCardsFaceUp,
|
||||
bankerCardsFaceUp: state.bankerCardsFaceUp,
|
||||
playerValue: state.playerHandValue,
|
||||
bankerValue: state.bankerHandValue,
|
||||
playerIsWinner: playerIsWinner,
|
||||
bankerIsWinner: bankerIsWinner,
|
||||
isTie: isTie,
|
||||
screenWidth: geometry.size.width
|
||||
)
|
||||
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
||||
|
||||
Spacer(minLength: Design.Spacing.xSmall)
|
||||
|
||||
@ -134,37 +136,38 @@ struct GameTableView: View {
|
||||
}
|
||||
.safeAreaPadding(.bottom)
|
||||
|
||||
// Result banner overlay (handles its own iPad sizing)
|
||||
if state.showResultBanner, let result = state.lastResult {
|
||||
ResultBannerView(
|
||||
result: result,
|
||||
totalWinnings: state.lastWinnings,
|
||||
betResults: state.betResults,
|
||||
playerHadPair: state.playerHadPair,
|
||||
bankerHadPair: state.bankerHadPair,
|
||||
currentBalance: state.balance,
|
||||
minBet: state.minBet,
|
||||
onNewRound: { state.newRound() },
|
||||
onGameOver: {
|
||||
// Reset game (sound already played when banner appeared)
|
||||
state.resetGame()
|
||||
// Result banner overlay (handles its own iPad sizing)
|
||||
if state.showResultBanner, let result = state.lastResult {
|
||||
ResultBannerView(
|
||||
result: result,
|
||||
totalWinnings: state.lastWinnings,
|
||||
betResults: state.betResults,
|
||||
playerHadPair: state.playerHadPair,
|
||||
bankerHadPair: state.bankerHadPair,
|
||||
currentBalance: state.balance,
|
||||
minBet: state.minBet,
|
||||
onNewRound: { state.newRound() },
|
||||
onGameOver: {
|
||||
// Reset game (sound already played when banner appeared)
|
||||
state.resetGame()
|
||||
}
|
||||
)
|
||||
.transition(.opacity)
|
||||
|
||||
// Confetti for wins
|
||||
if state.lastWinnings > 0 {
|
||||
ConfettiView()
|
||||
}
|
||||
)
|
||||
.transition(.opacity)
|
||||
|
||||
// Confetti for wins
|
||||
if state.lastWinnings > 0 {
|
||||
ConfettiView()
|
||||
}
|
||||
}
|
||||
|
||||
// Game Over overlay (handles its own iPad sizing)
|
||||
if state.balance == 0 && state.currentBets.isEmpty && !state.isAnimating {
|
||||
GameOverView(
|
||||
roundsPlayed: state.roundHistory.count,
|
||||
onPlayAgain: { state.resetGame() }
|
||||
)
|
||||
.transition(.opacity)
|
||||
// Game Over overlay (handles its own iPad sizing)
|
||||
if state.balance == 0 && state.currentBets.isEmpty && !state.isAnimating {
|
||||
GameOverView(
|
||||
roundsPlayed: state.roundHistory.count,
|
||||
onPlayAgain: { state.resetGame() }
|
||||
)
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@ -347,11 +350,27 @@ struct CardsDisplayArea: View {
|
||||
let playerIsWinner: Bool
|
||||
let bankerIsWinner: Bool
|
||||
let isTie: Bool
|
||||
/// Screen width for responsive card sizing
|
||||
var screenWidth: CGFloat = 400
|
||||
|
||||
// MARK: - Fixed font sizes for card area
|
||||
// Fixed because the card display has strict layout constraints
|
||||
// MARK: - Environment
|
||||
|
||||
private let labelFontSize: CGFloat = 14
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
// MARK: - Scaled font sizes for card area
|
||||
// Scales with hand size for proportional appearance
|
||||
|
||||
/// Whether we're on a large screen (iPad)
|
||||
private var isLargeScreen: Bool {
|
||||
horizontalSizeClass == .regular
|
||||
}
|
||||
|
||||
/// Label font size - only scales on iPad to avoid clipping on small iPhones
|
||||
private var labelFontSize: CGFloat {
|
||||
let baseSize: CGFloat = 14
|
||||
// Only apply scaling on large screens; keep original size on iPhone
|
||||
return isLargeScreen ? baseSize * Design.Size.handScale * Design.Size.largeScreenMultiplier : baseSize
|
||||
}
|
||||
|
||||
// MARK: - Accessibility
|
||||
|
||||
@ -387,8 +406,25 @@ struct CardsDisplayArea: View {
|
||||
return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue)
|
||||
}
|
||||
|
||||
/// Minimum height for label row - only scales on iPad
|
||||
private var labelRowMinHeight: CGFloat {
|
||||
let baseHeight: CGFloat = 30
|
||||
// Only apply scaling on large screens; keep original size on iPhone
|
||||
return isLargeScreen ? baseHeight * Design.Size.handScale * Design.Size.largeScreenMultiplier : baseHeight
|
||||
}
|
||||
|
||||
/// Spacing between PLAYER and BANKER hands - reduced on smaller screens
|
||||
private var handsSpacing: CGFloat {
|
||||
isLargeScreen ? Design.Spacing.xxxLarge : Design.Spacing.medium
|
||||
}
|
||||
|
||||
/// Horizontal padding inside the container - reduced on smaller screens
|
||||
private var containerPaddingH: CGFloat {
|
||||
isLargeScreen ? Design.Spacing.xLarge : Design.Spacing.small
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.xxxLarge) {
|
||||
HStack(spacing: handsSpacing) {
|
||||
// Player side
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Label with value
|
||||
@ -401,13 +437,14 @@ struct CardsDisplayArea: View {
|
||||
ValueBadge(value: playerValue, color: .blue)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 30)
|
||||
.frame(minHeight: labelRowMinHeight)
|
||||
|
||||
// Cards
|
||||
CompactHandView(
|
||||
cards: playerCards,
|
||||
cardsFaceUp: playerCardsFaceUp,
|
||||
isWinner: playerIsWinner
|
||||
isWinner: playerIsWinner,
|
||||
screenWidth: screenWidth
|
||||
)
|
||||
}
|
||||
.accessibilityElement(children: .ignore)
|
||||
@ -426,28 +463,29 @@ struct CardsDisplayArea: View {
|
||||
ValueBadge(value: bankerValue, color: .red)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 30)
|
||||
.frame(minHeight: labelRowMinHeight)
|
||||
|
||||
// Cards
|
||||
CompactHandView(
|
||||
cards: bankerCards,
|
||||
cardsFaceUp: bankerCardsFaceUp,
|
||||
isWinner: bankerIsWinner
|
||||
isWinner: bankerIsWinner,
|
||||
screenWidth: screenWidth
|
||||
)
|
||||
}
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel(String(localized: "Banker hand"))
|
||||
.accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : ""))
|
||||
}
|
||||
.padding(.top, Design.Spacing.large)
|
||||
.padding(.bottom, Design.Spacing.xLarge)
|
||||
.padding(.horizontal, Design.Spacing.xLarge)
|
||||
.padding(.top, Design.Spacing.medium)
|
||||
.padding(.bottom, Design.Spacing.large)
|
||||
.padding(.horizontal, containerPaddingH)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
|
||||
.fill(Color.black.opacity(Design.Opacity.quarter))
|
||||
.accessibilityHidden(true)
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.padding(.horizontal, Design.Spacing.small)
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,17 +494,59 @@ struct CompactHandView: View {
|
||||
let cards: [Card]
|
||||
let cardsFaceUp: [Bool]
|
||||
let isWinner: Bool
|
||||
/// Screen width passed from parent for responsive sizing
|
||||
var screenWidth: CGFloat = 400
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
// MARK: - Environment
|
||||
|
||||
@ScaledMetric(relativeTo: .caption) private var winBadgeFontSize: CGFloat = 10
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
// MARK: - Layout Constants
|
||||
// Fixed size: cards have strict visual constraints
|
||||
// Responsive sizing based on device
|
||||
|
||||
/// Whether we're on a large screen (iPad)
|
||||
private var isLargeScreen: Bool {
|
||||
horizontalSizeClass == .regular
|
||||
}
|
||||
|
||||
/// Whether we're on a small screen (iPhone SE, etc)
|
||||
private var isSmallScreen: Bool {
|
||||
!isLargeScreen && screenWidth < Design.Size.smallScreenThreshold
|
||||
}
|
||||
|
||||
/// WIN badge font size - only scales on iPad
|
||||
private var winBadgeFontSize: CGFloat {
|
||||
let baseSize: CGFloat = 10
|
||||
return isLargeScreen ? baseSize * Design.Size.handScale * Design.Size.largeScreenMultiplier : baseSize
|
||||
}
|
||||
|
||||
/// Card width - responsive based on screen size
|
||||
private var cardWidth: CGFloat {
|
||||
if isLargeScreen {
|
||||
return Design.Size.cardWidthTableLarge
|
||||
} else if isSmallScreen {
|
||||
return Design.Size.cardWidthTableSmall
|
||||
} else {
|
||||
return Design.Size.cardWidthTable
|
||||
}
|
||||
}
|
||||
|
||||
/// Card height based on aspect ratio
|
||||
private var cardHeight: CGFloat {
|
||||
cardWidth * Design.Size.cardAspectRatio
|
||||
}
|
||||
|
||||
/// Card overlap - scaled with card size
|
||||
private var cardOverlap: CGFloat {
|
||||
if isLargeScreen {
|
||||
return Design.Size.cardOverlapLarge
|
||||
} else if isSmallScreen {
|
||||
return Design.Size.cardOverlapSmall
|
||||
} else {
|
||||
return Design.Size.cardOverlap
|
||||
}
|
||||
}
|
||||
|
||||
private let cardWidth: CGFloat = Design.Size.cardWidthTable
|
||||
private let cardHeight: CGFloat = Design.Size.cardWidthTable * Design.Size.cardAspectRatio
|
||||
private let cardOverlap: CGFloat = Design.Size.cardOverlap
|
||||
private let placeholderSpacing: CGFloat = Design.Spacing.small
|
||||
|
||||
/// Fixed container width to prevent resizing during deal
|
||||
@ -538,10 +618,27 @@ struct ValueBadge: View {
|
||||
let value: Int
|
||||
let color: Color
|
||||
|
||||
// MARK: - Environment
|
||||
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var valueFontSize: CGFloat = 15
|
||||
@ScaledMetric(relativeTo: .headline) private var badgeSize: CGFloat = 26
|
||||
/// Whether we're on a large screen (iPad)
|
||||
private var isLargeScreen: Bool {
|
||||
horizontalSizeClass == .regular
|
||||
}
|
||||
|
||||
/// Scale factor for badge sizing - only applies on iPad to avoid clipping on iPhone
|
||||
private var scale: CGFloat {
|
||||
isLargeScreen ? Design.Size.handScale * Design.Size.largeScreenMultiplier : 1.0
|
||||
}
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var baseValueFontSize: CGFloat = 15
|
||||
@ScaledMetric(relativeTo: .headline) private var baseBadgeSize: CGFloat = 26
|
||||
|
||||
private var valueFontSize: CGFloat { baseValueFontSize * scale }
|
||||
private var badgeSize: CGFloat { baseBadgeSize * scale }
|
||||
|
||||
var body: some View {
|
||||
Text("\(value)")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user