Skip to content

Instantly share code, notes, and snippets.

@kakutani
Last active February 24, 2021 14:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kakutani/096e0ecbbbf81e76c73ac82f74a30f4c to your computer and use it in GitHub Desktop.
Save kakutani/096e0ecbbbf81e76c73ac82f74a30f4c to your computer and use it in GitHub Desktop.
end-less methods bowling (object-oriented)
# frozen_string_literal: true
module Bowling
class Game
def initialize(marks) = (@frame_builder = Frame.builder(marks))
def score = frames.sum(&:score)
private
def frames = (@frames ||= Array.new(10){ build_frame(final?(_1)) })
def build_frame(final) = @frame_builder.call(final)
def final?(index) = (index == 9)
end
class Frame
def self.builder(marks) = Builder.new(marks)
def initialize(rolls:, bonus:) = (@rolls, @bonus = rolls, bonus)
def score = (@rolls + @bonus).sum
module Bonus
class << self
def frame_if(sym, pins) = (bonus?(sym, pins) && bonus(sym).frame(pins))
def strike?(pins) = bonus?(:strike, pins)
private
def bonus?(sym, pins) = bonus(sym).satisfied?(pins)
def bonus(sym) = const_get(sym.capitalize)
end
module Strike
class << self
def point = 10
def satisfied?(pins) = (pins.first == Strike.point)
def frame(pins) = {rolls: [pins.shift], bonus: pins.take(2)}
end
end
module Spare
class << self
def satisfied?(pins) = (!Bonus.strike?(pins) && (pins.take(2).sum == 10))
def frame(pins) = {rolls: pins.shift(2), bonus: pins.take(1)}
end
end
module MarkXasStrike
refine String do def to_i(base = 10) = (self == 'X' ? Strike.point : super) end
end
end
class Builder
def initialize(marks) = (@marks = marks)
def call(final) = Frame.new(**(final ? final_frame : nonfinal_frame))
private
def final_frame = {rolls: pins.shift(3), bonus: []}
def nonfinal_frame = (frame_if(:strike) || frame_if(:spare) || open_frame)
def frame_if(bonus) = Bonus.frame_if(bonus, pins)
def open_frame = {rolls: pins.shift(2), bonus: []}
def pins = (@pins ||= parse_marks)
using Bonus::MarkXasStrike
def parse_marks = @marks.split(',').map(&:to_i)
end
end
end
# frozen_string_literal: true
require 'minitest/autorun'
require 'minitest/pride'
require_relative 'bowling'
class BowlingTest < Minitest::Test
def calculate_score(marks)
Bowling::Game.new(marks).score
end
def test_score1
marks = '6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,6,4,5'
assert_equal 139, calculate_score(marks)
end
def test_score2
marks = '6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,X,X,X'
assert_equal 164, calculate_score(marks)
end
def test_score3
marks = '0,10,1,5,0,0,0,0,X,X,X,5,1,8,1,0,4'
assert_equal 107, calculate_score(marks)
end
def test_score4
marks = '6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,X,0,0'
assert_equal 134, calculate_score(marks)
end
def test_perfect_game
marks = 'X,X,X,X,X,X,X,X,X,X,X,X'
assert_equal 300, calculate_score(marks)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment