【PHP】確認問題5-☆1:モンスター捕獲ゲームを作ろう

記事内に商品プロモーションを含む場合があります

この記事の練習問題で使用する知識:
基礎文法(レッスン1)、制御構造(レッスン2)、関数(レッスン3)、データ構造(レッスン4)、クラス(レッスン5)

PHPのゲームコード一覧はこちら

<<前のページ PHP記事一覧 次のページ>>

確認問題:オブジェクト指向設計を学ぶモンスター捕獲ゲーム

オブジェクト指向の特徴を活かしたモンスター捕獲ゲームを作成しましょう。

プレイヤーは、出現するモンスターとサイコロを振って対決し、サイコロの出目が相手より高ければモンスターを捕まえることができます。

モンスターは種類ごとに特殊能力を持ち、対戦中にそれが表示されます。

ゲームはプレイヤーがモンスターをすべて捕まえるか、捕獲に失敗するまで続きます。

この問題の要件

以下の要件に従ってコードを完成させてください。

  1. 抽象クラス Monster を作成し、モンスターの共通機能を定義すること。
  2. インターフェース DiceRollable を作成し、サイコロを振る機能を持つことを保証すること。
  3. トレイト DiceTrait を作成し、サイコロを振る機能を再利用可能にすること。
  4. 抽象クラス Monster では、以下を定義すること:
    • モンスター名を格納するプロパティ $name
    • 名前を取得する getName() メソッド。
    • 特殊能力を示す抽象メソッド specialAbility()
  5. 各モンスターは抽象クラス Monster を継承し、specialAbility() をオーバーライドすること。
  6. ゲーム管理クラス MonsterCaptureGame を作成し、以下の機能を実装すること:
    • モンスターのリストを管理する。
    • サイコロの結果による捕獲処理を管理する。
    • 捕獲したモンスターをリストに追加する。
    • プレイヤーの選択に基づいて処理を進める。
  7. 実行結果をプレイヤーに表示すること。

ただし、以下のような実行結果となるコードを書くこと。

*****↓↓正解コードの実行結果の例↓↓*****

モンスター捕獲ゲームを開始します!
スライムが現れた!
スライムは分裂する!
スライムがサイコロを振った。出目は 2
サイコロを振りますか? (yes/no): yes
プレイヤーがサイコロを振った。出目は 3
スライムを捕まえた!
ゴブリンが現れた!
ゴブリンは素早く動く!
ゴブリンがサイコロを振った。出目は 5
サイコロを振りますか? (yes/no): no
次のモンスターに進みます。
ドラゴンが現れた!
ドラゴンは火を吹く!
ドラゴンがサイコロを振った。出目は 2
サイコロを振りますか? (yes/no): yes
プレイヤーがサイコロを振った。出目は 5
ドラゴンを捕まえた!
捕まえたモンスター: スライム, ドラゴン
ゲーム終了

この問題を解くヒント

1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。

ヒント1【コードの構成を見る】

正解のコードは上から順に以下のような構成となっています。

1. インターフェースとトレイトの定義

  1. インターフェース DiceRollable の定義
    • サイコロを振るメソッド diceRoll() を定義するための設計書。
  2. トレイト DiceTrait の定義
    • サイコロを振って1から6までのランダムな値を返す機能を提供。
    • 他のクラスでこの機能を簡単に使えるようにするための仕組み。

2. 抽象クラスの定義

  1. 抽象クラス Monster の定義
    • モンスターに共通する機能やデータ(名前など)をまとめる。
    • DiceTrait を使用してサイコロを振る機能を追加。
    • 抽象メソッド specialAbility() を定義し、サブクラスで独自の能力を実装するように強制。
  2. コンストラクタの定義
    • モンスターの名前を初期化する処理を設定。
  3. 名前を取得するメソッド getName() を定義
    • モンスターの名前を取得して表示する機能。

3. モンスターの具体的なクラス定義

スライム、ゴブリン、ドラゴンのクラス定義

  • Monster クラスを継承して各モンスターを作成。
  • 特殊能力をオーバーライドして各モンスター固有の特徴を表示できるようにする。

4. ゲーム管理クラスの定義

ゲーム全体を管理するクラス MonsterCaptureGame の定義

  • モンスターリスト ($monsters) と捕獲したモンスターリスト ($capturedMonsters) を管理。
  • コンストラクタでモンスターリストを初期化。

5. ゲーム開始とモンスターとの対決処理

モンスターごとに対決するループ処理

  • モンスター名と特殊能力を表示。
  • サイコロの出目をランダムに決定。
  • 捕獲成功または失敗が決まるまでループを続ける。

6. プレイヤーの選択とサイコロ判定

  1. プレイヤーの選択を取得
    • 「yes」または「no」でサイコロを振るかどうかを決定。
    • 「no」を選択した場合は次のモンスターに進む。
  2. サイコロの結果を判定
    • プレイヤーとモンスターがそれぞれサイコロを振り、結果を比較。

7. 捕獲成功、引き分け、ゲームオーバーの判定

条件分岐で結果を判定

  • 出目がモンスターより高ければ捕獲成功。
  • 出目が同じなら再挑戦。
  • 出目が低ければゲームオーバー。

8. ゲーム終了処理

ゲーム終了時の表示処理

  • 捕まえたモンスターのリストを表示。
  • ゲーム終了メッセージを出力。

9. モンスターリストの初期化とゲーム開始

  1. モンスターリストの作成
    • スライム、ゴブリン、ドラゴンをゲームに登場させるためにインスタンス化。
  2. ゲーム開始
    • MonsterCaptureGame クラスを使ってゲームを開始。
ヒント2【穴埋め問題にする】

以下のコードをコピーし、コメントに従ってコードを完成させて下さい。

<?php

// インターフェース: 振る舞いの規定
interface DiceRollable {
    /*【穴埋め問題1】
    ここでdiceRollメソッドを定義し、整数を返すコードを書いてください。
    */
}

// トレイト: サイコロ機能を提供
trait DiceTrait {
    /*【穴埋め問題2】
    ここでdiceRollメソッドを実装し、1から6の乱数を返すコードを書いてください。
    */
}

// 抽象クラス: 共通機能を定義
abstract class Monster implements DiceRollable {
    use DiceTrait;

    protected string $name;

    /*【穴埋め問題3】
    ここでMonsterクラスのコンストラクタを定義し、$nameを初期化するコードを書いてください。
    */

    public function getName(): string {
        return $this->name;
    }

    /*【穴埋め問題4】
    ここで特定の能力を定義する抽象メソッドspecialAbilityを宣言してください。
    */
}

// モンスターごとの派生クラス
class Slime extends Monster {
    /*【穴埋め問題5】
    ここでspecialAbilityメソッドをオーバーライドし、"スライムは分裂する!"を返すコードを書いてください。
    */
}

class Goblin extends Monster {
    /*【穴埋め問題6】
    ここでspecialAbilityメソッドをオーバーライドし、"ゴブリンは素早く動く!"を返すコードを書いてください。
    */
}

class Dragon extends Monster {
    /*
    【穴埋め問題7】ここでspecialAbilityメソッドをオーバーライドし、"ドラゴンは火を吹く!"を返すコードを書いてください。
    */
}

// ゲームクラス
class MonsterCaptureGame {
    private array $monsters;
    private array $capturedMonsters = [];

    public function __construct(array $monsters) {
        /*【穴埋め問題8】
        ここで$monstersを初期化するコードを書いてください。
        */
    }

    public function start(): void {
        echo "モンスター捕獲ゲームを開始します!" . PHP_EOL;

        foreach ($this->monsters as $monster) {
            echo "{$monster->getName()}が現れた!" . PHP_EOL;
            echo $monster->specialAbility() . PHP_EOL;

            $captured = false;

            while (!$captured) {
                /*【穴埋め問題9】
                 ここでモンスターのサイコロの出目を表示するコードを書いてください。
                */

                // プレイヤー選択
                echo "サイコロを振りますか? (yes/no): ";
                $response = trim(fgets(STDIN));

                if (strtolower($response) === 'no') {
                    echo "次のモンスターに進みます。" . PHP_EOL;
                    break;
                }

                /*【穴埋め問題10】
                ここでプレイヤーのサイコロの出目を生成して表示するコードを書いてください。
                */

                if ($playerRoll > $monsterRoll) {
                    echo "{$monster->getName()}を捕まえた!" . PHP_EOL;
                    $this->capturedMonsters[] = $monster->getName();
                    $captured = true;
                } elseif ($playerRoll == $monsterRoll) {
                    echo "引き分けでもう一度!" . PHP_EOL;
                } else {
                    echo "{$monster->getName()}を捕まえられなかった。ゲームオーバー!" . PHP_EOL;
                    $this->endGame();
                    return;
                }
            }
        }

        $this->endGame();
    }

    private function endGame(): void {
        /*【穴埋め問題11】
        ここで捕まえたモンスター一覧を表示するコードを書いてください。
        */
        echo "ゲーム終了" . PHP_EOL;
    }
}

// モンスター初期化
$monsters = [
    /*【穴埋め問題12】
    ここでモンスターインスタンスを作成して配列に追加してください。
    */
];

// ゲーム開始
/*【穴埋め問題13】
ここでMonsterCaptureGameのインスタンスを作成してゲームを開始してください。
*/

この問題の穴埋めコードは以上です。

このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。

【コードの例】モンスター捕獲ゲーム

この問題の一つの正解例とそのコードの解説を以下に示します。

正解コードの例

例えば以下のようなプログラムが考えられます。

<?php

// インターフェース: 振る舞いの規定
interface DiceRollable {
    public function diceRoll(): int;
}

// トレイト: サイコロ機能を提供
trait DiceTrait {
    public function diceRoll(): int {
        return rand(1, 6);
    }
}

// 抽象クラス: 共通機能を定義
abstract class Monster implements DiceRollable {
    use DiceTrait;

    protected string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    public function getName(): string {
        return $this->name;
    }

    abstract public function specialAbility(): string;
}

// モンスターごとの派生クラス
class Slime extends Monster {
    public function specialAbility(): string {
        return "スライムは分裂する!";
    }
}

class Goblin extends Monster {
    public function specialAbility(): string {
        return "ゴブリンは素早く動く!";
    }
}

class Dragon extends Monster {
    public function specialAbility(): string {
        return "ドラゴンは火を吹く!";
    }
}

// ゲームクラス
class MonsterCaptureGame {
    private array $monsters;
    private array $capturedMonsters = [];

    public function __construct(array $monsters) {
        $this->monsters = $monsters;
    }

    public function start(): void {
        echo "モンスター捕獲ゲームを開始します!" . PHP_EOL;

        foreach ($this->monsters as $monster) {
            echo "{$monster->getName()}が現れた!" . PHP_EOL;
            echo $monster->specialAbility() . PHP_EOL;

            $captured = false;

            while (!$captured) {
                $monsterRoll = $monster->diceRoll();
                echo "{$monster->getName()}がサイコロを振った。出目は {$monsterRoll}" . PHP_EOL;

                // プレイヤー選択
                echo "サイコロを振りますか? (yes/no): ";
                $response = trim(fgets(STDIN));

                if (strtolower($response) === 'no') {
                    echo "次のモンスターに進みます。" . PHP_EOL;
                    break;
                }

                $playerRoll = rand(1, 6);
                echo "プレイヤーがサイコロを振った。出目は {$playerRoll}" . PHP_EOL;

                if ($playerRoll > $monsterRoll) {
                    echo "{$monster->getName()}を捕まえた!" . PHP_EOL;
                    $this->capturedMonsters[] = $monster->getName();
                    $captured = true;
                } elseif ($playerRoll == $monsterRoll) {
                    echo "引き分けでもう一度!" . PHP_EOL;
                } else {
                    echo "{$monster->getName()}を捕まえられなかった。ゲームオーバー!" . PHP_EOL;
                    $this->endGame();
                    return;
                }
            }
        }

        $this->endGame();
    }

    private function endGame(): void {
        echo "捕まえたモンスター: " . implode(", ", $this->capturedMonsters) . PHP_EOL;
        echo "ゲーム終了" . PHP_EOL;
    }
}

// モンスター初期化
$monsters = [
    new Slime('スライム'),
    new Goblin('ゴブリン'),
    new Dragon('ドラゴン')
];

// ゲーム開始
$game = new MonsterCaptureGame($monsters);
$game->start();

正解コードの解説

このコードはオブジェクト指向プログラミング(OOP)の基本概念を活用したモンスター捕獲ゲームを実装しています。

以下では、各部分をブロックごとに説明します。

インターフェースとトレイトの定義

interface DiceRollable {
    public function diceRoll(): int;
}

trait DiceTrait {
    public function diceRoll(): int {
        return rand(1, 6);
    }
}
  • インターフェース (interface)
    インターフェースは、クラスが実装すべきメソッドを定義します。ここでは、diceRoll() メソッドを定義しており、サイコロを振る機能を保証しています。
  • トレイト (trait)
    トレイトは複数のクラスで使い回せる共通のコードを提供します。この例ではサイコロの出目をランダムに決める処理を DiceTrait で実装し、再利用可能にしています。

抽象クラスの定義

abstract class Monster implements DiceRollable {
    use DiceTrait;

    protected string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    public function getName(): string {
        return $this->name;
    }

    abstract public function specialAbility(): string;
}
  • 抽象クラス (abstract)
    抽象クラスは、継承されることを前提としたクラスです。直接インスタンス化できず、共通の機能と設計を提供します。
  • プロパティとコンストラクタ
    $name はモンスター名を保持するプロパティです。コンストラクタで初期化されます。
  • 抽象メソッド (abstract)
    specialAbility() は各モンスター固有の能力を定義するため、サブクラスで必ず実装する必要があります。

モンスターの派生クラス

class Slime extends Monster {
    public function specialAbility(): string {
        return "スライムは分裂する!";
    }
}

class Goblin extends Monster {
    public function specialAbility(): string {
        return "ゴブリンは素早く動く!";
    }
}

class Dragon extends Monster {
    public function specialAbility(): string {
        return "ドラゴンは火を吹く!";
    }
}
  • 継承 (extends)
    Monster クラスを継承し、特定のモンスターごとに能力を定義しています。
  • オーバーライド
    各クラスは、親クラスの specialAbility() を具体的に実装して、独自の特徴を持たせています。

ゲーム管理クラス

class MonsterCaptureGame {
    private array $monsters;
    private array $capturedMonsters = [];

    public function __construct(array $monsters) {
        $this->monsters = $monsters;
    }
  • プロパティの宣言
    $monsters: ゲームに登場するモンスターのリスト。
    $capturedMonsters: 捕まえたモンスターを記録するリスト。
  • コンストラクタ
    モンスターリストを初期化し、ゲームを設定します。

ゲームロジック

foreach ($this->monsters as $monster) {
    echo "{$monster->getName()}が現れた!" . PHP_EOL;
    echo $monster->specialAbility() . PHP_EOL;

    while (!$captured) {
        $monsterRoll = $monster->diceRoll();
        echo "{$monster->getName()}がサイコロを振った。出目は {$monsterRoll}" . PHP_EOL;
  • ループ処理
    各モンスターとの対戦を順番に処理します。
  • 特殊能力の表示
    モンスター固有の特殊能力が画面に表示されます。
  • サイコロ対決
    モンスターとプレイヤーがサイコロを振り、結果を比較します。

結果判定と終了処理

if ($playerRoll > $monsterRoll) {
    echo "{$monster->getName()}を捕まえた!" . PHP_EOL;
    $this->capturedMonsters[] = $monster->getName();
    $captured = true;
} elseif ($playerRoll == $monsterRoll) {
    echo "引き分けでもう一度!" . PHP_EOL;
} else {
    echo "{$monster->getName()}を捕まえられなかった。ゲームオーバー!" . PHP_EOL;
    $this->endGame();
    return;
}
  • 条件分岐
    サイコロの結果に応じて、捕獲成功・再挑戦・ゲームオーバーを判定します。
  • 配列への追加
    捕まえたモンスターの名前は配列 $capturedMonsters に追加されます。

ゲーム終了処理

private function endGame(): void {
    echo "捕まえたモンスター: " . implode(", ", $this->capturedMonsters) . PHP_EOL;
    echo "ゲーム終了" . PHP_EOL;
}
  • 配列の結合 (implode)
    捕まえたモンスター名をカンマ区切りで表示します。
  • 終了メッセージ
    ゲームの終了をユーザーに通知します。

まとめ

このコードはPHPにおけるオブジェクト指向プログラミングの基本的な構造を学べる実践的な例です。

  • インターフェースで機能を定義し、トレイトで再利用可能なコードを提供する方法を理解できます。
  • 抽象クラスを使って共通機能を整理し、サブクラスで具体的な機能を実装する流れを体験できます。
  • ゲームの流れを通じて、ループや条件分岐、配列操作といった基本文法も習得できます。

このプログラムを基礎としてさらなる機能追加やカスタマイズに挑戦し、オブジェクト指向設計の理解を深めていきましょう!

PHPのゲームコード一覧はこちら

<<前のページ PHP記事一覧 次のページ>>

この記事への質問・コメント

この記事を作成するにあたりAIを活用しています。

問題ないことは確認していますが、もし間違いや表現の違和感などありましたら、ご指摘頂けると大変助かります。






    PHPテキスト&問題集へ戻る
    トップページへ戻る