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_rules.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_rules.go')
| -rw-r--r-- | internal/casino/pai_gow_rules.go | 96 |
1 files changed, 72 insertions, 24 deletions
diff --git a/internal/casino/pai_gow_rules.go b/internal/casino/pai_gow_rules.go index 8a9842c..ced7c47 100644 --- a/internal/casino/pai_gow_rules.go +++ b/internal/casino/pai_gow_rules.go @@ -22,8 +22,14 @@ type PaiGowConfig struct { Envy bool `yaml:"envy,omitempty"` PushAceHigh string `yaml:"push_ace_high,omitempty"` // Face Up only: 5_15_40 or 5_12_40 ActionTicks int `yaml:"action_ticks,omitempty"` + // A2345SecondHighest makes the wheel the second-highest straight, below + // Broadway and above a six-high straight. It defaults to true; only an + // explicit false demotes A2345 to the lowest straight. + A2345SecondHighest *bool `yaml:"a2345_second_highest,omitempty"` } +func (c PaiGowConfig) A2345High() bool { return ruleBool(c.A2345SecondHighest, true) } + func (c PaiGowConfig) Normalize() PaiGowConfig { if c.Variant == "" { c.Variant = PaiGowClassic @@ -85,7 +91,11 @@ func (c PaiGowConfig) Label() string { } func paiGowRules(c TableConfig) string { r := c.PaiGow.Normalize() - s := r.Label() + "\nSeven cards from a fresh 53-card deck. Joker is an ace unless completing a straight, flush or straight flush. A2345 is the second-highest straight.\nSet two cards low and five high: 24 chooses low cards 2/4; 13456 chooses the high hand. High must strictly outrank low. Copies go to the dealer.\nBoth hands win: win; both lose: loss; one wins: push." + wheel := "A2345 is the second-highest straight." + if !r.A2345High() { + wheel = "A2345 is the lowest straight." + } + s := r.Label() + "\nSeven cards from a fresh 53-card deck. Joker is an ace unless completing a straight, flush or straight flush. " + wheel + "\nSet two cards low and five high: 24 chooses low cards 2/4; 13456 chooses the high hand. High must strictly outrank low. Copies go to the dealer.\nBoth hands win: win; both lose: loss; one wins: push." switch r.Variant { case PaiGowClassic: s += " Wins pay 0.95:1 (profit rounded down, minimum one credit)." @@ -138,7 +148,14 @@ func pgCompare(a, b pgRank) int { } return 0 } -func pgNatural(cards []card) pgRank { +// pgNatural keeps the default-rules entry point used by tests and callers that +// do not thread a table configuration. Production evaluation goes through +// pgNaturalRules. +// +//nolint:unused // default-rules entry point +func pgNatural(cards []card) pgRank { return pgNaturalRules(cards, true) } + +func pgNaturalRules(cards []card, a2345High bool) pgRank { counts := [15]int{} flush := len(cards) == 5 for _, c := range cards { @@ -162,7 +179,11 @@ func pgNatural(cards []card) pgRank { straight = sorted[4] } if sorted[0] == 2 && sorted[1] == 3 && sorted[2] == 4 && sorted[3] == 5 && sorted[4] == 14 { - straight = 15 + if a2345High { + straight = 15 + } else { + straight = 5 + } } if straight == 14 { straight = 16 @@ -196,7 +217,9 @@ func pgNatural(cards []card) pgRank { copy(out[1:], ranks) return out } -func pgEvaluate(cards []card) pgRank { +func pgEvaluate(cards []card) pgRank { return pgEvaluateRules(cards, true) } + +func pgEvaluateRules(cards []card, a2345High bool) pgRank { j := -1 for i, c := range cards { if c.rank == "JK" { @@ -205,11 +228,11 @@ func pgEvaluate(cards []card) pgRank { } } if j < 0 { - return pgNatural(cards) + return pgNaturalRules(cards, a2345High) } copyCards := append([]card(nil), cards...) copyCards[j] = card{"A", "joker"} - best := pgNatural(copyCards) + best := pgNaturalRules(copyCards, a2345High) if len(cards) != 5 { return best } @@ -226,7 +249,7 @@ func pgEvaluate(cards []card) pgRank { continue } copyCards[j] = replacement - v := pgNatural(copyCards) + v := pgNaturalRules(copyCards, a2345High) if (v[0] == 4 || v[0] == 5 || v[0] == 8) && pgCompare(v, best) > 0 { best = v } @@ -252,28 +275,32 @@ func pgSet(cards []card, digits []int) pgSplit { } return s } -func (s pgSplit) valid() bool { - return len(s.high) == 5 && len(s.low) == 2 && pgCompare(pgEvaluate(s.high), pgEvaluate(s.low)) > 0 +func (s pgSplit) valid() bool { return s.validRules(true) } + +func (s pgSplit) validRules(a2345High bool) bool { + return len(s.high) == 5 && len(s.low) == 2 && pgCompare(pgEvaluateRules(s.high, a2345High), pgEvaluateRules(s.low, a2345High)) > 0 } -func pgHouse(cards []card) pgSplit { +func pgHouse(cards []card) pgSplit { return pgHouseRules(cards, true) } + +func pgHouseRules(cards []card, a2345High bool) pgSplit { var options []pgSplit best := pgRank{} for i := 0; i < 7; i++ { for j := i + 1; j < 7; j++ { s := pgSet(cards, []int{i, j}) - if !s.valid() { + if !s.validRules(a2345High) { continue } options = append(options, s) - if pgCompare(pgEvaluate(s.high), best) > 0 { - best = pgEvaluate(s.high) + if pgCompare(pgEvaluateRules(s.high, a2345High), best) > 0 { + best = pgEvaluateRules(s.high, a2345High) } } } var chosen pgSplit var low, high pgRank for _, s := range options { - h, l := pgEvaluate(s.high), pgEvaluate(s.low) + h, l := pgEvaluateRules(s.high, a2345High), pgEvaluateRules(s.low, a2345High) eligible := h[0] == best[0] // Split two pair/full house only for a paired front with at least a pair behind. if (best[0] == 2 || best[0] == 6) && l[0] == 1 && h[0] >= 1 { @@ -340,7 +367,9 @@ func pgSevenFlush(cards []card) bool { } return false } -func pgFortune(cards []card, envy bool) (FortuneRow, bool) { +func pgFortune(cards []card, envy bool) (FortuneRow, bool) { return pgFortuneRules(cards, envy, true) } + +func pgFortuneRules(cards []card, envy, a2345High bool) (FortuneRow, bool) { rows := FortunePayTable(envy) joker := false counts := [15]int{} @@ -361,7 +390,7 @@ func pgFortune(cards []card, envy bool) (FortuneRow, bool) { for i := 0; i < 7; i++ { for j := i + 1; j < 7; j++ { s := pgSet(cards, []int{i, j}) - v := pgEvaluate(s.high) + v := pgEvaluateRules(s.high, a2345High) index := len(rows) switch v[0] { case 9: @@ -408,11 +437,18 @@ func pgFortune(cards []card, envy bool) (FortuneRow, bool) { } return rows[bestIndex], true } -func pgBest(cards []card) pgRank { +// pgBest keeps the default-rules entry point used by tests and callers that do +// not thread a table configuration. Production evaluation goes through +// pgBestRules. +// +//nolint:unused // default-rules entry point +func pgBest(cards []card) pgRank { return pgBestRules(cards, true) } + +func pgBestRules(cards []card, a2345High bool) pgRank { var best pgRank for i := 0; i < 7; i++ { for j := i + 1; j < 7; j++ { - v := pgEvaluate(pgSet(cards, []int{i, j}).high) + v := pgEvaluateRules(pgSet(cards, []int{i, j}).high, a2345High) if pgCompare(v, best) > 0 { best = v } @@ -424,10 +460,14 @@ func pgBest(cards []card) pgRank { // Automatic pushes use the best of all seven dealer cards, not just the // chosen high hand. Pair/straight/flush hands headed by an ace do not qualify. func pgDealerPush(cards []card, variant string) string { + return pgDealerPushRules(cards, variant, true) +} + +func pgDealerPushRules(cards []card, variant string, a2345High bool) string { if variant != PaiGowEZ && variant != PaiGowFaceUp { return "" } - v := pgBest(cards) + v := pgBestRules(cards, a2345High) if v[0] == 0 { if variant == PaiGowEZ && v[1] == 12 { return "push (dealer queen-high)" @@ -439,11 +479,15 @@ func pgDealerPush(cards []card, variant string) string { return "" } func pgMainResult(player, dealer pgSplit, dealerCards []card, variant string, tie [2]card) string { - if push := pgDealerPush(dealerCards, variant); push != "" { + return pgMainResultRules(player, dealer, dealerCards, variant, tie, true) +} + +func pgMainResultRules(player, dealer pgSplit, dealerCards []card, variant string, tie [2]card, a2345High bool) string { + if push := pgDealerPushRules(dealerCards, variant, a2345High); push != "" { return push } - high := pgCompare(pgEvaluate(player.high), pgEvaluate(dealer.high)) > 0 - low := pgCompare(pgEvaluate(player.low), pgEvaluate(dealer.low)) > 0 + high := pgCompare(pgEvaluateRules(player.high, a2345High), pgEvaluateRules(dealer.high, a2345High)) > 0 + low := pgCompare(pgEvaluateRules(player.low, a2345High), pgEvaluateRules(dealer.low, a2345High)) > 0 if high && low { return "win" } @@ -475,7 +519,11 @@ type pgPushAce struct { } func pgPushAceClass(dealer, player []card, rates [3]int) pgPushAce { - if pgBest(dealer)[0] != 0 || pgBest(dealer)[1] != 14 { + return pgPushAceClassRules(dealer, player, rates, true) +} + +func pgPushAceClassRules(dealer, player []card, rates [3]int, a2345High bool) pgPushAce { + if pgBestRules(dealer, a2345High)[0] != 0 || pgBestRules(dealer, a2345High)[1] != 14 { return pgPushAce{klass: "none"} } for _, c := range dealer { @@ -483,7 +531,7 @@ func pgPushAceClass(dealer, player []card, rates [3]int) pgPushAce { return pgPushAce{klass: "joker", rate: rates[1], desc: "dealer ace-high with Joker"} } } - if pgBest(player)[0] == 0 && pgBest(player)[1] == 14 { + if pgBestRules(player, a2345High)[0] == 0 && pgBestRules(player, a2345High)[1] == 14 { return pgPushAce{klass: "both", rate: rates[2], desc: "both ace-high"} } return pgPushAce{klass: "plain", rate: rates[0], desc: "dealer ace-high without Joker"} |
