Dealing with Blackjack
Of all the casino games, blackjack has
one of the lowest edges for the house.
Under some rules it is even possible for
the player to have an edge on the house.
Various books and net resources can provide
a system of what to do when certain cards appear.
We want to figure out some probabilities that
go into making those systems. Namely, we
want to know for each upcard the dealer may
have showing, what are the probabilities the
dealer will end up with a given final count.
In blackjack, face cards (K, Q, J) counts as 10,
an ace counts as 1 or 11, and the other cards
count their value. The goal is to get as close
to 21 as possible without going over, and to
beat the dealer's hand. Once all the players
finish, the dealer plays his hand according
to specified rules. This project is only
going to look at the dealer's play.
The algorithm for the dealer can be expressed as follows:
- If the total is 16 or less, take another card and repeat this step
- Otherwise, take no more cards, and settle all bets
Note: in some casinos the dealer also takes a card
on a soft 17, ie an Ace plus 6 other points.
The dealer may therefore end up with any of the
following scores: 17, 18, 19, 20, 21, blackjack,
or bust. The question is, for any given upcard,
what are the probabilities the dealer will end up
with each of these possible results? A blackjack
is a 21 composed of exactly two cards - an Ace and
a 10-value card. Since the rules treat blackjacks
differently than other 21-counts, it should be
considered a separate result for the dealer.
Part I
The first step is creating the deck. The suits
do not matter, only the card values.
The effective values in one deck are: 4 each of
2 through 9, 16 10's, and 4 that can be 11 or 1.
You will have to figure out how to deal with Aces.
So an initial deck of 52 cards should have the
following values each repeated 4 times:
Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10.
For this project, use a shoe consisting of six decks.
There are many ways to simulate shuffling and dealing
cards on a computer. One way to handle shuffling
and dealing is to have a second vector of the same size
as the deck or shoe, which will contain the answer to
the question: has this card been dealt already?
The values in this vector will initially be set to
all 0's ('false'). When a card is randomly choosen
from the shoe the corresponding place in the other
vector is checked. If the value is 'false' it has
not yet been dealt, and the value is set to 1 ('true')
to indicate the card has now been dealt.
If the value is already 'true' what needs to be done?
How can the simulated deck be reshuffled?
Use a shoe of 6 decks of 52 cards, or 312 total cards.
Create a vector of the necessary length, and place
the numbers above in the vector so the counts are
correct. You should print out this vector of values
and verify for yourself that the values are correct.
Next, your program should deal cards from this
deck to the dealer's hand until the total is over
16, as the rule above indicates. You will have to be
careful how you treat Aces. Print out the values of
the cards in the order they are dealt, and verify
that the dealing stops as appropriate. You should
prompt the user once for a random seed value
and generate one deal only. (Therefore if you use
the same seed you should get the same hand dealt.)
How can you verify that your dealing algorithm is
not dealing the same card more than once?
Store this program as 21a.cpp in the lab3 folder
of your eng101 space. While you may handle aces
differently, your program should work something
like this:
ruby% a.out
Seed ? 1
Cards dealt: 11 5 10 10 :: total = 26
ruby% a.out
Seed ? 6
Cards dealt: 10 10 :: total = 20
ruby% a.out
Seed ? 9
Cards dealt: 4 3 3 2 4 10 :: total = 26
ruby% a.out
Seed ? 12345
Cards dealt: 11 6 :: total = 17
ruby% a.out
Seed ? 13
Cards dealt: 10 11 :: total = 21
ruby% a.out
Seed ? 1
Cards dealt: 11 5 10 10 :: total = 26
Part II
Now do the same thing for 1,000,000 deals, and
collect statistics on the results. Only prompt
once for a seed at the beginning of the deals!
For each deal, reshuffle the deck, and treat the
first card dealt as the upcard. Deal out a hand
for the dealer using the code you wrote for part
1. Each deal generates a data point consisting
of two values, the first card and the final dealer
result. Keep track of the times each data point
occurs using a 2D vector.
Remember that any hard total over a 21 is a bust;
it does not matter if the dealer gets a 22 or a 24.
(What is the highest total the dealer could have?)
Next display these statistics as probabilities
in a readable grid structure.
Finally, change the dealer rules so the dealer
hits on a soft 17, and generate revised statistics
on 1,000,000 deals using the same seed value.
Compare the two. Where did you expect differences?
Where are there differences? Can you determine
which one should be more favorable to the house?
If not, would more deals help?
Store your final program using the soft 17 rule
as 21b.cpp in the lab3 folder of your eng101 space.
Your program should have the following rough structure:
- Set up the deck
- do the following 1,000,000 times
- shuffle the deck
- remember the first card dealt
- if the total is less than 17 (optionally also a soft 17)
- deal another card from the deck
- update the total
- identify busts and blackjacks
- keep track of the times this final result happens
- print out the final statistics
Your program should generate results something like this:
Dealer results in 1000000 deals
UpCard 17 18 19 20 21 Bust BlkJk
1 0.058009 0.143974 0.143987 0.142291 0.065956 0.137050 0.308734
2 0.129509 0.135729 0.129861 0.125192 0.121202 0.358507 0
3 0.126504 0.132416 0.125264 0.122732 0.115489 0.377594 0
4 0.123957 0.126421 0.122380 0.118691 0.114468 0.394082 0
5 0.119597 0.124278 0.119127 0.112777 0.107366 0.416855 0
6 0.115530 0.113878 0.114906 0.110339 0.105096 0.440251 0
7 0.371553 0.135432 0.079290 0.079382 0.074211 0.260132 0
8 0.129021 0.360571 0.128917 0.070656 0.067057 0.243776 0
9 0.120687 0.118726 0.349259 0.119700 0.060798 0.230831 0
10 0.112073 0.111609 0.111833 0.341216 0.034549 0.211309 0.077410
Questions for thought and discussion
Some of these questions relate to assumptions built
into the project, some to decisions made in
creating the C++ program, and some to interpreting
the results.
Can you tell by looking at the above chart whether it
was made with the dealer staying or hitting on soft 17?
The deck is simulated by a vector of 312 values.
Should the initial order of values have an effect
on the final results? Does it?
This approach reshuffles the deck at the start
of each deal. This means the dealer is always
taking cards from an initially full, shuffled deck.
What is good or bad about this?
This project suggested using a second parallel
vector to handle shuffling a deck.
How else could shuffling have been handled?
How is one way better than another?
In C++ a bool is an int in disguise. Nevertheless
in C++ there is a difference between a vector of
ints and a vector of bools. What is the difference?
What are the benefits of one over the other?
Why do you think a vector of ints was suggested?
The dealer gets a 10-value card as an upcard
more often than any other card, so the counts
are higher for this upcard.
Approximately how much higher?
Should we do anything to adjust for this?
How do we know how many hands to try? Was
1 million a good choice? Do your probabilities
change if you do 10 million hands instead?
You may want to use the -O2 compiler option
and try this out.
In many casinos, the dealer actually checks
for a blackjack before the player plays
the hand. If the dealer has a blackjack,
it automatically beats anything a player
may have except another blackjack, which ties.
What if any consequence does this casino rule
have on a player using your statistics
to make play decisions?
This project recommended only prompting for
a seed value once before dealing all of the hands.
Certainly you wouldn't want to prompt the user
for a million seed numbers! However, another
possibility is that for the Nth deal, the
random number generator could be seeded with N.
Does this make a difference?
What might be good or bad about this?
How much time does the seeding process take?
How are deals #1 and deals #2 related with
this approach?
Does it assume more or less "randomness"
from the random number generator?
Does it assume anything about the seeding
process? Would seeding with a different
function of N matter?