aboutsummaryrefslogtreecommitdiff
path: root/internal/game/casino_commands_test.go
blob: 5e05d4e588f42b64056bf0a64a22f5265510559e (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
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{"SIMPLIFIED CRAPS", "DICE TOTAL", "PAYOUT", "3 or 4", "1:1", "10 or 11", "LOSE STAKE"} {
		if !strings.Contains(text, want) {
			t.Fatalf("missing %q: %s", want, text)
		}
	}
	for _, stale := range []string{"even money", "NET PROFIT", "stake back", "PASS ONLY", "ONE ROLL", "COME-OUT"} {
		if strings.Contains(text, stale) {
			t.Fatalf("stale simplified rules %q", stale)
		}
	}
	if strings.Contains(text, "Place a bet, then roll when ready.") {
		t.Fatal(text)
	}
	rows := strings.Split(strings.TrimSuffix(text, "\n"), "\n")
	for i := 0; i < len(rows)-1; i++ {
		if strings.TrimSpace(rows[i]) == "" && strings.TrimSpace(rows[i+1]) == "" {
			t.Fatalf("consecutive blank lines at %d: %q", i, text)
		}
	}
	// The paytable ends at its own bottom border: no footer block remains.
	if strings.Count(text, "─") == 0 || strings.Contains(text, "NO POINT, ODDS") {
		t.Fatalf("stale footer: %q", text)
	}
}