4

The Situation: I'm trying to implement two classes, one which is called 'special'. special has a member variable bool conditions and a method perform_special. The other class is named manager and it has as a member variable of type special. I want manager to call perform_special on its special member only if condition is true.

So far I have implemented this chunk of code:

#include<iostream>
using namespace std;

class special{
  public:
    special(){};
    void perform_special();
    void set_conditions( bool cond );
    bool get_conditions();
  private:
    bool conditions;
};
void special::perform_special(){ cout<<"special performed."<<endl; }
void special::set_conditions( bool cond ){ conditions = cond; }
bool special::get_conditions(){ return conditions; }

class manager{
  public:
    manager(){};
    void check_specials();
    void set_effect( special* eff );
  private:
    special* effect;
};
void manager::check_specials(){
  if(effect->get_conditions()){ effect->perform_special(); }
}
void manager::set_effect( special *eff ){ effect = eff; }

int main(){
  int a=3; int b=2;
  bool cond = a<b;
  special effect1;
  effect1.set_conditions( cond );

  a=2; b=3;
  manager manager1;
  manager1.set_effect( &effect1 );
  manager1.check_specials();

  return 0;
}

This is what it does: It creates the boolean cond which is immediately evaluated to be false because at this point, a<b is false. Now it gives this condition to a special variable which is again given to a manager variable. When the manager calls check_special on its special variable, nothing happens because cond is false.

here's my problem: I don't want cond to be evaluated immediately. As you can see in the code, the values of a and be may change and thus the value of the expression a<b. How can I achieve that the value of cond is only evaluated when the function check_specials() is called and using the latest values of the variables a and b?

Background: I'm trying to implement a text-based adventure-game, so 'special' would be some kind of special effect that happens if a bunch of conditions is true. 'manager' would be some kind of master class which handles the game and holds all the special effects etc. I want to declare those special effects in the main function and pass them to a variable 'game' of type manager and then start the routine of the game so I need manager to evaluate the conditional expressions for the special effects dynamically as the conditions obviously change over time.

rolly
  • 145
  • 9
  • 1
    Have a look at "actors". Boost phoenix and spirit put this kind of thing to good use. – Bathsheba Jan 31 '18 at 16:17
  • 1
    Try to stay away from `manager` and `master` classes, they tend to produce bad code. The easiest solution is probably to put required data into the appropriate places, so `manager` needs `a` and `b`, so it should have them as members instead of putting them inside `main`. This might solve all problems of this type. – nwp Jan 31 '18 at 16:21
  • You technically can use lambdas for later evaluation, but you will get into object lifetime troubles because you need to guarantee that the things you want to evaluate still exist when you evaluate them, which can get quite tricky. – nwp Jan 31 '18 at 16:23
  • "condition" is not a bool, it's a "closure" - something that keeps reference to some (possibly external) state and returns bool value that depends on that state. Depending on the version of C++ you're using and on the third-party libraries, there are different ways to implement this pattern. –  Jan 31 '18 at 16:24
  • @nwp why is this considered bad code? In my implementation of the actual game I intended to make the manager have all kind of stuff including a and b in the example. But somehow I need to pass the conditional aget_location() == room1 ); game.add_special( &ghost );` . This way I could very easy add and remove special effects without having to worry about the logic of the rest of the program. – rolly Jan 31 '18 at 16:30
  • 2
    It is considered bad because of "intended to make the manager have all kind of stuff including a and b in the example". Your classes should generally have exactly one responsibility and not do everything. See [god object](https://en.wikipedia.org/wiki/God_object). For games specifically look up "Entity component system". You really do not want a `special ghost("a ghost appears!");`, instead you probably want a `std::vector the_monsters;`. – nwp Jan 31 '18 at 16:36
  • @nwp ok, I get the problem with the master class... Concerning the ghost, in order to fill the `vector monsters` I still need to declare `special ghost("a ghost appears!")` at some point? What routine would you suggest to check if there are specals that need to happen? Manually adding a loop that goes through all elements in `the_monsters` without defining a class that handles these kinds of loops? – rolly Jan 31 '18 at 17:27
  • It depends a lot on context and is a bit difficult to describe. Without context you probably want some kind of even handler that handles clicks that cause actions like `if (input == Keys::forward) { move_forward(); }`. The implementation of `move_forward` does something like `if (collided_with_wall()) { play_oomph_sound(); return; } if (random_chance(42)) { monsters = create_random_encounter(player_level, area);}` and then implement `create_random_encounter` with something like `Monster_types types[] = {"Ghost", "Goblin", "Grue"}; return Monster{ types[random_int(0, 2)]};` and so on. – nwp Jan 31 '18 at 18:03
  • Adding more monsters to my above code is relatively easy, but adding a ghoul when you must write `special ghoul("a ghoul appears!");` somewhere isn't. Try to keep every function simple and only do what the name says and you should end up with a reasonable design. Also see [this chat](https://chat.stackoverflow.com/rooms/116940/c-questions-and-answers) for C++ questions and [this chat](https://chat.stackexchange.com/rooms/19/game-development) for game design questions. – nwp Jan 31 '18 at 18:06
  • @nwp thank you very much! So do I get this right: you state it would be easier to write a function that creates monsters instead of declaring them beforehand? PS: actually the game is text-based, so i won't have any `play_oomph_sound()` ;) The game having no other interactions than `cout` and `cin` with the player makes the routine step-by-step like, nothing needs to happen simultaneously, that's why i thought it would be nice to have an instance that checks for every new step if there is some predefined special thing to happen or not. – rolly Jan 31 '18 at 18:25

2 Answers2

2

You could build a Cond class, prototyped with:

struct Cond
{
    int& a;
    int& b;
    operator bool() const {return a < b;}
};

which you'd instantiate with

Cond cond{a, b};

in place of your current declaration of cond.

You'd then write if (cond) at the point of testing, which would use the current values of a and b via the references.

That's the bear bones of this lazy-evaluation technique, but it's just the start. The main pitfall is the potential for dangling references, perhaps solve with std::reference_wrapper.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 1
    Why can't defaulted copy constructor be relied on? – xskxzr Jan 31 '18 at 17:24
  • my problem with this solution is that the condition is fixed to be a comparison of two values. I the context of the game, I need to be able to pass arbitrary conditions – rolly Jan 31 '18 at 17:51
  • @xskxzr No reason - I was getting confused with the case where member variable references are set to other member variables: not the case here so no issue. Thanks for pointing out. – Bathsheba Jan 31 '18 at 18:47
1

You pass variable of type bool to method cond() and variable can only hold value. What you need to pass is a function, that returns bool:

class special{
  public:
      using predicate = std::function<bool()>;

      void set_conditions( predicate p );
      ...
};

now you can pass a function, functor, lambda etc, which will be evaluated later:

int main()
{
  int a=3; int b=2;
  auto cond = [&]{ return a<b; };
  special effect1;
  effect1.set_conditions( cond );
  ...
}
Slava
  • 43,454
  • 1
  • 47
  • 90
  • could you explain what the [&] means? Does this make `cond` a pointer to the function `return a – rolly Jan 31 '18 at 16:37
  • It is a lambda, that captures all local variables by reference (as you need to keep track of `a` and `b` values). Of course you need to be careful that lambda does not outlive that variables. I make that lambda capture everything for simplicity, you may explicitly write that you capture `a` and `b` – Slava Jan 31 '18 at 16:39
  • The key is catch-by-reference. I think you better highlight it. – xskxzr Jan 31 '18 at 17:16
  • I'm afraid I don't understand the syntax... I get errors `function does not name a type` and same for predicate. – rolly Jan 31 '18 at 17:44