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
|
package game
import (
"testing"
"thehouseoficarus/internal/player"
)
// TestResolveCommandPrefix covers the general (non-casino) prefix expansion:
// unique prefixes run, exact commands and ambiguous prefixes do not.
func TestResolveCommandPrefix(t *testing.T) {
plain, sess, _ := houseEdgeGame(t, "name: Nowhere\n")
if got, ok := plain.resolveCommandPrefix(sess, "inven"); !ok || got != "inventory" {
t.Fatalf("inven -> (%q,%v), want inventory", got, ok)
}
if got, ok := plain.resolveCommandPrefix(sess, "scor"); !ok || got != "score" {
t.Fatalf("scor -> (%q,%v), want score", got, ok)
}
// An exact command is dispatched by the registry and must not be rewritten.
if _, ok := plain.resolveCommandPrefix(sess, "look"); ok {
t.Fatal("exact command was rewritten")
}
// The play gateway is runnable anywhere, so it breaks the play/plant tie
// even without a casino fixture.
if got, ok := plain.resolveCommandPrefix(sess, "pl"); !ok || got != "play" {
t.Fatalf("pl -> (%q,%v), want play", got, ok)
}
if got, ok := plain.resolveCommandPrefix(sess, "plan"); !ok || got != "plant" {
t.Fatalf("plan -> (%q,%v), want plant", got, ok)
}
// A unique one-letter prefix runs: "k" can only mean kill.
if got, ok := plain.resolveCommandPrefix(sess, "k"); !ok || got != "kill" {
t.Fatalf("k -> (%q,%v), want kill", got, ok)
}
// An exact one-letter command is still handled by the registry.
if _, ok := plain.resolveCommandPrefix(sess, "i"); ok {
t.Fatal("exact single-letter command was rewritten")
}
// A many-way one-letter prefix stays ambiguous.
if _, ok := plain.resolveCommandPrefix(sess, "q"); ok {
t.Fatal("ambiguous one-letter prefix resolved")
}
// Nothing matches.
if _, ok := plain.resolveCommandPrefix(sess, "zz"); ok {
t.Fatal("unknown prefix resolved")
}
}
// TestCommandPrefixCasinoTieBreak verifies that the room's advertised verbs
// break a general prefix tie, and that the play gateway resolves whether or
// not the room actually holds a table.
func TestCommandPrefixCasinoTieBreak(t *testing.T) {
g, sess, _ := houseEdgeGame(t, "name: Wheel\ncasino_tables:\n - id: big6\n game: big6\n")
for _, in := range []string{"pl", "pla"} {
if got, ok := g.resolveCommandPrefix(sess, in); !ok || got != "play" {
t.Fatalf("%s at a casino -> (%q,%v), want play", in, got, ok)
}
}
// The full word still resolves to the other command.
if got, ok := g.resolveCommandPrefix(sess, "plan"); !ok || got != "plant" {
t.Fatalf("plan -> (%q,%v), want plant", got, ok)
}
// The same break happens on the real dispatch path.
g.queue = NewCommandQueue()
g.handleGameCommand(sess, "pl")
if qc, ok := g.queue.PeekActive("alice"); !ok || qc.Command != "play" {
t.Fatalf("pl queued %+v (ok=%v), want play", qc, ok)
}
}
// TestCommandPrefixSingleLetterDispatch checks the user-facing case that a
// one-letter prefix works when it can only mean one command.
func TestCommandPrefixSingleLetterDispatch(t *testing.T) {
g, sess, _ := houseEdgeGame(t, "name: Nowhere\n")
g.queue = NewCommandQueue()
g.handleGameCommand(sess, "k man")
qc, ok := g.queue.PeekActive("alice")
if !ok || qc.Command != "kill" || qc.Args != "man" {
t.Fatalf("k man queued %+v (ok=%v), want kill man", qc, ok)
}
}
// TestCommandPrefixRespectsAliases pins the alias/prefix relationship:
// a prefix never expands an alias, but an exact alias always wins before any
// prefix resolution.
func TestCommandPrefixRespectsAliases(t *testing.T) {
// Alias "kim" only shares a prefix with "ki". Typing "ki man" must not
// run the alias, and must not let it block the kill prefix either.
g, sess, _ := houseEdgeGame(t, "name: Nowhere\n")
g.queue = NewCommandQueue()
sess.Account = &player.Account{Name: "alice", Aliases: map[string]string{"kim": "drop sword"}}
g.handleGameCommand(sess, "ki man")
qc, ok := g.queue.PeekActive("alice")
if !ok || qc.Command != "kill" || qc.Args != "man" {
t.Fatalf("ki man with alias kim queued %+v (ok=%v), want kill man", qc, ok)
}
// Exact alias "ki" wins outright: the kill prefix is never considered.
g2, sess2, _ := houseEdgeGame(t, "name: Nowhere\n")
g2.queue = NewCommandQueue()
sess2.Account = &player.Account{Name: "alice", Aliases: map[string]string{"ki": "drop sword"}}
g2.handleGameCommand(sess2, "ki man")
qc2, ok := g2.queue.PeekActive("alice")
if !ok || qc2.Command != "drop" || qc2.Args != "sword man" {
t.Fatalf("exact alias ki queued %+v (ok=%v), want drop sword man", qc2, ok)
}
}
// TestCommandPrefixSeatedCasinoStaysScoped guards the existing rule that a
// seated casino player only abbreviates their own game's verbs: "spl" must
// not fall through the general resolver to blackjack's split at Big 6.
func TestCommandPrefixSeatedCasinoStaysScoped(t *testing.T) {
g, sess, _ := houseEdgeGame(t, "name: Wheel\ncasino_tables:\n - id: big6\n game: big6\n")
g.queue = NewCommandQueue()
g.executePlay(sess, []string{"big6"}, "play big6")
if _, playing := g.Casino.Participation(sess.Player.Name); !playing {
t.Fatal("join failed")
}
g.handleGameCommand(sess, "spl")
if qc, ok := g.queue.PeekActive("alice"); ok {
t.Fatalf("seated spl queued %+v, want nothing", qc)
}
}
|