Hyper-Positive-Diary

TWICE/遊戯王/バイオインフォ/読書記録/アニメの感想等を走り書きします。

【Python】遊戯王で理解するオブジェクト指向

~ Pythonを勉強中だけど、クラスがわからない、そんなあなたへ ~

お久しぶりです。2020年4月から、遊戯王の新アニメ、「遊戯王SEVENS」が始まろうとしています。 ラッシュデュエルという全く新しい遊び方が開発中であり、 時代は変わっていくんだなあ...と物思いにふけっている今日この頃です。

さて、本記事は、「Pythonを勉強中だけどclassってよくわからないんだよね〜」という方々が、 遊戯王の知識によりclassの概念を補完し、オブジェクト指向を理解する手助けになればいいな、という思いを込めて書きました。 一度classの勉強をして挫折したデュエルモンスターズ世代の同志が、 遊戯王の思い出とプログラミングへの自信を取り戻してくれたら嬉しいです。

また、プログラミングはわからないけど遊戯王は好きという人も、興味本位で覗いていってもらえると嬉しいです。 もしくは、プログラミング勉強中の遊戯王好きなお友達に宣伝していただけると励みになります。 ※ 遊戯王に例えると、イメージを掴みやすくなる分、正確さを欠きます。概観が掴めたという人は、是非オブジェクト指向の厳密な理解に励んでください。

クラス :「カードの基本情報」

クラスとは、遊戯王で例えるならば、カードの基本情報が書かれたものです。例えば、下のクラス 「BlackMagicianGirl」は、遊戯王カード「ブラック・マジシャン・ガール」の情報を表現したものです。

遊戯王  ブラック・マジシャン・ガール(U)(15AY-JPA04)

class BlackMagicianGirl :
    def __init__(self) :
        self.atack = 2000
        self.defence = 1700
        self.attribute = "闇"
        self.tribe = "魔法使い"
        self.name = "ブラック・マジシャン・ガール"
        self.level = 6

    def effect(self) :
        n_master = sum(["ブラック・マジシャン" in k for k in cemetary])
        self.atack += 300*n_master

カード情報は、大きく分けて、カードのステータスである「インスタンス変数」と、 カードの効果である「メソッド」に分かれます。 2行目、def init(self) 以下が、インスタンス変数にあたり、 攻撃力や守備力、属性、種族、レベルなどが書かれています。 11行目、def effect(self) 以下がメソッドにあたり、 ブラック・マジシャン・ガールの効果を表現しています。

メソッドを持たないクラス : 通常モンスター

メソッドを持たないインスタンス変数のみクラスももちろん定義できます。 遊戯王で例えるならば、効果を持たない通常モンスターにあたります。 例えばグレムリンだとこんな感じです。

【中古】[TCG]遊戯王 15AY-JPA11N グレムリン(20140308)

class Gremlin :
    def __init__(self) :
        self.atack = 1300
        self.defence = 1400
        self.attribute = "闇"
        self.tribe = "悪魔"
        self.name = "グレムリン"
        self.level = 4

インスタンス変数を持たないクラス : 魔法カード

メソッドを持たないクラスが通常モンスターであったように、 インスタンス変数を持たないメソッドのみのクラスを定義できます。 これは、攻撃力は守備力等のステータスを持たない、いわば魔法カードと言っていいでしょう。

小学生の頃めちゃくちゃお世話になったカード、「治療の神 ディアン・ケト」をクラスで書いてみます。

【プレイ用】遊戯王 YSD3-JP023 治療の神 ディアン・ケト(日本語版 ノーマル) 【中古】

class DianKeto :
    
    def activate(self) :
        new_life = life + 1000
        return(new_life)

ディアン・ケトライフポイントを1000回復してみましょう。

life = 4000
print(f"ライフポイント : {life}")
keto = DianKeto()
life = keto.activate()
print(f"ライフポイント : {life}")

出力はこんな感じです。

ライフポイント : 4000
ライフポイント : 5000

ライフポイントが4000から5000に上がります。 言わずと知れた最強カードです(大嘘)。

インスタンス化 : カードを「場に出すこと」

インスタンス化とは、クラスを呼び出すことです。 一度呼び出したカードの情報は、以下のように確認することができます。

BMG = BlackMagicianGirl()
print(f"カード名 : {BMG.name}")
print(f"属性 : {BMG.attribute}")
print(f"種族 : {BMG.tribe}")
print(f"レベル : {BMG.level}")

以下は出力です。

カード名 : ブラック・マジシャン・ガール
属性 : 闇
種族 : 魔法使い
レベル : 6

またインスタンス化とは、カードの召喚に近いでしょう。 一度呼び出した(召喚した)カードの情報は書き換えることが可能です。 それでは、ブラック・マジシャン・ガールを召喚(インスタンス化)し、自身の効果(メソッド)を適用し、 攻撃力を上げて見ましょう。 ブラック・マジシャン・ガールの攻撃力は、cemetery(墓地)にあるブラック・マジシャンの数につき、300上がります。 例えば、墓地にグレムリン1体とブラック・マジシャン1体の時。

cemetary = ["グレムリン", "ブラック・マジシャン"]
BMG = BlackMagicianGirl()
print(f"攻撃力 : {BMG.atack}")
BMG.effect()
print(f"攻撃力 : {BMG.atack}")

出力は以下のようになります。

攻撃力 : 2000
攻撃力 : 2300

場にいるBMG(ブラック・マジシャン・ガール)の攻撃力が、効果の適用後300上がりましたね。

一方ここで、

print(BlackMagicianGirl().atack)

としてみると、攻撃力は2000のままです。 しかしこれは先ほどインスタンス化(召喚)したBMGではないので、当たり前です。 クラスに書かれていることはカードの基本情報な訳ですから、簡単に書き換わってしまっては困ります。

つまり、インスタンス化した(場に出た)後、インスタンス(場のカード)の値は変化しても、 クラスそのものの値(カードの基本情報)は書き変わらないのです。 ここがオブジェクト指向の一つの肝なのではないでしょうか。

引数を持つメソッド : 対象を取る効果

メソッドに引数を与えることも可能です。厳密には違うのでしょうが、 「引数を持つメソッド」は「対象を取る効果」に近いかもしれません。 例えば、「突進」を以下のように実装してみます。

遊戯王カード 突進 レア ビギナーズ・エディション Vol.1 7期 BE01 YuGiOh! | 遊戯王 カード レア 速攻魔法

class Tosshinn :
    def __init__(self) :
        self.card_type = "magic"

    def activate(self, target) :
        target.atack += 700

突進で、場のブラック・マジシャン・ガールを対象にとり、攻撃力を上げてみましょう。

BMG = BlackMagicianGirl()
print(f"攻撃力 : {BMG.atack}")
Tosshinn().activate(BMG)
print(f"攻撃力 : {BMG.atack}")

出力はこんな感じです。

攻撃力 : 2000
攻撃力 : 2700

突進をactivate()発動した前と後で、BMGの攻撃力が変化していますね。

クラス外の変数を書き換える

ディアン・ケトの実装コードであれ?と思った方もいるかもしれません。 というのも、new_life という変数を定義せずに、

class DianKeto :

    def activate(self) :
        life += 1000
        return(life)

life = 4000
print(f"ライフポイント : {life}")
keto = DianKeto()
life = keto.activate()
print(f"ライフポイント : {life}")

とした方がシンプルなのです。しかしこれは、変数スコープの都合でエラーになるので注意が必要です。 (https://qiita.com/N-qiita/items/de972c176f50c22414a6)

---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-68-ef833056dfe9> in <module>
----> 1 life = keto.activate()

<ipython-input-64-b3c8af5464db> in activate(self)
      2 
      3     def activate(self) :
----> 4         life += 1000
      5         return(life)

UnboundLocalError: local variable 'life' referenced before assignment

ライフポイントや墓地などの、 どのカードも用いる可能性のある変数は、 外部変数として定義しておいて、メソッド内で外部変数だということを教えてやればいいのです。

class DianKeto :

    def activate(self) :
        global life
        life += 1000
        return(life)

life = 4000
print(f"ライフポイント : {life}")
keto = DianKeto()
life = keto.activate()
print(f"ライフポイント : {life}")
ライフポイント : 4000
ライフポイント : 5000

次回の予定

次回は、クラスの継承やオーバーライドを、同じく遊戯王で例えて説明していきたいと思います。 拡散いただけると励みになります。おもろいなとかちょっと役立つなと感じた方は、 デュエリストのお友達に教えてあげてください。 ではまた!