Вложенные if-операторы C ++ - PullRequest
       14

Вложенные if-операторы C ++

2 голосов
/ 15 декабря 2010

Я пишу программу Snake на C ++ (визуализация с JNI), и допустимые кнопки перемещения: влево (перемещение на 90 ° против часовой стрелки) или вправо (перемещение на 90 °по часовой стрелке).

В каждом цикле игрового цикла я извлекаю ключевое событие из интерфейса GUI и перемещаю свою змею в соответствии с этим ключевым событием, вот как я это делаю:

if(key_event != NULL){
    if(key_event == LEFT){
        if(moveDirection == UP || moveDirection == DOWN){
            moveDirection = LEFT;
            (moveDirection == UP) ? /*change turn after head*/ : /*change turn after head*/;
        } //else if (moveDir == LEFT || RIGHT)
    } //else if (key_event == RIGHT)
    //...
}

Если с: /*change turn after head*/, это потому, что если змея движется вниз и идет влево , то есть другой график для поворота, тогда, когда он идет вверх иидет осталось .

Это приводит ко многим операторам if и не очень читабельно, поэтому мне интересно, есть ли общий способ решения таких вложенных операторов if.

РЕДАКТИРОВАТЬ:
key_event и moveDirection являются перечислениями.

Ответы [ 10 ]

6 голосов
/ 15 декабря 2010

Такое ощущение, что Finite State Machine - это то, что вам нужно здесь. Вы можете определять клавиши как события, положения змей как состояния и описывать переходы из одного состояния в другое в зависимости от события (какая клавиша нажата). Это в значительной степени решит проблему и будет использовать таблицу переходов вместо вложенных операторов if и сделает вашу реализацию менее подверженной ошибкам. Boost Statechart Library может помочь вам быстро ее реализовать.

5 голосов
/ 15 декабря 2010

У меня лично была бы другая функция, чтобы применить движение.

if(key_event != NULL){
    if(key_event == LEFT){
       moveLeft();
    }
    ...
}

void moveLeft()
{
  switch(moveDirection)
  {
    case UP:
    case DOWN:
      moveDirection = LEFT;
      break;
    case ...
  }
}

Я использую регистр переключателя, на мой взгляд, он более удобочитаем для этого примера, а не если .. еще, если ... еще, если ...

Дело в том, что когда у вас есть вложенные циклы, посмотрите, можете ли вы разбить некоторые из них на функции, чтобы упростить проблему.

Надеюсь, это поможет.

3 голосов
/ 15 декабря 2010

Скорее всего, код будет более читабельным, если вы разберетесь с разделами проверок if в функциях. Не видя полного контекста выполняемой работы, трудно сказать наверняка, но что-то вроде get_new_direction(event, direction), вероятно, будет хорошим началом. Когда компоненты проверок if являются функциями с хорошими именами, это поможет как удобочитаемости, так и, возможно, уровням вложения.

2 голосов
/ 15 декабря 2010

Может помочь простая таблица поиска:

enum dir { UP, RIGHT, DOWN, LEFT };

dir lturn[] = {LEFT, UP, RIGHT, DOWN};
dir rturn[] = {RIGHT, DOWN, LEFT, UP};

, чтобы вы могли написать

dir curr_dir;
if (moveDirection == LEFT)
    curr_dir = lturn[curr_dir];
if (moveDirection == RIGHT)
    curr_dir = rturn[curr_dir];
1 голос
/ 15 декабря 2010

Я бы рекомендовал представлять направление змеи в виде вектора, причем четыре направления: (1 0) - вправо, (-1 0) - влево, (0 1) - вверх и (0 -1) вниз. Это исключило бы большинство переключателей в вашей программе, поскольку перемещение змеи обрабатывается простой математикой, а не отображением произвольных значений перечисления в движение.

struct Vector2D 
{ 
    int x, y; 
    Vector2D(int x, int y); 
    ... 
};

Затем поворот налево и направо становится:

Vector2D turn_left(Vector2D direction)
{
    return Vector2D(-direction.y, direction.x);
}

Vector2D turn_right(Vector2D direction)
{
    return Vector2D(direction.y, -direction.x);
}

И ключи будут обрабатываться:

if (key_event == LEFT) snake_direction = turn_left(snake_direction);
else if (key_event == RIGHT) snake_direction = turn_right(snake_direction);
1 голос
/ 15 декабря 2010

Вы можете сделать это с объектами:

class MoveState {
    public:
        MoveState* getNextState(MoveDirection change);
        GraphicsStuff* getDirectionChangeGraphics(MoveDirection change);
        void setNextState(MoveDirection change, MoveState* nextState, GraphicsStuff* graphics);
}

Тогда у вас есть экземпляры MoveState, представляющие каждое направление движения.Ваш внешний код может тогда быть:

MoveState STATES[4];

void init() {
    STATES[UP].setNextState(LEFT, &STATES[LEFT], whatever);
    STATES[UP].setNextState(RIGHT, &STATES[RIGHT], whatever);
    STATES[DOWN].setNextState(LEFT, &STATES[RIGHT], whatever);
    STATES[DOWN].setNextState(RIGHT, &STATES[LEFT], whatever);
    ...
}

void handleInput() {
    ...
    if(key_event != NULL){
        MoveDirection change = convertEventToDirection(key_event);
        MoveState* nextState = currentState->getNextState(change);
        if (nextState != NULL) {
            drawCoolGraphics(currentState->getDirectionChangeGraphics(change);
            currentState = nextState;
        }
    }
}

Вы также можете сделать это, создав подкласс MoveState для каждого направления движения.Это зависит от того, куда вы хотите поместить свой код и насколько он должен быть расширяемым.Подклассы более гибкие, но в этом случае могут быть излишними.

Это должно быть менее подвержено ошибкам, потому что вы можете тестировать каждый экземпляр MoveState гораздо проще, чем большой оператор if-then-else или switch.*

РЕДАКТИРОВАТЬ: Хех.По сути, это менее лаконичная версия того, что предложил @Vlad.

1 голос
/ 15 декабря 2010

Ну, очевидно, вы могли бы исключить первое «если» примерно так:

if( keyEvent == null )
return;

Далее используйте что-то вроде этого:

Action defaultAction = TurnLeftAction;
if( keyEvent == Up )
  defaultAction = TurnUpAction;
else if( keyEvent == Down)
 defaultAction = TurnDownAction;
else
 defaultAction = TurnRightAction;

defaultAction.Execute();

Надеюсь, вы уловили идею :) 1007 *

1 голос
/ 15 декабря 2010

Если ваш key_event - int или char, вы можете использовать оператор switch, который может быть более читабельным.

if (moveDirection == UP || moveDirection == DOWN)
{
    switch (key_event)
    {
        case LEFT:
            //Turn snake left
            break;
        case RIGHT:
            //Turn snake right
            break;
        default:
            //Invalid turn, do nothing
            break;
    }
}
else if (moveDirection == LEFT || moveDirection == RIGHT)
{
    switch (key_event)
    {
        case UP:
            //Turn snake up
            break;
        case DOWN:
            //Turn snake down
            break;
        default:
            //Invalid turn, do nothing
            break;
    }
}
0 голосов
/ 01 августа 2018

Вопрос немного старый, но я хочу внести свой вклад. Я лично предпочитаю раннее возвращение, чтобы избежать вложенных ifs. Также для ваших ситуаций я использую отображения. Я бы написал код, как показано ниже.

if(key_event == NULL)
    return;

static const map<pair<int , int> , int> directionMapping = {
    make_pair(make_pair(LEFT , UP) , 180) ,
    make_pair(make_pair(LEFT , DOWN) , 0) ,
    make_pair(make_pair(LEFT , RIGHT) , 90) ,
    make_pair(make_pair(LEFT , LEFT) , 270) ,
    make_pair(make_pair(RIGHT , UP) , 0) ,
    make_pair(make_pair(RIGHT , DOWN) , 180) ,
    make_pair(make_pair(RIGHT , RIGHT) , 270) ,
    make_pair(make_pair(RIGHT , LEFT) , 90)
};

auto newDirection = directionMapping.at(make_pair(key_event , current_direction));

cout << "New direction is " << newDirection << endl;

Это намного более читабельно, без вложенности, без глубины, более легко обслуживаемо. Например, если вы хотите поддерживать также клавиши со стрелками ВВЕРХ и ВНИЗ, просто добавьте несколько пар в отображение.

Отображение может использоваться для выполнения различных процедур. Посмотри на это

if(type == "Foo")
//Do something
else if(type == "Bar")
//Do another thing
else if(type == "Nothing")
//Do nothing

Способ отображения этого поддерживается после c ++ 11.

static const map<string , function<void()>> fMapping = {
        make_pair("Foo" , [](){ cout << "I am doing something"; }),
        make_pair("Bar" , [](){ cout << "I am doing another thing"; }) ,
        make_pair("Nothing" , [](){ cout << "I am doing nothig"; })
};

string type = "Foo";
fMapping.at(type)();

Обратите внимание, что этот тип отображения очень полезен в классах Factory.

0 голосов
/ 15 декабря 2010

Вы можете использовать таблицу переходов, предполагая, что ключевые события начинаются с нуля и являются целочисленным типом, и что эта функция нестатическая.Однако это довольно много семантических накладных расходов, а также времени и пространства.и хороший коммутатор, вероятно, является действительно самым простым вариантом.

...