diff options
| author | historia <[not public]> | 2026-09-29 18:58:28 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-29 18:58:28 -0400 |
| commit | 624626b9a10ae47cf7d5f6e91765570ed759bd74 (patch) | |
| tree | 9812f5d254b7d9cfe1d2fcb6da53362e974541b0 /internal/casino/roulette_geometry_test.go | |
| parent | ff9002b96e35ad0808945e7c35791460c9ca8776 (diff) | |
| download | thehouseoficarus-624626b9a10ae47cf7d5f6e91765570ed759bd74.tar.gz | |
feat: better roulette racetrack graphics and payout table
Diffstat (limited to 'internal/casino/roulette_geometry_test.go')
| -rw-r--r-- | internal/casino/roulette_geometry_test.go | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/internal/casino/roulette_geometry_test.go b/internal/casino/roulette_geometry_test.go new file mode 100644 index 0000000..37fec74 --- /dev/null +++ b/internal/casino/roulette_geometry_test.go @@ -0,0 +1,109 @@ +package casino + +import ( + "fmt" + "testing" +) + +func TestRouletteRejectsNonTouchingCombinations(t *testing.T) { + for _, tc := range []struct { + target string + double bool + }{ + {"corner 1 3 4 6", false}, {"corner 1 2 7 8", false}, + {"corner 1 3 34 36", true}, {"trio 0 1 3", false}, + {"trio 0 1 3", true}, {"trio 0 2 3", true}, + {"trio 00 1 2", true}, {"trio 0 00 1", true}, + {"trio 0 00 3", true}, {"split 0 3", true}, + {"split 00 1", true}, + } { + if _, mult := rouletteBet(tc.target, tc.double); mult != 0 { + t.Errorf("accepted %q (double=%v)", tc.target, tc.double) + } + } + for _, double := range []bool{false, true} { + for c := 0; c < 11; c++ { + for r := 1; r <= 2; r++ { + n := 3*c + r + target := fmt.Sprintf("corner %d %d %d %d", n, n+1, n+3, n+4) + if _, mult := rouletteBet(target, double); mult != 9 { + t.Errorf("rejected %s", target) + } + } + } + for _, target := range []string{"split 0 1", "split 0 2", "trio 0 1 2"} { + if _, mult := rouletteBet(target, double); mult == 0 { + t.Errorf("rejected %s", target) + } + } + } + for _, target := range []string{"split 00 2", "split 00 3", "trio 00 2 3", "trio 0 00 2"} { + if _, mult := rouletteBet(target, true); mult == 0 { + t.Errorf("rejected %s", target) + } + } +} + +// Sum the return over every equally likely outcome. Ordinary bets return +// 36 stakes per wheel; the American five-number basket returns only 35. +func TestRouletteWheelAndExpectedReturns(t *testing.T) { + for _, double := range []bool{false, true} { + wheel := WheelOrder(double) + want := 37 + if double { + want = 38 + } + seen := map[string]bool{} + for _, p := range wheel { + if _, valid := roulettePocketToken(p, double); !valid || seen[p] { + t.Fatalf("invalid wheel pocket %s", p) + } + seen[p] = true + } + if len(seen) != want { + t.Fatalf("wheel has %d pockets, want %d", len(seen), want) + } + targets := append(WheelOrder(double), "red", "black", "odd", "even", "low", "high", "1to12", "13to24", "25to36", "row1", "row2", "row3", "split 1 2", "street 1 2 3", "corner 1 2 4 5", "line 1 2 3 4 5 6", "trio 0 1 2", "basket") + for _, target := range targets { + wager, ok := rouletteWagerFor(target, double, false) + if !ok { + t.Fatalf("invalid target %s", target) + } + sum := 0 + for _, p := range wheel { + share, _ := wager.covers(p) + payout, err := share.payout(1) + if err != nil { + t.Fatal(err) + } + sum += payout + } + wantReturn := 36 + if double && target == "basket" { + wantReturn = 35 + } + if sum != wantReturn { + t.Errorf("%s: total return %d, want %d", target, sum, wantReturn) + } + } + } +} + +func TestRouletteSharePayoutCap(t *testing.T) { + shares := []rouletteShare{{36, 1}, {7, 1}, {36, 9}} + for _, bet := range rouletteCallBets { + for _, share := range bet.wager().shares { + shares = append(shares, share) + } + } + for _, share := range shares { + limit := ((MaxCredits+1)*share.den - 1) / share.num + got, err := share.payout(limit) + if err != nil || got > MaxCredits { + t.Fatalf("%+v at limit: %d, %v", share, got, err) + } + if _, err := share.payout(limit + 1); err == nil { + t.Fatalf("%+v accepted overflow", share) + } + } +} |
