1

[SOLVED] Here's the entirety of my code all finished a working:

#include <ncurses.h>
#include <random>
#include <ctime>
using namespace std;

//VARIABLES
///////////
    int tick_count = 0;
    char key;
    int width = 10, height = 10;

    class Entity {
    public:
        class Common {
        public:
            char icon;
            int x, y;
            char collide;
            int power = 0, speed = 0, defense = 0, health = 0;
            int hp;

            virtual void move(int dir) {
                switch (dir) {
                    case 1:
                        y--;
                        break;
                    case 2:
                        y++;
                        break;
                    case 3:
                        x--;
                        break;
                    case 4:
                        x++;
                        break;
                }
            }
            virtual ~Common() {}
        };

        class Player: public Common {
            public:
                int target, room;
                Player() {
                    icon = '@';
                    x = 1, y = 1;
                    collide = 'n';
                    power = 1, speed = 1, defense = 1, health = 10;
                    hp = health;
                }
        };
        class Kobold: public Common {
            public:
                Kobold() {
                    icon = 'K';
                    collide = 'n';
                    power = 1, speed = 0, defense = 1, health = 2;
                    hp = health;
                }
        };
        class Spider: public Common {
            public:
                Spider() {
                    icon = 'S';
                    collide = 'n';
                    power = 4, speed = 2, defense = 0, health = 3;
                    hp = health;
                }
        };
    };

    class Room {
    public:
        int width, height, difficulty, enemies;
    };

    Room room[1];
    Entity::Player player;
    Entity::Common * enemy[2];
    int enemy_ID, enemy_count = 2;
///////////

void INIT() {
    room[0].width = 10, room[0].height = 10, room[0].enemies = 0;
    player.x = player.y = 1, player.collide = 'n', player.room = 0;
    enemy[0] = new Entity::Kobold();
    enemy[1] = new Entity::Spider();
    enemy[0]->x = enemy[0]->y = 5;
    enemy[1]->x = enemy[1]->y = 8;
}

void DRAW() {
    clear();
    printw("ATK: %d, DEF: %d, HP: %d \n", player.power, player.defense, player.hp);
    printw("#");
    for (int i = 0; i < room[player.room].width - 1; i++) {
        printw("--");
    }
    printw("-#\n");
    for (int j = 0; j < room[player.room].height; j++) {
        printw("|");
        for (int i = 0; i < room[player.room].width; i++) {
            if (i == room[player.room].width - 1) {
                if (i == player.x and j == player.y) {
                    printw("%c", player.icon);
                }
                else if (i == enemy[enemy_ID]->x and j == enemy[enemy_ID]->y) {
                    printw("%c", enemy[enemy_ID]->icon);
                }
                else {
                    printw(" ");
                }

            }
            else {
                if (i == player.x and j == player.y) {
                    printw("%c ", player.icon);
                }
                else if (i == enemy[enemy_ID]->x and j == enemy[enemy_ID]->y) {
                    printw("%c ", enemy[enemy_ID]->icon);
                }
                else {
                    printw("  ");
                }
            }
        }
        printw("|\n");
    }
    printw("#");
    for (int i = 0; i < width - 1; i++) {
        printw("--");
    }
    printw("-#\n");

    //Debug
    mvprintw(1, 30, "%d %s %c %c", tick_count, typeid(enemy[0]).name(), player.collide, enemy[0]->collide);
}

void LOGIC() {
    //GAME LOGIC
    ////////////
        tick_count += 1;
        srand(time(NULL));
    ////////////

    //BATTLE LOGIC
    //////////////
        if (player.collide == 'e' or enemy[enemy_ID]->collide == 'p') {
            for (int i; i < enemy_count; i++) {
                if (enemy[enemy_ID]->collide == 'p')
                    player.target = i;
            }

            if (player.speed > enemy[player.target]->speed) {

            }
            else if (player.speed < enemy[player.target]->speed) {

            }
            else if (player.speed == enemy[player.target]->speed) {
                if (player.collide = 'e') {
                    if (rand() % 2) {
                        enemy[player.target]->hp -= player.power - enemy[player.target]->defense;
                        mvprintw(14, 0, "You hit %s with %d damage", enemy[player.target], player.power - enemy[player.target]->defense);
                        clrtoeol();
                    }
                    else {

                    }
                }
                else if (player.collide = 'n') {
                    player.hp -= enemy[player.target]->power - player.defense;
                    mvprintw(14, 0, "%s hit you with %d damage", enemy[player.target], enemy[player.target]->power - player.defense);
                    getch();
                    clrtoeol();
                }
            }
        }
    //////////////

    //PLAYER LOGIC
    //////////////
        if (player.x - 1 == enemy[enemy_ID]->x and player.y == enemy[enemy_ID]->y and key == 'a'
        or player.x + 1 == enemy[enemy_ID]->x and player.y == enemy[enemy_ID]->y and key == 'd'
        or player.y - 1 == enemy[enemy_ID]->y and player.x == enemy[enemy_ID]->x and key == 'w'
        or player.y + 1 == enemy[enemy_ID]->y and player.x == enemy[enemy_ID]->x and key == 's'
        )
            player.collide = 'e';
        else
            player.collide = 'n';

        if (player.x < 0)
            player.x = 0;
        else if (player.x == width)
            player.x = width - 1;
        else if (player.y < 0)
            player.y = 0;
        else if (player.y == height)
            player.y = height - 1;
    //////////////

    //ENEMY LOGIC
    /////////////
        for (enemy_ID = 0; enemy_ID < enemy_count - 1; enemy_ID++)
            enemy_ID++;

        if (enemy[enemy_ID]->x == player.x and enemy[enemy_ID]->y > player.y and enemy[enemy_ID]->y - 1 != player.y)
            enemy[enemy_ID]->move(1);
        else if (enemy[enemy_ID]->x == player.x and enemy[enemy_ID]->y < player.y and enemy[enemy_ID]->y + 1 != player.y)
            enemy[enemy_ID]->move(2);
        else if (enemy[enemy_ID]->y == player.y and enemy[enemy_ID]->x > player.x and enemy[enemy_ID]->x - 1 != player.x)
            enemy[enemy_ID]->move(3);
        else if (enemy[enemy_ID]->y == player.y and enemy[enemy_ID]->x < player.x and enemy[enemy_ID]->x + 1 != player.x)
            enemy[enemy_ID]->move(4);

        if (enemy[enemy_ID]->x - 1 == player.x and player.y == enemy[enemy_ID]->y
        or enemy[enemy_ID]->x + 1 == player.x and player.y == enemy[enemy_ID]->y
        or enemy[enemy_ID]->y - 1 == player.y and player.x == enemy[enemy_ID]->x
        or enemy[enemy_ID]->y + 1 == player.y and player.x == enemy[enemy_ID]->x
        )
            enemy[enemy_ID]->collide = 'p';
        else
            enemy[enemy_ID]->collide = 'n';
    /////////////
}

void INPUT() {
    key = getch();
    switch (key) {
        case 'w':
            player.move(1);
            break;
        case 's':
            player.move(2);
            break;
        case 'a':
            player.move(3);
            break;
        case 'd':
            player.move(4);
            break;
    }
}

int main() {
    initscr();
    cbreak();
    noecho();
    INIT();
    DRAW();
    while (key != '\n') {
        //Main Loop
        ///////////////////////
            INPUT();
            LOGIC();
            DRAW();
        ///////////////////////
        refresh();
    }
    endwin();
    return 0;
}

[QUESTION] (Sorry for potentially confusing title) I'm making an old styled ascii based rogue-like. Here's some code:

    class Entity {
    public:
        class Player {
        public:
            int x, y, target;
            char collide;
            int power = 1, speed = 1, defense = 1, health = 10;
            int hp = health;
            
            void move(int dir);
        };
        class Kobold {
        public:
            int x, y;
            char collide;
            const int power = 2, speed = 1, defense = 1, health = 3;
            int hp = health;

            void move(int dir);
        };

My goal is to have an array named enemy and have each array value (or whatever it's called) have a different class as its type. For Example: enemy[0] can be an object of Kobold which is a member of Entity, and have enemy[5] be an object of Spider which is a member of Entity and etc. This way it's easier to have enemies that that are easier to declare and reference in for loops. What I'm asking for is how to declare the array enemy in such a way. It would be nice to have and answer to what I'm asking, but I'm not sure it's possible in a simple way, so if anyone has a simpler or easier way to accomplish my goal I would also like to see it. Thanks in advance.

Edit: Example code (ignore that it's not real):

//Declaring an array with 8 values with no type
void enemy[8];

//Assigning the Kobold class to the first enemy as it's type
enemy[0] = Entity::Kobold;

//Assigning the Spider class to the 3rd enemy as it's type
enemy[2] = Entity::Spider;

//Player attacks 3rd enemy (spider) and inflicts damage
enemy[2].hp -= player.power;

//Checks if any enemy is dead, and if so then delete it
for (int i = 0; i < 8; i++) {
    if (enemy[i].hp <= 0) {
        //Removing enemy from it's class, therefore deleting it
        void enemy[i];
    }
}

//First enemy (Kobold) attacks player, inflicting damage
player.hp -= enemy[0].power;

//Player isn't dead, and attacks first enemy (Kobold) and kills it
enemy[0].hp -= player.power;

//Checks if any enemy is dead, and if so then delete it (again)
for (int i = 0; i < 8; i++) {
    if (enemy[i].hp <= 0) {
        //Removing enemy from it's class, therefore deleting it
        void enemy[i];
    }
}

printw("You Win!");

I know it's not effective and probably has some errors, but it's just there to help visualize what I'm trying to do.

Edit:

This is similar to what I'm asking: Is it possible to have an array of different objects that come from the same base class?

I am trying to make an array as an object of Entity but I want to have any array value be an object of any sub-class from Entity. So something like this:

Entity enemy[5];
enemy[0] = new Kobold;
enemy[4] = new Spider;

And then I can reference to the easier like this:

for (int i = 0; i < 5; i++) {
    if (array[i].x == player.x and array[i].y == player.y) {
        printw("player and an enemy are in the same spot");
    }
}

So the point is to easily access variables from every enemy (virtually) at once.

Another example (checking if any enemy has 0 hp):

for (int i = 0; i < amount_of_enemies; i++) {
    if (enemy[i].hp <= 0) {
        //some code to delete the enemy and give player XP etc.
    }
}
  • 4
    You may wish to read about ploymorphism and virtual functions in your favorite C++ textbook. – Igor Tandetnik Nov 10 '21 at 02:32
  • *"This way it's easier to have enemies [...]"* -- read that again. Your description of your problem suggests that an "enemy" is a concept in your code. Where is the `enemy` class that implements this concept? – JaMiT Nov 10 '21 at 03:09
  • 1
    Are you sure that `Player` and `Kobold` are **members** of the class `Entity`? Should not they be subclasses of `Entity`? – Serge Ballesta Nov 10 '21 at 10:12
  • @SergeBallesta Not sub classes either, those are *inner classes*. But yes, probably not what the questioneer wanted to do. – Aziuth Nov 10 '21 at 11:20
  • @SergeBallesta Yes that's what I meant, my bad. – DinoNuggies Nov 10 '21 at 22:04
  • @JaMiT The class for different enemies will be classes inside the ```Entity``` class named things like ```Kobold``` or ```Spider``` or ```Slime``` etc, and then I want to make an array called ```enemy``` and have somewhere around 8 array value things (Declaration would be ```int enemy[8];``` for example), and each array value can be an object of a different class inside ```Entity``` like ```Kobold```, ```Spider```, ```Slime``` etc. And then I need to be able to change which class each array value is an object of afterword. I will update the question with example code. – DinoNuggies Nov 10 '21 at 22:13
  • @DinoNuggies *"The class for different enemies will be classes inside the `Entity` class named things like `Kobold` or `Spider` or `Slime` etc,"* -- yes, that is your current design; my comment was trying to subtly point out that your current design is flawed. Hmm... could you explain why `Kobold`, `Spider`, etc. are inner classes of `Entity`? What makes them intrinsically part of the concept of an entity rather than separate concepts? In your design, what is an `Entity`? Why is a `Kobold` not an `Entity`? – JaMiT Nov 10 '21 at 23:37
  • @JaMiT "Why is a ```Kobold``` not an ```Entity```?" -- Well that's why I put it inside the ```Entity``` class, it's a form of sorting I suppose. So yes, in my mind ```Kobold``` *is* an entity because I made it a sub-class of ```Entity```. – DinoNuggies Nov 12 '21 at 16:56
  • @DinoNuggies If a `Kobold` *is an* `Entity`, then it should derive from `Entity`, as in `class Kobold : public Entity`. A [nested class](https://en.cppreference.com/w/cpp/language/nested_types) (what you call "sub-class") is more appropriate for concepts that are *part* of the containing class. The node class used by a linked list is a good example of something that warrants being a nested class. See [Why would one use nested classes in C++?](https://stackoverflow.com/questions/4571355/) (If you are looking for some form of encapsulation, you might be better served by a `namespace`.) – JaMiT Nov 13 '21 at 01:54

1 Answers1

1

Part 1

There are two popular ways that can be used here, one way is to use C++17's std::any. Another way is to use virtual methods and interfaces.

std::any approach allows to store any unrelated classes within same array std::vector<std::any>. std::any allows to store different types in single container supporting only single type.

Later when accessing entities from this array you have to check and cast their types to specific types and process each type inside if clause. Every if will have specific for this type processing code.

With any you can have totally different methods and fields for every processed class, for example methods .player_shout() (for Player) and .kobold_defense() (for Kobold) in code below.

Array may also have some types that you can't process or don't know, then you can skip them or handle in other way, see code below:

Try it online!

#include <iostream>
#include <vector>
#include <any>

class Entity {
public:        
    class Player {
    public:
        int x, y, target;
        char collide;
        int power = 1, speed = 1, defense = 1, health = 10;
        int hp = health;
        
        void move(int dir) { std::cout << "Moved Player to dir " << dir << std::endl; }
        void player_shout() { std::cout << "Player shouted!" << std::endl; }
    };
    class Kobold {
    public:
        int x, y;
        char collide;
        const int power = 2, speed = 1, defense = 1, health = 3;
        int hp = health;

        void move(int dir) { std::cout << "Moved Kobold to dir " << dir << std::endl; }
        void kobold_defense() { std::cout << "Kobold defensed!" << std::endl; }
    };
};

void ProcessEntities(std::vector<std::any> & entities) {
    for (std::size_t i = 0; i < entities.size(); ++i) {
        auto & e = entities[i];
        if (e.type() == typeid(Entity::Player)) {
            Entity::Player & player = *std::any_cast<Entity::Player>(&e);
            player.move(2);
            player.player_shout();
        } else if (e.type() == typeid(Entity::Kobold)) {
            Entity::Kobold & kobold = *std::any_cast<Entity::Kobold>(&e);
            kobold.move(1);
            kobold.kobold_defense();
        } else
            std::cout << "Unknown entity " << i << " of type "
                << e.type().name() << std::endl;
    }
}

struct BadType {};

int main() {
    std::vector<std::any> entities = {
        Entity::Player(), Entity::Kobold(), Entity::Player(),
        Entity::Player(), BadType(), Entity::Kobold(), BadType()};
    ProcessEntities(entities);
}

Output:

oved Player to dir 2
Player shouted!
Moved Kobold to dir 1
Kobold defensed!
Moved Player to dir 2
Player shouted!
Moved Player to dir 2
Player shouted!
Unknown entity 4 of type 7BadType
Moved Kobold to dir 1
Kobold defensed!
Unknown entity 6 of type 7BadType

Part 2

You may also use virtual methods to represent common class with some attributes (fields) and methods common to all entities. This is called a virtual interface approach.

All entities should inherit from this common interface and implement all inherited methods. Common class doesn't implement interface methods itself, although it can if needed.

You can have arbitrary deep hierarchy of virtual classes, anything is possible. Following example shows implementation of simple Common class and inheritance of it by all entities.

You can also mix virtual classes inheritance with type-casting approach (that was done for std::any in Part-1 of my answer). Following example calls common virtual method move() and also shows common attribute speed. And rest of logic is done through type-checking with type() method and type-casting with dynamic_cast, after casting you can use specific (non-inherited) attributes of each entity.

Hence you can mix in arbitrary way virtual inheritance with dynamic type casting.

Try it online!

#include <iostream>
#include <vector>
#include <memory>

class Entity {
public:
    class Common {
    public:
        int x, y;
        char collide;
        int power = 1, speed = 1, defense = 1, health = 10;
        int hp = health;

        virtual void move(int dir) = 0;
        virtual std::type_info const & type() const = 0;
        virtual ~Common() {}
    };

    class Player : public Common {
    public:
        int player_attr = 123;

        std::type_info const & type() const { return typeid(*this); }
        void move(int dir) { std::cout << "Moved Player to dir "
            << dir << " with health " << health << std::endl; }
        void player_shout() { std::cout << "Player shouted with attr "
            << player_attr << "!" << std::endl; }
    };
    class Kobold : public Common {
    public:
        int kobold_attr = 456;

        std::type_info const & type() const { return typeid(*this); }
        void move(int dir) { std::cout << "Moved Kobold to dir "
            << dir << " with health " << health << std::endl; }
        void kobold_defense() { std::cout << "Kobold defensed with attr "
            << kobold_attr << " !" << std::endl; }
    };
};

void ProcessEntities(std::vector<std::shared_ptr<Entity::Common>> & entities) {
    for (std::size_t i = 0; i < entities.size(); ++i) {
        auto & e = entities[i];
        std::cout << "Entity " << i << ": Speed " << e->speed << ". ";
        e->move(i % 3); // Common method
        if (e->type() == typeid(Entity::Player)) {
            Entity::Player & player = dynamic_cast<Entity::Player &>(*e);
            player.player_shout();
            std::cout << "Player attr " << player.player_attr << std::endl;
        } else if (e->type() == typeid(Entity::Kobold)) {
            Entity::Kobold & kobold = dynamic_cast<Entity::Kobold &>(*e);
            kobold.kobold_defense();
            std::cout << "Kobold attr " << kobold.kobold_attr << std::endl;
        } else
            std::cout << "Unknown entity " << i << std::endl;
    }
}

int main() {
    std::vector<std::shared_ptr<Entity::Common>> entities = {
        std::make_shared<Entity::Player>(), std::make_shared<Entity::Kobold>(),
        std::make_shared<Entity::Player>(), std::make_shared<Entity::Player>(),
        std::make_shared<Entity::Kobold>()};
    ProcessEntities(entities);
}

Output:

Entity 0: Speed 1. Moved Player to dir 0 with health 10
Player shouted with attr 123!
Player attr 123
Entity 1: Speed 1. Moved Kobold to dir 1 with health 10
Kobold defensed with attr 456 !
Kobold attr 456
Entity 2: Speed 1. Moved Player to dir 2 with health 10
Player shouted with attr 123!
Player attr 123
Entity 3: Speed 1. Moved Player to dir 0 with health 10
Player shouted with attr 123!
Player attr 123
Entity 4: Speed 1. Moved Kobold to dir 1 with health 10
Kobold defensed with attr 456 !
Kobold attr 456

If you need more simple version of virtual inheritance, to be more understandable for you, I've created this simpler code:

Try it online!

class Entity {
public:
    class Common {
    public:
        int x, y;
        char collide;
        int power = 0, speed = 0, defense = 0, health = 0;
        int hp = health;
        
        virtual void move(int dir) = 0;
        virtual ~Common() {}
    };
    class Player : public Common {
    public:
        Player() { power = 1; speed = 1; defense = 1; health = 10; hp = health; }
        void move(int dir) {}
    };
    class Kobold : public Common {
    public:
        Kobold() { power = 2; speed = 1; defense = 1; health = 3; hp = health; }
        void move(int dir) {}
    };
    class Spider : public Common {
    public:
        Spider() { power = 3; speed = 2; defense = 2; health = 5; hp = health; }
        void move(int dir) {}
    };
};

int main() {
    Entity::Common * enemies[3] = {
        new Entity::Kobold(), new Entity::Spider()};
    enemies[0]->move(3);
    enemies[0]->hp -= enemies[1]->power;
}

Part 3

If two first solutions (from Part 1 and 2 of my answer) don't fit for you, then I can suggest third solution based on std::variant and std::visit.

I took from your question examples of how you process enemies and player. And modified it a bit to use my third idea of using std::variant. See code below.

There are some tiny modification to your enemies classes like adding copy constructor (see CONSTR(Kobold);) and implementing Empty kind of entity to store empty values.

You can see in following code that I created two macros EN and EN2. Basically if you store your enemies as std::variant then you have to use one of these two macros in order to access and/or modify an enemy or interact with player. Because you can't use std::variant type directly. These macros use std::visit to access underlying specific type.

In order to start using std::variant solution like below you just have to define once special type Enemy, just by enumerating all possible enemy classes using Enemy = std::variant<Entity::Kobold, Entity::Spider, Entity::Dragon, Entity::Empty>;, see code.

I created Empty class to store empty values in array. You can check if element is empty by special macro EMPTY(enemy).

Try it online!

#include <variant>
#include <vector>
#include <iostream>

class Entity {
public:
    #define COPY(self, o) { self.x = o.x; self.y = o.y; self.collide = o.collide; self.power = o.power; self.speed = o.speed; self.defense = o.defense; self.health = o.health; self.hp = o.hp; }
    #define CONSTR(T) \
        T() {} \
        T(T const & o) { COPY((*this), o); } \
        T & operator =(T const & o) { COPY((*this), o); return *this; }

    class Player {
    public:
        int x, y, target;
        char collide;
        int power = 1, speed = 1, defense = 1, health = 10;
        int hp = health;
        
        void show_hp() const { std::cout << "Player health " << hp << std::endl; }
        void move(int dir) { std::cout << "Moved Player to dir " << dir << std::endl; }
    };
    class Kobold {
    public:
        int x, y;
        char collide;
        int power = 2, speed = 1, defense = 1, health = 1;
        int hp = health;

        CONSTR(Kobold);

        void show_hp() const { std::cout << "Kobold health " << hp << (hp <= 0 ? ", Dead!!!" : "") << std::endl; }
        void move(int dir) { std::cout << "Moved Kobold to dir " << dir << std::endl; }
    };
    class Spider {
    public:
        int x, y;
        char collide;
        int power = 3, speed = 1, defense = 1, health = 5;
        int hp = health;

        CONSTR(Spider);

        void show_hp() const { std::cout << "Spider health " << hp << (hp <= 0 ? ", Dead!!!" : "") << std::endl; }
        void move(int dir) { std::cout << "Moved Spider to dir " << dir << std::endl; }
    };
    class Dragon {
    public:
        int x, y;
        char collide;
        int power = 7, speed = 1, defense = 1, health = 20;
        int hp = health;

        CONSTR(Dragon);

        void show_hp() const { std::cout << "Dragon health " << hp << (hp <= 0 ? ", Dead!!!" : "") << std::endl; }
        void move(int dir) { std::cout << "Moved Dragon to dir " << dir << std::endl; }
    };
    class Empty {
    public:
        int x, y;
        char collide;
        int power = 0, speed = 0, defense = 0, health = 1;
        int hp = health;

        CONSTR(Empty);

        void show_hp() const { std::cout << "Empty!!!" << std::endl; }
        void move(int dir) { std::cout << "Empty!!!" << std::endl; }
    };
    #define EMPTY(x) (typeid(x) == typeid(Entity::Empty))
};

using Enemy = std::variant<
    Entity::Kobold, Entity::Spider, Entity::Dragon, Entity::Empty>;

#define EN(_pos, code) \
    std::visit([&](auto & enemy) code, enemies[_pos]);

#define EN2(_pos0, _pos1, code) \
    std::visit([&](auto & enemyA) { \
        std::visit([&](auto & enemyB) code, enemies[_pos1]); \
    }, enemies[_pos0]);

void Process(std::vector<Enemy> & enemies) {
    Entity::Player player;

    EN(2, {
        // Player attacks 3rd enemy (spider) and inflicts damage
        enemy.show_hp();
        enemy.hp -= player.power;
        enemy.show_hp();
    });

    //Checks if any enemy is dead, and if so then delete it
    for (int i = 0; i < enemies.size(); ++i) {
        EN(i, {
            if (!EMPTY(enemy) && enemy.hp <= 0) {
                //Removing enemy from it's class, therefore deleting it
                enemies[i] = Entity::Empty();
                std::cout << "Enemy " << i << " is dead and removed." << std::endl;
            }
        });
    }

    EN(0, {
        player.show_hp();
        //First enemy (Kobold) attacks player, inflicting damage
        player.hp -= enemy.power;
        player.show_hp();

        enemy.show_hp();
        //Player isn't dead, and attacks first enemy (Kobold) and kills it
        enemy.hp -= player.power;
        enemy.show_hp();
    });

    //Checks if any enemy is dead, and if so then delete it
    for (int i = 0; i < enemies.size(); ++i) {
        EN(i, {
            if (!EMPTY(enemy) && enemy.hp <= 0) {
                //Removing enemy from it's class, therefore deleting it
                enemies[i] = Entity::Empty();
                std::cout << "Enemy " << i << " is dead and removed." << std::endl;
            }
        });
    }

    EN2(3, 4, {
        enemyA.show_hp();
        enemyA.hp -= enemyB.power;
        enemyA.show_hp();

        enemyB.show_hp();
        enemyB.hp -= enemyA.power;
        enemyB.show_hp();
    });

    //Checks if any enemy is dead, and if so then delete it
    for (int i = 0; i < enemies.size(); ++i) {
        EN(i, {
            if (!EMPTY(enemy) && enemy.hp <= 0) {
                //Removing enemy from it's class, therefore deleting it
                enemies[i] = Entity::Empty();
                std::cout << "Enemy " << i << " is dead and removed." << std::endl;
            }
        });
    }

    std::cout << "You win!" << std::endl;
}

int main() {
    std::vector<Enemy> enemies = {
        Entity::Kobold(), Entity::Dragon(), Entity::Spider(),
        Entity::Spider(), Entity::Dragon(), Entity::Kobold()};
    Process(enemies);
}

Output:

Spider health 5
Spider health 4
Player health 10
Player health 8
Kobold health 1
Kobold health 0, Dead!!!
Enemy 0 is dead and removed.
Spider health 5
Spider health -2, Dead!!!
Dragon health 20
Dragon health 17
Enemy 3 is dead and removed.
You win!
Arty
  • 14,883
  • 6
  • 36
  • 69
  • Thank you for the detailed response, however it doesn't seem to be the right answer to my question. I need multiple objects acting as different in-game enemies with the attributes given via the class, but I also need to change the class for the enemies as well. My plan was to have a handful of enemy objects and just reuse them over and over, therefore if I have 8 enemy objects for example, I want to be able to change 3 of them to a Kobold and 2 of the to Spiders etc, and then later when the player enters a different room, I can use the same objects and change their type to something else. – DinoNuggies Nov 10 '21 at 22:00
  • However, after thinking it over, maybe just creating a new object for every encountered enemy might be a better idea. – DinoNuggies Nov 10 '21 at 22:01
  • Updated question – DinoNuggies Nov 10 '21 at 22:32
  • Ignore my first comment, I didn't read the code right the first time, it's actually the answer to my question. Thank you, very informative and easy to read, I apologize for not reading it right. – DinoNuggies Nov 10 '21 at 22:40
  • @DinoNuggies Yes, it is always better to create a new object if it is of different type. If it is of different type, but has a lot of common attributes with previous object then you just copy common attributes to new object, for example with constructor. If it is of same type then you can reuse it and modify necessary fields to make migration. As you have RPG-like console game then speed should not matter, in this case the cleanest to understand way is to just re-creating new objects and copying necessary fields. – Arty Nov 11 '21 at 03:21
  • @DinoNuggies Also if you like my answer then don't forget to Accept and/or UpVote it. Accepting answer and UpVoting is done through clicking Check-Mark and Up-Arrow at the top of my answer, to the left from top of my answer there is Check-Mark sign (for accepting answer as correct) and Upper-Triangle sign (for up-voting). – Arty Nov 11 '21 at 03:26
  • So after trying to integrate it into my code, it requires a lot of changing to my core designs. If you wouldn't mind, I would like to ask for some advice: Should I use this method and change my code to fit it, or should I find a different way? Btw I upvoted your answer :D – DinoNuggies Nov 12 '21 at 17:03
  • @DinoNuggies Thanks for trying to UpVote. I didn't notice that you have too low reputation (`11`) for up-voting, so your vote didn't count. You can start upvoting only if you have reputation of `15`. Regarding your code - to find out the best way for Upgrading your code I need to know more details about how your code is currently implemented and also why none of my two suggestions doesn't fit your design... Better not to start rewriting your code now until we figure out what is best for you. I'm sure that for any design there should exist a good solution. So give more details about your code. – Arty Nov 12 '21 at 17:24
  • I will update my question with more details. Thx for cooperating with me :) – DinoNuggies Nov 12 '21 at 17:54
  • @DinoNuggies Thanks for updating your question! Yes, for your case it is possible to make a solution that fits your current design, without a need for re-designing from your side. It is late night at my home. So I'll write an answer for you in the early morning, I'll extend my answer with some magical wrapper-class that will magically solve your task :), wait till morning. – Arty Nov 12 '21 at 18:27
  • @DinoNuggies Just updated my answer, added **Part 3** where I describe new variant of solution which will magically fit your current design. Please put a look at it and if you can't understand something, ask questions... – Arty Nov 13 '21 at 04:13
  • Thank you! I can see that this will work, but it's a lot of work to use. I guess I expected something more like this: https://stackoverflow.com/questions/4383805/is-it-possible-to-have-an-array-of-different-objects-that-come-from-the-same-bas/4383901 This is actually the same question I have, but all the answers there returned errors. I guess I'm just asking if there is a way as simple as the one in the link above, or does it have to be this complex? – DinoNuggies Nov 14 '21 at 18:46
  • @DinoNuggies By your link I see example of virtual inheritance code, this is exactly what I explained in my **Part-2** of answer. But I did a bit too complex implementation, it can be made simpler. I just simplified my Part-2 solution and made [this code](https://godbolt.org/z/xcq3vvdKa). Basically all what you have to do is to move all common fields that are present in all entities to common class `Common`, afterwards you just create an array of pointers `Common * enemies[10] = ...` , that's all. Is this my code link simple enough for you now? I also copied this link to my **Part-2** answer. – Arty Nov 15 '21 at 04:11
  • Yes, thank you, it works perfectly. Will mark as answer and add what I did to my question. – DinoNuggies Nov 16 '21 at 23:09