以前、Rubyの標準ライブラリのみで実装したボウリングプログラムを、オブジェクト指向版として実装しました。
1. 要件(ルール)
- 1ゲーム=10フレーム
- 1フレーム=2投(例外あり)
- ピンの数は10本
- 1投目で10本倒したらストライク
- ストライクの場合は2投目は表記しない
- 1投目で全て倒せなかった時、2投目で全て倒したらスペア
- スペアのフレームの得点は次の1投の点を加算する
- 例: 6 4 5 = 15
- 例: 6 4 10 = 20
- ストライクのフレームの得点は次の2投の点を加算する
- 例: 10 5 2 = 17
- 例: 10 10 10 = 30
- 10フレーム目は1投目がストライクもしくは2投目がスペアだった場合、3投目が投げられる
- ありえない投球数やありえない数字・記号がこない前提
bowling.rb
(実行ファイル)
#!/usr/bin/env ruby # frozen_string_literal: true require_relative 'game' shots = ARGV[0].split(',') game = Game.new(shots) puts game.score
ARGV[0]
でコマンドライン引数を取得し、カンマで区切られたショットのデータを配列に分割。- Gameクラスのインスタンスを生成し、ショットのデータを渡してゲームを開始。
- 最後に、ゲームのスコアを表示。
game.rb
# frozen_string_literal: true require_relative 'frame' class Game def initialize(shots) @shots = shots end def score @frames = create_frames total_score = 0 (0..9).each do |frame_index| frame = Frame.new(@frames[frame_index]) total_score += frame.score @frames[frame_index + 1] ||= [] @frames[frame_index + 2] ||= [] total_score += calculate_bonus_points(frame_index, frame) end total_score end def create_frames frame = [] frames = [] @shots.each do |s| frame << s if frames.length < 10 if frame.length == 2 || s == 'X' frames << frame.dup frame.clear end else frames.last << s end end frames end def calculate_bonus_points(frame_index, frame) if frame.strike? next_two_shots = (@frames[frame_index + 1] + @frames[frame_index + 2]).slice(0, 2) next_two_shots.sum { |s| Shot.new(s).score } elsif frame.spare? next_shot = @frames[frame_index + 1][0] Shot.new(next_shot).score else 0 end end end
initialize
メソッドでは、ショットのデータを受け取り、インスタンス変数@shots
に格納。score
メソッドはゲームのスコアを計算して返す。- まず、
create_frames
メソッドを呼び出してフレームの配列@frames
を作成。 - 次に、
total_score
変数を0で初期化。10フレーム分の処理を繰り返し、各フレームのスコアを計算し、total_score
に加算。 - また、ボーナスポイントも計算して加算。
- 最後に、計算された総合スコアを返す。
- まず、
create_frames
メソッドは、ショットのデータをフレームごとに分割し、フレームの配列frames
を作成。frame
配列にショットを追加していき、フレーム数が10未満の場合はフレームが完了した時点で新しいフレームを作成し、フレーム数が10の場合は最後のフレームに追加のショットを結合。
calculate_bonus_points
メソッドは、フレームのインデックスとフレームオブジェクトを受け取り、ボーナスポイントを計算して返す。 また、与えられたフレームがストライクの場合とスペアの場合で異なるボーナスポイントの計算を行う。- まず、フレームがストライクの場合は、次の2つのフレームのショットを取得し、それらのスコアを計算してボーナスポイントとする。
@frames[frame_index + 1]
と@frames[frame_index + 2]
を連結して、最初の2つのショットを取得。 - そして、それぞれのショットを
Shot
クラスのインスタンスとして生成し、スコアを取得。取得したスコアを合計した値がボーナスポイントとなる。
- まず、フレームがストライクの場合は、次の2つのフレームのショットを取得し、それらのスコアを計算してボーナスポイントとする。
- フレームがスペアの場合は、次のフレームの最初のショットのスコアをボーナスポイントとする。
@frames[frame_index + 1]
の最初の要素を取得し、それをShot
クラスのインスタンスとして生成し、スコアを取得。 - 最後に、フレームがストライクでもスペアでもない場合は、ボーナスポイントを0とする。 これにより、ゲームのスコア計算においてストライクやスペアの場合のボーナスポイントが正しく計算され、総合スコアに加算される。
※ (0..9).each do |frame_index|...end
の箇所については、injectを使っても良い。
frame.rb
# frozen_string_literal: true require_relative 'shot' class Frame def initialize(frame) @first_shot = Shot.new(frame[0]) @second_shot = Shot.new(frame[1]) @third_shot = Shot.new(frame[2]) end def score [ @first_shot.score, @second_shot.score, @third_shot.score ].sum end def strike? @first_shot.score == 10 end def spare? score == 10 end end
initialize
メソッドでは、frame
という引数を受け取り、その値を元にフレーム内の各ショットを表すインスタンス変数@first_shot
、@second_shot
、@third_shot
を作成。score
メソッドは、各ショットのスコアを取得し、それらの合計値を返す。strike?
メソッドは、フレームの最初のショットがストライクであるかどうかを確認。spare?
メソッドは、フレームのスコアがスペアであるかどうかを確認。
shot.rb
# frozen_string_literal: true class Shot def initialize(mark) @mark = mark end def score @mark == 'X' ? 10 : @mark.to_i end end
initialize
メソッドでは、mark
という引数を受け取り、その値をインスタンス変数@mark
に格納。score
メソッドは、ショットのスコアを計算して返す。もし@mark
が'X'(ストライク)であれば10を返し、そうでなければ@mark
を整数に変換して返す。