aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cash_hunt_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/cash_hunt_test.go')
-rw-r--r--internal/game/cash_hunt_test.go178
1 files changed, 178 insertions, 0 deletions
diff --git a/internal/game/cash_hunt_test.go b/internal/game/cash_hunt_test.go
new file mode 100644
index 0000000..65ef16f
--- /dev/null
+++ b/internal/game/cash_hunt_test.go
@@ -0,0 +1,178 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+ "unicode/utf8"
+
+ "thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/color"
+)
+
+func TestCashHuntCommandsAndReceipt(t *testing.T) {
+ for _, choice := range []string{"100", "pick"} {
+ t.Run(choice, func(t *testing.T) {
+ g, s, conn := casinoCrapsGame(t, "name: Vault\ncasino_tables:\n - id: cash_hunt\n game: cash_hunt\n")
+ s.Player.Options = map[string]any{"color": "none", "casino_hints": "off"}
+ g.executePlay(s, []string{"cash", "hunt"}, "play cash hunt")
+ table, ok := g.Casino.Participation(s.Player.Name)
+ if !ok || table == nil || table.Config.Game != casino.GameCashHunt {
+ t.Fatal("Cash Hunt alias did not seat player")
+ }
+ g.executeCommand(s, "difficulty", []string{"hard"}, "difficulty hard")
+ conn.data = nil
+ g.executeBet(s, []string{"1"}, "bet 1")
+ text := string(conn.data)
+ preview, hidden := strings.Index(text, "PRIZE PREVIEW"), strings.Index(text, "hidden and mixed")
+ if preview < 0 || hidden <= preview || !strings.Contains(text, " 100 ") || len(s.Player.PendingWagers) != 1 {
+ t.Fatalf("preview/shuffle sequence missing: %s", text)
+ }
+ if table.Busy(s.Player.Name) {
+ t.Fatal("choosing must not suppress the input prompt")
+ }
+ conn.data = nil
+ g.doLookTarget(s, "cash hunt")
+ if strings.Contains(string(conn.data), "PRIZE PREVIEW") || !strings.Contains(string(conn.data), "hidden and mixed") {
+ t.Fatal("look leaked sealed positions")
+ }
+ conn.data = nil
+ if choice == "pick" {
+ g.executeCommand(s, "pick", []string{"100"}, "pick 100")
+ } else {
+ // Exercise the real input path: a bare square number must survive
+ // classification instead of being rejected as an unknown verb.
+ g.handleGameCommand(s, choice)
+ }
+ text = string(conn.data)
+ if strings.Contains(text, "Unknown command") {
+ t.Fatalf("bare square number rejected: %s", text)
+ }
+ if len(s.Player.PendingWagers) != 0 || strings.Count(text, "Returned:") != 1 || !strings.Contains(text, ">100<") {
+ t.Fatalf("selection/receipt failed: %s", text)
+ }
+ conn.data = nil
+ g.executeCommand(s, "100", nil, "100")
+ if strings.Contains(string(conn.data), "Returned:") {
+ t.Fatal("duplicate selection paid again")
+ }
+ g.executeCommand(s, "paytable", nil, "paytable")
+ if !strings.Contains(string(conn.data), "50000x") {
+ t.Fatal("paytable missing jackpots")
+ }
+ })
+ }
+}
+
+func TestCashHuntBareNumberReachesGame(t *testing.T) {
+ // A player typing any square number, including 1 and 100, must select it.
+ g, s, conn := casinoCrapsGame(t, "name: Vault\ncasino_tables:\n - id: cash_hunt\n game: cash_hunt\n")
+ s.Player.Options = map[string]any{"color": "none", "casino_hints": "off"}
+ g.executePlay(s, []string{"cash", "hunt"}, "play cash hunt")
+ for _, square := range []string{"1", "9", "10", "99", "100"} {
+ g.executeBet(s, []string{"1"}, "bet 1")
+ conn.data = nil
+ g.handleGameCommand(s, square)
+ if !strings.Contains(string(conn.data), fmt.Sprintf(">%s<", square)) {
+ t.Fatalf("square %s was not selected: %s", square, conn.data)
+ }
+ }
+}
+
+func TestCashHuntSettleReachesGame(t *testing.T) {
+ // settle is not a shared-registry verb; a seated player must reach the game.
+ g, s, conn := casinoCrapsGame(t, "name: Vault\ncasino_tables:\n - id: cash_hunt\n game: cash_hunt\n")
+ s.Player.Options = map[string]any{"color": "none", "casino_hints": "off"}
+ g.executePlay(s, []string{"cash", "hunt"}, "play cash hunt")
+ conn.data = nil
+ g.handleGameCommand(s, "settle")
+ if strings.Contains(string(conn.data), "Unknown command") {
+ t.Fatalf("settle was rejected before reaching Cash Hunt: %s", conn.data)
+ }
+}
+
+func TestCashHuntPlayAliasesWithWager(t *testing.T) {
+ for _, name := range []string{"cash hunt", "cash_hunt", "cashhunt", "cash-hunt"} {
+ g, s, conn := casinoCrapsGame(t, "name: Vault\ncasino_tables:\n - id: cash_hunt\n game: cash_hunt\n")
+ args := append(strings.Fields(name), "1")
+ g.executePlay(s, args, "play "+name+" 1")
+ if len(s.Player.PendingWagers) != 1 || !strings.Contains(stripANSI(string(conn.data)), "PRIZE PREVIEW") {
+ t.Fatalf("inline wager failed for %q: %s", name, conn.data)
+ }
+ }
+}
+
+func TestCashHuntBoardRendering(t *testing.T) {
+ prizes := []int{0, 1, 2, 5, 10, 20, 100, 200, 500, 1000, 10000, 50000}
+ for _, mode := range []string{"none", "ansi", "xterm256"} {
+ for _, unicode := range []bool{false, true} {
+ for _, phase := range []string{"preview", "result"} {
+ s := &casino.CashHuntSnapshot{Phase: phase, Risk: "hard"}
+ for i := range s.Squares {
+ s.Squares[i] = prizes[i%len(prizes)]
+ }
+ if phase == "result" {
+ s.Selected = 100
+ }
+ rendered := renderCashHunt(s, mode, unicode)
+ if mode == "none" && strings.Contains(rendered, "\x1b") {
+ t.Fatal("plain board has ANSI escapes")
+ }
+ lines := strings.Split(stripANSI(rendered), "\n")
+ if len(lines) != 22 {
+ t.Fatalf("want two headers and 20 tile lines, got %d", len(lines))
+ }
+ for _, line := range lines[2:] {
+ if utf8.RuneCountInString(line) != 70 {
+ t.Fatalf("tile row is not 70 columns: %q", line)
+ }
+ }
+ board := strings.Join(lines[2:], "\n")
+ if strings.ContainsAny(board, "[]") {
+ t.Fatalf("square numbers must not use brackets: %q", board)
+ }
+ for row := 0; row < 10; row++ {
+ numberLine := lines[2+2*row]
+ for col := 0; col < 10; col++ {
+ square := row*10 + col + 1
+ label := fmt.Sprintf("%d", square)
+ if square == s.Selected {
+ label = fmt.Sprintf(">%d<", square)
+ }
+ got := numberLine[col*cashHuntTileWidth : (col+1)*cashHuntTileWidth]
+ if want := cashHuntCenter(label, cashHuntTileWidth); got != want {
+ t.Fatalf("square %d label: got %q want %q", square, got, want)
+ }
+ }
+ }
+ if !strings.Contains(board, "50000x") {
+ t.Fatal("largest prize clipped")
+ }
+ if !unicode {
+ for _, c := range stripANSI(rendered) {
+ if c > 127 {
+ t.Fatal("ASCII mode printed Unicode")
+ }
+ }
+ }
+ if mode == "xterm256" {
+ // Dark rainbow columns replace the old black/blue checker.
+ if strings.Contains(rendered, "\x1b[48;5;16m") || strings.Contains(rendered, "\x1b[48;5;17m") {
+ t.Fatal("black/blue tile background survived")
+ }
+ for _, shade := range cashHuntTileColors {
+ if !strings.Contains(rendered, color.BgCode("xterm256", shade)) {
+ t.Fatalf("missing dark rainbow shade %d", shade)
+ }
+ }
+ }
+ }
+ hidden := renderCashHunt(&casino.CashHuntSnapshot{Phase: "hidden"}, mode, unicode)
+ for _, line := range strings.Split(stripANSI(hidden), "\n") {
+ if utf8.RuneCountInString(line) != 70 {
+ t.Fatalf("shuffle banner width changed: %q", line)
+ }
+ }
+ }
+ }
+}