CasinoGames/Blackjack/Models/Hand.swift

161 lines
4.4 KiB
Swift

//
// Hand.swift
// Blackjack
//
// Represents a Blackjack hand with value calculation.
//
import Foundation
import CasinoKit
/// A hand of cards in Blackjack.
struct BlackjackHand: Identifiable, Equatable {
let id = UUID()
var cards: [Card]
var bet: Int
var isDoubledDown: Bool = false
var isSplit: Bool = false
var isStanding: Bool = false
var result: HandResult?
init(cards: [Card] = [], bet: Int = 0) {
self.cards = cards
self.bet = bet
}
/// The best possible value (highest without busting, or lowest if busted).
var value: Int {
let (hard, soft) = calculateValues()
if soft <= 21 {
return soft
}
return hard
}
/// Whether this hand has a soft value (usable ace).
var isSoft: Bool {
let (hard, soft) = calculateValues()
return soft <= 21 && soft != hard
}
/// Whether the hand is over 21.
var isBusted: Bool {
value > 21
}
/// Whether this is a natural blackjack (two cards totaling 21).
var isBlackjack: Bool {
cards.count == 2 && value == 21 && !isSplit
}
/// Whether this hand has a splittable pair (two cards of same rank).
/// Note: Additional conditions (balance, max splits, resplit aces) are checked by the engine.
var canSplit: Bool {
cards.count == 2 && cards[0].rank == cards[1].rank
}
/// Whether this hand has the card count to double down.
/// Note: Additional conditions (balance, DAS rule) are checked by the engine.
var canDoubleDown: Bool {
cards.count == 2 && !isDoubledDown
}
/// Whether this hand can hit.
/// Note: Standard Blackjack has NO card limit - you can hit until you bust or stand.
var canHit: Bool {
!isBusted && !isStanding && !isBlackjack
}
/// Calculates both hard and soft values.
private func calculateValues() -> (hard: Int, soft: Int) {
var hardValue = 0
var aceCount = 0
for card in cards {
switch card.rank {
case .ace:
hardValue += 1
aceCount += 1
case .two: hardValue += 2
case .three: hardValue += 3
case .four: hardValue += 4
case .five: hardValue += 5
case .six: hardValue += 6
case .seven: hardValue += 7
case .eight: hardValue += 8
case .nine: hardValue += 9
case .ten, .jack, .queen, .king:
hardValue += 10
}
}
// Calculate soft value (one ace as 11)
var softValue = hardValue
if aceCount > 0 && hardValue + 10 <= 21 {
softValue = hardValue + 10
}
return (hardValue, softValue)
}
/// Display string for the hand value.
var valueDisplay: String {
if isBlackjack {
return "BJ"
}
let (hard, soft) = calculateValues()
if isBusted {
return "\(hard) 💥"
}
if isSoft && soft != hard {
return "\(hard)/\(soft)"
}
return "\(value)"
}
}
// MARK: - Card Value Extension
extension Card {
/// The blackjack value of this card (Ace = 1 or 11, face cards = 10).
var blackjackValue: Int {
switch rank {
case .ace: return 1 // Or 11, handled by hand calculation
case .two: return 2
case .three: return 3
case .four: return 4
case .five: return 5
case .six: return 6
case .seven: return 7
case .eight: return 8
case .nine: return 9
case .ten, .jack, .queen, .king: return 10
}
}
/// The Hi-Lo card counting value.
/// Low cards (2-6): +1 (good for player when removed)
/// Neutral (7-9): 0
/// High cards (10-A): -1 (bad for player when removed)
var hiLoValue: Int {
switch rank {
case .two, .three, .four, .five, .six:
return 1 // Low cards
case .seven, .eight, .nine:
return 0 // Neutral
case .ten, .jack, .queen, .king, .ace:
return -1 // High cards
}
}
/// Display text for the Hi-Lo count value.
var hiLoDisplayText: String {
switch hiLoValue {
case 1: return "+1"
case -1: return "-1"
default: return "0"
}
}
}