248 lines
8.0 KiB
Swift
248 lines
8.0 KiB
Swift
//
|
|
// CardsDisplayArea.swift
|
|
// Baccarat
|
|
//
|
|
// The cards display area showing both Player and Banker hands.
|
|
//
|
|
|
|
import SwiftUI
|
|
import CasinoKit
|
|
|
|
/// The cards display area showing both hands.
|
|
struct CardsDisplayArea: View {
|
|
let playerCards: [Card]
|
|
let bankerCards: [Card]
|
|
let playerCardsFaceUp: [Bool]
|
|
let bankerCardsFaceUp: [Bool]
|
|
let playerValue: Int
|
|
let bankerValue: Int
|
|
let playerIsWinner: Bool
|
|
let bankerIsWinner: Bool
|
|
let isTie: Bool
|
|
/// Screen width for responsive card sizing
|
|
var screenWidth: CGFloat = 400
|
|
|
|
// MARK: - Environment
|
|
|
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
|
|
|
// MARK: - Computed Properties
|
|
|
|
/// Whether we're on a large screen (iPad)
|
|
private var isLargeScreen: Bool {
|
|
horizontalSizeClass == .regular
|
|
}
|
|
|
|
// Use global debug flag from Design constants
|
|
private var showDebugBorders: Bool { Design.showDebugBorders }
|
|
|
|
/// Label font size - only scales on iPad to avoid clipping on small iPhones
|
|
private var labelFontSize: CGFloat {
|
|
let baseSize: CGFloat = 14
|
|
return isLargeScreen ? baseSize * Design.Size.handScale * Design.Size.largeScreenMultiplier : baseSize
|
|
}
|
|
|
|
/// Minimum height for label row - only scales on iPad
|
|
private var labelRowMinHeight: CGFloat {
|
|
let baseHeight: CGFloat = 30
|
|
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.large
|
|
}
|
|
|
|
/// Horizontal padding inside the container
|
|
private var containerPaddingH: CGFloat {
|
|
isLargeScreen ? Design.Spacing.xLarge : Design.Spacing.medium
|
|
}
|
|
|
|
/// Outer horizontal padding
|
|
private var outerPaddingH: CGFloat {
|
|
isLargeScreen ? Design.Spacing.large : Design.Spacing.small
|
|
}
|
|
|
|
// MARK: - Accessibility
|
|
|
|
private var playerHandDescription: String {
|
|
if playerCards.isEmpty {
|
|
return String(localized: "No cards")
|
|
}
|
|
let visibleCards = zip(playerCards, playerCardsFaceUp)
|
|
.filter { $1 }
|
|
.map { "\($0.0.rank.accessibilityName) of \($0.0.suit.accessibilityName)" }
|
|
|
|
if visibleCards.isEmpty {
|
|
return String(localized: "Cards face down")
|
|
}
|
|
|
|
let format = String(localized: "handValueFormat")
|
|
return visibleCards.joined(separator: ", ") + ". " + String(format: format, playerValue)
|
|
}
|
|
|
|
private var bankerHandDescription: String {
|
|
if bankerCards.isEmpty {
|
|
return String(localized: "No cards")
|
|
}
|
|
let visibleCards = zip(bankerCards, bankerCardsFaceUp)
|
|
.filter { $1 }
|
|
.map { "\($0.0.rank.accessibilityName) of \($0.0.suit.accessibilityName)" }
|
|
|
|
if visibleCards.isEmpty {
|
|
return String(localized: "Cards face down")
|
|
}
|
|
|
|
let format = String(localized: "handValueFormat")
|
|
return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue)
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
HStack(spacing: handsSpacing) {
|
|
// Player side
|
|
playerHandSection
|
|
.debugBorder(showDebugBorders, color: .blue, label: "Player")
|
|
|
|
// Banker side
|
|
bankerHandSection
|
|
.debugBorder(showDebugBorders, color: .red, label: "Banker")
|
|
}
|
|
.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, outerPaddingH)
|
|
.debugBorder(showDebugBorders, color: .mint, label: "HandsContainer")
|
|
}
|
|
|
|
// MARK: - Private Views
|
|
|
|
private var playerHandSection: 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))
|
|
.foregroundStyle(.white)
|
|
|
|
if !playerCards.isEmpty && playerCardsFaceUp.contains(true) {
|
|
HandValueBadge(value: playerValue, color: .blue)
|
|
}
|
|
}
|
|
.frame(minHeight: labelRowMinHeight)
|
|
|
|
// Cards
|
|
CompactHandView(
|
|
cards: playerCards,
|
|
cardsFaceUp: playerCardsFaceUp,
|
|
isWinner: playerIsWinner,
|
|
screenWidth: screenWidth
|
|
)
|
|
}
|
|
.accessibilityElement(children: .ignore)
|
|
.accessibilityLabel(String(localized: "Player hand"))
|
|
.accessibilityValue(playerHandDescription + (playerIsWinner ? ", " + String(localized: "Winner") : ""))
|
|
}
|
|
|
|
private var bankerHandSection: 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))
|
|
.foregroundStyle(.white)
|
|
|
|
if !bankerCards.isEmpty && bankerCardsFaceUp.contains(true) {
|
|
HandValueBadge(value: bankerValue, color: .red)
|
|
}
|
|
}
|
|
.frame(minHeight: labelRowMinHeight)
|
|
|
|
// Cards
|
|
CompactHandView(
|
|
cards: bankerCards,
|
|
cardsFaceUp: bankerCardsFaceUp,
|
|
isWinner: bankerIsWinner,
|
|
screenWidth: screenWidth
|
|
)
|
|
}
|
|
.accessibilityElement(children: .ignore)
|
|
.accessibilityLabel(String(localized: "Banker hand"))
|
|
.accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : ""))
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
#Preview("Empty Hands") {
|
|
ZStack {
|
|
TableBackgroundView()
|
|
CardsDisplayArea(
|
|
playerCards: [],
|
|
bankerCards: [],
|
|
playerCardsFaceUp: [],
|
|
bankerCardsFaceUp: [],
|
|
playerValue: 0,
|
|
bankerValue: 0,
|
|
playerIsWinner: false,
|
|
bankerIsWinner: false,
|
|
isTie: false
|
|
)
|
|
}
|
|
}
|
|
|
|
#Preview("Player Wins") {
|
|
ZStack {
|
|
TableBackgroundView()
|
|
CardsDisplayArea(
|
|
playerCards: [
|
|
Card(suit: .spades, rank: .king),
|
|
Card(suit: .hearts, rank: .eight)
|
|
],
|
|
bankerCards: [
|
|
Card(suit: .clubs, rank: .seven),
|
|
Card(suit: .diamonds, rank: .five)
|
|
],
|
|
playerCardsFaceUp: [true, true],
|
|
bankerCardsFaceUp: [true, true],
|
|
playerValue: 8,
|
|
bankerValue: 2,
|
|
playerIsWinner: true,
|
|
bankerIsWinner: false,
|
|
isTie: false
|
|
)
|
|
}
|
|
}
|
|
|
|
#Preview("Banker Wins with 3 Cards") {
|
|
ZStack {
|
|
TableBackgroundView()
|
|
CardsDisplayArea(
|
|
playerCards: [
|
|
Card(suit: .spades, rank: .four),
|
|
Card(suit: .hearts, rank: .three),
|
|
Card(suit: .clubs, rank: .two)
|
|
],
|
|
bankerCards: [
|
|
Card(suit: .hearts, rank: .ace),
|
|
Card(suit: .diamonds, rank: .ace),
|
|
Card(suit: .spades, rank: .seven)
|
|
],
|
|
playerCardsFaceUp: [true, true, true],
|
|
bankerCardsFaceUp: [true, true, true],
|
|
playerValue: 9,
|
|
bankerValue: 8,
|
|
playerIsWinner: false,
|
|
bankerIsWinner: true,
|
|
isTie: false
|
|
)
|
|
}
|
|
}
|
|
|