Account Options

  1. Sign in
The old Google Groups will be going away soon, but your browser is incompatible with the new version.
Google Groups Home
« Groups Home
Studying Blackjack (#151)
There are currently too many topics in this group that display first. To make this topic appear first, remove this option from another topic.
There was an error processing your request. Please try again.
flag
  Messages 26 - 44 of 44 - Collapse all  -  Translate all to Translated (View all originals) < Older 
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Chris Lowis  
View profile  
 More options Jan 6 2008, 8:53 am
Newsgroups: comp.lang.ruby
From: Chris Lowis <chris.lo...@gmail.com>
Date: Sun, 6 Jan 2008 05:53:51 -0800 (PST)
Local: Sun, Jan 6 2008 8:53 am
Subject: Re: Studying Blackjack (#151)
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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
tho_mica_l  
View profile  
 More options Jan 6 2008, 10:32 am
Newsgroups: comp.lang.ruby
From: tho_mica_l <micat...@gmail.com>
Date: Sun, 6 Jan 2008 07:32:38 -0800 (PST)
Local: Sun, Jan 6 2008 10:32 am
Subject: Re: Studying Blackjack (#151)
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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Sander Land  
View profile  
 More options Jan 6 2008, 10:42 am
Newsgroups: comp.lang.ruby
From: Sander Land <sander.l...@gmail.com>
Date: Sun, 6 Jan 2008 10:42:47 -0500
Local: Sun, Jan 6 2008 10:42 am
Subject: Re: [QUIZ] Studying Blackjack (#151)
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

 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Eric I.  
View profile  
 More options Jan 6 2008, 11:55 am
Newsgroups: comp.lang.ruby
From: "Eric I." <rubytrain...@gmail.com>
Date: Sun, 6 Jan 2008 08:55:54 -0800 (PST)
Local: Sun, Jan 6 2008 11:55 am
Subject: Re: Studying Blackjack (#151)
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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Pawel Radecki  
View profile  
 More options Jan 6 2008, 1:25 pm
Newsgroups: comp.lang.ruby
From: Pawel Radecki <pawel.j.rade...@gmail.com>
Date: Sun, 6 Jan 2008 13:25:41 -0500
Local: Sun, Jan 6 2008 1:25 pm
Subject: Re: [QUIZ] Studying Blackjack (#151)
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 (pawel.j.rade...@gmail.com).

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

--
Pawel Radecki
m: +48 695 34-64-76
e: pawel.j.rade...@gmail.com
w: http://radeckimarch.blogspot.com/


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Gray  
View profile  
 More options Jan 6 2008, 1:58 pm
Newsgroups: comp.lang.ruby
From: James Gray <ja...@grayproductions.net>
Date: Sun, 6 Jan 2008 13:58:23 -0500
Local: Sun, Jan 6 2008 1:58 pm
Subject: Re: Studying Blackjack (#151)
On Jan 6, 2008, at 7:40 AM, Paul Novak wrote:

> On Jan 5, 7:38 pm, James Gray <ja...@grayproductions.net> wrote:
>> 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.

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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Eric I.  
View profile  
 More options Jan 6 2008, 3:30 pm
Newsgroups: comp.lang.ruby
From: "Eric I." <rubytrain...@gmail.com>
Date: Sun, 6 Jan 2008 12:30:25 -0800 (PST)
Local: Sun, Jan 6 2008 3:30 pm
Subject: Re: Studying Blackjack (#151)
On Jan 6, 12:02 am, "Eric I." <rubytrain...@gmail.com> wrote:

> 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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Denis Hennessy  
View profile  
 More options Jan 6 2008, 4:03 pm
Newsgroups: comp.lang.ruby
From: Denis Hennessy <de...@hennessynet.com>
Date: Sun, 6 Jan 2008 16:03:38 -0500
Local: Sun, Jan 6 2008 4:03 pm
Subject: Re: Studying Blackjack (#151)
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

On 6 Jan 2008, at 20:34, Eric I. wrote:


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Chris Lowis  
View profile  
 More options Jan 6 2008, 5:36 pm
Newsgroups: comp.lang.ruby
From: Chris Lowis <chris.lo...@gmail.com>
Date: Sun, 6 Jan 2008 14:36:56 -0800 (PST)
Local: Sun, Jan 6 2008 5:36 pm
Subject: Re: Studying Blackjack (#151)

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

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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Gray  
View profile  
 More options Jan 6 2008, 5:40 pm
Newsgroups: comp.lang.ruby
From: James Gray <ja...@grayproductions.net>
Date: Sun, 6 Jan 2008 17:40:02 -0500
Local: Sun, Jan 6 2008 5:40 pm
Subject: Re: Studying Blackjack (#151)
On Jan 6, 2008, at 3:03 PM, Denis Hennessy wrote:

> 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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Denis Hennessy  
View profile  
 More options Jan 6 2008, 6:54 pm
Newsgroups: comp.lang.ruby
From: Denis Hennessy <de...@hennessynet.com>
Date: Sun, 6 Jan 2008 18:54:04 -0500
Local: Sun, Jan 6 2008 6:54 pm
Subject: Re: Studying Blackjack (#151)
On 6 Jan 2008, at 22:40, James Gray wrote:

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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Denis Hennessy  
View profile  
 More options Jan 6 2008, 7:07 pm
Newsgroups: comp.lang.ruby
From: Denis Hennessy <de...@hennessynet.com>
Date: Sun, 6 Jan 2008 19:07:32 -0500
Local: Sun, Jan 6 2008 7:07 pm
Subject: Re: Studying Blackjack (#151)
On 6 Jan 2008, at 23:54, Denis Hennessy wrote:

.. original function ...

> end

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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Eric I.  
View profile  
 More options Jan 6 2008, 10:03 pm
Newsgroups: comp.lang.ruby
From: "Eric I." <rubytrain...@gmail.com>
Date: Sun, 6 Jan 2008 19:03:49 -0800 (PST)
Local: Sun, Jan 6 2008 10:03 pm
Subject: Re: Studying Blackjack (#151)
On Jan 6, 7:07 pm, Denis Hennessy <de...@hennessynet.com> wrote:

> 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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Koppel  
View profile  
 More options Jan 6 2008, 10:37 pm
Newsgroups: comp.lang.ruby
From: James Koppel <jamesbkop...@yahoo.com>
Date: Sun, 6 Jan 2008 22:37:34 -0500
Local: Sun, Jan 6 2008 10:37 pm
Subject: Re: [QUIZ] Studying Blackjack (#151)
My solution works by expanding a nested "probability hash." Each card is a key in this probability hash which contains another probability hash equivalent to what would happen if the dealer were to next gain that card. For example, the information on the situation in which the dealer starts with a 2 up and an ace down and then hits a jack would be contained in $probs[2][:A][:J]. In addition, each hash contains the following:

1)The hand of the dealer . E.g.: $probs[1][:A][:K][:hand]==[1,:A,:K]
2)The remaining deck, as represented by a card=>number of instances of that card remaining hash.
3)The probability of this situation occurring. This is easily calculated as the probability of the parent situation occurring times the probability of a the new card being drawn.

The program starts with the base case of full decks, empty hand, and probability of 1, and then expands down the sub-situations down to the point of 0-probability, bust, or dealer's hand being above 17. The probabilities of all the sub-situations of an upcard are then summed and outputted.

Interestingly, while most of my table is within rounding error of Dennis's, the results for the aces are remarkably different.

        17      18      19      20      21    BUST
 2  13.94%  13.33%  13.07%  12.40%  11.93%  35.33%
 3  13.28%  13.07%  12.46%  12.18%  11.54%  37.48%
 4  13.07%  12.02%  12.10%  11.64%  11.31%  39.85%
 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%
 A  12.85%  13.09%  13.02%  13.12%  36.34%  11.59%

Here's my program:

CARDS = (2..10).inject({}){|h,n|h[n]=n;h}.merge(
  {:J=>10,
    :Q=>10,
    :K=>10,
    :A=>nil})

MAX_HAND = 21
HIT_THRESHOLD = 17
NUM_DECKS = 2
HIGH_ACE = 11
LOW_ACE = 1
CARD_REPS = 4

class Array
  def count(obj)
    select{|el|el==obj}.size
  end

  def sum
    inject(0){|s,n|s+n}
  end
end

def sum_hand(hand)
  sum = hand.map{|c|CARDS[c]}.compact.sum
  sum += hand.count(:A)*HIGH_ACE
  hand.count(:A).times{sum -= (HIGH_ACE-LOW_ACE) if sum > MAX_HAND}
  sum <= MAX_HAND ? sum : nil
end

def expand_prob_hash(prob_hash)
  return if 0 == prob_hash[:prob] or nil == sum_hand(prob_hash[:hand]) or
      HIT_THRESHOLD<=sum_hand(prob_hash[:hand])
  CARDS.keys.each do |c|
    mod_deck = prob_hash[:deck].clone
    mod_deck[c] -= 1
    prob_hash[c] = {:hand=>prob_hash[:hand]+[c],
                              :deck=>mod_deck,
                              :prob=>prob_hash[:prob]*
                                  prob_hash[:deck][c]/prob_hash[:deck].values.sum}
    expand_prob_hash(prob_hash[c])
  end
end

def sum_probs(prob_hash)
  probs=((HIT_THRESHOLD..MAX_HAND).to_a+[nil]).inject({}){|h,n|h[n]=0.0;h}
  if prob_hash.has_key? :A
    CARDS.keys.each do |c|
      prob_part = sum_probs(prob_hash[c])
      prob_part.each_pair {|k,v| probs[k] += v}
    end
  elsif 0 == prob_hash[:prob]
    #do nothing
  else
      probs[sum_hand(prob_hash[:hand])] += prob_hash[:prob]
  end
  probs
end

$probs = {:hand=>[],
            :deck=>CARDS.keys.inject({}){|h,c|h[c]=CARD_REPS*NUM_DECKS;h},
            :prob=>1.0}
expand_prob_hash($probs)

puts "  "+((HIT_THRESHOLD..MAX_HAND).to_a+["BUST"]).map{|el|
  "%8s"%[el]}.join
[2,3,4,5,6,7,8,9,10,:J,:Q,:K,:A].each do |c|
  p = sum_probs($probs[c])
  printf "%2s ",c
  ((HIT_THRESHOLD..MAX_HAND).to_a+[nil]).each{|n|printf "%6.2f%% ",p[n]*100*CARDS.size}
  puts
end


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Eric I.  
View profile  
 More options Jan 7 2008, 12:09 pm
Newsgroups: comp.lang.ruby
From: "Eric I." <rubytrain...@gmail.com>
Date: Mon, 7 Jan 2008 09:09:16 -0800 (PST)
Local: Mon, Jan 7 2008 12:09 pm
Subject: Re: Studying Blackjack (#151)
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'

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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Gray  
View profile  
 More options Jan 9 2008, 3:10 pm
Newsgroups: comp.lang.ruby
From: James Gray <ja...@grayproductions.net>
Date: Wed, 9 Jan 2008 15:10:00 -0500
Subject: Re: [QUIZ] Studying Blackjack (#151)
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

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

Clever trick.  I like that.

James Edward Gray II


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Sander Land  
View profile  
 More options Jan 9 2008, 6:52 pm
Newsgroups: comp.lang.ruby
From: Sander Land <sander.l...@gmail.com>
Date: Wed, 9 Jan 2008 18:52:01 -0500
Local: Wed, Jan 9 2008 6:52 pm
Subject: Re: [QUIZ] Studying Blackjack (#151)
On Jan 9, 2008 9:10 PM, James Gray <ja...@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

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%


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Gray  
View profile  
 More options Jan 9 2008, 10:36 pm
Newsgroups: comp.lang.ruby
From: James Gray <ja...@grayproductions.net>
Date: Wed, 9 Jan 2008 22:36:54 -0500
Local: Wed, Jan 9 2008 10:36 pm
Subject: Re: [QUIZ] Studying Blackjack (#151)
On Jan 9, 2008, at 5:52 PM, Sander Land wrote:

Neat.  Just know that I wrote tomorrow's summary before you did  
this.  ;)

James Edward Gray II


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Ruby Quiz  
View profile  
 More options Jan 10 2008, 12:18 pm
Newsgroups: comp.lang.ruby
From: Ruby Quiz <ja...@grayproductions.net>
Date: Thu, 10 Jan 2008 12:18:47 -0500
Local: Thurs, Jan 10 2008 12:18 pm
Subject: [SUMMARY] Studying Blackjack (#151)
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 Lowis'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 Land'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..


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
End of messages < Older 
« Back to Discussions « Newer topic     Older topic »