Python初心者が簡単なRPG風テキストゲームを作ってみた結果　Part.2

# 概要
　今回は以前作成したRPG風テキストゲームにストーリー性を追加したものになります。
しらかみゅさん(@shiracamus)が僕の書いたコードを、大幅にクラスやインスタンスの概念などを改良したものから、さらに僕がストーリーを軽く肉付けしたものになります。
ストーリー自体は王道…、というかベタなものです。

# コード内容
```duel_braver.py
import time
import sys
import random
import keyboard
from typing import Callable

class Point:
    """ヒットポイント、マジックポイントなどいろいろステータス"""

    def __init__(self,point: int) -> None:
        self._remain: int = point
        self._max: int = point
    
    def __str__(self) -> str:
        """表示文字列を返す"""
        return str(self._remain)
    
    def use(self, point:int) -> bool:
        """point分使えるならTrueを返す、ポイント不足ならFalseを返す"""
        if self._remain < point:
            return False
        self.damage(point)
        return True
    
    def damage(self, point:int) -> None:
        """ダメージ分がHPから引かれる"""
        self._remain -= point

    def charge(self, point:int) -> None:
        """HP回復"""
        self._remain=min(self._remain+point,self._max)

    def is_full(self) -> bool:
        """HP上限突破を防ぐ"""
        return self._remain == self._max
    
    def is_empty(self) -> bool:
        """残りが0以下なら値を返す"""
        return self._remain <= 0
    
class Power:
    """武器攻撃力のクラス"""
    def __init__(self, minimum: int, maximum: int) -> None:
        self._points: range = range(minimum, maximum + 1)

    def use(self) -> int:
        """力を使う、ポイントを返す"""
        return random.choice(self._points)

class Magic:
    """魔法攻撃のクラス"""
    def __init__(self, name: str, point: int, power: Power) -> None:
        self._name: str = name # プレイヤー名
        self._point: int = point  # 魔法を使用するのに必要なポイント
        self._power: Power = power #攻撃

    def __str__(self) -> str:
        """表示文字列を返す"""
        return self._name

    def use(self, mp: Point) -> int:
        """mpを消費して魔法を使う。攻撃ポイントを返す"""
        if mp.use(self._point):
            return self._power.use()
        else:
            return 0  # mp不足
        
class HealthPosion:
    """回復薬"""

    def __init__(self,remain = 1,point = 100):
        self._remain = remain
        self._point = point

    def remain(self):
        return self._remain
    
    def use(self,hp: Point):
        """ポーションがあるならhpを回復してTrueを返す、ないならFalseを返す"""
        if self._remain <= 0:
            return False
        self._remain -= 1
        hp.charge(self._point)
        return True

class UpperPosion(HealthPosion):
    def __init__(self,remain = 1,point = 200):
        self._remain = remain
        self._point = point

    def remain(self):
        return self._remain
    
    def use(self,hp: Point):
        """ポーションがあるならhpを回復してTrueを返す、ないならFalseを返す"""
        if self._remain <= 0:
            return False
        self._remain -= 1
        hp.charge(self._point)
        return True

class Character:
    """プレイヤーと敵モンスターのデータの源！"""

    def __init__(self,name: str,health_point: int, magic_point: int,weapon:Power,shield:Power)->None:
        """キャラクターの雛型"""
        self._name: str = name
        self._hp:Point = Point(health_point)
        self._mp:Point = Point(magic_point)
        self._weapon:Power = weapon
        self._shield:Power = shield

    def __str__(self)->str:
        """表示文字列を返す"""
        return str(self._name)
    
    def _message(self,text: str,wait: int=1)->None:
        """キャラクタのメッセージ出力"""
        print(f"{self}:{text}")
        time.sleep(wait)

    def status(self)->str:
        """ステータスを返す"""
        return f"{self}のHP:{self._hp},MP:{self._mp}"
    
    def defense(self,point: int)->int:
        """攻撃を防御する"""
        return self._damage(point//2-self._shield.use()//4)
    
    def _damage(self,point)->int:
        """ダメージ処理"""
        self._hp.damage(point)
        self._message(f"{point}のダメージ")
        return point
    
    def is_dead(self)->bool:
        """死亡していたらTrueを返す"""
        return self._hp.is_empty()

class Weapon(Power):
    def __init__(self,name,minimum,maximum):
        self._name=name
        super().__init__(minimum,maximum)

    def __str__(self):
        """表示文字列を返す(武器名)"""
        return self._name

#プレイヤーのクラス    
class Player(Character):

    def __init__(self,name: str,weapon)->None:
        super().__init__(name,300,30,Power(0,0),Power(40,45))
        self._weapon:Weapon=weapon
        self._message(f'{weapon}で行く！')
        self._is_guard: bool=False
        self._health_posion: HealthPosion=HealthPosion(5)
        self._upper_posion:UpperPosion=UpperPosion(2)
        self._COMMANDS: dict[str,Callable[[Character],bool]]={
            'a':self._use_weapon,
            'm':self._use_magic,
            's':self._use_strong_magic,
            'g':self._guard,
            'd':self._heal,
            'h':self._upper_heal,
        }

    def status(self)->str:
        return f"{super().status()},回復薬:{self._health_posion.remain()},回復薬（上）:{self._upper_posion.remain()}"
    
    #防御
    def defense(self,point: int)->int:
        """防御する"""
        if self._is_guard:
            self._is_guard = False
            if random.random() >= 0.1:
                self._message("攻撃を弾いた！")
                return 0
        return super().defense(point)
    
    def attack(self,monster: Character)->None:
        """攻撃する"""
        print("-------------------------")
        print("|a:攻撃　|m:攻撃魔法　　|")
        print("|g:防御　|s:強力攻撃魔法|")
        print("|d:回復薬|h:回復薬（上）|")
        print("-------------------------")
        while((command := input("選択>>>"))not in self._COMMANDS or not self._COMMANDS[command](monster)):
            pass

    def _use_weapon(self,monster: Character)->bool:
        """武器を使って攻撃する"""
        self._message(f"「このォ！」")
        monster.defense(self._weapon.use())
        return True
    
    def _use_magic(self,monster: Character,magic: Magic = Magic("サンダーシュート",5,Power(125,130)))->bool:
        """魔法を使って攻撃する"""
        point = magic.use(self._mp)
        if point == 0:
            self._message(f"「MPが足りない！」")
            return False  # 再入力
        self._message(f"「{magic}！」", 0)
        monster.defense(point)
        return True

    def _use_strong_magic(self,monster:Character, magic:Magic = Magic("フレイムバースト", 10, Power(145, 150))) -> bool:
        """強力魔法を使って攻撃する"""
        return self._use_magic(monster, magic)
    
    def _guard(self,monster) -> bool:
        """盾を使って防御する"""
        self._is_guard = True
        self._message(f"「盾で防ぐぞ！」")
        return True

    def _heal(self,monster:Character) -> bool:
        """生命力を回復する"""
        if self._hp.is_full():
            self._message(f"「今はいらないな」")
            return False #再入力
        if self._health_posion.use(self._hp):
            self._message(f"「力がみなぎって来たぞ！」\nライフが回復した！")
            return True
        else:
            self._message(f"「回復薬が無い！」")
            return False #再入力

    def _upper_heal(self,monster:Character) -> bool:
        if self._hp.is_full():
            self._message(f"「今は使わなくていいな」")
            return False
        if self._upper_posion.use(self._hp):
            self._message(f"「うおおおぉっ！！」")
            return True
        else:
            self._message(f"「まずい！ 切れたか！」")
            return False

#モンスターのクラス
class Monster(Character):

    def __init__(self, name: str, hp: int, mp: int, ap: Power, sp: int, magic: Magic) -> None:
        super().__init__(name, hp, mp, ap, Power(sp, sp))
        self._magic: Magic = magic

    def attack(self, player: Character) -> None:
        if random.random() > 0.3:
            point = self._weapon.use()
            self._message(f"「喰らえ！」", 0)
        else:
            point = self._magic.use(self._mp)
            if point == 0:
                self._message("「MPが足りない！」")
                return
            self._message(f"「{self._magic}！」", 0)
        if player.defense(point) == 0:
            self._message("「やるな」")
        else:
            self._message("「馬鹿め！」")

#モンスターの一覧   
MONSTERS = (
    Monster("スカルヘッド",250,50,Power(50,60),20,
        Magic("スカルヘッドバット",5,Power(50,60))),
    Monster("ゴーストナイト",300,70,Power(75,85),20,
        Magic("ダークスラッシュ",5,Power(100,105))),
    Monster("デスマシーン",310,75,Power(80,85),25,
        Magic("デスクラッシュ",5,Power(105,110))),
    Monster("ギルドラゴン",320,80,Power(85,95),25,
        Magic("バーンフレイム",5,Power(110,115))),
    Monster("ダークウィザー",350,90,Power(105,115),25,
        Magic("キルストーム",5,Power(120,125))),
    Monster("ネビュラシーザー",500,100,Power(130,135),30,
        Magic("ヘル・デストロイ",5,Power(170,175))), 
)

WEAPONS = {
    "m": Weapon("マスターブレード",120,130),
    "b": Weapon("ビッグソード",100,110),
    "s": Weapon("スマートダガー",80,90)
}

class Old_Man(Character):
    def __init__(self, name: str)->None:
        self._name=name
    
    def go_fight(self)->None:
        self._message(f"「{name}よ、戦うのじゃ」")

    def help_me(self)->None:
        self._message(f"「大変だ！　ワシ達の村に魔物が襲ってきた！\n　　　　　 済まない、どうか助けてくれないか？」")

    def question(self) -> None:
        print()
        print("-------------------------")
        print("|yキー:はい,nキー:いいえ|")
        print("-------------------------")
        print()
        say=input('どうしますか？>>>')
        if say == 'y':
            self._message(f'「おお、やってくれるか。名前は何という？」')
        elif say == 'n':
            self._message(f"「そうか。仕方あるまい。\n　　　　　なら今の状況を受け入れるか」")
            game_over()
        else:
            self._message(f'「何をぐずぐずしている」')

def show_title() -> None:
    """タイトル表示"""
    print("*************")
    print("*DUEL BLAVER*")
    print("*************")

def go() -> Old_Man:
    old_man=Old_Man("長老ソル")
    old_man.go_fight()

def tell() -> Old_Man:
    old_man=Old_Man("長老ソル")
    old_man.question()

def entry_player() -> Player:
    global name
    """プレイヤー登場"""
    tell()
    name = input("名前を入力してください>>>")
    print()
    weapon = select_weapon()
    print()
    go()
    return Player(name,weapon)

def select_weapon() -> Weapon:
    print("----------------------------------------")
    print("|m:マスターブレード(入門者向け)　　　　|")
    print("|b:ビッグソード　　(プレイ済みの方向け)|")
    print("|s:スマートダガー　(熟練者向け)　　　　|")
    print("----------------------------------------")
    while(selection := input("武器を選択してください>>>")) not in WEAPONS:
        pass
    weapon = WEAPONS[selection]
    return weapon

def opening() -> None:
    show_title()
    time.sleep(1)
    print("一匹の魔物が王国を攻撃しようとしているぞ！")
    print("果たして止める事は出来るのか！？")
    time.sleep(2)
    old_man=Old_Man("長老ソル")
    old_man.help_me()
    time.sleep(1)

def ending() -> None:
    print("モンスターを倒した！\n")
    print(f"化け物は{name}の手で倒された。")
    print(f"この国にも平和が戻った。\nありがとう、{name}。")
    time.sleep(1)
    print("************")
    print("*GAME CLEAR*")
    print("************")

def appear_monster() -> Monster:
    """モンスター出現"""
    monster = random.choice(MONSTERS)
    print(f"{monster}出現！")
    return monster

def game_over():
    print("一匹の魔物の手によってこの国は滅び、\n次第にそこは魔物の巣窟と化し、魔界帝国が誕生した。\n")
    time.sleep(2)
    print("***********")
    print("*GAME OVER*")
    print("***********")
    sys.exit()

def main() -> None:
    """主なゲームの流れ一式"""
    print();opening()
    player = entry_player()
    print()
    monster = appear_monster()
    time.sleep(1)

    while True:
        print()
        print(player.status());print(monster.status())
        time.sleep(2)

        print(f"\n***{player}のターン！***")
        player.attack(monster)
        if monster.is_dead():
            ending()
            return

        print(f"\n***{monster}のターン！***")
        monster.attack(player)
        if player.is_dead():
            print(f"{player}は敗れた・・・\n")
            game_over()
            return

if __name__ == "__main__":
    main()
```

# 感想
まだ長老のセリフ云々は改良出来ると思うので、今後もいろいろ手を加えていきたいです。
可能であれば、いずれはモンスターと連戦できるように改良もしたいところ。