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