aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/video_poker_profiles.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/video_poker_profiles.go')
-rw-r--r--internal/casino/video_poker_profiles.go171
1 files changed, 171 insertions, 0 deletions
diff --git a/internal/casino/video_poker_profiles.go b/internal/casino/video_poker_profiles.go
new file mode 100644
index 0000000..9471ee6
--- /dev/null
+++ b/internal/casino/video_poker_profiles.go
@@ -0,0 +1,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"
+}