0
\$\begingroup\$

For a long time Robert Martin has been advising against if/else/switch branching logic and instead rely on the polymorphic behavior of different types. if/else/switch should only be used in the factory creating the types with different behaviours. He describes the approach in a recent blog post. I feel I understand the thrust of his argument as applied in his example situation and I really like the concept as it pushes decision making to a single location, DRY & OCP.

However, I'm finding it difficult to apply the principle is some basic game dev situations.

e.g. consider this pseudo-code controlling a ball bouncing around a box (think 'Breakout')

void ball::update(){
  if(collideswith(leftWall))
    velocity.x= +speed;
  else if(collideswith(rightWall))
    velocity.x = -speed;
  else if(collideswith(topWall))
    velocity.y = -speed;
  else if(collideswith(bottomWall))
    velocity.x = +speed;
}

I am unsure what kind of polymorphic class I could build to replace the branching logic here.

Replacing the above code with an Uncle Bob solution might be over-engineering, but am interested in learning more about applying the principle in these kinds of contexts.

\$\endgroup\$
6
  • \$\begingroup\$ ... just keep in mind that gamedevs tend to reduce the amount of polymorphic classes as much as possible because of the overhead they add. This is debatable though, as one should focus on where it counts (i.e. don't optimize too early). \$\endgroup\$
    – Vaillancourt
    Mar 10 at 13:02
  • \$\begingroup\$ @Vaillancourt agreed, but it's good to understand the options before making a decision \$\endgroup\$
    – Ken
    Mar 10 at 13:12
  • \$\begingroup\$ This reads a little to close to “______ sucks, am I right?” (What types of questions should I avoid asking?). I agree on not doing OO polymorphic patterns here. Too much overhead, little gain. Although generalization could be desirable. On the linked article Uncle Bob worries that some other part of the code will be "making policy decisions" - which is more branching - based on return value of this branching code… Well, in that case better make strategies, and that would make this the factory method. It is an entirely different situation here. \$\endgroup\$
    – Theraot
    Mar 10 at 13:44
  • \$\begingroup\$ @Theraot my intention was to write a question like"___ is a great idea, how can I apply it more widely" \$\endgroup\$
    – Ken
    Mar 10 at 15:05
  • \$\begingroup\$ And then you get it closed for being too broad :P - Philipp answer applies for the case at hand. However, let us say it is not collision detection and response, but, for example, is_key_pressed and whatever input handling logic. It would need a different approach. There could still be some generalization, such as key-callback pairs, I guess. \$\endgroup\$
    – Theraot
    Mar 10 at 15:10
1
\$\begingroup\$

I'll just cover the is_key_pressed I proposed in comments.

You could have a list of input handlers strategies. It would be a list of key-value pairs. Where they key is… well, a key, that could be pressed. And the value is what happens when the key is pressed.

You would have the type of the values be IInputStrategy (or IHandleInput using lolcat interface name convention). And you have multiple implementations, such as JumpStrategy, RunStrategy, ShootStrategy, and so on. Then you iterate over the list, for each one check if it the key is pressed, and call Execute on the value. Like this:

for ((key, strategy): strategies))
{
    if (is_key_pressed(key))
    {
        strategy.Execute(avatar);
    }
}

This has the advantage that we can easily add commands and change their binding in runtime. For example to configure the input we can just modify the key. Perhaps we can add or replace elements when the player unlocks a power up, or something like that. It also has the advantage of keeping the code for each command separate.

We could also wrap the calls to is_key_pressed in other objects. That could allow to configure inputs not based on a single key press. Then we can rewrite the code like this:

for (IInputStragy strategy: strategies))
{
    strategy.Execute(avatar);
}

That looks like a suitable replacement for a series of if statements. However, if we have a switch, we want to stop at the first one. So let us have Execute return bool:

for (IInputStragy strategy: strategies))
{
    if (strategy.Execute(avatar))
    {
        break;
    }
}

Well, that is not entirely disimilar to sequence and selector in behavior trees. That could be the path forward to continue to evolve this solution.

\$\endgroup\$
1
\$\begingroup\$

The OOP way to do this, would be to give the class wall a method handleCollision(ball) (although instead of ball, you might want to use the base-class of ball which represents any object which has a velocity and can bounce off of things).

And because you have both horizontal walls and vertical walls which do different things when a ball collides with them, you would create two classes inheriting from wall, namely verticalWall and horizontalWall. These classes would provide different implementation for handleCollision. One of them doing ball.velocity.x *= -1; and the other ball.velocity.y *= -1. leftWall and rightWall would be of type verticalWall and topWall and bottomWall would be of type horizontalWall.

You likely don't just want to limit yourself to 4 walls in your game but allow any number of walls, so it might be better to store those walls in an array. That allows you to write code like that:

for (wall w: walls) {
    if (collidesWith(w) {
        w.handleCollision(this);
    }
}

The array walls can contain any mix of horizontal and vertical walls, and the wonder of polymorphy will make sure that the correct handleCollision method is called for each of them within this loop.

\$\endgroup\$
6
  • \$\begingroup\$ I get what you are saying, but now wall depends on ball. \$\endgroup\$
    – Ken
    Mar 10 at 15:12
  • 1
    \$\begingroup\$ @Ken you could just have "physic objects", some are static, other aren't, and have a general collision resolution code. \$\endgroup\$
    – Theraot
    Mar 10 at 15:17
  • \$\begingroup\$ @Ken Why does that bother you? Yes, dependency should be avoided if possible, but creating an OOP design where nothing depends on nothing is not always possible and rarely desirable. But as I wrote in the first paragraph, you likely want that method to take the base class of any object which is supposed to bounce off of walls. \$\endgroup\$
    – Philipp
    Mar 10 at 15:20
  • \$\begingroup\$ @Philip You are correct. The only reason it "bothered" me was I was looking for/expect a solution that used the polymorphism discussed in the question. TBH I'm trying to address a gap in my understanding of Uncle Bobs approach. \$\endgroup\$
    – Ken
    Mar 10 at 15:28
  • \$\begingroup\$ Would pushing this to the extreme result in a component based architecture? \$\endgroup\$
    – Vaillancourt
    Mar 10 at 15:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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