175 lines
5.7 KiB
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
|
|
)
|
|
}
|
|
}
|