拡張性の高いコードを書く【ループ処理・配列】
はじめに
トランプゲーム『戦争』をRubyで実装する際、コードの拡張性の問題に直面しました。プレイヤー数が増えるにつれコードが複雑になってしまうのです。この解決方法をまとめました。
戦争ゲームのルール
本記事では拡張化に焦点をおくためルールを単純化します。
- プレイヤーは 1~13 までのカードをランダムに引く。
- 出したカードの強さの大小を比べて、一番強いカードを出した人が勝ち。
- 一番強い数値が複数出た場合は引き分けとなる。
愚直な実装
最初に、プレイヤーが二人の場合を考えました。
def judge_war_game(card1, card2)
if card1 > card2
'プレイヤー1の勝ち'
elsif card1 < card2
'プレイヤー2の勝ち'
else
'引き分け'
end
end
player1_card = rand(1..13)
puts "プレイヤー1のカード: #{player1_card}"
player2_card = rand(1..13)
puts "プレイヤー2のカード: #{player2_card}"
puts judge_war_game(player1_card, player2_card)
このコードは正常に勝敗判定が行われ、出力も期待通りです。しかし、プレイヤー数が増えると問題が起こります。以下はプレイヤーが三人の場合のコードです。
def judge_war_game(card1, card2, card3)
# 省略
end
player1_card = rand(1..13)
puts "プレイヤー1のカード: #{player1_card}"
player2_card = rand(1..13)
puts "プレイヤー2のカード: #{player2_card}"
player3_card = rand(1..13)
puts "プレイヤー3のカード: #{player3_card}"
puts judge_war_game(player1_card, player2_card, player3_card)
プレイヤーカードの入力が3回に増え、judge_war_game の引数も3つになり、judge_war_game の勝敗判定に至っては、コードが複雑になりそうで書く気も起きませんでした。プレイヤーが4人、5人…と増えたときの想像はしたくもありませんね。
拡張性のある実装
プレイヤー数の増加に伴うコードの重複を避けるために、ループ処理を使いました。また、引数の増加を防ぐために、配列にプレイヤーのカード情報を格納しました。
def judge_war_game(cards)
# 省略
end
player_count = 3
player_cards = []
player_count.times do |n|
player_card = rand(1..13)
player_cards << player_card
puts "プレイヤー#{n + 1}のカード: #{player_card}"
end
puts judge_war_game(player_cards)
プレイヤー数が増えても、player_countだけを書き換えれば済むようになりました。 次に、player_countを直接変更することなく、任意のプレイヤー数でゲームを実行できるようにします。
def register_player_count
loop do
print 'プレイヤーの人数を入力してください: '
input = gets.chomp
return input.to_i if input.match?(/\A[2-9]|[1-9]\d+\z/)
puts '2以上の整数を入力してください'
end
end
player_count = register_player_count
ループ処理を使い2以上の整数以外の入力は受け付けないようにしました。 最後に配列を使って勝敗判定のロジックを実装します。
def judge_war_game(cards)
highest_card = cards.max
highest_index = cards.index(highest_card)
if cards.count(highest_card) >= 2
'引き分け'
else
"プレイヤー#{highest_index + 1}の勝ち"
end
end
配列から最大値とそのインデックスをそれぞれ highest_card、highest_index 代入し、最大値が 2 つ以上存在する場合は引き分け、それ以外の場合は最大値を持つプレイヤーが勝利と判断します。
全体のコード
def judge_war_game(cards)
highest_card = cards.max
highest_index = cards.index(highest_card)
if cards.count(highest_card) >= 2
'引き分け'
else
"プレイヤー#{highest_index + 1}の勝ち"
end
end
def register_player_count
loop do
print 'プレイヤーの人数を入力してください: '
input = gets.chomp
return input.to_i if input.match?(/\A[2-9]|[1-9]\d+\z/)
puts '2以上の整数を入力してください'
end
end
player_count = register_player_count
player_cards = []
player_count.times do |n|
player_card = rand(1..13)
player_cards << player_card
puts "プレイヤー#{n + 1}のカード: #{player_card}"
end
puts judge_war_game(player_cards)
プレイヤー数が増えても、書き換えを必要としないコードが書けました。
おわりに
プログラミング学習初期においては、とりあえず動作するコードを書くことに集中しがちですが、プロジェクトの将来を見据えて、機能の変化や追加を容易にするためのコードを書くことが重要になります。配列やループ処理を使うことによって、データが増えても書き換えの少ないコードが書けるようになります。