aboutsummaryrefslogtreecommitdiff
path: root/internal/game/casino_commands_test.go
blob: 0ae1593f260f5e99c70241b61e0f5e40457f429c (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
package game

import (
	"strings"
	"testing"

	"thehouseoficarus/internal/casino"
	"thehouseoficarus/internal/color"
	"thehouseoficarus/internal/net"
	"thehouseoficarus/internal/player"
)

func TestCasinoCommandsWrapBeforeSessionWithoutLosingIndent(t *testing.T) {
	commands := []casino.MenuCommand{
		{Command: "bet <amount> on <target>", Desc: "Place bets until the dice roll; table rules lists targets"},
		{Command: "roll / autoroll / stop", Desc: "Shoot / repeat on your turn / return to manual play"},
		{Command: "unbet <target> / unbet all", Desc: "Remove one bet / every optional bet"},
		{Command: "table", Desc: "View the felt"},
		{Command: "insurance", Desc: "Buy insurance; a deliberately long description which requires a hanging continuation line"},
		{Command: "decline", Desc: "Continue without insurance (also the timeout default)"},
		{Command: "bet <amount> on player/banker/tie/player_pair/banker_pair", Desc: "One wager per round; 'on' is optional"},
		{Command: "option casino_display compact", Desc: "Compact action hints (detailed for full command menus)"},
		{Command: "spin", Desc: "Spin using your current wager"},
		{Command: "autospin", Desc: "Start automatic betting"},
		{Command: "unicode", Desc: strings.Repeat("é", 100)},
	}
	for _, wrap := range []int{0, 40, 60, 80, 120} {
		p := player.New("a")
		p.Options = map[string]any{"wrap_width": wrap}
		conn := &casinoTestConn{}
		sess := &net.Session{Player: p, Conn: conn}
		g := &Game{}
		text := g.casinoMenuCommands(sess, commands)
		sess.WriteLine(text)
		limit := 80
		if wrap > 0 {
			limit = min(limit, wrap)
		}
		for _, line := range strings.Split(strings.ReplaceAll(plainRoulette(string(conn.data)), "\r\n", "\n"), "\n") {
			if line == "" {
				continue
			}
			if color.VisibleLen(line) > limit {
				t.Fatalf("wrap %d overlong %d: %q", wrap, color.VisibleLen(line), line)
			}
			if !strings.HasPrefix(line, "  ") {
				t.Fatalf("wrap %d lost indent: %q", wrap, line)
			}
		}
		expected := strings.ReplaceAll(plainRoulette(text), "\r\n", "\n")
		actual := strings.TrimRight(strings.ReplaceAll(plainRoulette(string(conn.data)), "\r\n", "\n"), "\n")
		if expected != actual {
			t.Fatalf("session reflowed prewrapped menu at %d", wrap)
		}
	}
}
func TestCasinoCommandsHangingIndentAndShortDescription(t *testing.T) {
	sess := &net.Session{Player: player.New("a")}
	g := &Game{}
	commands := []casino.MenuCommand{
		{Command: "bet <amount> on <target>", Desc: "Place a wager; targets: table rules"},
		{Command: "table", Desc: "View the felt"},
	}
	text := plainRoulette(g.casinoMenuCommands(sess, commands))
	lines := strings.Split(text, "\n")
	if len(lines) != 2 {
		t.Fatal("short commands should not wrap", text)
	}
	col := strings.Index(lines[0], "Place")
	if col != strings.Index(lines[1], "View") {
		t.Fatal("descriptions not aligned", text)
	}
	commands[0].Desc = strings.Repeat("long description ", 10)
	text = plainRoulette(g.casinoMenuCommands(sess, commands))
	lines = strings.Split(text, "\n")
	for _, line := range lines[1 : len(lines)-1] {
		if !strings.HasPrefix(line, strings.Repeat(" ", col)) {
			t.Fatal("missing hanging indent", text)
		}
	}
}
func TestCasinoCompactHintsUseOnlyAvailableCommands(t *testing.T) {
	p := player.New("a")
	p.Options = map[string]any{"casino_display": "compact"}
	p.Credits = 10
	sess := &net.Session{Player: p}
	g := &Game{}
	menu := &casino.MenuView{Heading: "Your turn", Commands: []casino.MenuCommand{
		{Command: "hit", Desc: "Take a card"}, {Command: "stand", Desc: "Hold"}, {Command: "double", Cost: 20},
	}}
	text := plainRoulette(g.casinoPrivateMessage(sess, "casino_menu", menu, ""))
	if strings.Contains(text, "double") || !strings.Contains(text, "  h:hit") || !strings.Contains(text, "  st:stand") {
		t.Fatal(text)
	}
	menu.ShowBalance = true
	menu.Repeat = true
	menu.Commands = []casino.MenuCommand{{Command: "table", Desc: "View the felt"}}
	text = plainRoulette(g.casinoPrivateMessage(sess, "casino_menu", menu, ""))
	if strings.Contains(text, "bet") || strings.Contains(text, "sitout") {
		t.Fatal("compact mode invented actions", text)
	}
}
func TestSimplifiedFeltSpacingAndNoIdlePrompt(t *testing.T) {
	text := renderCrapsSnapshot(&casino.CrapsSnapshot{Variant: "simplified"}, "none", true)
	for _, want := range []string{"DICE TOTAL", "NET PROFIT", "3 or 4", "1:1 (even money)", "10 or 11", "LOSE STAKE", "ONE ROLL", "stake back"} {
		if !strings.Contains(text, want) {
			t.Fatalf("missing %q: %s", want, text)
		}
	}
	if strings.Contains(text, "WINS PAY 1:1") || strings.Contains(text, "COME-OUT") {
		t.Fatal("stale simplified rules", text)
	}
	if strings.Contains(text, "Place a bet, then roll when ready.") {
		t.Fatal(text)
	}
	rows := strings.Split(text, "\n")
	for i, row := range rows {
		if strings.Contains(row, "PASS ONLY") {
			if i+1 >= len(rows) || strings.Trim(rows[i+1], "│ ") != "" || !strings.HasPrefix(rows[i+2], "└") {
				t.Fatal("expected exactly one blank footer row", text)
			}
			return
		}
	}
	t.Fatal("missing pass label")
}