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
8 changes: 8 additions & 0 deletions 06.bowling_object/bowling.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

require_relative 'game'
require_relative 'frame'
require_relative 'shot'

game = Game.new(ARGV[0])
puts game.total_score
49 changes: 49 additions & 0 deletions 06.bowling_object/frame.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require_relative 'shot'

class Frame
attr_reader :first_shot, :second_shot, :third_shot

def initialize(first_score, second_score, third_score = nil)
@first_shot = Shot.new(first_score)
@second_shot = Shot.new(second_score)
@third_shot = third_score.nil? ? nil : Shot.new(third_score)
end

def total_score(next_frame, after_the_next_frame)
return [@first_shot, @second_shot, @third_shot].map(&:score).sum unless @third_shot.nil?

total_score = total_score_first_and_second
total_score += strike_bounus(next_frame, after_the_next_frame) if strike?
total_score += spare_bounus(next_frame) if spare?
total_score
end

def total_score_first_and_second
[@first_shot, @second_shot].map(&:score).sum
end

def strike_bounus(next_frame, after_the_next_frame)
if next_frame.strike?
# 9フレーム目の場合
return next_frame.total_score_first_and_second if after_the_next_frame.nil?

[next_frame.first_shot, after_the_next_frame.first_shot].map(&:score).sum
else
next_frame.total_score_first_and_second
end
end

def spare_bounus(next_frame)
next_frame.first_shot.score
end

def spare?
@first_shot.score + @second_shot.score == 10 && @first_shot.score != 10
end

def strike?
@first_shot.score == 10
end
end
37 changes: 37 additions & 0 deletions 06.bowling_object/game.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

require_relative 'frame'

class Game
ONE_TO_NICE_FRAMES_SHOTS_COUNT = 2 * 9
def initialize(row_scores)
framed_scores = parse_scores(row_scores)
# Frameクラス内のfirst_shot, second_shotとは命名の法則が異なっており統一感は無いが、
# ここでfirst_frame, second_frame・・・と宣言したら全体の記述が長くなり、
# むしろ可読性が落ちると判断したので配列で宣言
@frames = framed_scores.map do |score|
Frame.new(*score)
end
end

def total_score
total_score = 0
@frames.each_with_index do |frame, index|
next_frame, after_the_next_frame = @frames[index + 1, 2]
total_score += frame.total_score(next_frame, after_the_next_frame)
end
total_score
end

private

def parse_scores(row_scores)
scores = []
row_scores.split(',').each do |score|
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここを row_scores.split(',').take(18).each とすると、このブロックの内側の scores.length < 18 の判定や、 framed_scores = scores[0..17].each_slice(2).to_a[0..17] をなくせそうですね。

Copy link
Copy Markdown
Owner Author

@s-tone-gs s-tone-gs Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここを row_scores.split(',').take(18).each とすると

今回、row_scores.split(',')の結果返される配列の要素数に揺れがある(ストライク=Xと表現されていることが要因)ため、takeメソッドでの処理は難しいと判断しました。
現状の処理をより分かりやすく記述する方法が見つけられなかったため、この修正を保留しています。
もし何か良い方法があればご教授願いたいです🙇‍♂️

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、確かにこの時点ではxが未処理でしたね💦失礼いたしました。現状のままとしましょう。

scores.length < ONE_TO_NICE_FRAMES_SHOTS_COUNT && score == 'X' ? scores.push('X', '0') : scores << score
end
tenth_frame_scores = scores[ONE_TO_NICE_FRAMES_SHOTS_COUNT...scores.count]
framed_scores = scores.take(ONE_TO_NICE_FRAMES_SHOTS_COUNT).each_slice(2).to_a
framed_scores << tenth_frame_scores
end
end
9 changes: 9 additions & 0 deletions 06.bowling_object/shot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class Shot
attr_reader :score

def initialize(string_score)
@score = string_score == 'X' ? 10 : string_score.to_i
end
end
64 changes: 64 additions & 0 deletions 06.bowling_object/test_bowling.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

require_relative 'game'

def main
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

自動テストが書かれているので安心してリファクタリングできますね 👍

test_bowling_return_139
test_bowling_return_164
test_bowling_return_107
test_bowling_return_134
test_bowling_return_144
test_bowling_return_300
test_bowling_return_292
test_bowling_return_50
end

def test_bowling_return_139
template_test_bowling('6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,6,4,5', 139)
end

def test_bowling_return_164
template_test_bowling('6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,X,X,X', 164)
end

def test_bowling_return_107
template_test_bowling('0,10,1,5,0,0,0,0,X,X,X,5,1,8,1,0,4', 107)
end

def test_bowling_return_134
template_test_bowling('6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,X,0,0', 134)
end

def test_bowling_return_144
template_test_bowling('6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,X,1,8', 144)
end

def test_bowling_return_300
template_test_bowling('X,X,X,X,X,X,X,X,X,X,X,X', 300)
end

def test_bowling_return_292
template_test_bowling('X,X,X,X,X,X,X,X,X,X,X,2', 292)
end

def test_bowling_return_50
template_test_bowling('X,0,0,X,0,0,X,0,0,X,0,0,X,0,0', 50)
end

def template_test_bowling(input, expected)
game = Game.new(input)
puts "入力したスコア:#{input}"
puts "expected: #{expected}"

total_score = game.total_score
puts "actual: #{total_score}"

if total_score == expected
puts '正常'
else
puts '異常'
end
puts ''
end

main