2010-12-15 6 views
2

J'écris un programme de serpent en C++ (visualisation avec JNI) et les boutons de mouvement valides sont soit gauche (déplacer 90 ° dans le sens antihoraire) ou droite (déplacer 90 ° vers la droite).Nested instructions if C++

Dans chaque boucle de la boucle de jeu que je récupère un événement clé à partir d'une interface graphique et déplacer mon serpent selon cet événement clé, voici comment je fais ceci:

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) 
    //... 
} 

if avec: /*change turn after head*/ est parce que si le serpent se déplace vers le bas et va gauche il y a un autre graphique pour le tour alors quand il va jusqu'à et va gauche. Cela conduit à un grand nombre d'instructions if et n'est pas très lisible, donc je me demande s'il existe un moyen général de résoudre des instructions if imbriquées comme celle-ci.

EDIT:
key_event et moveDirection sont des enums.

Répondre

5

J'aurais personnellement une autre fonction pour appliquer le mouvement.

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

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

J'utilise le cas de commutation, à mon avis, il est plus en mesure de lecture pour cet exemple plutôt que si .. else if .. else if ...

Le point est, quand vous avez imbriqué boucles, voyez si vous pouvez en casser une partie pour simplifier le problème.

J'espère que cela aide.

+0

Fondamentalement, c'est une combinaison de walkTarget et les réponses de Mark B, cela devrait certainement améliorer la lisibilité, merci! – Aerus

3

Il est fort probable que le code sera plus lisible si vous factorisez des sections des contrôles if dans les fonctions. Sans voir le contexte complet du travail effectué, c'est difficile à dire, mais quelque chose comme get_new_direction(event, direction) serait probablement un bon début. Lorsque les composants des contrôles if sont bien nommés, cela aide à la fois la lisibilité et peut-être aussi les niveaux d'imbrication.

+0

Je me demande pourquoi je ne l'ai pas pensé! J'utiliserai certainement ceci, en combinaison avec la réponse de raRaRa, merci! – Aerus

1

Si votre événement_clé est un int ou un caractère, vous pouvez utiliser une instruction switch qui pourrait être plus lisible.

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

C'est en effet un 'int', je suis surpris car mon professeur a dit que les enums étaient des types autonomes en C++, pas comme en C s'ils étaient en fait des entiers. Sera utilisé en combinaison avec la réponse de raRaRa, merci! – Aerus

6

Il se sent comme Finite State Machine est ce que vous avez besoin. Vous pouvez définir des clés comme des événements, des positions de serpent comme des états et décrire des transitions d'un état dans un autre en fonction de l'événement (quelle touche enfoncée). Cela résoudra à peu près le problème et utilisera la table de transition au lieu des instructions if imbriquées et rendra votre implémentation moins sujet aux erreurs. Boost Statechart Library peut vous aider à l'implémenter rapidement.

+0

Je n'ai jamais entendu parler de ça auparavant, ça a l'air très intéressant et ça vaut vraiment le coup d'essayer, peut-être un peu exagéré pour ce projet. Le problème est que cela fait partie d'une affectation et que nous sommes seulement autorisés à utiliser des bibliothèques standard et des fichiers d'en-tête déjà donnés. Je vais certainement vérifier cela pour d'autres projets! – Aerus

1

Eh bien, vous pouvez évidemment éliminer le premier "si" avec quelque chose comme ceci:

if(keyEvent == null) 
return; 

Ensuite, utilisez quelque chose comme ceci:

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

defaultAction.Execute(); 

J'espère que vous Attrapé l'idée :)

+0

J'aime comment éliminer le premier si, je ne peux pas le faire cependant parce qu'il y a un morceau de code en dessous, qui serait ignoré. (Je pourrais mettre toutes les if-statements dans une nouvelle méthode et alors je serais capable de faire cela) Bonne idée cependant! – Aerus

0

Vous pouvez utiliser une table de saut, en supposant que les événements clés commencent à partir de zéro et sont de type intégral, et que cette fonction est non statique.Cependant, il y a beaucoup de frais généraux sémantiques, ainsi que du temps et de l'espace. et un bon switch-case est probablement l'option la plus simple.

+0

Je vais utiliser l'interrupteur pour l'instant puisque - comme vous l'avez mentionné - c'est l'option la plus simple qui me donne assez de lisibilité. – Aerus

1

Vous pouvez le faire avec des objets:

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

Ensuite, vous avez soit des cas de MoveState représentant chaque direction du mouvement. Votre code externe peut alors être:

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; 
     } 
    } 
} 

Vous pouvez aussi le faire par le sous-classement MoveState pour chaque direction du mouvement. Cela dépend de l'endroit où vous voulez placer votre code et de la façon dont il doit être extensible. Le sous-classement est plus flexible mais peut être excessif dans ce cas.

Cela devrait être moins sujette aux erreurs car vous pouvez tester chaque instance de MoveState beaucoup plus facilement qu'une grosse instruction if-then-else ou switch.

EDIT: Heh. C'est fondamentalement une version moins concise de ce que @Vlad a suggéré.

+0

Je pense que c'est une approche très générale à un problème comme celui-ci et, je devrais l'étudier un peu plus mais j'essaierai probablement de le mettre en application comme vous le dites, comme un exercice pour moi-même. Merci pour la contribution! – Aerus

2

Une simple table de recherche pourrait aider:

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

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

afin que vous puissiez écrire

dir curr_dir; 
if (moveDirection == LEFT) 
    curr_dir = lturn[curr_dir]; 
if (moveDirection == RIGHT) 
    curr_dir = rturn[curr_dir]; 
1

Je recommande ce qui représente la direction du serpent comme vecteur, les quatre directions étant (1 0) - droite, (-1 0) - gauche, (0 1) - haut et (0 -1) bas. Cela éliminerait la plupart des commutateurs de votre programme, puisque le déplacement du serpent est géré par un simple calcul, plutôt que par le mappage des valeurs d'énumération arbitraires au mouvement.

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

Puis tourner à gauche et à droite devient:

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

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

Et les clés seraient traitées par:

if (key_event == LEFT) snake_direction = turn_left(snake_direction); 
else if (key_event == RIGHT) snake_direction = turn_right(snake_direction); 
+0

Bien que ce soit aussi mon approche, je ne peux pas l'implémenter parce que cela fait partie d'une affectation.Dans cette tâche, nous recevons des fichiers d'en-tête et des restrictions (que l'OMI élimine les erreurs et les logiques que nous devrons résoudre pour résoudre ce problème) que nous devons implémenter. Mon serpent fonctionne et si jamais j'ai envie d'optimiser cela dans mon temps libre, je vais donner une approche à cette approche :). – Aerus

Questions connexes