aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/video_poker_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/video_poker_test.go')
-rw-r--r--internal/casino/video_poker_test.go141
1 files changed, 141 insertions, 0 deletions
diff --git a/internal/casino/video_poker_test.go b/internal/casino/video_poker_test.go
new file mode 100644
index 0000000..bd9e38d
--- /dev/null
+++ b/internal/casino/video_poker_test.go
@@ -0,0 +1,141 @@
+package casino
+
+import (
+ "strings"
+ "testing"
+)
+
+func pokerHand(text string) [5]card {
+ var cards [5]card
+ suits := map[byte]string{'c': "clubs", 'd': "diamonds", 'h': "hearts", 's': "spades"}
+ for i, s := range strings.Fields(text) {
+ if s == "JK" {
+ cards[i] = card{rank: "JK", suit: "joker"}
+ continue
+ }
+ cards[i] = card{rank: s[:len(s)-1], suit: suits[s[len(s)-1]]}
+ }
+ return cards
+}
+func TestPokerEvaluation(t *testing.T) {
+ tests := []struct {
+ variant, table, hand, category string
+ pay int
+ }{
+ {"jacks_or_better", "9/6", "Ah Kh Qh Jh 10h", "Royal flush", 800},
+ {"jacks_or_better", "9/6", "Ah Ac As Ad 10h", "Four of a kind", 25},
+ {"jacks_or_better", "9/6", "Ah 2h 3h 4h 5h", "Straight flush", 50},
+ {"jacks_or_better", "9/6", "Ah 2c 3h 4h 5h", "Straight", 4},
+ {"jacks_or_better", "9/6", "Qh Kh Ac 2h 3h", "No win", 0},
+ {"jacks_or_better", "9/6", "10h 10c 4s 6h 8c", "No win", 0},
+ {"jacks_or_better", "9/6", "Jh Jc 4s 6h 8c", "Jacks or better", 1},
+ {"jacks_or_better", "9/6", "Jh Jc Js 6h 6c", "Full house", 9},
+ {"jacks_or_better", "8/5", "Jh Jc Js 6h 6c", "Full house", 8},
+ {"jacks_or_better", "8/5", "Jh 3h 8h 6h 9h", "Flush", 5},
+ {"jacks_or_better", "9/6", "Jh Jc 4s 4h 8c", "Two pair", 2},
+ {"bonus_poker", "8/5", "Ah Ac As Ad 2c", "Four aces", 80},
+ {"bonus_poker", "8/5", "3h 3c 3s 3d Ac", "Four 2s-4s", 40},
+ {"bonus_poker", "8/5", "Kh Kc Ks Kd Ac", "Four 5s-Ks", 25},
+ {"double_bonus", "10/7", "Ah Ac As Ad 2c", "Four aces", 160},
+ {"double_bonus", "10/7", "3h 3c 3s 3d Ac", "Four 2s-4s", 80},
+ {"double_bonus", "10/7", "Kh Kc Ks Kd Ac", "Four 5s-Ks", 50},
+ {"double_bonus", "10/7", "Ah 2c 3h 4h 5h", "Straight", 5},
+ {"double_bonus", "9/6/4", "Ah 2c 3h 4h 5h", "Straight", 4},
+ {"double_double_bonus", "10/6", "Ah Ac As Ad 2c", "Four aces with 2-4 kicker", 400},
+ {"double_double_bonus", "10/6", "Ah Ac As Ad 5c", "Four aces", 160},
+ {"double_double_bonus", "9/6", "4h 4c 4s 4d Ac", "Four 2s-4s with A-4 kicker", 160},
+ {"double_double_bonus", "8/5", "2h 2c 2s 2d 4c", "Four 2s-4s with A-4 kicker", 160},
+ {"double_double_bonus", "10/6", "4h 4c 4s 4d 5c", "Four 2s-4s", 80},
+ {"double_double_bonus", "10/6", "Jh Jc Js 6h 6c", "Full house", 10},
+ {"deuces_wild", "25/15/9/5/3/2", "Ah Kh Qh Jh 10h", "Natural royal flush", 800},
+ {"deuces_wild", "25/15/9/5/3/2", "2h 2c 2s 2d Ac", "Four deuces", 200},
+ {"deuces_wild", "25/15/9/5/3/2", "Ah Kh Qh 2c 10h", "Wild royal flush", 25},
+ {"deuces_wild", "25/15/9/5/3/2", "Ah Kh Qc 2c 10h", "Straight", 2},
+ {"deuces_wild", "25/15/9/5/3/2", "Ah Ac As 2c 2h", "Five of a kind", 15},
+ {"deuces_wild", "25/15/9/5/3/2", "3h 4h 5h 2c 2h", "Straight flush", 9},
+ {"deuces_wild", "25/15/9/5/3/2", "3h 3c 3s 2c 7h", "Four of a kind", 5},
+ {"deuces_wild", "25/15/9/5/3/2", "3h 3c 7s 2c 7h", "Full house", 3},
+ {"deuces_wild", "25/15/9/5/3/2", "3h 4h 8h 2c Jh", "Flush", 2},
+ {"deuces_wild", "20/12/10/4/4/3", "3h 4h 8h 2c Jh", "Flush", 3},
+ {"deuces_wild", "20/12/10/4/4/3", "Ah Kh Qh 2c 10h", "Wild royal flush", 20},
+ {"deuces_wild", "25/15/9/5/3/2", "3h 3c 8s 2c Jh", "Three of a kind", 1},
+ {"deuces_wild", "25/15/9/5/3/2", "3h 3c 8s 8c Jh", "No win", 0},
+ {"deuces_wild", "25/15/9/5/3/2", "Ah Ac 8s 5c Jh", "No win", 0},
+ {"joker_poker", "50/17/7/5/3", "Ah Kh Qh Jh 10h", "Natural royal flush", 800},
+ {"joker_poker", "50/17/7/5/3", "Ah Kh Qh JK 10h", "Wild royal flush", 100},
+ {"joker_poker", "50/17/7/5/3", "Ah Ac As Ad JK", "Five of a kind", 200},
+ {"joker_poker", "50/17/7/5/3", "Kh 3c 5s 8d JK", "Kings or better", 1},
+ {"joker_poker", "50/17/7/5/3", "Qh Qc 5s 8d 9h", "No win", 0},
+ {"joker_poker", "50/17/7/5/3", "Qh Qc 5s 5d 9h", "Two pair", 1},
+ {"joker_poker", "50/17/7/5/3", "Kh Kc 5s 5d JK", "Full house", 7},
+ {"bonus_poker_deluxe", "9/6", "2h 2c 2s 2d Ac", "Four of a kind", 80},
+ {"bonus_poker_deluxe", "9/6", "Jh Jc 4s 4h 8c", "Two pair", 1},
+ }
+ for _, tt := range tests {
+ t.Run(tt.variant+"/"+tt.hand, func(t *testing.T) {
+ got, pay := evaluatePoker(pokerHand(tt.hand), VideoPokerConfig{Variant: tt.variant, PayTable: tt.table})
+ if got != tt.category || pay != tt.pay {
+ t.Fatalf("got %s/%d, want %s/%d", got, pay, tt.category, tt.pay)
+ }
+ })
+ }
+}
+func TestPokerHoldDigits(t *testing.T) {
+ for _, input := range []string{"531", "5 3 1", " 5\t3\n1 "} {
+ digits, ok := ParsePokerDigits(input)
+ if !ok || len(digits) != 3 {
+ t.Fatalf("%q: %v %v", input, digits, ok)
+ }
+ }
+ for _, input := range []string{"", " ", "0", "6", "123456", "1,3", "-1", "1x3"} {
+ if _, ok := ParsePokerDigits(input); ok {
+ t.Fatalf("accepted %q", input)
+ }
+ }
+}
+func TestPokerCabinets(t *testing.T) {
+ // Every cabinet type normalizes to its fixed feature and default game.
+ cfg := TableConfig{ID: "vp", Game: GameVideoPoker, VideoPoker: VideoPokerConfig{Cabinet: CabinetUXMulti}}.Normalize()
+ if err := cfg.Validate(); err != nil {
+ t.Fatal(err)
+ }
+ if cfg.VideoPoker.Feature != PokerUltimateX || cfg.VideoPoker.Payout != "full" || cfg.VideoPoker.Variant != "jacks_or_better" {
+ t.Fatalf("UX normalize: %+v", cfg.VideoPoker)
+ }
+ if cfg.VideoPoker.PayTable != "8/6" {
+ t.Fatalf("UX multi schedule %q", cfg.VideoPoker.PayTable)
+ }
+ single := TableConfig{ID: "vp", Game: GameVideoPoker, VideoPoker: VideoPokerConfig{Cabinet: CabinetUXSingle}}.Normalize()
+ if single.VideoPoker.PayTable != "9/6" {
+ t.Fatalf("UX single schedule %q", single.VideoPoker.PayTable)
+ }
+ hr := TableConfig{ID: "vp", Game: GameVideoPoker, VideoPoker: VideoPokerConfig{Cabinet: CabinetHotRoll}}.Normalize()
+ if hr.VideoPoker.PayTable != "9/6" || hr.VideoPoker.Feature != PokerHotRoll {
+ t.Fatalf("hot roll: %+v", hr.VideoPoker)
+ }
+ // Authored overrides are rejected; tiers must be known values.
+ for _, c := range []VideoPokerConfig{{Cabinet: "unknown"}, {Payout: "high"}, {Feature: "ux_stp"}} {
+ if (TableConfig{ID: "vp", Game: GameVideoPoker, VideoPoker: c}).Validate() == nil {
+ t.Fatalf("accepted %+v", c)
+ }
+ }
+ // Low/normal tiers resolve verified short-pay schedules on classic cabinets.
+ tiers := map[string]string{"full": "9/6", "normal": "8/5", "low": "7/5"}
+ for tier, want := range tiers {
+ cfg := TableConfig{ID: "vp", Game: GameVideoPoker, VideoPoker: VideoPokerConfig{Payout: tier}}.Normalize()
+ if err := cfg.Validate(); err != nil {
+ t.Fatal(err)
+ }
+ if cfg.VideoPoker.PayTable != want {
+ t.Fatalf("%s: %q", tier, cfg.VideoPoker.PayTable)
+ }
+ }
+ // The pay table carries across games: tier survives a game switch.
+ mid := TableConfig{ID: "vp", Game: GameVideoPoker, VideoPoker: VideoPokerConfig{Payout: "normal", Variant: "deuces_wild"}}.Normalize()
+ if mid.VideoPoker.PayTable != "25/15/9/4/4/3" || mid.VideoPoker.Feature != "" {
+ t.Fatalf("mid deuces: %+v", mid.VideoPoker)
+ }
+ if (TableConfig{ID: "bj", Game: GameBlackjack, VideoPoker: VideoPokerConfig{Cabinet: CabinetClassic, Payout: "full", Variant: "jacks_or_better"}}).Validate() == nil {
+ t.Fatal("foreign block accepted")
+ }
+}