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