オブジェクト指向プログラミングでポ〇モン風ゲーム（Python）

# はじめに
　題名のとおりです。オブジェクト指向プログラミングの練習でポケモ〇によく似たクソゲーを作ってみました。

# 要件定義
・Pythonでオブジェクト指向を使って作る
・ユーザーの入力を基に主人公の属性、選ぶポケモン、行動などを決定する
・Pythonの対話型シェルを使って動作させる

以上です。

# 環境、使用ツールなど
・Windows10
・Python3.8.8
・IDLE（Anaconda付属のもの）
・numpy(エンカウントする野生ポケモンをランダムに決めるのに使用)
・time(対話型シェルの反応を意図的に遅くできるsleepを使用)

# コード
``` Python:pokemon.py

import numpy as np
import time

class Player:
    def __init__(self):
        self.name = str(input("ようこそ、ポケットモンスターの世界へ！！私はオー○ド。チミのお名前は何と言うんじゃ？？ >> "))
        self.job = str(input(self.name + "は何の仕事をしておるんじゃ >> "))
        self.age = int(input("おいくつなんじゃ？ >> "))
        if not isinstance(self.age, int): # self.ageが数値型かどうかの確認
            time.sleep(0.3)
            print()
            print('年齢は数字で入力してください！！')
        self.HP = int(input("体力はどのくらいあるのかな >> "))
        if not isinstance(int(self.HP), int): # self.HPが数値型かどうかの確認
            time.sleep(0.3)
            print()
            print('HPは数字で入力してください！！')
        self.MP = int(input("魔法はどのくらい使えるのかな >> "))
        if not isinstance(int(self.MP), int): # self.MPが数値型かどうかの確認
            time.sleep(0.3)
            print()
            print('MPは数字で入力してください！！')
        self.power = int(input("腕っぷしはどのくらい自信があるんじゃ？ >> "))
        if not isinstance(int(self.age), int): # self.powerが数値型かどうかの確認
            time.sleep(0.3)
            print()
            print('腕っぷしの強さは数字で入力してください！！')

    def info(self):
        time.sleep(1.2)
        print()
        print("------------------")
        print("名前 : " + self.name)
        print("職業 : " + self.job)
        print("年齢 : " + str(self.age))
        print("HP   : " + str(self.HP))
        print("MP   : " + str(self.MP))
        print("power: " + str(self.power))
        print("-------------------")
        print()

        while True:
            print("これで登録するぞ？？良いな？？")
            answer = input("▶はい　▶分かった　▶うん　▶いやだ >> ")
            if answer == "はい" or answer == "分かった" or answer == "うん":
                time.sleep(1.5)
                print()
                print("主人公情報を登録しました。これであなたもこの世界の主人公です。")
                break
            elif answer == "いやだ":
                print("駄々をこねたため、" + self.name + "の目の前は真っ黄色になった。")
                exit()
            else:
                time.sleep(0.5)
                print()
                print("？？日本語で話してくれんかのう")
                print()
                time.sleep(1.5)

    def attack(self, enemy):
        print(self.name + "の攻撃！！！")
        print(enemy + "は" + str(self.power) + "のダメージを受けた！")

    def walk(self):
        while True:
            self.walkmiles = int(input('何歩あるく？？ >> '))
            self.direction = str(input('方角は？（↑,↓,←,→の中から選択） >> '))
            print()
            time.sleep(0.5)
            field = Field(self.walkmiles, self.direction, self.name, enemy_group)
            field.event()

class Enemy:
    def __init__(self, index, enemy, power, HP, me):
        self.index = index
        self.name = enemy
        self.power = power
        self.HP = HP
        self.me = me
        
        time.sleep(1)
        print("あ、やせいの" + self.name + "があらわれた！")

    def attack(self):
        
        time.sleep(1)
        print(self.name + "の攻撃！！   " + self.me + "は" + str(self.power) + "のダメージを受けた！！")

class Pokemon:
    def __init__(self):
        print()
        time.sleep(2)
        while True:
            self.name = str(input("どのポケモンが良いんじゃ ▶フシギダネ ▶フシギソウ ▶カメックス ▶ミュウツ― ▶ピカチュウ ▶ミミッキュ >> "))
            time.sleep(2)
            print()
            if self.name == "フシギダネ":
                self.power = 1
                self.HP = 6
                break
            elif self.name == "フシギソウ":
                self.power = 16
                self.HP = 22
                break
            elif self.name == "カメックス":
                self.power = 20
                self.HP = 80
                break
            elif self.name == "ミュウツ―":
                self.power = 120
                self.HP = 20
                break
            elif self.name == "ピカチュウ":
                self.power = 50
                self.HP = 1
                break
            else:
                time.sleep(1)
                print("そのポケモンは、ワシのお気に入りじゃから、あげん")
                print()
                time.sleep(1)

    def pokeindex(self):
        time.sleep(2)
        print('ポケモン図鑑に登録されました')
        time.sleep(1)
        print()   
        print("<><><>*****<><><>****<><><>******<><><>******<><><>******<><><>")
        print()
        time.sleep(0.5)
        print("なまえ   :" + self.name)
        time.sleep(0.5)
        print("こうげき :" + str(self.power))
        time.sleep(0.5)
        print("HP       :" + str(self.HP))
        time.sleep(1)
        if self.name == "フシギダネ":
            self.index = "臭い"
        elif self.name == "フシギソウ":
            self.index = "映画に出たことがある"
        elif self.name == "カメックス":
            self.index = "固い"
        elif self.name == "ミュウツ―":
            self.index = "ダークライよりは弱い"
        elif self.name == "ピカチュウ":
            self.index = "アニメでは強い"
        else:
            self.index = "けつばん"
        print("せつめい :" + self.index)
        time.sleep(1)
        print()
        print("<><><>*****<><><>****<><><>******<><><>******<><><>******<><><>")
        print()
        time.sleep(2)

class Field:
    def __init__(self, walkmiles, direction, me, enemies):
        self.walkmiles = walkmiles
        self.direction = direction
        self.me = me

        # 敵のリストから、それぞれの名前、攻撃力、HPのリストを作っておく
        self.enemy_names  = []
        self.enemy_powers = []
        self.enemy_HP     = []
        for index, name, attack, HP in enemies:
            self.enemy_names.append(name)
            self.enemy_powers.append(attack)
            self.enemy_HP.append(HP)
            #print("Fieldクラスの self.enemiesは")
            #print(self.enemy_names)

    def event(self):
        if self.direction == "↑":
            if self.walkmiles > 50:
                self.enemy_index = int(np.random.randint(1, 1001)) % len(self.enemy_names) # エンカウントする敵のインデックスをランダムに決める
                self.enemy_name  = self.enemy_names[self.enemy_index]
                self.enemy_power = self.enemy_powers[self.enemy_index]
                self.enemy_HP    = self.enemy_HP[self.enemy_index]
                encounter_enemy  = Enemy(self.enemy_index, self.enemy_name, self.enemy_power, self.enemy_HP, self.me)
                encounter_enemy.attack()
            else:
                print("池に落ちました。電源を切り、再起動をしてください。")
                exit()
        elif self.direction == "↓":
            if self.walkmiles > 100:
                print("壁にぶつかった！！身動きが取れない！！")
            elif self.walkmiles > 50:
                print("カーニバルの大群が向こうからやってくる！！ここは引き返そう")
            else:
                print("ここは隣国です。VISAを見せてください")
                visa = input("▶ しまった、VISAがない！！ ▶ そうだ、京都に行こう ▶ 偽造VISAを見せる ▶ 留学生であると、伝える >> ")
                if visa == "しまった、VISAがない！！":
                    print("隣国に一歩でも立ち入ったため、あなたはこの国の法律で裁かれます。")
                    print("目の前で虫が飛び始めた。緑内障かな")
                    exit()
                elif visa == "そうだ、京都に行こう":
                    print("京都に連行された。ジョウト地方へ行きます。一度電源を切り、ゲームカセットを交換してください。")
                elif visa == "偽造VISAを見せる":
                    print("隣国でお金持ちになった")
                elif visa == "留学生であると、伝える":
                    print("現在コロナ禍のため、立ち入り禁止です。")
        elif self.direction == "←":
            if self.walkmiles > 30:
                self.enemy_index = int(np.random.randint(1, 1001)) % len(self.enemy_names) # エンカウントする敵のインデックスをランダムに決める
                self.enemy_name  = self.enemy_names[self.enemy_index]
                self.enemy_power = self.enemy_powers[self.enemy_index]
                self.enemy_HP    = self.enemy_HP[self.enemy_index]
                encounter_enemy  = Enemy(self.enemy_index, self.enemy_name, self.enemy_power, self.enemy_HP, self.me)
                encounter_enemy.attack()
            else:
                time.sleep(1)
                print("村人A：こんにちは！ここはカントー地方だよ！君の名前は？ >> ")
        elif self.direction == "→":
            if self.walkmiles % 3 == 1:
                self.enemy_index = int(np.random.randint(1, 1001)) % len(self.enemy_names) # エンカウントする敵のインデックスをランダムに決める
                self.enemy_name  = self.enemy_names[self.enemy_index]
                self.enemy_power = self.enemy_powers[self.enemy_index]
                self.enemy_HP    = self.enemy_HP[self.enemy_index]
                encounter_enemy  = Enemy(self.enemy_index, self.enemy_name, self.enemy_power, self.enemy_HP, self.me)
                encounter_enemy.attack()
            elif self.walkmiles % 5 == 2:
                print("急にめまいがしてきて、目の前が真っ暗になった！！")
                print()
            elif self.walkmiles % 7 == 3 or self.walkmiles % 5 == 4:
                time.sleep(0.5)
                print("国民G: こんにちは。素数って知ってる？")
                time.sleep(1)
                print()
                answer2 = input("▶ 無視をする ▶ たたかう >> ")
                print()
                time.sleep(1)
                if answer2 == "無視をする":
                    print("国民G: 無視しないでよ！！僕は虫じゃないよ！！")
                elif answer2 == "たたかう":
                    input("国民Gとの戦闘が始まりました。ポケモンを選んでください。")
                    print("ポケモンを選んでいる間に全財産奪われて、目の前が真っ青になった！！！")
                else:
                    print("国民G: 君はおもしろいヤツだね。")
                print()
                time.sleep(1)
            else:
                print("国王: こんにちは。君に全財産を渡しに来たよ！")
                time.sleep(0.5)
                answer2 = input("▶ なんで？ ▶ にげる >> ")
                print()
                if answer2 == "なんで？":
                    time.sleep(1)
                    print()
                    print("王妃: 王様はきまぐれなのでございます。")
                elif answer2 == "にげる":
                    time.sleep(0.3)
                    input("どのくらいの速度で逃げる？")
                    print("にげられない！！")
                    time.sleep(1)
                    input("どのくらいの速度で逃げる？")
                    print("にげられない！！")
                    time.sleep(1)
                    input("どのくらいの速度で逃げる？")
                    print("にげられない！！")
                    time.sleep(0.3)
                    print("王様に捕まって、城に連れ去られた！！ゲームを終了します。")
                    exit()
                else:
                    time.sleep(1.5)
                    print("国王: 王様に向かってなんじゃその態度は！！！ゲームの進行に合わせんかい！！！")
        else:
            print("ゲーム規定に反するような動きをしたため、十字キーが壊れて使えなくなりました。")
            print("安全性確保のため、このゲームは10秒以内に自爆します。")
            time.sleep(1)
            for i in reversed(range(10)):
                print(i)
            time.sleep(1)
            print('ちゅ')
            time.sleep(0.4)
            print('ど')
            time.sleep(0.25)
            print('ーん')
            exit()

        print()
        time.sleep(1)

#enemy_group = ["コラッタ","ミュウツ―","ジラーチ","カイオーガ","ルカリオ","ポッポ","ゴマゾウ","ガブリアス"]
enemy_group = zip([0,1,2,3,4,5,6,7],
                  ["コラッタ","ミュウツ―","ジラーチ","カイオーガ","ルカリオ","ポッポ","ゴマゾウ","ガブリアス"],
                  [10, 180, 200, 150, 120, 1, 50, 150],
                  [10, 20, 200, 100, 80, 12, 50, 100])
#print(len(enemy_group))
#print(np.random.randint(1, 1001))
#print(np.random.randint(1, 1001) % len(enemy_group))

player1 = Player()
player1.info()
pokemon1 = Pokemon()
pokemon1.pokeindex()
player1.walk()

```

# コード解説
　まずはPlayerクラスで、対話形式により主人公の情報を登録します。年齢、HP、MPについては数字としても使いたいので、入力されたものをint型に変換します。しかしながら、文章の中に埋め込む際は文字列型に変換する必要があります。ここでいきなり数字以外の型が入力された時にエラーメッセージがでるようにしたかったのですが、int型に変換する時点で内部エラーとなってしまいます。  


　その後Enemyクラス、Pokemonクラスなどで野生ポケモン、最初に選ぶポケモンなどの処理を記述しています。Enemyクラスについては、野生ポケモンとの戦闘シーンで使います。


　最後にFieldクラスです。こちらは、Playerクラスのメソッドwalkで入力された歩数と方角を基に、起こるイベントを決定します。野生ポケモンとの遭遇の他、様々なおかしなイベントを設定しました。  


　クラス以外にはzip形式のリスト（Pythonでの配列）に野生ポケモンの情報を設定し、EnemyクラスやFieldクラスで呼び出して使います。野生ポケモンの名前以外にも攻撃力やHPもここで設定したため、リストの扱い方には苦労しました。出現する野生ポケモンを変えたい場合はここを編集すればOKです。将来的には、フィールド毎に出現するポケモンの種類を変えたりしたいですね。  


　最後に作成したクラスからインスタンスを作成し、必要に応じてメソッドも呼び出しています。実行すると最初の主人公登録からポケモン選択→図鑑登録→冒険開始などとシナリオが進んでいきます。  


　コードの途中で、デバッグ用のコードや途中経過のコードをコメントアウトであえて残しました。見づらいかもしれませんが、参考にしていただけると幸いです。

# ポイント
・time.sleep()を使ってシェルの反応速度をずらすことで、本物の会話のような自然さをだすのに努めました。
・numpyのrandom関数を使って、イベントの発生をやや不規則にしました。
・オブジェクト指向により、後で仕様変更がしやすいようにしました。
・動的型付け言語であるため、エラーが起こりやすいというPythonの特長を踏まえて、極力型を指定することでエラーを抑える工夫をしました。

# 反省点、課題、今後の展望など
・ユーザーからの入力に依存しているにも関わらず、int型と文字列型の相違によっておこるエラーを完全には防げませんでした。誤った入力がされた際にエラーメッセージなどがでるようにしたいです。
・野生ポケモンの配列の作成にかなり手こずったので、今後よりクラスに組み込みやすい方法を発見できたら改善します。
・野生ポケモンとの戦闘シーンを充実させて、自分のポケモンを参加させたり、HPと攻撃力を使って複雑なダメージ計算ができるようにもしたいです。
・最初のポケモン以外に、手持ちのポケモンを増やす仕組みも実装してみたいです。
・フィールドをクラスの継承によって多様にし、フィールド毎に起こるイベントや出現するポケモンを変えられたらよりらしくなるかなと思いました。
・まだまだ読みにくい点や保守性の観点で難がある部分も多いと思うので、より腕を上げたいです。

# 総論
・オブジェクト指向に慣れるにはRPGゲームが良いんだろうなと前から思っていましたが、実際に作ってみることでかなり練習になりました。
・オブジェクト指向プログラミングはやはり設計がしやすいなと思いました。
・エラー予防や入出力の管理という点からも勉強になりました。
・ゲームって作るのにすごい手間がかかっているんだなと思いました。
・Pythonこそ正義
・ミミッキュとガブリアスは正義

# 参考動画
https://twitter.com/yellow_detteiu/status/1505887288798367748?s=20&t=jnmTcD1wDbqYmfw01wo5Qw




それではごきげんよう～