package casino import ( "fmt" "strings" ) // Attach receipts to the recipient's final board/menu where possible, so the // renderer can place them between the reveal and the next-action prompt. // Departing recipients still receive their standalone private receipt. func withSettlementReceipts(events, receipts []Event) []Event { for _, receipt := range receipts { attached := false for i := len(events) - 1; i >= 0; i-- { if events[i].PrivateTo == receipt.PrivateTo && events[i].Snapshot != nil && !events[i].Public { events[i].Settlement = receipt.Settlement events[i].SettlementNote = receipt.SettlementNote attached = true break } } if !attached { events = append(events, receipt) } } return events } // profitOdds reduces an exact profit fraction without rounding currency. func profitOdds(num, den int) string { if den <= 0 { return "Varies" } a, b := num, den if a < 0 { a = -a } for b != 0 { a, b = b, a%b } if a == 0 { return "0:1" } return fmt.Sprintf("%d:%d", num/a, den/a) } func returnResult(stake, returned int) string { switch { case returned > stake: return "win" case returned < stake: return "loss" default: return "push" } } func rouletteSettlementOdds(w rouletteWager, pocket string) string { if s, ok := w.covers(pocket); ok { return profitOdds(s.num-s.den, s.den) } odds := "" for _, s := range w.shares { next := profitOdds(s.num-s.den, s.den) if odds != "" && odds != next { return "Varies" } odds = next } if odds == "" { return "Varies" } return odds } func baccaratSettlementOdds(spot string, player, banker []card, rules BaccaratConfig) string { rules = rules.Normalize() switch spot { case baccaratSpotPlayer: return "1:1" case baccaratSpotBanker: if rules.Variant == BaccaratSuper6 && baccaratTotal(banker) > baccaratTotal(player) && baccaratTotal(banker) == 6 { return "1:2" } return payoutRatio(rules.BankerPayout) case baccaratSpotTie: return payoutRatio(rules.TiePayout) case "player_pair", "banker_pair": return "11:1" case "dragon_7": return "40:1" case "panda_8": return "25:1" case "super_6": return "12:1" default: return "Varies" } } // For losing or active bets, inspect legal winning outcomes. Variable games // (field, horn and variant pass lines) must not advertise one fixed rate. func (t *crapsTable) settlementOdds(b *crapsBet) string { odds := "" for _, pair := range t.rollPairs() { done, num, den := t.outcomeFor(b, pair[0], pair[1]) if !done || num <= 0 { continue } next := profitOdds(num, den) if odds != "" && odds != next { return "Varies" } odds = next } if odds == "" { odds = "Varies" } if b.kind == "buy" || b.kind == "lay" { odds += " fee" } return odds } // Keep the base bet and its free-odds wager separate, including come-out // refunds where the base loses but the odds push. func (t *crapsTable) crapsSettlementLines(b *crapsBet, num, den int, refundOdds, active bool) []SettlementLine { base := *b base.odds = Wager{} returned := crapsReturn(&base, num, den, num, false) odds := t.settlementOdds(b) if num > 0 && !active { odds = profitOdds(num, den) if b.kind == "buy" || b.kind == "lay" { odds += " fee" } } lines := []SettlementLine{{Label: crapsBetLabel(b), Odds: odds, Stake: b.stake.Total, Returned: returned, Result: returnResult(b.stake.Total, returned), Unresolved: active}} if b.odds.Total > 0 { x, y := crapsTrueOdds(b.number) if strings.HasPrefix(b.kind, "dont") { x, y = y, x } paid := 0 if refundOdds { paid = b.odds.Total } else if num >= 0 { paid = b.odds.Total + b.odds.Total/y*x } lines = append(lines, SettlementLine{Label: crapsBetLabel(b) + " odds", Odds: profitOdds(x, y), Stake: b.odds.Total, Returned: paid, Result: returnResult(b.odds.Total, paid), Unresolved: active}) } return lines }