package game import ( "strings" "testing" "thehouseoficarus/internal/casino" "thehouseoficarus/internal/color" "thehouseoficarus/internal/combat" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) func TestCrapsHalfTableGeometryAndPuck(t *testing.T) { for _, unicode := range []bool{false, true} { for _, point := range []int{0, 4, 5, 6, 8, 9, 10} { s := &casino.CrapsSnapshot{Point: point, Field12: 3} art := renderCrapsFelt(s, "none", unicode) rows := strings.Split(strings.TrimSuffix(art, "\n"), "\n") if len(rows) != 26 { t.Fatal(len(rows)) } for _, row := range rows { if color.VisibleLen(row) != 77 { t.Fatalf("misaligned (%d): %s", color.VisibleLen(row), row) } } // Check connected box edges at the main rail and centre-action partition. if unicode { for _, r := range []rune{'┌', '┬', '┼', '├', '┤', '┴', '┘'} { if !strings.ContainsRune(art, r) { t.Fatalf("missing joint %c", r) } } } v := "|" if unicode { v = "│" } want := v + "OFF" + v if point != 0 { want = v + "ON " + v } if strings.Count(art, want) != 1 { t.Fatalf("missing/duplicate puck %d: %s", point, art) } if !strings.Contains(art, "12 PAYS 3:1") { t.Fatal("field variant invisible") } for _, bet := range []string{"DON'T COME", "C O M E", "FIELD", "DON'T PASS", "PASS LINE", "HARD 4", "HARD 6", "HARD 8", "HARD 10", "SEVEN", "CRAPS", "ELEVEN", "ACES", "ACE 3", "BOXCARS", "HORN", "HOP"} { if strings.Count(art, bet) != 1 { t.Fatalf("missing or duplicate %q", bet) } } } } t.Log("\n" + renderCrapsSnapshot(&casino.CrapsSnapshot{Point: 6, Field12: 3, Dice: [2]int{3, 3}, Queue: []string{"Alice", "Bob"}, Made: []int{4, 5}, Seen: []int{2, 4, 5, 6, 9}, Phase: "betting", ShootIn: 10, DealerIn: 70}, "none", true)) } func TestCrapsTrackersAndDiceStyles(t *testing.T) { s := &casino.CrapsSnapshot{Made: []int{4, 6}, Seen: []int{2, 12}} art := renderCrapsTrackers(s, "xterm256", true) plain := plainRoulette(art) for _, token := range []string{"[ 4*]", "[ 5 ]", "[ 6*]", " 2*", "12*"} { if !strings.Contains(plain, token) { t.Fatal("tracker missing", token) } } if strings.Count(art, color.BgCode("xterm256", 220)) != 4 { t.Fatal("wrong number of filled tracker tokens") } if !strings.Contains(renderCrapsFelt(s, "xterm256", true), color.BgCode("xterm256", 22)) { t.Fatal("felt not dark green") } g := &Game{} p := player.New("Alice") p.Options = map[string]any{"card_style": "light", "unicode": true} sess := &net.Session{Player: p} s.Dice = [2]int{2, 5} light := g.renderCasinoSnapshot(sess, s) p.Options["card_style"] = "dark" dark := g.renderCasinoSnapshot(sess, s) if light == dark { t.Fatal("card_style not wired to craps") } for a := 1; a <= 6; a++ { for b := 1; b <= 6; b++ { for _, unicode := range []bool{false, true} { lit := renderCrapsDice([2]int{a, b}, "xterm256", unicode, "light") dim := renderCrapsDice([2]int{a, b}, "xterm256", unicode, "dark") if !strings.Contains(lit, color.BgCode("xterm256", 15)) || !strings.Contains(dim, color.BgCode("xterm256", 0)) { t.Fatal("incorrect dice background") } pip := "o" if unicode { pip = "●" } if strings.Count(strings.ReplaceAll(plainRoulette(lit), "Total:", ""), pip) != a+b { t.Fatal("wrong pip count", a, b) } if plainRoulette(lit) != plainRoulette(dim) { t.Fatal("style changed dice outcome") } } } } } func TestCrapsRemindersFollowArt(t *testing.T) { g := &Game{Casino: casino.NewManager(1), Combat: combat.NewTracker(), Deps: Deps{World: world.New(t.TempDir())}} g.Casino.Join(1, casino.TableConfig{ID: "c", Game: casino.GameCraps}, "alice") conn := &casinoTestConn{} p := player.New("alice") sess := &net.Session{Player: p, Conn: conn} g.loggedInChars = map[string]*net.Session{"alice": sess} for _, display := range []string{"full", "compact"} { p.Options = map[string]any{"casino_display": display} conn.data = nil g.emitCasinoEvents(g.Casino.Table(1, "c").Status("alice")) text := plainRoulette(string(conn.data)) felt, commands := strings.Index(text, "PASS LINE"), strings.Index(text, "bet on ") if felt < 0 || commands < felt { t.Fatal("reminder above graphic", text) } for _, old := range []string{"Next in order:", "Fire points made:", "ATS numbers:", "Phase:", "Timer: 0"} { if strings.Contains(text, old) { t.Fatal("raw status leaked", old) } } } }