In Dungeons and Dragons, we have many spells, e.g. the Charm spell to get anyone to do what you want, the Web spell to entangle someone, the Invisibility spell to make yourself invisible (but once you attack or cast another spell,
you lose your invisibility). Implementing these spells is not a problem, and each spell will induce a special state of the LivingBeing
. The challenge is when the LivingBeing
, which has a StateMachine
data member holding
a tuple of state pointers, is in multiple spell states. For example, if you charmed a person to attack someone, he will pass an Attack*
pointer to LivingBeing::followsAction(Attack*)
, and LivingBeing::followsAction
will
be overloaded with all the necessary Action
derived pointers. But what type do we pass to LivingBeing::followsAction
if the charmed person is caught in the Web spell, and thus cannot move to attack anyone? It would have to
be AttackWithMissileWeapon
. What if the charmed person is also under the Invisible spell? Then Attack
will have a derived type AttackButLoseInvisibility
, that way LivingBeing::followsAction(AttackButLoseInvisibility*)
will automatically delete his IsInvisibleState
, rather than doing some unwanted if statements. The number of if statements will explode as we introduce hundreds of different spells, and they would have to be placed in so many various
places that we won't be able to find and the entire code cannot be maintained. The state pattern, of course, is designed to avoid these crazy if-checks and each relevant state class should have their own handleOtherState
methods. But how to access these
handleOtherState
methods of all the state classes without resorting to a bunch of if statements that we aim to avoid? We can define:
template <typename Handler, typename... StatesRelevantToHandler> struct RelevantStatesToHandle;
template <> struct RelevantStatesToHandle<CharmedState, AttackOrder> {
using type = HandleOtherRelevantStates<CharmedState, CaughtInWebSpellState, IsInvisibleState>;
};
template <> struct RelevantStatesToHandle<CharmedState, ComeToMeToProtectMeOrder> {
using type = HandleOtherRelevantStates<CharmedState, CaughtInWebSpellState>;
};
Here, we define all the relevant spell states to CharmedState
when the charm order is AttackOrder
. And (for now), they are CaughtInWebSpellState
, IsInvisibleState
, as described above. For a different charm order, like
ComeToMeToProtectMeOrder
, then list out the relevant states for that, e.g. CaughtInWebSpellState
, which would prevent him from moving towards his charmer to protect him. Then define for other spell states that may be
affected by other simultaneous states, and it does not even have to spellstates for the Handler
template, it can be any class that has the handleOtherState
overloads, e.g. it could be just LivingBeing
that wants to
just make a general attack (which can certainly be affect by the presence of simultaneous states). And when other new spells, and hence new spell states, are introduced, you simply decide what Handler classes would be affected
by this new state, and then place in the list of templates accordingly, and then the compiler will tell you where to define the necessary handleOtherState
, followsAction
, visit
, etc... overloads and such, during which you can
then decide on exactly how they would affect these Handler classes.
So what does the typename HandleOtherRelevantStates
do once it is found? As mentioned, it will invoke the handleOtherState
overloads, but the overloads from which classes? From ALL the classes listed out in the template list.
Here is its implementation:
template <typename, typename...> struct HandleOtherRelevantStates;
struct HandleOtherRelevantStates<Handler> {
template <typename... Args>
void operator()(Handler*, LivingBeing*, Menu& menu, Args&&...) const { menu.computeProductOfOptions(); } // End of general recursion.
template <typename CharmSpellOrder>
void operator()(Handler* charmedState, LivingBeing*, Menu& menu, const CharmSpellOrder* order) const {
menu.addOptionToMultiply(order->makeCharmedOption(charmedState));
menu.computeProductOfOptions();
}
// More overloads can be defined when needed.
};
template <typename Handler, typename State, typename... OtherStates>
struct HandleOtherRelevantStates<Handler, State, OtherStates...> : HandleOtherRelevantStates<Handler, OtherStates...> {
template <typename... Args>
void operator()(Handler* handler, LivingBeing* stateOwner, Menu& menu, Args&&... args) const {
const bool handleNextState = handler->handleOtherState(stateOwner->template getState<State>(), stateOwner, menu, std::forward<Args>(args)...);
if (handleNextState)
HandleOtherRelevantStates<Handler, OtherStates...>::operator()(handler, stateOwner, menu, std::forward<Args>(args)...);
}
};
So handleOtherState
is now being invoked, but what are Menu::addOptionToMultiply
and Menu::computeProductOfOptions()
? Well, the handleOtherState
methods will add a new Option
object to the menu of choices.
But the complication is that different Option
objects will generally be outputted as the relevant state classes from the template list carry out their own handleOtherState
, and we cannot include all of them because
they will generally conflict with each other. For example, a person in CharmedState
who gets an AttackOrder
but is also in CaughtInWebSpellState
should add AttackWithMissleWeapon
to the menu. But if he
is in IsInvisibleState
, the menu shall get the AttackButLoseInvisibility
option. And if the charmed person is in BOTH CaughtInWebSpellState
and IsInvisibleState
, the menu should NOT get BOTH the
AttackWithMissleWeapon
and AttackButLoseInvisibility
options, since choosing one will satisfy one state but not the other. Instead the menu should only receive one new option, which should be
AttackWithMissleWeaponButLoseInvisibility
, which will satify both the CaughtInWebSpellState
and IsInvisibleState
for the charmed person. But does that mean that we have to write out another undesired
if statement to achieve that, when we are trying to avoid a mountain of if statements? We should define a product of Actions
, in this case
AttackWithMissleWeapon x AttackButLoseInvisibility = AttackWithMissleWeaponButLoseInvisibility.
Unfortunately, these are objects we are passing to the menu (they have to be stored as their base class Action*
), so defining this product with templates won't work. Instead, we have to define the product by passing different Action
pointers in pairs, and since their
runtime types need to be known, the Visitor Pattern will require a triple dispatch, e.g.
std::shared_ptr<Option> multiplyOptions (const std::shared_ptr<Option>& a, const std::shared_ptr<Option>& b) {
const MultiplyActionsVisitor tripleDispatchVisitor(a,b);
return a->action->accept(tripleDispatchVisitor, b->action.get()); // Triple dispatch needed since the runtime types of both a->action and b->action are needed.
}
The visit
overloads of MultiplyActionsVisitor
to take care of the product mentioned above can be:
std::shared_ptr<Option> visit (const AttackWithMissileWeapon* a, const AttackButLoseInvisibility*) const {
return std::make_shared<Option>(std::make_shared<AttackWithMissileWeaponButLoseInvisibility>(a->target), "Attack " + a->target->name + " with a missle weapon, but will lose his invisibility after doing so.");
} // i.e. AttackWithMissileWeapon x AttackButLoseInvisibility = AttackWithMissileWeaponButLoseInvisibility
std::shared_ptr<Option> visit (const AttackButLoseInvisibility* a, const AttackWithMissileWeapon* b) const { return visit(b,a); } // This makes the product commutative, and is needed for all reverse products.
Note that the product has to be defined twice for the two different orders of multiplication, since the multiplication shall be commutative. Passing a
and b
and then returning visit(b,a)
will take care of that.
Special general products can be handled generically:
template <typename T> std::shared_ptr<Option> visit (const T*, const T*) const { return productEquals<T>(); } // Any Action derived type multiplied by itself will give itself (either option 1 or option 2 could also be returned since option1->action and option2->action are of type std::shared_ptr<T>).
template <typename T, typename U> std::shared_ptr<Option> visit (const T*, const U*) const {
if constexpr (std::is_base_of<T, U>::value) return productEquals<U>(); // The derived type will always be the product.
else if constexpr (std::is_base_of<U, T>::value) return productEquals<T>();
}
This takes care of multiplying two actions. To multiply any number of actions, we just loop through the optionsToMultiply
vector of the Menu
class and call multiplyOptions
with each iteration:
void Menu::computeProductOfOptions() {
if (optionsToMultiply.empty())
return;
if (optionsToMultiply.size() == 1)
addOption(optionsToMultiply.back()); // Since it is the only option possible.
else if (optionsToMultiply.size() > 1) {
std::shared_ptr<Option> option = optionsToMultiply[0];
for (std::size_t i = 1; i < optionsToMultiply.size(); ++i)
option = multiplyOptions(option, optionsToMultiply[i]); // The product of all std::shared_ptr<Option> pointers in optionsToMultiply. But the run-time type of optionsToMultiply[i]->action, for all i, need to be used, and since two at a time are needed for each consecutive product, the triple dispatch version of the Visitor Pattern is needed.
addOption(option);
}
optionsToMultiply.clear(); // Must clear, else multiplication with the next group of actions from a separate case will be done, which won't make sense. Besides, why keep them in memory anyway, once the product is found?
}
And as mentioned before, RelevantStatesToHandle
does not have to have a state class in its first template parameter, it could be any class that needs to call the handleOtherState
methods. For example
template <> struct RelevantStatesToHandle<Wizard, LivingBeing, Wizard::CastingSpellTag> {
using type = HandleOtherRelevantStates<Wizard, IsInvisibleState, CaughtInWebSpellState>;
};
will handle all the states that could affect a Wizard's ability to cast spells (you lose your invisibility if you cast a spell, and if you are caught in the Web spell, you have, say, a 50% of failure). The above will handle this without doing unwanted run-time checks which would be prone to error when new states are introduced.
So we have tackled much of the problems with simultaneous states of the person in question, but what if that person is trying to interact with others and the (simultaneous) states of those people also affect the menu options?
That is what I'm working on right now, and anyone with further insights can join in. Here is my rather longish entire code. It is the WizardState<Charm>::handleOtherState
overloads that I have this issue to tackle. My latest idea is to use RelevantStatesToHandle
with those being interacted with as well (it currently seems to be working, though definitely complicating matters). But I had to chop off some 10% of the last part for this to fit in due to the maximum limit allowed.
#include <iostream>
#include <list>
#include <string>
#include <memory>
#include <unordered_map>
#include <algorithm>
template <typename... States>
class StateMachine {
private:
std::tuple<States*...> states;
public:
template <typename T> T*& getState() { return std::get<T*>(states); }
template <typename T> T* getState() const { return std::get<T*>(states); }
template <typename T> bool stateIsNotNullptr() const { return std::get<T*>(states) != nullptr; }
template <int N> typename std::tuple_element<N, std::tuple<States*...>>::type& getState() { return std::get<N>(states); }
};
struct LivingBeing; struct Wizard;
struct CharmedState; struct UnderSleepSpellState; struct CaughtInWebSpellState; struct IsInvisibleState;
struct HasNoSharpWeaponState { ~HasNoSharpWeaponState() { std::cout << "***HasNoSharpWeaponState destroyed.\n"; } };
struct Action; struct Attack; struct AttackWithMissileWeapon; struct ProtectCharmer; struct AttackButLoseInvisibility; struct AttackWithMissileWeaponButLoseInvisibility; struct BeReadyToDefendOneself;
struct BreakFreeFromWebWithSharpWeapon; struct BreakFreeFromWebByHand; struct AskForHelpToUntangleFromWeb; struct GiveCharmOrderToProtectHim; struct GiveCharmOrderToAttackSomeone;
struct CastASpell; struct CastASpellButLoseInvisibility; struct CastASpellWithChanceOfFailure; struct CastASpellButLoseInvisibilityWithChanceOfFailure;
struct Option;
class MultiplyActionsVisitor;
std::list<LivingBeing*> allBeingsPresent;
struct ActionVisitor {
LivingBeing* livingBeing;
ActionVisitor (LivingBeing* being) : livingBeing(being) { }
// void visit (Action*) { std::cout << "ActionVisitor::visit overload missed!.\n"; }
void visit (Attack*);
void visit (AttackWithMissileWeapon*);
void visit (ProtectCharmer*);
void visit (AttackButLoseInvisibility*);
void visit (AttackWithMissileWeaponButLoseInvisibility*);
void visit (BeReadyToDefendOneself*);
void visit (BreakFreeFromWebWithSharpWeapon*);
void visit (BreakFreeFromWebByHand*);
void visit (AskForHelpToUntangleFromWeb*);
};
struct WizardActionVisitor {
Wizard* wizard;
WizardActionVisitor (Wizard* w) : wizard(w) { }
void visit (GiveCharmOrderToProtectHim*);
void visit (GiveCharmOrderToAttackSomeone*);
void visit (CastASpell*);
void visit (CastASpellButLoseInvisibility*);
void visit (CastASpellWithChanceOfFailure*);
void visit (CastASpellButLoseInvisibilityWithChanceOfFailure*);
};
struct Action {
virtual ~Action() = default;
virtual void accept (ActionVisitor&) { }
virtual void accept (WizardActionVisitor&) { }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const Action*) const { std::cout << "Error! Action derived class 'accept' overload missed with Action* as parameter!\n"; std::cin.get(); return nullptr; }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const Attack*) const { std::cout << "Error! Action derived class 'accept' overload missed with Attack* as parameter!\n"; std::cin.get(); return nullptr; }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const AttackButLoseInvisibility*) const { std::cout << "Error! Action derived class 'accept' overload missed with AttackButLoseInvisibility* as parameter!\n"; std::cin.get(); return nullptr; }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const AttackWithMissileWeapon*) const { std::cout << "Error! Action derived class 'accept' overload missed with AttackWithMissileWeapon* as parameter!\n"; std::cin.get(); return nullptr; }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const ProtectCharmer*) const { std::cout << "Error! Action derived class 'accept' overload missed with ProtectCharmer* as parameter!\n"; std::cin.get(); return nullptr; }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const AttackWithMissileWeaponButLoseInvisibility*) const { std::cout << "Error! Action derived class 'accept' overload missed with AttackWithMissileWeaponButLoseInvisibility* as parameter!\n"; std::cin.get(); return nullptr; }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const CastASpell*) const { std::cout << "Error! Action derived class 'accept' overload missed with CastASpell* as parameter!\n"; std::cin.get(); return nullptr; }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const CastASpellButLoseInvisibility*) const { std::cout << "Error! Action derived class 'accept' overload missed with CastASpellButLoseInvisibility* as parameter!\n"; std::cin.get(); return nullptr; }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const CastASpellWithChanceOfFailure*) const { std::cout << "Error! Action derived class 'accept' overload missed with CastASpellWithChanceOfFailure* as parameter!\n"; std::cin.get(); return nullptr; }
};
struct Attack : Action {
LivingBeing* target;
Attack (LivingBeing* t) : target(t) { }
~Attack() { std::cout << "***Attack object destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Action* action) const override { return action->accept(tripleDispatchVisitor, this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const AttackWithMissileWeapon*) const override;
};
struct AttackButLoseInvisibility : Attack {
using Attack::Attack;
~AttackButLoseInvisibility() { std::cout << "***AttackButLoseInvisibility object destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Action* action) const override { return action->accept(tripleDispatchVisitor, this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const AttackWithMissileWeapon*) const override;
};
struct AttackWithMissileWeapon : Attack {
using Attack::Attack;
~AttackWithMissileWeapon() { std::cout << "***AttackWithMissileWeapon object destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Action* action) const override { return action->accept(tripleDispatchVisitor, this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const Attack*) const override;
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const AttackButLoseInvisibility*) const override;
};
struct AttackWithMissileWeaponButLoseInvisibility : AttackWithMissileWeapon {
using AttackWithMissileWeapon::AttackWithMissileWeapon;
~AttackWithMissileWeaponButLoseInvisibility() { std::cout << "***AttackWithMissileWeaponButLoseInvisibility object destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
// virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Action* action) const override { return action->accept(tripleDispatchVisitor, this); } // Should examine why enabling this override leads to a runtime error (despite all the accept overloads above).
};
struct ProtectCharmer : Action {
LivingBeing* charmer;
ProtectCharmer (LivingBeing* c) : charmer(c) { }
~ProtectCharmer() { std::cout << "***ProtectCharmer object destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Action* action) const override { return action->accept(tripleDispatchVisitor, this); }
};
struct BeReadyToDefendOneself: Action {
~BeReadyToDefendOneself() { std::cout << "***BeReadyToDefendOneselfobject destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
};
struct BreakFreeFromWebWithSharpWeapon : Action {
~BreakFreeFromWebWithSharpWeapon() { std::cout << "***BreakFreeFromWebWithSharpWeapon object destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
};
struct BreakFreeFromWebByHand : Action {
~BreakFreeFromWebByHand() { std::cout << "***BreakFreeFromWebByHand object destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
};
struct AskForHelpToUntangleFromWeb : Action {
LivingBeing* helper;
AskForHelpToUntangleFromWeb (LivingBeing* h) : helper(h) { }
~AskForHelpToUntangleFromWeb() { std::cout << "***AskForHelpToUntangleFromWeb object destroyed.\n"; }
virtual void accept (ActionVisitor& visitor) override { visitor.visit(this); }
};
struct GiveCharmOrderToProtectHim : Action {
LivingBeing* charmedVictim;
GiveCharmOrderToProtectHim (LivingBeing* c) : charmedVictim(c) { }
~GiveCharmOrderToProtectHim() { std::cout << "***GiveCharmOrderToProtectHim object destroyed.\n"; }
virtual void accept (WizardActionVisitor& visitor) override { visitor.visit(this); }
};
struct GiveCharmOrderToAttackSomeone : Action {
LivingBeing* charmedVictim;
GiveCharmOrderToAttackSomeone (LivingBeing* c) : charmedVictim(c) { }
~GiveCharmOrderToAttackSomeone() { std::cout << "***GiveCharmOrderToAttackSomeone object destroyed.\n"; }
virtual void accept (WizardActionVisitor& visitor) override { visitor.visit(this); }
};
struct CastASpell : Action {
Wizard* spellCaster;
CastASpell (Wizard* w) : spellCaster(w) { }
~CastASpell() { std::cout << "***CastASpell object destroyed.\n"; }
virtual void accept (WizardActionVisitor& visitor) override { visitor.visit(this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Action* action) const override { return action->accept(tripleDispatchVisitor, this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const CastASpell*) const override;
};
struct CastASpellButLoseInvisibility : CastASpell {
using CastASpell::CastASpell;
~CastASpellButLoseInvisibility() { std::cout << "***CastASpellButLoseInvisibility object destroyed.\n"; }
virtual void accept (WizardActionVisitor& visitor) override { visitor.visit(this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Action* action) const override { return action->accept(tripleDispatchVisitor, this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const CastASpellWithChanceOfFailure*) const override;
};
struct CastASpellWithChanceOfFailure : CastASpell {
int percentageChanceOfFailure;
CastASpellWithChanceOfFailure (Wizard* wizard, int p) : CastASpell(wizard), percentageChanceOfFailure(p) { }
~CastASpellWithChanceOfFailure() { std::cout << "***CastASpellWithChanceOfFailure object destroyed.\n"; }
virtual void accept (WizardActionVisitor& visitor) override { visitor.visit(this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Action* action) const override { return action->accept(tripleDispatchVisitor, this); }
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const CastASpell*) const override;
virtual std::shared_ptr<Option> accept (const MultiplyActionsVisitor&, const CastASpellButLoseInvisibility*) const override;
};
struct CastASpellButLoseInvisibilityWithChanceOfFailure : CastASpellWithChanceOfFailure {
using CastASpellWithChanceOfFailure::CastASpellWithChanceOfFailure;
~CastASpellButLoseInvisibilityWithChanceOfFailure() { std::cout << "***CastASpellButLoseInvisibilityWithChanceOfFailure object destroyed.\n"; }
virtual void accept (WizardActionVisitor& visitor) override { visitor.visit(this); }
};
struct UnderSleepSpellState {
LivingBeing* spellcaster;
UnderSleepSpellState (LivingBeing* s) : spellcaster(s) {}
UnderSleepSpellState() = default;
~UnderSleepSpellState() { std::cout << "***UnderSleepSpellState destroyed.\n"; }
};
struct IsInvisibleState {
~IsInvisibleState() { std::cout << "***IsInvisibleState destroyed.\n"; }
};
struct Option {
std::shared_ptr<Action> action; // I tried using std::unique_ptr, but couldn't make it compile. Using std::shared_ptr still succeeds to destroying all Option objects automatically, so this is fine.
std::string description;
Option (const std::shared_ptr<Action>& a, const std::string& d) : action(a), description(d) { }
~Option() { std::cout << "***Option object destroyed.\n"; }
};
struct Menu {
std::vector<std::shared_ptr<Option>> options;
std::vector<std::shared_ptr<Option>> optionsToMultiply;
void addOption (const std::shared_ptr<Action>& action, const std::string& description) { options.push_back(std::make_shared<Option>(action, description)); }
void addOption (const std::shared_ptr<Option>& o) { options.push_back(o); }
void addOptionToMultiply (const std::shared_ptr<Option>& o) { optionsToMultiply.push_back(o); }
// template <typename... Args> addOptionToMultiply (Args&&... args) { optionsToMultiply.push_back(std::make_shared<Option>(std::forward<Args>(args)...)); }
bool empty() const { return options.empty(); }
void display() const { for (const std::shared_ptr<Option>& o : options) std::cout << "-" << o->description << '\n'; }
void computeProductOfOptions();
};
struct LivingBeing {
struct Data {
std::string name;
int hitPoints, strength;
};
std::string name;
int hitPoints, strength;
StateMachine<CharmedState, UnderSleepSpellState, CaughtInWebSpellState, IsInvisibleState, HasNoSharpWeaponState> stateMachine; // Could use concurrent state machines (e.g. spellStateMachine, WeaponStateMachine, etc...) but then getState() will require some template metaprogramming to determine which StateMachine data member to check.
LivingBeing() = default;
LivingBeing (const Data& data) : name(data.name), hitPoints(data.hitPoints), strength(data.strength) { allBeingsPresent.emplace_back(this); }
virtual ~LivingBeing() = default;
template <typename State> State*& getState() { return stateMachine.getState<State>(); }
template <typename State> State* getState() const { return stateMachine.getState<State>(); }
template <typename State> bool isOfState() const { return stateMachine.stateIsNotNullptr<State>(); }
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;}
void followsAction (Attack* a) { std::cout << "\nACTION TAKEN: " << name << " attacks " << a->target->name << ". Hit!\n"; a->target->losesHitPoints(6); }
void followsAction (AttackWithMissileWeapon* a) { std::cout << "\nACTION TAKEN: " << name << " attacks " << a->target->name << " with a missile. Hit!\n"; a->target->losesHitPoints(2); }
void followsAction (ProtectCharmer* p) { std::cout << "\nACTION TAKEN: " << name << " moves towards " << p->charmer->name << " in order to protect him.\n"; }
void followsAction (AttackButLoseInvisibility* a) { std::cout << "\nACTION TAKEN: " << name << " attacks " << a->target->name << ", but will lose his invisibility. Hit!\n"; a->target->losesHitPoints(2); std::cout << a->target->name << " is now visible again.\n"; delete getState<IsInvisibleState>(); LivingBeing::getState<IsInvisibleState>() = nullptr; }
void followsAction (AttackWithMissileWeaponButLoseInvisibility* a) { std::cout << "\nACTION TAKEN: " << name << " attacks " << a->target->name << " with a missile weapon, but will lose his invisibility. Hit!\n"; a->target->losesHitPoints(2); std::cout << a->target->name << " is now visible again.\n"; delete getState<IsInvisibleState>(); LivingBeing::getState<IsInvisibleState>() = nullptr; }
void followsAction (BeReadyToDefendOneself*) { std::cout << "\nACTION TAKEN: " << name << " stands ready to defend himself if he gets attacked.\n"; }
void followsAction (BreakFreeFromWebWithSharpWeapon*) { std::cout << "\nACTION TAKEN: " << name << " tries to break free from the web spell using a sharp weapon. It won't take him too long.\n"; }
void followsAction (BreakFreeFromWebByHand*) { std::cout << "\nACTION TAKEN: " << name << " tries to break free from the web spell using his bare hands. He needs a strength of 18 or more and it will take him quite some time.\n"; }
void followsAction (AskForHelpToUntangleFromWeb* a) { std::cout << "\nACTION TAKEN: " << name << " asks " << a->helper->name << " to help untangle him from the web.\n"; }
// void followsAction (DoNothing*) { std::cout << "\nACTION TAKEN: " << name << " does nothing.\n"; }
bool isIncapacitated() const { return stateMachine.getState<UnderSleepSpellState>() != nullptr; } // '|| stateMachine.getState<CaughtInWebSpellState>() != nullptr;' should not be used because a wizard can still cast spells or try to free himself.
bool hasMissileWeapon() const { return true; } // Check quantity of arrows, etc.. Returning true for now for simplicity.
bool isCaughtInWeb() const { return stateMachine.getState<CaughtInWebSpellState>() != nullptr; }
virtual void doSomething();
virtual bool isInvisible() const { return stateMachine.getState<IsInvisibleState>() != nullptr; }
bool isUnderSleepSpell() const { return stateMachine.getState<UnderSleepSpellState>(); }
bool isCaughtInWebSpell() const { return stateMachine.getState<CaughtInWebSpellState>() != nullptr; }
void losesLastSharpWeapon() { stateMachine.getState<HasNoSharpWeaponState>() = new HasNoSharpWeaponState; std::cout << name << " has just lost his last sharp weapon.\n"; }
bool handleOtherState (IsInvisibleState* state, LivingBeing*, Menu& menu, LivingBeing* target) {
if (!state) {
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<Attack>(target), "Attack " + target->name + ".");
menu.addOptionToMultiply(option);
}
else {
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<AttackButLoseInvisibility>(target), "Attack " + target->name + ", but will lose his invisibility in doing so.");
menu.addOptionToMultiply(option);
}
return true;
}
bool handleOtherState (CaughtInWebSpellState* state, LivingBeing*, Menu& menu, LivingBeing* target) {
if (!state) {
// Do nothing because the default case of no special state at all is carried out in the !state case of the above handleOtherState overload (with IsInvisibleState*). The same line could be put here, and the product of options will just give the same Attack option, but why bother when the above case is always checked first according to RelevantStatesToSpellState<LivingBeing, LivingBeing::type?
}
else {
if (hasMissileWeapon()) {
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<AttackWithMissileWeapon>(target), "Attack " + target->name + " with a missile weapon (his only mode of attack since he is caught in a web).");
menu.addOptionToMultiply(option);
}
}
return true;
}
};
void ActionVisitor::visit (Attack* action) { livingBeing->followsAction(action); }
void ActionVisitor::visit (AttackWithMissileWeapon* action) { livingBeing->followsAction(action); }
void ActionVisitor::visit (ProtectCharmer* action) { livingBeing->followsAction(action); }
void ActionVisitor::visit (AttackButLoseInvisibility* action) { livingBeing->followsAction(action); }
void ActionVisitor::visit (AttackWithMissileWeaponButLoseInvisibility* action) { livingBeing->followsAction(action); }
void ActionVisitor::visit (BeReadyToDefendOneself* action) { livingBeing->followsAction(action); }
void ActionVisitor::visit (BreakFreeFromWebWithSharpWeapon* action) { livingBeing->followsAction(action); }
void ActionVisitor::visit (BreakFreeFromWebByHand* action) { livingBeing->followsAction(action); }
void ActionVisitor::visit (AskForHelpToUntangleFromWeb* action) { livingBeing->followsAction(action); }
// StatesMediator will list out all beings that an attacker can attack upon considering the states of both the attacker and the target candidates. Some states will not be spell-based, e.g. the attacker has no missile-firing capability.
struct StatesMediator {
Menu menu;
LivingBeing* subject;
StatesMediator (LivingBeing* being) : subject(being) { }
inline Menu buildMenu();
inline Menu buildWizardMenu();
};
struct CharmedVisitor;
struct CharmedOrder {
virtual void accept (CharmedVisitor&) const = 0;
virtual ~CharmedOrder() = default;
};
template <typename OrderedAction> struct Order;
using AttackOrder = Order<Attack>;
using ComeToMeToProtectMeOrder = Order<ProtectCharmer>;
template <>
struct Order<Attack> : CharmedOrder {
LivingBeing* target;
Order (LivingBeing* t) : target(t) { }
~Order() { std::cout << "***AttackOrder destroyed.\n"; }
virtual void accept (CharmedVisitor&) const override;
std::shared_ptr<Option> makeCharmedOption (const CharmedState*) const;
};
template <>
struct Order<ProtectCharmer> : CharmedOrder {
~Order() { std::cout << "***ComeToMeToProtectMeOrder destroyed.\n"; }
virtual void accept (CharmedVisitor&) const override;
std::shared_ptr<Option> makeCharmedOption (const CharmedState*) const;
};
// ***** All the necessary Action products are defined here, to be used in the 'multiplyOptions' function, itself used in Menu::computeProductOfOptions().
class MultiplyActionsVisitor { // Uses triple dispatch.
const std::shared_ptr<Option>& option1, option2;
public:
MultiplyActionsVisitor (const std::shared_ptr<Option>& o1, const std::shared_ptr<Option>& o2) : option1(o1), option2(o2) { }
template <typename T> std::shared_ptr<Option> visit (const T*, const T*) const { return productEquals<T>(); } // Any Action derived type multiplied by itself will give itself (either option 1 or option 2 could also be returned since option1->action and option2->action are of type std::shared_ptr<T>).
template <typename T, typename U> std::shared_ptr<Option> visit (const T*, const U*) const {
if constexpr (std::is_base_of<T, U>::value) return productEquals<U>(); // The derived type will always be the product
else if constexpr (std::is_base_of<U, T>::value) return productEquals<T>();
} // Maybe try using std::enable_if to remove the runtime error?
std::shared_ptr<Option> visit (const AttackWithMissileWeapon* a, const AttackButLoseInvisibility*) const {
return std::make_shared<Option>(std::make_shared<AttackWithMissileWeaponButLoseInvisibility>(a->target), "Attack " + a->target->name + " with a missle weapon, but will lose his invisibility after doing so.");
} // i.e. AttackWithMissileWeapon x AttackButLoseInvisibility = AttackWithMissileWeaponButLoseInvisibility
std::shared_ptr<Option> visit (const AttackButLoseInvisibility* a, const AttackWithMissileWeapon* b) const { return visit(b,a); } // This makes the product commutative, and is needed for all reverse products.
std::shared_ptr<Option> visit (const CastASpellButLoseInvisibility*, const CastASpellWithChanceOfFailure* b) const {
return std::make_shared<Option>(std::make_shared<CastASpellButLoseInvisibilityWithChanceOfFailure>(b->spellCaster, b->percentageChanceOfFailure), "Cast a spell but will lose his invisibility after doing so, and has a " + std::to_string(b->percentageChanceOfFailure) + "% chance of failing to do so.");
} // i.e. CastASpellButLoseInvisibility x CastASpellWithChanceOfFailure = CastASpellButLoseInvisibilityWithChanceOfFailure
std::shared_ptr<Option> visit (const CastASpellWithChanceOfFailure* a, const CastASpellButLoseInvisibility* b) const { return visit(b,a); }
// std::shared_ptr<Option> visit (const AttackWithMissileWeapon*, const Attack*) const { // This is not needed anymore, due to the derived case in the second 'visit' overload.
// return productEquals<AttackWithMissileWeapon>(); // i.e. AttackWithMissileWeapon x Attack = AttackWithMissileWeapon
// }
// std::shared_ptr<Option> visit (const Attack* a, const AttackWithMissileWeapon* b) const { return visit(b,a); } // This is needed for all reverse products to make the product commutative.
private:
template <typename Product> std::shared_ptr<Option> productEquals() const {
if (std::dynamic_pointer_cast<Product>(option1->action) != nullptr) return option1;
return option2;
}
};
std::shared_ptr<Option> Attack::accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const AttackWithMissileWeapon* derived) const { return tripleDispatchVisitor.visit(derived, this); }
std::shared_ptr<Option> AttackButLoseInvisibility::accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const AttackWithMissileWeapon* derived) const { return tripleDispatchVisitor.visit(derived, this); }
std::shared_ptr<Option> AttackWithMissileWeapon::accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const Attack* derived) const { return tripleDispatchVisitor.visit(derived, this); }
std::shared_ptr<Option> AttackWithMissileWeapon::accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const AttackButLoseInvisibility* derived) const { return tripleDispatchVisitor.visit(derived, this); }
std::shared_ptr<Option> CastASpell::accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const CastASpell* derived) const { return tripleDispatchVisitor.visit(derived, this); }
std::shared_ptr<Option> CastASpellButLoseInvisibility::accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const CastASpellWithChanceOfFailure* derived) const { return tripleDispatchVisitor.visit(derived, this); }
std::shared_ptr<Option> CastASpellWithChanceOfFailure::accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const CastASpell* derived) const { return tripleDispatchVisitor.visit(derived, this); }
std::shared_ptr<Option> CastASpellWithChanceOfFailure::accept (const MultiplyActionsVisitor& tripleDispatchVisitor, const CastASpellButLoseInvisibility* derived) const { return tripleDispatchVisitor.visit(derived, this); }
std::shared_ptr<Option> multiplyOptions (const std::shared_ptr<Option>& a, const std::shared_ptr<Option>& b) {
const MultiplyActionsVisitor tripleDispatchVisitor(a,b);
return a->action->accept(tripleDispatchVisitor, b->action.get()); // Triple dispatch needed since the runtime types of both a->action and b->action are needed.
// Below works correctly too, but without using triple dispatch a lot of crazy dynamic casting is used and the dynamic casting also has to be done twice for each pair due to two ordering possibitlies for each pair, not to mention the performance cost of so much dynamic casting.
// const std::shared_ptr<AttackWithMissileWeapon> m = std::dynamic_pointer_cast<AttackWithMissileWeapon>(a->action);
// if (m && std::dynamic_pointer_cast<AttackButLoseInvisibility>(b->action)) // But what if a and b switched places? How to make this function symmetric in its arguments?
// return std::make_shared<Option>(std::make_shared<AttackWithMissileWeaponButLoseInvisibility>(m->target), "Follow the charm order and attack " + m->target->name + " with a missle weapon, but will lose his invisibility after doing so.");
// if (m && std::dynamic_pointer_cast<Attack>(b->action))
// return a;
// if (std::dynamic_pointer_cast<Attack>(a->action) && std::dynamic_pointer_cast<AttackWithMissileWeapon>(b->action)) // The reverse ordering of parameters of the above.
// return b;
// std::cout << "Error! Multiplication case missed in multiplyOptions (std::shared_ptr<Option> a, std::shared_ptr<Option> b)!\n"; std::cin.get();
// return nullptr;
}
void Menu::computeProductOfOptions() {
if (optionsToMultiply.empty())
return;
if (optionsToMultiply.size() == 1)
addOption(optionsToMultiply.back()); // Since it is the only option possible.
else if (optionsToMultiply.size() > 1) {
std::shared_ptr<Option> option = optionsToMultiply[0];
for (std::size_t i = 1; i < optionsToMultiply.size(); ++i)
option = multiplyOptions(option, optionsToMultiply[i]); // The product of all std::shared_ptr<Option> pointers in optionsToMultiply. But the run-time type of optionsToMultiply[i]->action, for all i, need to be used, and since two at a time are needed for each consecutive product, the triple dispatch version of the Visitor Pattern is needed.
addOption(option);
}
optionsToMultiply.clear(); // Must clear, else multiplication with the next group of actions from a separate case will be done, which won't make sense. Besides, why keep them in memory anyway, once the product is found?
}
struct CharmedState {
LivingBeing *charmedVictim, *charmer;
std::unique_ptr<CharmedOrder> order = nullptr;
// bool cannotFollowOrder = false, cannotMove = false; // These don't seem to be needed.
CharmedState (LivingBeing* a, LivingBeing* b) : charmedVictim(a), charmer(b) { } //, isUnderSleepSpell(charmedVictim->isOfState<UnderSleepSpellState>()) {}
~CharmedState() { std::cout << "***CharmedState destroyed.\n"; }
void receiveOrder (std::unique_ptr<CharmedOrder>&& ord) { order = std::move(ord); }
bool hasAnOrder() const { return order != nullptr; }
// bool canFollowOrder() const { return !charmedVictim->isOfState<UnderSleepSpellState>(); }
// bool canFollowMovementOrder() const { return !charmedVictim->isOfState<UnderSleepSpellState>() && !charmedVictim->isOfState<CaughtInWebSpellState>(); }
bool handleOtherState (CaughtInWebSpellState* state, LivingBeing*, Menu& menu, const AttackOrder* order) {
if (!state) return true; // Handle the next state, if any.
std::cout << "Charmed " << charmedVictim->name << ", though caught in a web, will try to attack " << order->target->name << " with a missile weapon (if he has one).\n";
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<AttackWithMissileWeapon>(order->target), "Follow " + charmer->name + "'s charm order and attack " + order->target->name + " with a missile weapon (if he has one).");
menu.addOptionToMultiply(option);
// cannotMove = true;
return true; // Handle the next state, if any.
}
bool handleOtherState (CaughtInWebSpellState* state, LivingBeing*, Menu&, const ComeToMeToProtectMeOrder*) {
if (!state) return true;
std::cout << "Charmed " << charmedVictim->name << " is caught in a web, and cannot go to " << charmer->name << " to protect him, despite his order to.\n"; // Don't add any new option to 'menu'.
// cannotMove = true;
return false; // Do not handle the next state, because the order cannot be followed.
}
bool handleOtherState (IsInvisibleState* state, LivingBeing*, Menu& menu, const AttackOrder* order) {
if (!state) return true;
std::cout << "Charmed " << charmedVictim->name << ", will attack " << order->target->name << ", but will lose his invisibility after doing so.\n";
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<AttackButLoseInvisibility>(order->target), "Follow " + charmer->name + "'s charm order and attack " + order->target->name + ", but will lose his invisibility after doing so.");
menu.addOptionToMultiply(option);
return true; // Handle the next state, if any.
}
// template <typename CharmedOrder> bool handleOtherState (std::nullptr_t, LivingBeing*, Menu&, const CharmedOrder*) { std::cout << "handleOtherState (const CharmedOrder&, std::nullptr_t) called.\n"; return true; } // No restrictions otherwise, but this is never called. Hence the need for 'if (!state) return;' in all the above overloads.
// template <typename...> struct HandleOtherRelevantStates; // This is not needed anymore, as the universal struct HandleOtherRelevantStates is now instead used for all possible state classes (else this recursive class would have to be defined for all spell state classes).
};
std::shared_ptr<Option> Order<Attack>::makeCharmedOption (const CharmedState* charmedState) const { return std::make_shared<Option>(std::make_shared<Attack>(target), "Follow " + charmedState->charmer->name + "'s charm spell attack order and and give protection to " + charmedState->charmer->name + "."); }
std::shared_ptr<Option> Order<ProtectCharmer>::makeCharmedOption (const CharmedState* charmedState) const { return std::make_shared<Option>(std::make_shared<ProtectCharmer>(charmedState->charmer), "Follow " + charmedState->charmer->name + "'s charm spell attack order and and give protection to " + charmedState->charmer->name + "."); }
struct CaughtInWebSpellState {
struct AskForHelpToUntangleFromWebTag { };
~CaughtInWebSpellState() { std::cout << "***CaughtInWebSpellState destroyed.\n"; }
bool handleOtherState (HasNoSharpWeaponState* state, LivingBeing* stateOwner, Menu& menu) { //, LivingBeing* stateOwner, Menu& menu) {
if (!state) {
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<BreakFreeFromWebWithSharpWeapon>(), "Try to break free from the web using a sharp weapon.");
menu.addOptionToMultiply(option);
}
else {
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<BreakFreeFromWebByHand>(), "Without any sharp weapon, " + stateOwner->name + " can only try to break free from the web using his sheer strength.");
menu.addOptionToMultiply(option);
}
return true; // Handle the next state, if any.
}
bool handleOtherState (IsInvisibleState* state, LivingBeing*, Menu& menu, LivingBeing* being, CaughtInWebSpellState::AskForHelpToUntangleFromWebTag&&) { // AskForHelpToUntangleFromWebTag is tag dispatching, to indicate what is being done with 'being' (other possibilities could be talking to, etc...) and thereby create separate "handleOtherState' overloads for each of these cases.
if (!state) {
std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<AskForHelpToUntangleFromWeb>(being), "Ask " + being->name + " for help to free him from the web.");
menu.addOptionToMultiply(option);
}
else {
std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<AskForHelpToUntangleFromWeb>(being), "Ask " + being->name + " for help to free him from the web, even though he cannot be seen.");
menu.addOptionToMultiply(option);
}
return true;
}
};
struct CharmedVisitor {
CharmedState* charmedState;
Menu& menu;
CharmedVisitor (CharmedState* c, Menu& m) : charmedState(c), menu(m) { }
template <typename CharmSpellOrder> void visit (const CharmSpellOrder*);
};
//// Templating the above for other spell states (but not currently being used):
//template <typename SpellState>
//struct SpellStateVisitor {
// SpellState* spellState;
// LivingBeing* beingUnderSpellState;
// Menu& menu;
// SpellStateVisitor (SpellState* c, LivingBeing* being, Menu& m) : spellState(c), beingUnderSpellState(being), menu(m) { }
// template <typename Arg>
// void visit (Arg&& arg) { typename RelevantStatesToHandle<SpellState, Arg>::type()(spellState, beingUnderSpellState, menu, std::forward<Arg>(arg)); }
//};
void AttackOrder::accept (CharmedVisitor& visitor) const { visitor.visit(this); }
void ComeToMeToProtectMeOrder::accept (CharmedVisitor& visitor) const { visitor.visit(this); }
struct Monster : virtual LivingBeing {
using LivingBeing::LivingBeing;
Monster (const Data& data) : LivingBeing(data) { }
virtual ~Monster() {std::cout << name << " destroyed.\n";}
};
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 CharacterClass : virtual LivingBeing {
using LivingBeing::LivingBeing;
};
struct Fighter : CharacterClass {
using CharacterClass::CharacterClass;
};
enum SpellType {Charm, Sleep, Invisibility, NumSpellTypes};
template <int N> struct WizardState;
using CharmerState = WizardState<Charm>;
using SleepSpellCastedState = WizardState<Sleep>;
using InvisibleState = WizardState<Invisibility>; // This is separate from the general IsInvisibleState for any general LivingBeing (e.g. invisible from a potion), and is needed because if the Wizard dies, this spell must end automatically through the recursive checkSpellState.
template <int Duration>
struct DurationalSpell {
std::unordered_map<LivingBeing*, int> spellVictims; // The int component is the duration before the charm spell wears off on the LivingBeing* (the first component). Using the Observer Pattern, this Charmer state (or whatever) object can be
// registerd to some RoundKeeper observer class, that will reduce all the mapped values by 1 every round, and when the value reaches 0, removeCharmedVictim will be called on that corresponding LivingBeing*, who will then have its CharmedState deleted.
// Once all the spellVictims have been removed this way, then this state itself is deleted. But this implementation is not central to the theme being illustrated here.
DurationalSpell (LivingBeing* c) { spellVictims.emplace(c, Duration); }
void addVictim (LivingBeing* v) { spellVictims.emplace(v, Duration); }
void removeVictim (LivingBeing* v) { spellVictims.erase(v); }
};
template <>
struct WizardState<Sleep> : DurationalSpell<8> { using DurationalSpell::DurationalSpell; };
template <>
struct WizardState<Invisibility> { }; // Lasts permanently until you attack.
struct Wizard : CharacterClass {
struct CastingSpellTag {};
StateMachine<CharmerState, SleepSpellCastedState, InvisibleState> wizardStateMachine;
// using CharacterClass::CharacterClass;
Wizard (const Data& data) : LivingBeing(data), CharacterClass(data) { }
inline bool handleOtherState (IsInvisibleState*, LivingBeing*, Menu&, CastingSpellTag&&);
inline bool handleOtherState (CaughtInWebSpellState*, LivingBeing*, Menu&, CastingSpellTag&&);
void castsCharmSpell (LivingBeing*);
void castsSleepSpell (LivingBeing*);
void castsInvisibilitySpell();
void castsWebSpell (LivingBeing*) const;
void orderCharmedVictimToAttack (LivingBeing*, LivingBeing*);
void orderCharmedVictimToProtectHim (LivingBeing*);
virtual bool isInvisible() const { return wizardStateMachine.getState<InvisibleState>() != nullptr; }
virtual void doSomething() override;
void followsAction (GiveCharmOrderToProtectHim* charmOrder) {
std::cout << "\nACTION TAKEN: " << name << " tells charmed victim " << charmOrder->charmedVictim->name << " to protect him.\n";
orderCharmedVictimToProtectHim(charmOrder->charmedVictim);
}
void followsAction (GiveCharmOrderToAttackSomeone* charmOrder){
std::cout << "\nACTION TAKEN: " << name << " tells charmed victim " << charmOrder->charmedVictim->name << " to attack someone.\n";
std::cout << "Who does you want " << name << " want " << charmOrder->charmedVictim->name << " to attack? (will choose allBeingsPresent.front() for now)\n";
orderCharmedVictimToAttack(charmOrder->charmedVictim, *std::next(allBeingsPresent.begin()));
}
void followsAction (CastASpell*) { std::cout << "\nACTION TAKEN: " << name << " will cast a spell. Choose a spell from this list of spells of his:\n"; }
void followsAction (CastASpellButLoseInvisibility*) { std::cout << "\nACTION TAKEN: " << name << " will cast a spell, but is now visible again. Choose a spell from this list of spells of his:\n"; delete getState<IsInvisibleState>(); LivingBeing::getState<IsInvisibleState>() = nullptr; }
void followsAction (CastASpellWithChanceOfFailure* c) { std::cout << "\nACTION TAKEN: " << name << " will try to cast a spell. "; if (std::rand() % 100 < c->percentageChanceOfFailure) std::cout << "But has failed to do so.\n"; else std::cout << "Choose a spell from this list of spells of his:\n"; }
void followsAction (CastASpellButLoseInvisibilityWithChanceOfFailure* c) {
std::cout << "\nACTION TAKEN: " << name << " will try to cast a spell, and will lose his invisibility if he succeeds. ";
if (std::rand() % 100 < c->percentageChanceOfFailure) std::cout << "But has failed to do so.\n";
else { std::cout << "He has suceeded, but is now visible again. Choose a spell from this list of spells of his:\n"; delete getState<IsInvisibleState>(); LivingBeing::getState<IsInvisibleState>() = nullptr; }
}
};
void WizardActionVisitor::visit (GiveCharmOrderToProtectHim* action) { wizard->followsAction(action); }
void WizardActionVisitor::visit (GiveCharmOrderToAttackSomeone* action) { wizard->followsAction(action); }
void WizardActionVisitor::visit (CastASpell* action) { wizard->followsAction(action); }
void WizardActionVisitor::visit (CastASpellButLoseInvisibility* action) { wizard->followsAction(action); }
void WizardActionVisitor::visit (CastASpellWithChanceOfFailure* action) { wizard->followsAction(action); }
void WizardActionVisitor::visit (CastASpellButLoseInvisibilityWithChanceOfFailure* action) { wizard->followsAction(action); }
template <> // CharmerState
struct WizardState<Charm> : DurationalSpell<10> {
struct GiveCharmOrderToProtectHimTag { };
struct GiveCharmOrderToAttackTag { };
using DurationalSpell::DurationalSpell;
void orderCharmedVictimToAttack (LivingBeing* charmedVictim, std::unique_ptr<AttackOrder>&& attackOrder) { charmedVictim->getState<CharmedState>()->receiveOrder(std::move(attackOrder)); }
void orderCharmedVictimToProtectHim (LivingBeing* charmedVictim, std::unique_ptr<ComeToMeToProtectMeOrder>&& order) { charmedVictim->getState<CharmedState>()->receiveOrder(std::move(order)); }
bool handleOtherState (CaughtInWebSpellState* state, LivingBeing* wizard, Menu& menu, LivingBeing* charmedVictim, GiveCharmOrderToProtectHimTag&&) {
if (!state || (state && std::rand() % 2 == 0)) { // 50% chance that 'charmedVictim' does not comprehend or hear the Wizard's charm command because the Wizard is caught in a web.
std::string description = "Get charmed " + charmedVictim->name + " to protect him";
if (charmedVictim->isCaughtInWebSpell())
description += " (but he needs to be extricated first, as he is currently caught in the Web Spell)";
if (charmedVictim->isUnderSleepSpell())
description += " (but he has to be waken from the sleep spell first)";
description += ".\n";
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<GiveCharmOrderToProtectHim>(charmedVictim), description);
menu.addOptionToMultiply(option);
}
else
std::cout << wizard->name << " cannot give a command to charmed " << charmedVictim << " to protect him because he is caught in a web and can't be heard properly.\n";
return true; // Handle the next state, if any.
}
bool handleOtherState (CaughtInWebSpellState* state, LivingBeing* wizard, Menu& menu, LivingBeing* charmedVictim, GiveCharmOrderToAttackTag&&) {
if (!state || (state && std::rand() % 2 == 0)) { // 50% chance that 'charmedVictim' does not comprehend or hear the Wizard's charm command because the Wizard is caught in a web.
std::string description = "Get charmed " + charmedVictim->name + " to attack someone (submenu to follow for whom to attack)";
if (charmedVictim->isCaughtInWebSpell())
description += " (but he can only attack with missile weapons, as he is currently caught in the Web Spell).\n";
if (charmedVictim->isUnderSleepSpell())
description += " (but he has to be waken from the sleep spell first)";
if (charmedVictim->isInvisible())
description += " (but once he attacks, his invisibility state will end)";
description += ".\n";
const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<GiveCharmOrderToAttackSomeone>(charmedVictim), description);
menu.addOptionToMultiply(option);
}
else
std::cout << wizard->name << " cannot give a command to charmed " << charmedVictim << " to attack anyone because he is caught in a web and can't be heard properly.\n";
return true; // Handle the next state, if any.
}
};
template <typename, typename...> struct HandleOtherRelevantStates;
// Will try using C++17 fold expressions later (but all the below overloads make it unlikely to work).
template <typename Handler>
struct HandleOtherRelevantStates<Handler> {
template <typename... Args>
void operator()(Handler*, LivingBeing*, Menu& menu, Args&&...) const {
std::cout << "End of general recursion of HandleOtherRelevantStates<Handler>::operator().\n"; show(menu.optionsToMultiply.size()) //////
menu.computeProductOfOptions(); } // End of general recursion.
// This overload with CharmSpellOrder template type covers the commented-out overloads below with specialized CharmOrder derived pointers.
template <typename CharmSpellOrder>
void operator()(Handler* charmedState, LivingBeing*, Menu& menu, const CharmSpellOrder* order) const {
menu.addOptionToMultiply(order->makeCharmedOption(charmedState)); // The Action type in the first argument of the Option constructor is std::shared_ptr<T>, where CharmSpellOrder is Order<T>.
menu.computeProductOfOptions();
}
// void operator()(Handler* charmedState, LivingBeing*, Menu& menu, const ComeToMeToProtectMeOrder*) const {
// const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<ProtectCharmer>(charmedState->charmer), "Follow " + charmedState->charmer->name + "'s protect-me charm spell order and give protection to " + charmedState->charmer->name + ".");
// menu.addOptionToMultiply(option);
// menu.computeProductOfOptions();
// }
// void operator()(Handler* charmedState, LivingBeing*, Menu& menu, const AttackOrder* order) const {
// const std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<Attack>(order->target), "Follow " + charmedState->charmer->name + "'s charm spell attack order and attack " + order->target->name + ".");
// menu.addOptionToMultiply(option);
// menu.computeProductOfOptions();
// }
};
template <typename Handler, typename State, typename... OtherStates>
struct HandleOtherRelevantStates<Handler, State, OtherStates...> : HandleOtherRelevantStates<Handler, OtherStates...> {
template <typename... Args>
void operator()(Handler* handler, LivingBeing* stateOwner, Menu& menu, Args&&... args) const {
const bool handleNextState = handler->handleOtherState(stateOwner->template getState<State>(), stateOwner, menu, std::forward<Args>(args)...);
if (handleNextState)
HandleOtherRelevantStates<Handler, OtherStates...>::operator()(handler, stateOwner, menu, std::forward<Args>(args)...);
}
};
// ***** This is where we declare all the states that can affect whatever spell state. Whenever defining new spells (and hence new spell states), we update this (do this first!), and then compling errors will tell us where else to add new overloads, etc... with these new states.
template <typename Handler, typename... StatesRelevantToHandler> struct RelevantStatesToHandle;
template <> struct RelevantStatesToHandle<LivingBeing, LivingBeing> {
using type = HandleOtherRelevantStates<LivingBeing, IsInvisibleState, CaughtInWebSpellState>; // States relevent to making general attacks.
};
template <> struct RelevantStatesToHandle<CharmedState, AttackOrder> {
using type = HandleOtherRelevantStates<CharmedState, CaughtInWebSpellState, IsInvisibleState>;
};
template <> struct RelevantStatesToHandle<CharmedState, ComeToMeToProtectMeOrder> {
using type = HandleOtherRelevantStates<CharmedState, CaughtInWebSpellState>;
};
template <> struct RelevantStatesToHandle<CaughtInWebSpellState> {
using type = HandleOtherRelevantStates<CaughtInWebSpellState, HasNoSharpWeaponState>; // CharmedState need not be included because it has been used above with CaughtInWebSpellState already.
};
template <> struct RelevantStatesToHandle<CaughtInWebSpellState, LivingBeing, CaughtInWebSpellState::AskForHelpToUntangleFromWebTag> {
using type = HandleOtherRelevantStates<CaughtInWebSpellState, IsInvisibleState>; // With the current design, if there is no other state relevant to CaughtInWebSpellState when asking for help to untangle from the web, then just put in any one state, and have the CaughtInWebSpellState::handleOtherState method output the same thing whether that state is nullptr or not. But at least one state must be put in.
};
template <> struct RelevantStatesToHandle<CharmerState, LivingBeing, CharmerState::GiveCharmOrderToProtectHimTag> {
using type = HandleOtherRelevantStates<CharmerState, CaughtInWebSpellState>;
};
template <> struct RelevantStatesToHandle<CharmerState, LivingBeing, CharmerState::GiveCharmOrderToAttackTag> {
using type = HandleOtherRelevantStates<CharmerState, CaughtInWebSpellState>;
};
template <> struct RelevantStatesToHandle<Wizard, LivingBeing, Wizard::CastingSpellTag> {
using type = HandleOtherRelevantStates<Wizard, IsInvisibleState, CaughtInWebSpellState>; // States relevent to general spellcasting.
};
struct Goblin : Monster {
Goblin (const LivingBeing::Data& data) : LivingBeing(data), Monster(data) { }
};
struct GoblinWizard : Goblin, Wizard {
GoblinWizard (const LivingBeing::Data& data) : LivingBeing(data), Goblin(data), Wizard(data) { std::cout << "GoblinWizard constructor called.\n"; }
};
void Wizard::orderCharmedVictimToAttack (LivingBeing* charmedVictim, LivingBeing* target) {
std::cout << name << " has ordered " << charmedVictim->name << " to attack " << target->name << ".\n";
wizardStateMachine.getState<CharmerState>()->orderCharmedVictimToAttack(charmedVictim, std::make_unique<AttackOrder>(target));
}
void Wizard::orderCharmedVictimToProtectHim (LivingBeing* charmedVictim) {
std::cout << name << " has ordered " << charmedVictim->name << " to go to him to protect him.\n";
wizardStateMachine.getState<CharmerState>()->orderCharmedVictimToProtectHim(charmedVictim, std::make_unique<ComeToMeToProtectMeOrder>());
}
template <typename CharmSpellOrder>
void CharmedVisitor::visit (const CharmSpellOrder* order) {
typename RelevantStatesToHandle<CharmedState, CharmSpellOrder>::type()(charmedState, charmedState->charmedVictim, menu, order);
}
inline Menu StatesMediator::buildMenu() {
if (subject->isIncapacitated())
return menu; // A menu empty of choices since 'subject' is not able to do anything of his choice.
CharmedState* charmedState = subject->getState<CharmedState>();
if (charmedState) { // (Non-attacking) options relevent to being under the Charm spell. If there is a charm order, all other menu options will not be outputted, including attacking (unless the charm order is to attack someone).
if (charmedState->hasAnOrder()) {
std::unique_ptr<CharmedOrder> order = std::move(charmedState->order);
CharmedVisitor visitor(charmedState, menu); // Pass menu and let it be updated through all the overloads.
order->accept(visitor); // The run-time type of 'order' must be extracted using the Visitor Pattern.
if (!menu.empty()) // Must not return 'menu' automatically. What if 'subject' is not able to follow the charm order (e.g. move somewhere, but he is caught in the Web spell), in which case the charm order would not be placed in 'menu' to begin with?
return menu;
}
else {
std::cout << '\n' << subject->name << " is under the Charm spell, but doesn't have any order from his charmer " << charmedState->charmer->name << ", and thus will not do anything special.\n";
menu.addOption(std::make_shared<BeReadyToDefendOneself>(), "Being under the Charm spell but with no order from his charmer " + charmedState->charmer->name + ", " + subject->name + " will not do anything special.");
}
}
CaughtInWebSpellState* caughtInWebSpellState = subject->getState<CaughtInWebSpellState>();
if (caughtInWebSpellState) { // (Non-attacking) options relevent to being caught in the Web spell.
typename RelevantStatesToHandle<CaughtInWebSpellState>::type()(caughtInWebSpellState, subject, menu);
if (subject->hasMissileWeapon()) {
// for (LivingBeing* target : allBeingsPresent)
// typename RelevantStatesToHandle<CaughtInWebSpellState, LivingBeing>::type()(caughtInWebSpellState, subject, menu, target); // Attacking options
for (LivingBeing* being : allBeingsPresent)
typename RelevantStatesToHandle<CaughtInWebSpellState, LivingBeing, CaughtInWebSpellState::AskForHelpToUntangleFromWebTag>::type()(caughtInWebSpellState, subject, menu, being, CaughtInWebSpellState::AskForHelpToUntangleFromWebTag()); // Ask for help to free him from the web.
}
}
//... Other general states to consider that may affect non-attacking actions.
// Attacking options
for (LivingBeing* target : allBeingsPresent) {
if (target == subject)
continue;
typename RelevantStatesToHandle<LivingBeing, LivingBeing>::type()(subject, subject, menu, target);
}
}
Goblin
struct doesn't look like it compiles. It only inherits fromMonster
, but the constructor also attempts to initializeLivingBeing
, which isn't a direct base. \$\endgroup\$