CasinoGames/Blackjack/Blackjack/Views/Table/DealerHandView.swift

175 lines
5.7 KiB
Swift

//
// DealerHandView.swift
// Blackjack
//
// Displays the dealer's hand with label, value badge, and cards.
//
import SwiftUI
import CasinoKit
struct DealerHandView: View {
let hand: BlackjackHand
let showHoleCard: Bool
let showCardCount: Bool
let showAnimations: Bool
let dealingSpeed: Double
let cardWidth: CGFloat
let cardSpacing: CGFloat
/// Number of cards that have completed their animation
let visibleCardCount: Int
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
/// The value text to display in the badge (based on visible cards and hole card state).
/// Shows soft values like "7/17" for consistency with player hand display.
private var displayValueText: String? {
guard !hand.cards.isEmpty && visibleCardCount > 0 else { return nil }
if showHoleCard {
// Hole card revealed - calculate value from visible cards
let visibleCards = Array(hand.cards.prefix(visibleCardCount))
let (hardValue, softValue) = BlackjackHand.calculateValues(for: visibleCards)
let hasSoftAce = BlackjackHand.hasSoftAce(for: visibleCards)
// When soft value is 21, there's no ambiguity - just show 21
if softValue == 21 {
return "21"
} else if hasSoftAce {
return "\(hardValue)/\(softValue)"
} else {
return "\(BlackjackHand.bestValue(for: visibleCards))"
}
} else {
// Hole card hidden - show only the first (face-up) card's value
return "\(hand.cards[0].blackjackValue)"
}
}
var body: some View {
VStack(spacing: Design.Spacing.small) {
// Label and value badge
HandLabelView(
title: String(localized: "DEALER"),
valueText: displayValueText,
badgeColor: Color.Hand.dealer
)
.animation(nil, value: visibleCardCount)
.animation(nil, value: showHoleCard)
// Cards with result badge overlay
CardStackView.dealer(
cards: hand.cards,
showHoleCard: showHoleCard,
cardWidth: cardWidth,
cardSpacing: cardSpacing,
showAnimations: showAnimations,
dealingSpeed: dealingSpeed,
showCardCount: showCardCount
)
.overlay {
// Result badge - centered on cards
if let result = hand.cards.count >= 2 && showHoleCard ? handResultText : nil {
Text(result)
.font(.system(size: labelFontSize, weight: .black))
.foregroundStyle(.white)
.padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, Design.Spacing.xSmall)
.background(
Capsule()
.fill(handResultColor)
.shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusMedium)
)
.transition(.scale.combined(with: .opacity))
}
}
}
.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")")
}
}
// MARK: - Previews
#Preview("Empty Hand") {
ZStack {
Color.Table.felt.ignoresSafeArea()
DealerHandView(
hand: BlackjackHand(),
showHoleCard: false,
showCardCount: false,
showAnimations: true,
dealingSpeed: 1.0,
cardWidth: 60,
cardSpacing: -20,
visibleCardCount: 0
)
}
}
#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,
showAnimations: true,
dealingSpeed: 1.0,
cardWidth: 60,
cardSpacing: -20,
visibleCardCount: 2
)
}
}
#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,
showAnimations: true,
dealingSpeed: 1.0,
cardWidth: 60,
cardSpacing: -20,
visibleCardCount: 2
)
}
}