CasinoGames/Blackjack/Views/Table/DealerHandView.swift

158 lines
5.3 KiB
Swift

//
// DealerHandView.swift
// Blackjack
//
// Displays the dealer's hand with cards and value.
//
import SwiftUI
import CasinoKit
struct DealerHandView: View {
let hand: BlackjackHand
let showHoleCard: Bool
let showCardCount: Bool
let cardWidth: CGFloat
let cardSpacing: CGFloat
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
var body: some View {
VStack(spacing: Design.Spacing.small) {
// Label and value
HStack(spacing: Design.Spacing.small) {
Text(String(localized: "DEALER"))
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
.foregroundStyle(.white)
// Show value: always show if hole card visible, or show single card value in European mode
if !hand.cards.isEmpty {
if showHoleCard {
ValueBadge(value: hand.value, color: Color.Hand.dealer)
} else if hand.cards.count == 1 {
// European mode: show single visible card value
ValueBadge(value: hand.cards[0].blackjackValue, color: Color.Hand.dealer)
}
}
}
// Cards
HStack(spacing: hand.cards.isEmpty ? Design.Spacing.small : cardSpacing) {
if hand.cards.isEmpty {
CardPlaceholderView(width: cardWidth)
CardPlaceholderView(width: cardWidth)
} else {
ForEach(hand.cards.indices, id: \.self) { index in
let isFaceUp = index == 0 || showHoleCard
CardView(
card: hand.cards[index],
isFaceUp: isFaceUp,
cardWidth: cardWidth
)
.overlay(alignment: .bottomLeading) {
if showCardCount && isFaceUp {
HiLoCountBadge(card: hand.cards[index])
}
}
.zIndex(Double(index))
}
// Show placeholder for second card in European mode (no hole card)
if hand.cards.count == 1 && !showHoleCard {
CardPlaceholderView(width: cardWidth)
.opacity(Design.Opacity.medium)
}
}
}
// Result badge
if let result = hand.cards.count >= 2 && showHoleCard ? handResultText : nil {
Text(result)
.font(.system(size: labelFontSize, weight: .black))
.foregroundStyle(handResultColor)
.padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, Design.Spacing.xSmall)
.background(
Capsule()
.fill(handResultColor.opacity(Design.Opacity.hint))
)
}
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(dealerAccessibilityLabel)
}
// MARK: - Computed Properties
private var handResultText: String? {
if hand.isBlackjack {
return String(localized: "BLACKJACK")
}
if hand.isBusted {
return String(localized: "BUST")
}
return nil
}
private var handResultColor: Color {
if hand.isBlackjack { return .yellow }
if hand.isBusted { return .green } // Good for player
return .white
}
private var dealerAccessibilityLabel: String {
if hand.cards.isEmpty {
return String(localized: "Dealer: No cards")
}
let visibleCards = showHoleCard ? hand.cards : [hand.cards[0]]
let cardsDescription = visibleCards.map { $0.accessibilityDescription }.joined(separator: ", ")
return String(localized: "Dealer: \(cardsDescription). Value: \(showHoleCard ? String(hand.value) : "hidden")")
}
}
#Preview("Empty Hand") {
ZStack {
Color.Table.felt.ignoresSafeArea()
DealerHandView(
hand: BlackjackHand(),
showHoleCard: false,
showCardCount: false,
cardWidth: 60,
cardSpacing: -20
)
}
}
#Preview("Two Cards - Hole Hidden") {
ZStack {
Color.Table.felt.ignoresSafeArea()
DealerHandView(
hand: BlackjackHand(cards: [
Card(suit: .spades, rank: .ace),
Card(suit: .hearts, rank: .king)
]),
showHoleCard: false,
showCardCount: false,
cardWidth: 60,
cardSpacing: -20
)
}
}
#Preview("Blackjack - Revealed") {
ZStack {
Color.Table.felt.ignoresSafeArea()
DealerHandView(
hand: BlackjackHand(cards: [
Card(suit: .spades, rank: .ace),
Card(suit: .hearts, rank: .king)
]),
showHoleCard: true,
showCardCount: true,
cardWidth: 60,
cardSpacing: -20
)
}
}