diff options
| author | historia <[not public]> | 2026-10-02 01:54:37 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-10-02 01:54:37 -0400 |
| commit | d3a305d1d7962f82af2397760cf8008758bddd1d (patch) | |
| tree | f91f0deb342c20f2993e5366f772c3432648f426 /internal/casino/house_edge_rules_test.go | |
| parent | 8eea0e4e06a4aaa6a18edabd9897d3bea1ea2ca0 (diff) | |
| download | thehouseoficarus-d3a305d1d7962f82af2397760cf8008758bddd1d.tar.gz | |
feat: better 'list' bet table, multiple bets in baccarat
Diffstat (limited to 'internal/casino/house_edge_rules_test.go')
| -rw-r--r-- | internal/casino/house_edge_rules_test.go | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/internal/casino/house_edge_rules_test.go b/internal/casino/house_edge_rules_test.go new file mode 100644 index 0000000..b79888f --- /dev/null +++ b/internal/casino/house_edge_rules_test.go @@ -0,0 +1,148 @@ +package casino + +import ( + "math" + "math/rand" + "strings" + "testing" +) + +func TestBlackjackEdgeRules(t *testing.T) { + no, yes := false, true + base := BlackjackConfig{DoubleSplitBlackjack: &no}.Normalize() + want := blackjackMainEdge(base) + if math.Abs(want-.0043) > 1e-10 { + t.Fatalf("standard eight decks: %g", want) + } + cases := []struct { + name string + change func(*BlackjackConfig) + higher bool + }{ + {"one deck", func(r *BlackjackConfig) { r.Decks = 1 }, false}, + {"two decks", func(r *BlackjackConfig) { r.Decks = 2 }, false}, + {"H17", func(r *BlackjackConfig) { r.DealerHitSoft17 = &yes }, true}, + {"6:5", func(r *BlackjackConfig) { r.BlackjackPayout = 1.2 }, true}, + {"no DAS", func(r *BlackjackConfig) { r.DoubleAfterSplit = &no }, true}, + {"surrender", func(r *BlackjackConfig) { r.Surrender = &yes }, false}, + {"no resplit", func(r *BlackjackConfig) { r.MaxSplitHands = 2 }, true}, + {"no split", func(r *BlackjackConfig) { r.MaxSplitHands = 1 }, true}, + {"resplit aces", func(r *BlackjackConfig) { r.ResplitAces = &yes }, false}, + {"double split blackjack", func(r *BlackjackConfig) { r.DoubleSplitBlackjack = &yes }, false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + r := base + tc.change(&r) + got := blackjackMainEdge(r) + if (got > want) != tc.higher || math.Abs(got-want) < 1e-8 { + t.Fatalf("edge %g base %g", got, want) + } + }) + } + h17 := base + h17.DealerHitSoft17 = &yes + if math.Abs(blackjackMainEdge(h17)-want-.0022) > .0003 { + t.Fatal("H17 adjustment departed from published reference") + } + for _, action := range []string{"hit", "stand", "split", "double", "decline", "surrender"} { + if got := (TableConfig{Game: GameBlackjack}).HouseEdge(action).Text(2); got != "" { + t.Errorf("action %s has standalone edge: %s", action, got) + } + } +} + +func TestVideoPokerEdgeEveryCabinetSchedule(t *testing.T) { + for cabinet, games := range cabinetGames { + for _, variant := range games { + for _, tier := range []string{"low", "normal", "full"} { + c := (VideoPokerConfig{Cabinet: cabinet, Payout: tier, Variant: variant}).Normalize() + for _, hands := range CabinetHandCounts(cabinet, variant) { + for _, feature := range []bool{false, true} { + for coins := 1; coins <= 5; coins++ { + e := pokerHouseEdge(c, "main", coins, feature, hands) + if len(e.Parts) != 1 || math.IsNaN(e.Parts[0].Loss) { + t.Fatalf("missing edge %s %s %s hands=%d feature=%v coins=%d: %+v", cabinet, variant, tier, hands, feature, coins, e) + } + } + } + } + } + } + } +} + +func TestVideoPokerEdgeReferences(t *testing.T) { + for _, tc := range []struct { + c VideoPokerConfig + coins int + feature bool + hands int + want float64 + }{ + {VideoPokerConfig{}, 5, false, 1, .00456096}, + {VideoPokerConfig{Payout: "low"}, 5, false, 1, .038528}, + {VideoPokerConfig{}, 1, false, 1, .00456096 + 550*.00002476}, + {VideoPokerConfig{Cabinet: CabinetUXSingle}, 5, true, 1, .0028}, + {VideoPokerConfig{Cabinet: CabinetUXMulti}, 5, true, 3, .007022}, + {VideoPokerConfig{Cabinet: CabinetUXMulti}, 5, true, 10, .005780}, + {VideoPokerConfig{Cabinet: CabinetSuperTimesPay}, 5, true, 1, 1 - .99543904*(18.05/15)*5/6}, + {VideoPokerConfig{Cabinet: CabinetHotRoll}, 5, true, 3, .00456096}, + {VideoPokerConfig{Variant: "deuces_wild"}, 5, false, 1, -.007620}, + } { + e := pokerHouseEdge(tc.c, "main", tc.coins, tc.feature, tc.hands) + if len(e.Parts) != 1 || math.Abs(e.Parts[0].Loss-tc.want) > 1e-9 { + t.Fatalf("%+v: %+v want %g", tc, e, tc.want) + } + } +} + +func TestHouseEdgeUsesVideoPokerSeat(t *testing.T) { + cfg := (TableConfig{Game: GameVideoPoker}).Normalize() + game := newVideoPokerTable(1, cfg, rand.New(rand.NewSource(1))).(*videoPokerTable) + game.players["alice"] = &pokerPlayer{config: (VideoPokerConfig{Variant: "deuces_wild"}).Normalize(), loaded: 5, hands: 1} + table := &Table{game: game} + if got := table.HouseEdge("alice", "main").Text(2); got != "House edge: -0.76%" { + t.Fatal(got) + } + game.players["alice"].loaded = 1 + if got := table.HouseEdge("alice", "main").Text(2); got == "House edge: -0.76%" { + t.Fatal("coin selection ignored") + } +} + +func TestPaiGowEdgeReferences(t *testing.T) { + for _, tc := range []struct { + r PaiGowConfig + bet string + want float64 + }{ + {PaiGowConfig{}, "fortune", .034318}, + {PaiGowConfig{Envy: true}, "fortune", .044292}, + {PaiGowConfig{Variant: PaiGowFaceUp, PushAceHigh: "5_15_40"}, "pushace", .0929}, + {PaiGowConfig{Variant: PaiGowFaceUp, PushAceHigh: "5_12_40"}, "pushace", .1052}, + } { + e := paiGowHouseEdge(TableConfig{PaiGow: tc.r}, tc.bet) + if math.Abs(e.Parts[0].Loss-tc.want) > .000001 { + t.Fatalf("%+v: %+v", tc, e) + } + } + for _, variant := range []string{PaiGowClassic, PaiGowEZ, PaiGowNoPush, PaiGowFaceUp} { + if e := paiGowHouseEdge(TableConfig{PaiGow: PaiGowConfig{Variant: variant}}, "main"); len(e.Parts) != 1 { + t.Fatal(variant, e) + } + } + if fortuneEnvyReturn(2, 5) <= fortuneEnvyReturn(1, 5) || fortuneEnvyReturn(2, 4) != 0 { + t.Fatal("Envy eligibility/scaling") + } +} + +func TestHouseEdgeTextOnlyPercentage(t *testing.T) { + e := HouseEdge{Parts: []EdgePart{{Label: "something", Loss: .027027}}, Note: "explanation"} + if e.Text(0) != "House edge: 3%" || e.Text(2) != "House edge: 2.70%" { + t.Fatal(e.Text(2)) + } + if strings.Contains(e.Text(2), "explanation") || unknownEdge("unknown").Text(2) != "" { + t.Fatal("extra prose") + } +} |
