diff options
Diffstat (limited to 'internal/game/cash_hunt_test.go')
| -rw-r--r-- | internal/game/cash_hunt_test.go | 178 |
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) + } + } + } + } +} |
