diff options
Diffstat (limited to 'internal/casino/house_edge_baccarat.go')
| -rw-r--r-- | internal/casino/house_edge_baccarat.go | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/internal/casino/house_edge_baccarat.go b/internal/casino/house_edge_baccarat.go new file mode 100644 index 0000000..1598fd6 --- /dev/null +++ b/internal/casino/house_edge_baccarat.go @@ -0,0 +1,131 @@ +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 +} |
