hellonagi

拡張性の高いコードを書く【ループ処理・配列】

はじめに

トランプゲーム『戦争』を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)

プレイヤー数が増えても、書き換えを必要としないコードが書けました。

おわりに

プログラミング学習初期においては、とりあえず動作するコードを書くことに集中しがちですが、プロジェクトの将来を見据えて、機能の変化や追加を容易にするためのコードを書くことが重要になります。配列やループ処理を使うことによって、データが増えても書き換えの少ないコードが書けるようになります。