Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
a470d8984c
commit
2c5b264f9b
@ -248,20 +248,92 @@ final class BlackjackEngine {
|
||||
|
||||
// MARK: - Basic Strategy Hint
|
||||
|
||||
/// Returns the basic strategy recommendation based on BJA chart.
|
||||
/// Returns the basic strategy recommendation based on standard casino strategy cards.
|
||||
/// Accounts for game settings (surrender, dealer hits soft 17, etc.)
|
||||
///
|
||||
/// Basic strategy is the mathematically optimal play for each hand combination.
|
||||
/// This implementation follows the standard multi-deck basic strategy chart.
|
||||
func getHint(playerHand: BlackjackHand, dealerUpCard: Card) -> String {
|
||||
let playerValue = playerHand.value
|
||||
let dealerValue = dealerUpCard.blackjackValue
|
||||
let dealerRank = dealerUpCard.rank
|
||||
let isSoft = playerHand.isSoft
|
||||
let canDouble = playerHand.cards.count == 2
|
||||
let surrenderAvailable = settings.lateSurrender
|
||||
let dealerHitsS17 = settings.dealerHitsSoft17
|
||||
|
||||
// SURRENDER (when available) - check first
|
||||
if surrenderAvailable && playerHand.cards.count == 2 {
|
||||
// 16 vs 9, 10, A - Surrender
|
||||
if playerValue == 16 && !isSoft && (dealerValue >= 9 || dealerValue == 1) {
|
||||
// Helper: Convert dealer rank to numeric value (Ace = 11 for comparison)
|
||||
let dealerValue: Int = {
|
||||
switch dealerRank {
|
||||
case .ace: return 11
|
||||
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
|
||||
}
|
||||
}()
|
||||
|
||||
// PAIRS - Check first (before surrender, since splitting Aces/8s is always correct)
|
||||
// This matches standard strategy cards: "Always split Aces and 8s"
|
||||
if playerHand.canSplit {
|
||||
let pairRank = playerHand.cards[0].rank
|
||||
switch pairRank {
|
||||
case .ace:
|
||||
// ALWAYS split Aces - this is one of the most important rules
|
||||
return String(localized: "Split")
|
||||
case .eight:
|
||||
// ALWAYS split 8s - two 8s (16) is the worst hand; two hands starting with 8 is better
|
||||
return String(localized: "Split")
|
||||
case .ten, .jack, .queen, .king:
|
||||
// NEVER split 10s - 20 is too strong to break up
|
||||
return String(localized: "Stand")
|
||||
case .five:
|
||||
// NEVER split 5s - treat as hard 10 and double when favorable
|
||||
if canDouble && dealerValue >= 2 && dealerValue <= 9 {
|
||||
return String(localized: "Double")
|
||||
}
|
||||
return String(localized: "Hit")
|
||||
case .four:
|
||||
// Split 4s only vs 5-6 when DAS allowed, otherwise hit
|
||||
if settings.doubleAfterSplit && (dealerValue == 5 || dealerValue == 6) {
|
||||
return String(localized: "Split")
|
||||
}
|
||||
return String(localized: "Hit")
|
||||
case .two, .three:
|
||||
// Split 2s/3s vs dealer 2-7
|
||||
if dealerValue >= 2 && dealerValue <= 7 {
|
||||
return String(localized: "Split")
|
||||
}
|
||||
return String(localized: "Hit")
|
||||
case .six:
|
||||
// Split 6s vs dealer 2-6
|
||||
if dealerValue >= 2 && dealerValue <= 6 {
|
||||
return String(localized: "Split")
|
||||
}
|
||||
return String(localized: "Hit")
|
||||
case .seven:
|
||||
// Split 7s vs dealer 2-7
|
||||
if dealerValue >= 2 && dealerValue <= 7 {
|
||||
return String(localized: "Split")
|
||||
}
|
||||
return String(localized: "Hit")
|
||||
case .nine:
|
||||
// Split 9s vs 2-6 and 8-9. Stand vs 7, 10, Ace
|
||||
if dealerValue == 7 || dealerValue == 10 || dealerRank == .ace {
|
||||
return String(localized: "Stand")
|
||||
}
|
||||
return String(localized: "Split")
|
||||
}
|
||||
}
|
||||
|
||||
// SURRENDER (when available) - check after pairs since splitting 8s beats surrendering 16
|
||||
if surrenderAvailable && playerHand.cards.count == 2 && !playerHand.isSplit {
|
||||
// 16 vs 9, 10, A - Surrender (but NOT a pair of 8s - those should split)
|
||||
if playerValue == 16 && !isSoft && (dealerValue >= 9 || dealerRank == .ace) {
|
||||
return String(localized: "Surrender")
|
||||
}
|
||||
// 15 vs 10 - Surrender
|
||||
@ -269,46 +341,11 @@ final class BlackjackEngine {
|
||||
return String(localized: "Surrender")
|
||||
}
|
||||
// 15 vs A - Surrender (if dealer hits soft 17)
|
||||
if playerValue == 15 && !isSoft && dealerValue == 1 && dealerHitsS17 {
|
||||
if playerValue == 15 && !isSoft && dealerRank == .ace && dealerHitsS17 {
|
||||
return String(localized: "Surrender")
|
||||
}
|
||||
}
|
||||
|
||||
// PAIRS
|
||||
if playerHand.canSplit {
|
||||
let pairRank = playerHand.cards[0].rank
|
||||
switch pairRank {
|
||||
case .ace:
|
||||
return String(localized: "Split")
|
||||
case .eight:
|
||||
return String(localized: "Split")
|
||||
case .ten, .jack, .queen, .king:
|
||||
return String(localized: "Stand")
|
||||
case .five:
|
||||
// Never split 5s - treat as hard 10
|
||||
return (canDouble && dealerValue <= 9) ? String(localized: "Double") : String(localized: "Hit")
|
||||
case .four:
|
||||
// Split 4s vs 5-6 (if DAS), otherwise hit
|
||||
return (settings.doubleAfterSplit && (dealerValue == 5 || dealerValue == 6))
|
||||
? String(localized: "Split") : String(localized: "Hit")
|
||||
case .two, .three:
|
||||
// Split 2s/3s vs 2-7
|
||||
return dealerValue >= 2 && dealerValue <= 7 ? String(localized: "Split") : String(localized: "Hit")
|
||||
case .six:
|
||||
// Split 6s vs 2-6
|
||||
return dealerValue >= 2 && dealerValue <= 6 ? String(localized: "Split") : String(localized: "Hit")
|
||||
case .seven:
|
||||
// Split 7s vs 2-7
|
||||
return dealerValue >= 2 && dealerValue <= 7 ? String(localized: "Split") : String(localized: "Hit")
|
||||
case .nine:
|
||||
// Split 9s vs 2-6, 8-9. Stand vs 7, 10, A
|
||||
if dealerValue == 7 || dealerValue == 10 || dealerValue == 1 {
|
||||
return String(localized: "Stand")
|
||||
}
|
||||
return String(localized: "Split")
|
||||
}
|
||||
}
|
||||
|
||||
// SOFT HANDS (Ace counted as 11)
|
||||
if isSoft {
|
||||
switch playerValue {
|
||||
@ -396,20 +433,45 @@ final class BlackjackEngine {
|
||||
|
||||
/// Returns the count-adjusted strategy recommendation with deviation explanation.
|
||||
/// Based on the "Illustrious 18" - the most valuable count-based deviations.
|
||||
///
|
||||
/// These deviations are used by card counters to improve on basic strategy
|
||||
/// when the true count indicates a significant advantage/disadvantage.
|
||||
func getCountAdjustedHint(playerHand: BlackjackHand, dealerUpCard: Card) -> String {
|
||||
let basicHint = getHint(playerHand: playerHand, dealerUpCard: dealerUpCard)
|
||||
let tc = Int(trueCount.rounded())
|
||||
let playerValue = playerHand.value
|
||||
let dealerValue = dealerUpCard.blackjackValue
|
||||
let dealerRank = dealerUpCard.rank
|
||||
let isSoft = playerHand.isSoft
|
||||
let canDouble = playerHand.cards.count == 2
|
||||
|
||||
// Helper: Convert dealer rank to numeric value (Ace = 11)
|
||||
let dealerValue: Int = {
|
||||
switch dealerRank {
|
||||
case .ace: return 11
|
||||
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
|
||||
}
|
||||
}()
|
||||
|
||||
// Helper to format true count with sign
|
||||
let tcDisplay = tc >= 0 ? "+\(tc)" : "\(tc)"
|
||||
|
||||
// Check for count-based deviations from basic strategy (Illustrious 18)
|
||||
// These are ordered by importance/frequency of occurrence
|
||||
|
||||
// Note: Pair deviations ONLY apply when player can actually split
|
||||
// Aces and 8s always split per basic strategy - no count deviation changes this
|
||||
|
||||
// 16 vs 10: Stand when TC ≥ 0 (basic strategy says Hit)
|
||||
if playerValue == 16 && !isSoft && dealerValue == 10 {
|
||||
// This is one of the most important deviations
|
||||
if playerValue == 16 && !isSoft && !playerHand.canSplit && dealerValue == 10 {
|
||||
if tc >= 0 {
|
||||
return String(localized: "Stand, not Hit (TC \(tcDisplay))")
|
||||
}
|
||||
@ -422,6 +484,9 @@ final class BlackjackEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// Insurance: Take when TC ≥ +3 (basic strategy says never take insurance)
|
||||
// Note: This is handled in the betting phase, not here
|
||||
|
||||
// 12 vs 2: Stand when TC ≥ +3 (basic strategy says Hit)
|
||||
if playerValue == 12 && !isSoft && dealerValue == 2 {
|
||||
if tc >= 3 {
|
||||
@ -451,41 +516,42 @@ final class BlackjackEngine {
|
||||
}
|
||||
|
||||
// 16 vs 9: Stand when TC ≥ +5 (basic strategy says Hit)
|
||||
if playerValue == 16 && !isSoft && dealerValue == 9 {
|
||||
if playerValue == 16 && !isSoft && !playerHand.canSplit && dealerValue == 9 {
|
||||
if tc >= 5 {
|
||||
return String(localized: "Stand, not Hit (TC \(tcDisplay))")
|
||||
}
|
||||
}
|
||||
|
||||
// 10 vs 10: Double when TC ≥ +4 (basic strategy says Hit)
|
||||
if playerValue == 10 && !isSoft && playerHand.cards.count == 2 && dealerValue == 10 {
|
||||
if playerValue == 10 && !isSoft && canDouble && dealerValue == 10 {
|
||||
if tc >= 4 {
|
||||
return String(localized: "Double, not Hit (TC \(tcDisplay))")
|
||||
}
|
||||
}
|
||||
|
||||
// 10 vs Ace: Double when TC ≥ +4 (basic strategy says Hit)
|
||||
if playerValue == 10 && !isSoft && playerHand.cards.count == 2 && dealerValue == 1 {
|
||||
if playerValue == 10 && !isSoft && canDouble && dealerRank == .ace {
|
||||
if tc >= 4 {
|
||||
return String(localized: "Double, not Hit (TC \(tcDisplay))")
|
||||
}
|
||||
}
|
||||
|
||||
// 9 vs 2: Double when TC ≥ +1 (basic strategy says Hit)
|
||||
if playerValue == 9 && !isSoft && playerHand.cards.count == 2 && dealerValue == 2 {
|
||||
if playerValue == 9 && !isSoft && canDouble && dealerValue == 2 {
|
||||
if tc >= 1 {
|
||||
return String(localized: "Double, not Hit (TC \(tcDisplay))")
|
||||
}
|
||||
}
|
||||
|
||||
// 9 vs 7: Double when TC ≥ +3 (basic strategy says Hit)
|
||||
if playerValue == 9 && !isSoft && playerHand.cards.count == 2 && dealerValue == 7 {
|
||||
if playerValue == 9 && !isSoft && canDouble && dealerValue == 7 {
|
||||
if tc >= 3 {
|
||||
return String(localized: "Double, not Hit (TC \(tcDisplay))")
|
||||
}
|
||||
}
|
||||
|
||||
// Pair of 10s vs 5: Split when TC ≥ +5 (basic strategy says Stand)
|
||||
// This is an advanced play - only for high counts
|
||||
if playerHand.canSplit && playerHand.cards[0].blackjackValue == 10 && dealerValue == 5 {
|
||||
if tc >= 5 {
|
||||
return String(localized: "Split, not Stand (TC \(tcDisplay))")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user