# Studying Blackjack (#151)

**URL:** <https://www.ruby-forum.com/t/studying-blackjack-151/125624>\
**Category:** Ruby\
**Created:** [January 10, 2008, 6:19pm UTC](https://www.ruby-forum.com/t/studying-blackjack-151/125624 "2008-01-10T18:19:50Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [January 10, 2008, 6:19pm UTC](https://www.ruby-forum.com/t/studying-blackjack-151/125624/1 "2008-01-10T18:19:50Z")

</div>

The solutions took two totally different approaches this week. Some  
exhaustively searched the possible hands a dealer can have. The search  
space  
isn’t too big in this case so that’s a viable approach that gives exact  
results.

Others chose to solve the problem with a simulation. This approach is  
very  
simple: deal a ton of hands, play them out by the dealer rules, and  
keep track  
of the results. If you simulate enough hands, this approach should zero  
in on  
the actual numbers.

Let’s see if the two approaches agree. Here are the expected results  
from Eric  
I.'s exhaustive search:

upcard bust 17 18 19 20 21  
natural

* * *

* * *

```
   2 | 35.33% 13.94% 13.33% 13.07% 12.40% 11.93% 

```

0.00%  
3 | 37.48% 13.28% 13.07% 12.46% 12.18% 11.54%  
0.00%  
4 | 39.85% 13.07% 12.02% 12.10% 11.64% 11.31%  
0.00%  
5 | 42.25% 12.10% 12.28% 11.73% 10.90% 10.73%  
0.00%  
6 | 42.21% 16.62% 10.62% 10.67% 10.12% 9.75%  
0.00%  
7 | 26.11% 37.05% 13.82% 7.80% 7.88% 7.34%  
0.00%  
8 | 24.16% 12.97% 36.12% 12.90% 6.89% 6.96%  
0.00%  
9 | 23.09% 12.09% 11.20% 35.41% 12.11% 6.10%  
0.00%  
10 | 21.32% 11.29% 11.22% 11.30% 33.56% 3.55%  
7.77%  
ace | 11.59% 12.85% 13.09% 13.02% 13.12% 5.28%  
31.07%

Now here are the same results from Chris L.'s simulation code (with  
the  
number of games increased to 100,000):

Upcard Bust 17 18 19 20 21 Natural  
c2 35.02% 14.06% 13.36% 13.12% 12.53% 11.90% 0.00%  
c3 37.24% 13.47% 12.94% 12.67% 12.27% 11.42% 0.00%  
c4 39.29% 13.25% 12.58% 12.16% 11.59% 11.13% 0.00%  
c5 41.78% 12.29% 12.24% 11.78% 11.20% 10.71% 0.00%  
c6 42.05% 16.36% 10.75% 10.69% 10.14% 10.02% 0.00%  
c7 25.97% 37.00% 13.89% 7.83% 7.75% 7.57% 0.00%  
c8 24.52% 12.75% 35.93% 12.75% 6.94% 7.10% 0.00%  
c9 22.86% 12.00% 11.97% 35.02% 12.08% 6.07% 0.00%  
ct 21.43% 11.04% 11.11% 11.05% 34.37% 3.36% 7.63%  
cj 21.39% 11.11% 11.15% 11.17% 34.07% 3.44% 7.67%  
cq 21.16% 10.97% 11.30% 11.13% 34.19% 3.47% 7.79%  
ck 21.23% 11.27% 11.13% 11.24% 34.07% 3.49% 7.58%  
ca 13.13% 12.88% 12.86% 12.71% 12.71% 4.95% 30.77%

As you can see, they are very close to each other.

There are advantages to both approaches and I had a hard time picking  
what to  
talk about in this summary. If accuracy is your biggest concern, you’re  
probably better off sticking with the exhaustive search. However,  
simulation  
gets very close results, is probably a little easier to code up, and  
would be an  
option even if the search space was much larger (though the results may  
be less  
accurate for that).

Given that, I’m going to show a simulation solution here. If you are  
more  
interested in the exhaustive search, Eric I.'s code is well commented  
and easy  
to read.

The solution I want to look at below is Sander L.'s first submission.  
It’s  
not perfect and I’ll try to point out the problems as they come up, but  
the code  
is a very straight-forward simulation with a few good tricks in it. I  
think  
that makes it worth a read through.

Sander’s solution is written for Ruby 1.9, so it doesn’t run without  
modification on earlier versions. I’ll point out the new features as we  
go and  
that will give us a chance to see how some of the new stuff gets used.

Here’s the start of the code:

class Array  
def score  
sort.inject(0){|s,c| s+c \> 21 && c==11 ? s+1 : s+c }  
end  
end

# …

This is Sander’s code for valuing Blackjack hands. Sander’s notion of a  
hand is  
simply an Array of 2 through 11 Integers. Blackjack isn’t affected by  
suits, so  
this code doesn’t bother to track them.

This code counts drops an ace an to 1 if counting it as 11 would bust  
the hand.  
Note that the hand is first sorted to push aces to the end and make sure  
they  
are counted last. Everything else gets the normal card value added on  
to a  
running total.

While, this code gets very close, it doesn’t work in all cases.  
Consider a hand  
containing a ten, an ace, and another ace. The code will count the ten,  
add  
eleven to it, because it wouldn’t bust the hand, then add one more.  
This gives  
a final total of twenty two, instead of the correct count of twelve. At  
least  
three solutions made the same error.

One way to fix the code is:

class Array  
def score  
sort.each\_with\_index.inject(0){|s,(c,i)|  
s+c \> 21 - (size - (i + 1)) && c==11 ? s+1 : s+c  
}  
end  
end

This does the same thing, but reduces the hand limit for each card we  
have left  
to count once we reach the aces. They are sorted to the end so we know  
we only  
have aces left and we could choose to count them as one each. Note that  
I used  
a 1.9 feature here of calling each\_with\_index() without a block to get  
an  
Enumerator object. That allows me to inject() over the values with  
their index.

Here’s the next bit of Sander’s code:

# …

unless ARGV[0]  
(2…11).each{|n| puts `ruby1.9 #{ __FILE__ } #{n}`}  
exit  
end

# …

I thought this was a great trick. As you are about to see, the rest of  
the code  
is written such that it only worries about a single upcard. That  
simplified the  
code, but it doesn’t let us see all of the results at once. To fix  
that, Sander  
just calls his own program once for each upcard, when one isn’t  
provided. It’s  
a recursive process call, so to speak.

The downside here is that this code didn’t run for me as written. I  
call my  
ruby 1.9 install ruby\_dev, so the hardcoded name bit me. A more  
portable way to  
get the name would be:

unless ARGV[0]  
require “rbconfig”  
(2…11).each{|n|  
puts `#{Config::CONFIG["ruby_install_name"]} #{ __FILE__ } #{n}`  
}  
exit  
end

Let’s more on. Here’s the code that reads the upcard and number of  
decks to  
use:

# …

puts “upcard: #{upcard = ARGV[0].to\_i}”  
NDECKS = (ARGV[1]||2).to\_i  
CARDS = (((2…11).to\_a+[10]\*3)_4_NDECKS).tap{|c|  
c.delete\_at c.index(upcard)  
}

# …

We know we have an upcard if we made it this far, since the code before  
calls us  
with one when the user doesn’t pass one in. That makes it safe to read  
in at  
this point. The number of decks is also read if available, or the  
default of  
two is assigned when it’s not.

The last bit of this code builds a full deck. It does that by  
generating the  
values 2 through 11, adding on three additional 10 values for the face  
cards,  
multiplying the whole set by 4 for the suits, and multiplying that  
standard deck  
by the count of decks we want to play with.

This gives us the number of requested full decks, but we need to remove  
the  
already used upcard. That’s what the tap() call does and it’s new in  
Ruby 1.9.  
delete\_at() returns the element deleted, but we need the resulting deck  
back  
instead, to store it for future use. That’s what tap() is for. You can  
tap()  
into a chain of calls to run some code, but you still get the object  
itself back  
as the result of the tap() call.

We’re now ready to run the simulation:

# …

score\_count = [0]\*27  
cards = []  
N=(ARGV[2]||100\_000).to\_i  
N.times{  
cards = CARDS.dup.shuffle if cards.size \< 17  
dealer = [upcard]  
dealer \<\< cards.pop while dealer.score \< 17  
score\_count[dealer.score] += 1  
}

# …

Believe it or not, that’s the entire simulation code.

It begins by creating an Array to hold the tally of scores, indexed by  
hand  
total. It’s interesting to look at the size of this Array, initialized  
to  
twenty seven 0’s. That makes the highest Array index twenty six, which  
is the  
largest hand a dealer will ever bust with. That is, they can draw a  
face card  
when hitting a sixteen (a ace would count as one in this case). Indices  
zero  
through sixteen won’t be touched, since a dealer won’t stop on these  
totals.

The rest of this code creates an Array to hold the cards left, and  
decides how  
many rounds to run the simulation for based on user input or a default.  
The  
simulation loop then starts, doing just four things: deal and shuffle()  
(added  
in Ruby 1.9) a fresh deck if we may not have enough cards to deal  
another hand  
(seventeen aces at minimum), initialize a hand with the upcard, deal  
additional  
cards until we crest a total of seventeen, and record the final hand  
total.

The good point about this code is that it reuses the deck for as long as  
it can.  
This simulation gets fairly accurate results by trying many possible  
hands with  
each trip through the deck.

One downside is that it doesn’t distinguish between a total of twenty  
one and a  
natural. You could fix this by dedicating some slot in the score Array  
for that  
(or switching to a score Hash of Hash.new(0)) and adding a check for  
this  
special hand after the hitting code.

With the results tallied, printing is all that remains:

# …

puts %w[17 18 19 20 21 bust].join(’ ')  
puts (score\_count[17…21] \<\< score\_count[22…-1].inject(:+)).  
map{|x| '%-4.1f%% ’ % (100.0\*x / N )}.join

The only real point of interest in this code is another 1.9 idiom.  
inject() has  
be enhanced so that it can now take a Symbol argument for the method to  
call in  
the block. That greatly simplifies the common summing case, as we see  
here.

Again the other solutions and approaches to this problem were all  
interesting.  
Do take the time to look through them.

My thanks to all the card players that decided to ante up for this quiz.

Tomorrow we will continue the Blackjack theme…
