502 lines
16 KiB
Ruby
Executable File
502 lines
16 KiB
Ruby
Executable File
#!/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
|
|
|
|
|