diff options
Diffstat (limited to 'internal/casino/video_poker_config.go')
| -rw-r--r-- | internal/casino/video_poker_config.go | 218 |
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 +} |
