ターン制バトルゲームから学ぶオブジェクト指向

C#の基礎、またオブジェクト指向からポリモーフィズムを学んだ方向けの学習用サンプルになります

ターン制の戦士と魔導士のバトルのイメージで眺めてください

コード

Character warrior = new Warrior(100, 20, 10);
Character mage = new Mage(80, 10, 15);
Character[] characters = { warrior, mage };

Console.WriteLine("バトル開始");

int turn = 0;

while (warrior.CurrentHP > 0 && mage.CurrentHP > 0)
{
    Console.WriteLine("ターン " + (turn + 1));
    Character attacker = characters[turn % 2];
    Character defender = characters[(turn + 1) % 2];

    int damage = attacker.Strength + attacker.GetAdditionalStat() - defender.Defense;

    defender.TakeDamage(damage);

    Console.WriteLine(attacker.GetType().Name + "が" + defender.GetType().Name + "に攻撃!" + damage + "のダメージ");
    Console.WriteLine(defender.GetType().Name + "のHPは、残り" + defender.CurrentHP + "です");

    turn++;
}

Console.WriteLine("バトル終了");
Console.WriteLine(warrior.CurrentHP > 0 ? "戦士の勝ち!" : "魔導士の勝ち!");

abstract class Character
{
    public int MaxHP { get; set; }
    public int CurrentHP { get; set; }
    public int Strength { get; set; }
    public int Defense { get; set; }

    public Character(int maxHP, int strength, int defense)
    {
        MaxHP = maxHP;
        CurrentHP = MaxHP;
        Strength = strength;
        Defense = defense;
    }

    public void TakeDamage(int damage)
    {
        CurrentHP -= damage;
        if (CurrentHP < 0)
        {
            CurrentHP = 0;
        }
    }

    public abstract int GetAdditionalStat();
}

class Warrior : Character
{
    public Warrior(int maxHP, int strength, int defense) : base(maxHP, strength, defense) { }

    public override int GetAdditionalStat()
    {
        return Strength / 2;
    }
}

class Mage : Character
{
    public Mage(int maxHP, int strength, int defense) : base(maxHP, strength, defense) { }

    public override int GetAdditionalStat()
    {
        return Defense / 2;
    }
}

実行結果

バトル開始
ターン 1
WarriorがMageに攻撃!15のダメージ
MageのHPは、残り65です
ターン 2
MageがWarriorに攻撃!7のダメージ
WarriorのHPは、残り93です
ターン 3
WarriorがMageに攻撃!15のダメージ
MageのHPは、残り50です
ターン 4
MageがWarriorに攻撃!7のダメージ
WarriorのHPは、残り86です
ターン 5
WarriorがMageに攻撃!15のダメージ
MageのHPは、残り35です
ターン 6
MageがWarriorに攻撃!7のダメージ
WarriorのHPは、残り79です
ターン 7
WarriorがMageに攻撃!15のダメージ
MageのHPは、残り20です
ターン 8
MageがWarriorに攻撃!7のダメージ
WarriorのHPは、残り72です
ターン 9
WarriorがMageに攻撃!15のダメージ
MageのHPは、残り5です
ターン 10
MageがWarriorに攻撃!7のダメージ
WarriorのHPは、残り65です
ターン 11
WarriorがMageに攻撃!15のダメージ
MageのHPは、残り0です
バトル終了
戦士の勝ち!

解説

全体の流れ

このコードは、C#によるターン制バトルシミュレーションのプログラムです。

  • キャラクタークラスは抽象クラスとして定義され、キャラクターのステータス(MaxHP, CurrentHP, Strength, Defense)のプロパティと、ダメージを受ける(TakeDamage)、追加のスタットを取得する(GetAdditionalStat)ためのメソッドを保持しています。
  • WarriorクラスはCharacterクラスを継承し、Strength / 2を返すように追加ステータスを実装しています。
  • MageクラスはCharacterクラスを継承しており、Defense / 2を返す追加ステータスを実装しています。
  • Warrior変数には、MaxHP 100、Strength 20、Defense 10を持つWarriorクラスのインスタンスが格納されます。
  • mage変数にはMageクラスのインスタンスが格納され、MaxHP80、Strength10、Defense15となります。
  • 文字配列には戦士変数と魔道士変数が格納されます。
  • turn 変数はターン数を表します.
  • whileループの中では、戦士の現在HPと魔導士の現在HPがともに0以上の間、処理が継続されます。
    • 毎ターン、attacker変数には攻撃側のキャラクターが、defender変数には防御側のキャラクターが格納されます。
    • ダメージ変数には、攻撃側の体力+追加ステータス-防御側の防御力が格納されます。
    • 防御側のCurrentHPはダメージ量だけ減少します。
    • コンソールには攻撃メッセージと各キャラクターのHPの状態が表示されます。
    • ターン変数が1つインクリメントされる。
  • whileループ終了後、勝者(戦士のCurrentHP>0)がコンソールに表示される。

コード解説

Character warrior = new Warrior(100, 20, 10);
Character mage = new Mage(80, 10, 15);
Character[] characters = { warrior, mage };

Console.WriteLine("バトル開始");

この部分は、2つのキャラクターオブジェクト(戦士、魔導士)を作成し、それらをキャラクター配列に格納しています。また、「バトル開始」というメッセージをコンソールに表示します。

  1. Character warrior = new Warrior(100, 20, 10);:ここでは、Warriorクラスから新しい戦士オブジェクトを作成し、Character型の変数warriorに代入します。引数100, 20, 10は、戦士の最大HP、攻撃力、防御力を表します。
  2. Character mage = new Mage(80, 10, 15);:ここでは、Mageクラスから新しい魔法使いオブジェクトを作成し、Character型の変数mageに代入します。引数80, 10, 15は、魔法使いの最大HP、攻撃力、防御力を表します。
  3. Character[] characters = { warrior, mage };:ここでは、作成した戦士と魔法使いオブジェクトを含む配列を作成します。
  4. Console.WriteLine("バトル開始");:ここでは、「バトル開始」というメッセージをコンソールに表示します。
int turn = 0;

while (warrior.CurrentHP > 0 && mage.CurrentHP > 0)
{
    Console.WriteLine("ターン " + (turn + 1));
    Character attacker = characters[turn % 2];
    Character defender = characters[(turn + 1) % 2];

    int damage = attacker.Strength + attacker.GetAdditionalStat() - defender.Defense;

    defender.TakeDamage(damage);

    Console.WriteLine(attacker.GetType().Name + "が" + defender.GetType().Name + "に攻撃!" + damage + "のダメージ");
    Console.WriteLine(defender.GetType().Name + "のHPは、残り" + defender.CurrentHP + "です");

    turn++;
}

このコードは、ターン制のバトルシミュレーションを行っています。

  1. 変数turnが宣言され、初期値は0となっています。
  2. 以下の処理が、warriorキャラクターのCurrentHP0以上、かつmageキャラクターのCurrentHP0以上の間繰り返されます。
    1. 現在のターン番号を表示します。
    2. 攻撃するキャラクターattackerと防御するキャラクターdefenderが決定されます。キャラクターの順番は、配列characters内のインデックスを切り替えて決定されます。
    3. damage変数に攻撃力に加えて、追加のステータス値を引いた値から防御力を引いた値が代入されます。この計算は、attackerStrengthプロパティとGetAdditionalStat()メソッド、defenderDefenseプロパティを利用しています。
    4. defenderdamage分のダメージを受けます。このダメージ処理は、TakeDamage()メソッドを利用しています。
    5. 攻撃者と被害者のキャラクター名と、与えたダメージと、残りのHPが表示されます。
    6. turn変数に1を加えます。
  3. どちらかのキャラクターのHPが0以下になったら、バトルは終了します。そして、生き残ったキャラクターの名前が表示されます。
Console.WriteLine("バトル終了");
Console.WriteLine(warrior.CurrentHP > 0 ? "戦士の勝ち!" : "魔導士の勝ち!");

このコードは、「バトル終了」という文字列を出力し、その後、warriorオブジェクトのCurrentHPが0より大きい場合に「戦士の勝ち!」、0以下の場合に「魔導士の勝ち!」という文字列を出力するものです。ここで、この文字列の出力結果は、勝者を示すものになります。

abstract class Character
{
    public int MaxHP { get; set; }
    public int CurrentHP { get; set; }
    public int Strength { get; set; }
    public int Defense { get; set; }

    public Character(int maxHP, int strength, int defense)
    {
        MaxHP = maxHP;
        CurrentHP = MaxHP;
        Strength = strength;
        Defense = defense;
    }

    public void TakeDamage(int damage)
    {
        CurrentHP -= damage;
        if (CurrentHP < 0)
        {
            CurrentHP = 0;
        }
    }

    public abstract int GetAdditionalStat();
}

このコードは、RPGゲームのキャラクターを表す抽象クラスCharacterを定義しています。

このクラスは、以下のフィールドを定義しています:

  • MaxHP: キャラクターの最大HP
  • CurrentHP: キャラクターの現在のHP
  • Strength: キャラクターの戦闘力
  • Defense: キャラクターの防御力

また、以下のメソッドを定義しています:

  • TakeDamage: 与えられたダメージを受ける。現在のHPが0未満にならないように調整されます。
  • GetAdditionalStat: 追加のステータス値を取得するための抽象メソッド。このメソッドは継承先でオーバーライドされます。

このクラスは抽象クラスであり、抽象メソッドを1つ持っています。抽象クラスは継承して使用することができ、継承先で抽象メソッドを実装する必要があります。

class Warrior : Character
{
    public Warrior(int maxHP, int strength, int defense) : base(maxHP, strength, defense) { }

    public override int GetAdditionalStat()
    {
        return Strength / 2;
    }
}

class Mage : Character
{
    public Mage(int maxHP, int strength, int defense) : base(maxHP, strength, defense) { }

    public override int GetAdditionalStat()
    {
        return Defense / 2;
    }
}

このコードは、「Warrior」クラスと「Mage」クラスを定義しています。これらのクラスは「Character」クラスを継承しています。「Character」クラスは抽象クラスであり、「MaxHP」、「CurrentHP」、「Strength」、「Defense」というプロパティを持っています。また、「TakeDamage」と「GetAdditionalStat」という抽象メソッドも定義されています。

「Warrior」クラスは「Character」クラスを継承しています。「Warrior」クラスは「MaxHP」、「Strength」、「Defense」というパラメータを受け取り、「Character」クラスのコンストラクタを呼び出して初期化します。「Warrior」クラスは「GetAdditionalStat」メソッドをオーバーライドして、「Strength」プロパティを2で割った値を返します。

「Mage」クラスも「Character」クラスを継承しています。「Mage」クラスは「MaxHP」、「Strength」、「Defense」というパラメータを受け取り、「Character」クラスのコンストラクタを呼び出して初期化します。「Mage」クラスは「GetAdditionalStat」メソッドをオーバーライドして、「Defense」プロパティを2で割った値を返します。