Python オブジェクト指向プログラミング入門:RPGゲームで学ぶクラス設計

今回は、Pythonを使った オブジェクト指向プログラミング の練習として、簡易RPGゲームのソースコードを解説します。このプログラムでは、キャラクタープレイヤーモンスター というクラス設計を通して、オブジェクト指向の基礎を学ぶことができます。

プログラム概要

このRPGゲームでは、プレイヤーがモンスターと戦います。以下の要素が含まれています。

  • Character クラス:基底クラスとしてキャラクターの基本情報を管理
  • Player クラス:プレイヤー専用の機能(回復や逃げる機能など)
  • Monster クラス:モンスターの特殊攻撃を含む派生クラス(炎、氷、風)
  • Game ロジック:攻撃、回復、逃げる、特殊攻撃などのアクション

プログラムの構成

1. クラスの構成図

Character (基底クラス)
├── Player (Character を継承)
└── Monster (Character を継承)
      ├── FireMonster (Monster を継承)
      ├── IceMonster (Monster を継承)
      └── WindMonster (Monster を継承)

Character クラス:基底クラス

character.py

class Character:

    def __init__(self, name: str, hp: int, attack_power: int) -> None:
        self.__name = name
        self.__hp = hp
        self.__attack_power = attack_power

    @property
    def name(self) -> str:
        return self.__name
    
    @property
    def attack_power(self) -> int:
        return self.__attack_power
    
    @property
    def hp(self) -> int:
        return self.__hp
    
    @hp.setter
    def hp(self, value: int) -> None:
        self.__hp = max(0, value)  # HPが負にならないようにする

    @attack_power.setter
    def attack_power(self, value: int) -> None:
        self.__attack_power = value

    """攻撃"""
    def attack(self, target: 'Character') -> None: 
        damage = self.__attack_power
        target.hp -= damage
        print(f"{self.name} は {target.name} に {damage} ダメージを与えた!")

    """生存確認"""
    def is_alive(self) -> bool:
        return self.__hp > 0

    """ステータス表示"""
    def show_status(self) -> None:
        print(f"{self.name}: HP {self.__hp}")

    """逃げる"""
    def escape(self) -> bool:
        return(1 if self.__hp > 30 else 0)

クラス概要

Character クラスは、プレイヤーとモンスターの共通の属性とメソッドを持っています。名前、HP、攻撃力 を管理し、以下の機能を提供します。

  • 攻撃 (attack)
  • 生存確認 (is_alive)
  • ステータス表示 (show_status)
  • 逃げる (escape)
class Character:

    def __init__(self, name: str, hp: int, attack_power: int) -> None:
        self.__name = name
        self.__hp = hp
        self.__attack_power = attack_power

    @property
    def name(self) -> str:
        return self.__name
    
    @property
    def attack_power(self) -> int:
        return self.__attack_power
    
    @property
    def hp(self) -> int:
        return self.__hp
    
    @hp.setter
    def hp(self, value: int) -> None:
        self.__hp = max(0, value)  # HPが負にならないようにする

ポイント解説

  1. カプセル化(プライベート変数)
    • self.__name, self.__hp, self.__attack_power はプライベート変数として定義されています。
    • 外部から直接変更できないようにすることで、不正なデータ操作を防ぎます。
  2. プロパティ(@property)
    • @property を使用して、ゲッターとセッターを定義。
    • hp のセッターでは、max(0, value) でHPがマイナスにならないように制御しています。
  3. 攻撃メソッド (attack)
def attack(self, target: 'Character') -> None: 
    damage = self.__attack_power
    target.hp -= damage
    print(f"{self.name} は {target.name} に {damage} ダメージを与えた!")
  • 他の Character クラスをターゲットにして攻撃します。
  • ターゲットのHPが減少 する仕組みです。
  1. 逃げる (escape)
def escape(self) -> bool:
    return(1 if self.__hp > 30 else 0)
  • HPが30以上の場合に 逃げることが成功 します。

Monster クラス:モンスターの基底クラスと派生クラス

monster.py

from character import Character

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

    def special_attack(self, target: Character) -> None:
        pass  # サブクラスで実装
   
# 派生モンスタークラス(特殊攻撃あり)
class FireMonster(Monster):

    def special_attack(self, target: Character) -> None:
        damage = self.attack_power + 10
        target.hp -= damage
        print(f"{self.name} の火炎攻撃! {target.name} に {damage} ダメージ!")

class IceMonster(Monster):

    def special_attack(self, target: Character) -> None:
        damage = self.attack_power + 5
        target.hp -= damage
        print(f"{self.name} の氷結攻撃! {target.name} に {damage} ダメージ!")

class WindMonster(Monster):
    def special_attack(self, target: 'Character') -> None:
        damage = self.attack_power + 40
        target.hp -= damage
        print(f"{self.name} の風の刃攻撃! {target.name} に {damage} ダメージ!")

クラス概要

Monster クラスは Character クラスを継承し、以下の 特殊攻撃 を持つ派生クラスを作成しています。

  • FireMonster:火炎攻撃
  • IceMonster:氷結攻撃
  • WindMonster:風の刃攻撃

基底クラス: Monster

class Monster(Character):

    def special_attack(self, target: Character) -> None:
        pass  # サブクラスで実装
  • special_attack() はサブクラスでオーバーライドされます。

派生クラス: FireMonster, IceMonster, WindMonster

class FireMonster(Monster):
    def special_attack(self, target: Character) -> None:
        damage = self.attack_power + 10
        target.hp -= damage
        print(f"{self.name} の火炎攻撃! {target.name} に {damage} ダメージ!")
  • attack_power10ポイント のボーナスを追加した火炎攻撃を行います。

Player クラス:プレイヤー専用の機能

player.py

クラス概要

Player クラスは Character を継承し、プレイヤー専用の以下の機能を追加しています。

  • 回復 (heal)
  • 逃げる (escape)

回復メソッド

def heal(self) -> None:
    self.hp += self.heal_amount
    print(f"{self.name} は {self.heal_amount} HPを回復した!")
  • heal_amount 分だけHPを回復します。

ゲームの流れ

game.py

import random
from player import Player
from monster import FireMonster, IceMonster, WindMonster

# ゲームロジック
def main():

    print("=== 簡易RPGゲーム ===")
    player = Player("プレイヤー", 100, 15)
    monsters = [
        FireMonster("炎の魔物", 50, 10), 
        IceMonster("氷の魔物", 60, 8),
        WindMonster("風の魔物", 100, 10),
        ]
    current_monster = random.choice(monsters)

    print(f"{current_monster.name} が現れた!")

    while player.is_alive() and current_monster.is_alive():
        
        print("\nあなたのターン")
        player.show_status()
        current_monster.show_status() # 現在のモンスターを表示1
        print("1: 攻撃する  2: 防御する  3: 回復する 4: 逃げる")
        
        match choice := str(input("行動を選んでください: ")):
            case "1":
                player.attack(current_monster)
            case "2":
                print(f"{player.name} は防御した! ダメージを軽減!")
            case "3":
                player.heal()
            case "4":
                if player.escape():
                    print('逃げ切れた!')
                    break
                else:
                    print('逃げられない...')
            case _:
                print("無効な選択です。")

        # モンスターのターン
        if current_monster.is_alive():
            print("\nモンスターのターン")
            if random.choice([True, False]):
                current_monster.special_attack(player)
            else:
                current_monster.attack(player)

        # 状態チェック
        if not player.is_alive():
            print("\nあなたは倒されました…ゲームオーバー。")
            break
        elif not current_monster.is_alive():
            print(f"\n{current_monster.name} を倒した!勝利!")

if __name__ == "__main__":
    main()

概要

  1. プレイヤーとモンスターの生成
  2. プレイヤーのターン:攻撃、防御、回復、逃げる
  3. モンスターのターン:通常攻撃または特殊攻撃
  4. 勝利・敗北の判定

ゲームループ

  while player.is_alive() and current_monster.is_alive():
        
        print("\nあなたのターン")
        player.show_status()
        current_monster.show_status() # 現在のモンスターを表示1
        print("1: 攻撃する  2: 防御する  3: 回復する 4: 逃げる")
        
        match choice := str(input("行動を選んでください: ")):
            case "1":
                player.attack(current_monster)
            case "2":
                print(f"{player.name} は防御した! ダメージを軽減!")
            case "3":
                player.heal()
            case "4":
                if player.escape():
                    print('逃げ切れた!')
                    break
                else:
                    print('逃げられない...')
            case _:
                print("無効な選択です。")
  • プレイヤーは、攻撃、防御、回復、逃げる の選択肢から行動を選択できます。

モンスターのターン

if current_monster.is_alive():
    print("\nモンスターのターン")
    if random.choice([True, False]):
        current_monster.special_attack(player)
    else:
        current_monster.attack(player)
  • 50% の確率 でモンスターは特殊攻撃を使用します。

ポリモーフィズムの動作例

以下のコードは、ポリモーフィズム を利用して、異なるモンスターオブジェクト を 共通のインターフェース で操作しています。

# モンスターのインスタンス生成
monsters = [
    FireMonster("炎の魔物", 50, 10), 
    IceMonster("氷の魔物", 60, 8),
    WindMonster("風の魔物", 100, 10),
]

# プレイヤーのインスタンス
player = Character("プレイヤー", 100, 15)

# ポリモーフィズムの実現:すべてのモンスターに対して special_attack を実行
for monster in monsters:
    monster.special_attack(player)

monsters リストには、FireMonsterIceMonsterWindMonster の 異なるオブジェクト が含まれています。

すべてのオブジェクトは、Monster クラスを 継承 しているため、共通のインターフェース (special_attack()) を持っています。

special_attack() メソッドは、各クラスでオーバーライド されているため、実行される動作が異なります。

オブジェクト指向のポイント

  • 継承CharacterPlayerMonster の継承関係
  • カプセル化:プライベート変数によるデータ保護
  • ポリモーフィズムspecial_attack() のオーバーライド

まとめ

このプログラムは、オブジェクト指向プログラミングの基礎 を学ぶのに最適な構成です。

  • クラス設計の考え方 を理解する
  • 継承カプセル化ポリモーフィズム を実践的に学ぶ
  • ゲームロジックの設計 を通してプログラムの流れを理解する