[QUIZ] Studying Blackjack (#151)

J

James Gray

In the old-way players could gather information as cards
were removed from play, of course it's quite a skill to remember the
information and be able to mentally keep track of the effect on the
odds.

This isn't really true, though it's a myth both Hollywood and the =20
casinos try hard to keep up. There are numerous card counting systems =20=

in common use today and, while they vary in difficulty level, several =20=

easy systems are something most people can learn in a matter of hours =20=

(though of course you'll need practice to play it proficiently in a =20
casino).

I actually had the idea for this quiz while learning one of the easier =20=

card counting systems. If you can add and subtract one to a single =20
running total and memorize less than 20 rules, you can learn the =20
system. Maybe I'll make that next week's quiz=85 :)

James Edward Gray II
 
E

Eric I.

After going the randomized simulations route, I was inspired by the
precise results you achieved by looking at all permutations. So I
decided to try using that technique myself. Here's what I got:


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%


Although very close, my results do differ slightly from yours. For
example you determine that when an ace is the upcard, there's a 36.07%
chance of getting 21 exactly. I get 36.35% (31.07% natural + 5.28%
"unnatural"). On the other hand we both get a 21.32% chance of
busting when the upcard is a 10 or facecard. It'll be interesting to
see why that's the case when we post our code solutions.

Eric

====

Are you interested in on-site Ruby training that's been highly
reviewed by former students? http://LearnRuby.com
 
T

tho_mica_l

Although very close, my results do differ slightly from yours. [...]
It'll be interesting to see why that's the case when we post our code solutions.

When actually calculating permutations it could also be interesting
to
have the absolute numbers. Eg how many outcomes are possible when the
upcard
is an Ace? How many lead to a score of 17 etc. IMHO percentages are
more
interesting in the context of a simulation.
 
P

Paul Novak

On Jan 5, 2008, at 4:02 PM, Rick DeNatale wrote:
I actually had the idea for this quiz while learning one of the easier
card counting systems. If you can add and subtract one to a single
running total and memorize less than 20 rules, you can learn the
system. Maybe I'll make that next week's quiz... :)

James Edward Gray II

A card-counting-system evaluator would be interesting as a follow-on
to this weeks quiz. If you were considering different systems, you
would want to know which ones worked better (or at all) and how much
additional advantage you might gain for learning a more complicated
system, etc.

Regards,

Paul
 
D

Denis Hennessy

Here's my solution, with a sample run.

Comments, corrections, criticism welcome.
/dh

$ ./blackjack.rb
Odds for each dealer outcome based on initial upcard (2 deck game)
17 18 19 20 21 BUST
A 12.58% 12.82% 12.75% 12.85% 36.07% 12.93%
2 13.93% 13.33% 13.07% 12.39% 11.92% 35.36%
3 13.27% 13.06% 12.45% 12.18% 11.53% 37.50%
4 13.07% 12.02% 12.10% 11.63% 11.31% 39.88%
5 12.10% 12.28% 11.73% 10.90% 10.73% 42.25%
6 16.62% 10.62% 10.67% 10.12% 9.75% 42.21%
7 37.05% 13.82% 7.80% 7.88% 7.34% 26.11%
8 12.97% 36.12% 12.90% 6.89% 6.96% 24.16%
9 12.09% 11.20% 35.41% 12.11% 6.10% 23.09%
10 11.29% 11.22% 11.30% 33.56% 11.31% 21.32%
J 11.29% 11.22% 11.30% 33.56% 11.31% 21.32%
Q 11.29% 11.22% 11.30% 33.56% 11.31% 21.32%
K 11.29% 11.22% 11.30% 33.56% 11.31% 21.32%

$ cat blackjack.rb
#!/usr/bin/env ruby

CARDS = %w(A 2 3 4 5 6 7 8 9 10 J Q K)
DECKS = ARGV.size == 1 ? ARGV[0].to_i : 2
SUITS = 4

def hand_value(hand)
value = 0
# First calculate values ignoring aces
hand.each do |c|
if c=='A'
next
elsif 'JQK'.include? c
value += 10
else
value += c.to_i
end
end
# Then add aces as 11 unless they would bust the hand
hand.each do |c|
if c=='A'
if value>10
value += 1
else
value += 11
end
end
end
value
end

def new_shute
cards = []
CARDS.each do |c|
DECKS.times { SUITS.times { cards << c }}
end
cards
end

def odds_of(cards, v)
count = 0
cards.each { |c| count += 1 if c==v }
(1.0 * count) / cards.length
end

# calc the odds of reaching result from a given hand
def calc_odds(hand, result)
current = hand_value(hand)
return 1.0 if current == result
return 0.0 if current >= 17

# Remove hand cards from full shute
cards = new_shute
hand.each {|c| cards.delete_at(cards.index(c))}

odds = 0.0
CARDS.each do |c|
odds_of_card = odds_of(cards, c)
if odds_of_card > 0.0
hand.push c
odds_of_result = calc_odds(hand, result)
odds += odds_of_card * odds_of_result
hand.pop
end
end

return odds
end

puts "Odds for each dealer outcome based on initial upcard (#{DECKS}
deck game)"

puts " 17 18 19 20 21 BUST"
CARDS.each do |c|
odds = {}
bust = 100.0
(17..21).each do |r|
odds[r] = calc_odds([c], r) * 100.0
bust -= odds[r]
end
printf "%2s %5.02f%% %5.02f%% %5.02f%% %5.02f%% %5.02f%% %5.02f%%
\n", c, odds[17], odds[18], odds[19], odds[20], odds[21], bust
end
 
C

Chris Lowis

Here's my solution. I'm particularly interested in comments on my
initialize method in the Dealer class. Is there a better way to make
the deck object available to the methods in this class ?

$ ruby quiz_151.rb
Upcard Bust 17 18 19 20 21 Natural
c2 34.90% 14.20% 13.38% 13.30% 12.16% 12.06% 0.00%
c3 37.30% 13.68% 12.68% 13.34% 11.56% 11.44% 0.00%
c4 39.88% 14.22% 11.74% 11.82% 11.08% 11.26% 0.00%
c5 41.88% 11.46% 11.94% 12.08% 11.86% 10.78% 0.00%
c6 42.30% 16.54% 11.06% 10.64% 10.00% 9.46% 0.00%
c7 26.82% 35.92% 13.12% 8.70% 7.94% 7.50% 0.00%
c8 23.84% 13.30% 35.72% 13.14% 6.60% 7.40% 0.00%
c9 22.94% 11.68% 12.46% 34.48% 12.24% 6.20% 0.00%
ct 21.32% 11.12% 11.56% 11.18% 34.06% 3.48% 7.28%
cj 20.98% 11.18% 11.28% 10.60% 34.14% 3.82% 8.00%
cq 21.40% 11.24% 11.34% 10.72% 34.36% 3.48% 7.46%
ck 20.54% 10.36% 10.38% 11.32% 35.72% 3.64% 8.04%
ca 13.08% 13.04% 12.96% 13.56% 11.92% 5.48% 29.96%

$ cat quiz_151.rb
#!/usr/bin/env ruby -w

class Deck
def initialize(number_of_decks)
@cards = []

suits = ["h","c","d","s"]
values = [2,3,4,5,6,7,8,9,"t","j","q","k","a"]

number_of_decks.times do
suits.each do |suit|
values.each do |value|
@cards << suit + value.to_s
end
end
end
shuffle
end

def shuffle
@cards = @cards.sort_by {rand}
end

def deal
@cards.pop
end

def deal_a(card)
# Deal a named card from the deck
@cards.delete_at(@cards.index(card))
end
end

class Dealer

def initialize(deck,upcard)
@hand = []
@score = 0
@hand << deck.deal_a(upcard)
@hand << deck.deal
@deck = deck
end

def bust?
current_score > 21
end

def natural?
current_score == 21 && @hand.length == 2
end

def current_score

# To deal with multiple aces, sort the current hand so that the
# aces appear as the last elements in the array.
values = []
@hand.each {|card| values << card[1].chr}
not_aces = values.find_all {|v| /[^a]/=~v}
aces = values.find_all {|v| /[a]/=~v}

values = not_aces + aces

# Calculate the score for this hand
score = 0
values.each do |value|
if /\d/ =~ value then score += value.to_i end
if /[t,k,j,q]/ =~ value then score += 10 end
if /[a]/ =~ value then
if score + 11 > 21
score += 1
elsif
score += 11
end
end
end
score
end

def play
until self.bust? || current_score >= 17
card = @deck.deal
@hand << card
end

if self.bust?
"bust"
elsif self.natural?
"natural"
else
current_score
end
end
end

if __FILE__ == $0
upcards =
["c2","c3","c4","c5","c6","c7","c8","c9","ct","cj","cq","ck","ca"]
outcomes = ["bust",17,18,19,20,21,"natural"]

no_of_games = 5000
printf("Upcard\tBust\t17\t18\t19\t20\t21\tNatural\n")
upcards.each do |upcard|
results = []
no_of_games.times {results << Dealer.new(Deck.new(8),upcard).play}

p = []
outcomes.each do |outcome|
number = results.find_all {|r| r==outcome}
p << (number.length.to_f/no_of_games)*100
end

printf("%s\t%5.2f%%\t%5.2f%%\t%5.2f%%\t%5.2f%%\t%5.2f%%\t%5.2f%%\t
%5.2f%%\n",
upcard,p[0],p[1],p[2],p[3],p[4],p[5],p[6])
end
end
 
T

tho_mica_l

Here is my solution. I originally tried to run through all solutions
but
then adopted the simulation strategy already mentioned by others
before.

If sample size is 55000 (default is 10000), this script runs about as
long (with ruby 1.8) as Denis Hennessy's solution (which uses much
less
memory though). Also, I get constantly less bust counts for an Ace as
upcard which makes me wonder if I did something wrong here. The other
figures appear about the same.

Sample results:

$ ruby quiz151b.rb 55000
bust natural 17 18 19 20 21
A: 11.62% 31.09% 12.65% 13.09% 12.88% 13.33% 5.35%
2: 35.43% 0.00% 13.84% 13.45% 12.88% 12.35% 12.05%
3: 37.39% 0.00% 13.56% 12.85% 12.73% 12.03% 11.43%
4: 40.08% 0.00% 12.85% 11.97% 12.11% 11.72% 11.28%
5: 42.21% 0.00% 12.25% 12.19% 12.02% 10.85% 10.48%
6: 41.83% 0.00% 16.71% 10.58% 10.74% 10.45% 9.68%
7: 26.29% 0.00% 36.96% 13.76% 7.76% 7.90% 7.32%
8: 24.48% 0.00% 13.05% 35.85% 12.92% 6.63% 7.06%
9: 23.39% 0.00% 11.97% 11.10% 35.50% 11.94% 6.11%
10: 21.21% 7.85% 11.01% 11.12% 11.52% 33.77% 3.52%
B: 21.15% 7.71% 11.26% 11.11% 11.50% 33.77% 3.49%
D: 21.36% 7.73% 11.25% 11.08% 11.32% 33.74% 3.52%
K: 21.65% 7.77% 11.47% 11.28% 11.17% 33.16% 3.51%


Regards,
Thomas.



#!/usr/bin/env ruby
# Author:: Thomas Link (micathom AT gmail com)
# Created:: 2008-01-05.

class Quiz151b
LABELS = ['bust', 'natural', *(17..21).to_a]
NAMES = ['A', *(2..10).to_a] << 'B' << 'D' << 'K'
CARDS = (1..10).to_a + [10] * 3

class << self
def run(sample=10000, decks=2)
puts ' ' + LABELS.map {|k| '%-7s' % k}.join(' ')
13.times do |upcard|
puts Quiz151b.new(upcard, decks).run(sample)
end
end
end

def initialize(upcard, decks)
@upcard = upcard
@cards = CARDS * (4 * decks)
@hands = []
end

def run(sample)
sample.times {@hands << deal(@upcard)}
self
end

def to_s
total = @hands.size
acc = Hash.new(0)
@hands.each do |sum, hand|
label = sum > 21 ? 'bust' :
sum == 21 && hand.size == 2 ? 'natural' :
sum
acc[label] += 1
end
'%02s: %s' % [
NAMES[@upcard],
LABELS.map {|k| '%6.2f%%' % (100.0 * acc[k] /
total)}.join(' ')
]
end

def deal(idx)
cards = @cards.dup
hand = []
sum = 0
loop do
hand << cards.delete_at(idx)
sum = count(hand)
return [sum, hand] if sum >= 17
idx = rand(cards.size)
end
end

def count(hand)
sum = 0
tidx = 21 - hand.size - 10
hand.dup.sort.reverse.each_with_index do |c, i|
sum += c == 1 && sum <= tidx + i ? 11 : c
end
return sum
end

end


if __FILE__ == $0
case ARGV[0]
when '-h', '--help'
puts "#$0 [DEALS=10000] [DECKS=2]"
else
Quiz151b.run(*ARGV.map {|e| e.to_i})
end
end
 
S

Sander Land

Here is my solution. It's just another simulation.
Ruby 1.9 only.

Usage: ruby1.9 [upcard] [number of decks] [number of games]

Pastie: http://pastie.caboo.se/135733

Code:

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

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

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)}

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
}

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
 
E

Eric I.

Here's my solution, which, like Denis Hennessy's solution, attempts to
calculate the exact results by looking at all potential, meaningful
permutations of a fresh shoe. By *meaningful* permutations, I mean
that once a result is determined (e.g., 20, bust), the order of the
remaining cards doesn't matter, and so doesn't need to be taken into
account.

The results Denis and I calculate differ slightly. I still haven't
figured out why....

Eric

----

Interested in hands-on, on-site Ruby training? See http://LearnRuby.com
for information about a well-reviewed class.

====

# A solution to RubyQuiz #151 by LearnRuby.com .
#
# For the game of casino Blackjack, determines the odds of all
# possible dealer outcomes, given a specific dealer upcard. Assumes
# the dealer is playing with a fresh shoe, w/o other players playing.
#
# See http://www.rubyquiz.com/quiz151.html for details.
#
# The latest version of this solution can also be found at
# http://learnruby.com/examples/ruby-quiz-151.shtml .


# mathn provides us with fractional (rational) results for partial
# calculations rather than floating point results, which can be
# subject to rounding errors. Rounding takes place at the point of
# final output.
require 'mathn'


# CONFIGURABLE PARAMETERS

# deck count is first command line argument or default of 2
deck_count = ARGV.size == 1 && ARGV[0].to_i || 2


# CONSTANTS

# The unique cards (10 and face cards are not distinguished).
CARDS = (2..10).to_a << :ace

# A deck is a hash keyed by the card, and the value is how many of
# that card there are. There are four of all cards except the
# 10-value cards, and there are sixteen of those.
DECK = CARDS.inject(Hash.new) { |hash, card| hash[card] = 4; hash }
DECK[10] = 16

# The possible results are 17--21 plus bust and natural. The order is
# given in a what might be considered worst to best order.
POSSIBLE_RESULTS = [:bust] + (17..21).to_a + [:natural]


# SET UP VARIABLES

# The shoe is a Hash that contains one or more decks and an embedded
# count of how many cards there are in the shoe (keyed by
# :cards_in_shoe)
shoe = DECK.inject(Hash.new) { |hash, card|
hash[card.first] = deck_count * card.last; hash }
shoe[:cards_in_shoe] =
shoe.inject(0) { |sum, card_count| sum + card_count.last }


# The results for a given upcard is a hash keyed by the result and
# with values equal to the odds that that result is acheived.
results_for_upcard =
POSSIBLE_RESULTS.inject(Hash.new) { |hash, r| hash[r] = 0; hash }

# The final results is a hash keyed by every possible upcard, and with
# a value equal to results_for_upcard.
results = CARDS.inject(Hash.new) { |hash, card|
hash[card] = results_for_upcard.dup; hash }


# METHODS


# returns the value of a hand
def value(hand)
ace_count = 0
hand_value = 0

hand.each do |card|
if card == :ace
ace_count += 1
hand_value += 11
else
hand_value += card
end
end

# flip aces from being worth 11 to being worth 1 until we get <= 21
# or we run out of aces
while hand_value > 21 && ace_count > 0
hand_value -= 10
ace_count -= 1
end

hand_value
end


# the dealer decides what to do -- stands on 17 or above, hits
# otherwise
def decide(hand)
value(hand) >= 17 && :stand || :hit
end


# computes the result of a hand, returning a numeric value, :natural,
# or :bust
def result(hand)
v = value(hand)
case v
when 21 : hand.size == 2 && :natural || 21
when 17..20 : v
when 0..16 : raise "error, illegal resulting hand value"
else :bust
end
end


# manages the consumption of a specific card from the shoe
def shoe_consume(shoe, card)
current = shoe[card]
raise "error, consuming non-existant card" if current <= 0
shoe[card] = current - 1
shoe[:cards_in_shoe] -= 1
end


# manages the replacement of a specific card back into the shoe
def shoe_replace(shoe, card)
shoe[card] += 1
shoe[:cards_in_shoe] += 1
end


# plays the dealer's hand, tracking all possible permutations and
# putting the results into the results hash
def play_dealer(hand, shoe, odds, upcard_result)
case decide(hand)
when :stand
upcard_result[result(hand)] += odds
when :hit
CARDS.each do |card|
count = shoe[card]
next if count == 0
card_odds = count / shoe[:cards_in_shoe]

hand.push(card)
shoe_consume(shoe, card)

play_dealer(hand, shoe, odds * card_odds , upcard_result)

shoe_replace(shoe, card)
hand.pop
end
else
raise "error, illegal hand action"
end
end


# MAIN PROGRAM


# calculate results

CARDS.each do |upcard|
shoe_consume(shoe, upcard)
play_dealer([upcard], shoe, 1, results[upcard])
shoe_replace(shoe, upcard)
end


# display results header

puts "Note: results are computed using a fresh %d-deck shoe.\n\n" %
deck_count

printf "upcard "
POSSIBLE_RESULTS.each do |result|
printf "%9s", result.to_s
end
puts

printf "-" * 6 + " "
POSSIBLE_RESULTS.each do |result|
print " " + "-" * 7
end
puts


# display numeric results

CARDS.each do |upcard|
printf "%6s |", upcard
POSSIBLE_RESULTS.each do |result|
printf "%8.2f%%", 100.0 * results[upcard][result]
end
puts
end
 
P

Pawel Radecki

Hi All,

Below is my solution. I used simulation strategy, too but I understand
one simulation as passing through all cards until set of decks is empty.
When ran through 1000 simulations it showed me much more chance (22%) of
getting bust while having an ace as an upcard. Remaining results differ
but not that much...

Results of three example upcards:

A:
natural -> 32%
bust -> 22%
20 -> 11%
18 -> 11%
17 -> 11%
19 -> 11%
21 -> 3%

7:
17 -> 37%
bust -> 25%
18 -> 14%
19 -> 8%
20 -> 8%
21 -> 7%
6:
bust -> 39%
17 -> 17%
19 -> 12%
18 -> 11%
20 -> 10%
21 -> 10%

Source code:

#!/usr/bin/env ruby

# Solution to Ruby Quiz #151 (see http://www.rubyquiz.com/quiz151.html)
# by Pawel Radecki ([email protected]).

COLOURS_IN_DECK = 4
SIMULATIONS_NO = 1000

class Array
def shuffle
sort_by { rand }
end
end

class Card

attr_reader :face

@@blackjack_values = { "A" => [1,11] , "K" => 10, "Q" => 10, "J" => 10,
"10" => 10, "9" => 9, "8" => 8, "7" => 7, "6" => 6, "5" =>
5, "4" => 4,
"3" => 3, "2" => 2}

@@list = ["A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4",
"3", "2" ]

def initialize(face)
if @@blackjack_values.keys.include? face
@face=face
else
raise Exception.new("Can't initialize card with face: "+face)
end
end

def blackjack_value
@@blackjack_values[@face]
end

def best_blackjack_value(score)
if (self.blackjack_value.respond_to? :pop)
if (score>10)
self.blackjack_value[0]
else
self.blackjack_value[1]
end
else
self.blackjack_value
end
end

def self.faces
@@blackjack_values.keys
end

def self.list
@@list
end

def to_s
return "#{@face}"
end

def inspect
return "#{@face}"
end
end

#one or more decks
class DeckSet

#new shuffled deck
def initialize (decks_no=2)
@cards = []

(decks_no*COLOURS_IN_DECK).times do
Card.faces.shuffle.each {|c| @cards << Card.new(c)}
end
end

def draw
@cards.pop
end

def empty?
@cards.empty?
end
end


USAGE = <<ENDUSAGE
Usage:
black_jack_dealer_chances.rb [-u <upcard>] [-d <decks_no>]
-u upcard: {#{Card.list.join(", ")}}
-d number of decks used

Calculates percentage chances of a black jack dealer reaching each
possible outcome.
Upcard may be given, number of the decks may be configured.

Example: black_jack_dealer_chances.rb -u "Q" -d 5
ENDUSAGE

if ARGV.length>4
puts USAGE
exit
end

upcard = nil
decks_no = 2

if ARGV.include?("-u")
upcard = ARGV[ARGV.index("-u")+1]
if (upcard.nil? || !Card.faces.include?(upcard))
puts USAGE
exit
end
ARGV.delete("-u")
ARGV.delete(upcard)
end

if ARGV.include?("-d")
decks_no = ARGV[ARGV.index("-d")+1]
if (decks_no.nil?)
puts USAGE
exit
end
ARGV.delete("-d")
ARGV.delete(decks_no)
end

histogram = Hash.new 0
sum = Hash.new 0
probability = []

SIMULATIONS_NO.times do
decks = DeckSet.new(decks_no.to_i)
while (!decks.empty?)
score = 0; hand = []
while score<17
hand << card=decks.draw
score+=card.best_blackjack_value(score)

if score==21 && hand.size==2
if $DEBUG
print "hand: "
p hand
print "score: "
p score
puts
end
sum[hand.first.face]+=1
histogram[[hand.first.face,"natural"]]+=1
break
elsif score>21
if $DEBUG
print "hand: "
p hand
print "score: "
p score
puts
end
sum[hand.first.face]+=1
histogram[[hand.first.face,"bust"]]+=1
break
elsif (17..21).include? score
if $DEBUG
print "hand: "
p hand
print "score: "
p score
puts
end
sum[hand.first.face]+=1
histogram[[hand.first.face,score]]+=1
break
elsif decks.empty?
break
end

end
end
end

histogram.keys.each { |el| probability <<
[el,histogram[el].to_f/sum[el.first]].flatten }
probability.sort! { |x,y| x.first != y.first ? Card.list.index(x.first)
<=> Card.list.index(y.first) : y.last <=> x.last}

card = nil
probability.each do |el|
if (upcard==nil || el.first==upcard)
if card!=el.first
card=el.first
puts "#{el.first}:"
end
printf("%8s -> %2.0f%% \n", el[1], el.last*100)
end
end

exit
 
J

James Gray

A card-counting-system evaluator would be interesting as a follow-on
to this weeks quiz. If you were considering different systems, you
would want to know which ones worked better (or at all) and how much
additional advantage you might gain for learning a more complicated
system, etc.

This has been well studied. The short story is that most of the
popular systems vary in expectations by pretty small amounts.

Given that, I personally prefer the easier systems. I'll sacrifice a
few percentage points for easier counting.

James Edward Gray II
 
E

Eric I.

Although very close, my results do differ slightly from yours.  For
example you determine that when an ace is the upcard, there's a 36.07%
chance of getting 21 exactly.  I get 36.35% (31.07% natural + 5.28%
"unnatural").  On the other hand we both get a 21.32% chance of
busting when the upcard is a 10 or facecard.  It'll be interesting to
see why that's the case when we post our code solutions.

It took me a while, but after comparing Denis' code and my own, I
figured out where the differences in results came from, and it was in
how aces are handled when valuing a hand.

I believe that Denis uses an incorrect algorithm. When valuing a
hand, he first sums up the values of all the non-aces and then makes a
second pass handling the aces. For each ace, if valuing it as 11
would *not* bust the hand, he values it at 11. Otherwise he values it
at 1.

But consider a three-card hand like ace, ace, 10. The first ace is
counted as 11 since that wouldn't bust the hand. The second ace is
counted as 1, since valuing it as 11 also would bust the hand. But
the hand still busts due to the 10. If both aces were counted as 1,
then the hand would not be a bust (so far, at least), and it would
need for another hit.

When I changed Denis' hand valuation logic on aces to my own, our
results were identical.

Eric
 
D

Denis Hennessy

Hi Eric,

That's interesting. The understanding I had was that a dealer could
're-value' their hand with each card dealt; that is - if they draw two
aces they're obviously 11 and 1 and they have to hit again. If they
then draw a card which would cause them to bust, they can treat both
aces as 1.

I don't know this for definite, except that the description I've seen
for what a dealer does did not specify what order the ace appears in.
Maybe someone knows what really happens....

/dh
 
C

Chris Lowis

But consider a three-card hand like ace, ace, 10.  The first ace is
I'm not sure if that's what I do with my algorithm, but that's what I
was trying to do,
by evaluating the score on every deal, and sorting the aces to the end
of the array to
value them. I need to compare other submissions with mine to be sure.

Chris
 
J

James Gray

The understanding I had was that a dealer could 're-value' their
hand with each card dealt; that is - if they draw two aces they're
obviously 11 and 1 and they have to hit again. If they then draw a
card which would cause them to bust, they can treat both aces as 1.

You have all of that right, but Eric was saying you don't properly
value hands with multiple aces in them. For example, ace, ace, and
ten should value as 12, not 22.

One way I've handled this in the pass was to sort the hand such that
aces are at the end, run through building up a total, and count an ace
as 11 when I passed it if doing so would keep the running count less
than or equal to 21 minus the cards left to count.

Hope that makes sense.

James Edward Gray II
 
D

Denis Hennessy

You have all of that right, but Eric was saying you don't properly
value hands with multiple aces in them. For example, ace, ace, and
ten should value as 12, not 22.

One way I've handled this in the pass was to sort the hand such that
aces are at the end, run through building up a total, and count an
ace as 11 when I passed it if doing so would keep the running count
less than or equal to 21 minus the cards left to count.

Hope that makes sense.

James Edward Gray II

Actually, I think my code is correct, but I might be snow-blind.
Here's the relevant function. It values all the non-ace cards first
and then values the aces.

def hand_value(hand)
value = 0
# First calculate values ignoring aces
hand.each do |c|
if c=='A'
next
elsif 'JQK'.include? c
value += 10
else
value += c.to_i
end
end
# Then add aces as 11 unless they would bust the hand
hand.each do |c|
if c=='A'
if value>10
value += 1
else
value += 11
end
end
end
value
end

/dh
 
D

Denis Hennessy

Actually, I think my code is correct, but I might be snow-blind.
Here's the relevant function. It values all the non-ace cards first
and then values the aces.

def hand_value(hand)

... original function ...

Gah! There was a bug - it valued aces after other cards but didn't
take into account other aces. Here's the corrected function which
works similarly to Eric's code (value aces as 11 and later re-value as
1 as needed):

def hand_value(hand)
value = 0
# First calculate values counting aces as 11
hand.each do |c|
if c=='A'
value += 11
elsif 'JQK'.include? c
value += 10
else
value += c.to_i
end
end
# Then re-value aces as 1 as long as hand is bust
hand.each do |c|
if c=='A'
if value>21
value -= 10
end
end
end
value
end
 
E

Eric I.

Gah! There was a bug - it valued aces after other cards but didn't  
take into account other aces.

Yeah, it was a pretty subtle issue. I read your code many times,
certain that it worked the same as mine. It was only because the
biggest differences in our results was when ace was the upcard that I
focused on the ace-handling code..

Thanks for going after the exact odds! I don't think I would have
tried it had you not demonstrated that it was possible.

Eric
 
E

Eric I.

# mathn provides us with fractional (rational) results for partial
# calculations rather than floating point results, which can be
# subject to rounding errors.  Rounding takes place at the point of
# final output.
require 'mathn'

Well, it looks like I over-engineered my solution. After noting that
Denis' updated solution and James Koppel's solution were providing the
same results as my solution, and that they were using floating point
math, I realized that my use of rational math to avoid rounding errors
and gain higher precision wasn't paying off.

So by making a change to one line in my play_dealer method:

< card_odds = count / shoe[:cards_in_shoe]
---
card_odds = count.to_f / shoe[:cards_in_shoe]

my output is identical, and the program is over five times faster.
I'm a big fan of the mathn library, but clearly I should be more
careful about using it.

Eric
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top