#!/usr/bin/env ruby -w #### #### Haggle scoring script #### ===================== #### #### Using Haggle rules (and test cases) from "A Gamut of Games", by Sid Sackson #### ################################################################################# ### raw user data goes here! #################################################### ################################################################################# PlayerData = [ ## ## test cases from "A Gamut of Games", by Sid Sackson ## # +---------------+--------+--------+--------+--------+--------+ # | NAME | YELLOW | PURPLE | PINK | ORANGE | WHITE | # +---------------+--------+--------+--------+--------+--------+ #%w( hand_1 0 6 1 3 1 ), # test case, 56 pts. #%w( hand_2 3 2 2 3 3 ), # test case, 51 pts. #%w( hand_3 0 2 5 2 2 ), # test case, 52 pts. #%w( hand_4 4 2 3 0 1 ), # test case, 54 pts. #%w( hand_5 6 0 1 0 2 ), # test case, 55 pts. ## ## played on July 31st, 2010 ## # +---------------+--------+--------+--------+--------+--------+ # | NAME | YELLOW | PURPLE | PINK | ORANGE | WHITE | # +---------------+--------+--------+--------+--------+--------+ %w( darla 4 6 3 3 3 ), %w( randy_y 1 3 2 0 4 ), %w( dundee 0 5 3 1 1 ), %w( casey 2 0 3 2 4 ), %w( vince 0 2 3 3 1 ), %w( matt 2 1 4 2 0 ), %w( josh 6 1 3 0 3 ), %w( bill 0 5 0 5 0 ), %w( fred 0 0 0 0 0 ), %w( aaron 0 0 0 0 0 ), %w( jacob 1 2 0 3 0 ), %w( shioui 0 2 3 2 6 ), %w( susan 1 4 1 3 2 ), %w( derek 1 0 0 0 0 ), %w( ratdogg 2 0 2 0 6 ), %w( eileen 3 3 4 3 0 ), %w( sarah_n 2 3 3 1 4 ), %w( tj 2 3 3 2 3 ), %w( ann 3 0 2 3 0 ), %w( randy_s 2 0 5 0 6 ), %w( adam 5 4 1 4 1 ), %w( steven 4 1 2 0 3 ), %w( sarah_w 1 0 0 0 0 ), %w( chris 0 5 0 5 0 ), %w( katie 2 3 3 4 1 ), %w( bonnie 2 5 3 2 2 ), ] ################################################################################# ### script begin ################################################################ ################################################################################# # seed random number generator for consistent results across runs (using my birthday) Kernel.srand(032472) # RULE 1. Orange cards have a basic value of 4 and are equal to a pink card and a yellow card. # RULE 2. White cards have the highest basic value and are equal to a pink card and a purple card. # RULE 3. Purple cards have a basic value of twice that of yellow and half that of orange. # yellow = 1, purple = 2, pink = 3, orange = 4, and white = 5 BaseValues = { :yellow => 1, :purple => 2, :pink => 3, :orange => 4, :white => 5 } #### #### a single card #### class Card include Comparable attr_reader :color # constructor def initialize(color) @color = color @multiplier = 1 end # double value of this card def double! @multiplier *= 2 end # quadruple value of this card def quadruple! @multiplier *= 4 end # test if card is doubled def is_doubled? @multiplier == 2 end # test if card is quadrupled def is_quadrupled? @multiplier == 4 end # zero out a card's value def zero! @multiplier = 0 end # create a string description of this card def to_s base = BaseValues[@color] comment_str = if is_doubled? " (doubled, was #{base})" elsif is_quadrupled? " (quadrupled, was #{base})" elsif ( value == 0 ) " (zeroed, was #{base})" else "" end @color.to_s + comment_str end # get card's current value def value BaseValues[@color] * @multiplier end # comparison (for sorting) def <=>(other) # sort by color's base value (i.e. by color), then value # note default is a reverse sort (high to low) [BaseValues[other.color],other.value] <=> [BaseValues[@color],value] end end #### #### a single hand (contains cards) #### class Hand attr_reader :name attr_reader :score # constructor def initialize(name,yellow,purple,pink,orange,white) @name = name @hand_multiplier = 1 @bonus = 0 @blue_hits = 0 @score = nil # RULE 5. A player can score only as many orange cards as he has purple cards. if orange > purple puts "*** RULE 5 *** can only score as many orange cards as purple cards" puts "#{@name} lost #{orange - purple} orange cards." orange = purple puts end # populate hand with Card objects @cards = [] yellow.times { @cards << Card.new(:yellow) } purple.times { @cards << Card.new(:purple) } pink.times { @cards << Card.new(:pink) } orange.times { @cards << Card.new(:orange) } white.times { @cards << Card.new(:white) } # # RULE 15. No more than thirteen cards in a hand can be scored. If more are handed in, # the excess will be removed at random. # # check rule 15 straight away.. 13 card limit, excess deleted at random if size > 13 puts "*** RULE 15 *** hand size too big (13 max)" puts "#{@name} has too many cards (#{size})." (size-13).times { puts " lost a #{rand_delete} card at random."} puts end end # double hand def double! @hand_multiplier *= 2 puts "#{@name}'s hand is doubled." end # convert to a string def to_s "#{yellow} yellow, #{purple} purple, #{pink} pink, #{orange} orange, #{white} white" end # get individual color counts def count_color(color) @cards.count { |x| x.color == color } end def yellow; count_color(:yellow); end def purple; count_color(:purple); end def pink; count_color(:pink); end def orange; count_color(:orange); end def white; count_color(:white); end # removes one randome card def rand_delete # return randomly deleted card (or nil if array is empty) @cards.slice!(Kernel.rand(@cards.size)) end # total number of cards def size @cards.size end # create an array of counts in specific order (used for sorting, various measurement fn's) def counts [yellow,purple,pink,orange,white] end # test for a "pyramid" def is_pyramid? counts.sort == [0,1,2,3,4] end # count sets of all five colors def sets_of_all_five_colors counts.min end # count for sets (of size X) in color Y.. i.e. "sets of 2 in blue (i.e. pairs)" def num_sets(color,set_size) count_color(color) / set_size end # return the size of the largest color def largest_color counts.max end # add bonus points to this hand def add_bonus!(amount) puts "#{@name} got a bonus of #{amount}." @bonus += amount end # add a "blue hit" (i.e. purple in our game) to this hand def blue_hit!(hitter = "") puts "#{hitter} hit #{@name} for 10 points." @blue_hits += 1 end # double the value of all red (pink on our game) cards def double_red! puts "#{@name} now has all pink cards doubled in value." @cards.select {|h| h.color == :pink }.each(&:double!) end # figure out this hand's score (meant to be called once) def calculate_score! # return if we already calculated this! return @score unless @score.nil? # otherwise start at 0 @score = 0 # print a little summary puts "Scoring #{@name}" puts "========" + "=" * @name.size puts "* has #{to_s}" puts "* starts with a bonus of #{@bonus}." puts "* starts with a hand multiplier of #{@hand_multiplier}." puts "* has been hit #{@blue_hits} times for -10 points each (#{-10 * @blue_hits} total)." puts # RULE 4. If a player has more than three white cards, all of his white cards lose their value. if white > 3 puts "*** RULE 4 *** if a player has more than 3 white, all white lose their value" puts "#{@name} has #{white} white cards. setting all to 0." @cards.select { |c| c.color == :white }.each(&:zero!) puts end # RULE 7. A set of three pink cards protects you from one set of five purple cards. if num_sets(:pink,3) > 0 puts "*** RULE 7 *** A set of three pink protects you from one hit of five purple" puts "#{@name} has a set of pink; reducing hits from purple by one (+10 points)." @blue_hits -= 1 @blue_hits = 0 if @blue_hits < 0 puts "after reduction, #{@name} has been hit #{@blue_hits} times for -10 points each (#{-10 * @blue_hits} total)." puts end # RULE 10. Each set of five different colors gives a bonus of 10 points. if sets_of_all_five_colors > 0 puts "*** RULE 10 *** each set of all five colors gives a bonus of 10" sets_of_all_five_colors.times { add_bonus!(10) } puts end # RULE 11. If a "pyramid" is handed in with no other cards, the value of the hand is # doubled. A pyramid consists of four cards of one color, three cards of a second color, # two cards of a third color, and one card of a fourth color. if is_pyramid? @hand_multiplier *= 2 puts "*** RULE 11 *** 'pyramid' hands are doubled" puts "#{@name} has a pyramid; new hand multiplier is #{@hand_multiplier}." puts end # RULE 13. Each set of two yellow cards doubles the value of one white card. yellow_sets = num_sets(:yellow,2) if yellow_sets > 0 puts "*** RULE 13 *** each set of 2 yellow doubles 1 white" whites_to_double = [white,yellow_sets].min puts "#{@name} has #{yellow_sets} sets; doubling #{whites_to_double} white cards." @cards.select{ |x| x.color == :white }.take(yellow_sets).each(&:double!) puts end # RULE 14. Each set of three purple cards quadruples the value of one orange card. blue_sets = num_sets(:purple,3) if blue_sets > 0 puts "*** RULE 14 *** each set of 3 purple quadruples 1 orange" oranges_to_double = [orange,blue_sets].min puts "#{@name} has #{blue_sets} sets; quadrupling #{oranges_to_double} orange cards." @cards.select{ |x| x.color == :orange }.take(blue_sets).each(&:quadruple!) puts end # add up score @score = @cards.inject(0) { |sum,x| sum + x.value } + @bonus + @blue_hits * -10 # output puts "Final Tally for #{@name}" puts "================" + "=" * @name.size # compute score based on all the scoring attributes set by the scorer @cards.sort.each { |x| puts " %4d %s" % [x.value,x] } puts " %4d [bonus]" % [@bonus] puts "+ %4d [hits]" % [@blue_hits * -10] puts "----------------------" puts " %4d SUBTOTAL" % [@score] puts "x %4d [multiplier]" % [@hand_multiplier] puts "----------------------" @score *= @hand_multiplier puts " %4d TOTAL" % [@score] puts puts end # comparison, for sorting against other hands def <=>(other) # default is reverse sort (high to low) other.score <=> score end end #### #### initialize and validate hands #### puts "***************************" puts "*** V A L I D A T I O N ***" puts "***************************" puts # create hands from PlayerData (at the top of this file) player_hands = [] PlayerData.each do |r| # note some validation occurs in Hand creation player_hands << Hand.new(r.shift,r.shift.to_i,r.shift.to_i,r.shift.to_i,r.shift.to_i,r.shift.to_i) end #### #### Global Rules Arbitration #### puts "***********************************" puts "*** G L O B A L S C O R I N G ***" puts "***********************************" puts # RULE 1. Orange cards have a basic value of 4 and are equal to a pink card and a yellow card. # RULE 2. White cards have the highest basic value and are equal to a pink card and a purple card. # RULE 3. Purple cards have a basic value of twice that of yellow and half that of orange. # yellow = 1, purple = 2, pink = 3, orange = 4, and white = 5 puts "*** RULES 1, 2, and 3 *** basic values" puts "yellow = 1, purple = 2, pink = 3, orange = 4, and white = 5" puts # RULE 9. If a player hands in seven or more cards of the same color, he is eliminated from # the game. player_hands.each do |h| if h.largest_color >= 7 puts "*** RULE 9 *** seven or more cards of one suit causes elimination" puts "#{h.name} is eliminated (#{h})!" puts end end player_hands.reject! { |h| h.largest_color >= 7 } # do the actual elimination # RULE 8. The player with the most yellow cards gets a bonus of the number of cards squared. # (For example, if most yellow cards are 5, bonus is 25.) If two or more players tie for most # yellow, they are eliminated and bonus goes to the next highest. # figure out the highest yellow count max_yellow = player_hands.map(&:yellow).max # now select the players with that amount max_yellow_hands = player_hands.select{ |h| h.yellow == max_yellow } # now apply rule if max_yellow_hands.size > 1 puts "*** RULE 8 *** players tied for most yellow are eliminated!" max_yellow_hands.each { |h| puts "#{h.name} is eliminated (#{h})!" } player_hands = player_hands - max_yellow_hands puts elsif max_yellow_hands.size == 1 puts "*** RULE 8 *** player with most yellow gets a bonus of number of cards squared" max_yellow_hands[0].add_bonus!(max_yellow_hands[0].yellow ** 2) puts end # RULE 12. The player with the most pink cards doubles their value. In case of a tie, no # player collects the extra value. # first get the guy(s) with the most pink max_red = player_hands.map(&:pink).max max_red_hands = player_hands.select { |h| h.pink == max_red } # apply rule if max_red_hands.size > 1 puts "*** RULE 12 *** in case of a tie (for most pink), no player collects the extra value" max_red_hands.each { |h| puts "#{h.name} is tied (#{h.pink} pink cards)."} puts elsif max_red_hands.size == 1 puts "*** RULE 12 *** player with most pink cards doubles their value" max_red_hands[0].double_red! puts end # RULE 6. If a player has five or more purple cards, 10 points are deducted from every other # player's score. player_hands.each do |h| if h.purple >= 5 puts "*** RULE 6 *** five or more purple deducts 10 points from all other players" (player_hands - [h]).each { |x| x.blue_hit!(h.name) } puts end end #### #### output final hand summaries, after eliminations, eliminated cards, etc. #### puts "*********************************" puts "*** F I N A L E N T R I E S ***" puts "*********************************" puts player_hands.sort_by(&:name).each { |h| puts "#{h.name}: #{h}"} puts #### #### Local Rules (i.e. global scoring is complete, now score each hand) #### puts "*******************************" puts "*** H A N D S C O R I N G ***" puts "*******************************" puts # iterate through each hand and count up local scoring rules; apply to each hand player_hands.sort_by(&:name).each(&:calculate_score!) #### #### output final ranking #### puts "*******************************" puts "*** F I N A L S C O R E S ***" puts "*******************************" puts player_hands.sort.each_with_index { |h,i| puts "#{i+1}. #{h.name}: #{h.score} points" } puts