12
\$\begingroup\$

When a Wizard casts the Monster Summoning spell, all of the summoned monsters shall follow his command. Should the Wizard interact with his summoned monsters directly (what are the potential pitfalls in this case?) or indirectly through a mediator?

In my code below, I'm using a mediator, but since such a specialized mediator (used only for this spell) do not pertain to all living beings (most living beings cannot even cast spells), I've placed the mediator data member only within a specific State class that is only created when the spell is cast. Here I have the mediator registering the summoned monsters when the spell is cast, dispatching the commands from the wizard to the summoned monsters, unregistering a summoned monster when it dies, and destroying all the summoned monsters in the event that the wizard himself dies (or loses his concentration).

Note: all classes are structs here for simplicity.

#include <iostream>
#include <list>
#include <string>

struct Mediator {
    struct LivingBeing* being;
    Mediator (LivingBeing* b) : being(b) {}
};

struct MonsterSummoningMediator : Mediator {
    std::list<struct SummonedMonster*> summonedMonsters;
    using Mediator::Mediator;
    ~MonsterSummoningMediator() {destroySummonedMonsters();}
    void registerSummonedMonster (SummonedMonster* m) {summonedMonsters.emplace_back(m);}
    void unregisterSummonedMonster (SummonedMonster* m) {summonedMonsters.remove(m);}
    inline void distributeAttackCommand();
    inline void distributeShieldMeCommand();
    inline void destroySummonedMonsters();
};

struct State {
    LivingBeing* being;
    State (LivingBeing* b) : being(b) {}
    virtual ~State() = default;
};

struct MonsterSummonerState : State {
    MonsterSummoningMediator* mediator;
    MonsterSummonerState (LivingBeing* being) :
        State(being), mediator(new MonsterSummoningMediator(being)) {}
    ~MonsterSummonerState() {delete mediator;}
    void addSummonedMonster(SummonedMonster* m) {mediator->registerSummonedMonster(m);}
};

struct LivingBeing {
    std::string name;
    State* state;
    LivingBeing() = default;
    LivingBeing (const std::string& n) : name(n) {}
    virtual ~LivingBeing() = default;
    virtual void dies() {std::cout << "\n" << name << " dies.\n";}
};

struct Monster : LivingBeing {
    using LivingBeing::LivingBeing;
    virtual ~Monster() {std::cout << name << " destroyed.\n";}
};

struct SummonedMonster {
    Monster* monster;
    LivingBeing* summoner;
    SummonedMonster (Monster* m, LivingBeing* s) : monster(m), summoner(s) {}
    ~SummonedMonster() {delete monster;}
    void receiveAttackCommand() {
        std::cout << monster->name << " will follow " << summoner->name << "'s order to attack.\n";
    }
    void receiveShieldMeCommand() {
        std::cout << monster->name << " will follow " << summoner->name << "'s order to shield him.\n";
    }
};

struct Wizard : LivingBeing {
    using LivingBeing::LivingBeing;
    void castsMonsterSummoning() {
        state = new MonsterSummonerState(this);
        MonsterSummonerState* monsterSummonerState = dynamic_cast<MonsterSummonerState*>(state);
        for (int i = 0; i < 5; i++) {
            Monster* monster = new Monster("Summoned Goblin");
            monsterSummonerState->addSummonedMonster(new SummonedMonster(monster, this));
        }
        std::cout << "Wizard::castsMonsterSummoning() called by " << name << ".\n";
    }
    // The MonsterSummoningMediator passes the order to all the summoned monsters.
    // There is no direct interaction between the summoner and the summoned monsters.
    void orderSummonedMonstersToAttack() {
        dynamic_cast<MonsterSummonerState*>(state)->mediator->distributeAttackCommand();
    }
    void orderSummonedMonstersToShieldMe() {
        dynamic_cast<MonsterSummonerState*>(state)->mediator->distributeShieldMeCommand();
    }
    virtual void dies() override {
        LivingBeing::dies();
        if (dynamic_cast<MonsterSummonerState*>(state))  // if (isAMonsterSummoner())
            endMonsterSummoningSpell();
    }
    void endMonsterSummoningSpell() {
        std::cout << "\n" << name << "'s Monster Summoning spell has ended.\n";
        delete state;
        state = new State(this);
    }
};

inline void MonsterSummoningMediator::distributeAttackCommand() {
    for (SummonedMonster* x : summonedMonsters)
        x->receiveAttackCommand();
}

inline void MonsterSummoningMediator::distributeShieldMeCommand() {
    for (SummonedMonster* x : summonedMonsters)
        x->receiveShieldMeCommand();
}

inline void MonsterSummoningMediator::destroySummonedMonsters() {
    for (SummonedMonster* x : summonedMonsters)
        delete x;
    summonedMonsters.clear();
}

int main() {
    Wizard wizard("Merlin");
    wizard.castsMonsterSummoning();
    wizard.orderSummonedMonstersToAttack();
    wizard.orderSummonedMonstersToShieldMe();
    wizard.dies();
}

Output:

Wizard::castsMonsterSummoning() called by Merlin.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to shield him.
Summoned Goblin will follow Merlin's order to shield him.
Summoned Goblin will follow Merlin's order to shield him.
Summoned Goblin will follow Merlin's order to shield him.
Summoned Goblin will follow Merlin's order to shield him.

Merlin dies.

Merlin's Monster Summoning spell has ended.
Summoned Goblin destroyed.
Summoned Goblin destroyed.
Summoned Goblin destroyed.
Summoned Goblin destroyed.
Summoned Goblin destroyed.

So far, the wizard and his summoned monsters are unaware of each other in the code. But direct interaction will occur between them and the wizard's enemies when they fight each other (this is shown below). Yet the summoned monsters are not really monsters--they are temporary illusions that can cause damage, so I'm confused about what the design should really be.

Should the proxy class SummonedMonster even be used at all? That's another issue I have. The reason for SummonedMonster is that it holds the data member SummonedMonster::summoner, which should not be in Monster (since not all monsters are summoned). But SummonedMonster::name has no value. Only SummonedMonster::monster->name has the desired value. Though I could define SummonedMonster::getName() const {return monster->name;}, I will have to do this for all data members of Monster. Should I instead define a State derived class SummonedMonsterState instead?

Here is an extended code illustrating the problems I've mentioned:

#include <iostream>
#include <list>
#include <string>

struct LivingBeing;
std::list<LivingBeing*> allBeingsPresent;

struct Mediator {
    LivingBeing* being;
    Mediator (LivingBeing* b) : being(b) {}
};

struct MonsterSummoningMediator : Mediator {
    std::list<struct SummonedMonster*> summonedMonsters;
    using Mediator::Mediator;
    ~MonsterSummoningMediator() {destroySummonedMonsters();}
    void registerSummonedMonster (SummonedMonster* monster) {summonedMonsters.emplace_back(monster);}
    void unregisterSummonedMonster (SummonedMonster* monster) {summonedMonsters.remove(monster);}
    inline void distributeAttackCommand();
    inline void distributeShieldMeCommand();
    inline void destroySummonedMonsters();
};

struct State {
    LivingBeing* being;
    State (LivingBeing* b) : being(b) {}
    virtual ~State() = default;
};

struct MonsterSummonerState : State {
    MonsterSummoningMediator* mediator;
    MonsterSummonerState (LivingBeing* being) : 
        State(being), mediator(new MonsterSummoningMediator(being)) {}
    ~MonsterSummonerState() {delete mediator;}
};

struct LivingBeing {
    std::string name;
    State* state;
    LivingBeing() = default;
    LivingBeing (const std::string& n) : name(n) {allBeingsPresent.emplace_back(this);}
    virtual ~LivingBeing() = default;
    virtual void dies() {std::cout << "\n" << name << " dies.\n";}
    void attacks (LivingBeing* target) {
        std::cout << "\n" << name << " attacks " << target->name << ".\n";
        target->dies();
    }
};

struct Monster : LivingBeing {
    using LivingBeing::LivingBeing;
    virtual ~Monster() {std::cout << name << " destroyed.\n";}
};

struct SummonedMonster : Monster {
    Monster* monster;  // Proxy Pattern
    LivingBeing* summoner;
    SummonedMonster (Monster* m, LivingBeing* s) : monster(m), summoner(s) {
        allBeingsPresent.emplace_back(this);
    }
    ~SummonedMonster() {delete monster;}
    void receiveAttackCommand() {std::cout << monster->name << " will follow " << summoner->name << "'s order to attack.\n";}
    void receiveShieldMeCommand() {std::cout << monster->name << " will follow " << summoner->name << "'s order to shield him.\n";}
    virtual inline void dies() override;
};

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

struct Fighter : CharacterClass {
    using CharacterClass::CharacterClass;
};

struct Wizard : CharacterClass {
    using CharacterClass::CharacterClass;
    void castsMonsterSummoning() {
        state = new MonsterSummonerState(this);
        MonsterSummoningMediator* monsterSummoningMediator = getMonsterSummoningMediator();
        for (int i = 0; i < 5; i++) {
            Monster* monster = new Monster("Summoned Goblin");
            monsterSummoningMediator->registerSummonedMonster(new SummonedMonster(monster, this));
            allBeingsPresent.remove(monster);  // Since only the new SummonedMonster* is to be inserted into allBeingsPresent.
        }
    }
    MonsterSummoningMediator* getMonsterSummoningMediator() const {
        return dynamic_cast<MonsterSummonerState*>(state)->mediator;
    }
    void orderSummonedMonstersToAttack() {
        getMonsterSummoningMediator()->distributeAttackCommand();
    }
    void orderSummonedMonstersToShieldMe() {
        getMonsterSummoningMediator()->distributeShieldMeCommand();
    }
    virtual void dies() override {
        LivingBeing::dies();
        if (dynamic_cast<MonsterSummonerState*>(state))  // if (isAMonsterSummoner())
            endMonsterSummoningSpell();
    }
    void endMonsterSummoningSpell() {
        std::cout << "\n" << name << "'s Monster Summoning spell has ended.\n";
        delete state;
        state = new State(this);
    }
};

inline void SummonedMonster::dies() {
    std::cout << "\n" << monster->name << " dies.\n";  // Mediator now unregisters 'this'.
    dynamic_cast<Wizard*>(summoner)->getMonsterSummoningMediator()->unregisterSummonedMonster(this);
}

inline void MonsterSummoningMediator::distributeAttackCommand() {
    for (SummonedMonster* x : summonedMonsters)
        x->receiveAttackCommand();
}

inline void MonsterSummoningMediator::distributeShieldMeCommand() {
    for (SummonedMonster* x : summonedMonsters)
        x->receiveShieldMeCommand();
}

inline void MonsterSummoningMediator::destroySummonedMonsters() {
    for (SummonedMonster* x : summonedMonsters)
        delete x;
    summonedMonsters.clear();
}

int main() {
    Wizard wizard("Merlin");
    Fighter fighter("Borg");
    wizard.castsMonsterSummoning();
    for (LivingBeing* x : allBeingsPresent) std::cout << x->name << " is present.\n";
    wizard.orderSummonedMonstersToAttack();
    wizard.orderSummonedMonstersToShieldMe();
    fighter.attacks(allBeingsPresent.back());  // The summoned monster dies.
    wizard.dies();
}

Output:

Merlin is present.
Borg is present.
 is present.  // name value of the summoned monster missing
 is present.  // and thus all other data member values missing.
 is present.
 is present.
 is present.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to attack.
Summoned Goblin will follow Merlin's order to shield him.
Summoned Goblin will follow Merlin's order to shield him.
Summoned Goblin will follow Merlin's order to shield him.
Summoned Goblin will follow Merlin's order to shield him.
Summoned Goblin will follow Merlin's order to shield him.

Borg attacks .  // name value of the summoned monster missing

Summoned Goblin dies.

Merlin dies.

Merlin's Monster Summoning spell has ended.
Summoned Goblin destroyed.
 destroyed.
Summoned Goblin destroyed.
 destroyed.
Summoned Goblin destroyed.
 destroyed.
Summoned Goblin destroyed.
 destroyed.
\$\endgroup\$
2
  • \$\begingroup\$ Observations: 1.) Currently Wizard can only have zero or one state. Multiple simultaneous states are not supported. 2.) Wizard::castsMonsterSummoning replaces any previous state regardsless of what it is and does not delete the previous state object. 3.) Wizard::orderSummonedMonstersTo... do not check if the state* points to a MonsterSummoningMediator. \$\endgroup\$ Commented Dec 25, 2014 at 15:03
  • \$\begingroup\$ Thanks for your time in reviewing. I have a finite state machine already in my production code to handle concurrent states (didn't bother to show it here to keep it simple)-- a vector of stacks of states to be precise. In fact the vector reserves states for each type of spell (can't think of another way), so the vector will be very long when I've coded many spells. There does need to be more run-time checks to be robust though, and I do have to investigate the complications of Wizard::castsMonsterSummoning being called repeatedly before the previous one expires (hence the stack of states). \$\endgroup\$
    – prestokeys
    Commented Dec 25, 2014 at 16:01

1 Answer 1

3
+50
\$\begingroup\$

The flaws of using the proxy pattern demonstrated above will be removed if I use the adapter pattern instead:

struct LivingBeing {
    struct SummonedTag {};
    std::string name;
    State* state;
    LivingBeing() = default;
    LivingBeing (const std::string& n) : name(n) {allBeingsPresent.emplace_back(this);}
    LivingBeing (const std::string& n, SummonedTag) : name(n) {}  // Do NOT call 'allBeingsPresent.emplace_back(this);' else there will be duplication.
    // ...
};

struct Monster : LivingBeing {
    using LivingBeing::LivingBeing;
    // ...
};

struct Goblin : Monster { using Monster::Monster; };

struct SummonedMonster : Monster {
    LivingBeing* summoner;
    SummonedMonster (LivingBeing* s, const std::string& name) : Monster(name), summoner(s) {}
    // ...
}

template <typename T>
struct Summoned : SummonedMonster, T {  // *** Adapter Pattern
    Summoned (LivingBeing* summoner, const std::string& name) : SummonedMonster(summoner, name),
        T(name, SummonedTag{}) {}
};

template <typename T>
void Wizard::castsMonsterSummoning() {
        state = new MonsterSummonerState(this);
        MonsterSummoningMediator* monsterSummoningMediator = getMonsterSummoningMediator();
        for (int i = 0; i < 5; i++)
            monsterSummoningMediator->registerSummonedMonster(new Summoned<T>(this, "Summoned Monster"));
    }
}

The resulting output does not show the problems in the question's output:

Wizard::castsMonsterSummoning() called by Merlin.
monsterSummoningMediator->summonedMonsters.size() = 5
Merlin is present.
Borg is present.
Summoned Monster is present.
Summoned Monster is present.
Summoned Monster is present.
Summoned Monster is present.
Summoned Monster is present.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to shield him.
Summoned Monster will follow Merlin's order to shield him.
Summoned Monster will follow Merlin's order to shield him.
Summoned Monster will follow Merlin's order to shield him.
Summoned Monster will follow Merlin's order to shield him.

Borg attacks Summoned Monster.

Summoned Monster dies.

Merlin dies.

Merlin's Monster Summoning spell has ended.
MonsterSummoningMediator destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.  // Hmmm... duplicate destructor call (seems harmless though).
Summoned Monster destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.

I still have to investigate if this works in more complex constructions though.

Update: I've experimented this new design with more complex constructions and its seems to be working fine. Here is my latest design, in case you wanted to see the whole thing:

#include <iostream>
#include <list>
#include <string>

struct LivingBeing;

std::list<LivingBeing*> allBeingsPresent;

struct Mediator {
    LivingBeing* being;
    Mediator (LivingBeing* b) : being(b) {}
};

struct MonsterSummoningMediator : Mediator {
    std::list<struct SummonedMonster*> summonedMonsters;
    using Mediator::Mediator;
    ~MonsterSummoningMediator() {std::cout << "MonsterSummoningMediator destroyed.\n";  destroySummonedMonsters();}
    void registerSummonedMonster (SummonedMonster* monster) {summonedMonsters.emplace_back(monster);}
    void unregisterSummonedMonster (SummonedMonster* monster) {
        summonedMonsters.remove(monster);
        std::cout << "MonsterSummoningMediator::unregisterSummonedMonster called.\n";
    }
    inline void distributeAttackCommand();
    inline void distributeShieldMeCommand();
    inline void destroySummonedMonsters();
};

struct State {
    LivingBeing* being;
    State (LivingBeing* b) : being(b) {}
    virtual ~State() = default;
};

struct MonsterSummonerState : State {
    MonsterSummoningMediator* mediator;
    MonsterSummonerState (LivingBeing* being) : State(being), mediator(new MonsterSummoningMediator(being)) {}
    ~MonsterSummonerState() {delete mediator;}
};

struct LivingBeing {
    struct Data {
        std::string name;
        int hitPoints, strength;
    };
    struct SummonedTag {};
    std::string name;
    int hitPoints, strength;
    State* state;
    LivingBeing() = default;
    LivingBeing (const Data& data) : name(data.name), hitPoints(data.hitPoints), strength(data.strength) {allBeingsPresent.emplace_back(this);}
    LivingBeing (const Data& data, SummonedTag) : name(data.name), hitPoints(data.hitPoints), strength(data.strength) {}  // Do NOT call 'allBeingsPresent.emplace_back(this);' else there will be duplication.
    virtual ~LivingBeing() = default;
    void losesHitPoints (int loss) {const int formerHitPoints = hitPoints;  hitPoints -= loss;  std::cout << name << " has gone from " << formerHitPoints << " hit points to " << hitPoints << " hit points." << std::endl;}
    virtual void dies() {std::cout << "\n" << name << " dies.\n";}
    void attacks (LivingBeing* target) {std::cout << "\n" << name << " attacks " << target->name << ".\n";  target->losesHitPoints(20);  target->dies();}
};

struct Monster : LivingBeing {
    using LivingBeing::LivingBeing;
    virtual ~Monster() {std::cout << name << " destroyed.\n";}
};

struct Goblin : Monster { using Monster::Monster; };

struct Spell {
    std::string name;
    Spell (const std::string& n) : name(n) {}
} *CharmPerson = new Spell("Charm Person"), *Haste = new Spell("Haste"), *SleepSpell = new Spell("Sleep");

struct GoblinWizard : Goblin {
    std::list<Spell*> spellsKnown;
    std::string specialty;
    int numWands;
    template <typename... Args>
    GoblinWizard (const LivingBeing::Data& data, const std::list<Spell*>& spells, const std::string s, int num, Args&&... args) :
        Goblin(data, std::forward<Args>(args)...), spellsKnown(spells), specialty(s), numWands(num) {}
};

struct Lich : Monster {
    struct Data {
        std::list<Spell*> spellsKnown;
        int undeadLevel;
        std::string deity;
    };
    Data lichData;
    template <typename... Args>
    Lich (const LivingBeing::Data& data, const Data& lData, Args&&... args) : Monster(data, std::forward<Args>(args)...), lichData(lData) {}
};

struct SummonedMonster : Monster {
    LivingBeing* summoner;
    SummonedMonster (LivingBeing* s, const LivingBeing::Data& data) : Monster(data, SummonedTag{}), summoner(s) {}
    void receiveAttackCommand() {std::cout << name << " (hit points = " << hitPoints << ", strength = " << strength << ") will follow " << summoner->name << "'s order to attack.\n";}
    void receiveShieldMeCommand() {std::cout << name << " (hit points = " << hitPoints << ", strength = " << strength << ") will follow " << summoner->name << "'s order to shield him.\n";}
    virtual inline void dies() override;
};

template <typename T>
struct Summoned : SummonedMonster, T {  // Adapter Pattern
    template <typename... Args>
    Summoned (LivingBeing* summoner, const LivingBeing::Data& data, Args&&... args) : SummonedMonster(summoner, data), T(data, std::forward<Args>(args)..., SummonedTag{}) {
        allBeingsPresent.emplace_back(dynamic_cast<SummonedMonster*>(this));  // This is the pointer that we want to insert into allBeingsPresent.
    }
};

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

struct Fighter : CharacterClass {
    using CharacterClass::CharacterClass;
};

struct Wizard : CharacterClass {
    using CharacterClass::CharacterClass;
    void castsMonsterSummoning() {
        state = new MonsterSummonerState(this);
        MonsterSummoningMediator* monsterSummoningMediator = getMonsterSummoningMediator();
        for (int i = 0; i < 5; i++) {
            monsterSummoningMediator->registerSummonedMonster(new Summoned<Goblin>
                (this, {"Summoned Goblin", 6 + i, 11 + i}));
            monsterSummoningMediator->registerSummonedMonster(new Summoned<GoblinWizard>
                (this, {"Summoned Goblin Wizard", 7 + i, 12 + i}, std::list<Spell*>{CharmPerson, Haste, SleepSpell}, "Illusion", 8));  // 'std::list<Spell*>' required in order for the compiler to deduce the what Args... is.
            monsterSummoningMediator->registerSummonedMonster(new Summoned<Lich>
                (this, {"Summoned Lich", 8 + i, 13 + i}, Lich::Data{{Haste, SleepSpell, CharmPerson}, 7, "Demogorgon"}));  // 'Lich::Data' required in order for the compiler to deduce the what Args... is.
        }
        std::cout << "Wizard::castsMonsterSummoning() called by " << name << ".\n";
    }
    MonsterSummoningMediator* getMonsterSummoningMediator() const {return dynamic_cast<MonsterSummonerState*>(state)->mediator;}
    void orderSummonedMonstersToAttack() {
        getMonsterSummoningMediator()->distributeAttackCommand();
    }
    void orderSummonedMonstersToShieldMe() {
        getMonsterSummoningMediator()->distributeShieldMeCommand();
    }
    virtual void dies() override {
        LivingBeing::dies();
        if (dynamic_cast<MonsterSummonerState*>(state))
            endMonsterSummoningSpell();
    }
    void endMonsterSummoningSpell() {
        std::cout << "\n" << name << "'s Monster Summoning spell has ended.\n";
        delete state;
        state = new State(this);
    }
};

inline void SummonedMonster::dies() {
    std::cout << "\n" << name << " dies.\n";
    dynamic_cast<Wizard*>(summoner)->getMonsterSummoningMediator()->unregisterSummonedMonster(this);
}

inline void MonsterSummoningMediator::distributeAttackCommand() {
    for (SummonedMonster* x : summonedMonsters)
        x->receiveAttackCommand();
}

inline void MonsterSummoningMediator::distributeShieldMeCommand() {
    for (SummonedMonster* x : summonedMonsters)
        x->receiveShieldMeCommand();
}

inline void MonsterSummoningMediator::destroySummonedMonsters() {
    for (SummonedMonster* x : summonedMonsters)
        delete x;
    summonedMonsters.clear();
}

int main() {
    Wizard wizard({"Merlin", 25, 9});
    Fighter fighter({"Borg", 48, 18});
    wizard.castsMonsterSummoning();
    for (LivingBeing* x : allBeingsPresent)
        std::cout << x->name << " (hit points = " << x->hitPoints << ", strength = " << x->strength << ") is present.\n";
    wizard.orderSummonedMonstersToAttack();
    wizard.orderSummonedMonstersToShieldMe();
    fighter.attacks(allBeingsPresent.back());  // The summoned monster dies.
    wizard.dies();
}
\$\endgroup\$
2
  • \$\begingroup\$ A gist to an alternative possibility (not a full review hence as comment): using the strategy pattern. Every LivingBeing (including the summoned monsters) uses a configurable strategy to select its actions. Actions (e.g. attack, defend, etc.) are objects see the command pattern. The following action selection strategies could exit: "via user interface" (for the player(s)); "via AI" (for NPC monsters); "controlled by other living being" (for summoned monsters/henchman). When summoning a monster, the summoner's set of actions temporarily gains "control summoned attack/defend"-actions. \$\endgroup\$ Commented Dec 25, 2014 at 12:10
  • \$\begingroup\$ Yes, the strategy pattern was what I was going to use next. Command pattern used as a call-back method when the summoner wants his summoned creatures to do whatever at a suitable time, and strategy pattern used to set which Command object to instantiate and use. That's later on though. Right now I'm still in the middle of creating the summoned creatures, with the mediator pattern used to subscribe and unsubscribe them and keep track of them, and adapter pattern to ensure that they fit into the current interfaces. \$\endgroup\$
    – prestokeys
    Commented Dec 25, 2014 at 14:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.