C++ コンソールミニゲーム作ってみよう! - Enemy Rush

前提


Step 01: 継承元の骨組みを作る

目標: 基底 GameObject と派生 Enemy。名前とHPを表示するだけ。

// game_step01.cpp
#include <iostream>
#include <string>
using namespace std;

class GameObject {
public:
    string name;
    void show() const { cout << "名前: " << name << "\n"; }
};

class Enemy : public GameObject {
public:
    int hp = 10;
    void info() const { cout << "HP: " << hp << "\n"; }
};

int main() {
    Enemy e;
    e.name = "スライム";
    e.hp = 12;
    e.show();
    e.info();
}

Step 02: オーバーライドとポリモーフィズム

目標: Enemyvirtual attack() を用意。SlimeGoblin が上書きする。

// game_step02.cpp  ← game_step01.cpp をコピーして改造
#include <iostream>
#include <string>
using namespace std;

class GameObject {
public:
    string name;
    void show() const { cout << "名前: " << name << "\n"; }
};

class Enemy : public GameObject {
public:
    int hp = 10;
    virtual void attack() { cout << name << " が殴ってきた!\n"; }
    virtual ~Enemy() = default; // 後で動的確保するので仮想デストラクタ
};

class Slime : public Enemy {
public:
    void attack() override { cout << "スライムが体当たり!\n"; }
};

class Goblin : public Enemy {
public:
    void attack() override { cout << "ゴブリンが棍棒!\n"; }
};

int main() {
    Enemy* es[2] = { new Slime(), new Goblin() };
    es[0]->name = "ぷるぷる"; es[1]->name = "ゴブさん";
    for (int i = 0; i < 2; i++) {
        es[i]->show();
        es[i]->attack();
        delete es[i];
    }
}

Step 03: 抽象クラス化して設計を強制

目標: Enemy を抽象クラスにし、attack() を純粋仮想に。

// game_step03.cpp  ← step02 をコピーして改造
#include <iostream>
#include <string>
using namespace std;

class GameObject {
public:
    string name;
    void show() const { cout << "名前: " << name << "\n"; }
};

class Enemy : public GameObject {
public:
    int hp = 10;
    virtual void attack() = 0; // 純粋仮想
    virtual ~Enemy() = default;
};

class Slime : public Enemy {
public:
    void attack() override { cout << "スライムが体当たり!\n"; }
};

class Goblin : public Enemy {
public:
    void attack() override { cout << "ゴブリンが棍棒!\n"; }
};

int main() {
    Enemy* e1 = new Slime(); e1->name = "ぷるぷる";
    Enemy* e2 = new Goblin(); e2->name = "ゴブさん";
    Enemy* es[2] = { e1, e2 };
    for (int i = 0; i < 2; i++) { es[i]->show(); es[i]->attack(); }
    for (int i = 0; i < 2; i++) delete es[i];
}

Step 04: 範囲forで一覧処理

目標: vector<Enemy*> と範囲forで全体処理。

// game_step04.cpp  ← step03 をコピー
#include <iostream>
#include <string>
#include <vector>
using namespace std;

class GameObject {
public:
    string name;
    void show() const { cout << "名前: " << name << "\n"; }
};

class Enemy : public GameObject {
public:
    int hp = 10;
    virtual void attack() = 0;
    virtual ~Enemy() = default;
};

class Slime : public Enemy {
public:
    void attack() override { cout << "スライムが体当たり!\n"; }
};

class Goblin : public Enemy {
public:
    void attack() override { cout << "ゴブリンが棍棒!\n"; }
};

int main() {
    vector<Enemy*> enemies;
    auto s = new Slime();  s->name = "ぷるぷる"; enemies.push_back(s);
    auto g = new Goblin(); g->name = "ゴブさん"; enemies.push_back(g);

    for (Enemy* e : enemies) {
        e->show();
        e->attack();
    }
    for (Enemy* e : enemies) delete e;
}

Step 05: 動的生成と削除の体験 (new/delete と寿命)

目標: ターンごとに敵が湧き、倒れたら delete~Enemy() のログで寿命を確認。

// game_step05.cpp  ← step04 をコピー
#include <iostream>
#include <string>
#include <vector>
#include <limits>
using namespace std;

class GameObject {
public:
    string name;
    void show() const { cout << "名前: " << name << "\n"; }
};

class Enemy : public GameObject {
public:
    int hp = 10;
    virtual void attack() = 0;
    virtual void takeDamage(int d) { hp -= d; }
    bool isDead() const { return hp <= 0; }
    virtual ~Enemy() { cout << name << " が消滅\n"; }
};

class Slime : public Enemy {
public:
    Slime() { hp = 18; }
    void attack() override { cout << "スライムの体当たり!\n"; }
};

class Goblin : public Enemy {
public:
    Goblin() { hp = 25; }
    void attack() override { cout << "ゴブリンの棍棒!\n"; }
};

int main() {
    vector<Enemy*> enemies;
    const int DAMAGE = 12;
    int turnMax = 6;

    for (int turn = 1; turn <= turnMax; ++turn) {
        // 出現(動的生成)
        if (turn % 2) {
            auto e = new Slime();  e->name = "スライム#" + to_string(turn); enemies.push_back(e);
        } else {
            auto e = new Goblin(); e->name = "ゴブリン#" + to_string(turn); enemies.push_back(e);
        }

        cout << "\n--- Turn " << turn << " ---\n";
        int idx = 1;
        for (Enemy* e : enemies) {
            cout << idx++ << ") "; e->show();
        }

        cout << "攻撃対象番号 (0でスキップ, -1で終了): ";
        int choice;
        if (!(cin >> choice)) { cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); choice = 0; }

        if (choice == -1) break;
        if (choice > 0 && choice <= (int)enemies.size()) {
            Enemy* t = enemies[choice - 1];
            t->takeDamage(DAMAGE);
            cout << "→ " << t->name << " に " << DAMAGE << " ダメージ\n";
            if (t->isDead()) {
                cout << "撃破! delete 実行\n";
                delete t;
                enemies.erase(enemies.begin() + (choice - 1));
            }
        }

        // 敵の行動
        for (Enemy* e : enemies) e->attack();
    }

    // 残骸掃除
    for (Enemy* e : enemies) delete e;
}

Step 06: 追加のオーバーライドで差別化

目標: Dragon を追加。被ダメ軽減など挙動差分を増やす。

// game_step06.cpp  ← step05 をコピーし Dragon を追加
#include <iostream>
#include <string>
#include <vector>
#include <limits>
using namespace std;

class GameObject {
public:
    string name;
    void show() const { cout << "名前: " << name << "\n"; }
};

class Enemy : public GameObject {
public:
    int hp = 10;
    virtual void attack() = 0;
    virtual void takeDamage(int d) { hp -= d; }
    bool isDead() const { return hp <= 0; }
    virtual ~Enemy() { cout << name << " が消滅\n"; }
};

class Slime : public Enemy {
public:
    Slime() { hp = 18; }
    void attack() override { cout << "スライムの体当たり!\n"; }
};

class Goblin : public Enemy {
public:
    Goblin() { hp = 25; }
    void attack() override { cout << "ゴブリンの棍棒!\n"; }
};

class Dragon : public Enemy {
public:
    Dragon() { name = "ドラゴン"; hp = 40; }
    void attack() override { cout << "ドラゴンの炎ブレス!\n"; }
    void takeDamage(int d) override { hp -= (d - 3 > 0 ? d - 3 : 0); }
};

int main() {
    vector<Enemy*> enemies;
    const int DAMAGE = 12;
    int turnMax = 9;

    for (int turn = 1; turn <= turnMax; ++turn) {
        // 出現ロジックを少し複雑に
        if (turn % 3 == 0) {
            auto e = new Dragon(); e->name += "#" + to_string(turn); enemies.push_back(e);
        } else if (turn % 2) {
            auto e = new Slime();  e->name = "スライム#" + to_string(turn); enemies.push_back(e);
        } else {
            auto e = new Goblin(); e->name = "ゴブリン#" + to_string(turn); enemies.push_back(e);
        }

        cout << "\n--- Turn " << turn << " ---\n";
        int idx = 1;
        for (Enemy* e : enemies) { cout << idx++ << ") "; e->show(); }

        cout << "攻撃対象番号 (0でスキップ, -1で終了): ";
        int choice; if (!(cin >> choice)) { cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); choice = 0; }

        if (choice == -1) break;
        if (choice > 0 && choice <= (int)enemies.size()) {
            Enemy* t = enemies[choice - 1];
            t->takeDamage(DAMAGE);
            cout << "→ " << t->name << " に " << DAMAGE << " ダメージ\n";
            if (t->isDead()) { cout << "撃破! delete 実行\n"; delete t; enemies.erase(enemies.begin() + (choice - 1)); }
        }

        for (Enemy* e : enemies) e->attack();
    }

    for (Enemy* e : enemies) delete e;
}

Step 07: 仕上げの小要素 (範囲forの参照・全体処理)

目標: 範囲forの参照で全員にバフ/デバフをかけるなどの全体更新を追加。

// 例: ターン開始時に全員が小回復(HP+1)
// vector<Enemy*> enemies; がある前提
for (Enemy*& e : enemies) {
    e->hp += 1; // 参照で書き換え。値コピーにしないこと。
}

最終課題 - 各自のオリジナル仕上げ

下から好きに 2つ以上 実装。設計とコードを提出。動作ログまたは短い動画を添付。

  1. スコアとコンボの実装。
  2. クリティカル/回避 (乱数)。
  3. 状態異常: 毒・炎上・鈍足。
  4. ターン経過で出現ペースや敵種が変わる。
  5. unique_ptr<Enemy> 版に書き換え (delete 忘れ対策)。
  6. Boss 追加。特性: 行動が2回、HP閾値でフェーズ移行。
  7. ログ出力のオン/オフ (-DDEBUG マクロやフラグで制御)。

提出コメントに含めること