HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Multiclassing and updating each of its component classes whenever it changes

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
eachupdatingwhenevermulticlassingitschangesclassesandcomponent

Problem

A Multiclass character can consist of any number of component classes. For example, if Fighter, Wizard, and Monk are character classes, then a Multiclass can be all 3 and having all three capabilities. Because multiple inheritance is impossible due to the unlimited number of such component classes, I'm using the Composite Pattern.

Now watch what happens here:

#include 
#include 
#include 

struct Coordinates {
    int x, y, z;
    void print() const {std::cout  componentClasses;  // Composite Pattern for the component classes
    using CharacterClass::CharacterClass;
    Multiclass (const std::string& name, int hitPoints) : CharacterClass (name, hitPoints) {
        // For simpicity, assume Fighter/Wizard/Monk is the Multiclass.
        componentClasses.push_back (new Fighter(name, hitPoints));
        componentClasses.push_back (new Wizard(name, hitPoints));
        componentClasses.push_back (new Monk(name, hitPoints));
    }
    Wizard* getWizard() const {return static_cast(componentClasses[1]);}
    void castsFireBall (LivingBeing& target) {getWizard()->castsFireBall(target);}
};

inline int distanceBetween (const Coordinates& c1, const Coordinates& c2) {
    return static_cast ( sqrt (pow(c1.x - c2.x, 2) + pow(c1.y - c2.y, 2) + pow(c1.z - c2.z, 2)) );
}

inline int distanceBetween (const LivingBeing& being1, const LivingBeing& being2) {
    return distanceBetween (being1.coordinates, being2.coordinates);
}

void Wizard::castsFireBall (LivingBeing& target) {
    if (distanceBetween(*this, target) < 10) {
        std::cout << name << " is caught in his own fireball because his coordinates are \n";
        coordinates.print();
    }
    else
        std::cout << target.name << " is hit by the fireball.\n";
}

int main() {
    Multiclass rex ("Rex", 50);
    Fighter borg ("Borg", 80);

    borg.showCoordinates();
    rex.moves ({100, 120, 0});
    rex.showCoordinates();

    rex.castsFireBall(borg);
}


Output:

`Borg's coordinates are (0, 0, 0)

Solution

Code Review

You need to start looking at class/struct differenes

This looks fine as a structure:

struct Coordinates {
    int x, y, z;
    void print() const {std::cout << "(" << x << ", " << y << ", " << z << ").\n";}
};


But this looks more like a class

struct LivingBeing {
    std::string name;
    int hitPoints;


You don't want other people reaching and changing the hit points of a character without using one of the offical mechanisme: like

void LivingBeing::inflictDammage(int damage)
{
    if (itenary.find("MagicRingOfStinky") {  // If you don't go through the inflictDamage
                                             // interface then people may forget
                                             // to check for the stinky ring!!
        damage = max(0, damage-2);
    }
    hitPoints -= damage;
}


Print routines:

These are fine. But also add the standard operator<<() to make it easy to print to any stream.

friend std::ostream& operator<<(std::ostream& str, Coordinates const& c)
 {
    c.print(str); // Need to modify print to take a stream.
    return str;
 }


Learn about smart pointers

Modern C++ does not use many RAW pointers (if you see them you are looking at people that are writting C with the C++ compiler). Your pointers should be held inside smart pointers (or resource aware objects of some type) that know how/when to deallocate them:

Here:

std::vector componentClasses;


A vector of pointers (OK I can live with that).

componentClasses.push_back (new Fighter(name, hitPoints));
    componentClasses.push_back (new Wizard(name, hitPoints));
    componentClasses.push_back (new Monk(name, hitPoints));


Pushing things created with new into it. Not that cool but I am looking for the point where these objects are destroyed and I see no destructor.

Design

I think you have some fundamental design flaws:

struct CharacterClass : LivingBeing {
    using LivingBeing::LivingBeing;
};


I think they basically stem from here. A character class is not a living being. A class is an attribute of sentient being that has taken the time to train for those abilities. i.e. A character may have "no class" as a child. But he trains with the local militia and gains some skills as a fighter (does not mean he "is-a" a fighter but rather has some skills as a fighter). Subsequently the character rescues a monk and as payment the monk imparts some training to the character that gives him more skills.

So to me the "class" of a character imparts some set of skills that a character can utilize when taking an action.

class Character: public LivingBeing
 {
     std::vector   skills;
     public:
        // As character gets more training/experience we can add new
        // skills. Like FighterLevel1 skills FighterLevel2 skills
        // etc.
        void undertakeTraining(SkillSet& newSkills)
        {
            skills.push_back(&newSkills);
        }
        // When we perform an action we ask each skill we have learned
        // how well that skill can do the action (both Monk and Fighter
        // can attack with bare hands but a monk has a higher skill level
        // at unarmed combat, but a fighter has a higher skill level at
        // armed combat).
        //
        // So find the skill set with the highest profeciency at a task
        // and use that.
        //
        // Note: The default skill at an action is `defaultInabilityToDoAction`
        //       This will return `skillLevel()` of 0 (or something obviously low)
        //       If you use this SkillAtAction to perform the task it is going
        //       to not be affective. So if you try and perform the action "FireBall"
        //       and you have no wizzard skill training then you are not going to
        //       find any ability to perform the action.
        void performAction(Action const& action)
        {
            static NoSkillAtAction  defaultInabilityToDoAction;

            std::reference_wrapper  skillAtAction = defaultInabilityToDoAction;

            for(SkillSet* skill: skills) {
                SkillAtAction&  canDoAction = skills.canDoAction(action);
                if (canDoAction.skillLevel() > skillAtActionskillLevel()) {
                    skillAtAction = canDoAction;
            }

            // Now Found the skill set that does the action at the highest Level
            skillAtAction.performAction(*this, action);
        }

    };


Other Classes you may need.

```
class Action
{
public:
virtual ~Action() {}
};
class SkillAtAction;
class SkillSet
{
public:
SkillAtAction& canDoAction(Action const& action);
};
class SkillAtAction
{
public:
virtual int skillLevel() = 0;
virtual void performAction(LivingBeing& src, Action const& action) = 0;
};

class defaultInabilityToDoAction: public SkillAtAction
{
public:
virtual

Code Snippets

struct Coordinates {
    int x, y, z;
    void print() const {std::cout << "(" << x << ", " << y << ", " << z << ").\n";}
};
struct LivingBeing {
    std::string name;
    int hitPoints;
void LivingBeing::inflictDammage(int damage)
{
    if (itenary.find("MagicRingOfStinky") {  // If you don't go through the inflictDamage
                                             // interface then people may forget
                                             // to check for the stinky ring!!
        damage = max(0, damage-2);
    }
    hitPoints -= damage;
}
friend std::ostream& operator<<(std::ostream& str, Coordinates const& c)
 {
    c.print(str); // Need to modify print to take a stream.
    return str;
 }
std::vector<CharacterClass*> componentClasses;

Context

StackExchange Code Review Q#75674, answer score: 2

Revisions (0)

No revisions yet.