1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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")
}
}
|