aboutsummaryrefslogtreecommitdiff
path: root/internal/game/crazy_time_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-10 04:08:09 -0400
committerhistoria <[not public]>2026-10-10 04:08:09 -0400
commit9fb1f9010526100e12dd1ab1b0ca98401bb6ed75 (patch)
tree4a128211bfb0dd429bbb559a591d6c4be25c7757 /internal/game/crazy_time_test.go
parentced77d7c89129da6ba4001ab8e3dad265aa41396 (diff)
downloadthehouseoficarus-main.tar.gz
feat: add casino game: crazy timeHEADmain
Diffstat (limited to 'internal/game/crazy_time_test.go')
-rw-r--r--internal/game/crazy_time_test.go193
1 files changed, 193 insertions, 0 deletions
diff --git a/internal/game/crazy_time_test.go b/internal/game/crazy_time_test.go
new file mode 100644
index 0000000..2c07862
--- /dev/null
+++ b/internal/game/crazy_time_test.go
@@ -0,0 +1,193 @@
+package game
+
+import (
+ "strings"
+ "testing"
+
+ "thehouseoficarus/internal/casino"
+)
+
+func crazyTimeRoom() string {
+ return "name: Crazy Time Coliseum\ncasino_tables:\n - id: crazy_time\n game: crazy_time\n min_bet: 1\n max_bet: 1000\n"
+}
+
+func TestCrazyTimePlayAliasAndMenu(t *testing.T) {
+ g, s, conn := casinoCrapsGame(t, crazyTimeRoom())
+ s.Player.Options = map[string]any{"color": "none", "casino_hints": "on"}
+ g.executePlay(s, []string{"crazy", "time"}, "play crazy time")
+ table, ok := g.Casino.Participation(s.Player.Name)
+ if !ok || table == nil || table.Config.Game != casino.GameCrazyTime {
+ t.Fatal("Crazy Time alias did not seat the player")
+ }
+ text := stripANSI(string(conn.data))
+ if !strings.Contains(text, "CRAZY TIME") {
+ t.Fatalf("join output missing the game: %q", text)
+ }
+ if !strings.Contains(text, "bet N on BET") {
+ t.Fatalf("join output missing the bet hint: %q", text)
+ }
+ if !strings.Contains(text, "98") {
+ t.Fatalf("join output missing the RTP: %q", text)
+ }
+ conn.data = nil
+ g.executeRules(s, nil, "rules")
+ if help := stripANSI(string(conn.data)); !strings.Contains(help, "CRAZY TIME") || !strings.Contains(help, "TOP SLOT") {
+ t.Fatalf("rules did not open the Crazy Time page: %q", help)
+ }
+}
+
+func TestCrazyTimeListCommand(t *testing.T) {
+ g, s, conn := casinoCrapsGame(t, crazyTimeRoom())
+ s.Player.Options = map[string]any{"color": "none"}
+ g.executePlay(s, []string{"crazy time"}, "play crazy time")
+ conn.data = nil
+ g.executeList(s, nil, "list")
+ text := stripANSI(string(conn.data))
+ for _, want := range []string{"COIN FLIP", "CASH HUNT", "PACHINKO", "CRAZY TIME", "ALL"} {
+ if !strings.Contains(text, want) {
+ t.Fatalf("list missing %s: %q", want, text)
+ }
+ }
+}
+
+func TestCrazyTimePlayAndSpin(t *testing.T) {
+ g, s, conn := casinoCrapsGame(t, crazyTimeRoom())
+ s.Player.Options = map[string]any{"color": "none", "casino_hints": "on"}
+ g.executePlay(s, []string{"crazy time"}, "play crazy time")
+ conn.data = nil
+ g.executeBet(s, []string{"5", "on", "cash", "hunt"}, "bet 5 on cash hunt")
+ if len(s.Player.PendingWagers) != 1 {
+ t.Fatalf("bet not committed: %+v", s.Player.PendingWagers)
+ }
+ if !strings.Contains(stripANSI(string(conn.data)), "CASH HUNT") {
+ t.Fatalf("bet confirmation missing the target: %q", conn.data)
+ }
+ // Spin and drive the whole round (main wheel plus any bonus) to a receipt.
+ conn.data = nil
+ g.executeTableAction(s, "spin")
+ for i := 0; i < 2000 && len(s.Player.PendingWagers) > 0; i++ {
+ g.CasinoTick()
+ }
+ if len(s.Player.PendingWagers) != 0 {
+ t.Fatalf("round never settled: %+v", s.Player.PendingWagers)
+ }
+}
+
+func TestCrazyTimeRendering(t *testing.T) {
+ for _, mode := range []string{"none", "ansi", "xterm256"} {
+ for _, unicode := range []bool{false, true} {
+ wheel := &casino.CrazyTimeSnapshot{
+ Phase: "result",
+ Pockets: []string{"1", "2", "5", "10", "COIN FLIP", "CASH HUNT", "PACHINKO", "CRAZY TIME", "1", "2", "5"},
+ TopSymbol: "CASH HUNT",
+ TopMult: 5,
+ }
+ rendered := renderCrazyTime(wheel, mode, unicode, false, 0)
+ if mode == "none" && strings.Contains(rendered, "\x1b") {
+ t.Fatalf("plain wheel has ANSI escapes: %q", rendered)
+ }
+ plain := stripANSI(rendered)
+ if !strings.Contains(plain, "CASH") || !strings.Contains(plain, "x5") {
+ t.Fatalf("wheel missing symbol/top multiplier: %q", plain)
+ }
+
+ bonus := &casino.CrazyTimeSnapshot{
+ Phase: "bonus",
+ Bonus: &casino.CrazyTimeBonusView{
+ Kind: casino.CrazyTimeBonusPachinko,
+ Phase: "result",
+ PachoPockets: []int{10, 20, -1, 5, 50, 100, 25, 15, 7, 10, 20, 5, 50, 100, 25, 15},
+ PachoRow: 16,
+ PachoLane: 4,
+ },
+ }
+ rendered = renderCrazyTime(bonus, mode, unicode, false, 0)
+ if plain := stripANSI(rendered); !strings.Contains(plain, "DBL") || !strings.Contains(plain, "100") {
+ t.Fatalf("pachinko bonus missing prizes: %q", plain)
+ }
+
+ coin := &casino.CrazyTimeSnapshot{
+ Phase: "bonus",
+ Bonus: &casino.CrazyTimeBonusView{Kind: casino.CrazyTimeBonusCoinFlip, Phase: "result", CoinLow: 3, CoinHigh: 25, CoinSide: "blue", CoinValue: 25, CoinResult: true},
+ }
+ if plain := stripANSI(renderCrazyTime(coin, mode, unicode, false, 0)); !strings.Contains(plain, "25x") {
+ t.Fatalf("coin flip bonus missing value: %q", plain)
+ }
+
+ hunt := &casino.CrazyTimeSnapshot{
+ Phase: "bonus",
+ Bonus: &casino.CrazyTimeBonusView{Kind: casino.CrazyTimeBonusCashHunt, Phase: "result", HuntFinal: []int{2, 5, 10, 25, 50, 100, 500, 15, 20, 2, 5, 10}, HuntPick: 3},
+ }
+ if plain := stripANSI(renderCrazyTime(hunt, mode, unicode, false, 0)); !strings.Contains(plain, "TILE 4") {
+ t.Fatalf("cash hunt bonus missing the pick: %q", plain)
+ }
+
+ ct := &casino.CrazyTimeSnapshot{
+ Phase: "bonus",
+ Bonus: &casino.CrazyTimeBonusView{
+ Kind: casino.CrazyTimeBonusWheel,
+ Phase: "result",
+ CTWheel: []int{10, 15, 20, 25, 50, 100, -1, 10, 15, 20, 25, 50, 100, 10, 15},
+ CTOffset: 0,
+ CTPick: 1,
+ CTResult: true,
+ CTValue: 25,
+ },
+ }
+ if plain := stripANSI(renderCrazyTime(ct, mode, unicode, false, 0)); !strings.Contains(plain, "POINTER") {
+ t.Fatalf("crazy wheel bonus missing the result: %q", plain)
+ }
+ }
+ }
+}
+
+func TestCrazyTimeBlockedVerbsDoNotLeak(t *testing.T) {
+ g, s, _ := casinoCrapsGame(t, crazyTimeRoom())
+ s.Player.Options = map[string]any{"color": "none"}
+ g.executePlay(s, []string{"crazy time"}, "play crazy time")
+ // "sp" must abbreviate to spin, not split (blackjack) or an unknown verb.
+ got, ok := g.casinoAbbreviation(s, "sp")
+ if !ok || got != "spin" {
+ t.Fatalf("sp resolved to %q,%v; want spin", got, ok)
+ }
+}
+
+func TestCrazyTimeBetOmitsBalanceAndResultShowsItOnce(t *testing.T) {
+ g, s, conn := casinoCrapsGame(t, crazyTimeRoom())
+ s.Player.Options = map[string]any{"color": "none", "casino_hints": "on"}
+ g.executePlay(s, []string{"crazy time"}, "play crazy time")
+ conn.data = nil
+ g.executeBet(s, []string{"5", "on", "all"}, "bet 5 on all")
+ out := stripANSI(string(conn.data))
+ if strings.Contains(out, "Available") {
+ t.Fatalf("bet printed the wallet: %q", out)
+ }
+ if !strings.Contains(out, "all four minigames") {
+ t.Fatalf("ALL bet not confirmed: %q", out)
+ }
+ conn.data = nil
+ g.executeTableAction(s, "spin")
+ for i := 0; i < 3000 && len(s.Player.PendingWagers) > 0; i++ {
+ g.CasinoTick()
+ }
+ if len(s.Player.PendingWagers) != 0 {
+ t.Fatal("round never settled")
+ }
+ out = stripANSI(string(conn.data))
+ if got := strings.Count(out, "Available "); got != 1 {
+ t.Fatalf("result printed the wallet %d times, want 1: %q", got, out)
+ }
+}
+
+func TestCrazyTimeTopSlotOnlyOnResult(t *testing.T) {
+ pockets := []string{"1", "2", "5", "10", "COIN FLIP", "CASH HUNT", "PACHINKO", "CRAZY TIME", "1", "2", "5"}
+ spin := &casino.CrazyTimeSnapshot{Phase: "spinning", Pockets: pockets, TopSymbol: "CASH HUNT", TopMult: 5}
+ if plain := stripANSI(renderCrazyTime(spin, "none", false, false, 0)); strings.Contains(plain, "TOP SLOT") {
+ t.Fatalf("spinning wheel printed TOP SLOT: %q", plain)
+ }
+ result := &casino.CrazyTimeSnapshot{Phase: "result", Pockets: pockets, TopSymbol: "CASH HUNT", TopMult: 5}
+ plain := stripANSI(renderCrazyTime(result, "none", false, false, 0))
+ if !strings.Contains(plain, "TOP SLOT") || !strings.Contains(plain, "x5") {
+ t.Fatalf("result wheel omitted the Top Slot: %q", plain)
+ }
+}