diff options
| author | historia <[not public]> | 2026-09-15 15:35:52 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-15 15:35:52 -0400 |
| commit | 0b0b25222b3131ada79ff78c28eb8bf07d977deb (patch) | |
| tree | 2ceaef5203595044f66a8f00064cd8e017f8f48d /internal/game/casino.go | |
| parent | 396c3facb8287b9a34076cae8f3d7b300d366d99 (diff) | |
| download | thehouseoficarus-0b0b25222b3131ada79ff78c28eb8bf07d977deb.tar.gz | |
feat: single-owner game state, transactional lifecycle, durable money on server stop
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, |
