diff options
| author | historia <[not public]> | 2026-10-01 22:47:41 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-10-01 22:47:41 -0400 |
| commit | 8eea0e4e06a4aaa6a18edabd9897d3bea1ea2ca0 (patch) | |
| tree | 63698c0618f382a553d4558a33e3d2087039c378 /internal/casino/machine.go | |
| parent | 6d9908b29c8187196d23b126b1165c7e0bd0fc76 (diff) | |
| download | thehouseoficarus-8eea0e4e06a4aaa6a18edabd9897d3bea1ea2ca0.tar.gz | |
feat: consistent wager outcome table across casino games
Diffstat (limited to 'internal/casino/machine.go')
| -rw-r--r-- | internal/casino/machine.go | 49 |
1 files changed, 30 insertions, 19 deletions
diff --git a/internal/casino/machine.go b/internal/casino/machine.go index aad6352..610e745 100644 --- a/internal/casino/machine.go +++ b/internal/casino/machine.go @@ -246,6 +246,11 @@ func (m *Machine) autoBetTick() []Event { // intact until the feature completes. func (m *Machine) finishSpin() []Event { events := []Event{} + if len(m.spin.Wins) > 0 { + matches := m.private(formatWins(m.spin.Wins, m.profile.Ways)) + matches[0].Color = "casino_win" + events = append(events, matches...) + } result := m.spin.Payout + m.payoutRemainder // Checked conversion: a runaway profile or overflow must clamp at the // economy cap instead of wrapping through a machine-sized int. A NaN or @@ -275,9 +280,9 @@ func (m *Machine) finishSpin() []Event { } payFailed := false - if payNow > 0 && m.player.Wallet != nil { - // Credit the payout and consume the triggering stake in ONE save: a - // crash can then never pay the win AND refund the same stake. + if (!inFeature || featureEnds) && m.player.Wallet != nil { + // Credit the return (including zero) and consume the triggering stake + // in ONE durable save before announcing any completed settlement. if err := m.player.Wallet.PayCredits(payNow, m.stakeID); err != nil { // The payout could not be made durable. Do not announce the win as // paid, stop any autobet chain, and keep the stake PENDING @@ -306,25 +311,33 @@ func (m *Machine) finishSpin() []Event { } if payNow > 0 && !payFailed { message := fmt.Sprintf("\nYou win %d credits.", payNow) - switch { - case inFeature && !featureEnds: - // Banked, not spent: say so, or the player would read the win - // as already in their balance. - message = fmt.Sprintf("\nYou win %d credits, paid when the free spins end.", payNow) - case featureEnds: + if featureEnds { message = fmt.Sprintf("\nThe free spins pay %d credits.", payNow) } winEvents := m.private(message) + label := "Spin" + if featureEnds { + label = "Spin + free spins" + } + winEvents[0].Settlement = []SettlementLine{{Label: label, Odds: "Varies", Stake: m.spinBet, Returned: payNow, Result: "win"}} winEvents[0].Color = "casino_result_win" if payNow > m.spinBet { winEvents[0].Color = "casino_big_win" } events = append(events, winEvents...) } - if len(m.spin.Wins) > 0 { - winEvents := m.private(formatWins(m.spin.Wins, m.profile.Ways)) - winEvents[0].Color = "casino_win" - events = append(events, winEvents...) + if !payFailed && payNow == 0 { + line := SettlementLine{Label: "Spin", Odds: "Varies", Stake: m.spinBet, Result: "loss"} + if inFeature { + line.Label = "Spin + free spins" + } + line.Unresolved = inFeature && !featureEnds + receipt := m.private(settlementText([]settlementLine{line})) + receipt[0].Settlement = []SettlementLine{line} + if line.Unresolved { + receipt[0].SettlementNote = fmt.Sprintf("%d credits banked; paid when free spins end.", int(m.featureBank)) + } + events = append(events, receipt...) } // A paid feature in progress never retriggers: scatters landing during a // free spin are ignored, matching the CalculateRTP model (the feature is @@ -348,10 +361,8 @@ func (m *Machine) finishSpin() []Event { } else { m.inFreeSpins = false if !payFailed && m.player.Wallet != nil { - // A win already consumed this round's stake inside PayCredits; a - // loss (payNow == 0) has not, so consume it here. Either way the - // round is over and the stake must not be refunded. - m.player.Wallet.ResolveStake(m.stakeID) + // PayCredits already consumed this round's stake, even on a loss. + // Never resolve older stakes retained after failed settlements. m.stakeID = "" m.stakeFailed = false } @@ -504,9 +515,9 @@ func stoppedScatters(grid [slotGridRows][slotGridReels]string, reels int, trigge } func formatWins(wins []SlotWin, ways int) string { - message := fmt.Sprintf("Winning lines/ways (%d-way evaluation):\n", ways) + message := fmt.Sprintf("Matches (%d-way evaluation):\n", ways) for _, win := range wins { - message += fmt.Sprintf(" %s x%d: %d ways x %.4gx bet / %d = %.4f credits\n", win.Symbol, win.Length, win.Ways, win.Multiplier, ways, win.Payout) + message += fmt.Sprintf(" %s x%d: %d ways\n", win.Symbol, win.Length, win.Ways) } return message[:len(message)-1] } |
