1st attempt of roation

Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
Matt Bruce 2025-12-28 18:25:33 -06:00
parent 8c35899c38
commit fa016047c2
4 changed files with 99 additions and 47 deletions

View File

@ -518,6 +518,13 @@ final class GameState {
return bet.amount < minBet return bet.amount < minBet
} }
/// Which main bet is placed - nil if no main bet, true if Player, false if Banker.
/// Used to determine card layout ordering (betted hand appears on bottom).
var bettedOnPlayer: Bool? {
guard let bet = mainBet else { return nil }
return bet.type == .player
}
/// Amount needed to reach the minimum bet. /// Amount needed to reach the minimum bet.
var amountNeededForMinimum: Int { var amountNeededForMinimum: Int {
guard let bet = mainBet else { return minBet } guard let bet = mainBet else { return minBet }

View File

@ -37,21 +37,11 @@ struct GameTableView: View {
isLandscape ? Design.Spacing.medium : Design.Spacing.xSmall isLandscape ? Design.Spacing.medium : Design.Spacing.xSmall
} }
/// Minimum spacer height - smaller in landscape to fit content
private var minSpacerHeight: CGFloat {
isLandscape ? 0 : Design.Spacing.xSmall
}
/// Smaller spacer height - reduced in landscape /// Smaller spacer height - reduced in landscape
private var smallSpacerHeight: CGFloat { private var smallSpacerHeight: CGFloat {
isLandscape ? Design.Spacing.xxSmall : Design.Spacing.small isLandscape ? Design.Spacing.xxSmall : Design.Spacing.small
} }
/// Medium spacer height - reduced in landscape
private var mediumSpacerHeight: CGFloat {
isLandscape ? Design.Spacing.xSmall : Design.Spacing.medium
}
/// Maximum width for game content on large screens /// Maximum width for game content on large screens
private var maxContentWidth: CGFloat { private var maxContentWidth: CGFloat {
isLandscape ? CasinoDesign.Size.maxContentWidthLandscape : CasinoDesign.Size.maxContentWidthPortrait isLandscape ? CasinoDesign.Size.maxContentWidthLandscape : CasinoDesign.Size.maxContentWidthPortrait
@ -73,6 +63,11 @@ struct GameTableView: View {
state.lastResult == .tie state.lastResult == .tie
} }
/// Whether we're in a dealing/result phase (vertical layout) vs betting phase (horizontal)
private var isDealing: Bool {
state.currentPhase != .betting
}
// Use global debug flag from Design constants // Use global debug flag from Design constants
private var showDebugBorders: Bool { Design.showDebugBorders } private var showDebugBorders: Bool { Design.showDebugBorders }
@ -195,7 +190,9 @@ struct GameTableView: View {
bankerIsWinner: bankerIsWinner, bankerIsWinner: bankerIsWinner,
isTie: isTie, isTie: isTie,
showAnimations: settings.showAnimations, showAnimations: settings.showAnimations,
dealingSpeed: settings.dealingSpeed dealingSpeed: settings.dealingSpeed,
bettedOnPlayer: state.bettedOnPlayer,
isDealing: isDealing
) )
.frame(maxWidth: maxContentWidth) .frame(maxWidth: maxContentWidth)
.padding(.horizontal, Design.Spacing.medium) .padding(.horizontal, Design.Spacing.medium)
@ -264,7 +261,7 @@ struct GameTableView: View {
.safeAreaPadding(.bottom, Design.Spacing.small) .safeAreaPadding(.bottom, Design.Spacing.small)
} }
/// Portrait layout with RoadMap inline /// Portrait layout - vertical card layout, no RoadMap (shown only in landscape)
private var portraitLayout: some View { private var portraitLayout: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Top bar with balance and info (from CasinoKit) // Top bar with balance and info (from CasinoKit)
@ -278,7 +275,7 @@ struct GameTableView: View {
) )
.debugBorder(showDebugBorders, color: .cyan, label: "TopBar") .debugBorder(showDebugBorders, color: .cyan, label: "TopBar")
// Cards display area // Cards display area - animates from horizontal to vertical when dealing
CardsDisplayArea( CardsDisplayArea(
playerCards: state.visiblePlayerCards, playerCards: state.visiblePlayerCards,
bankerCards: state.visibleBankerCards, bankerCards: state.visibleBankerCards,
@ -290,25 +287,16 @@ struct GameTableView: View {
bankerIsWinner: bankerIsWinner, bankerIsWinner: bankerIsWinner,
isTie: isTie, isTie: isTie,
showAnimations: settings.showAnimations, showAnimations: settings.showAnimations,
dealingSpeed: settings.dealingSpeed dealingSpeed: settings.dealingSpeed,
bettedOnPlayer: state.bettedOnPlayer,
isDealing: isDealing
) )
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity) .frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
.padding(.horizontal, Design.Spacing.medium) .padding(.horizontal, Design.Spacing.medium)
.debugBorder(showDebugBorders, color: .red, label: "CardsArea") .debugBorder(showDebugBorders, color: .red, label: "CardsArea")
Spacer(minLength: minSpacerHeight)
.debugBorder(showDebugBorders, color: .yellow, label: "Spacer2")
// Road map history
if settings.showHistory {
RoadMapView(results: state.recentResults)
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
.padding(.horizontal, Design.Spacing.medium)
.debugBorder(showDebugBorders, color: .orange, label: "RoadMap")
}
Spacer(minLength: smallSpacerHeight) Spacer(minLength: smallSpacerHeight)
.debugBorder(showDebugBorders, color: .yellow, label: "Spacer3") .debugBorder(showDebugBorders, color: .yellow, label: "Spacer2")
// Betting table // Betting table
BettingTableView( BettingTableView(

View File

@ -3,12 +3,16 @@
// 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
// with the betted hand on bottom.
// //
import SwiftUI import SwiftUI
import CasinoKit 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)
/// - Dealing/Result phases: Vertical layout with betted hand on bottom
struct CardsDisplayArea: View { struct CardsDisplayArea: View {
let playerCards: [Card] let playerCards: [Card]
let bankerCards: [Card] let bankerCards: [Card]
@ -21,6 +25,11 @@ struct CardsDisplayArea: View {
let isTie: Bool let isTie: Bool
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.
/// 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
// MARK: - State // MARK: - State
@ -50,9 +59,22 @@ struct CardsDisplayArea: View {
isLargeScreen ? 40 : Design.Size.labelRowHeight isLargeScreen ? 40 : Design.Size.labelRowHeight
} }
/// Spacing between PLAYER and BANKER hands - reduced on smaller screens /// Spacing between hands
private var handsSpacing: CGFloat { private var handsSpacing: CGFloat {
isLargeScreen ? Design.Spacing.xxxLarge : Design.Spacing.large if isDealing {
return isLargeScreen ? Design.Spacing.large : Design.Spacing.medium
} else {
return isLargeScreen ? Design.Spacing.xxxLarge : Design.Spacing.large
}
}
/// Card section width - larger in vertical mode (more horizontal space)
private var handSectionWidth: CGFloat {
if isDealing {
return containerWidth * 0.7
} else {
return (containerWidth - handsSpacing) / 2
}
} }
// MARK: - Accessibility // MARK: - Accessibility
@ -89,22 +111,50 @@ struct CardsDisplayArea: View {
return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue) return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue)
} }
/// Calculate hand section width from total container width /// Whether Player hand should be on bottom (true) or top (false) in vertical mode.
private var handSectionWidth: CGFloat { /// Defaults to Player on bottom if no bet placed.
(containerWidth - handsSpacing) / 2 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 {
HStack(spacing: handsSpacing) { layout {
// Player side // First position: Player in horizontal, or top hand in vertical
if isDealing && !playerOnBottom {
// Vertical mode, player on top
playerHandSection(width: handSectionWidth) playerHandSection(width: handSectionWidth)
.debugBorder(showDebugBorders, color: .blue, label: "Player") .debugBorder(showDebugBorders, color: .blue, label: "Player")
} else if isDealing && playerOnBottom {
// Banker side // Vertical mode, banker on top
bankerHandSection(width: handSectionWidth) bankerHandSection(width: handSectionWidth)
.debugBorder(showDebugBorders, color: .red, label: "Banker") .debugBorder(showDebugBorders, color: .red, label: "Banker")
} 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")
}
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.top, Design.Spacing.medium) .padding(.top, Design.Spacing.medium)
@ -126,6 +176,8 @@ 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.4, bounce: 0.15), value: playerOnBottom)
} }
// MARK: - Private Views // MARK: - Private Views
@ -193,7 +245,7 @@ struct CardsDisplayArea: View {
// MARK: - Previews // MARK: - Previews
#Preview("Empty Hands") { #Preview("Horizontal - Betting Phase") {
ZStack { ZStack {
TableBackgroundView() TableBackgroundView()
CardsDisplayArea( CardsDisplayArea(
@ -207,12 +259,14 @@ struct CardsDisplayArea: View {
bankerIsWinner: false, bankerIsWinner: false,
isTie: false, isTie: false,
showAnimations: true, showAnimations: true,
dealingSpeed: 1.0 dealingSpeed: 1.0,
bettedOnPlayer: nil,
isDealing: false
) )
} }
} }
#Preview("Player Wins") { #Preview("Vertical - Dealing (Bet on Player)") {
ZStack { ZStack {
TableBackgroundView() TableBackgroundView()
CardsDisplayArea( CardsDisplayArea(
@ -232,12 +286,14 @@ struct CardsDisplayArea: View {
bankerIsWinner: false, bankerIsWinner: false,
isTie: false, isTie: false,
showAnimations: true, showAnimations: true,
dealingSpeed: 1.0 dealingSpeed: 1.0,
bettedOnPlayer: true,
isDealing: true
) )
} }
} }
#Preview("Banker Wins with 3 Cards") { #Preview("Vertical - Dealing (Bet on Banker)") {
ZStack { ZStack {
TableBackgroundView() TableBackgroundView()
CardsDisplayArea( CardsDisplayArea(
@ -254,13 +310,14 @@ struct CardsDisplayArea: View {
playerCardsFaceUp: [true, true, true], playerCardsFaceUp: [true, true, true],
bankerCardsFaceUp: [true, true, true], bankerCardsFaceUp: [true, true, true],
playerValue: 9, playerValue: 9,
bankerValue: 8, bankerValue: 9,
playerIsWinner: false, playerIsWinner: false,
bankerIsWinner: true, bankerIsWinner: true,
isTie: false, isTie: false,
showAnimations: true, showAnimations: true,
dealingSpeed: 1.0 dealingSpeed: 1.0,
bettedOnPlayer: false,
isDealing: true
) )
} }
} }

View File

@ -17,7 +17,7 @@ enum Design {
// MARK: - Debug // MARK: - Debug
/// Set to true to show layout debug borders on views /// Set to true to show layout debug borders on views
static let showDebugBorders = true static let showDebugBorders = false
/// Set to true to show debug log statements /// Set to true to show debug log statements
static let showDebugLogs = false static let showDebugLogs = false