aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/video_poker_config.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-01 04:26:20 -0400
committerhistoria <[not public]>2026-10-01 04:26:20 -0400
commit0ba388bbe229dd1ccfcb0831d21a5542ae0935f1 (patch)
treec5686cf404e3dc5001d2c9b2792749ae7a3c9801 /internal/casino/video_poker_config.go
parente3ff06fc79682673194c8913f0feb9e90920e7d0 (diff)
downloadthehouseoficarus-0ba388bbe229dd1ccfcb0831d21a5542ae0935f1.tar.gz
feat: video poker machines added to casino
Diffstat (limited to 'internal/casino/video_poker_config.go')
-rw-r--r--internal/casino/video_poker_config.go218
1 files changed, 218 insertions, 0 deletions
diff --git a/internal/casino/video_poker_config.go b/internal/casino/video_poker_config.go
new file mode 100644
index 0000000..918fed3
--- /dev/null
+++ b/internal/casino/video_poker_config.go
@@ -0,0 +1,218 @@
+package casino
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Cabinet and Payout are authored settings. The remaining fields identify the
+// selected game and its resolved schedule; schedules are never separate games.
+type VideoPokerConfig struct {
+ Cabinet string `yaml:"cabinet,omitempty"`
+ Payout string `yaml:"payout,omitempty"`
+ Variant string `yaml:"-"`
+ PayTable string `yaml:"-"`
+ Feature string `yaml:"-"`
+}
+
+const (
+ CabinetClassic = "classic"
+ CabinetUXSingle = "ultimate_x_single"
+ CabinetUXMulti = "ultimate_x_multi"
+ CabinetSuperTimesPay = "super_times_pay"
+ CabinetHotRoll = "hot_roll"
+)
+
+func cabinetFeature(c string) string {
+ switch c {
+ case CabinetUXSingle, CabinetUXMulti:
+ return PokerUltimateX
+ case CabinetSuperTimesPay:
+ return PokerSuperTimesPay
+ case CabinetHotRoll:
+ return PokerHotRoll
+ }
+ return ""
+}
+
+var cabinetVariantAliases = map[string]string{
+ "jacks": "jacks_or_better", "jacks_or_better": "jacks_or_better", "jacks or better": "jacks_or_better",
+ "bonus": "bonus_poker", "bonus_poker": "bonus_poker", "bonus poker": "bonus_poker",
+ "deluxe": "bonus_poker_deluxe", "bonus_poker_deluxe": "bonus_poker_deluxe", "bonus poker deluxe": "bonus_poker_deluxe",
+ "double": "double_bonus", "double_bonus": "double_bonus", "double bonus": "double_bonus",
+ "ddb": "double_double_bonus", "double_double_bonus": "double_double_bonus", "double double bonus": "double_double_bonus",
+ "deuces": "deuces_wild", "deuces_wild": "deuces_wild", "deuces wild": "deuces_wild",
+ "joker": "joker_poker", "joker_poker": "joker_poker", "joker poker": "joker_poker",
+}
+var cabinetGames = map[string][]string{
+ CabinetClassic: {"jacks_or_better", "bonus_poker", "bonus_poker_deluxe", "double_bonus", "double_double_bonus", "deuces_wild", "joker_poker"},
+ CabinetUXSingle: {"jacks_or_better", "bonus_poker", "bonus_poker_deluxe", "double_bonus", "double_double_bonus", "deuces_wild", "joker_poker"},
+ // Bonus Poker has only two verified multi-line schedules; omit rather than
+ // inventing a third quality tier.
+ CabinetUXMulti: {"jacks_or_better", "bonus_poker_deluxe", "double_bonus", "double_double_bonus", "deuces_wild", "joker_poker"},
+ CabinetSuperTimesPay: {"jacks_or_better", "bonus_poker", "double_bonus", "double_double_bonus", "deuces_wild"},
+ CabinetHotRoll: {"jacks_or_better", "double_double_bonus"},
+}
+
+// Each tuple is low, normal, full. These are named published schedules, not
+// interpolated payouts. Full means the highest offered on this cabinet, not
+// a promise of identical RTP across games. Sources: building_guide/video_poker.md.
+var cabinetSchedules = map[string]map[string][3]string{
+ CabinetClassic: {
+ "jacks_or_better": {"7/5", "8/5", "9/6"},
+ "bonus_poker": {"6/5", "7/5", "8/5"},
+ "bonus_poker_deluxe": {"8/5", "9/5", "9/6"},
+ "double_bonus": {"9/6/4", "9/7", "10/7"},
+ "double_double_bonus": {"8/5", "9/6", "10/6"},
+ "deuces_wild": {"20/12/10/4/4/3", "25/15/9/4/4/3", "25/15/9/5/3/2"},
+ "joker_poker": {"50/15/7/5/3", "50/16/7/5/3", "50/17/7/5/3"},
+ },
+ CabinetUXSingle: {
+ "jacks_or_better": {"8/5", "8/6", "9/6"},
+ "bonus_poker": {"6/5", "7/5", "8/5"},
+ "bonus_poker_deluxe": {"8/5", "8/6", "9/6"},
+ "double_bonus": {"9/6/4", "9/6/5", "9/7"},
+ "double_double_bonus": {"8/5", "9/5", "9/6"},
+ "deuces_wild": {"25/16/13/4/3/2", "20/12/10/4/4/3", "25/15/9/4/4/3"},
+ "joker_poker": {"50/15/7/5/3", "50/16/7/5/3", "50/17/7/5/3"},
+ },
+ CabinetUXMulti: {
+ "jacks_or_better": {"7/5", "8/5", "8/6"},
+ "bonus_poker_deluxe": {"7/5", "8/5", "8/6"},
+ "double_bonus": {"9/5/4", "9/6/4", "9/6/5"},
+ "double_double_bonus": {"8/5", "9/5", "9/6"},
+ "deuces_wild": {"25/16/13/4/3/2", "20/12/10/4/4/3", "25/15/9/4/4/3"},
+ "joker_poker": {"50/15/7/5/3", "50/16/7/5/3", "50/17/7/5/3"},
+ },
+ CabinetSuperTimesPay: {
+ "jacks_or_better": {"7/5", "8/5", "9/6"},
+ "bonus_poker": {"6/5", "7/5", "8/5"},
+ "double_bonus": {"9/6/4", "9/6/5", "9/7"},
+ "double_double_bonus": {"8/5", "8/6", "9/6"},
+ "deuces_wild": {"20/12/10/4/4/3", "25/15/9/4/4/3", "25/15/9/5/3/2"},
+ },
+ CabinetHotRoll: {
+ "jacks_or_better": {"8/5", "9/5", "9/6"},
+ "double_double_bonus": {"8/5", "9/5", "9/6"},
+ },
+}
+
+func CabinetHandCounts(cabinet, variant string) []int {
+ switch cabinet {
+ case CabinetUXSingle:
+ return []int{1}
+ case CabinetUXMulti, CabinetHotRoll:
+ return []int{3, 5, 10}
+ case CabinetSuperTimesPay:
+ return []int{1, 3, 5, 10}
+ }
+ if variant == "jacks_or_better" {
+ return []int{1, 3, 5, 10, 25, 50, 100}
+ }
+ return []int{1}
+}
+func variantTitle(v string) string {
+ switch v {
+ case "jacks_or_better":
+ return "Jacks or Better"
+ case "bonus_poker":
+ return "Bonus Poker"
+ case "bonus_poker_deluxe":
+ return "Bonus Poker Deluxe"
+ case "double_bonus":
+ return "Double Bonus"
+ case "double_double_bonus":
+ return "Double Double Bonus"
+ case "deuces_wild":
+ return "Deuces Wild"
+ case "joker_poker":
+ return "Joker Poker (Kings or Better)"
+ }
+ return v
+}
+func (c VideoPokerConfig) CabinetLabel() string {
+ c = c.Normalize()
+ label := "Classic Video Poker"
+ switch c.Cabinet {
+ case CabinetUXSingle:
+ label = "Ultimate X Single Line"
+ case CabinetUXMulti:
+ label = "Ultimate X Multi-Hand"
+ case CabinetSuperTimesPay:
+ label = "Super Times Pay"
+ case CabinetHotRoll:
+ label = "Hot Roll"
+ }
+ return label + " / " + c.Payout + " pay"
+}
+func (c VideoPokerConfig) Normalize() VideoPokerConfig {
+ if c.Cabinet == "" {
+ c.Cabinet = CabinetClassic
+ }
+ if c.Payout == "" {
+ c.Payout = "full"
+ }
+ if c.Variant == "" {
+ c.Variant = "jacks_or_better"
+ }
+ if alias, ok := cabinetVariantAliases[c.Variant]; ok {
+ c.Variant = alias
+ }
+ c.Feature = cabinetFeature(c.Cabinet)
+ ix := -1
+ switch c.Payout {
+ case "low":
+ ix = 0
+ case "normal":
+ ix = 1
+ case "full":
+ ix = 2
+ }
+ // Explicit schedules are used by evaluator tests. Authored configurations
+ // and game selection always resolve from the cabinet-wide tier.
+ if c.PayTable == "" && ix >= 0 {
+ if schedules, ok := cabinetSchedules[c.Cabinet][c.Variant]; ok {
+ c.PayTable = schedules[ix]
+ }
+ }
+ return c
+}
+func (c VideoPokerConfig) Validate() error {
+ rawFeature := c.Feature
+ c = c.Normalize()
+ if _, ok := cabinetGames[c.Cabinet]; !ok {
+ return fmt.Errorf("unknown video poker cabinet %q", c.Cabinet)
+ }
+ if rawFeature != "" && rawFeature != c.Feature {
+ return fmt.Errorf("video poker feature does not match cabinet")
+ }
+ if c.Payout != "low" && c.Payout != "normal" && c.Payout != "full" {
+ return fmt.Errorf("video poker payout must be low, normal or full")
+ }
+ schedules, ok := cabinetSchedules[c.Cabinet][c.Variant]
+ if !ok {
+ return fmt.Errorf("unsupported game %q on %s", c.Variant, c.Cabinet)
+ }
+ expected := (VideoPokerConfig{Cabinet: c.Cabinet, Payout: c.Payout, Variant: c.Variant}).Normalize()
+ if c.PayTable != expected.PayTable {
+ return fmt.Errorf("schedule %q does not match cabinet tier (%v)", c.PayTable, schedules)
+ }
+ if _, ok := pokerPayTable(c); !ok {
+ return fmt.Errorf("unsupported schedule %q", c.PayTable)
+ }
+ return nil
+}
+func (c VideoPokerConfig) VariantOptions() []string {
+ return append([]string(nil), cabinetGames[c.Normalize().Cabinet]...)
+}
+func (c VideoPokerConfig) GameLabel() string {
+ c = c.Normalize()
+ return variantTitle(c.Variant) + " / " + c.PayTable
+}
+func (c VideoPokerConfig) GameChoices() string { return strings.Join(c.VariantOptions(), ", ") }
+
+type PokerPayRow struct {
+ Name string
+ Coins [5]int
+ NextAward int
+}