diff options
Diffstat (limited to 'internal/game/casino.go')
| -rw-r--r-- | internal/game/casino.go | 60 |
1 files changed, 55 insertions, 5 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go index 3edb26b..8ba9097 100644 --- a/internal/game/casino.go +++ b/internal/game/casino.go @@ -20,10 +20,15 @@ type casinoWallet struct { // Wager debits the stake durably. Order of operations: durable journal // record FIRST (with balance checkpoints), then the in-memory mutation, -// then the character save, then the settlement marker. A failed save -// reverts the in-memory mutation and voids the record: no charge is kept -// in memory, and the error distinguishes persistence failure from -// insufficient funds. +// then the character save, then the record is registered as an OUTSTANDING +// STAKE. It is NOT settled here: it stays open until the round resolves +// (casino.Wallet.ResolveStakes) or is refunded (RefundOutstanding). An open +// record at startup recovery means the round state was lost in a crash and +// the stake is refunded — the documented refund-on-crash policy. +// +// A failed save reverts the in-memory mutation and voids the record: no +// charge is kept in memory, and the error distinguishes persistence failure +// from insufficient funds. func (w casinoWallet) Wager(amount int) (casino.Wager, error) { if amount <= 0 { return casino.Wager{}, casino.ErrInsufficientFunds @@ -57,7 +62,9 @@ func (w casinoWallet) Wager(amount int) (casino.Wager, error) { w.g.Economy.void(rec) return casino.Wager{}, casino.ErrWalletPersistence } - w.g.Economy.settle(rec) + // The record stays open as an outstanding stake until the round + // resolves; recovery refunds open stakes after a crash. + w.g.Economy.registerStake(rec) return casino.Wager{Total: amount, Chips: chips, Credits: credits}, nil } @@ -90,6 +97,49 @@ func (w casinoWallet) PayCredits(amount int) error { return nil } +// ResolveStakes marks every outstanding stake for the player settled: the +// round resolved (won, lost, pushed, or forfeited), so startup recovery +// must no longer refund it. +func (w casinoWallet) ResolveStakes() { + w.g.Economy.ResolveStakes(w.p.Name) +} + +// RefundOutstanding returns every outstanding stake to the player: chips +// come back as chips, credits through the durable payout path. The stakes +// are resolved only once the return is durable; on failure the chip return +// is rolled back out of memory and the open records drive recovery instead. +func (w casinoWallet) RefundOutstanding() (chips, credits int, err error) { + for _, rec := range w.g.Economy.openStakes(w.p.Name) { + chips += rec.Chips + credits += rec.Credit + } + if chips <= 0 && credits <= 0 { + w.ResolveStakes() + return 0, 0, nil + } + if chips > 0 { + w.g.giveChips(w.p, chips) + } + if credits > 0 { + if err := w.PayCredits(credits); err != nil { + if chips > 0 { + // Roll the chip return back out of memory: the durable + // refund will re-apply both amounts from the open records. + w.p.RemoveItem("chips", chips) + } + return chips, credits, err + } + } else if chips > 0 { + // A chips-only refund needs its own durable save. + if err := w.g.AccountStore.SaveCharacter(w.p); err != nil { + w.p.RemoveItem("chips", chips) + return chips, 0, casino.ErrWalletPersistence + } + } + w.ResolveStakes() + return chips, credits, nil +} + // casinoAffordable is the most a player can wager: chips are spent first, // then credits. func casinoAffordable(p *player.Player) int { return p.Credits + countChips(p) } |
