aboutsummaryrefslogtreecommitdiff
path: root/internal/casino
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino')
-rw-r--r--internal/casino/baccarat.go48
-rw-r--r--internal/casino/baccarat_test.go32
-rw-r--r--internal/casino/manager.go14
3 files changed, 56 insertions, 38 deletions
diff --git a/internal/casino/baccarat.go b/internal/casino/baccarat.go
index c9a50a1..164d0ff 100644
--- a/internal/casino/baccarat.go
+++ b/internal/casino/baccarat.go
@@ -200,24 +200,28 @@ func (t *baccaratTable) deal() []Event {
if !p.HasBet {
continue
}
- result, payout := settleBaccaratBet(p.lastSpot, p.Wager.Total, playerTotal, bankerTotal, t.Rules)
- payoutFailed := false
- if payout > 0 && p.Wallet != nil {
+ result, payout, settleErr := settleBaccaratBet(p.lastSpot, p.Wager.Total, playerTotal, bankerTotal, t.Rules)
+ // A payout-overflow error means the win could not be computed; treat
+ // it like an unpersisted payout so the win is never announced as paid
+ // (blackjack handles the same case identically).
+ payoutFailed := settleErr != nil
+ if !payoutFailed && payout > 0 && p.Wallet != nil {
if err := p.Wallet.PayStakeCover(payout, []string{p.stakeID}); 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 and leave the stakes
// open for recovery to settle.
payoutFailed = true
- publicResult, _ := baccaratResultMessages(p.Name, result, payout)
- settlement := t.playerAction(p.Name, publicResult, payoutFailedMessage())
- if result == "win" {
- settlement.Color = "casino_big_win"
- }
- events = append(events, settlement)
}
}
- if !payoutFailed {
+ if payoutFailed {
+ publicResult, _ := baccaratResultMessages(p.Name, result, payout)
+ settlement := t.playerAction(p.Name, publicResult, payoutFailedMessage())
+ if result == "win" {
+ settlement.Color = "casino_big_win"
+ }
+ events = append(events, settlement)
+ } else {
publicResult, privateResult := baccaratResultMessages(p.Name, result, payout)
settlement := t.playerAction(p.Name, publicResult, privateResult)
if result == "win" {
@@ -360,15 +364,18 @@ func bankerDraws(bankerTotal, playerThird int) bool {
// winnings) for one wager. Player and banker bets push on a tie. Winnings
// round down to whole credits, so the banker commission effectively rounds
// up: never bet less than one commission unit.
-func settleBaccaratBet(spot string, bet int, playerTotal, bankerTotal int, rules BaccaratConfig) (string, int) {
+func settleBaccaratBet(spot string, bet int, playerTotal, bankerTotal int, rules BaccaratConfig) (string, int, error) {
switch {
case playerTotal > bankerTotal:
if spot == baccaratSpotPlayer {
- return "win", bet * 2
+ return "win", bet * 2, nil
}
case bankerTotal > playerTotal:
if spot == baccaratSpotBanker {
- bankerWin, _ := MulPayout(bet, rules.BankerPayout)
+ bankerWin, err := MulPayout(bet, rules.BankerPayout)
+ if err != nil {
+ return "win", bet + bankerWin, err
+ }
// The banker commission rounds UP, not down: when the wager is
// too small for a whole credit of commission, a winning banker
// bet returns only the stake. MulPayout's positive-stake floor
@@ -377,18 +384,21 @@ func settleBaccaratBet(spot string, bet int, playerTotal, bankerTotal int, rules
// push), which the "never bet less than one commission unit"
// rule exists to prevent.
if float64(bet)*rules.BankerPayout < 1 {
- return "push", bet
+ return "push", bet, nil
}
- return "win", bet + bankerWin
+ return "win", bet + bankerWin, nil
}
default:
if spot == baccaratSpotTie {
- tieWin, _ := MulPayout(bet, rules.TiePayout)
- return "win", bet + tieWin
+ tieWin, err := MulPayout(bet, rules.TiePayout)
+ if err != nil {
+ return "win", bet + tieWin, err
+ }
+ return "win", bet + tieWin, nil
}
- return "push", bet
+ return "push", bet, nil
}
- return "loss", 0
+ return "loss", 0, nil
}
func baccaratOutcomeMessage(playerTotal, bankerTotal int) string {
diff --git a/internal/casino/baccarat_test.go b/internal/casino/baccarat_test.go
index 39941eb..41f5acf 100644
--- a/internal/casino/baccarat_test.go
+++ b/internal/casino/baccarat_test.go
@@ -99,29 +99,29 @@ func TestBaccaratTableauBankerThirdCardRules(t *testing.T) {
func TestBaccaratSettlement(t *testing.T) {
rules := (BaccaratConfig{}).Normalize()
- if result, payout := settleBaccaratBet(baccaratSpotPlayer, 100, 6, 4, rules); result != "win" || payout != 200 {
- t.Fatalf("player win: result=%s payout=%d, want win/200", result, payout)
+ if result, payout, err := settleBaccaratBet(baccaratSpotPlayer, 100, 6, 4, rules); err != nil || result != "win" || payout != 200 {
+ t.Fatalf("player win: result=%s payout=%d err=%v, want win/200", result, payout, err)
}
- if result, payout := settleBaccaratBet(baccaratSpotBanker, 100, 6, 4, rules); result != "loss" || payout != 0 {
- t.Fatalf("banker bet on player win: result=%s payout=%d, want loss/0", result, payout)
+ if result, payout, err := settleBaccaratBet(baccaratSpotBanker, 100, 6, 4, rules); err != nil || result != "loss" || payout != 0 {
+ t.Fatalf("banker bet on player win: result=%s payout=%d err=%v, want loss/0", result, payout, err)
}
- if result, payout := settleBaccaratBet(baccaratSpotBanker, 100, 4, 6, rules); result != "win" || payout != 195 {
- t.Fatalf("banker win: result=%s payout=%d, want win/195 (5%% commission)", result, payout)
+ if result, payout, err := settleBaccaratBet(baccaratSpotBanker, 100, 4, 6, rules); err != nil || result != "win" || payout != 195 {
+ t.Fatalf("banker win: result=%s payout=%d err=%v, want win/195 (5%% commission)", result, payout, err)
}
- if result, payout := settleBaccaratBet(baccaratSpotBanker, 1, 4, 6, rules); result != "push" || payout != 1 {
- t.Fatalf("banker win on bet 1: result=%s payout=%d, want push/1 (a sub-credit commission rounds up, returning only the stake)", result, payout)
+ if result, payout, err := settleBaccaratBet(baccaratSpotBanker, 1, 4, 6, rules); err != nil || result != "push" || payout != 1 {
+ t.Fatalf("banker win on bet 1: result=%s payout=%d err=%v, want push/1 (a sub-credit commission rounds up, returning only the stake)", result, payout, err)
}
- if result, payout := settleBaccaratBet(baccaratSpotTie, 100, 7, 7, rules); result != "win" || payout != 900 {
- t.Fatalf("tie win: result=%s payout=%d, want win/900 (8:1)", result, payout)
+ if result, payout, err := settleBaccaratBet(baccaratSpotTie, 100, 7, 7, rules); err != nil || result != "win" || payout != 900 {
+ t.Fatalf("tie win: result=%s payout=%d err=%v, want win/900 (8:1)", result, payout, err)
}
- if result, payout := settleBaccaratBet(baccaratSpotPlayer, 100, 7, 7, rules); result != "push" || payout != 100 {
- t.Fatalf("player bet on tie: result=%s payout=%d, want push/100", result, payout)
+ if result, payout, err := settleBaccaratBet(baccaratSpotPlayer, 100, 7, 7, rules); err != nil || result != "push" || payout != 100 {
+ t.Fatalf("player bet on tie: result=%s payout=%d err=%v, want push/100", result, payout, err)
}
- if result, payout := settleBaccaratBet(baccaratSpotBanker, 100, 7, 7, rules); result != "push" || payout != 100 {
- t.Fatalf("banker bet on tie: result=%s payout=%d, want push/100", result, payout)
+ if result, payout, err := settleBaccaratBet(baccaratSpotBanker, 100, 7, 7, rules); err != nil || result != "push" || payout != 100 {
+ t.Fatalf("banker bet on tie: result=%s payout=%d err=%v, want push/100", result, payout, err)
}
- if result, payout := settleBaccaratBet(baccaratSpotTie, 100, 6, 4, rules); result != "loss" || payout != 0 {
- t.Fatalf("tie bet on player win: result=%s payout=%d, want loss/0", result, payout)
+ if result, payout, err := settleBaccaratBet(baccaratSpotTie, 100, 6, 4, rules); err != nil || result != "loss" || payout != 0 {
+ t.Fatalf("tie bet on player win: result=%s payout=%d err=%v, want loss/0", result, payout, err)
}
}
diff --git a/internal/casino/manager.go b/internal/casino/manager.go
index 8b87147..b1281b2 100644
--- a/internal/casino/manager.go
+++ b/internal/casino/manager.go
@@ -409,20 +409,28 @@ func (m *Manager) RekeyRooms(idMap map[int]int) {
// refunded through each game's refundAll before the entries drop.
func (m *Manager) EvictRoom(roomID int) []Event {
prefix := fmt.Sprintf("%d:", roomID)
- var events []Event
+ // Snapshot the games under the manager lock, then refund OUTSIDE it:
+ // refundAll appends to the money journal and saves character files, and
+ // holding m.mu across that I/O blocks every casino command and Tick.
+ type evictable interface{ refundAll() []Event }
+ var evicted []evictable
m.mu.Lock()
for key, table := range m.tables {
if strings.HasPrefix(key, prefix) {
- events = append(events, table.refundAll()...)
+ evicted = append(evicted, table)
delete(m.tables, key)
}
}
for key, machine := range m.machines {
if strings.HasPrefix(key, prefix) {
- events = append(events, machine.refundAll()...)
+ evicted = append(evicted, machine)
delete(m.machines, key)
}
}
m.mu.Unlock()
+ var events []Event
+ for _, game := range evicted {
+ events = append(events, game.refundAll()...)
+ }
return events
}