diff options
Diffstat (limited to 'internal/casino/machine.go')
| -rw-r--r-- | internal/casino/machine.go | 56 |
1 files changed, 40 insertions, 16 deletions
diff --git a/internal/casino/machine.go b/internal/casino/machine.go index 6a7b7aa..caff1bf 100644 --- a/internal/casino/machine.go +++ b/internal/casino/machine.go @@ -228,10 +228,26 @@ func (m *Machine) finishSpin() []Event { } credits := int(math.Floor(result)) m.payoutRemainder = result - float64(credits) + payFailed := false if credits > 0 && m.player.Wallet != nil { - m.player.Wallet.PayCredits(credits) + if err := m.player.Wallet.PayCredits(credits); err != nil { + // The payout could not be made durable; recovery will re-apply + // the owed amount from the open journal record. Do not announce + // the win as paid, stop any autobet chain, and leave the stake + // open so recovery settles it against the payout record. + payFailed = true + m.autoBet, m.autoCountdown = false, 0 + failedEvents := m.private(payoutFailedMessage()) + failedEvents[0].Color = "casino_payout" + events = append(events, failedEvents...) + } + } + if !payFailed && m.player.Wallet != nil { + // The paid spin resolved (free spins that follow wager nothing): + // the stake is no longer outstanding. + m.player.Wallet.ResolveStakes() } - if credits > 0 { + if credits > 0 && !payFailed { winEvents := m.private(fmt.Sprintf("\nYou win %d credits.", credits)) winEvents[0].Color = "casino_payout" if credits > m.spinBet { @@ -283,8 +299,12 @@ func (m *Machine) leave(name string) []Event { } // resetSeat frees the machine, discarding any in-flight spin and autobet -// state. A wager already taken for a spin in progress is forfeited. +// state. A wager already taken for a spin in progress is forfeited: its +// stake is resolved so startup recovery does not refund it. func (m *Machine) resetSeat() { + if m.player != nil && m.player.Wallet != nil { + m.player.Wallet.ResolveStakes() + } m.player = nil m.bet = 0 m.spin = nil @@ -387,27 +407,31 @@ func (m *Machine) menuEvents(prefix string) []Event { return m.privateMenu(prefix) } -// refundAll refunds any committed stake at graceful shutdown. Locked. +// refundAll refunds any outstanding stake at graceful shutdown/eviction. +// The wallet resolves every outstanding stake exactly once; a completed +// spin has no outstanding stake and is not refunded again. Locked. func (m *Machine) refundAll() []Event { m.mu.Lock() defer m.mu.Unlock() - if m.player == nil || m.player.Wallet == nil || m.spinBet <= 0 { + if m.player == nil || m.player.Wallet == nil { return nil } - name, refund := m.player.Name, m.spinBet - wallet := m.player.Wallet - m.spinBet = 0 - m.bet = 0 - if err := wallet.PayCredits(refund); err != nil { - return []Event{{RoomID: m.RoomID, TableID: m.Config.ID, Type: EventResult, - PrivateTo: name, Player: name, - PrivateMessage: "Shutdown refund could not be persisted; the stake is recorded for recovery.", - Color: "casino_action", Public: false}} + name, wallet := m.player.Name, m.player.Wallet + chips, credits, err := wallet.RefundOutstanding() + refund := chips + credits + if err != nil || refund <= 0 { + if err != nil { + return []Event{{RoomID: m.RoomID, TableID: m.Config.ID, Type: EventResult, + PrivateTo: name, Player: name, + PrivateMessage: "Shutdown refund could not be persisted; the stake is recorded for recovery.", + Color: "casino_action", Public: false}} + } + return nil } return []Event{{RoomID: m.RoomID, TableID: m.Config.ID, Type: EventResult, PrivateTo: name, Player: name, - Message: fmt.Sprintf("%s is refunded %d credits as the casino closes.", name, refund), - PrivateMessage: fmt.Sprintf("The house refunds your %d credit stake for the shutdown.", refund), + Message: fmt.Sprintf("%s is refunded %d as the casino closes.", name, refund), + PrivateMessage: fmt.Sprintf("The house refunds your %d stake for the shutdown.", refund), Color: "casino_action", Public: true}} } |
