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