bjstrat letters

c++ code to adapt to removal of generic non-pair cards in pair splitting

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 more fully addresses adapting to non-pair card removal than this approximate solution c++ code to approximate computation of any number of splits in blackjack.

In blackjack expected values for non-pair hands are relatively simple to compute. Basic computed values are stand EV, double EV, and hit EV for the 2 initially dealt player cards. This relative simplicity can be extended to computing splits by employing 2 basic values. First is EVx, which is defined as the EV of drawing to a single pair card without resplitting if another pair card is drawn. Second is EVPair, which is the EV of playing an unsplit pair.

EVx is EV of drawing ranks 1,2,3,4,5,6,7,8,9,10, one of which is the pair value, to a single pair card
let EVn = EV of drawing a non-pair card to a single pair card
if pP = probability of drawing another pair card, the basic relationship of EVx, EVPair and EVn is:
EVn = (EVx - pP*EVPair) / (1 - pP)

This relationship can be extended to any shoe composition so the value of drawing a non-pair card can always be computed in terms of EVx, EVPair, and probability of drawing a pair card. The algorithm relies on this relationship to compute its values.

Finite Shoe Algorithm Parameters

pCards = number of undrawn to pair cards (initially = 2)
remSp = number of remaining splits (= allowed splits - 1)
p = number of pair cards present after 2 pair cards and dealer up card have been dealt
np = number of non-pair cards present after 2 pair cards and dealer up card have been dealt
pRem = number of pair cards removed from initial shoe state (initially = 0)
xh[] = array to store multipliers of EVx values (each element initialized to 0)
ph[] = array to store multipliers of EVPair values (each element initialized to 0)
prob = probability (initially = 1)

Use of Algorithm

The algorithm by itself does not compute split EV. It requires computation of EVx and EVPair values given number of pair cards removed. The range of EVx and EVPair values needed depends upon number of remSp = allowed splits - 1. For example computation of 3 allowed splits (remSp = 2) requires values for 0,1,2,3,4 pair cards removed.

Code

double getSplitHands(const int &pCards, const int &remSp, const int &p, const int &np,
		     const int &pRem, double xh[], double ph[], const double &prob) {
	if (p == 0 || remSp == 0) {
		xh[pRem] += prob * pCards;

		return (double)pCards;
	}

	double pP = double(p) / (p + np);
	double hp = 0, hn = 0;

// hp = expected hands when pair card is drawn
	hp = pP * getSplitHands(pCards + 1, remSp - 1, p - 1, np, pRem + 1, xh, ph, pP * prob);

// hn = expected hands when non-pair card is drawn
// computed on condition of not drawing a pair card so only pair cards are removed
	if (pCards > 1) {
		double hfull = 0, hfull_p = 0;

		hfull = getSplitHands(pCards - 1, remSp, p, np, pRem, xh, ph, prob);
		hfull_p = getSplitHands(pCards - 1, remSp, p - 1, np, pRem + 1, xh, ph, (-pP * prob));
		hn = (hfull - pP * hfull_p) + (1 - pP); // = (1 - pP) * ((hfull - pP * hfull_p) / (1 - pP) + 1)
	}
	else // pCards == 1
		hn = (1 - pP);

	xh[pRem] += prob;
	ph[pRem] -= (pP * prob);

	return hp + hn;
}

Example Finite Shoe: split EV 2-2 v 3, NDAS 6 decks s17, 3 allowed splits

double EVx[5] = {-.047220209964047750,-.047045547796896001,-.046879352186933704,
                 -.046721724929954231,-.046572767612829226};
double EVPair[5] = {-.081884586831497574,-.081716096948035125,-.081558453563186556,
                    -.081411936217665126, 0}; // EVPair[4] doesn't need to be computed
double xh[5] = {0}, ph[5] = {0}; // initialize to 0
int remSp = 2;
double expectedHands = getSplitHands(2, remSp, 22, 287, 0, xh, ph, 1); // run code
after code runs xh[0] = 2, xh[1] = 0.28478964401294499, xh[2] = 0.038834951456310690
                xh[3] = -0.0037949463963821515, xh[4] = 7.8544642190915976e-005
                ph[0] = -0.14239482200647249, ph[1] = -0.019417475728155338
                ph[2] = 0.0018974731981910753, ph[3] = -3.9272321095457988e-005
                expectedHands = 2.1599540968575321

// use algorithm data to compute split EV
double testEV = 0;
double handCheck = 0;
for (int pRem = 0; pRem <= 4; ++pRem) {
	testEV += xh[pRem] * EVx[pRem] + ph[pRem] * EVPair[pRem];
	handCheck += xh[pRem] + ph[pRem];
}

testEV computes to -0.096390309951751879 = split EV 2-2 v 3, NDAS 6 decks s17
handCheck computes to 2.1599540968575317 = EVx hands + EVPair hands // agrees with expectedHands

Infinite Shoe Version

The infinite shoe version is essentially the same as the finite version with one exception. In an infinite shoe the probability of drawing any rank is constant whereas in a finite shoe rank probabilities change as cards are removed. Rather than passing p = pair cards present and np = non-pair cards present as in the finite shoe code, a single parameter p = constant probability of drawing a pair card is passed in the infinite shoe version.

Infinite Shoe Code

double infGetSpHands(const int &pCards, const int &remSp, const double &p, const int &pRem, double xh[],
                     double ph[], const double &prob) {
	if (remSp == 0) {
		xh[pRem] += prob * pCards;

		return (double)pCards;
	}

	double hp = 0, hn = 0;
	hp = p * infGetSpHands(pCards + 1, remSp - 1, p, pRem + 1, xh, ph, p * prob);

	if (pCards > 1) {
		double hfull = 0, hfull_p = 0;

		hfull = infGetSpHands(pCards - 1, remSp, p, pRem, xh, ph, prob);
		hfull_p = infGetSpHands(pCards - 1, remSp, p, pRem + 1, xh, ph, (-p * prob));
		hn = (hfull - p * hfull_p) + (1 - p); // = (1 - p) * ((hfull - p * hfull_p) / (1 - p) + 1)
	}
	else // pCards == 1
		hn = (1 - p);

	xh[pRem] += prob;
	ph[pRem] -= (p * prob);

	return hp + hn;
}

Example Infinite Shoe: split EV T-T v 6, standard infinite deck, H17, NDAS, 3 allowed splits

// 10,10 vs 6; standard infinite deck; H17, NDAS | p = 4/13 = prob(10)
const double EVPair = 0.678245261281511; // EV for playing (10,10) = 20 vs 6 (not split) using best strategy
const double EVx = 0.282480847764203; // EV for a single 10, no more splits allowed
// EVn not necessary to algorithm: EVn = (EVx - p*EVPair) / (1 - p)
const double EVn = 0.106585552867622; // EV for a single hand when non-pair card is drawn first
const double p = double(4) / 13; // constant probability of drawing a pair card

double xh[5] = {0}, ph[5] = {0}; // initialize to 0
int remSp = 2;
double expectedHands = infGetSpHands(2, remSp, p, 0, xh, ph, 1); // run code
after code runs xh[0] = 2, xh[1] = 1.230769230769231, xh[2] = 0.757396449704142
                xh[3] = -0.349567592171143, xh[4] = 0.035853086376527
                ph[0] = -0.615384615384615, ph[1] = -0.378698224852071
                ph[2] = 0.174783796085571, ph[3] = -0.017926543188264
                expectedHands = 2.837225587339379

// use algorithm data to compute split EV
double testEV = 0;
double handCheck = 0;
for (int pRem = 0; pRem <= 4; ++pRem) {
	testEV += xh[pRem] * EVx[pRem] + ph[pRem] * EVPair[pRem];
	handCheck += xh[pRem] + ph[pRem];
}

testEV computes to 0.47011779565486361 = split EV T-T v 6, NDAS standard infinite shoe H17
handCheck computes to 2.8372255873393790 = EVx hands + EVPair hands // agrees with expectedHands

 

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