Created
January 19, 2015 06:50
-
-
Save anicholson/f0164c3948c96311cf40 to your computer and use it in GitHub Desktop.
Bowling Kata
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Bowling | |
class EmptyFrame | |
def pin_total | |
0 | |
end | |
def scores | |
[0,0] | |
end | |
def strike?; false; end | |
def spare?; false; end | |
def last?; true; end | |
end | |
class Frame | |
def initialize(last = false) | |
@scores = [] | |
@last = last | |
end | |
def last? | |
@last | |
end | |
def full? | |
if last? | |
if (scores.first == 10) || (scores.count >= 2 && (scores[0] + scores[1] == 10)) | |
scores.count == 3 | |
else | |
scores.count == 2 | |
end | |
else | |
strike? || spare? || (@scores.count == 2) | |
end | |
end | |
def record(pins) | |
@scores << pins | |
end | |
def spare? | |
@scores.count == 2 && pin_total == 10 | |
end | |
def strike? | |
@scores.count == 1 && pin_total == 10 | |
end | |
def pin_total | |
@scores.reduce(0, &:+) | |
end | |
def scores | |
@scores | |
end | |
def next | |
@next_frame | |
end | |
def next=(frame) | |
@next_frame = frame | |
end | |
end | |
def initialize | |
@score_history = [] | |
@frames = [] | |
@current_frame = Frame.new | |
end | |
def hit(pins) | |
raise ArgumentError unless pins.is_a? Integer | |
raise ArgumentError unless pins >= 0 && pins <= 10 | |
@score_history << pins | |
@current_frame.record(pins) | |
if @current_frame.full? | |
@frames << @current_frame | |
@current_frame.next = Frame.new(last_frame?) | |
@current_frame = @current_frame.next | |
end | |
end | |
def last_frame? | |
@frames.count == 9 | |
end | |
def score | |
score = 0 | |
@frames.each_with_index do |frame, idx| | |
next_frame = @frames[idx + 1] || EmptyFrame.new | |
the_frame_after_that = @frames[idx + 2] || EmptyFrame.new | |
next_score = if frame.strike? | |
if frame.last? | |
frame.pin_total | |
else | |
if next_frame.strike? | |
20 + the_frame_after_that.scores.first | |
elsif next_frame.last? | |
10 + next_frame.scores[0] + next_frame.scores[1] | |
else | |
10 + next_frame.pin_total | |
end | |
end | |
elsif frame.spare? | |
if frame.last? | |
frame.pin_total + frame.scores.last | |
else | |
10 + next_frame.scores.first | |
end | |
else | |
frame.pin_total | |
end | |
score += next_score | |
puts "Frame #{idx}: #{next_score} (#{score})" | |
end | |
score | |
end | |
private | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'bowling' | |
describe Bowling do | |
describe '#hit' do | |
it { is_expected.to respond_to :hit } | |
it 'rejects non-numeric args' do | |
expect { subject.hit('aha') }.to raise_exception | |
end | |
it 'rejects negative numbers' do | |
expect { subject.hit(-1) }.to raise_exception | |
end | |
it 'rejects higher scores than there are pins to hit' do | |
expect { subject.hit(11) }.to raise_exception | |
end | |
end | |
describe '#score' do | |
it { is_expected.to respond_to :score } | |
it 'scores 0 for an all-gutter game' do | |
20.times { subject.hit 0 } | |
expect(subject.score).to eq(0) | |
end | |
it 'scores the sum of pins for a game with no strikes or spares' do | |
attempts = (1..20).map {|_| rand(5) } | |
attempts.each {|attempt| subject.hit attempt } | |
puts attempts | |
expect(subject.score).to eq(attempts.reduce(0, &:+)) | |
end | |
it 'adds the next score on when a spare is scored' do | |
subject.hit 6 | |
subject.hit 4 # spare | |
subject.hit 8 | |
subject.hit 0 | |
expect(subject.score).to eq(26) | |
end | |
it 'only recognises scores on frame boundaries' do | |
subject.hit 6 | |
subject.hit 3 | |
subject.hit 7 # not a spare | |
subject.hit 1 | |
expect(subject.score).to eq(17) | |
end | |
it 'adds the next two scores on when a strike is scored' do | |
subject.hit 10 # Strike! | |
subject.hit 3 | |
subject.hit 6 | |
expect(subject.score).to eq(28) | |
end | |
it 'handles turkeys' do | |
3.times { subject.hit 10 } | |
subject.hit 0 | |
subject.hit 9 | |
expect(subject.score).to eq(78) | |
end | |
it 'handles strikes after spares' do | |
subject.hit 7 | |
subject.hit 3 # spare | |
subject.hit 10 # Strike! | |
expect(subject.score).to eq(30) | |
end | |
it 'handles spares after strikes' do | |
subject.hit 10 # Strike! | |
subject.hit 5 | |
subject.hit 5 # spare | |
expect(subject.score).to eq(30) | |
end | |
it 'scores 270 for a 9-frame perfect' do | |
9.times { subject.hit 10 } | |
subject.hit 0 | |
subject.hit 0 | |
expect(subject.score).to eq(240) | |
end | |
it 'scores 300 for a perfect game' do | |
12.times { subject.hit 10 } | |
expect(subject.score).to eq(300) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment