aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/roulette_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-29 13:28:06 -0400
committerhistoria <[not public]>2026-09-29 13:28:06 -0400
commit9803facd4368420a5dce3af59c835e13cb015cf5 (patch)
tree4ecb75f9c88b16ae72d3573bef026d760eb8d7a8 /internal/casino/roulette_test.go
parent13db3e50cb6e29eedbf25416e5284bf08b0adefb (diff)
downloadthehouseoficarus-9803facd4368420a5dce3af59c835e13cb015cf5.tar.gz
feat: roulette in domita casino
Diffstat (limited to 'internal/casino/roulette_test.go')
-rw-r--r--internal/casino/roulette_test.go171
1 files changed, 171 insertions, 0 deletions
diff --git a/internal/casino/roulette_test.go b/internal/casino/roulette_test.go
new file mode 100644
index 0000000..8defe1e
--- /dev/null
+++ b/internal/casino/roulette_test.go
@@ -0,0 +1,171 @@
+package casino
+
+import (
+ "math/rand"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+func TestRouletteRulesAndWheels(t *testing.T) {
+ for _, double := range []bool{false, true} {
+ wheel := rouletteWheel(double)
+ seen := map[string]bool{}
+ want := 37
+ if double {
+ want = 38
+ }
+ if len(wheel) != want {
+ t.Fatal("wheel size", len(wheel))
+ }
+ for _, pocket := range wheel {
+ if seen[pocket] {
+ t.Fatal("duplicate", pocket)
+ }
+ seen[pocket] = true
+ }
+ for n := 0; n <= 36; n++ {
+ if !seen[strconv.Itoa(n)] {
+ t.Fatal("missing", n)
+ }
+ }
+ if seen["00"] != double {
+ t.Fatal("wrong double zero")
+ }
+ for _, spot := range strings.Fields("red black even odd low high dozen1 dozen2 dozen3 column1 column2 column3") {
+ pockets, mult := rouletteBet(spot, double)
+ if len(pockets)*mult != 36 {
+ t.Fatalf("%s coverage/payout %d/%d", spot, len(pockets), mult)
+ }
+ for _, p := range pockets {
+ if p == "0" || p == "00" {
+ t.Fatal("zero in outside bet")
+ }
+ }
+ }
+ pockets, mult := rouletteBet("00", double)
+ if double && (len(pockets) != 1 || mult != 36) || !double && mult != 0 {
+ t.Fatal("00 rules")
+ }
+ }
+ for _, spot := range []string{"-1", "37", "000", "01", "banana", ""} {
+ if _, m := rouletteBet(spot, true); m != 0 {
+ t.Fatal("accepted", spot)
+ }
+ }
+}
+func rouletteFixture() *rouletteTable {
+ return newRouletteTable(1, TableConfig{ID: "r", Game: GameRoulette, BettingTicks: 2, Roulette: RouletteConfig{SpinTicks: 2}}, rand.New(rand.NewSource(7)))
+}
+func TestRouletteTimedPhasesAndSettlement(t *testing.T) {
+ for _, double := range []bool{false, true} {
+ table := rouletteFixture()
+ table.Config.Roulette.DoubleZero = double
+ wheel := rouletteWheel(double)
+ index := rand.New(rand.NewSource(7)).Intn(len(wheel))
+ table.join("a")
+ w := &testWallet{chips: 100}
+ table.bet("a", wheel[index], 10, w)
+ table.bet("a", "red", 10, w)
+ if w.chips != 90 || table.phase != phaseBetting {
+ t.Fatal("duplicate or early spin")
+ }
+ table.tick()
+ if table.phase != phaseBetting {
+ t.Fatal("betting ended early")
+ }
+ events := table.tick()
+ if table.phase != phasePlaying || len(events) == 0 {
+ t.Fatal("no spinning phase")
+ }
+ table.join("b")
+ other := &testWallet{chips: 100}
+ table.bet("b", "red", 10, other)
+ if other.chips != 100 {
+ t.Fatal("bet while spinning")
+ }
+ table.tick()
+ if w.paid != 0 {
+ t.Fatal("settled early")
+ }
+ table.action("a", "leave_after")
+ events = table.tick()
+ if w.paid != 360 || len(w.open) != 0 || table.hasParticipant("a") {
+ t.Fatalf("bad settlement: %+v", w)
+ }
+ s := events[0].Snapshot.(*RouletteSnapshot)
+ if s.Pockets != [3]string{wheel[(index+len(wheel)-1)%len(wheel)], wheel[index], wheel[(index+1)%len(wheel)]} {
+ t.Fatal("incorrect wheel neighbors")
+ }
+ table.tick()
+ table.refundAll()
+ if w.paid != 360 {
+ t.Fatal("double paid")
+ }
+ }
+}
+func TestRouletteRefundAndPersistenceFailure(t *testing.T) {
+ for _, spinning := range []bool{false, true} {
+ table := rouletteFixture()
+ table.join("a")
+ w := &testWallet{chips: 100}
+ table.bet("a", "red", 10, w)
+ if spinning {
+ table.tick()
+ table.tick()
+ }
+ table.markDisconnected("a")
+ table.refundAll()
+ table.tick()
+ if w.chips != 100 || len(w.open) != 0 || w.paid != 0 {
+ t.Fatalf("disconnect refund: %+v", w)
+ }
+ }
+ table := rouletteFixture()
+ table.join("a")
+ w := &failingPayoutWallet{testWallet: testWallet{chips: 100}}
+ table.bet("a", "red", 10, w)
+ for i := 0; i < 4; i++ {
+ table.tick()
+ }
+ if len(w.open) != 1 {
+ t.Fatal("failed settlement consumed stake")
+ }
+ table.refundAll()
+ table.refundAll()
+ if w.chips != 100 || len(w.open) != 0 {
+ t.Fatal("failed settlement not refunded once")
+ }
+}
+func TestRouletteViewsAndConfig(t *testing.T) {
+ for _, double := range []bool{false, true} {
+ s := &RouletteSnapshot{DoubleZero: double, Phase: "playing"}
+ wheel := s.ASCII()
+ if strings.Contains(wheel, "00") != double || strings.Count(wheel, "[ 0 ]") != 1 {
+ t.Fatal("wrong wheel zero pockets", wheel)
+ }
+ s.Phase = "betting"
+ layout := s.ASCII()
+ if !strings.Contains(layout, "column3") || !strings.Contains(layout, "36R") || strings.Contains(layout, "[00 G]") != double {
+ t.Fatal("bad layout", layout)
+ }
+ s.Phase = "result"
+ s.Pockets = [3]string{"26", "0", "32"}
+ if !strings.Contains(s.ASCII(), "| 26 | 0 | 32 |") {
+ t.Fatal(s.ASCII())
+ }
+ }
+ cfg := TableConfig{ID: "r", Game: GameRoulette}
+ if err := cfg.Validate(); err != nil {
+ t.Fatal(err)
+ }
+ cfg.Roulette.SpinTicks = -1
+ if cfg.Validate() == nil {
+ t.Fatal("invalid ticks accepted")
+ }
+ cfg.Roulette.SpinTicks = 2
+ cfg.Game = GameBlackjack
+ if cfg.Validate() == nil {
+ t.Fatal("foreign rule block accepted")
+ }
+}