package casino import ( "strings" "sync" ) type baccaratProbabilities struct { player, banker, tie, dragon, panda, six float64 } var baccaratEdgeCache sync.Map // deck count -> baccaratProbabilities func baccaratHouseEdge(cfg TableConfig, bet string) HouseEdge { c := cfg.Baccarat.Normalize() spot := strings.Fields(bet) if len(spot) == 0 { return unknownEdge("unrecognized baccarat target") } p := baccaratFreshProbabilities(c.Decks) var e HouseEdge switch spot[0] { case "player": e = edge(p.banker - p.player) case "banker": loss := p.player - c.BankerPayout*p.banker if c.Variant == BaccaratSuper6 { loss += .5 * p.six } if c.Variant == BaccaratEZ { loss += p.dragon } e = edge(loss) case "tie": e = edge(1 - (1+c.TiePayout)*p.tie) case "player_pair", "banker_pair": e = edge(1 - 12*float64(4*c.Decks-1)/float64(52*c.Decks-1)) case "dragon_7": e = edge(1 - 41*p.dragon) case "panda_8": e = edge(1 - 26*p.panda) case "super_6": e = edge(1 - 13*p.six) default: return unknownEdge("unrecognized baccarat target") } e.Note = "fresh shoe; before fractional-credit rounding" return e } // Enumerate value sequences WITHOUT replacement from the configured shoe. // Tens/J/Q/K coalesce into value zero; rank-pair probability is separately // exact by symmetry. At most 10^6 branches, cached by deck count. Uses the // same banker tableau as playTableau; no sampled or generic casino rates. func baccaratFreshProbabilities(decks int) baccaratProbabilities { if v, ok := baccaratEdgeCache.Load(decks); ok { return v.(baccaratProbabilities) } var out baccaratProbabilities counts := [10]int{} for i := range counts { counts[i] = 4 * decks } counts[0] = 16 * decks var cards [6]int var deal func(int, float64) finish := func(pt, bt, pn, bn int, prob float64) { switch { case pt > bt: out.player += prob if pn == 3 && pt == 8 { out.panda += prob } case bt > pt: out.banker += prob if bn == 3 && bt == 7 { out.dragon += prob } if bt == 6 { out.six += prob } default: out.tie += prob } } deal = func(n int, prob float64) { if n >= 4 { pt, bt := (cards[0]+cards[1])%10, (cards[2]+cards[3])%10 if pt >= 8 || bt >= 8 { finish(pt, bt, 2, 2, prob) return } playerDraw := pt <= 5 if playerDraw && n == 4 { // draw Player's third below } else { third, pn := -1, 2 if playerDraw { third = cards[4] pt = (pt + third) % 10 pn = 3 } bankerDraw := (third < 0 && bt <= 5) || (third >= 0 && bankerDraws(bt, third)) if !bankerDraw { finish(pt, bt, pn, 2, prob) return } needed := 5 if playerDraw { needed = 6 } if n == needed { finish(pt, (bt+cards[n-1])%10, pn, 3, prob) return } } } remaining := float64(52*decks - n) for value, count := range counts { if count == 0 { continue } counts[value]-- cards[n] = value deal(n+1, prob*float64(count)/remaining) counts[value]++ } } deal(0, 1) baccaratEdgeCache.Store(decks, out) return out }