Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
a0ac5a6e64
commit
7e16e67826
@ -411,7 +411,7 @@
|
|||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -443,7 +443,7 @@
|
|||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|||||||
@ -403,96 +403,99 @@ final class BlackjackEngine {
|
|||||||
let dealerValue = dealerUpCard.blackjackValue
|
let dealerValue = dealerUpCard.blackjackValue
|
||||||
let isSoft = playerHand.isSoft
|
let isSoft = playerHand.isSoft
|
||||||
|
|
||||||
// Check for count-based deviations from basic strategy
|
// Helper to format true count with sign
|
||||||
|
let tcDisplay = tc >= 0 ? "+\(tc)" : "\(tc)"
|
||||||
|
|
||||||
// 16 vs 10: Stand at TC 0+ (basic says Hit)
|
// Check for count-based deviations from basic strategy (Illustrious 18)
|
||||||
|
|
||||||
|
// 16 vs 10: Stand when TC ≥ 0 (basic strategy says Hit)
|
||||||
if playerValue == 16 && !isSoft && dealerValue == 10 {
|
if playerValue == 16 && !isSoft && dealerValue == 10 {
|
||||||
if tc >= 0 {
|
if tc >= 0 {
|
||||||
return String(localized: "Stand (Count: 16v10 at TC≥0)")
|
return String(localized: "Stand instead of Hit (TC \(tcDisplay), deck is neutral/rich)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 15 vs 10: Stand at TC +4+ (basic says Hit)
|
// 15 vs 10: Stand when TC ≥ +4 (basic strategy says Hit/Surrender)
|
||||||
if playerValue == 15 && !isSoft && dealerValue == 10 {
|
if playerValue == 15 && !isSoft && dealerValue == 10 {
|
||||||
if tc >= 4 {
|
if tc >= 4 {
|
||||||
return String(localized: "Stand (Count: 15v10 at TC≥+4)")
|
return String(localized: "Stand instead of Hit (TC \(tcDisplay), deck is very rich)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12 vs 2: Stand at TC +3+ (basic says Hit)
|
// 12 vs 2: Stand when TC ≥ +3 (basic strategy says Hit)
|
||||||
if playerValue == 12 && !isSoft && dealerValue == 2 {
|
if playerValue == 12 && !isSoft && dealerValue == 2 {
|
||||||
if tc >= 3 {
|
if tc >= 3 {
|
||||||
return String(localized: "Stand (Count: 12v2 at TC≥+3)")
|
return String(localized: "Stand instead of Hit (TC \(tcDisplay), dealer likely to bust)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12 vs 3: Stand at TC +2+ (basic says Hit)
|
// 12 vs 3: Stand when TC ≥ +2 (basic strategy says Hit)
|
||||||
if playerValue == 12 && !isSoft && dealerValue == 3 {
|
if playerValue == 12 && !isSoft && dealerValue == 3 {
|
||||||
if tc >= 2 {
|
if tc >= 2 {
|
||||||
return String(localized: "Stand (Count: 12v3 at TC≥+2)")
|
return String(localized: "Stand instead of Hit (TC \(tcDisplay), dealer likely to bust)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12 vs 4: Hit at TC < 0 (basic says Stand)
|
// 12 vs 4: Hit when TC < 0 (basic strategy says Stand)
|
||||||
if playerValue == 12 && !isSoft && dealerValue == 4 {
|
if playerValue == 12 && !isSoft && dealerValue == 4 {
|
||||||
if tc < 0 {
|
if tc < 0 {
|
||||||
return String(localized: "Hit (Count: 12v4 at TC<0)")
|
return String(localized: "Hit instead of Stand (TC \(tcDisplay), deck is poor)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 13 vs 2: Hit at TC < -1 (basic says Stand)
|
// 13 vs 2: Hit when TC < -1 (basic strategy says Stand)
|
||||||
if playerValue == 13 && !isSoft && dealerValue == 2 {
|
if playerValue == 13 && !isSoft && dealerValue == 2 {
|
||||||
if tc < -1 {
|
if tc < -1 {
|
||||||
return String(localized: "Hit (Count: 13v2 at TC<-1)")
|
return String(localized: "Hit instead of Stand (TC \(tcDisplay), deck is very poor)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16 vs 9: Stand at TC +5+ (basic says Hit)
|
// 16 vs 9: Stand when TC ≥ +5 (basic strategy says Hit)
|
||||||
if playerValue == 16 && !isSoft && dealerValue == 9 {
|
if playerValue == 16 && !isSoft && dealerValue == 9 {
|
||||||
if tc >= 5 {
|
if tc >= 5 {
|
||||||
return String(localized: "Stand (Count: 16v9 at TC≥+5)")
|
return String(localized: "Stand instead of Hit (TC \(tcDisplay), deck is extremely rich)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10 vs 10: Double at TC +4+ (basic says Hit)
|
// 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 && playerHand.cards.count == 2 && dealerValue == 10 {
|
||||||
if tc >= 4 {
|
if tc >= 4 {
|
||||||
return String(localized: "Double (Count: 10v10 at TC≥+4)")
|
return String(localized: "Double instead of Hit (TC \(tcDisplay), high cards favor you)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10 vs A: Double at TC +4+ (basic says Hit)
|
// 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 && playerHand.cards.count == 2 && dealerValue == 1 {
|
||||||
if tc >= 4 {
|
if tc >= 4 {
|
||||||
return String(localized: "Double (Count: 10vA at TC≥+4)")
|
return String(localized: "Double instead of Hit (TC \(tcDisplay), high cards favor you)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9 vs 2: Double at TC +1+ (basic says Hit)
|
// 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 && playerHand.cards.count == 2 && dealerValue == 2 {
|
||||||
if tc >= 1 {
|
if tc >= 1 {
|
||||||
return String(localized: "Double (Count: 9v2 at TC≥+1)")
|
return String(localized: "Double instead of Hit (TC \(tcDisplay), slight edge to double)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9 vs 7: Double at TC +3+ (basic says Hit)
|
// 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 && playerHand.cards.count == 2 && dealerValue == 7 {
|
||||||
if tc >= 3 {
|
if tc >= 3 {
|
||||||
return String(localized: "Double (Count: 9v7 at TC≥+3)")
|
return String(localized: "Double instead of Hit (TC \(tcDisplay), deck favors doubling)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pair of 10s vs 5: Split at TC +5+ (basic says Stand)
|
// Pair of 10s vs 5: Split when TC ≥ +5 (basic strategy says Stand)
|
||||||
if playerHand.canSplit && playerHand.cards[0].blackjackValue == 10 && dealerValue == 5 {
|
if playerHand.canSplit && playerHand.cards[0].blackjackValue == 10 && dealerValue == 5 {
|
||||||
if tc >= 5 {
|
if tc >= 5 {
|
||||||
return String(localized: "Split (Count: 10,10v5 at TC≥+5)")
|
return String(localized: "Split instead of Stand (TC \(tcDisplay), dealer very likely to bust)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pair of 10s vs 6: Split at TC +4+ (basic says Stand)
|
// Pair of 10s vs 6: Split when TC ≥ +4 (basic strategy says Stand)
|
||||||
if playerHand.canSplit && playerHand.cards[0].blackjackValue == 10 && dealerValue == 6 {
|
if playerHand.canSplit && playerHand.cards[0].blackjackValue == 10 && dealerValue == 6 {
|
||||||
if tc >= 4 {
|
if tc >= 4 {
|
||||||
return String(localized: "Split (Count: 10,10v6 at TC≥+4)")
|
return String(localized: "Split instead of Stand (TC \(tcDisplay), dealer very likely to bust)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -661,6 +661,7 @@ final class GameState {
|
|||||||
var roundWinnings = 0
|
var roundWinnings = 0
|
||||||
var wasBlackjack = false
|
var wasBlackjack = false
|
||||||
var hadBust = false
|
var hadBust = false
|
||||||
|
var perHandWinnings: [Int] = []
|
||||||
|
|
||||||
// Evaluate each hand
|
// Evaluate each hand
|
||||||
for i in 0..<playerHands.count {
|
for i in 0..<playerHands.count {
|
||||||
@ -677,8 +678,12 @@ final class GameState {
|
|||||||
result: result,
|
result: result,
|
||||||
isDoubled: playerHands[i].isDoubledDown
|
isDoubled: playerHands[i].isDoubledDown
|
||||||
)
|
)
|
||||||
|
let totalBet = playerHands[i].bet * (playerHands[i].isDoubledDown ? 2 : 1)
|
||||||
|
let handWinnings = payout - totalBet
|
||||||
|
|
||||||
balance += payout
|
balance += payout
|
||||||
roundWinnings += payout - playerHands[i].bet * (playerHands[i].isDoubledDown ? 2 : 1)
|
roundWinnings += handWinnings
|
||||||
|
perHandWinnings.append(handWinnings)
|
||||||
|
|
||||||
if result == .blackjack {
|
if result == .blackjack {
|
||||||
wasBlackjack = true
|
wasBlackjack = true
|
||||||
@ -686,6 +691,24 @@ final class GameState {
|
|||||||
if result == .bust {
|
if result == .bust {
|
||||||
hadBust = true
|
hadBust = true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
perHandWinnings.append(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate insurance result
|
||||||
|
var insResult: HandResult? = nil
|
||||||
|
var insWinnings = 0
|
||||||
|
if insuranceBet > 0 {
|
||||||
|
if dealerHand.isBlackjack {
|
||||||
|
insResult = .insuranceWin
|
||||||
|
insWinnings = insuranceBet * 2 // Insurance pays 2:1
|
||||||
|
balance += insuranceBet * 3 // Return bet + 2x winnings
|
||||||
|
roundWinnings += insWinnings
|
||||||
|
} else {
|
||||||
|
insResult = .insuranceLose
|
||||||
|
insWinnings = -insuranceBet // Lost insurance bet
|
||||||
|
roundWinnings += insWinnings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -704,11 +727,13 @@ final class GameState {
|
|||||||
bustCount += 1
|
bustCount += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create round result with all hand results
|
// Create round result with all hand results and per-hand winnings
|
||||||
let allHandResults = playerHands.map { $0.result ?? .lose }
|
let allHandResults = playerHands.map { $0.result ?? .lose }
|
||||||
lastRoundResult = RoundResult(
|
lastRoundResult = RoundResult(
|
||||||
handResults: allHandResults,
|
handResults: allHandResults,
|
||||||
insuranceResult: insuranceBet > 0 ? (dealerHand.isBlackjack ? .insuranceWin : .insuranceLose) : nil,
|
handWinnings: perHandWinnings,
|
||||||
|
insuranceResult: insResult,
|
||||||
|
insuranceWinnings: insWinnings,
|
||||||
totalWinnings: roundWinnings,
|
totalWinnings: roundWinnings,
|
||||||
wasBlackjack: wasBlackjack
|
wasBlackjack: wasBlackjack
|
||||||
)
|
)
|
||||||
|
|||||||
@ -68,10 +68,34 @@ enum HandResult: Equatable {
|
|||||||
struct RoundResult: Equatable {
|
struct RoundResult: Equatable {
|
||||||
/// Results for all player hands (index 0 = Hand 1, index 1 = Hand 2, etc.)
|
/// Results for all player hands (index 0 = Hand 1, index 1 = Hand 2, etc.)
|
||||||
let handResults: [HandResult]
|
let handResults: [HandResult]
|
||||||
|
/// Net winnings for each hand (parallel to handResults)
|
||||||
|
let handWinnings: [Int]
|
||||||
let insuranceResult: HandResult?
|
let insuranceResult: HandResult?
|
||||||
|
/// Insurance winnings (positive if won, negative if lost)
|
||||||
|
let insuranceWinnings: Int
|
||||||
let totalWinnings: Int
|
let totalWinnings: Int
|
||||||
let wasBlackjack: Bool
|
let wasBlackjack: Bool
|
||||||
|
|
||||||
|
/// Convenience initializer without per-hand winnings (backwards compatibility)
|
||||||
|
init(handResults: [HandResult], insuranceResult: HandResult?, totalWinnings: Int, wasBlackjack: Bool) {
|
||||||
|
self.handResults = handResults
|
||||||
|
self.handWinnings = [] // Empty means don't show per-hand amounts
|
||||||
|
self.insuranceResult = insuranceResult
|
||||||
|
self.insuranceWinnings = 0
|
||||||
|
self.totalWinnings = totalWinnings
|
||||||
|
self.wasBlackjack = wasBlackjack
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full initializer with per-hand winnings
|
||||||
|
init(handResults: [HandResult], handWinnings: [Int], insuranceResult: HandResult?, insuranceWinnings: Int, totalWinnings: Int, wasBlackjack: Bool) {
|
||||||
|
self.handResults = handResults
|
||||||
|
self.handWinnings = handWinnings
|
||||||
|
self.insuranceResult = insuranceResult
|
||||||
|
self.insuranceWinnings = insuranceWinnings
|
||||||
|
self.totalWinnings = totalWinnings
|
||||||
|
self.wasBlackjack = wasBlackjack
|
||||||
|
}
|
||||||
|
|
||||||
/// The main/best result for display purposes (first hand, or best if split)
|
/// The main/best result for display purposes (first hand, or best if split)
|
||||||
var mainHandResult: HandResult {
|
var mainHandResult: HandResult {
|
||||||
// Return the best result for the headline
|
// Return the best result for the headline
|
||||||
|
|||||||
@ -36,12 +36,16 @@ enum Design {
|
|||||||
|
|
||||||
enum Size {
|
enum Size {
|
||||||
// Hand scaling factor (1.5 = 50% larger hands)
|
// Hand scaling factor (1.5 = 50% larger hands)
|
||||||
static let handScale: CGFloat = 1.5
|
static let handScale: CGFloat = 1.75
|
||||||
|
|
||||||
// Cards - scaled for better visibility
|
// Cards - scaled for better visibility
|
||||||
static let cardWidth: CGFloat = 60 * handScale // 90pt at 1.5x
|
static let cardWidth: CGFloat = 60 * handScale // 90pt at 1.5x
|
||||||
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
|
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
|
||||||
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap * handScale // Scaled overlap
|
|
||||||
|
/// Card overlap (negative = cards stack left over right).
|
||||||
|
/// More negative = more overlap (less card visible).
|
||||||
|
/// With 90pt cards: -40 = ~44% overlap, -50 = ~55% overlap
|
||||||
|
static let cardOverlap: CGFloat = -50
|
||||||
|
|
||||||
// Player hands container height (accommodates larger cards + labels)
|
// Player hands container height (accommodates larger cards + labels)
|
||||||
// Reduced from 180 to fit content more snugly
|
// Reduced from 180 to fit content more snugly
|
||||||
|
|||||||
@ -72,10 +72,19 @@ struct GameTableView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func mainGameView(state: GameState) -> some View {
|
private func mainGameView(state: GameState) -> some View {
|
||||||
ZStack {
|
GeometryReader { geometry in
|
||||||
// Background
|
ZStack {
|
||||||
TableBackgroundView()
|
// Background
|
||||||
|
TableBackgroundView()
|
||||||
|
|
||||||
|
mainContent(state: state, screenHeight: geometry.size.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func mainContent(state: GameState, screenHeight: CGFloat) -> some View {
|
||||||
|
ZStack {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Top bar
|
// Top bar
|
||||||
TopBarView(
|
TopBarView(
|
||||||
@ -110,7 +119,8 @@ struct GameTableView: View {
|
|||||||
// Table layout - fills available space
|
// Table layout - fills available space
|
||||||
BlackjackTableView(
|
BlackjackTableView(
|
||||||
state: state,
|
state: state,
|
||||||
onPlaceBet: { placeBet(state: state) }
|
onPlaceBet: { placeBet(state: state) },
|
||||||
|
screenHeight: screenHeight
|
||||||
)
|
)
|
||||||
.frame(maxWidth: maxContentWidth)
|
.frame(maxWidth: maxContentWidth)
|
||||||
|
|
||||||
|
|||||||
@ -68,19 +68,31 @@ struct ResultBannerView: View {
|
|||||||
.font(.system(size: amountFontSize, weight: .bold, design: .rounded))
|
.font(.system(size: amountFontSize, weight: .bold, design: .rounded))
|
||||||
.foregroundStyle(winningsColor)
|
.foregroundStyle(winningsColor)
|
||||||
|
|
||||||
// Breakdown - all hands
|
// Breakdown - all hands with amounts for splits
|
||||||
VStack(spacing: Design.Spacing.small) {
|
VStack(spacing: Design.Spacing.small) {
|
||||||
ForEach(result.handResults.indices, id: \.self) { index in
|
ForEach(result.handResults.indices, id: \.self) { index in
|
||||||
let handResult = result.handResults[index]
|
let handResult = result.handResults[index]
|
||||||
|
let handWinnings = index < result.handWinnings.count ? result.handWinnings[index] : nil
|
||||||
// Hand numbering: index 0 = Hand 1 (played first, displayed rightmost)
|
// Hand numbering: index 0 = Hand 1 (played first, displayed rightmost)
|
||||||
let handLabel = result.handResults.count > 1
|
let handLabel = result.handResults.count > 1
|
||||||
? String(localized: "Hand \(index + 1)")
|
? String(localized: "Hand \(index + 1)")
|
||||||
: String(localized: "Main Hand")
|
: String(localized: "Main Hand")
|
||||||
ResultRow(label: handLabel, result: handResult)
|
// Show amounts for split hands, or for single hand if there are winnings
|
||||||
|
let showAmount = result.hadSplit && handWinnings != nil
|
||||||
|
ResultRow(
|
||||||
|
label: handLabel,
|
||||||
|
result: handResult,
|
||||||
|
amount: showAmount ? handWinnings : nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let insuranceResult = result.insuranceResult {
|
if let insuranceResult = result.insuranceResult {
|
||||||
ResultRow(label: String(localized: "Insurance"), result: insuranceResult)
|
let showInsAmount = result.insuranceWinnings != 0
|
||||||
|
ResultRow(
|
||||||
|
label: String(localized: "Insurance"),
|
||||||
|
result: insuranceResult,
|
||||||
|
amount: showInsAmount ? result.insuranceWinnings : nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.medium)
|
.padding(Design.Spacing.medium)
|
||||||
@ -189,6 +201,25 @@ struct ResultBannerView: View {
|
|||||||
struct ResultRow: View {
|
struct ResultRow: View {
|
||||||
let label: String
|
let label: String
|
||||||
let result: HandResult
|
let result: HandResult
|
||||||
|
var amount: Int? = nil
|
||||||
|
|
||||||
|
private var amountText: String? {
|
||||||
|
guard let amount = amount else { return nil }
|
||||||
|
if amount > 0 {
|
||||||
|
return "+$\(amount)"
|
||||||
|
} else if amount < 0 {
|
||||||
|
return "-$\(abs(amount))"
|
||||||
|
} else {
|
||||||
|
return "$0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var amountColor: Color {
|
||||||
|
guard let amount = amount else { return .white }
|
||||||
|
if amount > 0 { return .green }
|
||||||
|
if amount < 0 { return .red }
|
||||||
|
return .blue
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
@ -198,9 +229,18 @@ struct ResultRow: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
// Show amount if provided
|
||||||
|
if let amountText = amountText {
|
||||||
|
Text(amountText)
|
||||||
|
.font(.system(size: Design.BaseFontSize.body, weight: .semibold, design: .rounded))
|
||||||
|
.foregroundStyle(amountColor)
|
||||||
|
.frame(width: 70, alignment: .trailing)
|
||||||
|
}
|
||||||
|
|
||||||
Text(result.displayText)
|
Text(result.displayText)
|
||||||
.font(.system(size: Design.BaseFontSize.body, weight: .bold))
|
.font(.system(size: Design.BaseFontSize.body, weight: .bold))
|
||||||
.foregroundStyle(result.color)
|
.foregroundStyle(result.color)
|
||||||
|
.frame(width: 100, alignment: .trailing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,7 +249,9 @@ struct ResultRow: View {
|
|||||||
ResultBannerView(
|
ResultBannerView(
|
||||||
result: RoundResult(
|
result: RoundResult(
|
||||||
handResults: [.blackjack],
|
handResults: [.blackjack],
|
||||||
|
handWinnings: [150],
|
||||||
insuranceResult: nil,
|
insuranceResult: nil,
|
||||||
|
insuranceWinnings: 0,
|
||||||
totalWinnings: 150,
|
totalWinnings: 150,
|
||||||
wasBlackjack: true
|
wasBlackjack: true
|
||||||
),
|
),
|
||||||
@ -224,11 +266,30 @@ struct ResultRow: View {
|
|||||||
ResultBannerView(
|
ResultBannerView(
|
||||||
result: RoundResult(
|
result: RoundResult(
|
||||||
handResults: [.bust, .win, .push],
|
handResults: [.bust, .win, .push],
|
||||||
|
handWinnings: [-100, 100, 0],
|
||||||
insuranceResult: nil,
|
insuranceResult: nil,
|
||||||
totalWinnings: 25,
|
insuranceWinnings: 0,
|
||||||
|
totalWinnings: 0,
|
||||||
wasBlackjack: false
|
wasBlackjack: false
|
||||||
),
|
),
|
||||||
currentBalance: 1025,
|
currentBalance: 1000,
|
||||||
|
minBet: 10,
|
||||||
|
onNewRound: {},
|
||||||
|
onPlayAgain: {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Split with Insurance") {
|
||||||
|
ResultBannerView(
|
||||||
|
result: RoundResult(
|
||||||
|
handResults: [.lose, .win],
|
||||||
|
handWinnings: [-100, 200],
|
||||||
|
insuranceResult: .insuranceWin,
|
||||||
|
insuranceWinnings: 100,
|
||||||
|
totalWinnings: 200,
|
||||||
|
wasBlackjack: false
|
||||||
|
),
|
||||||
|
currentBalance: 1200,
|
||||||
minBet: 10,
|
minBet: 10,
|
||||||
onNewRound: {},
|
onNewRound: {},
|
||||||
onPlayAgain: {}
|
onPlayAgain: {}
|
||||||
|
|||||||
@ -12,6 +12,9 @@ struct BlackjackTableView: View {
|
|||||||
@Bindable var state: GameState
|
@Bindable var state: GameState
|
||||||
let onPlaceBet: () -> Void
|
let onPlaceBet: () -> Void
|
||||||
|
|
||||||
|
/// Screen height passed from parent for responsive sizing
|
||||||
|
var screenHeight: CGFloat = 800
|
||||||
|
|
||||||
/// Whether to show Hi-Lo card count values on cards.
|
/// Whether to show Hi-Lo card count values on cards.
|
||||||
var showCardCount: Bool { state.settings.showCardCount }
|
var showCardCount: Bool { state.settings.showCardCount }
|
||||||
|
|
||||||
@ -32,6 +35,23 @@ struct BlackjackTableView: View {
|
|||||||
// Use global debug flag from Design constants
|
// Use global debug flag from Design constants
|
||||||
private var showDebugBorders: Bool { Design.showDebugBorders }
|
private var showDebugBorders: Bool { Design.showDebugBorders }
|
||||||
|
|
||||||
|
/// Dynamic spacer height based on screen size.
|
||||||
|
/// Formula: spacing = clamp((screenHeight - baseline) * scale, min, max)
|
||||||
|
/// This produces smooth scaling across all device sizes:
|
||||||
|
/// - iPhone SE (~667pt): ~20pt
|
||||||
|
/// - iPhone Pro Max (~932pt): ~76pt
|
||||||
|
/// - iPad Mini (~1024pt): ~95pt
|
||||||
|
/// - iPad Pro 12.9" (~1366pt): ~150pt (capped)
|
||||||
|
private var dealerPlayerSpacing: CGFloat {
|
||||||
|
let baseline: CGFloat = 550 // Below this, use minimum
|
||||||
|
let scale: CGFloat = 0.2 // 20% of height above baseline
|
||||||
|
let minSpacing: CGFloat = 20 // Floor for smallest screens
|
||||||
|
let maxSpacing: CGFloat = 150 // Ceiling for largest screens
|
||||||
|
|
||||||
|
let calculated = (screenHeight - baseline) * scale
|
||||||
|
return min(maxSpacing, max(minSpacing, calculated))
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: Design.Spacing.small) {
|
VStack(spacing: Design.Spacing.small) {
|
||||||
// Dealer area
|
// Dealer area
|
||||||
@ -44,9 +64,9 @@ struct BlackjackTableView: View {
|
|||||||
)
|
)
|
||||||
.debugBorder(showDebugBorders, color: .red, label: "Dealer")
|
.debugBorder(showDebugBorders, color: .red, label: "Dealer")
|
||||||
|
|
||||||
// Flexible space between dealer and player (minimum 60pt)
|
// Flexible space between dealer and player - scales with screen size
|
||||||
Spacer(minLength: 60)
|
Spacer(minLength: dealerPlayerSpacing)
|
||||||
.debugBorder(showDebugBorders, color: .yellow, label: "Spacer")
|
.debugBorder(showDebugBorders, color: .yellow, label: "Spacer \(Int(dealerPlayerSpacing))")
|
||||||
|
|
||||||
// Player hands area - only show when there are cards dealt
|
// Player hands area - only show when there are cards dealt
|
||||||
if state.playerHands.first?.cards.isEmpty == false {
|
if state.playerHands.first?.cards.isEmpty == false {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user