aboutsummaryrefslogtreecommitdiff
path: root/internal/game/pai_gow_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-01 17:24:54 -0400
committerhistoria <[not public]>2026-10-01 17:24:54 -0400
commit4e2a95503e4ea4c830510f5aea23fac7948ddc41 (patch)
tree690b06df9094480febfaaaf7d9fc71ef7b81848d /internal/game/pai_gow_test.go
parentd762f8e1360cf38b8e11e69083c2dad59ec7130e (diff)
downloadthehouseoficarus-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.go170
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")
}