diff options
| author | historia <[not public]> | 2026-10-01 16:26:11 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-10-01 16:26:11 -0400 |
| commit | d762f8e1360cf38b8e11e69083c2dad59ec7130e (patch) | |
| tree | 4e684608e02b39b6b085e2a2ab9726bdae063240 /internal/casino/pai_gow_test.go | |
| parent | c94728c929045f7c1bdf0166df152d944e5f99bc (diff) | |
| download | thehouseoficarus-d762f8e1360cf38b8e11e69083c2dad59ec7130e.tar.gz | |
fix: trim extra info from games, A2345 support in pai gow, remove bet timers for single player games
Diffstat (limited to 'internal/casino/pai_gow_test.go')
| -rw-r--r-- | internal/casino/pai_gow_test.go | 163 |
1 files changed, 139 insertions, 24 deletions
diff --git a/internal/casino/pai_gow_test.go b/internal/casino/pai_gow_test.go index f258b04..02ea187 100644 --- a/internal/casino/pai_gow_test.go +++ b/internal/casino/pai_gow_test.go @@ -181,17 +181,19 @@ func TestPaiGowLifecyclePrivacyAndUniqueDeck(t *testing.T) { wallets := map[string]*testWallet{} for _, name := range []string{"a", "b", "c", "d", "e", "f"} { tab.join(name) - w := &testWallet{chips: 100} - wallets[name] = w - tab.bet(name, "main", 10, w) + wallets[name] = &testWallet{chips: 100} + } + var events []Event + for _, name := range []string{"a", "b", "c", "d", "e", "f"} { + w := wallets[name] + // Side bets settle before the main wager; the last main bet deals. tab.bet(name, "fortune", 5, w) + events = tab.bet(name, "main", 10, w) } tab.join("overflow") if tab.hasParticipant("overflow") { t.Fatal("seventh seat") } - tab.tick() - events := tab.tick() if tab.phase != "setting" { t.Fatal(tab.phase) } @@ -214,6 +216,9 @@ func TestPaiGowLifecyclePrivacyAndUniqueDeck(t *testing.T) { t.Fatal(len(seen)) } for _, e := range events { + if e.Snapshot == nil { + continue + } if e.Public || e.PrivateTo == "" { t.Fatal("private cards leaked") } @@ -265,11 +270,10 @@ func TestPaiGowAtomicRetryAndEnvy(t *testing.T) { b := &pgFailWallet{testWallet: testWallet{chips: 100}} tab.join("a") tab.join("b") - tab.bet("a", "main", 20, a) tab.bet("a", "fortune", 5, a) + tab.bet("a", "main", 20, a) + // b's main wager completes the table and deals immediately. tab.bet("b", "main", 20, b) - tab.action("a", "deal") - tab.action("b", "deal") // Script the dealt cards before either player sets. a loses Fortune but // collects b's fixed quad Envy, even though b didn't bet Fortune. tab.player("a").cards = pgCards("2s 4h 6c 8d Js Qh Ac") @@ -303,8 +307,8 @@ func TestPaiGowRejectedFoulDoesNotLock(t *testing.T) { tab := pgTestTable(false) w := &testWallet{chips: 100} tab.join("a") + // A lone main wager deals immediately, so the cards are already out. tab.bet("a", "", 10, w) - tab.action("a", "deal") p := tab.player("a") p.cards = pgCards("As Ah 2c 3d 4s 7h 9c") events := tab.action("a", "12") @@ -326,16 +330,19 @@ func TestPaiGowEnvyEligibilityAndMultipleAwards(t *testing.T) { stake, envy int }{{true, 5, 25}, {true, 4, 0}, {false, 5, 0}} { tab := pgTestTable(tt.enabled) + wallets := map[string]*testWallet{} for _, name := range []string{"a", "b", "c"} { tab.join(name) - w := &testWallet{chips: 100} - tab.bet(name, "main", 20, w) + wallets[name] = &testWallet{chips: 100} + } + for _, name := range []string{"a", "b", "c"} { + w := wallets[name] if name == "a" { tab.bet(name, "fortune", tt.stake, w) } + tab.bet(name, "main", 20, w) } - tab.tick() - tab.tick() + // The last main wager deals; script the hands before setting. // Own royal and dealer seven-card straight flush must NOT earn Envy. tab.player("a").cards = pgCards("10s Js Qs Ks As 2h 3d") tab.player("b").cards = pgCards("Ks Kh Kc Kd 2h 3c 7h") @@ -401,16 +408,17 @@ func TestPaiGowFaceUpAndPushAceHigh(t *testing.T) { w := &testWallet{chips: 500} tab.join("a") tab.bet("a", "pushace", 10, w) - if len(w.open) > 0 { - t.Fatal("pushace before main") + if len(w.open) != 1 { + t.Fatal("pushace before main should be accepted") } - tab.bet("a", "main", 20, w) - if events := tab.bet("a", "pushace", 11, w); len(w.open) != 1 || !strings.Contains(events[0].Message, "half the main wager") { + tab.bet("a", "fortune", 5, w) + // The cap is checked when the main wager lands. + if events := tab.bet("a", "main", 10, w); len(events) == 0 || !strings.Contains(events[0].Message, "half the main wager") { t.Fatal("pushace cap not enforced", events, w.open) } - tab.bet("a", "pushace", 10, w) - tab.bet("a", "fortune", 5, w) - tab.action("a", "deal") + tab.bet("a", "main", 20, w) + pushaceStake := tab.player("a").pushace.Total + // A lone main wager deals immediately; the dealer is revealed in setting. snap := tab.status("a")[0].Snapshot.(*PaiGowSnapshot) // The dealer acts first and the reveal happens in the setting phase. if len(snap.DealerHigh) != 5 || len(snap.DealerLow) != 2 { @@ -432,7 +440,7 @@ func TestPaiGowFaceUpAndPushAceHigh(t *testing.T) { expect += 5 * (row.Odds + 1) // the Fortune stake, before settlement resets it } if class := pgPushAceClass(tab.dealer, p.cards, [3]int{5, 15, 40}); class.klass != "none" { - expect += p.pushace.Total * (class.rate + 1) + expect += pushaceStake * (class.rate + 1) } if p.payout != expect { t.Fatalf("payout %d want %d (dealer %v, result %s)", p.payout, expect, tab.dealer, p.result) @@ -452,13 +460,120 @@ func TestPaiGowConfigurationAndRefund(t *testing.T) { w := &testWallet{chips: 100} tab.join("a") tab.bet("a", "fortune", 5, w) - if len(w.open) > 0 { - t.Fatal("side before main") + if len(w.open) != 1 { + t.Fatal("side before main should be accepted") } tab.bet("a", "main", 10, w) - tab.bet("a", "fortune", 5, w) tab.refundAll() if w.chips != 100 || len(w.open) > 0 { t.Fatal("refund failed") } } + +func TestPaiGowA2345Option(t *testing.T) { + if !(PaiGowConfig{}).A2345High() { + t.Fatal("A2345 must default to the second-highest straight") + } + wheel := pgCards("As 2h 3c 4d 5s") + if got := pgEvaluate(wheel); got != (pgRank{4, 15}) { + t.Fatalf("default wheel rank %v", got) + } + if got := pgEvaluateRules(wheel, false); got != (pgRank{4, 5}) { + t.Fatalf("demoted wheel rank %v", got) + } + six := pgCards("2h 3c 4d 5s 6h") + if pgCompare(pgEvaluateRules(wheel, false), pgEvaluateRules(six, false)) >= 0 { + t.Fatal("a demoted wheel must be the lowest straight") + } + jokerWheel := pgCards("Ah 2c 3d 4s JK") + if got := pgEvaluateRules(jokerWheel, false); got[0] != 4 || got[1] != 5 { + t.Fatalf("demoted Joker wheel rank %v", got) + } +} + +func TestPaiGowSoloMainDealsAfterSideBets(t *testing.T) { + tab := pgTestTable(false) + w := &testWallet{chips: 100} + tab.join("a") + // Side bets come first; they are accepted before any main wager. + tab.bet("a", "fortune", 5, w) + if len(w.open) != 1 { + t.Fatalf("fortune before main rejected: %v", w.open) + } + if tab.phase != "waiting" { + t.Fatalf("a side bet alone opened the betting window: %s", tab.phase) + } + // The main wager deals a lone table immediately. + tab.bet("a", "main", 10, w) + if tab.phase != "setting" { + t.Fatalf("solo main did not deal: %s", tab.phase) + } + if events := tab.bet("a", "fortune", 5, w); len(events) == 0 || !hasMessage(events, "Betting is closed") { + t.Fatalf("bet accepted after the deal: %+v", events) + } +} + +func TestPaiGowWindowDealsWhenEveryBettorIsIn(t *testing.T) { + tab := pgTestTable(false) + wa, wb := &testWallet{chips: 100}, &testWallet{chips: 100} + tab.join("a") + tab.join("b") + tab.bet("a", "main", 10, wa) + if tab.phase != "betting" { + t.Fatalf("window did not open for a second seat: %s", tab.phase) + } + tab.bet("b", "main", 10, wb) + if tab.phase != "setting" { + t.Fatalf("all-bet table did not deal: %s", tab.phase) + } +} + +func TestPaiGowUnbetAndStopRefundBeforeDeal(t *testing.T) { + tab := pgTestTable(false) + wa := &testWallet{chips: 100} + tab.join("a") + tab.join("b") + tab.bet("a", "main", 10, wa) + if events := tab.action("a", "unbet main"); !hasMessage(events, "take back") { + t.Fatalf("unbet main failed: %+v", events) + } + if wa.chips+wa.paid != 100 || len(wa.open) != 0 || tab.phase != "waiting" || tab.player("a").main.Total != 0 { + t.Fatalf("unbet main left state: chips=%d paid=%d phase=%s", wa.chips, wa.paid, tab.phase) + } + // A side bet placed and then abandoned is returned when the seat leaves. + tab.bet("a", "fortune", 5, wa) + if wa.chips+wa.paid != 95 { + t.Fatalf("fortune not charged: chips=%d paid=%d", wa.chips, wa.paid) + } + if events := tab.leave("a"); !hasMessage(events, "You leave Pai Gow Poker.") { + t.Fatalf("leave during betting: %+v", events) + } + if wa.chips+wa.paid != 100 || len(wa.open) != 0 || tab.hasParticipant("a") { + t.Fatalf("stop did not refund/remove: chips=%d paid=%d seated=%v", wa.chips, wa.paid, tab.hasParticipant("a")) + } +} + +func TestPaiGowPushAceCapAtMainWager(t *testing.T) { + cfg := TableConfig{ID: "pg", Game: GamePaiGow, PaiGow: PaiGowConfig{Variant: PaiGowFaceUp, PushAceHigh: "5_15_40"}}.Normalize() + tab := newPaiGowTable(1, cfg, rand.New(rand.NewSource(9))).(*paiGowTable) + w := &testWallet{chips: 100} + tab.join("a") + tab.bet("a", "pushace", 10, w) + if events := tab.bet("a", "main", 10, w); len(events) == 0 || !hasMessage(events, "half the main wager") { + t.Fatalf("pushace cap not checked when the main lands: %+v", events) + } + if w.chips != 90 || tab.player("a").main.Total != 0 { + t.Fatalf("rejected main changed state: chips=%d main=%d", w.chips, tab.player("a").main.Total) + } +} + +func TestPaiGowRefundAllReturnsSideBetsWithoutMain(t *testing.T) { + tab := pgTestTable(false) + w := &testWallet{chips: 100} + tab.join("a") + tab.bet("a", "fortune", 5, w) + tab.refundAll() + if w.chips != 100 || len(w.open) != 0 { + t.Fatalf("side-only refund failed: chips=%d open=%d", w.chips, len(w.open)) + } +} |
