Compute Probabilities

k_c

Well-Known Member
#21
bruceB said:
What algothrym do you use to compute whether to hit or stand? I can compute the probabilities for the dealer and for your hand, but how do know whether to hit or stand?
Standing is the simplest thing to compute. To get the stand EV, all you need is the dealer's probability of a final hand of 17,18,19,20,21,bust, or blackjack given his up card and the shoe comp from which cards are drawn.

Hitting is more complicated. As has been suggested, you need to weight all possible draws, choose the best strategy for each draw, and compute the best hit EV. This is for a composition dependent strategy.

For a total dependent strategy, you determine the best hitting strategy versus each up card based on a given hard or soft total and use that to compute hit EV. Total dependent is a little less optimal than composition dependent. People generally use total dependent because there are just too many card combinations to remember.

As Canceler says, once you have the stand EV and hit EV, hit if hit EV > stand EV otherwise stand.

k_c
 

eps6724

Well-Known Member
#22
Stupid Question Time

STUPID QUESTION TIME...

Isn't this what the basic strategy and indice changes already did? If you're looking for just a mental excercise, I guess I can understand. If not, am I missing something important to improve myself with????

Oh, and according to a Waffle House waitress (and THEY KNOW Southern English) "y'all" is singular. The plural is "all y'all'!
 

Canceler

Well-Known Member
#23
eps6724 said:
Isn't this what the basic strategy and indice changes already did?
Yes, the work has already been done.

eps6724 said:
If you're looking for just a mental excercise, I guess I can understand.
My hat is off to k_c, MGP, and anyone else who has written a CA program. :1st: More than just a mental exercise, it must be truly mind-numbing!

eps6724 said:
If not, am I missing something important to improve myself with????
No.

eps6724 said:
Oh, and according to a Waffle House waitress (and THEY KNOW Southern English) "y'all" is singular. The plural is "all y'all'!
That's not what I've heard. But what do I know, I'm from Minnesota!
 

ChefJJ

Well-Known Member
#24
eps6724 said:
Oh, and according to a Waffle House waitress (and THEY KNOW Southern English) "y'all" is singular. The plural is "all y'all'!
Y'all is singular and plural. Actually, it's just plural.
 

MangoJ

Well-Known Member
#26
k_c said:
The program in Ken's link is by Eric Farmer. It is a very fine program and like mine computes composition dependent EVs. My program, however computes EVs vs up cards of ten or ace assuming player loses to dealer's blackjack, whereas Eric's computes the EV assuming dealer has already checked for blackjack and doesn't have it. The proper strategy can be derived from either set of EVs. In order to convert use the formula -
Code:
EV(dealer has checked for BJ) = [EV(player loses to DBJ) + pBJ]/(1-pBJ)
     where pBJ = probability of dealer blackjack
Sorry for necroposting. I'm currently working with Farmers code.
The above formula for translating EVs between a peeking and non-peeking Dealer is correct only for Stand and Hit EVs.

For DoubleDown (when you loose the additional bets on a Dealers Blackjack) it is
Code:
EV(dealer has checked for BJ) = [EV(player loses to DBJ) + 2*pBJ]/(1-pBJ)
I guess one could use this formula also for the split (again, if you loose your additional bet on Dealers BJ).
 

k_c

Well-Known Member
#27
MangoJ said:
Sorry for necroposting. I'm currently working with Farmers code.
The above formula for translating EVs between a peeking and non-peeking Dealer is correct only for Stand and Hit EVs.

For DoubleDown (when you loose the additional bets on a Dealers Blackjack) it is
Code:
EV(dealer has checked for BJ) = [EV(player loses to DBJ) + 2*pBJ]/(1-pBJ)
I guess one could use this formula also for the split (again, if you loose your additional bet on Dealers BJ).
The formula applies to doubles as well.

Example: Single deck, 6-5 versus ace, dealer stands on soft 17, full peek
Prob dealer BJ: 32.65

Unconditional EVs (player's non-blackjack loses to dealer's blackjack)
stand: -.75.35%, double: -16.47%, hit: -21.01%

Conditional EVs (dealer checked for BJ, doesn't have it)
stand: -63.40%, double: +24.03%, hit: +17.29%

Conditional EVs can be computed by substituting unconditional EVs and prob dealer BJ into the formula for stand, hit, double. There may be round off error.

Formula:
CEV (in %) = (UEV + pDBJ)/(1-pDBJ/100)

It doesn't matter which set of EVs are used to determine strategy. Both produce the correct strategy. Single splits are computed just like above. Multiple splits are more complicated.

For reference-
ENHC EVs (no peek)
stand: -75.35%, double: -49.13%, hit: -21.01%
 

MangoJ

Well-Known Member
#28
Hm, I don't get it your way.

The UEV should be for stand/hit:

(1): UEV = (1-pDBJ) * CEV + pDBJ * (-1)

Dealer has no Blackjack with probability (1-pDBJ), in which case your hand is worth the CEV. If Dealer has a blackjack (probability pDBJ), your hand is worth (-1) - a sure loss (given that you don't have a BJ as we are evaluating a standing/hit position).
Solving for CEV gives original formula.

But if you double (assuming doubled bets also loose), things are different:
(2): UEV = (1-pDBJ) * CEV + pDBJ * (-2)
A doubled hand is worth -2 on a Dealers Blackjack, as you loose both bets.
Solving for CEV gives my proposed formula.


For reference-
ENHC EVs (no peek)
stand: -75.35%, double: -49.13%, hit: -21.01%
I agree on the numbers for the 6-5 vs A scenarios for stands+hits.
So what is/ the correct EV for doubling in the ENHC scenario, is it -49.1254% from the factor-2 formula, or the -16.4723% from the factor-1 formula ?
Basic strategy tells to NOT double vs a non-peeked A, agreed ?
This is also the result for the factor-2 calculation, as
-49.1254% (double) < -21.0057% (hit).

Whereas if the factor-1 formula is right, it would suggest doubling, as
-21.0057% (hit) < -16.4723% (double).


Maybe I don't fully understand EVs for doubled bet. I always had the impression that a sure doubled win is of EV +2, and a sure doubled loss is of -2.
If you somehow know the Dealer has a Blackjack for sure (pDBJ=1) your UEV of a doubled bet should be -2, which is the result of (2), but not of (1).

Maybe you can clarify this for me ?
 
Last edited:

k_c

Well-Known Member
#29
MangoJ said:
Hm, I don't get it your way.

The UEV should be for stand/hit:

(1): UEV = (1-pDBJ) * CEV + pDBJ * (-1)

Dealer has no Blackjack with probability (1-pDBJ), in which case your hand is worth the CEV. If Dealer has a blackjack (probability pDBJ), your hand is worth (-1) - a sure loss (given that you don't have a BJ as we are evaluating a standing/hit position).
Solving for CEV gives original formula.

But if you double (assuming doubled bets also loose), things are different:
(2): UEV = (1-pDBJ) * CEV + pDBJ * (-2)
A doubled hand is worth -2 on a Dealers Blackjack, as you loose both bets.
Solving for CEV gives my proposed formula.




I agree on the numbers for the 6-5 vs A scenarios for stands+hits.
So what is/ the correct EV for doubling in the ENHC scenario, is it -49.1254% from the factor-2 formula, or the -16.4723% from the factor-1 formula ?
Basic strategy tells to NOT double vs a non-peeked A, agreed ?
This is also the result for the factor-2 calculation, as
-49.1254% (double) < -21.0057% (hit).

Whereas if the factor-1 formula is right, it would suggest doubling, as
-21.0057% (hit) < -16.4723% (double).


Maybe I don't fully understand EVs for doubled bet. I always had the impression that a sure doubled win is of EV +2, and a sure doubled loss is of -2.
If you somehow know the Dealer has a Blackjack for sure (pDBJ=1) your UEV of a doubled bet should be -2, which is the result of (2), but not of (1).

Maybe you can clarify this for me ?


What I have always done is to start with unconditional EV where player's non-blackjack loses to dealer's blackjack.

Try a simple example. Assume player has 11 versus dealer up card of ace and there are nothing but 16 tens plus 1 ace left in the shoe.

Player doubles: EV = -1.765. This is ENHC (no peek) EV. He loses 2 units 16/17 of the time and wins 2 units 1/17 of the time.
EV = -30/17 = -1.765

If instead rule is full peek, player gets a 1 unit rebate 16/17 (prob dealer BJ) of the time.
EV = 16/17 * (-1) + 1/17 * 2 = -14/17 = -.8235 = Unconditional full peek double EV

If full peek, dealer checks for BJ, and doesn't have it 16/17 (prob dealer BJ) of the time player gets another 1 unit rebate and percentage of hands played = 1 - 16/17.
EV = (16/17 * (-1) + 1/17 * 2 + 16/17 * (+1)) / (1 - 16/17) = +2 = Conditional full peek double EV


That's about as well as I can explain it.
 

MangoJ

Well-Known Member
#30
Thank you k_c, I appreciate very much the time you put in teaching someone how just popped in.

I now learned my problem, there are maybe different "EVs" people are talking about, and one should specify.

The EV(ENHC) is -30/17 = -1.764, agreed. You win 2 units with chance of 1/17, and loose 2 units with chance of 16/17.

The EV I'm interested in is the EV in a holecard game where the dealer checks for Blackjack before any player take their action on their hand (lets forget about insurance).
It's then your "Conditional full peek double EV" (let me call it CFP - don't know the official name) which is applicable in the very instant you are asked how to play your hand.
Obviously if you know the Dealer has no BJ, he must have the Ace, and your double will win against the Dealers bust. So EV(CFP) = +2, this I also agree with you.


The formula I was posting was the connection between EV(ENHC) and EV(CFP), which is

EV(ENHC) = (1-pDBJ) * EV(CFP) - 2*pDBJ

and reversed

EV(CFP) = (EV(ENHC) + 2*pDBJ)/(1-pDBJ)

(In Post #28 in ignorance of proper naming conventions I labeled EV(ENHC) as UEV and EV(CFP) as CEV).

I may ask to confirm me that these formulas are consistent with EV(CFP)=2 and EV(ENHC)=-30/17 (and pDBJ=16/17). And further - in the scenario of having Farmer's code producing EV(CFP), for evaluating ENHC double decisions you indeed need the conversion with factor 2.
 

London Colin

Well-Known Member
#31
I've made a number of changes to my copy of Eric Farmer's code. I haven't done any work on it in quite some time. One day I hope to go back to it and get it into a state in which I am happy to release it. (I don't think I've introduced any bugs, but it is all a bit messy and hard to follow at present.)

Support for ENHC is one of the features I added. As I recall, I didn't have to alter any formulas, just change some of the logic at certain points in the code. If you are interested, I can post the relevant parts.
 

MangoJ

Well-Known Member
#32
I'm writing a ENHC analyzer from scratch for study, but had problems with split evaluation which I could only "guess". I'm now trying Farmers code, and stand/hit/double EVs matches exactly (with correction for ENHC), split-EVs are within 0.5% - so looks promising.

Farmers code looks more sophisticated with much less memory consumption (mine uses 10GB!) and wider range of rule options.
Presumably I could learn a lot from Farmers code, however I'm not good at reading someone else's algorithms, not to speak of modifying them. I'm too much afraid of mistakes (which can be costly if you commit on your results) and rather like to have two independent sources.

Colin, if you could share your modifications concerning ENHC I would really appreciate it and happily provide feedback.
 

London Colin

Well-Known Member
#33
MangoJ said:
I'm writing a ENHC analyzer from scratch for study, but had problems with split evaluation which I could only "guess". I'm now trying Farmers code, and stand/hit/double EVs matches exactly (with correction for ENHC), split-EVs are within 0.5% - so looks promising.
There is an issue with the pair splitting in Eric's code. Even when the strategy is for perfect play (identified by the constant BJ_MAX_VALUE), Eric decided that post-split actions should reflect human-style play, rather than computer-perfect play, and all post-split actions should be the same as they would be when playing the same hand as the first two cards. This gives slightly different EVs for splitting pairs, compared to other figures you may see quoted, or come up with yourself.

However, he did post alternative code on the forum at bjmath.com, and I incorporated that into my version. At present I choose between the two methods via conditional compilation, using a macro NEW_SPLIT to select the repacement version. I'll post the relevant code for this, as well as the ENHC stuff.


MangoJ said:
Farmers code looks more sophisticated with much less memory consumption (mine uses 10GB!) and wider range of rule options.
Presumably I could learn a lot from Farmers code, however I'm not good at reading someone else's algorithms, not to speak of modifying them. I'm too much afraid of mistakes (which can be costly if you commit on your results) and rather like to have two independent sources.
Eric's code is a little hard to follow, and with my amendments things only get worse.:) It is certainly an excellent resource, though.

MangoJ said:
Colin, if you could share your modifications concerning ENHC I would really appreciate it and happily provide feedback.
No problem.


ENHC

In the BJPlayer object, I have added a boolean flag called enhc, which is true when the rule is in force. (It gets set via other changes I have made in the BJRules object, but we can skip that detail.)

This flag is then used as follows -

At the end of BJPlayer::reset():
Code:
{
....

// condition individual hands on no dealer blackjack
// (unless ENHC rule is in effect)
    [B]if (!enhc)[/B] conditionNoBlackjack();

// finalize progress indicator.
    progress.indicate(100);
}
In BJPlayer::computeDoubleDownCount():
Code:
// We only lose our initial wager if the dealer has blackjack.
// (unless ENHC rule is in effect)

                if (upCard == 1 && !enhc) {
                    hand.valueDoubleDown[split][upCard] = shoe.
                            getProbability(10);
                } else if (upCard == 10 && !enhc) {
                    hand.valueDoubleDown[split][upCard] = shoe.
                            getProbability(1);
                } else {
                    hand.valueDoubleDown[split][upCard] = 0;
                }
And similarly in BJPlayer::computeSplit():
Code:
// We only lose our initial wager if the dealer has blackjack.
// (unless ENHC rule is in effect)

                    if (upCard == 1 && !enhc) {
                        valueSplit[pairCard][upCard] = shoe.
                                getProbability(10);
                    } else if (upCard == 10 && !enhc) {
                        valueSplit[pairCard][upCard] = shoe.
                                getProbability(1);
                    } else {
                        valueSplit[pairCard][upCard] = 0;
                    }
And that's it! Hopefully you can match the above code fragments with the original. I didn't want to show more of the surrounding code because I have made so many other, unrelated changes that it would get confusing.


I'll make a separate post for the NEW_SPLIT code.
 

London Colin

Well-Known Member
#34
New_split

In BJPlayer::computeHitCount():
Code:
                            switch (strategy.getOption(currentHand, upCard,
                                    doubleDown, false, false)) {

// To be consistent with a "fixed" playing strategy, use the option maximizing
// expected value for the non-split hand.

                            case BJ_MAX_VALUE :
#ifdef NEW_SPLIT
                                value = hitHand.valueStand[split][upCard];
                                if (value < hitHand.
                                        valueHit[split][upCard]) {
                                    value = hitHand.
                                            valueHit[split][upCard];
                                }
                                if (doubleDown) {
                                    if (value < hitHand.
                                            valueDoubleDown[split][upCard]) {
                                        value = hitHand.
                                            valueDoubleDown[split][upCard];
                                    }
                                }
#else
                                j = findHand(currentHand);
                                testValue = playerHands[j].
                                        valueStand[false][upCard];
                                value = hitHand.valueStand[split][upCard];
                                if (testValue < playerHands[j].
                                        valueHit[false][upCard]) {
                                    testValue = playerHands[j].
                                            valueHit[false][upCard];
                                    value = hitHand.
                                            valueHit[split][upCard];
                                }
                                if (doubleDown) {
                                    if (testValue < playerHands[j].
                                          valueDoubleDown[false][upCard]) {
                                        value = hitHand.
                                            valueDoubleDown[split][upCard];
                                    }
                                }
#endif
                                break;
And similarly in BJPlayer::computeSplit():
Code:
                                    switch (strategy.getOption(currentHand,
                                            upCard, doubleDown, false, false)){

// Again, use the playing option that maximizes expected value for the
// non-split hand.

                                    case BJ_MAX_VALUE :
#ifdef NEW_SPLIT
                                        value = hand.
                                                valueStand[true][upCard];
                                        if (value < hand.
                                                valueHit[true][upCard]) {
                                            value = hand.
                                                    valueHit[true][upCard];
                                        }
                                        if (doubleDown) {
                                            if (value < hand.
                                                valueDoubleDown[true]
                                                        [upCard]) {
                                                    value = hand.
                                                       valueDoubleDown[true]
                                                        [upCard];
                                            }
                                        }

#else
                                        j = findHand(currentHand);
                                        testValue = playerHands[j].
                                                valueStand[false][upCard];
                                        value = hand.
                                                valueStand[true][upCard];
                                        if (testValue < playerHands[j].
                                                valueHit[false][upCard]) {
                                            testValue = playerHands[j].
                                                   valueHit[false][upCard];
                                            value = hand.
                                                    valueHit[true][upCard];
                                        }
                                        if (doubleDown) {
                                            if (testValue < playerHands[j].
                                                valueDoubleDown[false]
                                                        [upCard]) {
                                                value = hand.
                                                    valueDoubleDown[true]
                                                        [upCard];
                                            }
                                        }
#endif
                                        break;
N.B. The comments at the beginning of these two blocks of code are from the original, and relate to the code which is now in the #else ... #endif sections.
 
Last edited:

MangoJ

Well-Known Member
#35
Thanks for the snippets, looks easy enough. I'll probably place the ENHC flag into BJRules (which is loaded from the BJPlayer constructor anyway). I'm glad someone is still interested code written 10 years ago.

My idea of splitting-EV was (currently without resplits), to draw two cards, and then calculate EV for the new formed hands seperatly from the remaining shoe. This would result in only ~10^2/2! hand evaluations. The drawback is, that the evaluation is only exact for the first split hand, while the second split hand fully ignores the drawn cards from the first hand, hence the second hand is played more conservatively. Maybe this is what Eric calls "human-style" play.
With resplits one could follow the road of calculating ~10^2/2!, 10^3/3!, 10^4/4! split hands.

I'm using version 5.2. of Eric's code, that is the latest I could found (think from 2006). I'm eager to learn about the NEW_SPLIT section.
 

London Colin

Well-Known Member
#36
To clarify a few things...

The base version, upon which I made all my changes was 5.0. However, I think 5.2 was identical, as the only changes Eric made were in the programs which use the library, not the library itself.

The NEW_SPLIT code was just something he posted on the forum at bjmath.com, in response to some questioning. Unfortunately, that forum is down at the moment (don't know if it will ever come back up), so it's not possible to review all the discussions that went on there.


Both the old and new splitting code account for all the possible splits and resplits which may occur in order to calculate the EV of an initial pair. But they do it differently.

Consider an example -

Suppose you are evaluating 9,9 vs 2. When evaluating the split, you have to look at all the possible 9,X vs 2 hands (one half of the split), in the context of a shoe that has had one 9 removed. (And when X=9, there is the possibility of a resplit, meaning the same process must be repeated with two 9s removed from the shoe, and so on.)

Now consider the case when X=4. We will already have calculated the EVs for every possible action on 9,4 vs 2 for the full shoe, and found that standing was the highest.

Eric's original code dictates that the post-split strategy will always be to stand on the 9,4 vs 2, without checking whether hitting is now a better option (which it might be, because the missing 9 is one of the cards that would bust the hand if we hit). And if standing is actually still better that might not remain the case after further resplits, but again the code will always stand.

What the code does do, however, is recalculate the EV for the post-split hand, in the context of the missing nine(s), and feed this into the calculation of the overall EV for splitting the original 9,9 pair.


The NEW_SPLIT code, however, will pick the action on 9,4 vs 2 with the highest EV in the context of the missing nine(s).

As I recall, the figures the new code produces match those of Cacarulo, on bjmath.com, and in the book Blackjack Attack III.

Hope all that makes sense.:)
 

MangoJ

Well-Known Member
#37
Still their remains the problem of split-hand-depenend strategy.
So if you stand on 9,4 vs 2 - for the second hand the situation is that now there is a 9 AND a 4 missing. Which of course alters the EV of the second hand - and will thus have an effect on the overall splitting EV.

Say we evaluate only marginal decisions. From composition-dependent strategy one should hit with 10,2 against 4, but stand with 9,3. Obviously the reason to hit is the 10 already drawn from the deck.

Now what happens if you split 9,9 vs 4 ? Say you get a 9,3 and 9,10 - and for the sake of argument when playing the first hand you don't know that the second hand is yet.
If you get the 9,3 as first hand (not knowing of the removed 10) you will stand. Second hand is 9,10 (stand).

But what if you get the 9,10 first, of course you stand. Now revealing the 9,3 hand you might want to hit (because of the removed 10 on the other hand). So the 9,3 hit EV has changed (*).

Playing both hands in different order has a different EV. Because the second hand has information the first hand didn't have. Think of resplits and you see the problem gets larger.


(*) One might ask that the same argument holds also for the dealers hand, that is drawing cards will alter dealers outcome as "taking bust card". This is clearly wrong, as the dealer has no freedom of decisions and cannot adjust his play to the cards seen. The dealer could simply complete his hand first in the game (without revealing the value or number of drawn cards) and nothing would change.
But the player does have a strategy decision. And on marginally hands, seeing cards does alter the split-EV.
 

London Colin

Well-Known Member
#38
It's true; the second half of a split hand is effectively played at a higher penetration (deriving from the average number of cards drawn to the first half of the split, including resplits), but I think the overall difference this makes will be very small.

I think the difficulties involved in accounting for all possibilities, together with the extra processing that this would require, make such an approach unfeasible.

Accounting for all the split cards (but not the cards drawn to the other hands in the split) represents a good compromise between truly 'perfect' play and Basic Strategy. And as I said, it seems to be the basis for the published figures in BJA III.

Effectively what it gives us is a number of separate strategies. For any given hand, we have a strategy for how to play that hand after 0, 1, 2 or 3 splits have taken place.
 

MangoJ

Well-Known Member
#39
Thank you Colin, at least I'm not lost in ploppy-talk while hallucinating.

The reason I'm so curious about those split-EVs I'm running a (supposely) perfect-strategy simulator on different penetration depths (say - 5/10/15 seen random cards removed from the single deck).

In theory, as more and more cards are removed, you have better knowledge of the remaining cards, and can make better strategy decisions, which should increase EV - even with flat betting. But the problem is, my playing results drop slightly (but significantly!) with penetration depths.

There are only two explanations: My pseudo-random number generator is biased or correlated (didn't checked it), or I do wrong playing decisions.
As I could replicate Erics exact EVs for stand/hit/double with my code, it must be the split EVs which are significantly off.

Not sure what to do next steps - I'm from an academic background, this really bugs me. Something must be wrong...
 

London Colin

Well-Known Member
#40
MangoJ said:
Thank you Colin, at least I'm not lost in ploppy-talk while hallucinating.

The reason I'm so curious about those split-EVs I'm running a (supposely) perfect-strategy simulator on different penetration depths (say - 5/10/15 seen random cards removed from the single deck).

In theory, as more and more cards are removed, you have better knowledge of the remaining cards, and can make better strategy decisions, which should increase EV - even with flat betting. But the problem is, my playing results drop slightly (but significantly!) with penetration depths.

There are only two explanations: My pseudo-random number generator is biased or correlated (didn't checked it),
Or there might be a bias in your algorithm for generating the subsets, even if the RNG is OK. (There is an old thread somewhere in which k_c and I discuss the subtleties of shuffle algorithms.)

MangoJ said:
or I do wrong playing decisions.
As I could replicate Erics exact EVs for stand/hit/double with my code, it must be the split EVs which are significantly off.

Not sure what to do next steps - I'm from an academic background, this really bugs me. Something must be wrong...
Just to be clear, are you saying that you have coded your own Combinatorial Analysis program, which you are plugging in to a simulator to provide the perfect-play decisions for a random sample of deck subsets?

I started down that path on a project of my own, plugging in Eric's CA, but in the end found it easier to enumerate every possible subset for a given depth, and accumulate the average, off-the-top EV for that depth, as calculated by the CA. (With 8 cards removed, prior to the 2 initial player cards plus dealer upcard, I think it took about a day to run. So it wouldn't be practical to go much deeper.)

Because I don't play any simulated hands it does mean that I don't get truly perfect splitting EVs (for the reasons we have been discussing), but on the other hand I don't have to worry about RNG problems or how a large a sample size might be required to get accurate figures.
 
Top