aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/craps_simplified_test.go
blob: 705204bfcde26f8b6e24f476766c141fb2ed29d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package casino

import (
	"strings"
	"testing"
)

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-tick automatic window", v.ShootIn, v.DealerIn)
	}
	events = tb.advance(1)
	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])
	}
	events = tb.advance(1)
	if text := crapsEventText(events); !strings.Contains(text, "luck") {
		t.Fatal("second countdown line missing", text)
	}
	if tb.dice != [2]int{} {
		t.Fatal("rolled before the final tick", tb.dice)
	}
	events = tb.advance(1)
	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 ticks") {
		t.Fatal("simplified opened a manual betting window", text)
	}
	tb.advance(3)
	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.advance(3)
	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)
	}
}