Pastie now auto-senses if line-wrap is a bad or good idea. Feedback?
## mark a section (Learn more)
class Frame attr_reader :score, :type def initialize(frame) @frame = frame @score = 0 @state = nil @bonus = Array.new @first_pin = nil end def display_score ret = if @state.nil? "" elsif @state == :strike "X" elsif @state == :spare "#{display_pin(@first_pin)}/" else "#{display_pin(@first_pin)}#{display_pin(@score-@first_pin)}" end if @frame == 10 @bonus.each {|b| ret << display_pin(b)} end return ret end def display_pin(pins) if pins == 0 return '-' elsif pins == 10 return 'X' else return pins.to_s end end def score_roll(pins) @first_pin ||= pins if @state.nil? @score += pins if @score == 10 @state = :strike else @state = :incomplete end elsif @state == :incomplete @score += pins if @score > 10 raise "Illegal roll in incomplete frame with score #{@score - pins}: #{pins}" end if @score == 10 @state = :spare else @state = :open end end end def finished? [:open, :strike, :spare].include?(@state) end def strike? @state == :strike end def spare? @state == :spare end def working? (@bonus.size < 1 && @state == :spare) || (@bonus.size < 2 && @state == :strike) end def bonus(pins) @bonus << pins @score += pins end end # Represents a bowling game class BowlingGame attr_reader :name, :frames # Create a bowling game for the given named player def initialize(name) @name = name @frames = Array.new(10) { |i| Frame.new(i+1) } @working = Array.new end # Score a roll of the given number of pins def score_roll(pins) # Find the current frame frame = @frames.find {|f| !f.finished? } # If we have no current frame and nothing is working, we are # scoring too many rolls if frame.nil? && @working.empty? raise "Too many rolls are being scored in this game." end # Score bonus pins for strikes and spares that are working @working.delete_if {|f| f.bonus(pins); !f.working? } # If we found no current frame, we are in bonus rolls of # the tenth frame return if !frame # Score this ball on the current frame and move it to # working if we rolled a spare or strike frame.score_roll(pins) if frame.spare? || frame.strike? @working << frame end end # Returns the current score of this game def score @frames.inject(0) {|score, f| score += f.score } end # Prints a ascii score sheet def print_score_sheet running_score = 0 print "+---" * 11, "+\r" puts @name @frames.each do |f| print "|%3s" % f.display_score end print "|%3s|\n" % self.score @frames.each do |f| running_score += f.score print "|%3s" % (f.finished? && !f.working? ? running_score.to_s : '') end print "| |\n" print "+---" * 11, "+\n" end end if __FILE__ == $0 name, *pins = *ARGV game = BowlingGame.new(name) pins.inject(game) {|game, p| game.score_roll(p.to_i); game} game.print_score_sheet end
This paste will be private.
From the Design Piracy series on my blog: