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

僕は、[ドラクエライバルズ](https://www.dragonquest.jp/rivals/) がかなり好きでよくプレイしているのですが、ライバルズを題材に Ruby のプログラミング問題を考えてみたので公開します。

![logo.png](https://qiita-image-store.s3.amazonaws.com/0/65743/8a7f0a9a-1758-a622-8bfb-66ec45b503df.png)

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

---

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

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

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

```rb
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 を実行したときの例

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

---

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

```rb
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` というメソッドを持ったクラスを準備する（振る舞いを同じにする）ことで以下のようなコードを書くことができます。

```rb
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 のように、同じメソッドを持ったクラスを準備しておくと、以下のようなコードが動作するようになります。

```rb
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` の引数に渡していますが、渡すインスタンスは、別のクラスのものでも動くわけです。

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

このような実装を、[ダックタイピング](http://obelisk.hatenablog.com/entry/2015/07/20/205620) と呼びます。

---

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

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

```rb
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` メソッドで簡単に変換できます。

```rb
require "active_support/all"

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

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

---

### case で分けずに class で分けるとどんなメリットがあるのか
例えば、 **「トルネコ強くてムカつくので、スキルを弱体化させてやる」** と思い、変更を加えるとしましょう。

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

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

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

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

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 クラスを見てあげればいいだけなので

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

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

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

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

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

おわり。