149 lines
5.3 KiB
Swift
149 lines
5.3 KiB
Swift
//
|
|
// PlayerHandsContainer.swift
|
|
// Blackjack
|
|
//
|
|
// Scrollable container for player hands (supports split hands).
|
|
//
|
|
|
|
import SwiftUI
|
|
import CasinoKit
|
|
|
|
/// Horizontally scrollable container that displays one or more player hands.
|
|
/// Handles split hands by showing them side-by-side with auto-scrolling to the active hand.
|
|
struct PlayerHandsContainer: View {
|
|
let hands: [BlackjackHand]
|
|
let activeHandIndex: Int
|
|
let isPlayerTurn: Bool
|
|
let showCardCount: Bool
|
|
let showAnimations: Bool
|
|
let dealingSpeed: Double
|
|
let cardWidth: CGFloat
|
|
let cardSpacing: CGFloat
|
|
|
|
/// Number of visible cards for each hand (completed animations)
|
|
let visibleCardCounts: [Int]
|
|
|
|
/// Current hint to display (shown on active hand only).
|
|
let currentHint: String?
|
|
|
|
/// Whether the hint toast should be visible.
|
|
let showHintToast: Bool
|
|
|
|
/// Total card count across all hands - used to trigger scroll when hitting
|
|
private var totalCardCount: Int {
|
|
hands.reduce(0) { $0 + $1.cards.count }
|
|
}
|
|
|
|
var body: some View {
|
|
ScrollViewReader { proxy in
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
HStack(spacing: Design.Spacing.large) {
|
|
// Display hands in reverse order (right to left play order)
|
|
// Visual order: Hand 3, Hand 2, Hand 1 (left to right)
|
|
// Play order: Hand 1 played first (rightmost), then Hand 2, etc.
|
|
ForEach(Array(hands.enumerated()).reversed(), id: \.element.id) { index, hand in
|
|
let isActiveHand = index == activeHandIndex && isPlayerTurn
|
|
let visibleCount = index < visibleCardCounts.count ? visibleCardCounts[index] : 0
|
|
PlayerHandView(
|
|
hand: hand,
|
|
isActive: isActiveHand,
|
|
showCardCount: showCardCount,
|
|
showAnimations: showAnimations,
|
|
dealingSpeed: dealingSpeed,
|
|
// Hand numbers: rightmost (index 0) is Hand 1, played first
|
|
handNumber: hands.count > 1 ? index + 1 : nil,
|
|
cardWidth: cardWidth,
|
|
cardSpacing: cardSpacing,
|
|
visibleCardCount: visibleCount,
|
|
// Only show hint on the active hand
|
|
currentHint: isActiveHand ? currentHint : nil,
|
|
showHintToast: isActiveHand && showHintToast
|
|
)
|
|
.id(hand.id)
|
|
.transition(.scale.combined(with: .opacity))
|
|
}
|
|
}
|
|
.animation(.spring(duration: Design.Animation.springDuration), value: hands.count)
|
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
|
}
|
|
.scrollClipDisabled()
|
|
.scrollBounceBehavior(.always)
|
|
.defaultScrollAnchor(.center)
|
|
.onChange(of: activeHandIndex) { _, _ in
|
|
scrollToActiveHand(proxy: proxy)
|
|
}
|
|
.onChange(of: totalCardCount) { _, _ in
|
|
scrollToActiveHand(proxy: proxy)
|
|
}
|
|
.onChange(of: hands.count) { _, _ in
|
|
scrollToActiveHand(proxy: proxy)
|
|
}
|
|
.onAppear {
|
|
scrollToActiveHand(proxy: proxy)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
|
|
private func scrollToActiveHand(proxy: ScrollViewProxy) {
|
|
guard activeHandIndex < hands.count else { return }
|
|
let activeHandId = hands[activeHandIndex].id
|
|
withAnimation(.easeInOut(duration: Design.Animation.quick)) {
|
|
proxy.scrollTo(activeHandId, anchor: .center)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
#Preview("Single Hand") {
|
|
ZStack {
|
|
Color.Table.felt.ignoresSafeArea()
|
|
PlayerHandsContainer(
|
|
hands: [BlackjackHand(cards: [
|
|
Card(suit: .hearts, rank: .ace),
|
|
Card(suit: .spades, rank: .king)
|
|
], bet: 100)],
|
|
activeHandIndex: 0,
|
|
isPlayerTurn: true,
|
|
showCardCount: false,
|
|
showAnimations: true,
|
|
dealingSpeed: 1.0,
|
|
cardWidth: 60,
|
|
cardSpacing: -20,
|
|
visibleCardCounts: [2],
|
|
currentHint: "Stand",
|
|
showHintToast: true
|
|
)
|
|
}
|
|
}
|
|
|
|
#Preview("Split Hands") {
|
|
ZStack {
|
|
Color.Table.felt.ignoresSafeArea()
|
|
PlayerHandsContainer(
|
|
hands: [
|
|
BlackjackHand(cards: [
|
|
Card(suit: .clubs, rank: .eight),
|
|
Card(suit: .spades, rank: .jack)
|
|
], bet: 100),
|
|
BlackjackHand(cards: [
|
|
Card(suit: .hearts, rank: .eight),
|
|
Card(suit: .diamonds, rank: .five)
|
|
], bet: 100)
|
|
],
|
|
activeHandIndex: 1,
|
|
isPlayerTurn: true,
|
|
showCardCount: true,
|
|
showAnimations: true,
|
|
dealingSpeed: 1.0,
|
|
cardWidth: 60,
|
|
cardSpacing: -20,
|
|
visibleCardCounts: [2, 2],
|
|
currentHint: "Hit",
|
|
showHintToast: true
|
|
)
|
|
}
|
|
}
|
|
|