package game import ( "strings" "testing" "thehouseoficarus/internal/casino" ) func coinFlipRoom() string { return "name: Two Moons Flip\ncasino_tables:\n - id: coin_flip\n game: coin_flip\n min_bet: 1\n max_bet: 100\n" } func TestCoinFlipPlayAndFlip(t *testing.T) { g, s, conn := casinoCrapsGame(t, coinFlipRoom()) s.Player.Options = map[string]any{"color": "none", "casino_hints": "on"} g.executePlay(s, []string{"coin", "flip"}, "play coin flip") table, ok := g.Casino.Participation(s.Player.Name) if !ok || table == nil || table.Config.Game != casino.GameCoinFlip { t.Fatal("Coin Flip alias did not seat the player") } if table.StakeMultiple() != 100 { t.Fatalf("stake multiple %d, want 100", table.StakeMultiple()) } // Sitting down must not print a coin or a multiplier. if text := string(conn.data); strings.Contains(text, "▐") || strings.Contains(text, "BLUE") { t.Fatalf("join printed a coin: %s", text) } // The player only holds 100 credits, so a stake of 1 reserves 100. conn.data = nil g.executeBet(s, []string{"1"}, "bet 1") if len(s.Player.PendingWagers) != 1 || s.Player.PendingWagers[0].Credits != 100 { t.Fatalf("bet did not reserve 100x: %+v", s.Player.PendingWagers) } if !strings.Contains(string(conn.data), "Stake 1") { t.Fatalf("bet did not print the stake line: %s", conn.data) } // The first spin frame is held back one tick. conn.data = nil g.CasinoTick() if !strings.Contains(string(conn.data), "▼") { t.Fatalf("first tick did not start the red spin: %s", conn.data) } // Advance until the round lands (the spin + flip plan is at most ~40 frames). for i := 0; i < 200 && len(s.Player.PendingWagers) > 0; i++ { conn.data = nil g.CasinoTick() } if len(s.Player.PendingWagers) != 0 { t.Fatalf("round never settled: %+v", s.Player.PendingWagers) } if !strings.Contains(stripANSI(string(conn.data)), "Returned:") { t.Fatalf("settlement receipt missing: %s", conn.data) } } func TestCoinFlipStakeLimit(t *testing.T) { g, s, _ := casinoCrapsGame(t, coinFlipRoom()) s.Player.Options = map[string]any{"color": "none"} g.executePlay(s, []string{"coin flip"}, "play coin flip") // 100 credits cannot cover a stake of 2 (200 reserved). g.executeBet(s, []string{"2"}, "bet 2") if len(s.Player.PendingWagers) != 0 { t.Fatalf("accepted a stake the reserve cannot cover: %+v", s.Player.PendingWagers) } } func TestCoinFlipBetMaxUsesReserve(t *testing.T) { g, s, _ := casinoCrapsGame(t, coinFlipRoom()) s.Player.Options = map[string]any{"color": "none"} g.executePlay(s, []string{"coin flip"}, "play coin flip") // 100 credits with a 100x reserve means the largest stake is 1. g.executeBet(s, []string{"max"}, "bet max") if len(s.Player.PendingWagers) != 1 || s.Player.PendingWagers[0].Credits != 100 { t.Fatalf("bet max did not reserve 100x the affordable stake: %+v", s.Player.PendingWagers) } } func TestCoinFlipSettleReachesGame(t *testing.T) { g, s, conn := casinoCrapsGame(t, coinFlipRoom()) s.Player.Options = map[string]any{"color": "none"} g.executePlay(s, []string{"coin flip"}, "play coin flip") conn.data = nil g.handleGameCommand(s, "settle") if strings.Contains(string(conn.data), "Unknown command") { t.Fatalf("settle was rejected before reaching Coin Flip: %s", conn.data) } } func TestCoinFlipWheelInk(t *testing.T) { if ink, bold := coinFlipWheelInk(30, casino.CoinFlipSideRed); ink != -1 || bold { t.Fatalf("30x = %d,%v; want plain", ink, bold) } if ink, bold := coinFlipWheelInk(60, casino.CoinFlipSideRed); ink != coinFlipRedInk || bold { t.Fatalf("60x = %d,%v; want red not bold", ink, bold) } if ink, bold := coinFlipWheelInk(80, casino.CoinFlipSideBlue); ink != coinFlipBlueInk || !bold { t.Fatalf("80x = %d,%v; want blue bold", ink, bold) } if ink, bold := coinFlipWheelInk(-90, casino.CoinFlipSideRed); ink != coinFlipRedInk || !bold { t.Fatalf("-90x = %d,%v; want red bold", ink, bold) } } func TestCoinFlipRendering(t *testing.T) { for _, mode := range []string{"none", "ansi", "xterm256"} { for _, unicode := range []bool{false, true} { spin := &casino.CoinFlipSnapshot{ Phase: casino.CoinFlipPhaseRedSpin, SpinSide: casino.CoinFlipSideRed, SpinValues: []int{-10, -20, -30, -40, -50, -60, -70}, } rendered := renderCoinFlip(spin, mode, unicode) if mode == "none" && strings.Contains(rendered, "\x1b") { t.Fatalf("plain spin has ANSI escapes: %q", rendered) } if !strings.Contains(rendered, "▼") && unicode { t.Fatalf("spin strip missing its pointer: %q", rendered) } set := &casino.CoinFlipSnapshot{ Phase: casino.CoinFlipPhaseRedSpin, SpinSide: casino.CoinFlipSideRed, SpinValues: []int{-10, -20, -30, -40, -50, -60, -70}, Set: true, } if !strings.Contains(renderCoinFlip(set, mode, unicode), "SET") { t.Fatalf("landed spin missing SET: %q", renderCoinFlip(set, mode, unicode)) } results := &casino.CoinFlipSnapshot{Phase: casino.CoinFlipPhaseResults, Red: -61, Blue: 85} rendered = renderCoinFlip(results, mode, unicode) if plain := stripANSI(rendered); !strings.Contains(plain, "-61x") || !strings.Contains(plain, "+85x") { t.Fatalf("results panel missing a face: %q", plain) } coin := &casino.CoinFlipSnapshot{Phase: casino.CoinFlipPhaseFlipping, Side: casino.CoinFlipSideBlue, Multiplier: 42} rendered = renderCoinFlip(coin, mode, unicode) if plain := stripANSI(rendered); !strings.Contains(plain, "+42x") { t.Fatalf("coin missing its multiplier: %q", plain) } if strings.Contains(rendered, "COIN FLIP") || strings.Contains(rendered, "stake") { t.Fatalf("flip printed chrome instead of just the coin: %q", rendered) } settled := &casino.CoinFlipSnapshot{Phase: casino.CoinFlipPhaseResult, Side: casino.CoinFlipSideRed, Multiplier: -50, Result: true} rendered = renderCoinFlip(settled, mode, unicode) if plain := stripANSI(rendered); !strings.Contains(plain, "-50x") { t.Fatalf("settled coin missing its multiplier: %q", plain) } if mode == "xterm256" && !strings.Contains(rendered, "48;5;196") { t.Fatalf("settled red coin has no red fill: %q", rendered) } if mode == "ansi" && !strings.Contains(rendered, "\x1b[41m") { t.Fatalf("settled red coin has no red fill: %q", rendered) } } } if got := renderCoinFlip(&casino.CoinFlipSnapshot{Phase: casino.CoinFlipPhaseIdle}, "none", true); got != "" { t.Fatalf("idle coin rendered %q", got) } }