11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ドラクエライバルズで学ぶ Ruby プログラミング

Last updated at Posted at 2019-03-31
1 / 8

僕は、ドラクエライバルズ がかなり好きでよくプレイしているのですが、ライバルズを題材に Ruby のプログラミング問題を考えてみたので公開します。

logo.png

回答を見る前に問題を解きたい場合は、スライドで見るのがおすすめです。
気が向いたらどうぞ。


問題

ドラクエライバルズには、7人のリーダーが存在する。各リーダーは、それぞれ以下のスキルを持っている。

リーダー名 役職(role) スキル名
テリー 戦士(warrior) 稲妻の加護
ゼシカ 魔法使い(witch) 紅蓮の火球
アリーナ 武闘家(monk) おてんば姫
ククール 僧侶(priest) 癒しの波動
トルネコ 商人(merchant) お宝発見
ミネア 占い師(fortune_teller) 水晶占い
ピサロ 魔王(satan) 魔族の戦士

それぞれのリーダーの情報のうち、以下のものが配列として渡される。

leaders = [
  { name: "テリー", role: "warrior" },
  { name: "ゼシカ", role: "witch" },
  { name: "アリーナ", role: "monk" },
  { name: "ククール", role: "priest" },
  { name: "トルネコ", role: "merchant"},
  { name: "ミネア", role: "fortune_teller" },
  { name: "ピサロ", role: "satan" } 
]

上記の渡された情報をもとに、インスタンスを作成し、それぞれのインスタンスメソッド execute_skill を実行すると、以下の出力が得られるようなコードを実装せよ。
(ただし、渡される配列の値を全て使う必要はない。必要だと判断したものだけ使って実装して良いものとする。)

テリーは稲妻の加護を使った!
ゼシカは紅蓮の火球を使った!
アリーナはおてんば姫を使った!
ククールは癒しの波動を使った!
トルネコはお宝発見を使った!
ミネアは水晶占いを使った!
ピサロは魔族の戦士を使った!

注) 渡された情報のうち、戦士であるテリーの情報からインスタンスを作成し、execute_skill を実行したときの例

leader = Leader.new(params)
leader.execute_skill
=> テリーは稲妻の加護を使った!

解答例 その1. case で分岐する

コード量は冗長になってしまうが、case で分岐して処理するのがひとつ。
あまりいい実装ではありません。

class Leader
  attr_reader :name, :role

  def initialize(**params)
    @name = params[:name]
    @role = params[:role]
  end

  def execute_skill
    skill =
      case @role
      when "warrior"
        "稲妻の加護"
      when "witch"
        "紅蓮の火球"
      when "monk"
        "おてんば姫"
      when "priest"
        "癒しの波動"
      when "merchant"
        "お宝発見"
      when "fortune_teller"
        "水晶占い"
      when "satan"
        "魔族の戦士"
      else
        raise "role が不適切です"
      end
    puts "#{@name}#{skill}を使った!"
  end
end

leaders = [
  { name: "テリー", role: "warrior" },
  { name: "ゼシカ", role: "witch" },
  { name: "アリーナ", role: "monk" },
  { name: "ククール", role: "priest" },
  { name: "トルネコ", role: "merchant"},
  { name: "ミネア", role: "fortune_teller" },
  { name: "ピサロ", role: "satan" } 
]

leaders.each do |params|
  leader = Leader.new(params)
  leader.execute_skill
end

解答例 その2. 振る舞いが同じなクラスを作る

同じ execute_skill というメソッドを持ったクラスを準備する(振る舞いを同じにする)ことで以下のようなコードを書くことができます。

class Warrior
  def execute_skill
    puts "テリーは稲妻の加護を使った!"
  end
end

class Witch
  def execute_skill
    puts "ゼシカは紅蓮の火球を使った!"
  end
end

class Monk
  def execute_skill
    puts "アリーナはおてんば姫を使った!"
  end
end

class Priest
  def execute_skill
    puts "ククールは癒しの波動を使った!"
  end
end

class Merchant
  def execute_skill
    puts "トルネコはお宝発見を使った!"
  end
end

class FortuneTeller
  def execute_skill
    puts "ミネアは水晶占いを使った!"
  end
end

class Satan
  def execute_skill
    puts "ピサロは魔族の戦士を使った!"
  end
end

require "active_support/all"

leaders = [
  { name: "テリー", role: "warrior" },
  { name: "ゼシカ", role: "witch" },
  { name: "アリーナ", role: "monk" },
  { name: "ククール", role: "priest" },
  { name: "トルネコ", role: "merchant"},
  { name: "ミネア", role: "fortune_teller" },
  { name: "ピサロ", role: "satan" } 
]

roles = leaders.map { |params| params[:role].camelize }
roles.each do |role|
  # role の文字列から各 class のインスタンスを作成
  leader = role.constantize.new
  leader.execute_skill
end

ダックタイピング

解答例 その2 のように、同じメソッドを持ったクラスを準備しておくと、以下のようなコードが動作するようになります。

class Player
  def touch_skill(leader)
    leader.execute_skill
  end
end

# リーダーとして戦士を選択済みとする
selected_leader = Warrior.new

player = Player.new
player.touch_skill(selected_leader)

これは、実際にプレイするひとが、スキルを発動することをイメージして書いたコードです。
この例では、 Warrior のインスタンスを touch_skill の引数に渡していますが、渡すインスタンスは、別のクラスのものでも動くわけです。

このように、 振る舞いを同じにしておく(同じメソッドを持っていて実行できる) ようにしておくと、拡張性の高いコードを書くことができます。

このような実装を、ダックタイピング と呼びます。


その他の解説

文字列から class を作る

今回の解答では、文字列を class に変換する処理をしています。これは、 ActiveSupport が提供している constantize メソッドで実現できます。

もう少し簡単な例で書くと、こんな感じ。

require "active_support/all"

# 文字列 "Array" から class の実態を作成して使う
"Array".constantize.new(5)
=> [nil, nil, nil, nil, nil]

ただ、クラス名と同じ文字列を準備する必要があります。


クラス名と同じ文字列を準備する

ミネアの role である fortune_teller を例にして考えてみます。
この文字列を元に、クラスを作るには FortuneTeller というかたちに変える必要がありますね。

  • アンダーバーで文字をつなげる書き方を、スネークケース(snake case)
  • 切り替わり部分を大文字にしてつなげる書き方を、キャメルケース(camel case)

と呼びます。

Ruby は基本的にスネークケースを使いますが、クラスはキャメルケースで書かれるので、スネークケースとキャメルケースの変換が必要になります。

キャメルケースへの変換は、これも ActiveSupport が提供している camelize メソッドで簡単に変換できます。

require "active_support/all"

role = "fortune_teller"
role.camelize
=> "FortuneTeller"

あとは、これをもとにクラスに変換してあげればいいことがわかりますね。ちなみに、スネークケースに戻す場合は underscore を使います。


case で分けずに class で分けるとどんなメリットがあるのか

例えば、 「トルネコ強くてムカつくので、スキルを弱体化させてやる」 と思い、変更を加えるとしましょう。

すると、Leader クラスひとつで処理を書いていると、case 文がめちゃくちゃ長くなって、とても読みづらいことになるんです。

今は execute_skill だけなのでまだマシですが、実際はたくさんのメソッドがあり、すべて分岐処理が必要になるので、Leader クラスが肥大化します。

一つのファイルが長くなると、「トルネコ(商人)の挙動を変えたいだけなのに、関係のないリーダーのコードもたくさん書かれている大きなファイル」を見て行く必要がある んですよね。(以下を見れば、その気持ちがわかると思います笑)

# トルネコのスキルを弱体化させたいだけなのに
# 関係のないリーダーのスキルがたくさん並んでいて読みにくい

def execute_skill
  skill =
    case @role
    when "warrior"
      "稲妻の加護"
    when "witch"
      "紅蓮の火球"
    when "monk"
      "おてんば姫"
    when "priest"
      "癒しの波動"
    when "merchant"
      "お宝発見"
    when "fortune_teller"
      "水晶占い"
    when "satan"
      "魔族の戦士"
    else
      raise "role が不適切です"
    end
  puts "#{@name}#{skill}を使った!"
end

余計な情報が詰まっているので、負担がかかるわけです。

対して、クラスごとに分割しておくと、とても見やすくなります。
トルネコ(商人)であれば、Merchant クラスを見てあげればいいだけなので

class Merchant
  def execute_skill
    puts "トルネコはお宝発見を使った!"
  end
end

どこを直せばいいのか、すぐにわかりますよね。負担も少ない。
あとは以下のように変えてあげれば OK ですね。

class Merchant
  def execute_skill
    puts "トルネコはお宝発見を使った!しかし何も見つからなかった!"
  end
end

こういった理由から、case でわけずに、適切にクラスにして分けてあげると読みやすく、変更に強いコードが書けるようになります。

ちなみに僕はテリーが一番好きですが、超幸せトルネコは決まると楽しいのでよく使ってます。

おわり。

11
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?