diff options
Diffstat (limited to 'internal/casino/video_poker_test.go')
| -rw-r--r-- | internal/casino/video_poker_test.go | 141 |
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") + } +} |
