aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_prefix_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-03 17:36:06 -0400
committerhistoria <[not public]>2026-10-03 17:36:06 -0400
commitc6d4702c0be4d2b37e1d82ff96265c52847c3f9a (patch)
treec0a3d0638b961a9ba06e6fdb30ee5f1cd2061f2e /internal/game/cmd_prefix_test.go
parent0fb83bdf433b9b48240f4ac7a5d4c912f6b808cc (diff)
downloadthehouseoficarus-c6d4702c0be4d2b37e1d82ff96265c52847c3f9a.tar.gz
feat: partial commands now always work
Diffstat (limited to 'internal/game/cmd_prefix_test.go')
-rw-r--r--internal/game/cmd_prefix_test.go125
1 files changed, 125 insertions, 0 deletions
diff --git a/internal/game/cmd_prefix_test.go b/internal/game/cmd_prefix_test.go
new file mode 100644
index 0000000..8f42a6f
--- /dev/null
+++ b/internal/game/cmd_prefix_test.go
@@ -0,0 +1,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)
+ }
+}