【Ruby】チンチロをリファクタリングと機能追加してみた。

# チンチロプログラムのリファクタリングをしました。
[先日作成したチンチロプログラム](https://qiita.com/mocchi32/items/81176e875b133f7a3c64)のリファクタリングを行いました。

前回作成時点では、
「モノは動くけどクラス定義やメソッドがこれでいいのか、全然わかんない！」状態でした。

現在通っているプログラミングスクール講師の方にコードレビューをしていただき、
特に正しいクラス定義、メソッド定義の仕方について理解した上で実装できるようになりました。

リファクタリング前後のコードを見比べると明らかに分かりやすく、かつ
保守性の高いコードになったかなと思います。

やっぱり他の人に見てもらうって大事ですねえ。感謝。

# 機能追加
機能追加しました。
① プレイヤー人数設定(2~4人)
② ゲーム終了判定
③ 賭け金の入力制限

リファクタリング後に機能追加したのですが、
「嗚呼！これがオブジェクト指向かっ！！」と、その保守性の高さを身をもって経験できました🤗

## main.rb
```ruby:
# チンチロ for 2~4player

# ●ルール
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# ゲームは２〜4人で行われ、まずは親を決める。親以外のメンバーは子になる
# 子が賭け金を決めたら、親がサイコロを振って勝負する役（数）を決める。３回まで振ることができるが、役が出たらその役で勝負
# 子が一人ずつサイコロを振る。親より強い役が出たら子の勝ち。同じなら親の勝ち。親より弱いのが出たら子の負け。親に勝ったら子が賭けた金額を親からもらい、負けたら親に払う。
# 役によってもらえる賭け金が増減する(下記参照)。ex) 子:ピンゾロ 親：ヒフミ => 子が賭け金の10倍もらえる
# 子全員との勝負が終わったら親の交代で、左隣の人に親の権利が移る。
# これを繰り返して参加者全員に親が回ったら、一回のゲームが終了。
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# ● 役(強い順)
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# 1.ピンゾロ :　111　　　　　　　　　　　　　　　　　　　　　　　 賭け金の5倍貰える
# 2.アラシ.  :  222,333,444,555,666 　　　　　　　　　　　　　　  賭け金の3倍貰える
# 3.シゴロ.  :  456 　　　　　　　　　　　　　　　　　　　　　　  賭け金の2倍貰える
# 4.出目　   :  同じ数字2つ+別の数字 ex) 116 → 出目6 551 → 出目1  賭け金の1倍貰える
# 5.目ナシ.  :  他の役以外 　　　　　　　　　　　　　　　　　　　 賭け金の1倍払う
# 6.ヒフミ.  :  123 　　　　　　　　　　　　　　　　　　　　　　  賭け金の2倍払う
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

require "./tintiro_game_manager.rb"

DELIMITER = '----------------------------------------'
DELIMITER_STAR = '★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★'
DEFAULT_MONEY = 50000
ROLE = { parent: '親', child: '子'}
roles = [ROLE[:parent], ROLE[:child], ROLE[:child], ROLE[:child]]


tintiro = TintiroGameManager.new

#タイトル出力
TintiroGameManager.output_title

#プレイヤー人数入力(2~4人)
tintiro.input_player_num

#プレイヤー名前入力
tintiro.input_player_name

#User作成
tintiro.create_users(roles)

#所持金表示
tintiro.output_possession_meney

play_count = 1
while play_count <= tintiro.player_num
  #親子/掛け金決め
  tintiro.decide_role_and_betmoney
  
  # 子のターン
  tintiro.child_turn

  # 親のターン 
  tintiro.parent_turn
  
  #結果出力
  tintiro.output_result
  
  #所持金表示
  tintiro.output_possession_meney
  
  #ゲーム終了判定
  break if tintiro.determine_game_end?(play_count)
  
  #親子入れ替え、掛け金初期化
  tintiro.reset_user(roles)
  sleep(2)
  play_count += 1
  
end
```

## tintiro_game_manager.rb
ゲーム進行に関わるクラス
```ruby:
require "./user.rb"
require "./hand.rb"

class TintiroGameManager
  
  attr_reader :player_num
  
  def initialize
  end
  
  #タイトル出力
  def self.output_title
    print <<~EOS
      #{DELIMITER_STAR}
      ★             チンチロ                  ★
      #{DELIMITER_STAR}
    EOS
  end
  
  #プレイヤー人数入力(2~4人)
  def input_player_num
    print "何人で遊びますか？(2~4人) : "
    @player_num = gets.to_i
    while (@player_num < 2) || (@player_num > 4)
      puts '2~4を入力してください'
      @player_num = gets.to_i
    end
    output_DELIMITER
  end
  
  #プレイヤー名前入力
  def input_player_name
    @player_names = []
    @player_num.times { |i|
      print "名前(#{i + 1}人目)を入力してください : "
      @player_names[i] = gets.to_s.chomp
    }
  end
  
  #User作成
  def create_users(roles)
    @players = []
    @register_order_players = []
    @player_num.times { |i|
      @players[i] = User.new(roles[i], @player_names[i])
      @register_order_players[i] = @players[i]
    }
  end
  
  #所持金を表示する
  def output_possession_meney
    output_DELIMITER
    puts '<所持金>'
    @register_order_players.each { |player|
      player.show_possession_money
    }
    output_DELIMITER
  end
  
  #親子/賭け金決め
  def decide_role_and_betmoney
    puts "★★★★★★★★★★ #{ROLE[:parent]}は「#{@players[0].name}」です ★★★★★★★★★★"
    output_DELIMITER
    puts "● #{ROLE[:child]}の賭け金を決めます。"
    @players.each_with_index { |player, i|
      next if i == 0 #親は賭けないので飛ばす
      player.set_bet_money(player)
      puts "→ #{player.name}は#{player.bet_money}ソシー賭けました"
      output_DELIMITER
    }
    #子と親で配列分割
    @childs = @players.last(@player_num - 1)
    @parent = @players.first(1)
  end
  
  #子のターン
  def child_turn
    @child_hands = []
    @childs.each_with_index { |child, i| 
      @child_hands[i] = Hand.new
      child.play_child(@child_hands[i])
    }
  end
  
  #親のターン
  def parent_turn
    @parent_hand = Hand.new
    @parent[0].play_parent(@parent_hand)
  end
  
  #結果出力
  def output_result
    @childs.zip(@child_hands) { |child, child_hand| 
      #勝敗判定
      determine_win_lose(child, child_hand, @parent, @parent_hand)
      #賞金計算
      calc_award(child) 
     }
  end
  
  #ゲーム終了判定
  def determine_game_end?(play_count)
    #プレイヤー全員に親が回ったらゲーム終了
    if play_count == @player_num
      puts '全員に親が回ったのでゲームを終了します。'
      output_DELIMITER
      return true
    elsif play_count < @player_num
      #所持金不足によるゲーム終了判定
      if money_shortage?
        output_DELIMITER
        true
      end
    end
  end
  
  #親子入れ替え、賭け金を初期化
  def reset_user(roles)
    @players << @players.shift #playerの順番を回す
    @players.each_with_index { |player, i| 
      player.bet_money = 0
      player.role = roles[i]
    }
    puts '● 親子を交換します。'
    output_DELIMITER
  end
  
  # ----------------------------------- private -------------------------------------------
  private
  
  def output_DELIMITER
    puts DELIMITER
  end
  
  # 勝敗判定
  def determine_win_lose(child, child_hand, parent, parent_hand)
    # 役が出目同士のとき
    if parent_hand.hand_info[:hand_num] == 4 && child_hand.hand_info[:hand_num] == 4
      if parent_hand.hand_info[:deme_num][0] >= child_hand.hand_info[:deme_num][0]
        @winner = parent[0]
        @loser = child
      else
        @winner = child
        @loser = parent[0]
      end
    # 役が出目同士以外のとき
    elsif parent_hand.hand_info[:hand_num] <= child_hand.hand_info[:hand_num] 
      @winner = parent[0]
      @loser = child
    else 
      @winner = child
      @loser = parent[0]
    end
    
    output_DELIMITER
    puts "#{@winner.name}(#{@winner.role})の勝ち！"
    @bet_money = child.bet_money
  end
  
  #賞金計算
  def calc_award(child)
    case @winner.hand[:hand_num]
      when 1 #ピンゾロ
        award_money = child.bet_money * 5
      when 2 #アラシ
        award_money = child.bet_money * 3
      when 3 #シゴロ
        award_money = child.bet_money * 2
      else #出目/目ナシ
        award_money = child.bet_money * 1
    end
    
    award_money *= 2 if @loser.hand[:hand_num] == 6 #負けた方がヒフミの場合、賞金２倍
    puts "#{@winner.name}(#{@winner.role})が、#{award_money}ソシー獲得しました！"
    @winner.possession_money += award_money
    @loser.possession_money -= award_money
  end
  
   #所持金不足によるゲーム終了判定
  def money_shortage?
    end_flag = 0
    @players.each { |player|
      if player.possession_money <= 0
        puts "#{player.name}の所持金が無くなったためゲームを終了します。"
        end_flag = 1
      end
    }
    if end_flag == 1
      return true
    end
  end
  
end
```
## user.rb
ユーザーに関するクラス
```ruby:
require "./hand.rb"


class User
  
  attr_accessor :role, :possession_money, :bet_money
  attr_reader :hand, :name
  
  def initialize(role, player_name)
    @role = role
    @name = player_name
    @bet_money = 0
    @possession_money = DEFAULT_MONEY
  end
  
  # ユーザーの所持金を表示する
  def show_possession_money 
    name_length10 = ['%10s']
    name_length10 = @name.ljust(10)
    puts "#{name_length10}： #{@possession_money}ソシー"
  end
  
  #賭け金設定
  def set_bet_money(player)
    puts "・#{player.name}は「#{player.role}」です。"
    print '賭け金を決めてください(ソシー) : '
    while true
      @bet_money = gets.to_i
      # @bet_money = 100
      if @bet_money > player.possession_money
        puts '賭け金が足りません。再度入力してください。'
        next
      elsif @bet_money <= 0
        puts '賭け金は0以上にしてください。再度入力してください。'
        next
      end
      break
    end
  end
  
  # 子ターン
  def play_child(child_hand)
    throw_dice(child_hand)
    child_hand.output_hand(@role, @name)
  end
  
  # 親ターン
  def play_parent(parent_hand)
    throw_dice(parent_hand)
    parent_hand.output_hand(@role, @name)
  end
  
  #親判定
  def parent?
    role == ROLE[:parent]
  end
  
  # ----------------------------------- private -------------------------------------------
  private 
  
  #サイコロを振る
  def throw_dice(hand)
    sleep(1)
    puts "★★★★★★★★★★ #{@name}(#{@role})のターン ★★★★★★★★★★"
    play_count = 1 
    while (play_count <= 3) && hand.hand_info[:hand_num] == 5
      dices = Array.new(3).map{rand(1..6)}
      puts "● #{play_count}投目"
      dices.each do |dice|
        print "#{dice} "
        sleep(0.5)
      end
      print "\n"
      @hand = hand.get_hand(dices)
      play_count += 1
    end
  end
  
end
```
## hand.rb
役に関するクラス
```ruby:
class Hand
  attr_reader :hand_info
  
  def initialize
    @hand_info = {hand_name: '', hand_num: 5, deme_num: 0}
  end
  
  #役を判定し、取得する
  def get_hand(dices)
    if (dices[0] == dices[1]) && (dices[1] == dices[2])
      @hand_info[:hand_name] = 'アラシ'
      @hand_info[:hand_num] = 2
      if dices.sum == 3 
        @hand_info[:hand_name] = 'ピンゾロ'
        @hand_info[:hand_num] = 1
      end
    elsif (dices.sum >= 15) && (dices.include?(5)) 
      @hand_info[:hand_name] = 'シゴロ'
      @hand_info[:hand_num] = 3
    elsif dices.sum == 6
      @hand_info[:hand_name] = 'ヒフミ'
      @hand_info[:hand_num] = 6
    else
      @hand_info[:hand_name] = '目ナシ'
      @hand_info[:hand_num] = 5
    end
  
    #出目判定
    1.upto(6){ |n|
      # 2/3のサイコロが同じ目だった場合
      if dices.select{ |m| m == n}.size == 2
        @hand_info[:hand_name] = '出目'
        @hand_info[:hand_num] = 4
        @hand_info[:deme_num] = dices.reject{ |k| k == n}
        break    
      end
    } 
  
    if @hand_info[:hand_num] == 5  
      puts '目ナシ...'
    end
    
    @hand_info
  end

  #役を出力する
  def output_hand(role, name)
    if @hand_info[:hand_num] == 4
      puts "#{name}(#{role})の結果 : #{@hand_info[:hand_name]}(#{@hand_info[:deme_num][0]})"
    else
      puts "#{name}(#{role})の結果 : #{@hand_info[:hand_name]}"
    end
  end

end
```
# 最後に
プログラミング楽しい。時間が瞬で溶けていく🫠