------art_3010_26146912.1199835152196
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Hi, here goes my solution to this weeks Ruby Quiz, I was interested in
exact values so I explored the whole game tree of the dealer.
I also allowed to remove visible cards from the deck, here are some numbers:
2 decks, all cards
608/109 > ruby sol_r_dober-1.0.rb 2
Card    17         18         19         20         21       NATURAL     BUST
  2  13.9367%   13.3348%   13.0743%   12.3997%   11.9254%    0.0000%   35.3291%
  3  13.2764%   13.0676%   12.4575%   12.1811%   11.5380%    0.0000%   37.4794%
  4  13.0704%   12.0208%   12.1038%   11.6366%   11.3145%    0.0000%   39.8540%
  5  12.1005%   12.2831%   11.7318%   10.9022%   10.7320%    0.0000%   42.2505%
  6  16.6224%   10.6170%   10.6749%   10.1217%    9.7508%    0.0000%   42.2132%
  7  37.0478%   13.8195%    7.8013%    7.8781%    7.3390%    0.0000%   26.1143%
  8  12.9697%   36.1182%   12.9025%    6.8857%    6.9610%    0.0000%   24.1630%
  9  12.0940%   11.2016%   35.4051%   12.1118%    6.0977%    0.0000%   23.0898%
  H  11.2904%   11.2156%   11.3008%   33.5608%    3.5463%    7.7670%   21.3191%
  A  12.8467%   13.0890%   13.0179%   13.1159%    5.2754%   31.0680%   11.5872%

3 decks no Aces left, (thus no naturals)
Card    17         18         19         20         21       NATURAL     BUST
  2  13.0138%   13.3157%   12.8600%   12.0227%   11.3617%    0.0000%   37.4260%
  3  12.5628%   13.0186%   12.2907%   11.8168%   11.0145%    0.0000%   39.2965%
  4  12.0887%   12.1174%   12.1185%   11.3440%   10.8491%    0.0000%   41.4825%
  5  12.1908%   12.1116%   11.5061%   10.6511%   10.4057%    0.0000%   43.1347%
  6   6.8368%   12.2179%   11.5637%   10.9612%   10.4570%    0.0000%   47.9633%
  7  39.2553%    6.8531%    8.9188%    8.2753%    7.6780%    0.0000%   29.0194%
  8  13.2365%   39.2553%    5.7605%    7.8262%    7.1811%    0.0000%   26.7404%
  9  12.2132%   12.5372%   38.3526%    4.7974%    6.8658%    0.0000%   25.2339%
  H  11.3942%   12.2132%   12.2841%   36.6997%    3.9018%    0.0000%   23.5070%
  A  is not left in deck

1 deck, lots of high cards visible, 3 Aces, 8 honors and 3 Nines
Card    17         18         19         20         21       NATURAL     BUST
  2  16.0973%   15.5326%   15.0611%   13.9028%    9.9494%    0.0000%   29.4568%
  3  15.3853%   15.8948%   14.1015%   13.5737%   12.4078%    0.0000%   28.6369%
  4  15.7013%   14.1293%   14.5066%   12.7401%   12.0064%    0.0000%   30.9162%
  5  14.5124%   15.1168%   13.9803%   12.1107%   11.3529%    0.0000%   32.9269%
  6  13.5209%   14.4244%   13.5992%   12.1245%   11.6677%    0.0000%   34.6633%
  7  31.4337%   12.8325%   11.0155%   10.3669%    8.9369%    0.0000%   25.4145%
  8  11.6807%   31.1870%   11.7455%    9.9479%    9.2536%    0.0000%   26.1854%
  9  17.7379%    8.6150%   29.9423%   10.1906%    8.3671%    0.0000%   25.1472%
  H  16.4181%   17.2912%    9.5652%   25.4766%    6.0171%    2.7027%   22.5291%
  A  16.0890%   18.4076%   18.0607%   10.4038%    7.0168%   21.6216%    8.4006%

and here is the code

--------------------------- >8 -----------------------
require 'mathn'
require "enumerator"

class String
  def each_char &blk
    return enum_for(:each_byte).map{ |b| b.chr } unless blk
    enum_for(:each_byte).each do |b| blk.call b.chr end
  end
end

class Object
  def tap
    yield self
    self
  end

  def ivars_set ivar_hash
    ivar_hash.each do | ivar_name, ivar_value |
      instance_variable_set "@#{ivar_name}", ivar_value
    end
    self
  end
end

module Probability
  def to_p digits
    "%#{digits+3}.#{digits}f%%" % ( 100 * to_f )
  end
end
[ Fixnum, Rational ].each do | number_class |
  number_class.send :include, Probability
end

NATURAL  
BUST     
BANK_STANDS  7
BLACKJACK    1

DataError  lass::new RuntimeError
Cards  w{ 2 3 4 5 6 7 8 9 h a }

class String
  def ace
    downcase "a" ? 1 : 0
  end

  def value
    case self
    when "A", "a"
      11
    when "H", "h"
      10
    when "2".."9"
      to_i
    else
      nil
    end
  end
end

class Fixnum
  def add_value face_or_value
    face_or_value  ace_or_value.value if String  face_or_value
    self + face_or_value
  end
end

# unmutable objects representing cards that still can be drawn
class Pack

  def initialize *args
    @data  ash[ *args ] # a count of faces as a hash
    @total  data.inject(0){ |sum, (k,v)| sum + v } # total count of cards
  end

  def each_with_p
    @data.each do |face, count|
      next if count.zero?
      yield face, probability( face )
    end
  end

  def probability face
    Rational @data[face], @total
  end

  def - face
    data, total  data, @total
    self.class.allocate.instance_eval { | new_pack |
      @data  ata.dup
      @data[ face ] - 
      raise DataError, "Cannot remove #{face}" if @data[ face ] < 0
      @total  otal - 1
      new_pack
    }
  end
end # class Pack

# represents the hand of the dealer, immutable
class Hand
  attr_reader :probability
  def initialize pack, card
    @pack  ack
    @cards   card ]
    @count  ard.value
    @aces  ard.ace
    @probability  
  end

  def adjust_prob p
    @probability * 
  end

  def result
    return BUST if @count > BLACKJACK
    return NATURAL if @count BLACKJACK && @cards.size 2
    return nil if @count < BANK_STANDS
    @count - BANK_STANDS
  end

  def + face
    count  count + face.value
    aces  aces + face.ace
    loop do
      break if count < LACKJACK || aces.zero?
      count - 0
      aces - 
    end
    self.class.allocate.tap{ | new_hand |
      new_hand.ivars_set :cards ( @cards.dup << face ), :count count,
                         :aces aces, :pack @pack - face,
:probability @probability
    }
  end

  # the workerbee, recursive traversal of the game tree of the
  # dealers hand.
  def compute results
    @pack.each_with_p do | face, p |
      new_hand  elf + face
      new_hand.adjust_prob p
      r  ew_hand.result
      if r then
        results[ r ] + ew_hand.probability
      else
        new_hand.compute results
      end
    end
  end

end


def output card, results
  puts "  #{card.upcase}  #{results.map{ |r| r.to_p(4) }.join("   ")}"
end

def usage
  puts %<usage:
  ruby #{$0} <number of decks> [visible cards]
  visible cards: One or more strings with single characters indicating
       face values as follows, 23456789[hH][aA]
  >
  exit -1
end
usage if ARGV.empty? || /^-h|^--help/  ARGV.first

number_of_decks  RGV.shift.to_i
visible_cards  RGV.join.each_char.to_a
pack  ack.new(
      *Cards.map{ |c| [c.downcase,
        ( c.downcase "h" ? 4 : 1 ) * 4 * number_of_decks ]
     }.flatten )
visible_cards.each do |vc|
 pack  ack - vc.downcase
end

puts "Card    17         18         19         20         21
NATURAL     BUST"
Cards.each do
  | card |
  begin
    hand  and.new pack - card, card
    results  rray.new( 7 ){ 0 }
    hand.compute results
    output  card, results
  rescue DataError
    puts "  #{card.upcase}  is not left in deck"
  end
end
------------------------------------------- 8<
--------------------------------------

------art_3010_26146912.1199835152196
Content-Type: application/x-ruby; name=sol_r_dober-1.0.rb
Content-Transfer-Encoding: base64
X-Attachment-Id: f_fb72t0qo0
Content-Disposition: attachment; filename=sol_r_dober-1.0.rb

IyEvdXNyL2Jpbi9ydWJ5CiMgdmltOiBzdz0yIHRzPTIgZnQ9cnVieSBleHBhbmR0YWIgdHc9MCBu
dSBzeW46CiMKcmVxdWlyZSAnbWF0aG4nCnJlcXVpcmUgImVudW1lcmF0b3IiCgpjbGFzcyBTdHJp
bmcKICBkZWYgZWFjaF9jaGFyICZibGsKICAgIHJldHVybiBlbnVtX2Zvcig6ZWFjaF9ieXRlKS5t
YXB7IHxifCBiLmNociB9IHVubGVzcyBibGsKICAgIGVudW1fZm9yKDplYWNoX2J5dGUpLmVhY2gg
ZG8gfGJ8IGJsay5jYWxsIGIuY2hyIGVuZAogIGVuZCAKZW5kCgpjbGFzcyBPYmplY3QKICBkZWYg
dGFwCiAgICB5aWVsZCBzZWxmCiAgICBzZWxmCiAgZW5kCgogIGRlZiBpdmFyc19zZXQgaXZhcl9o
YXNoCiAgICBpdmFyX2hhc2guZWFjaCBkbyB8IGl2YXJfbmFtZSwgaXZhcl92YWx1ZSB8CiAgICAg
IGluc3RhbmNlX3ZhcmlhYmxlX3NldCAiQCN7aXZhcl9uYW1lfSIsIGl2YXJfdmFsdWUKICAgIGVu
ZAogICAgc2VsZgogIGVuZAplbmQKCm1vZHVsZSBQcm9iYWJpbGl0eQogIGRlZiB0b19wIGRpZ2l0
cwogICAgIiUje2RpZ2l0cyszfS4je2RpZ2l0c31mJSUiICUgKCAxMDAgKiB0b19mICkKICBlbmQK
ZW5kClsgRml4bnVtLCBSYXRpb25hbCBdLmVhY2ggZG8gfCBudW1iZXJfY2xhc3MgfAogIG51bWJl
cl9jbGFzcy5zZW5kIDppbmNsdWRlLCBQcm9iYWJpbGl0eQplbmQKCk5BVFVSQUwgPSA1CkJVU1Qg
ICAgPSA2CkJBTktfU1RBTkRTID0gMTcKQkxBQ0tKQUNLICAgPSAyMQoKRGF0YUVycm9yID0gQ2xh
c3M6Om5ldyBSdW50aW1lRXJyb3IKQ2FyZHMgPSAld3sgMiAzIDQgNSA2IDcgOCA5IGggYSB9Cgpj
bGFzcyBTdHJpbmcKICBkZWYgYWNlIAogICAgZG93bmNhc2UgPT0gImEiID8gMSA6IDAKICBlbmQK
CiAgZGVmIHZhbHVlCiAgICBjYXNlIHNlbGYKICAgIHdoZW4gIkEiLCAiYSIKICAgICAgMTEKICAg
IHdoZW4gIkgiLCAiaCIKICAgICAgMTAKICAgIHdoZW4gIjIiLi4iOSIKICAgICAgdG9faQogICAg
ZWxzZQogICAgICBuaWwKICAgIGVuZAogIGVuZAplbmQKCmNsYXNzIEZpeG51bQogIGRlZiBhZGRf
dmFsdWUgZmFjZV9vcl92YWx1ZQogICAgZmFjZV9vcl92YWx1ZSA9IGZhY2Vfb3JfdmFsdWUudmFs
dWUgaWYgU3RyaW5nID09PSBmYWNlX29yX3ZhbHVlCiAgICBzZWxmICsgZmFjZV9vcl92YWx1ZQog
IGVuZAplbmQKCiMgdW5tdXRhYmxlIG9iamVjdHMgcmVwcmVzZW50aW5nIGNhcmRzIHRoYXQgc3Rp
bGwgY2FuIGJlIGRyYXduCmNsYXNzIFBhY2sKCiAgZGVmIGluaXRpYWxpemUgKmFyZ3MKICAgIEBk
YXRhID0gSGFzaFsgKmFyZ3MgXSAjIGEgY291bnQgb2YgZmFjZXMgYXMgYSBoYXNoCiAgICBAdG90
YWwgPSBAZGF0YS5pbmplY3QoMCl7IHxzdW0sIChrLHYpfCBzdW0gKyB2IH0gIyB0b3RhbCBjb3Vu
dCBvZiBjYXJkcwogIGVuZAoKICBkZWYgZWFjaF93aXRoX3AKICAgIEBkYXRhLmVhY2ggZG8gfGZh
Y2UsIGNvdW50fAogICAgICBuZXh0IGlmIGNvdW50Lnplcm8/CiAgICAgIHlpZWxkIGZhY2UsIHBy
b2JhYmlsaXR5KCBmYWNlICkKICAgIGVuZAogIGVuZAoKICBkZWYgcHJvYmFiaWxpdHkgZmFjZQog
ICAgUmF0aW9uYWwgQGRhdGFbZmFjZV0sIEB0b3RhbAogIGVuZAoKICBkZWYgLSBmYWNlCiAgICBk
YXRhLCB0b3RhbCA9IEBkYXRhLCBAdG90YWwKICAgIHNlbGYuY2xhc3MuYWxsb2NhdGUuaW5zdGFu
Y2VfZXZhbCB7IHwgbmV3X3BhY2sgfAogICAgICBAZGF0YSA9IGRhdGEuZHVwCiAgICAgIEBkYXRh
WyBmYWNlIF0gLT0gMQogICAgICByYWlzZSBEYXRhRXJyb3IsICJDYW5ub3QgcmVtb3ZlICN7ZmFj
ZX0iIGlmIEBkYXRhWyBmYWNlIF0gPCAwCiAgICAgIEB0b3RhbCA9IHRvdGFsIC0gMQogICAgICBu
ZXdfcGFjawogICAgfQogIGVuZAplbmQgIyBjbGFzcyBQYWNrCgojIHJlcHJlc2VudHMgdGhlIGhh
bmQgb2YgdGhlIGRlYWxlciwgaW1tdXRhYmxlCmNsYXNzIEhhbmQKICBhdHRyX3JlYWRlciA6cHJv
YmFiaWxpdHkKICBkZWYgaW5pdGlhbGl6ZSBwYWNrLCBjYXJkCiAgICBAcGFjayA9IHBhY2sKICAg
IEBjYXJkcyA9IFsgY2FyZCBdCiAgICBAY291bnQgPSBjYXJkLnZhbHVlCiAgICBAYWNlcyA9IGNh
cmQuYWNlCiAgICBAcHJvYmFiaWxpdHkgPSAxCiAgZW5kCgogIGRlZiBhZGp1c3RfcHJvYiBwCiAg
ICBAcHJvYmFiaWxpdHkgKj0gcAogIGVuZAoKICBkZWYgcmVzdWx0CiAgICByZXR1cm4gQlVTVCBp
ZiBAY291bnQgPiBCTEFDS0pBQ0sKICAgIHJldHVybiBOQVRVUkFMIGlmIEBjb3VudCA9PSBCTEFD
S0pBQ0sgJiYgQGNhcmRzLnNpemUgPT0gMgogICAgcmV0dXJuIG5pbCBpZiBAY291bnQgPCBCQU5L
X1NUQU5EUwogICAgQGNvdW50IC0gQkFOS19TVEFORFMKICBlbmQKCiAgZGVmICsgZmFjZQogICAg
Y291bnQgPSBAY291bnQgKyBmYWNlLnZhbHVlCiAgICBhY2VzID0gQGFjZXMgKyBmYWNlLmFjZQog
ICAgbG9vcCBkbwogICAgICBicmVhayBpZiBjb3VudCA8PSBCTEFDS0pBQ0sgfHwgYWNlcy56ZXJv
PwogICAgICBjb3VudCAtPSAxMAogICAgICBhY2VzIC09IDEKICAgIGVuZAogICAgc2VsZi5jbGFz
cy5hbGxvY2F0ZS50YXB7IHwgbmV3X2hhbmQgfAogICAgICBuZXdfaGFuZC5pdmFyc19zZXQgOmNh
cmRzID0+ICggQGNhcmRzLmR1cCA8PCBmYWNlICksIDpjb3VudCA9PiBjb3VudCwKICAgICAgICAg
ICAgICAgICAgICAgICAgIDphY2VzID0+IGFjZXMsIDpwYWNrID0+IEBwYWNrIC0gZmFjZSwgOnBy
b2JhYmlsaXR5ID0+IEBwcm9iYWJpbGl0eQogICAgfQogIGVuZAoKICAjIHRoZSB3b3JrZXJiZWUs
IHJlY3Vyc2l2ZSB0cmF2ZXJzYWwgb2YgdGhlIGdhbWUgdHJlZSBvZiB0aGUKICAjIGRlYWxlcnMg
aGFuZC4KICBkZWYgY29tcHV0ZSByZXN1bHRzCiAgICBAcGFjay5lYWNoX3dpdGhfcCBkbyB8IGZh
Y2UsIHAgfAogICAgICBuZXdfaGFuZCA9IHNlbGYgKyBmYWNlCiAgICAgIG5ld19oYW5kLmFkanVz
dF9wcm9iIHAKICAgICAgciA9IG5ld19oYW5kLnJlc3VsdAogICAgICBpZiByIHRoZW4KICAgICAg
ICByZXN1bHRzWyByIF0gKz0gbmV3X2hhbmQucHJvYmFiaWxpdHkKICAgICAgZWxzZQogICAgICAg
IG5ld19oYW5kLmNvbXB1dGUgcmVzdWx0cwogICAgICBlbmQKICAgIGVuZAogIGVuZAoKZW5kCgoK
ZGVmIG91dHB1dCBjYXJkLCByZXN1bHRzCiAgcHV0cyAiICAje2NhcmQudXBjYXNlfSAgI3tyZXN1
bHRzLm1hcHsgfHJ8IHIudG9fcCg0KSB9LmpvaW4oIiAgICIpfSIKZW5kCgpkZWYgdXNhZ2UKICBw
dXRzICU8dXNhZ2U6CiAgcnVieSAjeyQwfSA8bnVtYmVyIG9mIGRlY2tzPiBbdmlzaWJsZSBjYXJk
c10KICB2aXNpYmxlIGNhcmRzOiBPbmUgb3IgbW9yZSBzdHJpbmdzIHdpdGggc2luZ2xlIGNoYXJh
Y3RlcnMgaW5kaWNhdGluZwogICAgICAgZmFjZSB2YWx1ZXMgYXMgZm9sbG93cywgMjM0NTY3ODlb
aEhdW2FBXQogID4KICBleGl0IC0xCmVuZAp1c2FnZSBpZiBBUkdWLmVtcHR5PyB8fCAvXi1ofF4t
LWhlbHAvID09PSBBUkdWLmZpcnN0CgpudW1iZXJfb2ZfZGVja3MgPSBBUkdWLnNoaWZ0LnRvX2kK
dmlzaWJsZV9jYXJkcyA9IEFSR1Yuam9pbi5lYWNoX2NoYXIudG9fYQpwYWNrID0gUGFjay5uZXco
IAogICAgICAqQ2FyZHMubWFweyB8Y3wgW2MuZG93bmNhc2UsIAogICAgICAgICggYy5kb3duY2Fz
ZSA9PSAiaCIgPyA0IDogMSApICogNCAqIG51bWJlcl9vZl9kZWNrcyBdCiAgICAgfS5mbGF0dGVu
ICkKdmlzaWJsZV9jYXJkcy5lYWNoIGRvIHx2Y3wKIHBhY2sgPSBwYWNrIC0gdmMuZG93bmNhc2UK
ZW5kCgpwdXRzICJDYXJkICAgIDE3ICAgICAgICAgMTggICAgICAgICAxOSAgICAgICAgIDIwICAg
ICAgICAgMjEgICAgICAgTkFUVVJBTCAgICAgQlVTVCIKQ2FyZHMuZWFjaCBkbwogIHwgY2FyZCB8
CiAgYmVnaW4KICAgIGhhbmQgPSBIYW5kLm5ldyBwYWNrIC0gY2FyZCwgY2FyZAogICAgcmVzdWx0
cyA9IEFycmF5Lm5ldyggNyApeyAwIH0KICAgIGhhbmQuY29tcHV0ZSByZXN1bHRzCiAgICBvdXRw
dXQgIGNhcmQsIHJlc3VsdHMKICByZXNjdWUgRGF0YUVycm9yCiAgICBwdXRzICIgICN7Y2FyZC51
cGNhc2V9ICBpcyBub3QgbGVmdCBpbiBkZWNrIgogIGVuZAplbmQK
------art_3010_26146912.1199835152196--