From: Robert Dober Date: 2008-01-09T08:32:26+09:00 Subject: Re: Studying Blackjack (#151) ------=_Part_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 = 5 BUST = 6 BANK_STANDS = 17 BLACKJACK = 21 DataError = Class::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 = face_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 = Hash[ *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 = data.dup @data[ face ] -= 1 raise DataError, "Cannot remove #{face}" if @data[ face ] < 0 @total = total - 1 new_pack } end end # class Pack # represents the hand of the dealer, immutable class Hand attr_reader :probability def initialize pack, card @pack = pack @cards = [ card ] @count = card.value @aces = card.ace @probability = 1 end def adjust_prob p @probability *= p 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 <= BLACKJACK || aces.zero? count -= 10 aces -= 1 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 = self + face new_hand.adjust_prob p r = new_hand.result if r then results[ r ] += new_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 % [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 = ARGV.shift.to_i visible_cards = ARGV.join.each_char.to_a pack = Pack.new( *Cards.map{ |c| [c.downcase, ( c.downcase == "h" ? 4 : 1 ) * 4 * number_of_decks ] }.flatten ) visible_cards.each do |vc| pack = pack - vc.downcase end puts "Card 17 18 19 20 21 NATURAL BUST" Cards.each do | card | begin hand = Hand.new pack - card, card results = Array.new( 7 ){ 0 } hand.compute results output card, results rescue DataError puts " #{card.upcase} is not left in deck" end end ------------------------------------------- 8< -------------------------------------- ------=_Part_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 ------=_Part_3010_26146912.1199835152196--