Hi, here goes my solution to this weeks Ruby Q., 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 %<usage:
ruby #{$0} [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<