aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/blackjack.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/blackjack.go')
-rw-r--r--internal/casino/blackjack.go61
1 files changed, 49 insertions, 12 deletions
diff --git a/internal/casino/blackjack.go b/internal/casino/blackjack.go
index 7c01c34..d3b26af 100644
--- a/internal/casino/blackjack.go
+++ b/internal/casino/blackjack.go
@@ -380,6 +380,8 @@ func splitCompatible(a, b card, unlikeTens bool) bool {
}
func (t *blackjackTable) startRound() []Event {
+ // The cut-card reshuffle happens between rounds, never mid-hand.
+ t.shoe.shuffleIfDue()
t.dealer = blackjackHand{cards: []card{t.shoe.draw(), t.shoe.draw()}}
for _, p := range t.players.ordered() {
if !p.HasBet {
@@ -542,10 +544,27 @@ func (t *blackjackTable) settle() []Event {
if !p.HasBet {
continue
}
+ payoutFailed := false
for _, hand := range p.hands {
result, payout := settleHand(hand, dealerTotal, dealerBlackjack, t.Rules)
if payout > 0 && p.Wallet != nil {
- p.Wallet.PayCredits(payout)
+ if err := p.Wallet.PayCredits(payout); err != nil {
+ // The payout could not be made durable. The journal
+ // record stays open: recovery re-applies the owed
+ // amount. The win is not announced as paid, and the
+ // stakes stay open so recovery settles them against
+ // the payout record.
+ payoutFailed = true
+ publicResult, _ := blackjackResultMessages(p.Name, result, payout)
+ settlement := t.playerAction(p.Name, publicResult, payoutFailedMessage())
+ if result == "blackjack" {
+ settlement.Color = "casino_jackpot_win"
+ } else if result == "win" {
+ settlement.Color = "casino_big_win"
+ }
+ events = append(events, settlement)
+ continue
+ }
}
publicResult, privateResult := blackjackResultMessages(p.Name, result, payout)
settlement := t.playerAction(p.Name, publicResult, privateResult)
@@ -562,9 +581,20 @@ func (t *blackjackTable) settle() []Event {
insurancePayout, _ = MulPayout(p.insurance, 1+t.Rules.InsurancePayout)
}
if insurancePayout > 0 {
- p.Wallet.PayCredits(insurancePayout)
+ if err := p.Wallet.PayCredits(insurancePayout); err != nil {
+ payoutFailed = true
+ events = append(events, t.private(p.Name, payoutFailedMessage()))
+ } else {
+ events = append(events, t.private(p.Name, fmt.Sprintf("Insurance pays %d credits.", insurancePayout)))
+ }
+ } else {
+ events = append(events, t.private(p.Name, "Insurance pays 0 credits."))
}
- events = append(events, t.private(p.Name, fmt.Sprintf("Insurance pays %d credits.", insurancePayout)))
+ }
+ if p.Wallet != nil && !payoutFailed {
+ // The round resolved; the stakes are no longer outstanding and
+ // startup recovery must not refund them.
+ p.Wallet.ResolveStakes()
}
}
t.resetRound()
@@ -701,6 +731,11 @@ func (t *blackjackTable) markDisconnectedLocked(name string) []Event {
if index < 0 {
return nil
}
+ // A disconnected player forfeits the round in flight: resolve the
+ // stakes now so startup recovery does not refund them later.
+ if p := t.players.get(name); p != nil && p.HasBet && p.Wallet != nil {
+ p.Wallet.ResolveStakes()
+ }
t.players.remove(name)
events := []Event{t.event(fmt.Sprintf("%s stands up.", name), true)}
if t.players.count() == 0 {
@@ -730,7 +765,8 @@ func (t *blackjackTable) hasParticipant(name string) bool {
}
// refundAll resolves every committed wager at this table (graceful
-// shutdown): stakes return to their players through the wallet.
+// shutdown/eviction): the wallet returns every outstanding stake — opening
+// bet, doubles, splits, and insurance — exactly once.
func (t *blackjackTable) refundAll() []Event {
t.mu.Lock()
defer t.mu.Unlock()
@@ -739,17 +775,18 @@ func (t *blackjackTable) refundAll() []Event {
if !p.HasBet || p.Wallet == nil {
continue
}
- name, refund := p.Name, p.Wager.Total
- if refund <= 0 {
- continue
- }
- if err := p.Wallet.PayCredits(refund); err != nil {
- events = append(events, t.private(name, "Shutdown refund could not be persisted; the stake is recorded for recovery."))
+ name := p.Name
+ chips, credits, err := p.Wallet.RefundOutstanding()
+ refund := chips + credits
+ if err != nil || refund <= 0 {
+ if err != nil {
+ events = append(events, t.private(name, "Shutdown refund could not be persisted; the stake is recorded for recovery."))
+ }
continue
}
events = append(events, t.playerAction(name,
- fmt.Sprintf("%s is refunded %d credits as the casino closes.", name, refund),
- fmt.Sprintf("The house refunds your %d credit stake for the shutdown.", refund)))
+ fmt.Sprintf("%s is refunded %d as the casino closes.", name, refund),
+ fmt.Sprintf("The house refunds your %d stake for the shutdown.", refund)))
}
return events
}