# [QUIZ] Studying Blackjack (#151)

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
if you can.

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

The majority of the strategy in Blackjack hinges around the dealer's hand. The
reasons are likely obvious to most of you: that's the hand you have to beat and
the dealer plays by fixed rules we can predict.

For those unfamiliar with Blackjack, you only need to know a tiny bit about the
game for the purposes of this exercise. The goal for both the player and the
dealer is to draw cards to make a hand with the highest total possible, without
going over 21. Going over 21 is called "busting" and it means you lose the
hand. Face cards count for ten, aces are one or eleven (whichever is better for
the hand), and all other cards count for their face value. You start with two
cards and, if they happen to be a ten valued card and an ace (a count of 21),
the hand is called a "natural." A natural is an automatic win in most cases.

The dealer begins with one of his two cards face up and one face down. We call
the former the "upcard." The dealer will "hit" or take more cards until he
reaches a count of 17 or higher. After that he will "stand" or leave the hand
where it is. That tells us that there are only seven possible outcomes for the
dealer: get dealt a natural, bust, or hit to a total of 17, 18, 19, 20, or 21.

We start every hand knowing half of what the dealer holds thanks to the upcard.
Believe it or not, you can make pretty reliable guesses about how the hand will
go with just that knowledge.

Write a Ruby program that shows the percent chance of a dealer reaching each
possible outcome based on the upcard showing.

I'll give you some hints to verify your results. Basic Blackjack strategy
teaches that we should assume the dealer "has a ten in the hole" (as the face
down card). It's not always true, of course, but 17 is a common outcome for a
dealer with an upcard of seven. Finally, we call five and six "the dealer's
bust cards" for reasons that will become obvious if you are outputting correct
percentages.

In the casinos Blackjack is often played with more than one deck shuffled
together. One, two, six, and eight deck games are common. You may want to
offer the option to adjust the deck size your program uses. Either way, let's
default to two decks as an average of what a player will face.

Write a Ruby program that shows the percent chance of a dealer reaching each
possible outcome based on the upcard showing

Are we able to post partial / full outcomes within the 48 hours?

Cheers,
Dave

Write a Ruby program that shows the percent chance of a dealer
reaching each possible outcome based on the upcard showing.

I'm sorry for asking this dummy question (and I hope not to spoil it
for
anyone) but all this jargon makes me feel strange and it's early in
the
morning. Do I understand the game right in that:

1. The dealer gets a card
2. If the sum of his cards is < 17, repeat step #1
3. If the sum is >= 17, the result is registered
4. If the dealer has 6 + Ace, he stops and doesn't try to get a 4
or
something lower?

The set of possible cards is limited to 2 decks, i.e. 2 * 13 (2..10,
B,
D, K, A) cards by default.

A set of two card decks contains two 2s?

The probabilities for 10, B, D, K should be all the same since they
all
count 10 and removing them from the card set results in the same
probability
to get another card with a value of 10 for each of them?

Here's my solution, with a sample run.

/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

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

Here is my solution. I originally tried to run through all solutions
but
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

Sample results:

\$ ruby quiz151b.rb 55000
bust natural 17 18 19 20 21
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

···

A: 11.62% 31.09% 12.65% 13.09% 12.88% 13.33% 5.35%

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

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

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

You bet. Feel free.

James Edward Gray II

···

On Jan 4, 2008, at 4:27 PM, Sharon Phillips wrote:

Write a Ruby program that shows the percent chance of a dealer reaching each
possible outcome based on the upcard showing

Are we able to post partial / full outcomes within the 48 hours?

The set of possible cards is limited to 2 decks, i.e. 2 * 13 (2..10,
B,
D, K, A) cards by default.

It's * 4 because there are 4 colours.

Write a Ruby program that shows the percent chance of a dealer
reaching each possible outcome based on the upcard showing.

I'm sorry for asking this dummy question (and I hope not to spoil it
for
anyone) but all this jargon makes me feel strange and it's early in
the
morning.

No worries. I probably didn't explain it well.

Do I understand the game right in that:

1. The dealer gets a card
2. If the sum of his cards is < 17, repeat step #1
3. If the sum is >= 17, the result is registered
4. If the dealer has 6 + Ace, he stops and doesn't try to get a 4
or
something lower?

Yes to all of the above.

The forth rule actually varies somewhat in the casinos. Some tables allow a dealer to hit on "soft 17" (a total of 17 involving an ace used as an eleven). I left that out to keep this exercise simple.

The set of possible cards is limited to 2 decks, i.e. 2 * 13 (2..10,
B,
D, K, A) cards by default.

A set of two card decks contains two 2s?

I meant to do the experiment using two full decks shuffled together. That would be a total of 104 cards and include eight twos, threes, etc.

I hope that clears things up.

James Edward Gray II

···

On Jan 5, 2008, at 2:05 AM, tho_mica_l wrote:

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

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

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)

COLOURS_IN_DECK = 4
SIMULATIONS_NO = 1000

class Array
def shuffle
sort_by { rand }
end
end

class Card

@@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

···

--
m: +48 695 34-64-76

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

That has the some bug Eric found in Dennis's code:

>> [10, 11, 11].score
=> 22

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

Clever trick. I like that.

James Edward Gray II

···

On Jan 6, 2008, at 9:42 AM, Sander Land wrote:

> Are we able to post partial / full outcomes within the 48 hours?
You bet. Feel free.

Here's mine so far, my first ruby quiz !

Upcard Bust 17 18 19 20 21 Natural
c1 26.20% 16.80% 13.00% 14.20% 13.90% 15.90% 0.00%
c2 38.80% 14.60% 11.40% 11.00% 12.30% 11.90% 0.00%
c3 40.50% 14.40% 13.40% 11.20% 12.00% 8.50% 0.00%
c4 38.00% 15.20% 14.30% 11.70% 10.70% 10.10% 0.00%
c5 42.30% 12.80% 12.30% 11.80% 9.20% 11.60% 0.00%
c6 39.10% 23.50% 10.30% 10.00% 8.80% 8.30% 0.00%
c7 25.60% 35.40% 14.00% 10.70% 6.50% 7.80% 0.00%
c8 23.70% 13.00% 33.60% 16.20% 6.20% 7.30% 0.00%
c9 21.90% 10.50% 11.90% 35.90% 12.90% 6.90% 0.00%
ct 19.40% 12.50% 10.90% 11.20% 31.70% 5.10% 9.20%
cj 19.70% 14.40% 10.60% 12.30% 29.90% 5.80% 7.30%
cq 18.90% 13.50% 10.90% 10.30% 32.20% 6.60% 7.60%
ck 19.50% 12.30% 12.80% 11.10% 31.90% 6.00% 6.40%
ca 14.00% 13.40% 14.40% 13.10% 12.60% 4.10% 28.40%

Seems to satisfy the hints given in the quiz, but I'm still not sure
if I deal with the case where the dealer gets dealt two or more aces
correctly.

Chris

Here's my results so far. I don't completely trust them yet since I'm not sure I understand the blackjack rules properly (I'm assuming the dealer has to use an ace as 11 unless they bust).

/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%

···

On 4 Jan 2008, at 22:31, James Gray wrote:

On Jan 4, 2008, at 4:27 PM, Sharon Phillips wrote:

Write a Ruby program that shows the percent chance of a dealer reaching each
possible outcome based on the upcard showing

Are we able to post partial / full outcomes within the 48 hours?

You bet. Feel free.

James Edward Gray II

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]

···

On Jan 6, 11:55 am, "Eric I." <rubytrain...@gmail.com> wrote:

# 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'

---

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

Eric

Thanks. I should have thought of this considering Dennis' results matched mine.
Here's my corrected solution. I renamed the ace to "1" for a more
elegant solution.

class Array
def score
(s=inject(:+)) <= 11 && index(1) ? s+10 : s
end
end

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

puts "upcard: #{upcard = ARGV[0].to_i}"
NDECKS = (ARGV[1]||2).to_i
CARDS = (((1..10).to_a+[10]*3)*4*NDECKS).tap{|c| c.delete_at c.index(upcard)}

score_count = [0]*27
cards = []
N=(ARGV[2]||1_000_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

Results match Eric's results now.

upcard: 1
17 18 19 20 21 bust
12.8% 13.1% 13.0% 13.1% 36.3% 11.6%
upcard: 2
17 18 19 20 21 bust
14.0% 13.4% 13.1% 12.3% 11.9% 35.3%
upcard: 3
17 18 19 20 21 bust
13.3% 13.2% 12.5% 12.2% 11.5% 37.3%
upcard: 4
17 18 19 20 21 bust
13.1% 12.1% 12.1% 11.6% 11.3% 39.8%
upcard: 5
17 18 19 20 21 bust
12.2% 12.3% 11.7% 10.8% 10.7% 42.2%
upcard: 6
17 18 19 20 21 bust
16.6% 10.7% 10.7% 10.1% 9.8 % 42.1%
upcard: 7
17 18 19 20 21 bust
36.9% 13.9% 7.8 % 7.9 % 7.4 % 26.0%
upcard: 8
17 18 19 20 21 bust
12.9% 36.0% 12.9% 6.9 % 7.0 % 24.3%
upcard: 9
17 18 19 20 21 bust
12.1% 11.2% 35.3% 12.1% 6.1 % 23.1%
upcard: 10
17 18 19 20 21 bust
11.2% 11.2% 11.3% 33.6% 11.3% 21.3%

···

On Jan 9, 2008 9:10 PM, James Gray <james@grayproductions.net> wrote:

On Jan 6, 2008, at 9:42 AM, Sander Land wrote:

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

That has the some bug Eric found in Dennis's code:

>> [10, 11, 11].score
=> 22

Here are my results. I combined the 10 and the face cards in one row
since they're essentially equivalent. And the data was generated by
doing 10,000 deals per upcard.

upcard bust 17 18 19 20 21
natural
------ ------- ------- ------- ------- ------- -------

···

-------
2 | 35.95% 13.32% 13.17% 13.34% 12.44% 11.78%
0.00%
3 | 37.85% 13.41% 12.93% 11.69% 12.51% 11.61%
0.00%
4 | 40.14% 13.24% 11.86% 12.03% 11.48% 11.25%
0.00%
5 | 41.80% 12.82% 12.32% 11.41% 11.34% 10.31%
0.00%
6 | 41.76% 17.65% 10.59% 10.36% 10.04% 9.60%
0.00%
7 | 26.77% 36.23% 13.53% 8.29% 7.88% 7.30%
0.00%
8 | 24.96% 12.84% 35.69% 12.73% 7.04% 6.74%
0.00%
9 | 22.98% 12.27% 10.69% 35.47% 12.22% 6.37%
0.00%
10 | 21.61% 11.21% 11.68% 11.23% 32.97% 3.55%
7.75%
ace | 11.60% 12.33% 13.03% 13.06% 13.43% 5.27%
31.28%

I do wonder, though, whether the deck is biased somehow when the
dealer deals him/herself since all the players are dealt beforehand.
For example, it seems a player is more likely to consume a sequence of
low cards through consecutive hits than a sequence of high cards. So
depending on the number of players at the table, the cards the dealer
is likely to get may change somewhat. And perhaps the dealer's odds
change further the deeper into the shoe the table gets. Does anyone
know?

Eric

====

Are you interested in on-site Ruby training that uses well-designed,
real-world, hands-on exercises? http://LearnRuby.com

Are we able to post partial / full outcomes within the 48 hours?

You bet. Feel free.

Here's mine so far, my first ruby quiz !

Welcome to the quiz. Glad to have you.

…but I'm still not sure if I deal with the case where the dealer gets dealt two or more aces correctly.

If a dealer has two aces, the first one would be 11 and the second one a 1 for a total of twelve. Any aces added into the mix would be another one. The original aces would drop to a one if keeping it at 11 takes the dealer over 21.

James Edward Gray II

···

On Jan 4, 2008, at 6:35 PM, Chris wrote:

Hi Chris,

You have an extra card in your hand (a '1' as well as an ace).

/dh

···

On 5 Jan 2008, at 00:35, Chris wrote:

Are we able to post partial / full outcomes within the 48 hours?

You bet. Feel free.

Here's mine so far, my first ruby quiz !

Upcard Bust 17 18 19 20 21 Natural
c1 26.20% 16.80% 13.00% 14.20% 13.90% 15.90% 0.00%
c2 38.80% 14.60% 11.40% 11.00% 12.30% 11.90% 0.00%
c3 40.50% 14.40% 13.40% 11.20% 12.00% 8.50% 0.00%
c4 38.00% 15.20% 14.30% 11.70% 10.70% 10.10% 0.00%
c5 42.30% 12.80% 12.30% 11.80% 9.20% 11.60% 0.00%
c6 39.10% 23.50% 10.30% 10.00% 8.80% 8.30% 0.00%
c7 25.60% 35.40% 14.00% 10.70% 6.50% 7.80% 0.00%
c8 23.70% 13.00% 33.60% 16.20% 6.20% 7.30% 0.00%
c9 21.90% 10.50% 11.90% 35.90% 12.90% 6.90% 0.00%
ct 19.40% 12.50% 10.90% 11.20% 31.70% 5.10% 9.20%
cj 19.70% 14.40% 10.60% 12.30% 29.90% 5.80% 7.30%
cq 18.90% 13.50% 10.90% 10.30% 32.20% 6.60% 7.60%
ck 19.50% 12.30% 12.80% 11.10% 31.90% 6.00% 6.40%
ca 14.00% 13.40% 14.40% 13.10% 12.60% 4.10% 28.40%

Seems to satisfy the hints given in the quiz, but I'm still not sure
if I deal with the case where the dealer gets dealt two or more aces
correctly.

Chris