package casino import "math" // A small infinite-shoe decision model supplies the effect of payout, H17, // DAS and the game's unusual doubled-split-21 rule. Deck count and resplitting // use the published basic-strategy rule adjustments at: // https://wizardofodds.com/games/blackjack/rule-variations/ // This is a reference estimate, not a finite-shoe/composition strategy solver. func blackjackMainEdge(r BlackjackConfig) float64 { r = r.Normalize() base := BlackjackConfig{}.Normalize() no := false base.DoubleSplitBlackjack = &no loss := .0043 - (blackjackReferenceEV(r) - blackjackReferenceEV(base)) // Improvements relative to eight decks; interpolate uncommon deck counts. decks := []float64{0, .0048, .0019, .00125, .0006, .0003, .0002, .0001, 0} if r.Decks < len(decks) { loss -= decks[r.Decks] } else { loss += .0006 * (1 - 8/float64(r.Decks)) } if r.MaxSplitHands == 2 { loss += .001 } else if r.MaxSplitHands == 3 { loss += .0001 } if r.MaxSplitHands > 1 && r.AllowsResplitAces() { loss -= .0008 } return loss } // The split model allows one split; published adjustments above account for // the configured resplit limit. Unlike-ten splitting does not change normal // optimal play (tens are stood on). Shuffle penetration is not an off-top rule. func blackjackReferenceEV(r BlackjackConfig) float64 { probability := func(v int) float64 { if v == 10 { return 4.0 / 13 } return 1.0 / 13 } add := func(total, soft, v int) (int, int) { if v == 1 { total += 11 soft++ } else { total += v } for total > 21 && soft > 0 { total -= 10 soft-- } return total, soft } result := 0.0 for up := 1; up <= 10; up++ { // Outcomes 17..21 and bust (22), conditional on no dealer natural. type state struct{ total, soft int } memoDealer := map[state][23]float64{} var dealer func(int, int) [23]float64 dealer = func(total, soft int) [23]float64 { key := state{total, soft} if v, ok := memoDealer[key]; ok { return v } var out [23]float64 if total > 21 { out[22] = 1 } else if total >= 17 && !(total == 17 && soft > 0 && r.DealerHitsSoft17()) { out[total] = 1 } else { for v := 1; v <= 10; v++ { t, s := add(total, soft, v) child := dealer(t, s) for i, p := range child { out[i] += probability(v) * p } } } memoDealer[key] = out return out } natural := 0.0 if up == 1 { natural = probability(10) } if up == 10 { natural = probability(1) } var outcomes [23]float64 for hole := 1; hole <= 10; hole++ { if up+hole == 11 && (up == 1 || hole == 1) { continue } t, s := add(0, 0, up) t, s = add(t, s, hole) dist := dealer(t, s) for i, p := range dist { outcomes[i] += probability(hole) * p / (1 - natural) } } stand := func(total int) float64 { if total > 21 { return -1 } ev := outcomes[22] for d := 17; d <= 21; d++ { if total > d { ev += outcomes[d] } else if total < d { ev -= outcomes[d] } } return ev } memoPlayer := map[state]float64{} var play func(int, int) float64 play = func(total, soft int) float64 { if total >= 21 { return stand(total) } key := state{total, soft} if v, ok := memoPlayer[key]; ok { return v } hit := 0.0 for v := 1; v <= 10; v++ { t, s := add(total, soft, v) hit += probability(v) * play(t, s) } ev := math.Max(stand(total), hit) memoPlayer[key] = ev return ev } hand := func(a, b int, split bool) float64 { total, soft := add(0, 0, a) total, soft = add(total, soft, b) if split && a == 1 { return stand(total) } // one card on split aces ev := play(total, soft) if total < 21 && (!split || r.AllowsDoubleAfterSplit()) { doubled := 0.0 for v := 1; v <= 10; v++ { t, _ := add(total, soft, v) value := stand(t) if split && t == 21 && r.CountsDoubleSplitBlackjack() { value = r.BlackjackPayout } doubled += 2 * probability(v) * value } ev = math.Max(ev, doubled) } if !split && r.AllowsSurrender() { ev = math.Max(ev, -.5) } return ev } for a := 1; a <= 10; a++ { for b := 1; b <= 10; b++ { ev := 0.0 if a+b == 11 && (a == 1 || b == 1) { ev = (1 - natural) * r.BlackjackPayout } else { ev = hand(a, b, false) if a == b && r.MaxSplitHands > 1 { split := 0.0 for v := 1; v <= 10; v++ { split += 2 * probability(v) * hand(a, v, true) } ev = math.Max(ev, split) } ev = (1-natural)*ev - natural } result += probability(up) * probability(a) * probability(b) * ev } } } return result }