package casino import ( "fmt" "strings" ) // VideoPokerConfig is the authored cabinet/payout pair plus the rest of the // cabinet settings. 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. 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) CabinetName() string { c = c.Normalize() switch c.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 "Classic Video Poker" } // CabinetAliases lists the single-token names that address this cabinet type // in the play menu, beyond the room's own table id. "video poker" itself means // the classic cabinet so a lone lounge stays reachable without a specific // machine name. func (c VideoPokerConfig) CabinetAliases() []string { switch c.Normalize().Cabinet { case CabinetUXSingle: return []string{"ux", "ultimate_x", "ultimate_x_single"} case CabinetUXMulti: return []string{"uxm", "ultimate_x_multi", "ultimate_x_multi_hand"} case CabinetSuperTimesPay: return []string{"stp", "super_times", "super_times_pay"} case CabinetHotRoll: return []string{"hr", "hot_roll"} } return []string{"vp", "poker", "classic", "video_poker"} } func (c VideoPokerConfig) CabinetLabel() string { c = c.Normalize() return c.CabinetName() + " / " + 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 }