aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/video_poker_profiles.go
blob: 9471ee6c1d2226a2ec7298c7588fc514d9444cde (plain)
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package casino

import "math/rand"

// Bonus feature identifiers (cabinet editions).
const (
	PokerUltimateX     = "ultimate_x"
	PokerSuperTimesPay = "super_times_pay"
	PokerHotRoll       = "hot_roll"
)

// NextAward exposes the published Ultimate X multiplier the named hand earns
// for the selected hand count; 1 means the line earned nothing.
func (c VideoPokerConfig) NextAward(hands int, category string) int {
	if cabinetFeature(c.Normalize().Cabinet) != PokerUltimateX {
		return 1
	}
	return pokerNextMultiplier(c.Cabinet, c.Variant, hands, category)
}

// Each tuple is [single-line, 3-play, 5-play, 10-play]. These are the
// published Ultimate X award schedules, NOT an interpolation.
func pokerNextMultiplier(cabinet, variant string, hands int, category string) int {
	ix := 0
	switch {
	case cabinet == CabinetUXSingle:
		ix = 0
	case hands == 3:
		ix = 1
	case hands == 5:
		ix = 2
	case hands == 10:
		ix = 3
	}
	var m map[string][4]int
	switch variant {
	case "deuces_wild":
		m = map[string][4]int{"Natural royal flush": {2, 2, 2, 4}, "Four deuces": {2, 2, 2, 4}, "Wild royal flush": {2, 2, 2, 4}, "Five of a kind": {2, 2, 3, 3}, "Straight flush": {11, 12, 12, 12}, "Four of a kind": {7, 7, 7, 7}, "Full house": {5, 5, 5, 5}, "Flush": {5, 5, 5, 5}, "Straight": {3, 3, 3, 3}, "Three of a kind": {2, 2, 2, 2}}
	case "joker_poker":
		m = map[string][4]int{"Natural royal flush": {2, 2, 3, 4}, "Five of a kind": {2, 2, 3, 4}, "Wild royal flush": {2, 2, 3, 4}, "Straight flush": {2, 2, 3, 4}, "Four of a kind": {2, 2, 2, 2}, "Full house": {6, 7, 7, 7}, "Flush": {6, 6, 6, 6}, "Straight": {5, 5, 5, 5}, "Three of a kind": {4, 4, 4, 4}, "Two pair": {3, 3, 3, 3}, "Kings or better": {2, 2, 2, 2}}
	case "double_bonus", "double_double_bonus":
		m = map[string][4]int{"Royal flush": {2, 2, 2, 4}, "Straight flush": {2, 2, 2, 4}, "Four aces": {2, 2, 2, 4}, "Four 2s-4s": {2, 2, 2, 4}, "Four aces with 2-4 kicker": {2, 2, 2, 4}, "Four 2s-4s with A-4 kicker": {2, 2, 2, 4}, "Four 5s-Ks": {2, 2, 3, 3}, "Full house": {12, 12, 12, 12}, "Flush": {10, 10, 10, 10}, "Straight": {7, 8, 8, 8}, "Three of a kind": {4, 4, 4, 4}, "Two pair": {3, 3, 3, 3}, "Jacks or better": {2, 2, 2, 2}}
		if variant == "double_double_bonus" {
			for _, k := range []string{"Four aces", "Four 2s-4s", "Four aces with 2-4 kicker", "Four 2s-4s with A-4 kicker"} {
				row := m[k]
				row[0] = 4
				m[k] = row
			}
			m["Royal flush"] = [4]int{4, 2, 2, 4}
			m["Straight flush"] = [4]int{4, 2, 2, 4}
			m["Four 5s-Ks"] = [4]int{4, 2, 3, 3}
		}
	default:
		m = map[string][4]int{"Royal flush": {2, 2, 2, 7}, "Straight flush": {2, 2, 2, 7}, "Four of a kind": {2, 2, 3, 3}, "Full house": {12, 12, 12, 12}, "Flush": {10, 11, 11, 11}, "Straight": {7, 7, 7, 7}, "Three of a kind": {4, 4, 4, 4}, "Two pair": {3, 3, 3, 3}, "Jacks or better": {2, 2, 2, 2}}
	}
	if variant == "bonus_poker" {
		for _, k := range []string{"Royal flush", "Straight flush", "Four aces", "Four 2s-4s", "Four 5s-Ks"} {
			m[k] = [4]int{3, 2, 2, 4}
		}
		m["Four 5s-Ks"] = [4]int{3, 2, 3, 3}
		m["Straight"] = [4]int{7, 8, 8, 8}
	}
	if row, ok := m[category]; ok {
		return row[ix]
	}
	return 1
}

// FeatureRules names each feature's published cost and trigger behavior.
func (c VideoPokerConfig) FeatureRules() string {
	switch cabinetFeature(c.Normalize().Cabinet) {
	case PokerUltimateX:
		return "Ultimate X: feature on costs five extra coins per hand. Each winning line earns the NEXT hand's multiplier for that line; a current multiplier never multiplies another multiplier. The feature is not required to use a multiplier that is already showing. Multipliers stay with the machine until they are used."
	case PokerSuperTimesPay:
		return "Super Times Pay: feature on costs one extra coin per hand. Before the deal a 1-in-15 trigger awards one multiplier shared by every line: 2x/3x/4x/5x/8x/10x with probabilities 17/33/16/24/6/4 percent."
	case PokerHotRoll:
		return "Hot Roll: feature on costs five extra coins per hand. Before the deal a 1-in-6 trigger rolls two fair dice; their sum multiplies every line this round."
	}
	return "This cabinet carries no bonus feature."
}

// FeatureFeeCoins is the extra coin cost per hand while the feature is on.
func (c VideoPokerConfig) FeatureFeeCoins() int {
	switch cabinetFeature(c.Normalize().Cabinet) {
	case PokerUltimateX, PokerHotRoll:
		return 5
	case PokerSuperTimesPay:
		return 1
	}
	return 0
}

// pokerCoinPay is the total coin return of one line: rates are per coin, and
// the 52-card natural royal pays the flat 250-per-coin table below max bet.
func pokerCoinPay(category string, perCoin, coins int) int {
	if coins < 1 || coins > 5 {
		return 0
	}
	if (category == "Royal flush" || category == "Natural royal flush") && coins < 5 {
		return 250 * coins
	}
	return perCoin * coins
}

// PayRows renders the pay table for the selected game and hand count.
func (c VideoPokerConfig) PayRows(hands int) []PokerPayRow {
	c = c.Normalize()
	schedule, ok := pokerPayTable(c)
	if !ok {
		return nil
	}
	var rows []PokerPayRow
	for _, k := range pokerCategories {
		if n := schedule.pays[k]; n > 0 {
			row := PokerPayRow{Name: k, NextAward: c.NextAward(hands, k)}
			for i := range row.Coins {
				row.Coins[i] = pokerCoinPay(k, n, i+1)
			}
			rows = append(rows, row)
		}
	}
	return rows
}

// pokerRandomMultiplier rolls the deal-time bonus of Super Times Pay and
// Hot Roll; 1 means no multiplier. Hot Roll returns the dice that produced it.
func pokerRandomMultiplier(feature string, rng *rand.Rand) (int, [2]int) {
	if feature == PokerHotRoll && rng.Intn(6) == 0 {
		dice := [2]int{rng.Intn(6) + 1, rng.Intn(6) + 1}
		return dice[0] + dice[1], dice
	}
	if feature == PokerSuperTimesPay && rng.Intn(15) == 0 {
		n := rng.Intn(100)
		for _, v := range []struct{ n, m int }{{17, 2}, {33, 3}, {16, 4}, {24, 5}, {6, 8}, {4, 10}} {
			if n < v.n {
				return v.m, [2]int{}
			}
			n -= v.n
		}
	}
	return 1, [2]int{}
}

// pokerHelpTopic names the help page the "rules" command opens while seated
// at this cabinet; every shipped cabinet type has its own page.
func pokerHelpTopic(c VideoPokerConfig) string {
	switch c.Normalize().Cabinet {
	case CabinetUXSingle:
		return "ultimate x single line"
	case CabinetUXMulti:
		return "ultimate x multi hand"
	case CabinetSuperTimesPay:
		return "super times pay"
	case CabinetHotRoll:
		return "hot roll"
	}
	return "video poker"
}

// FeatureLabel is the feature's display name for badges and menus.
func (c VideoPokerConfig) FeatureLabel() string {
	switch cabinetFeature(c.Normalize().Cabinet) {
	case PokerUltimateX:
		return "Ultimate X"
	case PokerSuperTimesPay:
		return "Super Times Pay"
	case PokerHotRoll:
		return "Hot Roll"
	}
	return "Classic"
}