【Rubyで解く競プロ】 ボウリングのスコア計算

## はじめに
ボウリングのスコア計算をするプログラムをRubyで書きました。
競技プログラミング初心者向けになっていると思います。
競プロ始めたての方(もしくはRuby始めたての方)には、楽しんで読んでもらえると思います。

## [問題] ボウリングスコア計算

**ボウリングのスコア計算をせよ**
![bowlingscore1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305294/57fa6f06-5cda-38a2-91bb-a811808f1c92.jpeg)

**[入力]**

- 一行の文字列で与えられる。
- 文字と文字の間にはスペースが含まれる。
- 文字は、`0 <= n <=10`のいずれかである。
- 数字は倒れたピン数を表す。

```txt:入力例
"3 7 10 8 2 8 1 8 1 6 3 8 2 8 2 9 1 9 1 9"
```

**[スコア計算のルール]**

- ピン数は１０本であり、最終フレームは１０フレーム目である。
- １投目に全てのピンを倒すことをストライク、１フレーム内で全てのピンを倒すことをスペアと呼ぶ。
- 通常は、２投で１フレームである。
- 最終フレーム以外、ストライクの場合は１投でフレームを終え、次の２投先のスコアを、ストライクを取ったスコアに加算する。 
- 最終フレーム以外、スペアの場合は、次の１投先のスコアをスペアを取ったスコアに加算する。
- 最終フレームは、２投目までに全ピンを倒すことができた場合のみ、３投する。
- 最終フレームは、ストライク、スペアの特別加算をしない。

詳しいルール説明は、[こちら](http://park1.wakwak.com/~shiraki/bowling/score.html)を参照。

**[出力]**
最終的なスコアを計算し、出力せよ。

```txt:出力例
160
```

## 解き方

ここからは解き方を説明します。
少々長くなるので、答えをさっと知りたい方は、「完成プログラム」と書かれているところまでスクロールしてください。


この問題を解く際、私は次のステップでコーディングする必要があると考えました。

1. フレーム毎にスコアを分ける
2. ストライク、スペアの場合の特別加算について考える
3. 全フレームのスコアの合計値を出す

一つずつ説明します。

**フレーム毎にスコアを分ける**

まずは、入力値を分解し、フレーム毎にまとめます。
ここで注意しないといけないのは、１フレームは必ずしも２投からなるわけでは無いということです。
ストライクを取った場合は１投で１フレーム、最終フレームでは２投目までにストライクかスペアを出した場合は３投で１フレームになります。
そのため、「入力値をsplitして２個ずつまとめる」といったような単純な実装ではいけません。
ここがこの問題の最初の壁です。

フレームが切り替わるタイミングを考えてみます。

- ２投した場合
- ストライクを取った場合

フレームが切り替わるタイミングは、以上の２つのうちのどれかです。
フレームは１０フレームが最後であるとわかっています。そのため、フレーム数をカウントする変数を用意し、「ストライクを取った場合」、「ストライクを取らずに２投した場合」でそれぞれフレーム数を一つずつ増やせば、最終フレームまで分割することができます。

一旦、ここまでの流れをプログラムで見てみましょう。

```rb:bowling.rb
# 入力値をスペース区切りで分割し、数値型の配列にする
input_array = gets.split(" ").map(&:to_i)
# => [3, 7, 10, 8, 2, 8, 1, 8, 1, 6, 3, 8, 2, 8, 2, 9, 1, 9, 1, 9]

# フレーム数
frame = 0
# 最後のフレーム数
max_frame = 10
# フレーム内での投数
count = 0

input_array.length.times do |i|
  # 最終フレームでは無い場合
  if frame + 1 != max_frame
    # ストライクの場合
    if input_array[i] == 10 && count == 0
      # ストライクの時は、１投で２回分投げたとみなす
      count = 1
    end
  end
  
  count += 1
  
  if frame + 1 == max_frame
    # 最終フレームの処理
  else
   # フレームの切り替え
   if count == 2
     count = 0
     frame += 1
   end
  end
end
```

**ストライク、スペアの特別加算**

スコア計算のルールをもう一度おさらいします。
ストライクのときは、次の２投の倒したピンをストライクを出したフレームで加算します。
スペアのときは、次の１投の倒したピンを、スペアを出したフレームで加算します。

ストライクとスペアの区別は、フレーム内での１投目で１０ピンを倒したか、２投目で倒したかでわかります。

先ほど、フレーム毎にスコアを区切るプログラムを書いたので、ストライクとスペアを区別できるようになったはずです。


次に、ストライクとスペアの特別計算をします。
これも難しく考える必要はなく、ストライクは次の２投分を加算し、スペアの場合は次の１投分を加算してあげると良いです。
ストライクとスペアを見分けることができれば、特別計算のロジックはそれほど苦労せずとも組めます。

ここで、注意しなければならないのは、最終フレームでは特別加算が行われないということです。
これも、フレーム数がわかっていれば簡単に実装できます。

この問題は、投球をフレーム毎に分けることができたら、ゴールに一気に近づけます。

あとは、フレーム毎にあるスコアの合計を求めれば、プログラムの完成です。



**完成プログラム**

```rb:bowling.rb
# 入力値をスペース区切りで分割し、数値型の配列にする
input_array = gets.split(" ").map(&:to_i)
# => [3, 7, 10, 8, 2, 8, 1, 8, 1, 6, 3, 8, 2, 8, 2, 9, 1, 9, 1, 9]
# フレーム数
frame = 0
# 最後のフレーム数
max_frame = 10
# フレーム内での投数
count = 0
# フレーム毎のスコアをもつ配列
score = [0] * max_frame

input_array.length.times do |i|

  # 最終フレームでは無い場合
  if frame + 1 != max_frame

    if input_array[i] == 10
      if count == 0   # ２投目で１０ピン倒した場合はスペアになることを考慮
        # ストライクの時は、１投で２回分投げたとみなす
        count = 1
        score[frame] = input_array[i] + input_array[i+1] + input_array[i+2]
      else   # スペアの場合(1)
        score[frame] = input_array[i] + input_array[i+1]
      end
    # スペアの場合 (2)
    elsif input_array[i] + score[frame] == 10
      score[frame] = score[frame] + input_array[i] + input_array[i+1]
    else   # ストライク、スペア以外の場合
      score[frame] += input_array[i]
    end

    count += 1
    # フレームの切り替え処理
    if count == 2
      count = 0
      frame += 1
    end

  # 最終フレームの処理
  else
    score[frame] += input_array[i]
  end
end

# 最終スコアを出力
puts score.sum
# => 160
```

## 最後に

毎度のことですが、正直私のプログラムはまだまだリファクタできるところがたくさんあると思います。
よろしければ、コメントで教えて頂ければ幸いです。

最後まで読んで頂きありがとうございました。