package game import ( "strings" "testing" "unicode/utf8" "thehouseoficarus/internal/cardart" "thehouseoficarus/internal/casino" "thehouseoficarus/internal/world" ) func TestPaiGowCommandsLookAndSettlement(t *testing.T) { g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: pai_gow\n pai_gow:\n envy: true\n") g.executePlay(sess, []string{"pai"}, "play pai") table, playing := g.Casino.Participation(sess.Player.Name) if !playing || table.Config.Game != casino.GamePaiGow { t.Fatal("alias failed") } conn.data = nil g.executeTableStatus(sess, []string{"rules"}, "table rules") text := stripANSI(string(conn.data)) for _, want := range []string{"FORTUNE", "ENVY (FIXED)", "750:1", "Three pair"} { if !strings.Contains(text, want) { t.Fatalf("missing %s: %s", want, text) } } g.executeBet(sess, []string{"20"}, "bet 20") g.executeBet(sess, []string{"5", "fortune"}, "bet 5 fortune") g.executeTableAction(sess, "deal") snap := table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot) if len(snap.Cards) != 7 || len(snap.DealerHigh) != 0 || len(sess.Player.PendingWagers) != 2 { t.Fatal("deal/side bet") } conn.data = nil g.executeCommand(sess, "22", nil, "22") if text = string(conn.data); !strings.Contains(text, "different numbers") { t.Fatal(text) } if classifyCommand("67") != ClassInstant || classifyCommand("13456") != ClassInstant { t.Fatal("numeric classification") } // A numeric selection either locks a valid split or gives a clear foul; // either way it must reach Pai Gow rather than the generic unknown verb. conn.data = nil g.executeCommand(sess, "67", nil, "67") text = string(conn.data) if table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot).Phase == "setting" && !strings.Contains(text, "FOUL HAND") { t.Fatal(text) } g.executeCommand(sess, "house", nil, "house") if table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot).Phase != "waiting" || len(sess.Player.PendingWagers) != 0 { t.Fatal("settlement did not finish") } } func TestPaiGowFeltAndJokerAllModes(t *testing.T) { store := cardart.Load("../../data/cards.yaml") for _, size := range []int{1, 5, 7, 9} { if len(store.Art("JK", size)) != size { t.Fatalf("missing JK art at %d", size) } for _, unicode := range []bool{false, true} { for _, style := range []string{"dark", "light"} { for _, art := range []*cardart.Store{nil, store} { lines := jokerCardArt(size, unicode, style, art) if len(lines) != size { t.Fatal(size, lines) } for _, line := range lines { if size > 1 && utf8.RuneCountInString(line) != size { t.Fatalf("%d width: %q", size, line) } if !unicode { for _, r := range line { if r > 127 { t.Fatalf("ASCII Joker has Unicode: %q", line) } } } } for _, mode := range []string{"none", "xterm256", "truecolor"} { out := cardsLine(mode, []casino.CardView{{Rank: "JK", Suit: "joker"}}, cardRenderSize(unicode, size), unicode, style, art) if !strings.Contains(strings.Join(out, "\n"), "J") { t.Fatal(out) } } } } } } for _, envy := range []bool{false, true} { for _, unicode := range []bool{false, true} { cfg := casino.TableConfig{Game: casino.GamePaiGow, PaiGow: casino.PaiGowConfig{Envy: envy}} felt := renderPaiGowFelt(cfg, "none", unicode) lines := strings.Split(strings.TrimRight(felt, "\n"), "\n") width := utf8.RuneCountInString(lines[0]) for _, line := range lines { if utf8.RuneCountInString(line) != width { t.Fatal("jagged felt", line) } } if !strings.Contains(felt, "Three pair") || !strings.Contains(felt, "1000:1") { t.Fatal(felt) } if !envy && (!strings.Contains(felt, "ENVY OFF") || !strings.Contains(felt, "30:1")) { t.Fatal(felt) } if cfg.PaiGow.Envy && (strings.Contains(felt, "ENVY OFF") || !strings.Contains(felt, "ENVY (FIXED)")) { t.Fatal(felt) } } } cfg := casino.TableConfig{MinBet: 5, MaxBet: 1000, Game: casino.GamePaiGow, PaiGow: casino.PaiGowConfig{Variant: casino.PaiGowFaceUp, PushAceHigh: "5_15_40"}} felt := renderPaiGowFelt(cfg, "none", true) if !strings.Contains(felt, "DEALER FACE UP") || !strings.Contains(felt, "PUSH ACE HIGH 5/15/40:1") { t.Fatal(felt) } } func TestVestaPaiGowRooms(t *testing.T) { _, rooms, _ := loadVesta(t) for id, variant := range map[int]string{6195: casino.PaiGowClassic, 6196: casino.PaiGowEZ, 6197: casino.PaiGowNoPush, 6198: casino.PaiGowFaceUp} { r, ok := rooms[id] if !ok || len(r.CasinoTables) != 1 { t.Fatalf("missing Pai Gow room %d", id) } cfg := r.CasinoTables[0] if cfg.Validate() != nil || cfg.Game != casino.GamePaiGow || cfg.PaiGow.Variant != variant || cfg.PaiGow.Envy != (id != 6197) { t.Fatalf("bad Pai Gow configuration: %+v", cfg) } } if rooms[6192].Exits[world.East].Room != 6195 || rooms[6195].Exits[world.West].Room != 6192 || rooms[6195].Exits[world.North].Room != 6196 || rooms[6196].Exits[world.South].Room != 6195 || rooms[6195].Exits[world.South].Room != 6197 || rooms[6197].Exits[world.North].Room != 6195 || rooms[6196].Exits[world.North].Room != 6198 || rooms[6198].Exits[world.South].Room != 6196 { t.Fatal("broken Pai Gow navigation") } } func TestJokerEmptySuitDoesNotPanic(t *testing.T) { for _, size := range []int{5, 7, 9} { if len(cardArtLines(casino.CardView{Rank: "JK"}, size, true, "dark", nil)) != size { t.Fatal(size) } } }