package game import ( "strings" "testing" "thehouseoficarus/internal/casino" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" ) type casinoTestConn struct { data []byte } func (c *casinoTestConn) ReadMessage() (string, error) { return "", nil } func (c *casinoTestConn) Write(b []byte) (int, error) { c.data = append(c.data, b...) return len(b), nil } func (c *casinoTestConn) Close() error { return nil } func (c *casinoTestConn) SetEcho(bool) error { return nil } func TestExecuteBetRejectsInvalidMachineArgs(t *testing.T) { g := &Game{Casino: casino.NewManager(1)} cfg := casino.MachineConfig{ID: "slots", Game: casino.GameSlots} g.Casino.JoinMachine(1, cfg, "alice") conn := &casinoTestConn{} sess := &net.Session{Player: player.New("alice"), Conn: conn} g.loggedInChars = map[string]*net.Session{"alice": sess} for _, args := range [][]string{{"banana"}, {"-5"}, {"10", "extra"}} { conn.data = nil g.executeBet(sess, args, "bet "+strings.Join(args, " ")) if !strings.Contains(string(conn.data), "Use bet, bet , or bet max.") { t.Fatalf("bet %v: expected usage error, got %q", args, string(conn.data)) } } // A bare bet at a machine reaches the spin path instead of erroring. g.Casino.SetMachineBet(1, "slots", "alice", 10) conn.data = nil g.executeBet(sess, nil, "bet") if !strings.Contains(string(conn.data), "You don't have enough chips and credits") { t.Fatalf("bare bet should attempt a spin, got %q", string(conn.data)) } } func TestRenderSlotSnapshotColorsWordSymbols(t *testing.T) { snapshot := &casino.SlotSnapshot{StoppedReels: 1} snapshot.Grid[0][0] = "scatter" if !strings.Contains(renderSlotSnapshot(snapshot, "none", false), "scatter") { t.Fatal("expected word-based slot symbols") } output := renderSlotSnapshot(snapshot, "xterm256", true) if !strings.Contains(output, "\033[38;5;") { t.Fatalf("expected colored slot symbols, got %q", output) } for _, line := range strings.Split(output, "\n") { if color.VisibleLen(line) != 51 { t.Fatalf("line visible width = %d, want 51: %q", color.VisibleLen(line), line) } } if !strings.Contains(output, "┌") || !strings.Contains(output, "└") { t.Fatalf("slot output lacks the Unicode frame: %q", output) } } func TestRenderBlackjackSnapshotUsesCompactASCIIArt(t *testing.T) { snapshot := &casino.BlackjackSnapshot{ Dealer: []casino.BlackjackCardView{{Rank: "?", Hidden: true}, {Rank: "K", Suit: "hearts"}}, Players: []casino.BlackjackPlayerView{{Name: "alice", Score: "20", Cards: []casino.BlackjackCardView{{Rank: "5", Suit: "clubs"}, {Rank: "Q", Suit: "spades"}}}}, } plain := renderBlackjackSnapshot(snapshot, "none", false, 5, "dark", nil) if !strings.Contains(plain, "+---+") || !strings.Contains(plain, "Dealer:") || !strings.Contains(plain, "alice (20):") { t.Fatalf("unexpected blackjack art: %q", plain) } output := renderBlackjackSnapshot(snapshot, "xterm256", false, 5, "dark", nil) if !strings.Contains(output, "\033[38;5;") { t.Fatalf("expected colored blackjack art: %q", output) } if strings.ContainsAny(output, "│─┌┐└┘") { t.Fatalf("blackjack art contains Unicode borders: %q", output) } for _, line := range strings.Split(output, "\n") { if color.VisibleLen(line) > 80 { t.Fatalf("blackjack line exceeds console width: %d", color.VisibleLen(line)) } } unicodeOutput := renderBlackjackSnapshot(snapshot, "none", true, 7, "dark", nil) if !strings.Contains(unicodeOutput, "│5 │") || !strings.Contains(unicodeOutput, "│ 5│") || !strings.Contains(unicodeOutput, "│ ♣ │") { t.Fatalf("unexpected Unicode card alignment: %q", unicodeOutput) } coloredUnicode := renderBlackjackSnapshot(snapshot, "xterm256", true, 7, "dark", nil) if !strings.Contains(coloredUnicode, "\033[38;5;15m") || !strings.Contains(coloredUnicode, "\033[38;5;27m") { t.Fatalf("expected white borders and blue card art: %q", coloredUnicode) } light := renderBlackjackSnapshot(snapshot, "xterm256", true, 7, "light", nil) if !strings.Contains(light, "\033[48;5;15m") { t.Fatalf("expected white card background in light style: %q", light) } lightLines := strings.Split(light, "\n") if strings.Contains(lightLines[1], "\033[48;5;15m") || strings.Contains(lightLines[7], "\033[48;5;15m") { t.Fatalf("card edges should not have a background: %q", light) } lightPlain := renderBlackjackSnapshot(snapshot, "none", true, 7, "light", nil) if !strings.Contains(lightPlain, "▀▀▀▀▀▀▀") || !strings.Contains(lightPlain, "▄▄▄▄▄▄▄") { t.Fatalf("expected half-height light card edges: %q", lightPlain) } }