exercism/ruby

Bowling: missing test

gemp opened this issue · 1 comments

gemp commented

I stumbled upon a case that wasn't covered by the bowling test suite. This solution adds the next frame bonus to a spare followed by a strike, i.e: [5,5,10,+6][10,6][6,0]... All current tests pass with this bug.

Proposed test:

  def test_a_spare_followed_by_a_strike_should_not_get_bonus_from_next_frame 
    game = Game.new
    rolls = [5, 5, 10, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    rolls.each { |pins| game.roll(pins) }
    assert_equal 42, game.score
  end
kotp commented
class Game

  def initialize
    @frames = Hash.new { |frame, key| frame[key] = Frame.new(key) }
    @frames[1]
  end

  def roll(pins)
    game_should_not_be_over
    next_frame if current_frame.complete?
    current_frame.add(pins)
    add_bonus if current_frame.complete?
  end

  def score
    game_must_be_over
    @frames.values.map(&:score).sum
  end

private

  def current
    @frames.keys.max
  end

  def current_frame
    @frames[current]
  end

  def next_frame
    @frames[current.next]
  end

  def previous(prev = 1)
    current >= prev ? @frames[current - prev] : Frame.new(0)
  end

  def add_bonus
    previous.add_bonus(current_frame.points(2))
    previous(2).add_bonus(current_frame.points(1), previous.strike?)
  end

  def game_over
    current_frame.last? && current_frame.complete?
  end

  def game_must_be_over
    raise BowlingError, "The game is not over" unless game_over
  end

  def game_should_not_be_over
    raise BowlingError, "The game is over" if game_over
  end


  class Frame

    def initialize(turn)
      @points = []
      @complete = false
      @last = turn == 10
    end

    def add(pins)
      validate(pins)
      @points << pins
      @complete = @points.size == rolls_to_complete
    end

    def points(max = 3)
      @points.first(max)
    end

    def score
      @points.sum
    end

    def add_bonus(rolls, should_add = true)
      return unless bonus? && should_add
      bonus = strike? ? rolls.sum : rolls.first
      @points << bonus
    end

    def bonus?
      spare? || strike?
    end

    def strike?
      @points.first == 10
    end

    def spare?
      !strike? && score >= 10
    end

    def complete?
      @complete
    end

    def last?
      @last
    end

  private

    def rolls_to_complete
      if last? then bonus? ? 3 : 2 else strike? ? 1 : 2 end
    end

    def validate(pins)
      incoherent_pins = !pins.between?(0,10)
      invalid = last? ? score < 20 && score + pins > 20 : score + pins > 10
      raise BowlingError, "Wrong number of pins" if incoherent_pins || invalid
    end

  end


  class BowlingError < StandardError
  end

end

Posted here for "we are talking about it here" convenience.