diff options
| author | historia <[not public]> | 2026-10-01 17:24:54 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-10-01 17:24:54 -0400 |
| commit | 4e2a95503e4ea4c830510f5aea23fac7948ddc41 (patch) | |
| tree | 690b06df9094480febfaaaf7d9fc71ef7b81848d /internal/game/pai_gow_test.go | |
| parent | d762f8e1360cf38b8e11e69083c2dad59ec7130e (diff) | |
| download | thehouseoficarus-4e2a95503e4ea4c830510f5aea23fac7948ddc41.tar.gz | |
feat: set and autoset in pai gow, better showdown arrangement, better formatting
Diffstat (limited to 'internal/game/pai_gow_test.go')
| -rw-r--r-- | internal/game/pai_gow_test.go | 170 |
1 files changed, 166 insertions, 4 deletions
diff --git a/internal/game/pai_gow_test.go b/internal/game/pai_gow_test.go index af6b749..9b37a68 100644 --- a/internal/game/pai_gow_test.go +++ b/internal/game/pai_gow_test.go @@ -39,19 +39,179 @@ func TestPaiGowCommandsLookAndSettlement(t *testing.T) { 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. + // A numeric selection either previews a valid arrangement or gives a clear + // foul; either way it must reach Pai Gow rather than the generic unknown + // verb, and must not lock the hand before "set". 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") { + snap = table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot) + if snap.Phase == "setting" && !strings.Contains(text, "FOUL HAND") && !strings.Contains(text, "arranged hand") { t.Fatal(text) } g.executeCommand(sess, "house", nil, "house") + g.executeCommand(sess, "set", nil, "set") if table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot).Phase != "waiting" || len(sess.Player.PendingWagers) != 0 { t.Fatal("settlement did not finish") } } +func pgTestSnap() *casino.PaiGowSnapshot { + cv := func(rank, suit string) casino.CardView { return casino.CardView{Rank: rank, Suit: suit} } + return &casino.PaiGowSnapshot{ + Config: casino.TableConfig{Game: casino.GamePaiGow, PaiGow: casino.PaiGowConfig{Envy: true}}, + Phase: "setting", + Cards: []casino.CardView{cv("A", "diamonds"), cv("K", "clubs"), cv("9", "clubs"), cv("A", "spades"), cv("Q", "spades"), cv("2", "diamonds"), cv("6", "hearts")}, + } +} + +func TestPaiGowFeltOnlyOnJoin(t *testing.T) { + s := pgTestSnap() + s.ShowFelt = true + withFelt := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil)) + if !strings.Contains(withFelt, "FORTUNE") { + t.Fatalf("join did not show the felt:\n%s", withFelt) + } + s.ShowFelt = false + without := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil)) + if strings.Contains(without, "FORTUNE") || strings.Contains(without, "COMMISSION") { + t.Fatalf("felt leaked into an action:\n%s", without) + } +} + +func TestPaiGowCardsShowNumbersUnderneath(t *testing.T) { + s := pgTestSnap() + out := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil)) + if !strings.Contains(out, "Your seven cards") { + t.Fatalf("missing dealt-hand header:\n%s", out) + } + // The old side-by-side position key must be gone; the numbers now sit + // under the cards, one per column. + if strings.Contains(out, "1=A") || strings.Contains(out, "=") { + t.Fatalf("stale position key still present:\n%s", out) + } + for _, n := range []string{"1", "2", "3", "4", "5", "6", "7"} { + if !strings.Contains(out, n) { + t.Fatalf("missing position %s:\n%s", n, out) + } + } +} + +func TestPaiGowArrangedPreviewHighThenLow(t *testing.T) { + s := pgTestSnap() + s.Preview = true + s.PreviewHigh = s.Cards[:5] + s.PreviewLow = s.Cards[5:] + s.PreviewHighNums = []int{1, 2, 3, 4, 5} + s.PreviewLowNums = []int{6, 7} + out := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil)) + if !strings.Contains(out, "HIGH") || !strings.Contains(out, "LOW") { + t.Fatalf("missing HIGH/LOW labels:\n%s", out) + } + // The five-card high hand precedes the two-card low hand, and its number + // row still keys back to the dealt positions. + hi := strings.Index(out, "HIGH") + lo := strings.Index(out, "LOW") + if hi < 0 || lo < 0 || hi > lo { + t.Fatalf("HIGH/LOW order wrong:\n%s", out) + } + if !strings.Contains(out, "arranged hand") { + t.Fatalf("missing set prompt:\n%s", out) + } +} + +func TestPaiGowShowdownOrderNamesAndColors(t *testing.T) { + cv := func(rank, suit string) casino.CardView { return casino.CardView{Rank: rank, Suit: suit} } + s := pgTestSnap() + s.Phase = "waiting" + s.Showdown = &casino.PaiGowShowdown{ + Result: "loss", + Hands: []casino.PaiGowHandView{ + {Label: "Your high", Strength: "STRAIGHT ACE-HIGH", Cards: []casino.CardView{cv("A", "diamonds"), cv("K", "clubs"), cv("Q", "clubs"), cv("J", "spades"), cv("10", "spades")}, Winning: true, HasWin: true}, + {Label: "Dealer high", Strength: "TWO PAIR ACES AND KINGS", Cards: []casino.CardView{cv("A", "clubs"), cv("A", "hearts"), cv("K", "diamonds"), cv("K", "spades"), cv("9", "clubs")}, Winning: false, HasWin: true}, + {Label: "Your low", Strength: "PAIR OF ACES", Cards: []casino.CardView{cv("A", "diamonds"), cv("2", "diamonds")}, Winning: false, HasWin: true}, + {Label: "Dealer low", Strength: "HIGH CARD KING", Cards: []casino.CardView{cv("K", "clubs"), cv("9", "hearts")}, Winning: true, HasWin: true}, + }, + Stake: 25, Payout: 50, Paid: true, + } + raw := renderPaiGow(s, "xterm256", true, 7, "dark", nil) + clean := stripANSI(raw) + high := strings.Index(clean, "Your high") + dealerHigh := strings.Index(clean, "Dealer high") + low := strings.Index(clean, "Your low") + dealerLow := strings.Index(clean, "Dealer low") + if !(high >= 0 && high < dealerHigh && dealerHigh < low && low < dealerLow) { + t.Fatalf("showdown order wrong:\n%s", clean) + } + for _, want := range []string{"STRAIGHT ACE-HIGH", "TWO PAIR ACES AND KINGS", "PAIR OF ACES", "HIGH CARD KING", "DEALER WINS THE ROUND", "Net +25 credits."} { + if !strings.Contains(clean, want) { + t.Fatalf("showdown missing %q:\n%s", want, clean) + } + } + // Winning hand strengths are bold yellow; losing ones are bold white. + if !strings.Contains(raw, "38;5;220m") { + t.Fatalf("winning strength not yellow:\n%q", raw) + } + if !strings.Contains(raw, "38;5;15m") { + t.Fatalf("losing strength not white:\n%q", raw) + } +} + +func TestPaiGowOutcomeOmitsEnvyWithoutOthers(t *testing.T) { + s := pgTestSnap() + s.Phase = "waiting" + s.Showdown = &casino.PaiGowShowdown{ + Result: "win", + Hands: []casino.PaiGowHandView{ + {Label: "Your high", Strength: "PAIR OF KINGS", Winning: true, HasWin: true}, + {Label: "Dealer high", Strength: "HIGH CARD ACE", Winning: false, HasWin: true}, + {Label: "Your low", Strength: "PAIR OF ACES", Winning: true, HasWin: true}, + {Label: "Dealer low", Strength: "HIGH CARD KING", Winning: false, HasWin: true}, + }, + Payout: 20, Paid: true, + } + out := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil)) + if strings.Contains(out, "Envy") { + t.Fatalf("envy shown without other players:\n%s", out) + } + if !strings.Contains(out, "YOU WIN THE ROUND") { + t.Fatalf("overall win missing:\n%s", out) + } +} + +func TestPaiGowSetCommandLocksPreview(t *testing.T) { + g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: paigow\n min_bet: 5\n max_bet: 1000\n") + g.executePlay(sess, []string{"pai"}, "play pai") + g.executeBet(sess, []string{"20"}, "bet 20") + table, _ := g.Casino.Participation(sess.Player.Name) + // house only arranges; the hand stays unset until set. + conn.data = nil + g.executeCommand(sess, "house", nil, "house") + snap := table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot) + if !snap.Preview || table.State() != "setting" { + t.Fatalf("house should preview, not settle: preview=%v phase=%s", snap.Preview, table.State()) + } + // set locks and, for a lone bettor, settles the round. + g.executeCommand(sess, "set", nil, "set") + if table.State() != "waiting" { + t.Fatalf("set did not settle the round: %s", table.State()) + } +} + +func TestPaiGowAutosetConfigSkipsPreview(t *testing.T) { + g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: paigow\n min_bet: 5\n max_bet: 1000\n pai_gow:\n autoset: true\n") + g.executePlay(sess, []string{"pai"}, "play pai") + g.executeBet(sess, []string{"20"}, "bet 20") + conn.data = nil + g.executeCommand(sess, "house", nil, "house") + text := stripANSI(string(conn.data)) + if strings.Contains(text, "arranged hand") { + t.Fatalf("autoset showed the arrangement preview:\n%s", text) + } + if !strings.Contains(text, "DEALER WINS") && !strings.Contains(text, "YOU WIN") && !strings.Contains(text, "PUSH") { + t.Fatalf("autoset did not reach the showdown:\n%s", text) + } +} + func TestPaiGowFeltAndJokerAllModes(t *testing.T) { store := cardart.Load("../../data/cards.yaml") for _, size := range []int{1, 5, 7, 9} { @@ -228,8 +388,10 @@ func TestPaiGowStopMidRoundQueuesDeparture(t *testing.T) { if _, playing := g.Casino.Participation(sess.Player.Name); !playing { t.Fatal("stop mid-round removed the seat too early") } - // Finishing the hand applies the queued departure. + // Finishing the hand applies the queued departure. Autoset is off, so + // house arranges and set commits. g.executeCommand(sess, "house", nil, "house") + g.executeCommand(sess, "set", nil, "set") if _, playing := g.Casino.Participation(sess.Player.Name); playing { t.Fatal("queued stop did not leave after the round") } |
