diff options
Diffstat (limited to 'internal/casino/baccarat.go')
| -rw-r--r-- | internal/casino/baccarat.go | 29 |
1 files changed, 20 insertions, 9 deletions
diff --git a/internal/casino/baccarat.go b/internal/casino/baccarat.go index f4f5fbd..231a011 100644 --- a/internal/casino/baccarat.go +++ b/internal/casino/baccarat.go @@ -173,6 +173,8 @@ func (t *baccaratTable) action(name, action string) []Event { } func (t *baccaratTable) rebet(name string) (string, int) { + t.mu.Lock() + defer t.mu.Unlock() if p := t.players.get(name); p != nil && p.lastBet > 0 { return p.lastSpot, p.lastBet } @@ -182,8 +184,13 @@ func (t *baccaratTable) rebet(name string) (string, int) { // deal plays out the hand by the punto banco tableau, settles every wager, // and resets the table for the next round. func (t *baccaratTable) deal() []Event { - // The cut-card reshuffle happens between rounds, never mid-hand. + // The cut-card reshuffle and the round-coverage guarantee both happen at + // this boundary, before a single card of the coup is dealt (deal() is + // synchronous, so a wager here has never had a card dealt for it): + // draw() rebuilds an exhausted shoe in place, which mid-coup could deal + // a card the coup already holds. t.shoe.shuffleIfDue() + t.shoe.ensureForRound(baccaratRoundCards) player, banker := playTableau(t.shoe) playerTotal, bankerTotal := baccaratTotal(player), baccaratTotal(banker) round := t.event("New baccarat hand!", true) @@ -265,19 +272,20 @@ func (t *baccaratTable) resetRound() { } } -// markDisconnected removes the player from the table, forfeiting any wager -// they committed (deal only settles seated bettors). An open betting window -// resolves or resets on the next tick now that they no longer hold it open. +// markDisconnected removes the player from the table. A committed wager is +// REFUNDED rather than forfeited: deal() is synchronous, so a seated bettor +// whose wager is still outstanding has had no card dealt for it (the coup +// only starts once every connected seat has bet) and nothing is revealed by +// returning the stake. An open betting window then resolves or resets on the +// next tick now that they no longer hold it open. func (t *baccaratTable) markDisconnected(name string) []Event { t.mu.Lock() defer t.mu.Unlock() if !t.players.has(name) { 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() + p.Wallet.RefundOutstanding() } t.players.remove(name) events := []Event{t.event(fmt.Sprintf("%s stands up.", name), true)} @@ -395,9 +403,11 @@ func baccaratResultMessages(name, result string, payout int) (string, string) { } } -// refundAll resolves every committed wager at this table (graceful +// refundAll refunds every committed wager at this table (graceful // shutdown/eviction): the wallet returns every outstanding stake exactly -// once. +// once. It then RESETS the round, so a refunded round can never settle (and +// pay) afterwards: calling it twice, or letting the table keep running, +// cannot pay the same round twice. func (t *baccaratTable) refundAll() []Event { t.mu.Lock() defer t.mu.Unlock() @@ -419,6 +429,7 @@ func (t *baccaratTable) refundAll() []Event { fmt.Sprintf("%s is refunded %d as the casino closes.", name, refund), fmt.Sprintf("The house refunds your %d stake for the shutdown.", refund))) } + t.resetRound() return events } |
