aboutsummaryrefslogtreecommitdiff
path: root/internal/game/render_roulette_tracker_test.go
blob: a629b579d3f33f9a96b0fe3cc133bee57b68ceea (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
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)
	}
}