aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/house_edge_video_poker.go
blob: 52af719367485c7d19b1c4094f129ee1da76138f (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
package casino

// Published optimal max-coin returns and royal frequencies. Source pages:
// https://wizardofodds.com/games/video-poker/tables/{jacks-or-better,
// bonus-poker,bonus-poker-deluxe,double-bonus,double-double-bonus,
// deuces-wild,joker-poker-kings-or-better}/
// Below max coins we price the reduced royal using that schedule's strategy;
// this is a reference estimate rather than a second optimal-strategy solver.
type pokerReturn struct{ rtp, royal float64 }

var pokerReturns = map[string]map[string]pokerReturn{
	"jacks_or_better":     {"9/6": {.99543904, .00002476}, "9/5": {.984498, .000025}, "8/6": {.983927, .000025}, "8/5": {.97298434, .00002489}, "7/5": {.961472, .000025}},
	"bonus_poker":         {"8/5": {.991660, .000025}, "7/5": {.980147, .000025}, "6/5": {.968687, .000025}},
	"bonus_poker_deluxe":  {"9/6": {.996417, .000024}, "9/5": {.985495, .000025}, "8/6": {.984928, .000024}, "8/5": {.974009, .000025}, "7/5": {.962526, .000025}},
	"double_bonus":        {"10/7": {1.001725, .000021}, "9/7": {.991065, .000021}, "9/6/5": {.978062, .000024}, "9/6/4": {.963754, .000024}, "9/5/4": {.952738, .000025}, "8/5": {.941897, .000025}},
	"double_double_bonus": {"10/6": {1.000670, .000025}, "9/6": {.989808, .000025}, "8/6": {.978949, .000025}, "9/5": {.978729, .000025}, "8/5": {.967861, .000025}},
	"deuces_wild":         {"25/15/9/5/3/2": {1.007620, .000022}, "25/15/9/4/4/3": {.989131, .000023}, "20/12/10/4/4/3": {.975791, .000023}, "25/16/13/4/3/2": {.967651, .000023}},
	"joker_poker":         {"50/17/7/5/3": {.980854, .000025}, "50/16/7/5/3": {.972349, .000025}, "50/15/7/5/3": {.963848, .000025}},
}

// Ultimate X is a continuing-game reference, not the value of cashing in a
// particular banked multiplier. Entries are single / three / five / ten hands.
// Sources: .../tables/ultimate-x-single-line/ and .../tables/ultimate-x/.
var pokerUXReturns = map[string]map[string][4]float64{
	"jacks_or_better":     {"9/6": {.9972, 0, 0, 0}, "8/6": {.9857, .992978, .994008, .994220}, "8/5": {.9738, .978730, .979671, .979795}, "7/5": {.9623, .967211, .968142, .968267}},
	"bonus_poker":         {"8/5": {.9921, .992828, .993408, .994034}, "7/5": {.9809, .981242, .981817, .982439}, "6/5": {.9694, 0, 0, 0}},
	"bonus_poker_deluxe":  {"9/6": {.9996, 0, 0, 0}, "8/6": {.9882, .995712, .996777, .997022}, "8/5": {.9747, .981052, .982014, .982169}, "7/5": {.9632, .969549, .970502, .970658}},
	"double_bonus":        {"9/7": {.9921, .988866, .989590, .990359}, "9/6/5": {.9811, .984517, .985227, .985987}, "9/6/4": {.9662, .973059, .973764, .974517}, "9/5/4": {.9539, .959471, .960118, .960810}},
	"double_double_bonus": {"9/6": {.9905, .997261, .997928, .998663}, "9/5": {.9793, .983633, .984253, .984940}, "8/5": {.9680, .972266, .972859, .973521}},
	"deuces_wild":         {"25/15/9/4/4/3": {.9905, .991282, .992657, .994362}, "20/12/10/4/4/3": {.9788, .979863, .981217, .982817}, "25/16/13/4/3/2": {.9681, .969387, .970777, .972477}},
	"joker_poker":         {"50/17/7/5/3": {.9824, .989856, .990144, .990445}, "50/16/7/5/3": {.9738, .981190, .981474, .981771}, "50/15/7/5/3": {.9652, .972528, .972808, .973102}},
}

func videoPokerHouseEdge(c TableConfig, bet string) HouseEdge {
	r := c.VideoPoker.Normalize()
	return pokerHouseEdge(r, bet, 5, true, CabinetHandCounts(r.Cabinet, r.Variant)[0])
}

func pokerHouseEdge(c VideoPokerConfig, bet string, coins int, feature bool, hands int) HouseEdge {
	if bet != "main" {
		return unknownEdge("unrecognized video poker wager")
	}
	c = c.Normalize()
	ref, ok := pokerReturns[c.Variant][c.PayTable]
	if !ok {
		return unknownEdge("unrecognized poker schedule")
	}
	rtp := ref.rtp
	if coins > 0 && coins < 5 {
		rtp -= 550 * ref.royal
		feature = false
	}
	if feature {
		switch c.Feature {
		case PokerUltimateX:
			ix := 0
			if c.Cabinet == CabinetUXMulti {
				switch hands {
				case 3:
					ix = 1
				case 5:
					ix = 2
				case 10:
					ix = 3
				default:
					return unknownEdge("unsupported hand count")
				}
			}
			rtp = pokerUXReturns[c.Variant][c.PayTable][ix]
			if rtp == 0 {
				return unknownEdge("unsupported Ultimate X schedule")
			}
		case PokerSuperTimesPay:
			// E[multiplier] = 14/15 + (2*.17+3*.33+4*.16+5*.24+8*.06+10*.04)/15.
			rtp *= (14.0/15 + 4.05/15) * 5 / 6
		case PokerHotRoll:
			// Five extra coins buy 5/6 normal hands and 1/6 at mean 7x.
			rtp *= (5.0/6 + 7.0/6) / 2
		}
	}
	return edge(1 - rtp)
}