Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-12-29 16:39:20 -06:00
parent abf4ba9b97
commit 1fe7bbb274
3 changed files with 143 additions and 60 deletions

View File

@ -994,13 +994,10 @@ final class GameState: CasinoGameState {
// MARK: - Game Reset // MARK: - Game Reset
/// Resets the entire game (keeps statistics). /// Resets the entire game (keeps statistics).
/// Uses CasinoKit's performResetGame() which properly handles session ending.
func resetGame() { func resetGame() {
balance = settings.startingBalance performResetGame()
roundHistory = [] // Note: newRoundInternal() is called by resetForNewSession()
engine = BaccaratEngine(deckCount: settings.deckCount.rawValue)
startNewSession()
newRoundInternal()
saveGameData()
// Play new game sound // Play new game sound
sound.playNewRound() sound.playNewRound()

View File

@ -518,7 +518,12 @@ final class GameState: CasinoGameState {
playerHandsVisibleCardCount = [0] playerHandsVisibleCardCount = [0]
dealerVisibleCardCount = 0 dealerVisibleCardCount = 0
let delay = settings.showAnimations ? 0.3 * settings.dealingSpeed : 0 // Animation timing
let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
// Small delay for card to appear on screen before updating badge (~15% of animation)
let cardAppearDelay = settings.showAnimations ? animationDuration * 0.15 : 0
// Remaining delay after badge update to complete the animation
let remainingDelay = settings.showAnimations ? animationDuration * 0.85 : 0
// European no-hole-card: deal 3 cards (player, dealer, player) // European no-hole-card: deal 3 cards (player, dealer, player)
// American style: deal 4 cards (player, dealer, player, dealer) // American style: deal 4 cards (player, dealer, player, dealer)
@ -532,16 +537,23 @@ final class GameState: CasinoGameState {
dealerHand.cards.append(card) dealerHand.cards.append(card)
} }
sound.play(.cardDeal) sound.play(.cardDeal)
if delay > 0 {
try? await Task.sleep(for: .seconds(delay)) // Wait for card to appear on screen
if cardAppearDelay > 0 {
try? await Task.sleep(for: .seconds(cardAppearDelay))
} }
// Mark card as visible after animation delay // Now mark card as visible (badge updates)
if i % 2 == 0 { if i % 2 == 0 {
playerHandsVisibleCardCount[0] += 1 playerHandsVisibleCardCount[0] += 1
} else { } else {
dealerVisibleCardCount += 1 dealerVisibleCardCount += 1
} }
// Wait for remaining animation before dealing next card
if remainingDelay > 0 {
try? await Task.sleep(for: .seconds(remainingDelay))
}
} }
} }
@ -660,15 +672,24 @@ final class GameState: CasinoGameState {
playerHands[activeHandIndex].cards.append(card) playerHands[activeHandIndex].cards.append(card)
sound.play(.cardDeal) sound.play(.cardDeal)
// Wait for animation if enabled // Animation timing
let delay = settings.showAnimations ? 0.3 * settings.dealingSpeed : 0 let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
if delay > 0 { let cardAppearDelay = settings.showAnimations ? animationDuration * 0.15 : 0
try? await Task.sleep(for: .seconds(delay)) let remainingDelay = settings.showAnimations ? animationDuration * 0.85 : 0
// Wait for card to appear on screen
if cardAppearDelay > 0 {
try? await Task.sleep(for: .seconds(cardAppearDelay))
} }
// Mark card as visible after animation // Mark card as visible (badge updates)
playerHandsVisibleCardCount[activeHandIndex] += 1 playerHandsVisibleCardCount[activeHandIndex] += 1
// Wait for remaining animation before processing result
if remainingDelay > 0 {
try? await Task.sleep(for: .seconds(remainingDelay))
}
// Check for bust or 21 // Check for bust or 21
if playerHands[activeHandIndex].isBusted { if playerHands[activeHandIndex].isBusted {
playerHands[activeHandIndex].result = .bust playerHands[activeHandIndex].result = .bust
@ -717,14 +738,23 @@ final class GameState: CasinoGameState {
playerHands[activeHandIndex].cards.append(card) playerHands[activeHandIndex].cards.append(card)
sound.play(.cardDeal) sound.play(.cardDeal)
// Wait for animation if enabled // Animation timing
let delay = settings.showAnimations ? 0.3 * settings.dealingSpeed : 0 let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
if delay > 0 { let cardAppearDelay = settings.showAnimations ? animationDuration * 0.15 : 0
try? await Task.sleep(for: .seconds(delay)) let remainingDelay = settings.showAnimations ? animationDuration * 0.85 : 0
// Wait for card to appear on screen
if cardAppearDelay > 0 {
try? await Task.sleep(for: .seconds(cardAppearDelay))
} }
// Mark card as visible after animation // Mark card as visible (badge updates)
playerHandsVisibleCardCount[activeHandIndex] += 1 playerHandsVisibleCardCount[activeHandIndex] += 1
// Wait for remaining animation
if remainingDelay > 0 {
try? await Task.sleep(for: .seconds(remainingDelay))
}
} }
if playerHands[activeHandIndex].isBusted { if playerHands[activeHandIndex].isBusted {
@ -762,34 +792,47 @@ final class GameState: CasinoGameState {
balance -= originalHand.bet balance -= originalHand.bet
sound.play(.chipPlace) sound.play(.chipPlace)
// Deal one card to each hand // Replace original with split hands first (so visible counts are tracked correctly)
let delay = settings.showAnimations ? 0.3 * settings.dealingSpeed : 0
if let card1 = engine.dealCard() {
hand1.cards.append(card1)
sound.play(.cardDeal)
if delay > 0 {
try? await Task.sleep(for: .seconds(delay))
}
}
if let card2 = engine.dealCard() {
hand2.cards.append(card2)
sound.play(.cardDeal)
if delay > 0 {
try? await Task.sleep(for: .seconds(delay))
}
}
// Replace original with split hands
playerHands.remove(at: activeHandIndex) playerHands.remove(at: activeHandIndex)
playerHands.insert(hand1, at: activeHandIndex) playerHands.insert(hand1, at: activeHandIndex)
playerHands.insert(hand2, at: activeHandIndex + 1) playerHands.insert(hand2, at: activeHandIndex + 1)
// Update visible card counts - each split hand starts with 2 visible cards // Each split hand starts with 1 visible card (the original cards)
playerHandsVisibleCardCount.remove(at: activeHandIndex) playerHandsVisibleCardCount.remove(at: activeHandIndex)
playerHandsVisibleCardCount.insert(2, at: activeHandIndex) playerHandsVisibleCardCount.insert(1, at: activeHandIndex)
playerHandsVisibleCardCount.insert(2, at: activeHandIndex + 1) playerHandsVisibleCardCount.insert(1, at: activeHandIndex + 1)
// Animation timing
let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
let cardAppearDelay = settings.showAnimations ? animationDuration * 0.15 : 0
let remainingDelay = settings.showAnimations ? animationDuration * 0.85 : 0
// Deal one card to each hand
if let card1 = engine.dealCard() {
playerHands[activeHandIndex].cards.append(card1)
sound.play(.cardDeal)
if cardAppearDelay > 0 {
try? await Task.sleep(for: .seconds(cardAppearDelay))
}
playerHandsVisibleCardCount[activeHandIndex] += 1
if remainingDelay > 0 {
try? await Task.sleep(for: .seconds(remainingDelay))
}
}
if let card2 = engine.dealCard() {
playerHands[activeHandIndex + 1].cards.append(card2)
sound.play(.cardDeal)
if cardAppearDelay > 0 {
try? await Task.sleep(for: .seconds(cardAppearDelay))
}
playerHandsVisibleCardCount[activeHandIndex + 1] += 1
if remainingDelay > 0 {
try? await Task.sleep(for: .seconds(remainingDelay))
}
}
// If split aces, typically only one card each and stand // If split aces, typically only one card each and stand
if originalHand.cards[0].rank == .ace && !settings.resplitAces { if originalHand.cards[0].rank == .ace && !settings.resplitAces {
@ -848,20 +891,24 @@ final class GameState: CasinoGameState {
private func dealerTurn() async { private func dealerTurn() async {
currentPhase = .dealerTurn currentPhase = .dealerTurn
let delay = settings.showAnimations ? 0.5 * settings.dealingSpeed : 0 // Animation timing
let animationDuration = Design.Animation.springDuration * settings.dealingSpeed
let delay = settings.showAnimations ? animationDuration : 0
// For flip animation, card face becomes visible halfway through (at 90° rotation)
let flipMidpointDelay = settings.showAnimations ? animationDuration / 2.0 : 0
// European no-hole-card: deal the second card now // European no-hole-card: deal the second card now
if settings.noHoleCard && dealerHand.cards.count == 1 { if settings.noHoleCard && dealerHand.cards.count == 1 {
if let card = engine.dealCard() { if let card = engine.dealCard() {
dealerHand.cards.append(card) dealerHand.cards.append(card)
// Mark card as visible immediately - face is visible as soon as card appears
dealerVisibleCardCount += 1
sound.play(.cardDeal) sound.play(.cardDeal)
// Wait for animation to complete before checking blackjack
if delay > 0 { if delay > 0 {
try? await Task.sleep(for: .seconds(delay)) try? await Task.sleep(for: .seconds(delay))
} }
// Mark card as visible after animation
dealerVisibleCardCount += 1
} }
// Check for dealer blackjack in European mode // Check for dealer blackjack in European mode
@ -881,31 +928,38 @@ final class GameState: CasinoGameState {
return return
} }
} else { } else {
// American style: reveal hole card (card is already in hand, just mark as visible) // American style: reveal hole card (card is already in hand)
// The flip animation shows the card face at the midpoint (90° rotation)
sound.play(.cardFlip) sound.play(.cardFlip)
if delay > 0 { // Wait until card face becomes visible (halfway through flip)
try? await Task.sleep(for: .seconds(delay)) if flipMidpointDelay > 0 {
try? await Task.sleep(for: .seconds(flipMidpointDelay))
} }
// Mark hole card as visible (if not already) // Mark hole card as visible now that card face is showing
if dealerVisibleCardCount < dealerHand.cards.count { if dealerVisibleCardCount < dealerHand.cards.count {
dealerVisibleCardCount = dealerHand.cards.count dealerVisibleCardCount = dealerHand.cards.count
} }
// Wait for remaining flip animation to complete before drawing more cards
if flipMidpointDelay > 0 {
try? await Task.sleep(for: .seconds(flipMidpointDelay))
}
} }
// Dealer draws // Dealer draws
while engine.dealerShouldHit(hand: dealerHand) { while engine.dealerShouldHit(hand: dealerHand) {
if let card = engine.dealCard() { if let card = engine.dealCard() {
dealerHand.cards.append(card) dealerHand.cards.append(card)
// Mark card as visible immediately - face is visible as soon as card appears
dealerVisibleCardCount += 1
sound.play(.cardDeal) sound.play(.cardDeal)
// Wait for animation to complete before drawing next card
if delay > 0 { if delay > 0 {
try? await Task.sleep(for: .seconds(delay)) try? await Task.sleep(for: .seconds(delay))
} }
// Mark card as visible after animation
dealerVisibleCardCount += 1
} }
} }
@ -1209,13 +1263,10 @@ final class GameState: CasinoGameState {
// MARK: - Game Reset // MARK: - Game Reset
/// Resets the entire game (keeps statistics). /// Resets the entire game (keeps statistics).
/// Uses CasinoKit's performResetGame() which properly handles session ending.
func resetGame() { func resetGame() {
balance = settings.startingBalance performResetGame()
roundHistory = [] // Note: newRound() is called by resetForNewSession()
engine.reshuffle()
startNewSession()
newRound()
saveGameData()
} }
} }

View File

@ -126,3 +126,38 @@ public extension CasinoGameState {
} }
} }
// MARK: - Game Reset Extensions
public extension CasinoGameState {
/// Default implementation of resetGame that properly handles session ending.
/// Games can override this but should call the helper methods in the correct order.
///
/// The correct order is:
/// 1. End current session (captures actual ending balance)
/// 2. Reset balance to starting balance
/// 3. Reset game-specific state (via resetForNewSession)
/// 4. Start new session with fresh balance
/// 5. Save data
func performResetGame() {
// 1. End current session FIRST while balance still shows actual state
if currentSession != nil {
endCurrentSession(reason: .manualEnd)
}
// 2. Reset balance to starting balance
balance = startingBalance
// 3. Let game reset its specific state (reshuffle deck, clear history, etc.)
resetForNewSession()
// 4. Create new session with fresh balance
currentSession = GameSession<Stats>(
gameStyle: currentGameStyle,
startingBalance: balance
)
// 5. Save
saveGameData()
}
}