初心者がボウリングゲームをRubyのみで書いてみた

# はじめに

こんにちは。
一般事務からエンジニアへ転職するために基礎学習しています。
Ruby演習として、VSコード上でRubyのみにて
「Bowling Game ~本日のラッキーボーイ＆ガールは一体誰だ！~」
を作成しました。
まだまだひよっこですが、初投稿やってみます。

# 何を書いたか
ボウリングゲーム の　コード　(Ruby)

<仕様>

- 実際の球を投げる行為は、毎回、ランダムな数字が生成および表示されて、それを入力(半角数字)することとした。
 表示されたランダムな数字と入力した数字が異なる場合は、スコアは0となる。
 (スコアを0とさせることで、入力した数字が10以下であるかどうかの判定用コードは作成していない。)
- ボウリングゲーム のプレイヤーは何人でも可能である。
- 毎回、ユーザーごとに１投目２投目(１０ゲームは3投目がある場合もある)を入力してもらった後、そのゲームまでの表を表示する。
- 合計スコアが確定していない時は、"..."で表示される。
- ストライクとスペアの時は、"ナイスストライク！""ナイススペア！"と表示する。(※１)
- 最後、全員の順位と勝者名が表示される。

1. ( = ※1 )
マークの表示切替は今回していない。(マーク：ストライク、スペア、ガーター)
2. ( = ※2 )
初心者にとっては、完成までは、コードを書換えして、動作が正しいか確認する時に有効的である。また、ゲームを楽しみたい時にも、コードを書換えして、好きな数字を入力して楽しむこともできる。（途中ズルして高い得点にして、逆転することもできるようになる）



# なぜ書いたか
Rubyの基礎学習を終了したため、ボウリングゲーム作成してみました。
WEB検索して、Rubyだけで完結しているボウリングゲームを見つけることができなかったため、記事にします。

今回の目的は、とりあえず完成させることを目的としていました。
コードが長くなってしまいましたが、Rubyのみで作成したい初心者の方の力に少しでもなれたらと思います。



# 本題
### VSコード実行方法
```shell:tarminal
ruby ~/ファイル場所/bowling_game.rb
```

### 完成図
```shell:tarminal
ようこそ BowlingGameへ


このBowlingGameのスコアは全てランダムで決定される運ゲーです

 ※ ここからの数字は、全て半角で入力ください


Let us BowlingGame Start！！

~本日のラッキーボーイ＆ガールは一体誰だ！~


プレイヤーの数を入力してください
2

Player1人目の名前を入力してください
エビ

Player2人目の名前を入力してください
カニ

ここから先のスコアは、ランダムで表示されます
表示された数字を入力ください

エビさん　1game 1投目は4です
4

エビさん　1game 2投目は3です
3
+------------------+
|   Bowling_Game   |
+------+-----------+
| name |   game1   |
+------+-----------+
|      |   4 | 3   |
| エビ  | --------- |
|      |     7     |
+------+-----------+

カニさん　1game 1投目は7です
7

カニさん　1game 2投目は1です
1
+------------------+
|   Bowling_Game   |
+------+-----------+
| name |   game1   |
+------+-----------+
|      |   7 | 1   |
| カニ  | --------- |
|      |     8     |
+------+-----------+

エビさん　2game 1投目は10です
10 

ナイスストライク！

+------------------------------+
|         Bowling_Game         |
+------+-----------+-----------+
| name |   game1   |   game2   |
+------+-----------+-----------+
|      |   4 | 3   |  10 | 0   |
| エビ  | --------- | --------- |
|      |     7     |    ...    |
+------+-----------+-----------+

カニさん　2game 1投目は5です
5

カニさん　2game 2投目は2です
2
+------------------------------+
|         Bowling_Game         |
+------+-----------+-----------+
| name |   game1   |   game2   |
+------+-----------+-----------+
|      |   7 | 1   |   5 | 2   |
| カニ  | --------- | --------- |
|      |     8     |    15     |
+------+-----------+-----------+

エビさん　3game 1投目は5です
5

エビさん　3game 2投目は1です
1

.
.
.
.

エビさん　9game 1投目は0です
0

エビさん　9game 2投目は7です
7
+------------------------------------------------------------------------------------------------------------------+
|                                                   Bowling_Game                                                   |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| name |   game1   |   game2   |   game3   |   game4   |   game5   |   game6   |   game7   |   game8   |   game9   |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
|      |   4 | 3   |  10 | 0   |   5 | 1   |   6 | 4   |   6 | 3   |   4 | 0   |   4 | 5   |   9 | 1   |   0 | 7   |
| エビ  | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- |
|      |     7     |    23     |    29     |    45     |    54     |    58     |    67     |    77     |    84     |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+

カニさん　9game 1投目は6です
6

カニさん　9game 2投目は4です
4

ナイススペア！

+------------------------------------------------------------------------------------------------------------------+
|                                                   Bowling_Game                                                   |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| name |   game1   |   game2   |   game3   |   game4   |   game5   |   game6   |   game7   |   game8   |   game9   |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
|      |   7 | 1   |   5 | 2   |   9 | 0   |  10 | 0   |   9 | 0   |   1 | 1   |   7 | 3   |   7 | 0   |   6 | 4   |
| カニ  | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- |
|      |     8     |    15     |    24     |    43     |    52     |    54     |    71     |    78     |    ...    |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+

エビさん　10game 1投目は2です
2

エビさん　10game 2投目は6です
6
+------------------------------------------------------------------------------------------------------------------------------+
|                                                         Bowling_Game                                                         |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| name |   game1   |   game2   |   game3   |   game4   |   game5   |   game6   |   game7   |   game8   |   game9   |  game10   |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
|      |   4 | 3   |  10 | 0   |   5 | 1   |   6 | 4   |   6 | 3   |   4 | 0   |   4 | 5   |   9 | 1   |   0 | 7   | 2 | 6 | 0 |
| エビ  | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- |
|      |     7     |    17     |    23     |    39     |    48     |    52     |    61     |    71     |    78     |    86     |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+

カニさん　10game 1投目は2です
2

カニさん　10game 2投目は2です
2
+------------------------------------------------------------------------------------------------------------------------------+
|                                                         Bowling_Game                                                         |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| name |   game1   |   game2   |   game3   |   game4   |   game5   |   game6   |   game7   |   game8   |   game9   |  game10   |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
|      |   7 | 1   |   5 | 2   |   9 | 0   |  10 | 0   |   9 | 0   |   1 | 1   |   7 | 3   |   7 | 0   |   6 | 4   | 2 | 2 | 0 |
| カニ  | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- |
|      |     8     |    15     |    24     |    34     |    43     |    45     |    62     |    69     |    81     |    85     |
+------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+

ゲームエンド！

今回は、エビが勝利しました！
お疲れ様でした。

1位 エビさん  (86点)
2位 カニさん  (85点)



```




### コード

```ruby:bowling_game.rb

#terminal-tableという名前のGem（Rubyのパッケージ）を読み込んでいる
#Gemがインストールされていない場合は、このコードを実行する前にgem install terminal-tableなどのコマンドを使ってインストールする必要がある
require 'terminal-table'

#users という名前の変数に、空の配列を割り当てる
#この後のコードで 「user <<」 (<<:配列に要素を追加するための演算子)を活用して、配列に要素を追加させていく
users = []


#ゲーム開始時に表示させる文章
puts "ようこそ BowlingGameへ\n\n
このBowlingGameのスコアは全てランダムで決定される運ゲーです\n
 ※ ここからの数字は、全て半角で入力ください\n\n
Let's BowlingGame Start！！\n
~本日のラッキーボーイ＆ガールは一体誰だ！~\n\n"


# プレイヤーの数を入力
puts "\nプレイヤーの数を入力してください"
num_players = gets.chomp.to_i

num_players.times do |i|
  puts "\nPlayer#{i+1}人目の名前を入力してください"
  player_name = gets.chomp
  #「user << {}」 配列にハッシュを追加していく
  #ハッシュ(Hash)：キーと値のペアを格納するデータ構造。インデックス番号ではなくキーによって要素にアクセスする
  # frame/total_scoreはこの後、配列として使用したいため「[]」で表示させる (total_scoreは最後に勝者を決めるために作成)
  # 今後の配列のイメージ　frame = [[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0,0]]
  # 今後の配列のイメージ　total_score = [[0,0,0,......0,0]]
  users << { name: player_name , frame: [] , total_score: [] }
end


# プレイ開始
puts "\nここから先のスコアは、ランダムで表示されます\n表示された数字を入力ください\n"

10.times do |j|
  users.each do |user|

    # ランダムな数字を生成して表示
   random_number = rand(0..10)
   puts "\n#{user[:name]}さん　#{j+1}game 1投目は#{random_number}です"
   temporary_score1 = gets.chomp.to_i
   
    # 入力した数字が、生成されたランダムな数字と異なる場合、スコアは0とする
    if temporary_score1 != nil
      if temporary_score1 != random_number
        #（※２）　score1 = temporary_score1 と入力したら、好きな数字を入力できる
        score1 = 0
        puts "入力に謝りがありましたため、今回のスコアは 0点 です"
      else
        score1 = temporary_score1
      end
    end

    #場合分け　 ( j <= 8 /　 j == 9 )
    #j == 9の時は、frame内のスコアは３つ発生する
    #j <= 8 : user[:frame] << [score1,score2] 
    #j == 9 : user[:frame] << [score1,score2,score3] 
    if j <= 8

      if score1 == 10
        score2 = 0
        puts "\nナイスストライク！\n\n"

      else
        # ２投目の場合は、10 - score1までの数字で　ランダムな数字を生成して表示
        score1_sub = 10 - score1
        random_number = rand(0..score1_sub)
        puts "\n#{user[:name]}さん　#{j+1}game 2投目は#{random_number}です"
        temporary_score2 = gets.chomp.to_i

        if temporary_score2 != nil
          if temporary_score2 != random_number
            #（※２）　score2 = temporary_score2
            score2 = 0
            puts "入力に謝りがありましたため、今回のスコアは 0点 です"
          else
          score2 = temporary_score2
            if score1 + score2 == 10
              puts "\nナイススペア！\n\n"
            end
          end
        end
      end

      user[:frame] << [score1,score2] 

    elsif j == 9 
      if score1 <= 9

        score1_sub = 10 - score1
        random_number = rand(0..score1_sub)
        puts "\n#{user[:name]}さん　#{j+1}game 2投目は#{random_number}です"
        temporary_score2 = gets.chomp.to_i

        if temporary_score2 != nil
          if temporary_score2 != random_number
              #（※２）　score2 = temporary_score2
              score2 = 0
              puts "入力に謝りがありましたため、今回のスコアは 0点 です"
          else        
            score2 = temporary_score2
          end

          if score1 + score2 <= 9
            score3 = 0

          elsif score1 + score2 == 10
            puts "\nナイススペア！\nボーナスゲーム！\n\n"
            random_number = rand(0..10)
            puts "\n#{user[:name]}さん　#{j+1}game 3投目は#{random_number}です"
            temporary_score3 = gets.chomp.to_i
            if temporary_score3 != nil
              if temporary_score3 != random_number
                #（※２）　score3 = temporary_score3
                score3 = 0
                puts "入力に謝りがありましたため、今回のスコアは 0点 です"
              else
                score3 = temporary_score3
              end
            end
          end                   
        end

      elsif score1 == 10
        puts "\nナイスストライク！\nボーナスゲーム！\n\n"
        random_number = rand(0..10)
        puts "\n#{user[:name]}さん　#{j+1}game 2投目は#{random_number}です"
        temporary_score2 = gets.chomp.to_i

        if temporary_score2 != nil
          if temporary_score2 != random_number
              #（※２）　score2 = temporary_score2
              score2 = 0
              puts "入力に謝りがありましたため、今回のスコアは 0点 です"
          else
            score2 = temporary_score2
          end

          if  score2 <= 9
            score2_sub = 10 - score2
            random_number = rand(0..score2_sub)
            puts "\n#{user[:name]}さん　#{j+1}game 3投目は#{random_number}です"
            temporary_score3 = gets.chomp.to_i
            if temporary_score3 != nil
              if temporary_score3 != random_number
                #（※２）　score3 = temporary_score3
                score3 = 0
                puts "入力に謝りがありましたため、今回のスコアは 0点 です"
              else
                score3 = temporary_score3
              end
            end

          elsif score2 == 10
            puts "\nナイスストライク！\nボーナスゲーム！\n\n"
            random_number = rand(0..10)
            puts "\n#{user[:name]}さん　#{j+1}game 3投目は#{random_number}です"
            temporary_score3 = gets.chomp.to_i

            if temporary_score3 != nil
              if temporary_score3 != random_number
                #（※２）　score3 = temporary_score3
                score3 = 0
                puts "入力に謝りがありましたため、今回のスコアは 0点 です"
              end
            else
                score3 = temporary_score3
            end
          end          
          
        end
      end
      user[:frame] << [score1,score2,score3] 
    end


    #表を作成していく
    row_data = [] #row_data という名前の変数に、空の配列を割り当てる
    frame_before_total = 0  # 前のフレームまでの合計スコアを保持する変数
    total_score = 0  # 最終的な合計スコアを保持する変数
    
  
    #\nを使用して文字が真ん中に来るようにしている
    row_data << "\n#{user[:name]}\n\n"
    #user[:frame].each_with_index do |frame,index|内で、
    #毎回配列が作成される為(毎回全てのindex時の表が作成されることになる)、
    #内側にtemporary_table(一時的なtable)を作成して、最後、「table = temporary_table」とすることで
    #一番最後のindex時のみがtableとして反映されるようにしている
    table = Terminal::Table.new
    
      user[:frame].each_with_index do |frame,index|
        framebefore = index >0 ? frame_before_total : 0 #index==0の時は、０となる
        framenow = frame.sum
        bounus = 0 #ストライクやスペアが出た時の次のフレームの加算点　（初期値は0）
        non_frame_total = 0 #合計が確定していない時は、"..."と表示するため、初期値は0としておく

        if index <= 8 || (index == 9 && user[:frame].length <= 9)
          frame_next = user[:frame].length > 0 ? user[:frame][index+1] : nil       
          frame_next_after_next = user[:frame].length > 1 ? user[:frame][index+2] :  nil

          
            if frame.sum == 10 && frame[0] == 10
              if user[:frame].length == 10 && index >= 8
                bounus += frame_next[0] + frame_next[1]

              elsif user[:frame].length <= 9
                bounus += frame_next.sum if frame_next
                non_frame_total = "..." unless frame_next

                if frame_next && frame_next.sum == 10 && frame_next[0] == 10                 
                bounus += frame_next_after_next[0] if frame_next_after_next
                non_frame_total = "..." unless frame_next_after_next
                end
              end
            
            elsif
              frame.sum == 10
              bounus += frame_next[0] if frame_next
              non_frame_total = "..." unless frame_next
            end

            # non_frame_totalを初期値0と設定したため、
            #その後に、"..."へ上書きされていなければ、frame_total を計算する
            frame_total = non_frame_total != 0? non_frame_total : framenow + framebefore + bounus

            # スコアの表示方法 2フレーム目以降は横に並べる
          if index == 0
            row_data << "#{frame[0]} | #{frame[1]}\n---------\n#{frame_total}"
          else
            row_data << "#{frame[0]} | #{frame[1]}\n---------\n#{frame_total}"
          end
    
            # 前のフレームまでの合計スコアを更新
            frame_before_total = frame_total
            temporary_total_score = frame_total

        elsif index == 9 && user[:frame].length == 10
            
          # 10ゲーム目の時は、次のフレームに対するbounusは発生しない
          frame_total = framenow + framebefore
          # スコアの表示方法 2フレーム目以降は横に並べる(10ゲーム目は３フレームを横に並べる)
          row_data << "#{frame[0]} | #{frame[1]} | #{frame[2]}\n---------\n#{frame_total}"    
          temporary_total_score = frame_total
  
        end

        # テーブルの作成と表示
        temporary_table = Terminal::Table.new do |t|
          t.title = "Bowling_Game"
          t.rows = [row_data]
          t.headings = ['name'] + (0..index).map { |i| "game#{i+1}" }

          # 全体の中央揃えを設定
          t.style = {
            all_separators: true,
            alignment: :center }
      end
      
      table = temporary_table
      user[:total_score] << temporary_total_score
   end
      
      puts table


  end

end

#結果発表
#スコアの順位順に並び替え　users（変数)の中の total_scoreの配列の最後の値で降順(-w:降順/w:昇順)
#降順となるため、１位の人は、sorted_user_scores[0]が該当する
sorted_user_scores = users.sort_by { |w| -w[:total_score].last }
puts  "\nゲームエンド！\n\n今回は、#{sorted_user_scores[0][:name]}が勝利しました！\nお疲れ様でした。\n\n"

#順位発表
#total_scoreの配列の最後の値を表示したいため、「.last」必要
sorted_user_scores.each_with_index do |w,index|
  puts "#{index+1}位 #{w[:name]}さん  (#{w[:total_score].last}点)"
end


```






# 最後に

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