package casino import ( "strings" "testing" "time" ) func TestSimplifiedCrapsAll36SettlementsAndHouseEdge(t *testing.T) { // Total returns for a one-credit wager, including the winning stake. returns := [13]int{2: 4, 3: 2, 4: 2, 10: 3, 11: 3, 12: 6} totalReturned, wins := 0, 0 for a := 1; a <= 6; a++ { for b := 1; b <= 6; b++ { tb := variantFixture("simplified") tb.join("a") w := &testWallet{chips: 100} tb.bet("a", "pass", 1, w) want := returns[a+b] for _, row := range tb.projectionFor(tb.players.get("a")) { if row.Total == a+b && row.Net != want-1 { t.Fatalf("preview for %d+%d: got %d, want %d", a, b, row.Net, want-1) } } tb.resolveRoll(a, b) if w.paid != want || len(w.open) != 0 || tb.players.get("a").HasBet || tb.point != 0 { t.Fatalf("roll %d+%d: paid %d, want %d; open %v, point %d", a, b, w.paid, want, w.open, tb.point) } totalReturned += w.paid if w.paid > 0 { wins++ } } } // 36 credits wagered, 35 returned: house edge exactly 1/36 = 2.777...%. if totalReturned != 35 || wins != 12 { t.Fatalf("all 36 rolls: returned %d, wins %d", totalReturned, wins) } } func TestSimplifiedCrapsAutomaticCountdownAndRoll(t *testing.T) { tb := variantFixture("simplified") tb.join("alice") w := &testWallet{chips: 100} events := tb.bet("alice", "pass", 10, w) if text := crapsEventText(events); strings.Contains(strings.ToLower(text), "shake") { t.Fatal("countdown started before the clock", text) } if v := tb.view(); v.ShootIn != 3 || v.DealerIn != 0 { t.Fatal("no 3-second automatic window", v.ShootIn, v.DealerIn) } if text := crapsEventText(tb.tickElapsed(600 * time.Millisecond)); strings.Contains(strings.ToLower(text), "dice") { t.Fatal("countdown fired early", text) } events = tb.tickElapsed(600 * time.Millisecond) // 1.2s text := crapsEventText(events) if !strings.Contains(text, "dice") || !strings.Contains(text, "alice") { t.Fatal("first countdown line missing", text) } if !events[0].Public || events[0].PrivateMessage == "" { t.Fatal("countdown must broadcast and speak to the shooter", events[0]) } if text := crapsEventText(tb.tickElapsed(600 * time.Millisecond)); strings.Contains(strings.ToLower(text), "luck") { t.Fatal("second countdown fired early", text) } events = tb.tickElapsed(600 * time.Millisecond) // 2.4s if text := crapsEventText(events); !strings.Contains(text, "luck") { t.Fatal("second countdown line missing", text) } if tb.dice != [2]int{} { t.Fatal("rolled before 3 seconds", tb.dice) } events = tb.tickElapsed(600 * time.Millisecond) // 3.0s text = strings.ToLower(crapsEventText(events)) if !strings.Contains(text, "shake") { t.Fatal("third countdown line missing", crapsEventText(events)) } if tb.dice == [2]int{} { t.Fatal("dice did not roll automatically") } if tb.view().ShootIn != 0 { t.Fatal("window stayed open after the roll") } // A rejected manual roll says the game rolls by itself. tb = variantFixture("simplified") tb.join("alice") w = &testWallet{chips: 100} tb.bet("alice", "pass", 10, w) if text := crapsEventText(tb.action("alice", "roll")); !strings.Contains(text, "rolls by itself") { t.Fatal(text) } if text := crapsEventText(tb.action("alice", "autoroll")); !strings.Contains(text, "rolls by itself") { t.Fatal(text) } // Multiplayer: a single shared countdown, no 10-second manual window. tb = variantFixture("simplified") tb.join("alice") tb.join("bob") w = &testWallet{chips: 1000} tb.bet("alice", "pass", 10, w) if text := crapsEventText(tb.syncRoundClock()); strings.Contains(text, "10 seconds") { t.Fatal("simplified opened a manual betting window", text) } tb.tickElapsed(3 * time.Second) if tb.dice == [2]int{} { t.Fatal("multiplayer countdown did not roll") } } func TestSimplifiedCrapsCommandsHideRollAndRebet(t *testing.T) { tb := variantFixture("simplified") tb.join("alice") w := &testWallet{chips: 100} tb.bet("alice", "pass", 10, w) tb.tickElapsed(3 * time.Second) cmds := tb.commands("alice") for _, c := range cmds { if c.Command == "roll" || c.Command == "autoroll" { t.Fatal("simplified offered a roll command", c) } } joined := make([]string, len(cmds)) for i, c := range cmds { joined[i] = c.Command } if strings.Contains(strings.Join(joined, "|"), "on pass") { t.Fatal("simplified bet command still names a target", joined) } spot, amount := tb.rebet("alice") if spot != "pass" || amount != 10 { t.Fatal("bare bet must rebet the last wager", spot, amount) } } func TestSimplifiedCrapsBetDefaultsToPass(t *testing.T) { tb := variantFixture("simplified") tb.join("alice") w := &testWallet{chips: 100} tb.bet("alice", "", 10, w) bets := tb.players.get("alice").bets if len(bets) != 1 || bets[0].kind != "pass" { t.Fatal("targetless bet did not land on pass", bets) } }