aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/machine.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/machine.go')
-rw-r--r--internal/casino/machine.go56
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}}
}