package game import ( "strings" "testing" "thehouseoficarus/internal/casino" "thehouseoficarus/internal/color" ) func TestBlackjackRulesFeltGeometryAndPhrases(t *testing.T) { cfg := casino.TableConfig{ID: "midnight", Game: casino.GameBlackjack, MinBet: 100, MaxBet: 10000, MaxPlayers: 6, BettingTicks: 18, Blackjack: casino.BlackjackConfig{ActionTicks: 15, DoubleSplitBlackjack: &[]bool{false}[0]}} for _, unicode := range []bool{false, true} { art := renderBlackjackRulesFelt(cfg, "none", unicode) rows := strings.Split(strings.TrimSuffix(art, "\n"), "\n") if len(rows) != 21 { t.Fatalf("felt height %d", len(rows)) } for _, row := range rows { if color.VisibleLen(row) != 77 { t.Fatalf("misaligned (%d): %s", color.VisibleLen(row), row) } } for _, want := range []string{ "BLACKJACK PAYS 3 TO 2", "DEALER MUST STAND ON 17 AND DRAW TO 16", "INSURANCE PAYS 2 TO 1", "THE SHOE", "PAYS", "DOUBLE & SPLIT", "HOUSE", "DECKS", "SHUFFLE AT", "25% REMAINING", "RESHUFFLE MID-ROUND", "HOLE CARD", "ORDINARY WIN", "SPLIT+DOUBLE 21", "DOUBLE", "ANY TWO, ONE CARD", "MAX HANDS", "ONE CARD, NO HITS", "SURRENDER", "NOT OFFERED", "100-10000 CREDITS", "18 TICKS", "15 TICKS", } { if !strings.Contains(art, want) { t.Fatalf("missing %q in unicode=%v felt", want, unicode) } } // The raw dump's phrases and fixed engine rules must not leak onto the felt. for _, stale := range []string{"Number of decks", "Dealer hole card / peek:", "FRACTIONS", "BJ CHECK", "HOUSE BLACKJACK"} { if strings.Contains(art, stale) { t.Fatalf("stale prose %q on felt", stale) } } } } func TestBlackjackRulesFeltFollowsConfig(t *testing.T) { yes, no := true, false cfg := casino.TableConfig{ID: "high", Game: casino.GameBlackjack, MinBet: 5, MaxBet: 500, Blackjack: casino.BlackjackConfig{Decks: 1, BlackjackPayout: 1.2, DealerHitSoft17: &yes, Surrender: &yes, ShuffleAt: 0.5, MaxSplitHands: 2, DoubleAfterSplit: &no, ResplitAces: &yes, SplitUnlikeTenValues: &no, DoubleSplitBlackjack: &no, InsurancePayout: 2}} art := renderBlackjackRulesFelt(cfg, "none", true) for _, want := range []string{"BLACKJACK PAYS 6 TO 5", "DEALER HITS SOFT 17", "LATE, TWO CARDS", "50% REMAINING", "AFTER SPLIT NO", "RESPLIT ACES YES", "PAIRS IDENTICAL RANKS", "SPLIT+DOUBLE 21 1:1", "5-500 CREDITS"} { if !strings.Contains(art, want) { t.Fatalf("missing %q: %s", want, art) } } } func TestBlackjackJoinPrintsFeltNotProse(t *testing.T) { g := &Game{Casino: casino.NewManager(1)} g.Casino.Join(1, casino.TableConfig{ID: "bj", Game: casino.GameBlackjack, BettingTicks: 2}, "alice") events := g.Casino.Join(1, casino.TableConfig{ID: "bj", Game: casino.GameBlackjack, BettingTicks: 2}, "bob") found := false for _, e := range events { if view, ok := e.Snapshot.(*casino.BlackjackRulesView); ok { found = true art := renderBlackjackRulesFelt(view.Config, "none", true) if !strings.Contains(art, "BLACKJACK PAYS 3 TO 2") || !strings.Contains(art, "DECKS") { t.Fatal("rules felt missing phrases", art) } if e.Message != "" { t.Fatal("rules event carries prose", e.Message) } } if strings.Contains(e.Message+e.PrivateMessage, "Number of decks") { t.Fatal("raw rules prose leaked onto join", e) } } if !found { t.Fatal("join did not attach the rules felt") } }