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
|
package game
import (
"strings"
"testing"
"thehouseoficarus/internal/casino"
"thehouseoficarus/internal/color"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
func TestRouletteTrackerFormats(t *testing.T) {
for _, mode := range []string{"none", "ansi", "xterm256"} {
for _, unicode := range []bool{false, true} {
for _, history := range [][]string{nil, {"0"}, {"1", "2", "00", "1", "36", "00", "36", "36", "2", "0"}} {
s := &casino.RouletteSnapshot{DoubleZero: true, History: history}
art := renderRouletteTracker(s, mode, unicode)
rows := strings.Split(art, "\n")
if len(rows) != 5 {
t.Fatalf("tracker has %d rows", len(rows))
}
for _, row := range rows {
if color.VisibleLen(row) != 74 {
t.Errorf("tracker row width %d: %q", color.VisibleLen(row), plainRoulette(row))
}
if !unicode {
for _, r := range plainRoulette(row) {
if r > 127 {
t.Errorf("non-ASCII rune %q", r)
}
}
}
}
if mode == "none" && strings.Contains(art, "\x1b") {
t.Fatal("color disabled but ANSI present")
}
if mode != "none" {
for _, bg := range []int{0, 1, 2} {
if !strings.Contains(art, color.BgCode(mode, bg)) {
t.Errorf("missing color %d", bg)
}
}
}
plain := plainRoulette(art)
if strings.Contains(plain, "History, not odds") {
t.Fatal("tracker should not carry the removed caption")
}
if strings.Contains(plain, "newest first") {
t.Fatal("tracker should not label the recent order")
}
if len(history) == 0 {
for _, want := range []string{"last 0 spins", "awaiting the first spin", "Hot: --", "Cold: --", "Red --", "Black --"} {
if !strings.Contains(plain, want) {
t.Errorf("missing %q", want)
}
}
}
}
}
}
}
func TestRouletteTrackerStatistics(t *testing.T) {
s := &casino.RouletteSnapshot{DoubleZero: true, History: []string{"1", "2", "00", "1", "36", "00", "36", "36", "2", "0"}}
plain := plainRoulette(renderRouletteTracker(s, "none", true))
for _, want := range []string{"last 10 spins", "Red 50.0%", "Black 20.0%", "Zero 30.0%", "Hot: 36R (3) 00G (2) 1R (2)", "Cold: 3R (0) 4B (0) 5R (0)"} {
if !strings.Contains(strings.Join(strings.Fields(plain), " "), want) {
t.Errorf("missing %q:\n%s", want, plain)
}
}
// Only the newest eight, newest on the left, and 00 stays distinct from 0.
recent := strings.Split(plain, "\n")[1]
hits := racetrackNumbers(recent)
if got := strings.Join(hits, ","); got != "0,2,36,36,00,36,1,00" {
t.Errorf("recent hits: %s", got)
}
// Unhit pockets aren't called hot. Cold tie order includes 00 only on
// American wheels; it is not silently merged with zero.
s.History = []string{"0"}
if line := strings.Split(renderRouletteTracker(s, "none", false), "\n")[2]; !strings.Contains(strings.Join(strings.Fields(line), " "), "Hot: 0G (1) | Cold: 00G (0)") {
t.Errorf("zero ranking: %s", line)
}
s.DoubleZero = false
if strings.Contains(renderRouletteTracker(s, "none", false), "00G") {
t.Fatal("European tracker contains 00")
}
}
func TestRouletteTrackerAboveBettingBoardsOnly(t *testing.T) {
for _, racetrack := range []bool{false, true} {
s := &casino.RouletteSnapshot{Racetrack: racetrack, Phase: "betting", History: []string{"17"}, Wheel: casino.WheelOrder(false)}
art := renderRouletteSnapshot(s, "none", true, 7)
tracker := renderRouletteTracker(s, "none", true)
if !strings.HasPrefix(art, "\n"+tracker+"\n") {
t.Fatal("tracker not above board")
}
for _, phase := range []string{"playing", "result"} {
s.Phase = phase
s.Pockets = [5]string{"25", "17", "34", "6", "27"}
if strings.Contains(renderRouletteSnapshot(s, "none", true, 7), "History:") {
t.Fatalf("tracker appeared during %s", phase)
}
}
}
}
func TestRouletteRulesUseLiveHistory(t *testing.T) {
g := &Game{Casino: casino.NewManager(7)}
p := player.New("alice")
p.RoomID, p.Credits = 1, 100
p.Options = map[string]any{"color": "none", "unicode": true}
sess := &net.Session{Player: p}
cfg := casino.TableConfig{ID: "r", Game: casino.GameRoulette, BettingTicks: 1, Roulette: casino.RouletteConfig{SpinTicks: 1}}
g.Casino.Join(1, cfg, p.Name)
g.Casino.Bet(1, "r", p.Name, "red", 1, casinoWallet{g: g, p: p, saveCharacter: func(*player.Player) error { return nil }})
g.Casino.Tick()
g.Casino.Tick()
art := g.renderCasinoRules(sess, cfg)
if !strings.Contains(art, "History: last 1 spin") || strings.Contains(art, "awaiting the first spin") {
t.Fatalf("rules did not show live history:\n%s", art)
}
}
|