diff options
Diffstat (limited to 'internal/game/casino.go')
| -rw-r--r-- | internal/game/casino.go | 67 |
1 files changed, 57 insertions, 10 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go index eb6fa4a..7442178 100644 --- a/internal/game/casino.go +++ b/internal/game/casino.go @@ -18,29 +18,76 @@ type casinoWallet struct { p *player.Player } -func (w casinoWallet) Wager(amount int) (casino.Wager, bool) { +// 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. +func (w casinoWallet) Wager(amount int) (casino.Wager, error) { if amount <= 0 { - return casino.Wager{}, false + return casino.Wager{}, casino.ErrInsufficientFunds } - chips := countChips(w.p) + creditsBefore := w.p.Credits + chipsBefore := countChips(w.p) + chips := chipsBefore if chips > amount { chips = amount } credits := amount - chips - if w.p.Credits < credits { - return casino.Wager{}, false + if creditsBefore < credits { + return casino.Wager{}, casino.ErrInsufficientFunds + } + rec, err := w.g.Economy.start("wager", w.p, chips, credits, + creditsBefore, creditsBefore-credits, chipsBefore, chipsBefore-chips) + if err != nil { + return casino.Wager{}, casino.ErrWalletPersistence } if chips > 0 { w.p.RemoveItem("chips", chips) } w.p.Credits -= credits - w.g.AccountStore.SaveCharacter(w.p) - return casino.Wager{Total: amount, Chips: chips, Credits: credits}, true + if err := w.g.AccountStore.SaveCharacter(w.p); err != nil { + // Revert the in-memory mutation: the play must not proceed on a + // stake that could not be made durable. + if chips > 0 { + w.g.giveChips(w.p, chips) + } + w.p.Credits += credits + w.g.Economy.void(rec) + return casino.Wager{}, casino.ErrWalletPersistence + } + w.g.Economy.settle(rec) + return casino.Wager{Total: amount, Chips: chips, Credits: credits}, nil } -func (w casinoWallet) PayCredits(amount int) { - w.p.Credits += amount - w.g.AccountStore.SaveCharacter(w.p) +// PayCredits applies a payout durably. The journal record is written first; +// if the save fails, the record stays open and startup recovery re-pays it +// (the player is owed the settlement). +func (w casinoWallet) PayCredits(amount int) error { + if amount <= 0 { + return nil + } + creditsBefore := w.p.Credits + chipsBefore := countChips(w.p) + creditsAfter, err := addCreditsChecked(creditsBefore, amount) + if err != nil { + return err + } + rec, err := w.g.Economy.start("payout", w.p, 0, amount, + creditsBefore, creditsAfter, chipsBefore, chipsBefore) + if err != nil { + return casino.ErrWalletPersistence + } + w.p.Credits = creditsAfter + if err := w.g.AccountStore.SaveCharacter(w.p); err != nil { + w.p.Credits = creditsBefore + // Keep the record open: recovery re-pays on next startup, since a + // reported win must be durable. + return casino.ErrWalletPersistence + } + w.g.Economy.settle(rec) + return nil } // casinoAffordable is the most a player can wager: chips are spent first, |
