0

I have a game with many types of turret, each type can simply denoted by enum.

Duplicate-code problem arise when there are subcategories of turret.

I currently code like this :-

switch(type_of_turret){
    //category : SHOOTER
    case SHOOTER_FIRE :
    case SHOOTER_ICE :
    {
        float temperator=0;
        switch(type_of_turret){               //duplicate switch case
            case SHOOTER_FIRE : temperator=1000;     break;
            case SHOOTER_ICE :  temperator=-1000;   break;
        }
        temperator=sinWave(temperator,time); //(Edit) an example of calculation
        shoot(rigidBody,temperator);
    }break;
    //category : PROPULSION
    ......
    break;
}

There are not so many types of type_of_turret (10 to 20), but the duplicating already looks bad for me.

If I can declare variable inside the switch (but not inside case), it would be nice :-

switch(type_of_turret){
    float temperator=0;                    //neat but wrong syntax
    case SHOOTER_FIRE : temperator=1000;   /*break?*/  //neat 
    case SHOOTER_ICE : temperator=-1000;   /*break?*/  //neat
        temperator=sinWave(temperator,time);  //(Edit) just an example 
        shoot(rigidBody,temperator);       //neat
    break;
    ....
    break;
}

Question

How to make the code more neat?
Specifically, how can I imitate "the declaration variable inside switch-case" behavior?

My poor solution

Create class for each category, e.g. Shooter and Propulsion derived from Turret.
Then create subcategory of Shooter using enum or more derive classes.
Disadvantage: introduces unnecessary complexity, and performance may decrease (virtual function)

Edit (Another disadvantage): It breaks "no-method religious" of my Entity–Component–System as in the following quote :-

you’ll see that the methods for acting upon Components should all live inside the Systems, not the Components

http://t-machine.org/index.php/2007/12/22/entity-systems-are-the-future-of-mmog-development-part-3/

Reference

There are some similar questions on stackoverflow, e.g. Why can't variables be declared in a switch statement?, but none discuss what alternative approaches are.

Community
  • 1
  • 1
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • @Danh he doesn't know which label caused the switch case to execute. On the other hand, what about declaring the common variables outside the switch? – Marco A. Nov 21 '16 at 08:37
  • Why do you need to declare the variable inside the `switch` at all? Can't you just declare it before that? – UnholySheep Nov 21 '16 at 08:38
  • 1
    Why use a variable at all? Why not call `shoot` directly? Like `case SHOOTER_FIRE: shoot(rigidBody, TEMPERATURE_FIRE); break;` – Some programmer dude Nov 21 '16 at 08:38
  • @UnholySheep It would be a bit messy. A strange variable about `SHOOTER` in outer scope that also accessible by `PROPULSION` category inside the switch-case. – javaLover Nov 21 '16 at 08:39
  • @Some programmer dude In real case, to get `TEMPERATURE`'s value, I have to calculate using algorithm that common among every `SHOOTER`. – javaLover Nov 21 '16 at 08:41
  • 1
    @javaLover Is those enum divided to fire and ice, if yes, bit mask maybe suitable – Danh Nov 21 '16 at 08:45
  • @Danh Thank, that would help! I forgot about that. – javaLover Nov 21 '16 at 08:47
  • 4
    The syntactic problem (cannot declare variable in switch) can be solved by putting the switch in a block on its own (`{ int temp; switch(type) { case 1: ...}}`. But your approach generally looks to me like too much ad-hoc code. Typically a switch indicates the failure to declare derived classes with virtual functions. **Do not be afraid of virtual!** The performance penalty is, for example, smaller than the test and jumps which may happen in a switch. Typically the code using the turret base class does not want to know about the turret's exact type. You even name the enum `type_of_turret`! – Peter - Reinstate Monica Nov 21 '16 at 08:50
  • 1
    So call a function which does the "calculation" and returns the temperature, and call it inline in the `shoot` call? Like `shoot(rigidBody, calculateTemperature(type_of_turret))` – Some programmer dude Nov 21 '16 at 08:53
  • @Peter A. Schneider Good point. It is clear as if you can see my full code. – javaLover Nov 21 '16 at 08:55
  • @Some programmer dude Yes, I think it is ok. I just noticed that Walter's answer is same as your comment. Thank : ) – javaLover Nov 21 '16 at 09:02
  • 1
    If I'm reading that article correctly, why do you have a Turret class at all? Turrets would be the guid, and ShootsFire / ShootsIce would be components that have a data member for temperature etc – Caleth Nov 21 '16 at 10:10
  • @Caleth Yes, you are right. Thank! – javaLover Nov 21 '16 at 10:16

2 Answers2

1

Your problem does not really call for a common variable for some, but not all, of the switch cases. Instead, the obvious solution is to simply shoot() from each SHOOTER case:

enum struct shooter { fire, ice };
inline int temperature(shooter);

switch(type_of_turret){
case SHOOTER_FIRE: shoot(rigidBody, temperature(shooter::fire));
  break;
case SHOOTER_ICE: shoot(rigidBody, temperature(shooter::ice));
  break;
......
}
Walter
  • 44,150
  • 20
  • 113
  • 196
1

As I said in a comment, do not be afraid of polymorphic classes with virtual functions. For example, I would define a virtual shoot() function in each turret sub class which takes care of the temperature, or even a virtual MyTemp():

class BaseTurretT
{   
    virtual int MyTemp() = 0;
    public:
    virtual void shootAt(RigidBodyT &rb)
    {
        // default implementation, may be overriden
        shoot(rb, MyTemp());
    }
    // ...
};

class HotTurretT: public BaseTurretT
{
    int MyTemp() { return 1000; }
    // ...
};

class ColdTurretT: public BaseTurretT
{
    int MyTemp() { return -1000; }
    // ...
};        

// more turrets in the future.

With these definitions, your whole switch collapses to a single line. Assume we have a vector of pointers to base turrets. The objects pointed to are of the various derived turrets, but the using code doesn't know and doesn't care:

RigidBodyT rb; 
vector<BaseTurretT *> turrets;
// ... 
for(BaseTurretT *turret: turrets)
{
    turret->shootAt(rb);    // no switch. No idea which turret type.
}

One of the best things of this design is its maintainability. If you want to add more turrets in the future (and looking at the way games evolve, that seems like a safe bet), you do not have to revisit the places where you use turrets. You just add a new class, and all the existing code takes care of itself.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • 1
    Of course, this is the polymorphic solution, but not an answer to the question. – Walter Nov 21 '16 at 09:09
  • 1
    @Walter It is an answer insofar as the question *disappears,* together with a whole shipload of other worries, when this solution is applied. You could also argue that the OP actually had a different problem but could not identify it. (I gave the trivial narrow answer to the actual question in a comment.) – Peter - Reinstate Monica Nov 21 '16 at 09:15
  • @Peter A. Schneider I tried to use your solution, and still fear. It breaks my ECS (Entity–component–system) that a component class should contained no function. (I have edited the question, sorry for not mentioning at first.) What is your opinion toward it? – javaLover Nov 21 '16 at 09:28
  • 1
    I quickly skimmed through the linked article and it seems to me focused on extreme optimization. That may be a guiding principle for the design of your system (and to a degree probably overrides principles like information hiding or maintainability). I have simply no experience with that. If it is necessary it is necessary ;-). It is clear for example that an opaque turret base class prevents many optimizations, and that arrays of properties are easier to "SIMD" on a GPU than scattered objects all holding their own data. – Peter - Reinstate Monica Nov 21 '16 at 10:02