bjstrat letters

c++ code to compute 1, 2, or 3 splits in blackjack

When I began writing a combinatorial analyzer, this is the algorithm I came up with to compute splits. It works for 1, 2, or 3 splits. It may be extendable to more splits but I never found a simple way to do this. At about that time MGP shared his method of computing splits with me. This inspired me to come up with a separate method that is extendable to any number of splits and involves computing multipliers for shoe states dependent only on the number of pair cards that have been removed from the shoe. The algorithm I am posting here also only considers shoe states dependent on number of pair cards removed.

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.

Multiple splits
When a pair can be resplit the playing strategy is precluded from being a fixed strategy. Hand1 can use up as many allowable splits as is warranted leaving hand2 with less options so hand1 EV is not equal to hand2 EV and expected number of hands for hand1 is greater than expected number of hands for hand2.

Terminology
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
pRem = number of pair cards removed from the starting condition (range = 0 to 2*(allowed splits) - 2)
remSp = number of remaining splits; starting value = allowed splits - 1
EVx[pRem][upCard-1]=EV(draw to a single pair card with pRem pair cards removed) vs. upCard 1 to 10
EVx[][] contains EV when hand can no longer be split if a pair card is drawn
EVPair_p[pRem][upCard-1]=EV(unsplit 2-card pair hand with pRem pair cards removed) vs. upCard 1 to 10
npev = non-pair EV = EV of drawing to 2 card player hands resulting from a split, excluding the pair hand
p0 = probability of drawing a pair card from the starting condition
p1 = probability of drawing a pair card from the starting condition less 1 pair card

Logic
If allowed splits = 1, remSp = 0 and split EV = 2 * EVx[0][upCard - 1]
If more than 1 split is allowed
hand1 EV = npev + p0 * (split EV with 1 less remaining split, 1 less pair card) for the starting condition
hand2 EV would equal hand1 EV except that hand2 may not be able to be resplit because
hand1 has used up all remaining splits
we proceed as if hand1 EV = hand2 EV and then repair the erroneous EV
in the cases of 2 or 3 allowed splits (remSp=1 or remSp=2)
EV needs to be repaired when a pair card is drawn to each hand
for remSp=1, EV is repaired by replacing 2 EVx hands with 2 additional pair cards removed
with one EVPair_p hand with the same number of pair cards removed
for remSp=2, EV is repaired by replacing 2 pair hands with 1 remaining split
with 4 hands of 0 remaining splits (with 2 additional pair cards removed)

Why does it work?
A recursive function can be written to compute the number of expected hands resulting from a split. Obviously there are 2 expected hands when 1 split is allowed. The parameters are number of undrawn to pair cards (pCards,) number of remaining splits (remSp,) number of pair cards (p,) number of non-pair cards (np.) If the function is named getSplitHands then hands = getSplitHands(2,2,14,35) returns expected hands for T-T vs 6 dealt from a full single deck for example. In the repair the EV algorithm instead of computing for 2 pCards, 1 pCard is computed and multiplied by 2 (hands = 2 * getSplitHands(1,2,14,35).) This results in an inflated number of expected hands. The EV repair method reduces the inflated number of expected hands to the expected hands for the starting condition for 2 or 3 allowed splits. For 4 or more splits the objective would be to try and find a discernible pattern that could be applied to an ever increasing number of allowed splits.

Code

// this procedure computes 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, max allowed splits = 3)
//     for an initial shoe state after 2 pair cards and up card have been removed
//          p = pair cards present, np = non-pair cards present
// it requires 2 arrays of data (pRem, upCard parameters determine which data procedure uses)
//     needed range of pRem (pair cards removed) is 0 to 2 * (allowed splits - 2)
//     initially pRem = 0; needed value of pRem determined within procedure
//     EVx[pRem][10] (EV draw to single pair card, pRem pair cards removed from initial shoe, up card 1-10)
//     EVPair[pRem][10] (EV play unsplit pair hand, pRem pair cards removed from initial shoe, up card 1-10)
//     EVPair[0][upCard - 1] = EV of unsplit pair hand with 1 pair card removed from initial shoe state
//     EVPair[1][upCard - 1] = EV of unsplit pair hand with 2 pair cards removed from initial shoe state, etc.
//     If no pair cards remain after pRem pair cards are removed, EV[pRem][upCard - 1] = 0
// after procedure split EV for input values is in spEV parameter

void getSpEV(const int &upCard, const int &remSp, const int &pRem, const int &p, const int &np,
	     const double EVx[][10], const double EVPair[][10], double &spEV)
{
	if (p == 0 || remSp == 0) {
		spEV = 2 * EVx[pRem][upCard - 1];
		return;
	}

	double wrongEV_p = 0;
	
	// compute wrong EV, 1 pair card removed
	getSpEV(upCard, remSp - 1, pRem + 1, p - 1, np, EVx, EVPair, wrongEV_p);

	double p0, p1;
	double f = 1;
	double npev;
	double splitEV = 0;

	p0 = double(p) / (p + np);
	p1 = double(p - 1) / (p - 1 + np);

	f *= p0;

	npev = EVx[pRem][upCard - 1] - p0 * EVPair[pRem][upCard - 1];
	splitEV += npev * 2;
	splitEV += f * wrongEV_p * 2;

	f *= p1;

// repair EV
	if (remSp == 1) {
		// repair for allowed splits = 2, remaining splits = 1
		splitEV -= f * 2 * EVx[pRem + 2][upCard - 1];
		splitEV += f * EVPair[pRem + 2][upCard - 1];
	}
	else if (remSp == 2) {
		double wrongEV_pp = 0;
		
		// compute wrong EV, 2 pair cards removed
		getSpEV(upCard, remSp - 1, pRem + 2, p - 2, np, EVx, EVPair, wrongEV_pp);

		// repair for allowed splits = 3, remaining splits = 2
		splitEV -= f * 2 * wrongEV_pp;
		splitEV += f * 4 * EVx[pRem + 2][upCard - 1];
	}
// end EV repair

	spEV = splitEV;
}

 

Copyright 2010 (www.bjstrat.net)
All rights reserved.