// // BaccaratEngine.swift // Baccarat // // Core game engine implementing all baccarat rules including third card logic. // import Foundation import CasinoKit /// The baccarat game engine implementing Punto Banco rules. struct BaccaratEngine { private(set) var shoe: Shoe private(set) var playerHand: Hand private(set) var bankerHand: Hand /// Creates a new engine with a fresh shoe. init(deckCount: Int = 8) { shoe = Shoe(deckCount: deckCount) playerHand = Hand() bankerHand = Hand() // Burn first card according to casino rules shoe.burn(1) } /// Clears hands and checks if shoe needs reshuffling. mutating func prepareNewRound() { playerHand.clear() bankerHand.clear() if shoe.needsReshuffle { shoe.shuffle() shoe.burn(1) } } /// Deals the initial two cards to both Player and Banker. /// Returns the cards in order they were dealt: P1, B1, P2, B2 mutating func dealInitialCards() -> [Card] { var dealtCards: [Card] = [] // Deal alternating: Player, Banker, Player, Banker if let p1 = shoe.deal() { playerHand.addCard(p1) dealtCards.append(p1) } if let b1 = shoe.deal() { bankerHand.addCard(b1) dealtCards.append(b1) } if let p2 = shoe.deal() { playerHand.addCard(p2) dealtCards.append(p2) } if let b2 = shoe.deal() { bankerHand.addCard(b2) dealtCards.append(b2) } return dealtCards } /// Determines if the Player should draw a third card. /// Player draws on 0-5, stands on 6-7. Natural (8-9) prevents drawing. func shouldPlayerDraw() -> Bool { // Check for naturals first if playerHand.isNatural || bankerHand.isNatural { return false } return playerHand.value <= 5 } /// Draws a third card for the Player if rules allow. /// - Returns: The drawn card, or nil if Player stands. mutating func drawPlayerThirdCard() -> Card? { guard shouldPlayerDraw() else { return nil } if let card = shoe.deal() { playerHand.addCard(card) return card } return nil } /// Determines if the Banker should draw a third card. /// This follows the complex Punto Banco third card rules. func shouldBankerDraw() -> Bool { // Check for naturals first if playerHand.isNatural || bankerHand.isNatural { return false } let bankerValue = bankerHand.value // If Player didn't draw (stood on 6-7), Banker uses simple rules if playerHand.cardCount == 2 { return bankerValue <= 5 } // Player drew a third card - apply complex Banker rules guard let playerThirdCard = playerHand.thirdCard else { return bankerValue <= 5 } let p3Value = playerThirdCard.baccaratValue // Banker third card rules based on Banker's total and Player's third card switch bankerValue { case 0, 1, 2: // Banker always draws on 0-2 return true case 3: // Banker draws unless Player's third card was 8 return p3Value != 8 case 4: // Banker draws if Player's third card was 2-7 return (2...7).contains(p3Value) case 5: // Banker draws if Player's third card was 4-7 return (4...7).contains(p3Value) case 6: // Banker draws if Player's third card was 6-7 return (6...7).contains(p3Value) case 7: // Banker stands on 7 return false default: // 8-9 are naturals, shouldn't reach here return false } } /// Draws a third card for the Banker if rules allow. /// - Returns: The drawn card, or nil if Banker stands. mutating func drawBankerThirdCard() -> Card? { guard shouldBankerDraw() else { return nil } if let card = shoe.deal() { bankerHand.addCard(card) return card } return nil } /// Determines the winner of the current round. func determineResult() -> GameResult { let playerValue = playerHand.value let bankerValue = bankerHand.value if playerValue > bankerValue { return .playerWins } else if bankerValue > playerValue { return .bankerWins } else { return .tie } } // MARK: - Side Bet Checks /// Whether the Player hand has a pair (first two cards same rank). var playerHasPair: Bool { guard playerHand.cardCount >= 2, let first = playerHand.cards.first, let second = playerHand.cards.dropFirst().first else { return false } return first.rank == second.rank } /// Whether the Banker hand has a pair (first two cards same rank). var bankerHasPair: Bool { guard bankerHand.cardCount >= 2, let first = bankerHand.cards.first, let second = bankerHand.cards.dropFirst().first else { return false } return first.rank == second.rank } /// The margin of victory for Player (positive) or Banker (negative). /// Zero means tie. var victoryMargin: Int { playerHand.value - bankerHand.value } /// Whether the winning hand had a natural (8 or 9). var winnerHadNatural: Bool { let result = determineResult() switch result { case .playerWins: return playerHand.isNatural case .bankerWins: return bankerHand.isNatural case .tie: return false } } // MARK: - Payout Calculations /// Calculates the payout for a bet given the result. /// - Parameters: /// - bet: The bet that was placed. /// - result: The result of the round. /// - Returns: The net winnings (positive), net loss (negative), or 0 for push. func calculatePayout(bet: Bet, result: GameResult) -> Int { switch bet.type { case .player, .banker, .tie: return calculateMainBetPayout(bet: bet, result: result) case .playerPair: return playerHasPair ? Int(Double(bet.amount) * bet.type.payoutMultiplier) : -bet.amount case .bankerPair: return bankerHasPair ? Int(Double(bet.amount) * bet.type.payoutMultiplier) : -bet.amount case .dragonBonusPlayer: return calculateDragonBonusPayout(bet: bet, forPlayer: true, result: result) case .dragonBonusBanker: return calculateDragonBonusPayout(bet: bet, forPlayer: false, result: result) } } /// Calculates payout for main bets (Player, Banker, Tie). private func calculateMainBetPayout(bet: Bet, result: GameResult) -> Int { if result.isPush(for: bet.type) { // Push - bet is returned return 0 } if result.isWinningBet(bet.type) { // Win - return winnings based on payout multiplier return Int(Double(bet.amount) * bet.type.payoutMultiplier) } else { // Loss - lose the bet amount return -bet.amount } } /// Calculates Dragon Bonus payout. private func calculateDragonBonusPayout(bet: Bet, forPlayer: Bool, result: GameResult) -> Int { // Determine if the side we bet on won let ourSideWon = forPlayer ? (result == .playerWins) : (result == .bankerWins) if !ourSideWon { // Dragon Bonus loses if our side didn't win (including ties) return -bet.amount } // Calculate margin let margin = abs(victoryMargin) let isNatural = forPlayer ? playerHand.isNatural : bankerHand.isNatural // Get the multiplier if let multiplier = DragonBonusPayout.multiplier(for: margin, isNatural: isNatural) { return bet.amount * multiplier } else { // Win by less than 4 - loses return -bet.amount } } /// Plays a complete round automatically and returns the result. /// Used for simulation/testing purposes. mutating func playRound() -> GameResult { prepareNewRound() _ = dealInitialCards() _ = drawPlayerThirdCard() _ = drawBankerThirdCard() return determineResult() } }