package game import ( "strings" "testing" "thehouseoficarus/internal/casino" "thehouseoficarus/internal/color" ) func TestBaccaratRulesFeltGeometryAndPhrases(t *testing.T) { cfg := casino.TableConfig{ID: "harbor", Game: casino.GameBaccarat, MinBet: 1, MaxBet: 100, MaxPlayers: 8, BettingTicks: 35, Baccarat: casino.BaccaratConfig{Variant: casino.BaccaratCommission, PairBets: true, Decks: 8, ShuffleAt: 0.25, TiePayout: 8, BankerPayout: 0.95}} for _, unicode := range []bool{false, true} { art := renderBaccaratRulesFelt(cfg, "none", unicode) rows := strings.Split(strings.TrimSuffix(art, "\n"), "\n") if len(rows) != 22 { 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{ "PUNTO BANCO — COMMISSION", "HIGHEST LAST DIGIT WINS", "STOPS ALL DRAWING", "PLAYER", "TIE", "BANKER", "PAYS 1:1", "PAYS 8 TO 1", "5% COMMISSION", "PLAYER & BANKER PUSH", "SIDE BETS", "HOUSE", "PLAYER PAIR", "BANKER PAIR", "11:1", "DRAGON 7", "PANDA 8", "SUPER 6", "NOT OFFERED", "DECKS", "SHUFFLE AT", "BET LIMITS", "1-100 CREDITS", "35 TICKS", "DRAWING — AUTOMATIC PUNTO BANCO", "DRAWS 0-5", "6 DRAWS VS 6-7", "ONE MAIN BET (PLAYER/BANKER/TIE) PLUS SIDE BETS", } { if !strings.Contains(art, want) { t.Fatalf("missing %q in unicode=%v felt", want, unicode) } } // The raw dump's phrases must not leak onto the felt. for _, stale := range []string{"Number of decks", "Drawing: Automatic", "Fractional credits:"} { if strings.Contains(art, stale) { t.Fatalf("stale prose %q on felt", stale) } } } } func TestBaccaratRulesFeltFollowsConfig(t *testing.T) { cfg := casino.TableConfig{ID: "crown", Game: casino.GameBaccarat, MinBet: 100, MaxBet: 10000, BettingTicks: 35, Baccarat: casino.BaccaratConfig{Variant: casino.BaccaratEZ, Dragon7: true, Panda8: true, Decks: 6, TiePayout: 9, BankerPayout: 1, ShuffleAt: 0.5}} art := renderBaccaratRulesFelt(cfg, "none", true) for _, want := range []string{"EZ BACCARAT — NO COMMISSION", "PAYS 9 TO 1", "3-CARD 7 PUSHES", "DRAGON 7" + strings.Repeat(" ", 23) + "40:1", "PANDA 8" + strings.Repeat(" ", 24) + "25:1", "100-10000 CREDITS", "50% REMAINING"} { if !strings.Contains(art, want) { t.Fatalf("missing %q: %s", want, art) } } cfg2 := casino.TableConfig{ID: "charter", Game: casino.GameBaccarat, Baccarat: casino.BaccaratConfig{Variant: casino.BaccaratSuper6, Super6Bet: true, TiePayout: 9}} art2 := renderBaccaratRulesFelt(cfg2, "none", true) for _, want := range []string{"SUPER 6 BACCARAT — NO COMMISSION", "WINNING 6 PAYS 1:2", "SUPER 6" + strings.Repeat(" ", 24) + "12:1", "PAYS 9 TO 1"} { if !strings.Contains(art2, want) { t.Fatalf("missing %q: %s", want, art2) } } } func TestBaccaratJoinPrintsFeltNotProse(t *testing.T) { g := &Game{Casino: casino.NewManager(1)} g.Casino.Join(1, casino.TableConfig{ID: "bac", Game: casino.GameBaccarat}, "alice") events := g.Casino.Join(1, casino.TableConfig{ID: "bac", Game: casino.GameBaccarat}, "bob") found := false for _, e := range events { if view, ok := e.Snapshot.(*casino.BaccaratRulesView); ok { found = true art := renderBaccaratRulesFelt(view.Config, "none", true) if !strings.Contains(art, "PUNTO BANCO — COMMISSION") || !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") } }