🥭

Ruby で創る RPG 第八章 メソッドを自作する

2024/01/11に公開


第八章 メソッドを自作する

オブジェクト指向とメソッドについて学ぶ

「とっても素敵ね。スライムを倒すことができるようになったわ。」
「遙香お姉さまありがとう。教えてくれて。」
「じゃあ、次はどんなふうに発展させていこうか。」
「スライムを攻撃し続けるのも楽しいんだけれど、やっぱり普通は反撃もしてくると思うんだ。」
「うん、そうね。じゃあスライムが反撃するようにしてみましょうか?」
「うん。今まで登場したのはスライムだけだったけれど、勇者の体力も用意して、勇者が攻撃したらスライムが反撃するってすればいいから、すぐできそう。」
「ええ、そうね。」
「あと、これはこれは提案なのだけれども、すぐできそうだし、せっかくだからメソッドを自作してみると言うのはどうかしら。」
「メソッドって何?」
「そうね、頭が良くなる◯◯メソッドなんてみっくんは聞いたことないかしら?」
「あ〜 聞いたことあるよ。よく宣伝してた。」
「ええ、頭が良くなる指導方法として有名だものね。メソッドって、日本語にすると方式や手法、そんな言葉にもなったりするわ。プログラミングの世界だとそうね〜。まず、関数って用語から説明した方が良いかしら。」
「前回もちょっと出てきたけれど、数学での関数は、y = ax + b だったり、y = f(x)のように表したわよね。」
「うん。そうだよ。」
「そして、fのことを関数名、xのことを引数(ひきすう)と言うことも紹介したわ。Rubyにもたくさんの関数が用意されているのだけれど、Rubyの世界では 関数と言うよりは、メソッドって呼ぶ方が一般的だわ。」
「関数とメソッドって名前が違うけれど、どう違うの?」
「そうね〜。関数っていうのは、数学での関数もそうだけれど、何か値を渡すと結果を返してくれる、そういう存在ね。プログラミングの世界だと、結果を返してくれる他に、何かひとまとまりになった処理を記述したりするのが関数ね。」
「そうなんだね。で、メソッドと言うのは?」
オブジェクト指向と言う考え方があるのだけれど、データ処理ひとまとまりにするとより生産性の高いプログラミングができるの。」
「そうなんだ〜。データと処理をひとまとまりにするってどういうことなの?」
「そうね〜。いまみっくんが作っているRPGの例だと、スライムの体力と言うデータがあるわよね。」
「うん、あるよ。」
「そして、スライムの体力を減らすと言う処理もあるわよね。」
「うん、体力というデータもあるし、攻撃を受けたから体力も減らす処理もしなくちゃいけないよ。」
「ええ、攻撃を受けたときには、体力を減らさないといけないわよね。体力というデータと体力を減らすと言う処理。これをひとまとまりにしておくと簡単に扱えそうじゃない。今はhpというスライムの体力、yusha_hpという勇者の体力という二種類のデータがあって、hp -= damageというスライムの体力を減らす処理、yusha_hp -= damageという勇者の体力を減らす処理という二種類の処理もあるわ。同じようだけれど二種類のデータがあって、二種類の処理がある。ひとまとまりにするとプログラムが簡単に書けそうな気がするし、間違いも減りそうよね。」
「うん、そうだね。そのオブジェクト指向っていう考え方で書くと、どういう風になるの?」
「そうね〜。slime.damage(3), yusha.damage(2) って感じかしら。スライムは3ポイントのダメージを負った。勇者は2ポイントのダメージを負ったっていう意味よ。」
damageっていうのが、処理の部分でメソッドっていうんだね。」
「ええ、そうよ。」
「で、体力のデータはどこにあるの?」
「体力のデータは、slimeっていうオブジェクトの中に格納されているの。」
「そうなんだ〜。じゃあ、中にある体力の値は見るにはどうしたらいいの?」
slime.hpってすると、見ることができるわ。」
「そうなんだね〜。なんだか、hpyusha_hphp -= damageyusha_hp -= damage って一個ずつ書くよりまとまっていて良さそう。早く書いて見たいな〜。」
「ええ、そうね。じゃあ、まずはメソッドを作るところから始めて見ましょう。」
「うん。」
Rubyにはいくつもの便利なメソッドが用意されているわ。例えば乱数を与えてくれるrand関数がそうね。そして、それらを使うのはもちろんだけれど、用意されているRubyの構文やメソッドなどの部品を組み合わせて、自分が実現したい処理の塊を作り上げていくの。それが自作のメソッドね。」
「うん、そうなんだね。」
「ええ、メソッドを作ることができたら、プログラムの見通しも良くなるし、分かりやすくて使いやすいメソッドを作っていくのはプログラミングの楽しみでもあるわ。」
「そうなんだね。じゃあ、早速作って見たいな。」
「ええ、じゃあ、作っていきましょう。題材はそうねぇ、スライムの活気度を表すメソッドなんてどうかしら。」
「ああ、ピンピンしているとか死にそうだ、とかって言ってるやつだね。」
「そうよ。Rubyには様々な関数が用意されているけれども、さすがにスライムの活気度を表すようなメソッドは用意されていないわ。」
「もちろん、そうだよ。だって僕が考えたんだから。」
「そうよね。じゃあ早速作ってみましょう。メソッドの名前は何にする? 名は体を表すという格言があるけれど、何をしているのか、処理の内容が一言でわかる、そういう名前が理想だわ。」
「そうなんだね。じゃあ、活気度を表示するから、puts_livelinessなんてどうかな?」
「そうね〜。じゃあひとまずそれで書いて見ましょう。」

「メソッドの基本的な書き方は次の通りよ。」
と、遙香お姉さまは書き方を教えてくれる。

def puts_liveliness
  puts "スライムはピンピンしている"
end

「これで、メソッドの定義はできたわ。」
「簡単だね。」
「ええ、簡単に書けるところが Rubyの良いところよ。」
「どうやって使うの?」
「使い方も簡単よ。呼び出すっていう言い方をしたりもするけれど、使いたいところに書くだけなの。」

puts_liveliness

「みっくん、書いて見て。」
「うん、やって見るね。」
「できたかしら。」
「うん、できたよ。」
「じゃあ、動かして見て。」
「うん、お〜 ピンピンしてるって表示されたよ。」
「おめでとう。自作メソッド完成ね♡」

お姉さまに褒められ、僕はとっても嬉しい。

「そして、遙香さん。あのね。何回攻撃してもずっとピンピンしているって表示されるんだけれど・・・」
「そうね。スライムの体力をぜんぜん考慮していないから、いつもピンピンしていると表示されちゃうわよね。スライムの体力によって表示内容を変化させるためには、まず、このメソッドにスライムの体力を渡してあげる必要があるわ。」
「そうなんだねってそうだよね。スライムの体力ってどうやって渡したらいいの。」
「それには次のように書くわ。」

def puts_liveliness(hp, max_hp)
  liveliness = (hp.to_r / max_hp.to_r) * 100
  case liveliness
  when 70..100
    puts "スライムはピンピンしている"
  when 30..70
    puts "スライムは弱ってきている"
  else
    puts "スライムは死にそうだ"
  end
end

「あ〜。元のプログラム、そのままだね。」
「ええ、そうね。hpmax_hpを渡してあげて、活気度を計算、判定して表示させている。元のプログラム、そのままね。」
「そっか〜。」
「メソッドに渡してあげているhpmax_hpのことを**引数(ひきすう)**っていうけれど、引数が必要ない時には、最初の例のように書けるの。で、引数が必要な時にはさっきのように、丸括弧の中に引数名を指定してね。」

括弧を省略できる場合と、必要な場合について

「うん、わかったよ。で、使う時にはどうするの?」
「引数がある時の使い方だけれど、二種類あるわ。」
「二種類あるんだね。」
「ええ。最初の例はこうよ。」

puts_liveliness(hp, max_hp)

「あ〜、そのまんまだね。」
「ええ、丸括弧の中に引数名を書くの。」
「なんか、数学の関数に良く似ているね。」
「ええ、そうね。乱数を得たい時に使ったrand関数でも、rand(1..6)のように書いたけれども、それと一緒ね。」
「ちなみに、メソッド定義で使った引数名と、呼び出す時の引数名は異なっていてもいいわ。つまりこう言うことね。」

def puts_liveliness(hp, max_hp)
  # 略
end

slime_hp     = 6
slime_max_hp = 12
puts_liveliness(slime_hp, slime_max_hp)

「みっくんも、あたしから呼ばれる時と、クラスの中で呼ばれる時とで、どういう名前で呼ばれるか違うと思うけれど、それと一緒ね。メソド定理するときの引数と、実際に呼び出すときの引数は一緒でもいいし、異なっていてもいいの。」
「うん、わかったよ。」

「それから、もう一つの書き方はどんなふうになるの?」
「もう一つの書き方ね。Rubyではメソッド呼び出しの際に、丸括弧を省略することもできるの。」
「そうなんだー。じゃあ、こんな風に書けるってこと?」

puts_liveliness hp, max_hp

「ええ、そうよ。簡潔に書けるところは簡潔に。それがRubyの良いところね。メソッドを定義したときに、2つの引数を取るよって教えておいたわ。だからそれをしっかり覚えておいてくれて、使う時にputs_liveliness hp, max_hpって書くと、後ろに続いているhpmax_hpが引数のことだって分かるから、丸括弧がなくても 動かすことができるの。」
「すごいね。」
「ほんとね。凄いわよね。丸括弧の有無は好みもあるし、無いと動かないところもあるから適宜使い分けてね。」
「丸括弧が無いと動かないところってどういうところなの?」
「そうね。例えば長い計算式を書いているところだったりすると、丸括弧の有無でどの計算を行いたいのか不明確になって、エラーが出たり、思っていたのとは違う結果になったりするわ。」
「どんな感じなの?」
「そうね〜。例えば、何度も登場するrand関数の例だけれど、二つのサイコロを振った合計の数だけダメージを与えられるなんて、良くある設定よね。」
「うん、そうだね。」
「で、丸括弧を使うと次のようにかけるわ。」

damage = rand(1..6) + rand(1..6)

「うん、二つのサイコロの和が、ダメージになるんだね。」
「ええ、そうよ。そして、括弧を使わないで書くと、こんな風になるわ」

damage = rand 1..6 + rand 1..6

「これを実行するとね、次のような感じで、エラーメッセージが表示されるわ。」

syntax error, unexpected integer literal, expecting `do' or '{' or '(' (SyntaxError)
rand 1..6 - rand 1..6
                 ^

「みっくん、英語は好きかしら?」
「ううん、あんまり好きじゃないよ。みんなみんな英語英語って言うけれど、僕日本人だし、あんまり英語は好きじゃない。」
「そうね。あたしもそう思うわ。人の思考能力や人格の基礎を形作るのは母国語だから、日本語を大事にして欲しいわよね。そして、和魂洋才という言葉があるけれども、簡単な英語は読めると、プログラミングしていく上でいろいろ便利なこともあるから、一緒に読んでいきましょうか。」
「うん。」
「じゃあ、まず最初のsyntax errorってところから。syntaxは文法のことなの。だから、これは、書いたプログラムがRubyルビーの文法に従っていないわよって言う、Rubyからのお知らせね。Rubyは賢くて、プログラマの意図を読み取って実行してくれるのだけれども、うまく解釈できなかったときに、こういったメッセージを表示してくれるの。エラーって聞くとびっくりするかもしれないけれど、驚かなくていいわ。Rubyメッセージ、お知らせだって思ってね。みっくんのように賢いプログラマからしたら、簡単に気づくことなのだけれども、Rubyにとっては難しいこともあるの。」
「そうなんだね。うん。で、続きはなんて書いてあるの?」
「次は、unexpected integer literalね。unexpected は、予期していない、予想外の、想定外のっていう意味ね。で、何を予期していなかったのかっていうと、integer literalを予期していなかったって言っているわ。integerは整数のこと。literal は、文字 という意味の英単語よ。literature って言ったら文学だし、letterは手紙だったりするわよね。語源を同じくする単語だわ。
つまり、整数の文字は予期していなかったっていうことね。整数の文字ってなあにってなるかもだけれど、1(いち)や2(に)という数字のことね。」
「お〜。なるほど。integer literalって言うと難しく感じるけれど、数字を表す文字のことだったんだね。」
「ええ、そうね。で、続きが書かれているけれど、expecting `do' or '{' or '('って書かれているわね。」
「うん、そうだね。え〜っと、expecting は、期待するっていうことだから、do{(が来て欲しかったってこと?」
「ええ、その通りよ。みっくん、素敵。」
お姉さまは、僕の頭を撫でてくれる。僕の頭ももっと賢くなりそうだ。

「で、最後の行なんだけれど、rand 1..6 - rand 1..6って書いていて、良く見ると、その次のように、^って、これ ハット っていう記号なんだけれど、ちょうど、1のところについているのが分かる?」
「うん、わかるよ。」
「ええ、ということで、プログラムを先頭から見て行った時に、この1に出会ったところで、文法通り解釈できなかった、do{(が来て欲しかったのだけれどな〜って、Rubyが言っているのね。」
「そうなんだね。で、それに応えてあげるのがみっくんの役割よ。どうしたらいいと思う?」
(が欲しいと言っているのだから(を付けてあげたらいいんだね。」
「ええ、その通りよ。」
「つまり、こう言う風に書けばいいんだ。」

damage = rand(1..6) + rand(1..6)

「ええ、正解よ。」
「これが最初に言っていた、丸括弧がないとエラーが出る場合もあるということだね。」
「ええ、そうよ。」
「じゃあ、次の意図していない結果になると言うのは?」
「ええ、ピタゴラスの定理のお話をしたのだけれど、そこでルートが登場したわよね。」
「うん、有理数で世界は成り立っていると信じていた、ピタゴラス教団の人たちにとって、有理数で表せない数があると言うのはとっても驚きで、秘密を漏らした子は海に投げ込まれちゃったんだよね。」
「ええ、そうよ。よく覚えているわね。Rubyにもルートを計算する関数が用意されているわ。」
「どうやって書くの?」
「例えば、\sqrt{2} を求めたかったら、Math.sqrt(2)と書くの。Math は Mathematics、数学のこと。sqrtは、square root 平方根の略ね。」
「うん」
「Math モジュールには、平方根を求めるための sqrt、立方根を求めるための cbrt など、数学で良く使う関数がたくさん用意されているわ。」
「で、本題なんだけれど、正十二角形を書いて、円周率を求めたわよね。」
「うん、計算頑張ったよ。」
「みっくんが求めた円周率は、次のような式だったわよね。」

\pi= \sqrt{2-\sqrt{3}} \times 6
pi = Math.sqrt(2 - Math.sqrt(3)) * 6
puts pi
#=> 3.1058285412302498

「うん、そうだよ。」
「で、少し難しいけれど、これ、次のようにもなるの。」

\pi= (\sqrt{6}-\sqrt{2}) \times 3

「そうなんだね。」
「ええ、そうなの。でね。Rubyで計算させて見るね。」

pi = (Math.sqrt(6) - Math.sqrt(2)) * 3
#=> 3.105828541230248

「最後が少し違っているけれど、これは数値計算の誤差なの。コンピュータだからって正確ってわけでは無いいい例ね。」
「うん。わかった。」
「そしてね。最初の十桁くらいは全部一緒になっているわよね。」
「うん、だから、みっくんが求めた式が、いまあたしが示した式と一緒ってことはいいわよね。」
「うん。了解だよ。」
「あ、あと、#=> って書き方だけれど、Rubyでは、#がコメントだったわよね。」
「うん、そうだよ。説明書きなんかを書いたりするんだったよね。」
「ええ、で、良く使われるのが、#=> って書き方。この例の場合だと、piは、3.105828541230248になっているっていう実行結果を示しているの。良く使われるから知っておいてね。」
「うん。わかったよ。」
「じゃあ、みっくん、\sqrt{2}はなんだったか、覚えている?」
「一夜一夜に人見頃、1.41421356だよね。」
「正解、じゃあ、括弧無しで書いてみようか。」

pi = Math.sqrt 6 - 1.41421356 * 3
#=> 1.325654298827564

「ぜんぜん、違う値になったわよね。」
「うん、そうだね。」
「これはきっと理由もわかっているかと思うけれど。」
「うん、まず、引き算と掛け算は掛け算が優先されるので、1.41421356 * 3 が先に計算されて、4.24264068 になる。それから、6 - 4.24264068 を計算して、1.75735932 になって、\sqrt{1.75735932} を求めて、1.325654298827564 になるんだね。」
「ええ、その通りよ。括弧を使って書くと次のようになるわ。」

pi = Math.sqrt(6 - (1.41421356 * 3))

「元の式と全然違うやん。」
「ええ、そうね。だから結果も全然違ってきて当然ね。計算の優先順位を明確にするためにも、括弧は大切ね。」
「うん、わかったよ。」

「じゃあ、自作メソッドを使って、これまでのプログラム書き直して見るね。」
「ええ、みっくん賢いしすぐに終わると思うわ。あたしここで見ていてあげるね。」

遙香お姉さまが見守る中、僕はかたかたタイピングを続ける。
そして完成したのが次のコードだ。

https://github.com/Atelier-Mirai/Ruby_RPG/blob/master/rpg/rpg08.rb

Discussion