c++ code to approximate computation of any number of splits in blackjack
This is a simple recursive algorithm that could theoretically be used to approximately compute any number of allowed splits. It uses an infinite shoe composition as a model so it will be more accurate as number of full decks increases. In an infinite shoe probability of drawing any rank is fixed because removal of any 1 card is insignificant when drawn from an infinite number of cards. In a depleted shoe, though, probability of drawing a pair tends to decrease so splitting occurs less in that case.
A pair hand consists of 2 pair cards to be split given a specific dealer up card. With a finite number of decks a simple way to view the shoe composition after the initial pair and up card have been dealt is to separate it into 2 groups of cards; undealt pair cards and undealt non-pair cards. This algorithm somewhat conforms to the infinite shoe model by assuming only removal of pair cards from initial composition has an effect upon the value of any drawing sequence while removal of non-pair cards affects only the probability of any particular drawing sequence.
Theory
Single split
When a pair is limited to one split the expected value of the first hand is equal to that of the second provided a fixed strategy is used. This makes sense since any cards available to the first hand are also available to the second, although in different sequences. If each is played with the same strategy then hand1 EV = hand2 EV. This can be verified by computing EV for hand1 and multiplying by 2. After that go through all possible draws to hand1 followed by all to hand2 using any fixed strategy and sum hand1 EV and hand2 EV. The 2 EVs will be the same. For 1 allowed split there is no difference in return values for this algorithm relative to my present algorithm. The present algorithm adapts the value of a drawing sequence to number of non-pair cards removed whereas this algorithm does not. The removal of non-pair cards is only relevant to more than 1 allowed split.
Multiple splits
This algorithm uses a fixed strategy for multiple splits as does the present one. The difference in this algorithm relative to the present one is that it assumes value of a pair hand to which a non-pair card has been drawn relies only upon number of pair cards removed whereas present algorithm adapts additionally to number of non-pair cards removed. In computing expected value for multiple splits there are drawing sequences depending upon number of allowed splits. 2 types of hands are encountered within these sequences:
1. Pair cards to which a non-pair card is the first card drawn (PN hands) 2. Pair cards to which a pair card is drawn. These are split again at every opportunity until no more splits are allowed. There can be 2 or more of these in a drawing sequence. When no more splitting is allowed it no longer matters if a non-pair or pair card is drawn (Px hands)
This compares the drawing sequences for 2 and 3 allowed splits relative to this and present algoritms:
2 allowed splits Sequence Hands This algorithm Present algorithm ________________________________________________________________________________________________________ NN 2 1st N = 2nd N 0 Ps removed N removed is considered for 2nd N NPxx 3 N hand & x hands 1 P removed N removed is considered for x hands Pxxx 3 Same as present 1 P removed Same as this 1 P removed 3 allowed splits Sequence Hands This algorithm Present algorithm ________________________________________________________________________________________________________ NN 2 1st N = 2nd N, 0 Ps removed N removed is considered for 2nd N PNNN 3 all N hands 1 P removed all N hands 2 Ns 1 P removed NPNN 3 all N hands 1 P removed all N hands 2 Ns 1 P removed PPxxxx 4 Same as present 2 Ps removed Same as this 2 Ps removed PNPxxx 4 N hand & x hands 2 Ps removed N hand 2 Ps removed, x hands 2 Ps & 1 N removed NPPxxx 4 N hand & x hands 2 Ps removed N hand 2 Ps removed, x hands 2 Ps & 1 N removed PNNPxx 4 N hands & x hands 2 Ps removed N hands 2 Ps 1 N removed, x hands 2 Ps & 2 Ns removed NPNPxx 4 N hands & x hands 2 Ps removed N hands 2 Ps 1 N removed, x hands 2 Ps & 2 Ns removed
These are differences and similarities of this and present algorithms relative to PN and Px hands:
Similarity Difference PN hands All PN hands in a drawing This algorithm evaluates without regard to other non-pair cards sequence are equal in a sequence. Present algorithm evaluates all PN hands as equal only after the final non-pair card has been drawn and compensates for prior non-pair cards drawn. Px hands All Px hands in a drawing This algorithm evaluates without regard to non-pair cards drawn sequence are equal in a sequence. Present algorithm evaluates compensating for non-pair cards drawn in a sequence.
This algorithm evaluates both PN and Px hands for the actual shoe state at the end of all drawing sequences whereas present algorithm needs access to additional states in order to evaluate in consideration of non-pair cards removed. This algorithm requires only 3 shoe states to compute 3 allowed splits whereas present algorithm requires 5. (0,1,2 pair cards removed as opposed to 0,1,2,3,4.) In general for n allowed splits present algorithm requires (2*n -1) shoe states whereas this one requires only n. For any algorithm to be valid it must be guaranteed that all player hands as well as dealer hand can be resolved without running out of cards.
Use of this algorithm
In order to compute split expected values it is necessary to first compute these values:
EVx[pRem][upCard-1]=EV(draw to a single pair card with pRem pair cards removed) vs. upCard 1 to 10 EVx[pRem][upCard-1] contains EV when hand can no longer be split if a pair card is drawn EVPair[pRem][upCard-1]=EV(unsplit 2-card pair hand with pRem pair cards removed) vs. upCard 1 to 10 EVPair[pRem][upCard-1] contains EV for unsplit pair, necessarily removing 1 additional pair card (value is placed in element pRem and not pRem + 1 for convenience) In the course of computing EVx, the value of EVPair can be recorded for each needed pRem. Range of pair cards removed (pRem) is as stated above.
The newer algorithm passes 2 arrays of double values (xh[] and ph[].) Each element of each array should be initialized to 0. The minimum range of each array is 0 to remSp (remSp = (allowed splits) - 1.) The index of each array corresponds to number of pair cards removed (pRem in algorithm.) xh[0] and ph[0] corresponds to 0 pair cards removed, xh[1] and ph[1] corresponds to 1 pair card removed, and so on. The values for splitting can be computed by simply multiplying EVx[pRem] by xh[pRem] and adding to EVPair[pRem] * ph[pRem] for each value of pRem after algorithm has executed. As stated previously, a pair card drawn in order to be able to compute EVPair[pRem] is treated the same as any other rank and does not cause pRem to be incremented by 1 by convention. The value of EVPair[pRem] is recorded when cycling through all ranks when computing EVx[pRem].
Terminology
pRem = number of pair cards removed from the starting condition (always initially = 0)
p = number of pair cards present in shoe after pair hand and dealer up card have been dealt
np = number of non-pair cards present in shoe after pair hand and dealer up card have been dealt
example: pair of tens versus dealer up card of 6 dealt from full single deck: p = 14, np = 35
example: pair (2-2) versus dealer up card of 3 dealt from 6 full decks: p = 22, np = 287
pCards = number of undrawn to pair cards (always initially = 2)
nHands = number of hands where a non-pair card is the 1st card drawn to a pair card (always initially = 0)
remSp = number of remaining splits (= (allowed splits - 1))
xh[pRem] = multiplier for EVx[pRem] values (each element initialized to 0)
ph[pRem] = multiplier for EVPair[pRem] values (each element initialized to 0)
prob = probability (always initially = 1)
hands = number of expected hands (passed by reference in a variable initialized to 0)
Methodology
Let EVx[pRem] = value of drawing when no more splitting is allowed with pRem pair cards removed
Value of EVx[pRem] is assumed to be independent of non-pair cards drawn
Let p[pRem] = probability of drawing a pair card with pRem pair cards removed from initial condition
Multipliers stored in xh[pRem] and ph[pRem] assume the value of a non-pair hand is immutable
for each number of pair cards removed (pRem)
Value of non-pair hand = (EVx[pRem] - p[pRem] * EVPair[pRem]) / (1 - p[pRem])
p[pRem] is constantly updated (= pP within algorithm) enabling updating of xh[] and ph[] according to above.
Code
// this procedure computes multipliers which can be used to compute split EV // for a given pair and up card (data assumed to be for known pair) // for a given number of remaining splits (remSp = (allowed splits - 1)) // for an initial shoe state after 2 pair cards and up card have been removed // p = pair cards present, np = non-pair cards present // after execution: // array xh[] will contain computed multipliers for EVx[] described above // array ph[] will contain computed multipliers for EVPair[] described above // hands will contain number of expected hands for remSp = ((allowed splits) - 1) // split EV can be easily computed by accumulating the sum of EVx[pRem}*xh[pRem] + EVPair[pRem]*ph[pRem] // for the range of pRem = 0 to pRem = (allowed splits - 1) void getMultipliers(const int &pRem, const int &p, const int &np, const int &pCards, const int &nHands, const int &remSp, double xh[], double ph[], const double &prob, double &hands) { int tot; double pP; tot = p + np; pP = (double)p / tot; if (p == 0 || remSp == 0) { hands += prob * (pCards + nHands); if (np == 0) { xh[pRem] += prob * pCards; ph[pRem] += prob * nHands; } else { // (1 - pP) > 0 xh[pRem] += prob * (pCards + nHands / (1 - pP)); ph[pRem] -= prob * nHands * pP / (1 - pP); } return; } if (np == 0) { if (pCards == 0) { hands += prob * nHands; xh[pRem] += prob * nHands; return; } else // if (pCards > 0) goto maybeOutOf_np; } if (pCards == 0) { hands += prob * nHands; xh[pRem] += prob * nHands / (1 - pP); ph[pRem] -= prob * nHands * pP / (1 - pP); return; } maybeOutOf_np: // pair card drawn getMultipliers(pRem + 1, p - 1, np, pCards + 1, nHands, remSp - 1, xh, ph, pP * prob, hands); // non-pair card drawn if (np > 0) getMultipliers(pRem, p, np - 1, pCards - 1, nHands + 1, remSp, xh, ph, (1 - pP) * prob, hands); } /* example: to get multipliers that can be used to compute pair = (2-2) versus up card = 3 for allowed splits = 3 (remSp = 2) dealt from 6 full decks; (p=22, np=287) --Any pair that results in undealt pair cards = 22 and undealt non-pair cards = 287 will result in the same set of multipliers-- double totHands = 0; double xh[3] = {0}, ph[3] = {0} // enough space for 3 allowed splits getMultipliers(0, 22, 287, 2, 0, 2, xh, ph, 1, totHands); after execution xh contains EVx multipliers, ph EVPair multipliers, totHands number of expected hands split EV is computed from EVx[pRem], EVPair[pRem], xh[pRem], ph[pRem] as follows: double splitEV = 0; int pRem; for (pRem = 0; pRem <= 2; ++pRem) splitEV += xh[pRem] * EVx[pRem] + ph[pRem] * EVPair[pRem]; // EVPair[pRem] references shoe state // before pair card is drawn double expectedHands = totHands; Sample values: 2-2 versus 3 dealt from 6 full decks & 1 full deck computed as above for 3 allowed splits (remSp = 2) rules: dealer stands on soft 17 3 allowed splits versus up cards 2-10, no doubling after splitting 1 allowed split versus ace, 1 card to split ace, no doubling after splitting aces splitEV dealt from 6 full decks = -9.634%; -9.639% when additionally compensating non-pair cards splitEV dealt from 1 full deck = -6.851%; -6.862% when additionally compensating non-pair cards Overall EV for above rules- 6 decks: -.5441%; -.5446% when additionally compensating non-pair cards 1 deck: +.04304%; +.04186% when additionally compensating non-pair cards */
As long as number of undealt non-pair cards is relatively large this algorithm seems to be a simple and reasonably effective alternative. As the number of non-pair cards left dwindles it would tend to vary more from the algorithm that more fully compensates for their removal. My present algorithm is similar to the newer one in that it computes multipliers but for more shoe states. These additional shoe states are needed to compensate for actual number of non-pair cards removed.
Many thanks to Eric Farmer for his assistance in assessing the mathematical soundness of this algorithm.