--------------000503060202040102080806 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 This was a fun one :) I'm looking forward to seeing other solutions. Regs, Derek -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.4 (MingW32) iD8DBQFCPX/KxPPkePIbSlwRAoexAKDgcRpsOdBoAujcknMiQWriyjKDWACg1vvn fvyLC2qKvFRFMSB5LE77dxc= =XVKj -----END PGP SIGNATURE----- --------------000503060202040102080806 Content-Type: text/plain; name="holdem.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="holdem.rb" #!/usr/local/bin/ruby -w # Global mappings of cards to numbers $values = { "A" => 1, "2" => 2, "3" => 3, "4" => 4, "5" => 5, "6" => 6, "7" => 7, "8" => 8, "9" => 9, "T" => 10, "J" => 11, "Q" => 12, "K" => 13, "X" => 14 # this is an ace high, as opposed to ace low } # Just to make things pretty -- but Six plural i don't handle $strs = { "A" => "Ace", "2" => "Two", "3" => "Three", "4" => "Four", "5" => "Five", "6" => "Six", "7" => "Seven", "8" => "Eight", "9" => "Nine", "T" => "Ten", "J" => "Jack", "Q" => "Queen", "K" => "King" } # This is the class that handles everything for us. Holds onto the hand # and classifies it class Classifier attr_reader :rank, :folded, :goods, :type, :kickers attr_accessor :winner def initialize(hand) @hand = hand @goods = [] @folded = false @winner = false @type = "" end # Just sorts the goods def sortgoods @goods = @goods.flatten.sort { |x, y| $values[x[0].chr] <=> $values[y[0].chr] } end # run through the possibles in order of "goodness" and classify the hand # in its best possible light. Yes, this recomputes things sometimes, but # who cares? def classify # Store both kickers. We do this so that we can eliminate one if # necessary on the high card scenario @kickers = @hand[0..1].map { |x| if x[0].chr == "A" "X" + x[1].chr else x end }.sort { |x, y| $values[x[0].chr] <=> $values[y[0].chr] }.map { |x| $values[x[0].chr] }.reverse # Player chickened out, so quit. We assume that if there are 7 cards # then they went all the way. if @hand.length < 7 @goods = [] @folded = true @rank = -1 @type = "" # Sweetness elsif royal_flush @rank = 200 @type = "Royal Flush" sortgoods # Almost sweetness elsif straight_flush @rank = $values[@goods[0][0].chr] + 180 @type = "Straight Flush (to the #{$strs[@goods[4][0].chr]})" # If it's some "of a kind" and it's four of a kind, then... elsif kind and @goods.length == 1 and @goods[0].length == 4 @rank = $values[@goods[0][0][0].chr] + 180 @rank += 13 if @goods[0][0][0].chr == "A" @type = "Four of a Kind (#{$strs[@goods[0][0][0].chr]}s)" sortgoods # I've never gotten 4 of a kind, but full house is much easier elsif full_house @rank = $values[@goods[0][0].chr] + 140 @rank += 13 if @goods[0][0].chr == "A" @type = "Full House (#{$strs[@goods[0][0].chr]}s over " + "#{$strs[@goods[3][0].chr]}s)" sortgoods # 5 of the same suit elsif flush @rank = $values[@goods[0][0].chr] + 120 @type = "Flush (#{$strs[@goods[0][0].chr]} high)" sortgoods # sequentially numbered, and sorted elsif straight @rank = $values[@goods[0][0].chr] + 100 @rank += 13 if @goods[0][0].chr == "A" @type = "Straight (to the #{$strs[@goods[4][0].chr]})" # 3 of a kind elsif kind and @goods.length == 1 and @goods[0].length == 3 @rank = $values[@goods[0][0][0].chr] + 80 @rank += 13 if @goods[0][0][0].chr == "A" @type = "Three of a Kind (#{$strs[@goods[0][0][0].chr]}s)" sortgoods # 2 pair elsif kind and @goods.length == 2 @rank = $values[@goods[1][0][0].chr] + $values[@goods[0][0][0].chr] + 40 @rank += 13 if @goods[0][0][0].chr == "A" @type = "Two Pair (#{$strs[@goods[0][0][0].chr]}s and " + "#{$strs[@goods[1][0][0].chr]}s)" sortgoods # one pair elsif kind and @goods.length == 1 @rank = $values[@goods[0][0][0].chr] + 20 @rank += 13 if @goods[0][0][0].chr == "A" @type = "Pair (#{$strs[@goods[0][0][0].chr]}s)" sortgoods # high card else handdup = @hand.dup aces = handdup.find_all { |x| x[0].chr == "A" } aces.each { |ace| handdup.push("X" + ace[1].chr) } c = handdup.sort { |x, y| $values[x[0].chr] <=> $values[y[0].chr] }[-1] # Toss this kicker, if need be @kickers.delete($values[c[0].chr]) @rank = $values[c[0].chr] c = "A" + c[1].chr if c[0].chr == "X" @goods = [c] @type = "#{$strs[c[0].chr]} High" # push on -1 for completeness. I don't actually use the second value # but if i ever do, all kickers have 2 elements. the second one, in # this case, just sucks. @kickers.push(-1) if @kickers.length == 1 end end # Find out if we have a straight def straight handdup = @hand.dup # find all aces and push on new aces in their "high" value with the # same suit aces = handdup.find_all { |x| x[0].chr == "A" } aces.each { |ace| handdup.push("X" + ace[1].chr) } # Sort it ascending handdup = handdup.sort { |x, y| $values[x[0].chr] <=> $values[y[0].chr] } c = 1 result = [handdup[0]] # iterate starting at the second card handdup[1..-1].each_index { |x| # no hope -- we've exhausted the cards at this point break if handdup.length - x < 5 - result.length # If there is a one number difference if $values[handdup[x + 1][0].chr] - $values[handdup[x][0].chr] == 1 result.push(handdup[x + 1]) elsif $values[handdup[x + 1][0].chr] - $values[handdup[x][0].chr] > 1 # There's a gap bigger than one. We're toast, unless we've already # found something if result.length != 5 result = [handdup[x + 1]] else break end end } # Convert X's back into A's result.map! { |x| if x[0].chr == "X" "A" + x[1].chr else x end } # no luck here result = [] if result.length < 5 # Whoopie... we found a straight result = result[-5..-1] if result.length > 5 @goods = result return true if @goods.length != 0 return false end # Use the results of the straight to find the straight flush. def straight_flush straight # This code tests to see if this is a straight flush and if it isn't it # swaps in the two unused cards to see if they make a straight flush. # We don't care about efficiency here by writing a lot of tests. Just # swap the damned things in and see if it worked. if @goods.map { |x| x[1].chr }.uniq.length != 1 if @goods.length != 0 nons = @hand.select { |x| not @goods.member? x } goodsdup = @goods.dup if goodsdup.delete_if { |x| x[0].chr == nons[0][0].chr }.length == 4 goodsdup.push(nons[0]) end if goodsdup.map { |x| x[1].chr }.uniq.length != 1 if goodsdup.delete_if { |x| x[0].chr == nons[1][0].chr }.length == 4 goodsdup.push(nons[1]) end if goodsdup.map { |x| x[1].chr }.uniq.length != 1 if @goods.delete_if { |x| x[0].chr == nons[1][0].chr }.length == 4 @goods.push(nons[1]) end if @goods.map { |x| x[1].chr }.uniq.length != 1 @goods = [] end else @goods = goodsdup end else @goods = goodsdup end end end return true if @goods.length != 0 return false end # find out if the straight flush is royal. def royal_flush straight_flush @goods = [] unless @goods.length == 5 and @goods[4][0].chr == "A" return true if @goods.length != 0 return false end # find out if there is a flush... there's got to be a cooler way to do # this def flush @goods = @hand.select { |x| x[1].chr == "s" } @goods = @hand.select { |x| x[1].chr == "c" } if @goods.length == 0 @goods = @hand.select { |x| x[1].chr == "d" } if @goods.length == 0 @goods = @hand.select { |x| x[1].chr == "h" } if @goods.length == 0 @goods = @goods.sort { |x, y| $values[x[0].chr] <=> $values[y[0].chr] }.reverse return true if @goods.length == 5 return false end # Return all "of a kind"s def all_kinds result = [] hash = {} @hand.each { |x| hash[x[0].chr] ||= [] hash[x[0].chr].push(x) } hash.each_value { |v| result.push(v) if v.length > 1 } return [] if result.length == 0 result = result.sort { |x, y| if x.length == y.length $values[x[0][0].chr] <=> $values[y[0][0].chr] else x.length <=> y.length end }.reverse return result end # return the best "of a kind" def kind result = all_kinds if result.length != 0 @goods = result if result.length == 1 @goods = [result[0]] if result[0].length > 2 @goods = [result[0], result[1]] if result.length >= 2 else @goods = [] end return true if @goods.length != 0 return false end # find out if there is 3 of a kind and 2 of a kind. if there is then # this is a full house def full_house result = all_kinds if result.length == 2 and result[0].length == 3 @goods = result[0] + result[1] else @goods = [] end return true if @goods.length != 0 return false end # pretty output -- ordered the way Bob wanted def to_s if @type.length != 0 # figure out the kickers, used cards, and non-used cards kicks = @hand[0..1].sort { |x, y| x[0].chr <=> y[0].chr } used = @goods nons = @hand.select { |x| not used.member? x }.sort { |x, y| x[0].chr <=> y[0].chr } # get rid of the kickers if they're used kicks.delete_at(0) if used.member? kicks[0] kicks.delete_at(-1) if used.member? kicks[-1] # get rid of the nons if they're kickers kicks.each { |x| nons.delete(x) } else used = kicks = [] nons = @hand end "%-20s #{@type}" % (used + kicks + nons).join(' ') end end # Read the stuff in -- currently only reads from a file simply because i # wasn't able to get windows piping to work properly... i hate windows, but # had no unix box at the time. hands = [] while line = gets hands.push(Classifier.new(line.chomp.split(/ /))) hands[-1].classify end # Rank them sorted = hands.sort { |x, y| if x.rank == y.rank x.kickers[0] <=> y.kickers[0] else x.rank <=> y.rank end }.reverse # Set any pushes to winners sorted.select { |x| x.rank == sorted[0].rank and x.kickers[0] == sorted[0].kickers[0] }.each { |x| x.winner = true } # The holy grail hands.each { |x| puts x.to_s + (x.winner ? " (winner)" : "") } --------------000503060202040102080806--