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 1to12 13to24 25to36 row1 row2 row3") { 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 TestRouletteRowBetMapping(t *testing.T) { // row1 is the top numbered line (3,6,...36) and row3 the bottom (1,4,...34). row1, mult := rouletteBet("row1", false) if mult != 3 || len(row1) != 12 || row1[0] != "3" || row1[len(row1)-1] != "36" { t.Fatalf("row1 = %v (x%d), want 3..36", row1, mult) } row3, mult := rouletteBet("row3", false) if mult != 3 || len(row3) != 12 || row3[0] != "1" || row3[len(row3)-1] != "34" { t.Fatalf("row3 = %v (x%d), want 1..34", row3, mult) } } 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 TestRouletteConfig(t *testing.T) { 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") } }