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
|
package casino
import (
"reflect"
"strconv"
"testing"
)
func TestPokerEveryCabinetTierGameAndAward(t *testing.T) {
for cabinet, games := range cabinetGames {
for _, tier := range []string{"low", "normal", "full"} {
table, _, _ := pokerTestCabinet(t, VideoPokerConfig{Cabinet: cabinet, Payout: tier})
for _, variant := range games {
events := table.action("alice", "game "+variant)
p := table.players["alice"]
if p.variant != variant || p.config.Payout != tier || len(events) != 1 || events[0].Snapshot == nil {
t.Fatalf("%s/%s/%s failed selection: %+v", cabinet, tier, variant, p.config)
}
if err := p.config.Validate(); err != nil {
t.Fatal(err)
}
for _, hands := range CabinetHandCounts(cabinet, variant) {
table.action("alice", "hands "+strconv.Itoa(hands))
for _, row := range p.config.PayRows(hands) {
if row.Coins[4] <= 0 {
t.Fatalf("zero payout: %s", row.Name)
}
if p.config.Feature == PokerUltimateX && (row.NextAward < 2 || row.NextAward > 12) {
t.Fatalf("missing UX award: %s/%s/%d/%s = %d", cabinet, variant, hands, row.Name, row.NextAward)
}
}
}
}
// The authored tier is machine state, not a player setting: a new
// occupant inherits it unchanged.
table.leave("alice")
table.join("bob")
if table.players["bob"].config.Payout != tier {
t.Fatalf("%s/%s: tier changed with occupant", cabinet, tier)
}
}
}
}
func TestPokerInvalidLoadPreservesPreviousCoins(t *testing.T) {
table, _, _ := pokerTestCabinet(t, VideoPokerConfig{Cabinet: CabinetClassic})
table.action("alice", "coins 2")
for _, input := range []string{"coins nope", "coins 0", "coins 5", "coins -1", "coins 1 2"} {
table.action("alice", input)
if table.players["alice"].loaded != 2 || table.players["alice"].lastBet != 2 {
t.Fatalf("invalid load %q changed wager", input)
}
}
}
func TestPokerDuplicateHoldDigitsToggleOnce(t *testing.T) {
table, w, _ := pokerTestCabinet(t, VideoPokerConfig{Cabinet: CabinetClassic})
pokerDeal(t, table, w)
table.action("alice", "5 3 1 1 3 5")
want := [5]bool{true, false, true, false, true}
if table.players["alice"].held != want {
t.Fatal("duplicates did not toggle once")
}
table.action("alice", "hold none")
if table.players["alice"].held != want {
t.Fatal("hold none changed cards")
}
table.action("alice", "hold 531")
if table.players["alice"].held != ([5]bool{}) {
t.Fatal("second hold command did not release cards")
}
}
func TestPokerFailedUXSettlementPreservesBankAndFinalHands(t *testing.T) {
table, w, bank := pokerTestCabinet(t, VideoPokerConfig{Cabinet: CabinetUXSingle})
p := table.players["alice"]
key := table.multiplierKey(p)
table.banks[key] = []int{3}
pokerDeal(t, table, w)
p.cards = pokerHand("Ah 3h 6h 9h Jh")
table.action("alice", "12345")
w.fail = true
table.action("alice", "draw")
frozen := append([]PokerLine(nil), p.lines...)
if bank.writes != 0 || !reflect.DeepEqual(table.banks[key], []int{3}) || !p.active {
t.Fatal("failed payout consumed old bank")
}
table.action("alice", "draw")
if !reflect.DeepEqual(frozen, p.lines) {
t.Fatal("retry changed final cards")
}
w.fail = false
table.action("alice", "draw")
if w.paid != 90 || bank.writes != 1 || !reflect.DeepEqual(table.banks[key], []int{10}) {
t.Fatalf("retry: paid=%d bank=%v writes=%d", w.paid, table.banks[key], bank.writes)
}
table.action("alice", "draw")
if bank.writes != 1 || w.paid != 90 {
t.Fatal("settlement replayed")
}
table.leave("alice")
table.join("bob")
snap := table.status("bob")[0].Snapshot.(*VideoPokerSnapshot)
if !reflect.DeepEqual(snap.Next, []int{10}) {
t.Fatal("new occupant did not inherit earned bank")
}
}
func TestPokerKnownFeatureScheduleValues(t *testing.T) {
cases := []struct{ cabinet, tier, variant, table string }{
{CabinetHotRoll, "normal", "jacks_or_better", "9/5"},
{CabinetHotRoll, "normal", "double_double_bonus", "9/5"},
{CabinetSuperTimesPay, "full", "double_double_bonus", "9/6"},
{CabinetSuperTimesPay, "full", "double_bonus", "9/7"},
{CabinetUXMulti, "full", "double_bonus", "9/6/5"},
}
for _, tt := range cases {
c := (VideoPokerConfig{Cabinet: tt.cabinet, Payout: tt.tier, Variant: tt.variant}).Normalize()
if c.PayTable != tt.table {
t.Fatalf("%+v => %s", tt, c.PayTable)
}
}
for _, variant := range []string{"bonus_poker", "double_double_bonus"} {
cfg := (VideoPokerConfig{Cabinet: CabinetUXSingle, Variant: variant}).Normalize()
want := 3
if variant == "double_double_bonus" {
want = 4
}
for _, category := range []string{"Royal flush", "Straight flush", "Four aces", "Four 2s-4s", "Four 5s-Ks"} {
if got := cfg.NextAward(1, category); got != want {
t.Fatalf("%s %s: %dx != %dx", variant, category, got, want)
}
}
}
}
|