Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

source "https://rubygems.org"

# gem "rails"

gem "rspec", "~> 3.12"
26 changes: 26 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.5.0)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
rspec-mocks (~> 3.12.0)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-support (3.12.0)

PLATFORMS
arm64-darwin-22

DEPENDENCIES
rspec (~> 3.12)

BUNDLED WITH
2.4.12
15 changes: 15 additions & 0 deletions lib/frame.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Frame
attr_reader :scores

def initialize(*scores)
@scores = scores
end

def strike?
@scores.first == 10
end

def spare?
!strike? && @scores.sum == 10
end
end
46 changes: 46 additions & 0 deletions lib/scorecard.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require 'frame'

class Scorecard
def initialize
@frames = []
end

def add_frame(*scores)
@frames << Frame.new(*scores)
end

def calculate_score
@frames[0..9].reduce(0) { |sum, frame| sum + get_frame_score(frame) }
end

def perfect_game?
calculate_score == 300
end

def gutter_game?
calculate_score == 0
end

private

def get_frame_score(frame)
i = @frames.index(frame)
score = @frames[i].scores.sum
return score + get_next_roll(i) if frame.spare?
return score + get_next_two_rolls(i) if frame.strike?
return score
end

def get_next_roll(current_frame_index)
@frames[current_frame_index + 1].scores.first
end

def get_next_two_rolls(current_frame_index)
score = get_next_roll(current_frame_index)
if @frames[current_frame_index + 1].scores.length == 1
return score + @frames[current_frame_index + 2].scores.first
else
return score + @frames[current_frame_index + 1].scores.last
end
end
end
68 changes: 68 additions & 0 deletions spec/frame_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
require 'frame'

RSpec.describe Frame do
context 'when initialized with two zeros' do
it 'has those scores' do
frame = Frame.new(0, 0)
expect(frame.scores).to eq [0, 0]
end

it 'is not a strike' do
frame = Frame.new(0, 0)
expect(frame.strike?).to eq false
end

it 'is not a spare' do
frame = Frame.new(0, 0)
expect(frame.spare?).to eq false
end
end

context 'when initialised with two numbers that add up to 9' do
it 'has those scores' do
frame = Frame.new(4, 5)
expect(frame.scores).to eq [4, 5]
end

it 'is not a strike' do
frame = Frame.new(4, 5)
expect(frame.strike?).to eq false
end

it 'is not a spare' do
frame = Frame.new(4, 5)
expect(frame.spare?).to eq false
end
end

context 'when initialised with two numbers that add up to 10' do
it 'is a spare' do
frame = Frame.new(2, 8)
expect(frame.spare?).to eq true
end
end

context 'when initialised with another two numbers that add up to 10' do
it 'is also a spare' do
frame = Frame.new(9, 1)
expect(frame.spare?).to eq true
end
end

context 'when initialised with 10' do
it 'has that score' do
frame = Frame.new(10)
expect(frame.scores).to eq [10]
end

it 'is a strike' do
frame = Frame.new(10)
expect(frame.strike?).to eq true
end

it 'is not a spare' do
frame = Frame.new(10)
expect(frame.spare?).to eq false
end
end
end
220 changes: 220 additions & 0 deletions spec/integration/scorecard_frame_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
require 'frame'
require 'scorecard'

describe 'integration' do
context 'when there are no spares or strikes' do
it 'calculates the score' do
scorecard = Scorecard.new
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)

expect(scorecard.calculate_score).to eq 30
end
end

context 'when there are spares' do
it 'calculates the score' do
scorecard = Scorecard.new
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 9)
scorecard.add_frame(3, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(5, 5)
scorecard.add_frame(9, 0)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)

expect(scorecard.calculate_score).to eq 64
end
end

context 'when there are strikes' do
it 'calculates the score' do
scorecard = Scorecard.new
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(10)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(10)
scorecard.add_frame(3, 2)

expect(scorecard.calculate_score).to eq 54
end
end

context 'when there are multiple strikes in a row' do
it 'calculates the score' do
scorecard = Scorecard.new
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(3, 2)
scorecard.add_frame(3, 2)

expect(scorecard.calculate_score).to eq 89
end
end

context 'when 10th frame is a spare' do
it 'adds the first endgame bonus score to the total score' do
scorecard = Scorecard.new
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(3, 2)
scorecard.add_frame(3, 7)
# bonus
scorecard.add_frame(3)

expect(scorecard.calculate_score).to eq 97
end
end

context 'when 10th frame is a strike' do
it 'adds two endgame bonus scores to the total score' do
scorecard = Scorecard.new
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(3, 2)
scorecard.add_frame(10)
# bonus
scorecard.add_frame(3, 10)

expect(scorecard.calculate_score).to eq 107
end
end

context 'the game ends with two strikes in a row' do
it 'the first endgame bonus is used as a bonus for the ninth frame' do
scorecard = Scorecard.new
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(1, 2)
scorecard.add_frame(10)
scorecard.add_frame(10)
# bonus
scorecard.add_frame(3, 10)

expect(scorecard.calculate_score).to eq 125
end
end

context 'the game is all strikes' do
it 'is a perfect game with score of 300' do
scorecard = Scorecard.new
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
# bonus
scorecard.add_frame(10, 10)

expect(scorecard.calculate_score).to eq 300
expect(scorecard.perfect_game?).to eq true
expect(scorecard.gutter_game?).to eq false
end
end

context 'the player never hits a pin' do
it 'is a gutter game with a score of 0' do
scorecard = Scorecard.new
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)

expect(scorecard.calculate_score).to eq 0
expect(scorecard.perfect_game?).to eq false
expect(scorecard.gutter_game?).to eq true
end
end

context 'the player hits one pin' do
it 'is not a gutter game' do
scorecard = Scorecard.new
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 1)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)
scorecard.add_frame(0, 0)

expect(scorecard.calculate_score).to eq 1
expect(scorecard.perfect_game?).to eq false
expect(scorecard.gutter_game?).to eq false
end
end

context 'the player doesn\'t get all strikes' do
it 'is not a perfect game' do
scorecard = Scorecard.new
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(9, 1)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
scorecard.add_frame(10)
# bonus
scorecard.add_frame(10, 10)

expect(scorecard.calculate_score).to eq 279
expect(scorecard.perfect_game?).to eq false
expect(scorecard.gutter_game?).to eq false
end
end
end
4 changes: 4 additions & 0 deletions spec/scorecard_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
require 'scorecard'

RSpec.describe Scorecard do
end
Loading