2
\$\begingroup\$

So I create 3D tree like structures using L-systems. Basically this means I generate a string like this:

 "FFFFFFFF-[[FFFF-[[FF-[[F-[[X]+X]+F[+FX]-X]+F]]]]]"

This string is then interpreted by a turtle performing certain actions for each character in the string. The problem is how to map characters to turtle actions in an elegant way in C++.

Right now I use a enum to specify the actions and performAction calls methods based on the action. Is there a better way of doing it?

The turtle code:

enum TurtleAction {
    MoveForward,
    TurnLeft,
    TurnRight,
    PitchDown,
    PitchUp,
    RollLeft,
    RollRight,
    TurnAround,
    PushState,
    PopState
};

struct TurtleState {
    Vec3f pos;

    Vec3f H;
    Vec3f L;
    Vec3f U;

    double width;
};

class Turtle {
    TurtleState m_currentState;
    stack<TurtleState> m_states;

    void(*m_drawFunc)(Vec3f,double); ///< Drawing callback

public:
    double alpha; ///< Turning angle

    Turtle(TurtleState startState,
           void(*drawFunc)(Vec3f,double));
    void performAction(TurtleAction action);

private:
    void move(double dist);
    void turn(double deg);
    void pitch(double deg);
    void roll(double deg);

    void push();
    void pop();

    void rotate(const Mat44f & R);
};



void
Turtle::performAction(TurtleAction action){
    switch (action) {
        case MoveForward:
            move(1.0f);
            break;

        case TurnLeft:
            turn(-alpha);

        case TurnRight:
            turn(+alpha);

        case PitchDown:
            pitch(-alpha);

        case PitchUp:
            pitch(+alpha);

        case RollLeft:
            roll(-alpha);

        case RollRight:
            roll(+alpha);

        case TurnAround:
            turn(180.0f);

        case PushState:
            push();

        case PopState:
            pop();

        default:
            break;
    }
}

The string parsing:

class LSystem {

    map<char,TurtleAction> m_actionRules;
    TurtleState m_start;

public:

    void draw(string & str,void(*drawFunc)(Vec3f,double));
    void computeString(string & axiom, int generations);

    LSystem();
    ~LSystem();

};

void
LSystem::draw(string &str, void (*drawFunc)(Vec3f, double))
{
    Turtle turtle(m_start,drawFunc);
    turtle.alpha = 25.0f;

    for (int s = 0; s < str.size(); s++) {
        char c = str[s];
        map<char,TurtleAction>::iterator it = m_actionRules.find(c);
        if (it != m_actionRules.end()) {
            turtle.performAction(it->second);
        }
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ Have you considered that sometimes more than one character chained together can have a different meaning. Or more than one character can mean one instructions. \$\endgroup\$ Mar 5, 2013 at 0:33
  • \$\begingroup\$ No. Right now I only work with single character instructions. Actually I don't know how easily it could be done with varying instruction lengths. Either I would need to introduce some separation characters or have some kind of state machine. \$\endgroup\$
    – Nicholas
    Mar 5, 2013 at 10:38

1 Answer 1

-1
\$\begingroup\$

The design flaw would have been the use of this switch case with 11 entries. That's already a lot. By the way I warn you, you have forgotten 9 break out of 11 cases.

However, in your case, each case relates to a single action. I mean, there is not a lot new to understand while reading: for each case you define a single behaviour action, inside a single word function call. (You don't put multi line, multi code, with multi conditions behaviour that is complex inside your switch case... : this is the thing to avoid and you have avoided it)

So honestly speaking it's totally acceptable.


Only for your culture I am mentionning some ways to get rid of switch case. I repeat myself, you shouldn't apply them in your case right now.

However, the first thing you may notice in such design, maybe there are common behaviours that are dupplicated between all your function call inside the switch case.

Imagine, if in roll() and turn() you would first have to

  1. Turn head to the right to see if nothing tries to cross my path
  2. Turn head to the left to see if nothing is moving straight right on me

and then only to roll() or to turn()

Then you could start thinking about using the Strategy pattern instead of simple function calls. This way, the strategy object which contain the action to do, can share common behaviour in a mother class.

If you started to have really many Strategies, that should be tune and tweaked precisely, then you would think about implementing the Interpreter Pattern so as to map between the precise language in your entry and the Strategies to apply.

And if you start to have a big Interpreter object and you think you need a grammar to express all the possibilities, then no doubt you should think about a Domain Specific Language. ( with you how parser etc... ).

In your case the switch case is entirely sufficient ;-)

\$\endgroup\$
1
  • \$\begingroup\$ Thanks alot :) The breaks were certainly a problem. Ok I will look into the two patterns. Thanks for the review! \$\endgroup\$
    – Nicholas
    Mar 4, 2013 at 15:27

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.