English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Créer un jeu "Serpent" en MQL5

Créer un jeu "Serpent" en MQL5

MetaTrader 5Expert Advisors | 16 novembre 2021, 15:47
99 0
MRoVas
[Supprimé]

Introduction

Dans cet article, nous allons examiner un exemple de rédaction d'un jeu "Snake" en MQL5.

Depuis la 5ème version de MQL, la programmation du jeu est devenue possible, principalement grâce aux fonctionnalités de traitement des événements, y compris les événements personnalisés. La programmation orientée-objet simplifie la conception de tels programmes, rend le code plus clair et réduit le nombre d'erreurs.

Après la lecture de cet article, vous découvrirez le traitement des événements OnChart, des exemples d'utilisation des classes de la bibliothèque standard MQL5 et des recettes d'appels cycliques de la fonction après un certain temps pour effectuer d'éventuels calculs.

Description du Jeu

Le jeu "Snake" a été choisi comme exemple, principalement, en raison de la simplicité de son implémentation. Tout le monde qui s'intéressait beaucoup à la programmation est en mesure d'écrire ce jeu.

D'après Wikipédia :

Snake est un jeu vidéo paru pour la première fois au milieu des années 1970 dans les salles d'arcade et a maintenu sa popularité depuis lors, devenant en quelque sorte un classique.

Le joueur contrôle une créature longue et mince, ressemblant à un serpent, qui erre dans un avion bordé, ramassant de la nourriture (ou un autre objet), essayant d'éviter d’ heurter sa propre queue ou les "murs" qui entourent l'aire de jeu. Dans certaines variantes sur le terrain, il y a des obstacles supplémentaires. Chaque fois que le serpent mange un morceau de nourriture, sa queue s'allonge, ce qui rend le jeu davantage difficile. L'utilisateur contrôle la direction de la tête du serpent (haut, bas, gauche ou droite), et le corps du serpent suit. Le joueur ne peut pas empêcher le serpent de bouger pendant que le jeu est en cours, et ne peut pas faire reculer le serpent.

Cette implémentation de "Snake" dans MQL5 aura quelques limitations et fonctionnalités.

Le nombre de niveaux est égal à 6 (de 0 à 5). Il y a 5 vies disponibles à chaque niveau. Après l'utilisation de toutes les vies ou après le passage réussi de tous les niveaux, le jeu reviendra au niveau initial. Vous pouvez créer vos propres niveaux. La vitesse du serpent et sa longueur maximale sont les mêmes pour chaque niveau.

Le terrain de jeu se compose de 4 éléments :

  1. Titre du jeu. Il est utilisé pour le positionnement du jeu sur le graphique. En déplaçant le titre, tous les éléments du jeu sont déplacés.
  2. Terrain de jeu. C'est un tableau (table) de cellules de dimensions 20x20. Chaque cellule a une taille de 20x20 pixels. Les éléments sur le terrain de jeu sont :
    • Le Serpent. Il se compose d'au moins trois éléments consécutifs - la tête, le corps et la queue. La tête peut être déplacée vers la gauche, la droite, le haut et le bas. Tous les autres éléments du serpent sont déplacés après la tête. 
    • L'Obstacle. Il est représenté par un rectangle gris, dans le cas d'une collision de la tête d'un serpent avec l'obstacle, le niveau en cours est relancé et le nombre de vies est diminué par 1.
    • La Nourriture. La nourriture est présentée par baie, en cas de collision de la tête du serpent avec la nourriture, la taille du serpent (longueur de son corps) est augmentée. Après avoir mangé 12 morceaux, le serpent passe au niveau suivant.
  3. Le panneau d'information (barre d'état du jeu). Il se compose de trois éléments :
    • Niveau. Affiche le niveau actuel.
    • Reste de nourriture. Indique la quantité de baies qu'il reste à manger.
    • Vies Affiche le nombre de vies disponibles.
  4. Panneau Il se compose de trois boutons :
    • Bouton "Démarrer". Démarre le niveau actuel.
    • Le bouton "Pause". Suspend le jeu.
    • Le bouton "Arrêter". Arrête le jeu, tandis que la transition se produit au niveau initial.

Tous ces éléments sont visibles sur la figure 1 :


Fig. 1. Éléments du jeu "Serpent"

Le titre du jeu est un objet de type "Bouton". Tous les éléments du terrain de jeu sont des objets de type "BmpLabel". Le panneau d'information est composé de trois objets de type "Éditer", le panneau de configuration est composé de trois objets de type "Bouton". Tous les objets sont positionnés par définition de distances le long de X et Y en pixels par rapport au coin supérieur gauche du graphique.

Il est à noter que jouer aux bords du terrain ne ’est pas un obstacle au mouvement du serpent. Par exemple, alors le serpent passe par le bord gauche, il apparaît à droite. On peut le voir sur la figure 2 :


Figure 2. Passage du serpent à travers le bord du terrain de jeu

La tête et la queue du serpent, contrairement au corps, peuvent être tournées. La direction de la tête est déterminée par la direction du mouvement du serpent ou par la position de ses éléments voisins. La direction de la queue n'est déterminée que par la position de l'élément voisin. 

Par exemple, si l'élément de queue voisin est sur le côté gauche, la queue est tournée vers la gauche. Un peu différemment est avec la tête. La tête est tournée à gauche, si son élément voisin se trouve sur le côté droit. Les exemples de directions tête et queue sont présentés dans les figures ci-dessous. Faites attention au tour de la tête et de la queue par rapport à leurs éléments voisins.





La tête et la queue sont dirigées vers la gauche La tête et la queue sont dirigées vers la droite La tête et la queue sont dirigées vers le bas La tête et la queue sont dirigées vers le haut


Le mouvement du serpent s'effectue en trois étapes :

  1. Le mouvement de la tête de cellule vers la droite, la gauche, le haut ou le bas selon la direction.
  2. Le mouvement du dernier élément d'un corps de serpent sur la place de la tête précédente.
  3. Déplacer la queue du serpent à l'emplacement précédent du dernier élément du corps. Le mouvement de la queue du serpent à l'emplacement précédent du dernier élément du corps du serpent.

Si le serpent mange la nourriture, la queue ne bouge pas. Au lieu de cela, un nouvel élément du corps est créé, qui s'est déplacé à la place passée du dernier élément du corps du serpent. 

Un exemple de mouvement de serpent vers la gauche est présenté dans les figures ci-dessous :





Position initiale  Une cellule vers le mouvement de la tête gauche
Mouvement du dernier élément du corps
à la place précédente de la tête
Mouvement de queue sur la place passée
du dernier élément du corps

Théorie 

Ensuite, nous discuterons des outils et des techniques qui sont utilisés lors de la rédaction de jeux.

La bibliothèque standard MQL5

Il est pratique d'utiliser les tableaux d'objets du même type (par exemple, des cellules de terrain de jeu, des éléments de serpent) pour les manipuler (créer, déplacer, supprimer). Ces tableaux et objets peuvent être implémentés à l'aide des classes de la bibliothèque standard MQL5.

L'utilisation des classes de la bibliothèque standard MQL5 permet de simplifier le processus de rédaction des programmes. Pour le jeu, nous utiliserons les classes de bibliothèque suivantes :

  1. Le CArrayObj est la classe pour l'organisation des données (tableau dynamique de pointeurs).
  2. Les CChartObjectEdit , CChartObjectButton , CChartObjectBmpLabel - sont des classes de contrôle , représentant respectivement "Edit", "Button" et "BmpLabel".

Pour utiliser les classes de la bibliothèque standard MQL5, il est nécessaire de les inclure à l'aide de l’instruction de compilation suivante :

#include <path_to_the_file_with_classes_description>

Par exemple, pour utiliser des objets de type CChartObjectButton nous devons écrire :

#include <ChartObjects\ChartObjectsTxtControls.mqh>

Les chemins de fichiers peuvent être trouvés dans la référence MQL5.

Lorsque vous travaillez avec les classes de la bibliothèque standard MQL5, il est important de comprendre que certaines d'entre elles héritent les unes les autres. Par exemple, la CChartObjectButton classe hérite CChartObjectEdit et en retour la classe CChartObjectEdit hérites la classe CChatObjectLabel etc. Cela indique que pour les classes dérivées, les propriétés et méthodes de la classe parente sont disponibles.

Pour comprendre les avantages de l'utilisation des classes de la bibliothèque standard MQL5, examinons un exemple de création de bouton et implémentons-le de deux manières (sans et avec utilisation des classes).

Voici un exemple sans l'utilisation des classes :

//Creating a button with name "button"
ObjectCreate(0,"button",OBJ_BUTTON,0,0,0);
//Specifying the text on the button
ObjectSetString(0,"button",OBJPROP_TEXT,"Button text");
//Specifying the button size
ObjectSetInteger(0,"button",OBJPROP_XSIZE,100);
ObjectSetInteger(0,"button",OBJPROP_YSIZE,20);
//Specifying the button position
ObjectSetInteger(0,"button",OBJPROP_XDISTANCE,10);
ObjectSetInteger(0,"button",OBJPROP_YDISTANCE,10);

Un exemple avec l'utilisation des classes :

CChartObjectButton *button;
//Creating an object of CChartObjectButton class and assign a pointer to the button variable
button=new CChartObjectButton;
//Creating a button with properties (in pixels): (width=100, height=20, positions: X=10,Y=10)
button.Create(0,"button",0,10,10,100,20);
//Specifying the text on the button
button.Description("Button text");

On peux voir, il est plus simple de travailler avec les classes. De plus, les objets de classe peuvent être stockés dans des tableaux et facilement manipulés.

Les méthodes et propriétés des classes de contrôles d'objets sont bien et clairement décrites dans la référence MQL5 aux classes de la bibliothèque standard.

Nous utiliserons la classe CArrayObj de la Bibliothèque Standard pour organiser le tableau d'objets, elle permet de soulager l'utilisateur de nombreuses opérations de routine (comme le redimensionnement d'un tableau lors de l'ajout d'un nouvel élément, la suppression d'objets dans le tableau, etc.)

Fonctionnalités de la classe CArrayObj

La classe CArrayObj permet d'organiser un tableau dynamique de pointeurs vers les objets de type classe CObject. Le CObject est une classe parente pour toutes les classes de la Bibliothèque Standard. Cela indique que nous pouvons créer un tableau dynamique de pointeurs vers les objets de n'importe quelle classe des classes de la bibliothèque standard tge. Si vous devez créer un tableau dynamique d'objets de votre propre classe, il doit être hérité de la classe CObject.

Dans l'exemple suivant, le compilateur n'imprimera pas les erreurs, car la classe personnalisée est le successeur de la classe CObject :

#include <Arrays\ArrayObj.mqh>
class CMyClass:public CObject
  {
   //fields and methods
  };
//creating an object of CMyClass type and assign it to the value of the my_obj variable
CMyClass *my_obj=new CMyClass;
//declaring a dynamic array of object pointers
CArrayObj array_obj;
//adding the my_obj object pointer at the end of the array_obj array
array_obj.Add(my_obj);

Pour le cas suivant, le compilateur générera une erreur, car my_obj n'est pas un pointeur vers la classe CObject, ou une classe qui hérite de la classe CObject :

#include <Arrays\ArrayObj.mqh>
class CMyClass
  {
   //fields and methods
  };
//creating an object of CMyClass type and assing it to the value of the my_obj variable
CMyClass *my_obj=new CMyClass;
//declaring a dynamic array of object pointers
CArrayObj array_obj;
//adding the my_obj object pointer at the end of the array_obj array
array_obj.Add(my_obj);

Lors de la rédaction le jeu va utiliser les méthodes suivantes de la classe CArrayObj :

  • Add - Ajoute un élément à la fin du tableau
  • Insert - Insère un élément à la position indiquée du tableau.
  • Detach - Supprime l'élément à la position indiquée (l'élément est supprimé du tableau).
  • Total - Obtient le nombre d'éléments dans le tableau.
  • At - Obtient l'élément à la position indiquée (l'élément n'est pas supprimé du tableau).

Voici un exemple de travail avec la classe CArrayObj :

#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CMyClass:public CObject
  {
   public:
   char   s;
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void MyPrint(CArrayObj *array_obj)
  {
   CMyClass *my_obj;
   for(int i=0;i<array_obj.Total();i++)
     {
      my_obj=array_obj.At(i);
      printf("%C",my_obj.s);
     }
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //creating the pointer to the object of CArrayObj class
   CArrayObj *array_obj=new CArrayObj();
   //declaring the CMyClass object pointer
   CMyClass *my_obj;
   //filling the array_obj dynamic array
   for(int i='a';i<='c';i++)
     {
      //creating the CMyClass class object
      my_obj=new CMyClass();
      my_obj.s=char(i);
      //adding an object of CMyClass class at the end of the array_obj dynamic array
      array_obj.Add(my_obj);
     }
   //printing result
   MyPrint(array_obj);
   //creating new object of CMyClass class
   my_obj=new CMyClass();
   my_obj.s='d';
   //inserting new element at the first position of the array
   array_obj.Insert(my_obj,1);
   //printing result
   MyPrint(array_obj);
   //detaching the element from the third position of the array
   my_obj=array_obj.Detach(2);
   //printing result
   MyPrint(array_obj);
   //deleting the dynamic array and all objects with pointers of the array
   delete array_obj;
   return(0);
  }

Dans cet exemple, la fonction OnInit crée un tableau dynamique avec trois éléments. La sortie du contenu du tableau est effectuée par l'appel de la fonction MyPrint.

Après avoir rempli le tableau à l'aide de la méthode Add, son contenu peut être représenté par (a, b, c). 

Après avoir appliqué la méthode Insert, le contenu du tableau peut être représenté par (a, d, b, c).

Enfin, après avoir appliqué la méthode Detach, le tableau ressemblera à (a, d, c).

Lorsque l'opérateur delete est appliqué à la variable array_obj, le destructeur de classe CArrayObj est appelé, ce qui supprime non seulement le tableau array_obj, mais également les objets dont les pointeurs y sont stockés. Pour l'éviter, avant d'appliquer la commande delete, l'indicateur de gestion de la mémoire de la classe CArrayObj doit être défini comme false. Ce drapeau est défini par la méthode FreeMode.

S'il n'est pas nécessaire de supprimer les objets, dont les pointeurs sont stockés dans le tableau dynamique lors de la suppression d'un tableau dynamique de pointeurs d'objets, vous devez rédiger le code suivant :

array_obj.FreeMode(false);
delete array_obj;

Gestionnaire d'évènement                                        

S'il y a un ensemble d'événements générés, ils s'accumulent dans la file d'attente, puis ils arrivent systématiquement à la fonction de traitement des événements.

Pour la gestion des événements générés lors de l'utilisation du graphique, ainsi que des événements personnalisés, MQL5 dispose de la fonction OnChartEvent. Chaque événement dispose d’ un identifiant et des paramètres, transmis à la fonction OnChartEvent

La fonction OnChartEvent est appelée uniquement lorsque le thread est hors de toutes les autres fonctions du programme. Ainsi, dans l'exemple suivant, le OnChartEvent ne prendra jamais le contrôle.

#include <ChartObjects\ChartObjectsTxtControls.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void MyFunction()
  {
   CChartObjectButton *button;
   button=new CChartObjectButton;
   button.Create(0,"button",0,10,10,100,20);
   button.Description("Button text");
   while(true)
     {
      //The code, that should be called periodically
     }
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   MyFunction();
   return(0);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                const long &lparam,
                const double &dparam,
                const string &sparam)
  {
   if(id==CHARTEVENT_OBJECT_CLICK && sparam=="button") Alert("Button click");
  }

Une boucle while infinie ne permet pas de revenir de la fonction MyFunction. La fonction OnChartEvent ne peut pas prendre le contrôle. Ainsi, l'appui sur le bouton n'appelle pas la fonction Alert.

Exécution de Code Périodique avec la Gestion des Évènements

Dans le jeu, l'appel périodique de la fonction de mouvement du serpent avec la capacité de gérer les événements après un certain intervalle de temps est nécessaire. Mais comme cela a été montré ci-dessus, une boucle sans fermée conduit au fait que la fonction OnChartEvent n'est pas appelée et que la gestion des événements devient impossible.

Il faut donc inventer un autre mode d'exécution périodique du code.

Utiliser OnTimer

Le langage MQL5 dispose d’ une fonction spéciale OnTimer, qui est appelée périodiquement selon le nombre de secondes prédéfini. Pour se faire, nous utiliserons la fonction EventSetTimer.

L'exemple précédent peut être réécrit comme :

#include <ChartObjects\ChartObjectsTxtControls.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void MyFunction()
  {
   //The code, that should be executed periodically
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   CChartObjectButton *button;
   button=new CChartObjectButton;
   button.Create(0,"button",0,10,10,100,20);
   button.Description("Button text");
   EventSetTimer(1);
   return(0);
  }
void OnTimer()
{
   MyFunction();
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(id==CHARTEVENT_OBJECT_CLICK && sparam=="button") Alert("Button click");
  }

Dans la fonction OnInit, le bouton a créé et défini une période égal à une seconde, pour l'appel de la fonction OnTimer, L'appel de la fonction OnTimer est effectué toutes les secondes, la fonction OnTimer appelle le code (MyFunction), qui doit être exécuté périodiquement.

Faites attention que l'appel de la fonction OnTimer est un multiple de secondes. Pour appeler la fonction après un nombre indiqué de millisecondes, l'autre méthode est nécessaire. Cette méthode consiste en l’usage des événements personnalisés.

Utilisation des événements personnalisés

L'événement personnalisé est généré par la fonction EventChartCustom, l’évènement ID et ses paramètres sont définis dans les paramètres d'entrée de la fonction EventChartCustom. Le nombre d'ID personnalisés peut atteindre 65536 - de 0 à 65535. Le compilateur MQL5 ajoute automatiquement l'identifiant de constante CHARTEVENT_CUSTOM à l' ID pour distinguer les événements personnalisés des autres types d'événements. Ainsi, la plage réelle des ID personnalisés va de CHARTEVENT_CUSTOM à CHARTEVENT_CUSTOM+65535 ( CHARTEVENT_CUSTOM_LAST ).

Un exemple d'appel périodique de MyFunction utilisant les événements personnalisés est présenté ci-dessous :

#include <ChartObjects\ChartObjectsTxtControls.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void MyFunction()
  {
   //The code, that should be executed periodically
   Sleep(200);
   EventChartCustom(0,0,0,0,"");
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   CChartObjectButton *button;
   button=new CChartObjectButton;
   button.Create(0,"button",0,10,10,100,20);
   button.Description("Button text");
   MyFunction();
   return(0);
  }
//+------------------------------------------------------------------+
//| OnChartEvent processing function                                 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(id==CHARTEVENT_OBJECT_CLICK && sparam=="button") Alert("Button click");
   if(id==CHARTEVENT_CUSTOM) MyFunction();
  }

Dans cet exemple, avant la fonction MyFunction, il y a un retard de 200 ms (le temps de l'appel périodique de cette fonction) et un événement personnalisé est généré. La fonction OnChartEvent gère tous les événements, pour le cas de l'événement personnalisé, elle appelle à nouveau la fonction MyFunction. Ainsi, l'appel périodique de la fonction MyFunction est implémenté de cette façon, et il est possible de définir la période d'appel égale à des millisecondes.

Partie pratique

Examinons un exemple de rédaction d'un jeu "Snake".

Définition de la carte des constantes et des niveaux

Mapper les niveaux dans un fichier séparé (en-tête) "Snake.mqh" et est un niveau de tableau tridimensionnel [6] [20] [20]. La carte des niveaux se trouve dans un fichier d'en-tête séparé "Snake.mqh" et représentée sous la forme d'un tableau tridimensionnel game_level[6][20][20]. Chaque élément de ce tableau est un tableau à bi-dimensionnel , qui comporte la description du niveau individuel. Si la valeur d'un élément est égale à 9, c'est un obstacle. Si la valeur d'un élément du tableau est égale à 1,2 ou 3, c'est respectivement la tête, le corps ou la queue du serpent qui définit sa position initiale sur le terrain de jeu. Vous pouvez ajouter de nouveaux niveaux ou modifier les niveaux existants dans le tableau de niveaux.

De plus, le fichier "Snake.mqh" comporte les constantes, utilisées dans le jeu. Par exemple, en modifiant les constantes SPEED_SNAKE et MAX_LENGTH_SNAKE, vous pouvez augmenter/diminuer la vitesse du serpent et sa longueur maximale à chaque niveau. Toutes les constantes sont commentées.

//+------------------------------------------------------------------+
//|                                                        Snake.mqh |
//|                                        Copyright Roman Martynyuk |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Roman Martynyuk"
#property link      "http://www.mql5.com"
#include <VirtualKeys.mqh>                                                    //File with keycodes
#include <Arrays\ArrayObj.mqh>                                                //File with CArrayObj class
#include <ChartObjects\ChartObjectsBmpControls.mqh>                           //File with CChartObjectBmpLabel class
#include <ChartObjects\ChartObjectsTxtControls.mqh>                           //File with CChartObjectButton and CChartObjectEdit classes
#define CRASH_NO                          0                                   //No crash
#define CRASH_OBSTACLE_OR_SNAKE           1                                   //Crash with an "Obstacle" or snake body
#define CRASH_FOOD                        2                                   //Crash with a "Food""
#define DIRECTION_LEFT                    0                                   //Left
#define DIRECTION_UP                      1                                   //Up
#define DIRECTION_RIGHT                   2                                   //Right
#define DIRECTION_DOWN                    3                                   //Down
#define COUNT_COLUMNS                     ArrayRange(game_level,2)            //Number of columns of playing field
#define COUNT_ROWS                        ArrayRange(game_level,1)            //Number of rows of playing field
#define COUNT_LEVELS                      ArrayRange(game_level,0)             //Number of levels
#define START_POS_X                       0                                   //Starting X position of the game
#define START_POS_Y                       0                                   //Starting Y position of the game
#define SQUARE_WIDTH                      20                                  //Square (cell) width (in pixels)
#define SQUARE_HEIGHT                     20                                  //Square (cell) height (in pixels)
#define IMG_FILE_NAME_SQUARE              "\\Images\\Games\\Snake\\square.bmp"          //Path to the "Square" image
#define IMG_FILE_NAME_OBSTACLE            "\\Images\\Games\\Snake\\obstacle.bmp"        //Path to the "Obstacle" image
#define IMG_FILE_NAME_SNAKE_HEAD_LEFT     "\\Images\\Games\\Snake\\head_left.bmp"       //Path to the snake's head (left) image
#define IMG_FILE_NAME_SNAKE_HEAD_UP       "\\Images\\Games\\Snake\\head_up.bmp"         //Path to the snake's head (up) image
#define IMG_FILE_NAME_SNAKE_HEAD_RIGHT    "\\Images\\Games\\Snake\\head_right.bmp"      //Path to the snake's head (right) image
#define IMG_FILE_NAME_SNAKE_HEAD_DOWN     "\\Images\\Games\\Snake\\head_down.bmp"       //Path to the snake's head (down) image
#define IMG_FILE_NAME_SNAKE_BODY          "\\Images\\Games\\Snake\\body.bmp"            //Path to the snake's body image
#define IMG_FILE_NAME_SNAKE_TAIL_LEFT     "\\Images\\Games\\Snake\\tail_left.bmp"       //Path to the snake's tail (left) image
#define IMG_FILE_NAME_SNAKE_TAIL_UP       "\\Images\\Games\\Snake\\tail_up.bmp"         //Path to the snake's tail (up) image
#define IMG_FILE_NAME_SNAKE_TAIL_RIGHT    "\\Images\\Games\\Snake\\tail_right.bmp"      //Path to the snake's tail (right) image
#define IMG_FILE_NAME_SNAKE_TAIL_DOWN     "Games\\Snake\\tail_down.bmp"       //Path to the snake's tail (down) image
#define IMG_FILE_NAME_FOOD                "Games\\Snake\food.bmp"             //Path to the "Food" image
#define SQUARE_BMP_LABEL_NAME             "snake_square_%u_%u"                //Name of the "Square" graphic label
#define OBSTACLE_BMP_LABEL_NAME           "snake_obstacle_%u_%u"              //Name of the "Obstacle" graphic label
#define SNAKE_ELEMENT_BMP_LABEL_NAME      "snake_element_%u"                  //Name of the "Snake" graphic label
#define FOOD_BMP_LABEL_NAME               "snake_food_%u"                     //Name of the "Food" graphic label
#define LEVEL_EDIT_NAME                   "snake_level_edit"                  //Name of the "Level" edit
#define LEVEL_EDIT_TEXT                   "Level: %u of %u"                   //Text of the "Level" edit
#define FOOD_LEFT_OVER_EDIT_NAME          "snake_food_available_edit"         //Name of the "Food left" edit
#define FOOD_LEFT_OVER_EDIT_TEXT          "Food left over: %u"                //Text of the "Food left" edit
#define LIVES_EDIT_NAME                   "snake_lives_edit"                  //Name of the "Lives" edit
#define LIVES_EDIT_TEXT                   "Lives: %u"                         //Text of the "Lives" edit
#define START_GAME_BUTTON_NAME            "snake_start_game_button"           //Name of the "Start" button
#define START_GAME_BUTTON_TEXT            "Start"                             //Text of the "Start" button
#define PAUSE_GAME_BUTTON_NAME            "snake_pause_game_button"           //Name of the "Pause" button
#define PAUSE_GAME_BUTTON_TEXT            "Pause"                             //Text of the "Pause" button
#define STOP_GAME_BUTTON_NAME             "snake_stop_game_button"            //Name of the "Stop" button
#define STOP_GAME_BUTTON_TEXT             "Stop"                              //Text of the "Stop" button
#define CONTROL_WIDTH                     (COUNT_COLUMNS*(SQUARE_WIDTH-1)+1)/3//Control Panel Width (1/3 of playing field width)
#define CONTROL_HEIGHT                    40                                  //Control Panel Height
#define CONTROL_BACKGROUND                C'240,240,240'                      //Color of Control Panel buttons
#define CONTROL_COLOR                     Black                               //Text Color of Control Panel Buttons
#define STATUS_WIDTH                      (COUNT_COLUMNS*(SQUARE_WIDTH-1)+1)/3//Status Panel Width (1/3 of playing field width)
#define STATUS_HEIGHT                     40                                  //Status Panel Height
#define STATUS_BACKGROUND                 LemonChiffon                        //Status Panel Background Color
#define STATUS_COLOR                      Black                               //Status Panel Text Color
#define HEADER_BUTTON_NAME                "snake_header_button"               //Name of the "Header" button
#define HEADER_BUTTON_TEXT                "Snake"                             //Text of the "Header" button
#define HEADER_WIDTH                      COUNT_COLUMNS*(SQUARE_WIDTH-1)+1    //Width of the "Header" button (playing field width)
#define HEADER_HEIGHT                     40                                  //Height of the "Header" button
#define HEADER_BACKGROUND                 BurlyWood                           //Header Background Color
#define HEADER_COLOR                      Black                               //Headet Text Color
#define COUNT_FOOD                        3                                   //Number of "Foods" at playing field
#define LIVES_SNAKE                       5                                   //Number of snake lives at each level
#define SPEED_SNAKE                       100                                 //Snake Speed (in milliseconds)
#define MAX_LENGTH_SNAKE                  15                                  //Maximal Snake Length
#define MAX_LEVEL                         COUNT_LEVELS-1                      //Maximal Level
int game_level[][20][20]=
  {
     {
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,3,2,1,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
     }
      ,
     {
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,9,9,9,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
     }
      ,
     {
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,9,1,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,9,9,9,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
     }
      ,
     {
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0},
        {0,0,0,0,0,9,9,0,0,0,0,0,3,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,9,9,9,9,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
     }
      ,
     {
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,1,2,3,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,9,9,9,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,9,9,9,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,9,9,9,9,0},
        {0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,9,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
     }
      ,
     {
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0},
        {0,1,2,3,0,0,0,0,0,0,0,9,9,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,9,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,9,9,9,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,9,9,0,0,0,0,0,0,9,9,9,9,0},
        {0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,9,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,0}
     }
  };
//+------------------------------------------------------------------+

Notez, la définition de la constante #define SQUARE_BMP_LABEL_NAME "snake_square_% u_% U". Nous allons créer le terrain de jeu. Chaque cellule du terrain de jeu est une étiquette bitmap, elle doit avoir un nom unique. Le nom d'une cellule est défini par cette constante, la spécification de format d'un nom de cellule est %u, cela indique l'entier inscrit.

Si vous indiquez le nom lors de la création du BmpLabel comme : StringFormat (SQUARE_BMP_LABEL_NAME, 1,0), le nom sera égal à "snake_square_1_0".

Les classes

Deux classes personnalisées ont été élaborées pour le jeu, elles sont situées dans le fichier "Snake.mq5".

La classe ChartFieldElement :

//+------------------------------------------------------------------+
//| CChartFieldElement class                                         |
//+------------------------------------------------------------------+
class CChartFieldElement:public CChartObjectBmpLabel
  {
private:
   int               pos_x,pos_y;
public:
   int               GetPosX(){return pos_x;}
   int               GetPosY(){return pos_y;}
   //setting position (pos_x,pos_y) in internal coordinates
   void              SetPos(int val_pos_x,int val_pos_y)
     {
      pos_x=(val_pos_x==-1)?COUNT_COLUMNS-1:((val_pos_x==COUNT_COLUMNS)?0:val_pos_x);
      pos_y=(val_pos_y==-1)?COUNT_ROWS-1:((val_pos_y==COUNT_ROWS)?0:val_pos_y);
     }
   //conversion of internal coordinates to absolute and object movement on the chart
   void              Move(int start_pos_x,int start_pos_y)
     {
      X_Distance(start_pos_x+pos_x*SQUARE_WIDTH-pos_x+(SQUARE_WIDTH-X_Size())/2);
      Y_Distance(start_pos_y+pos_y*SQUARE_HEIGHT-pos_y+(SQUARE_HEIGHT-Y_Size())/2);
     }
  };

La classe CChartFiledElement hérite de la classe CChartObjectBmpLabel, et par la suite l’étend. Tout le terrain de jeu, comme la barrière de cellule, la tête, le corps et la queue du serpent, et la « nourriture » sont les objets de cette classe. Les propriétés pos_x and pos_y sont des coordonnées relatives des éléments sur le terrain de jeu, c'est-à-dire les index de ligne et de colonne de l'élément. La méthode SetPos définit ces coordonnées. La méthode Move convertit les coordonnées relatives en distances le long des axes X et Y en pixels et déplace l'élément. Pour ce faire, il appelle les méthodes de classe de X_Distance et YDistance CChartObjectBmpLabel.

La classe CSnakeGame :

//+------------------------------------------------------------------+
//| CSnakeGame class                                                 |
//+------------------------------------------------------------------+
class CSnakeGame
  {
private:
   CArrayObj        *square_obj_arr;                     //Array of playing field cells
   CArrayObj        *control_panel_obj_arr;              //Array of control panel buttons
   CArrayObj        *status_panel_obj_arr;               //Array of control panel edits
   CArrayObj        *obstacle_obj_arr;                   //Array of an obstacles
   CArrayObj        *food_obj_arr;                       //Array of "Food"
   CArrayObj        *snake_element_obj_arr;              //Array of snake elements
   CChartObjectButton *header;                           //Header
   int               direction;                          //Snake movement direction
   int               current_lives;                      //Number of snake Lives
   int               current_level;                      //Level
   int               header_left;                        //Left position of a header (X)
   int               header_top;                         //Top position of a header (Y)
public:
   //class constructor
   void              CSnakeGame()
     {
      current_lives=LIVES_SNAKE;
      current_level=0;
      header_left=START_POS_X;
      header_top=START_POS_Y;
     }
   //method for definition of header_left and header_top fields
   void              SetHeaderPos(int val_header_left,int val_header_top)
     {
      header_left=val_header_left;
      header_top=val_header_top;
     };
   //Get/Set direction methods
   void              SetDirection(int d){direction=d;}
   int               GetDirection(){return direction;}
   //Header creation and deletion methods
   void              CreateHeader();
   void              DeleteHeader();
   //Playing field creation, movement and deletion methods
   void              CreateField();
   void              FieldMoveOnChart();
   void              DeleteField();
   //Obstacle creation, movement and deletion methods
   void              CreateObstacle();
   void              ObstacleMoveOnChart();
   void              DeleteObstacle();
   //Snake creation, movement and deletion methods
   void              CreateSnake();
   void              SnakeMoveOnChart();
   void              SnakeMoveOnField();                 //snake movement on the playing field
   void              SetTrueSnake();                     //setting the images of the current snake's head and tail
   int               Check();                            //check for the collision with the playing field elements
   void              DeleteSnake();
   //Food creation, movement and deletion methods
   void              CreateFood();
   void              FoodMoveOnChart();
   void              FoodMoveOnField(int food_num);
   void              DeleteFood();
   //Status panel creation, movement and deletion methods
   void              CreateControlPanel();
   void              ControlPanelMoveOnChart();
   void              DeleteControlPanel();
   //Control panel creation, movement and deletion methods
   void              CreateStatusPanel();
   void              StatusPanelMoveOnChart();
   void              DeleteStatusPanel();
   //Move all elements on the chart
   void              AllMoveOnChart();
   //Game initialization
   void              Init();
   //Game deinitialization
   void              Deinit();
   //Game control methods
   void              StartGame();
   void              PauseGame();
   void              StopGame();
   void              ResetGame();
   void              NextLevel();
  };

Le CSnakeGame est la classe principale du jeu, il comporte les champs et les méthodes de création, de déplacement et de suppression des éléments du jeu. On peut voir, au début de la description de la classe, les champs pour l’ organisation de tableaux dynamiques de pointeurs d'éléments de jeu sont déclarés. Par exemple, les pointeurs des éléments snake sont stockés dans le champ snake_element_obj_arr. Le zéroième indice de tableau du tableau snake_element_obj_arr sera une tête de serpent, et le dernier - sa queue. En le connaissant, vous pouvez facilement manipuler le serpent sur le terrain de jeu.

Examinons toutes les méthodes de la classe CSnakeGame. Les méthodes sont mises implémentées sur la base de la théorie, présentée dans le chapitre « Théorie » de cet article.

L'en-tête du jeu

//+------------------------------------------------------------------+
//| Header creation method                                           |
//+------------------------------------------------------------------+
void CSnakeGame::CreateHeader(void)
  {
   //creating a new object of CChartObjectButton class and specifying the properties of header of CSnakeGame class
   header=new CChartObjectButton;
   header.Create(0,HEADER_BUTTON_NAME,0,header_left,header_top,HEADER_WIDTH,HEADER_HEIGHT);
   header.BackColor(HEADER_BACKGROUND);
   header.Color(HEADER_COLOR);
   header.Description(HEADER_BUTTON_TEXT);
   //the header is selectable
   header.Selectable(true);
  }
//+------------------------------------------------------------------+
//| Header deletion method                                           |
//+------------------------------------------------------------------+
void CSnakeGame::DeleteHeader(void)
  {
   delete header;
  }

Le terrain de jeu

//+------------------------------------------------------------------+
//| Playing Field creation method                                    |
//+------------------------------------------------------------------+
void CSnakeGame::CreateField()
  {
   int i,j;
   CChartFieldElement *square_obj;
   //creating an object of CArrayObj class and assign the square_obj_arr properties of CSnakeGame class
   square_obj_arr=new CArrayObj();
   for(i=0;i<COUNT_ROWS;i++)
      for(j=0;j<COUNT_COLUMNS;j++)
        {
         square_obj=new CChartFieldElement();
         square_obj.Create(0,StringFormat(SQUARE_BMP_LABEL_NAME,i,j),0,0,0);
         square_obj.BmpFileOn(IMG_FILE_NAME_SQUARE);
         //specifying the internal coordinates of the cell
         square_obj.SetPos(j,i);
         square_obj_arr.Add(square_obj);
        }
   //moving the playing field cells
   FieldMoveOnChart();
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| The movement of playing field cells on the chart                 |
//+------------------------------------------------------------------+
void CSnakeGame::FieldMoveOnChart()
  {
   CChartFieldElement *square_obj;
   int i;
   i=0;
   while((square_obj=square_obj_arr.At(i))!=NULL)
     {
      square_obj.Move(header_left,header_top+HEADER_HEIGHT);
      i++;
     }
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Deletion of a playing field                                      |
//+------------------------------------------------------------------+
void CSnakeGame::DeleteField()
  {
   delete square_obj_arr;
   ChartRedraw();
  }

Les obstacles

//+------------------------------------------------------------------+
//| Creation of the obstacles                                        |
//+------------------------------------------------------------------+
void CSnakeGame::CreateObstacle()
  {
   int i,j;
   CChartFieldElement *obstacle_obj;
   //creating an object of CArrayObj class and assign the obstacle_obj_arr properties of CSnakeGame class
   obstacle_obj_arr=new CArrayObj();
   for(i=0;i<COUNT_ROWS;i++)
      for(j=0;j<COUNT_COLUMNS;j++)
         if(game_level[current_level][i][j]==9)
           {
            obstacle_obj=new CChartFieldElement();
            obstacle_obj.Create(0,StringFormat(OBSTACLE_BMP_LABEL_NAME,i,j),0,0,0);
            obstacle_obj.BmpFileOn(IMG_FILE_NAME_OBSTACLE);
            //specifying the internal coordinates of the obstacle
            obstacle_obj.SetPos(j,i);
            obstacle_obj_arr.Add(obstacle_obj);
           }
   //moving the obstacle on the chart
   ObstacleMoveOnChart();
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Obstacle movement method                                         |
//+------------------------------------------------------------------+
void CSnakeGame::ObstacleMoveOnChart()
  {
   CChartFieldElement *obstacle_obj;
   int i;
   i=0;
   while((obstacle_obj=obstacle_obj_arr.At(i))!=NULL)
     {
      obstacle_obj.Move(header_left,header_top+HEADER_HEIGHT);
      i++;
     }
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Obstacle deletion method                                         |
//+------------------------------------------------------------------+
void CSnakeGame::DeleteObstacle()
  {
   delete obstacle_obj_arr;
   ChartRedraw();
  }

Le serpent

//+------------------------------------------------------------------+
//| Snake creation method                                            |
//+------------------------------------------------------------------+
void CSnakeGame::CreateSnake()
  {
   int i,j;
   CChartFieldElement *snake_element_obj,*snake_arr[];
   ArrayResize(snake_arr,3);
   //creating an object of CArrayObj class and assign it to the snake_element_obj_arr properties of CSnakeGame class
   snake_element_obj_arr=new CArrayObj();
   for(i=0;i<COUNT_COLUMNS;i++)
      for(j=0;j<COUNT_ROWS;j++)
         if(game_level[current_level][i][j]==1 || game_level[current_level][i][j]==2 || game_level[current_level][i][j]==3)
           {
            snake_element_obj=new CChartFieldElement();
            snake_element_obj.Create(0,StringFormat(SNAKE_ELEMENT_BMP_LABEL_NAME,game_level[current_level][i][j]),0,0,0);
            snake_element_obj.BmpFileOn(IMG_FILE_NAME_SNAKE_BODY);
            //specifying the internal coordinates of the snake element
            snake_element_obj.SetPos(j,i);
            snake_arr[game_level[current_level][i][j]-1]=snake_element_obj;
           }
   snake_element_obj_arr.Add(snake_arr[0]);
   snake_element_obj_arr.Add(snake_arr[1]);
   snake_element_obj_arr.Add(snake_arr[2]);
   //moving the snake on the chart
   SnakeMoveOnChart();
   //setting the correct images of the snake's head and tail
   SetTrueSnake();
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Snake movement on the chart                                      |
//+------------------------------------------------------------------+
void CSnakeGame::SnakeMoveOnChart()
  {
   CChartFieldElement *snake_element_obj;
   int i;
   i=0;
   while((snake_element_obj=snake_element_obj_arr.At(i))!=NULL)
     {
      snake_element_obj.Move(header_left,header_top+HEADER_HEIGHT);
      i++;
     }
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Snake movement on the playing field                              |
//+------------------------------------------------------------------+
void CSnakeGame::SnakeMoveOnField()
  {
   int prev_x,prev_y,next_x,next_y,check;
   CChartFieldElement *snake_head_obj,*snake_body_obj,*snake_tail_obj;
   //getting the snake's head from the array
   snake_head_obj=snake_element_obj_arr.At(0);
   //saving the coordinates of a head
   prev_x=snake_head_obj.GetPosX();
   prev_y=snake_head_obj.GetPosY();
   //setting the new internal coordinates for the head depending on the movement direction
   switch(direction)
     {
      case DIRECTION_LEFT:snake_head_obj.SetPos(prev_x-1,prev_y);break;
      case DIRECTION_UP:snake_head_obj.SetPos(prev_x,prev_y-1);break;
      case DIRECTION_RIGHT:snake_head_obj.SetPos(prev_x+1,prev_y);break;
      case DIRECTION_DOWN:snake_head_obj.SetPos(prev_x,prev_y+1);break;
     }
   //moving the snake's head
   snake_head_obj.Move(header_left,header_top+HEADER_HEIGHT);
   //check for the snake's head collision with the other playing field elements (obstacle, snake body, food)
   check=Check();
   //getting the last element of the snake's body
   snake_body_obj=snake_element_obj_arr.Detach(snake_element_obj_arr.Total()-2);
   //saving coordinates of the snake's body 
   next_x=snake_body_obj.GetPosX();
   next_y=snake_body_obj.GetPosY();
   //moving the snake's body to the previous head's position
   snake_body_obj.SetPos(prev_x,prev_y);
   snake_body_obj.Move(header_left,header_top+HEADER_HEIGHT);
   //saving the previous position of the snake's body
   prev_x=next_x;
   prev_y=next_y;
   //inserting the snake's body to the first position of the snake_element_obj_arr array
   snake_element_obj_arr.Insert(snake_body_obj,1);
   //if the snake's head has collided with the "Food"
   if(check>=CRASH_FOOD)
     {
      //creating new element of the snake's body
      snake_body_obj=new CChartFieldElement();
      snake_body_obj.Create(0,StringFormat(SNAKE_ELEMENT_BMP_LABEL_NAME,snake_element_obj_arr.Total()+1),0,0,0);
      snake_body_obj.BmpFileOn(IMG_FILE_NAME_SNAKE_BODY);
      //moving the body element to the end of the snake before the tail
      snake_body_obj.SetPos(prev_x,prev_y);
      snake_body_obj.Move(header_left,header_top+HEADER_HEIGHT);
      //adding the body to the penultimate position of the snake_element_obj_arr array
      snake_element_obj_arr.Insert(snake_body_obj,snake_element_obj_arr.Total()-1);
      //if snake's body isn't equal to the maximal snake length
      if(snake_element_obj_arr.Total()!=MAX_LENGTH_SNAKE)
        {
         //moving the eaten element on the new place on the playing field
         FoodMoveOnField(check-CRASH_FOOD);
        }
      //else we generate the custom event, that indicates that current snake length is the maximal possible
      else EventChartCustom(0,2,0,0,"");
     }
   //else if there isn't collision with the food, moving the tail to the position of the snake's body
   else
     {
      snake_tail_obj=snake_element_obj_arr.At(snake_element_obj_arr.Total()-1);
      snake_tail_obj.SetPos(prev_x,prev_y);
      snake_tail_obj.Move(header_left,header_top+HEADER_HEIGHT);
     }
   //setting the correct images for the head and tail
   SetTrueSnake();
   ChartRedraw();
   //generating the custom event for periodic call of this snake movement function
   EventChartCustom(0,0,0,0,"");
   Sleep(SPEED_SNAKE);
  }
//+------------------------------------------------------------------+
//| Setting the correct images for the snake's head and tail         |
//+------------------------------------------------------------------+
void CSnakeGame::SetTrueSnake()
  {
   CChartFieldElement *snake_head,*snake_body,*snake_tail;
   int total,x1,x2,y1,y2;
   total=snake_element_obj_arr.Total();
   //getting the snake's head
   snake_head=snake_element_obj_arr.At(0);
   //saving position of a head
   x1=snake_head.GetPosX();
   y1=snake_head.GetPosY();
   //getting the first element of the snake's body
   snake_body=snake_element_obj_arr.At(1);
   //saving coordinates of the body
   x2=snake_body.GetPosX();
   y2=snake_body.GetPosY();
   //choosing the file with an image depening on the position of the head and the first body element relative to each other
   //setting the snake's movement direction depending on the snake's head direction
   if(x1-x2==1 || x1-x2==-(COUNT_COLUMNS-1))
     {
      snake_head.BmpFileOn(IMG_FILE_NAME_SNAKE_HEAD_RIGHT);
      direction=DIRECTION_RIGHT;
     }
   else if(y1-y2==1 || y1-y2==-(COUNT_ROWS-1))
     {
      snake_head.BmpFileOn(IMG_FILE_NAME_SNAKE_HEAD_DOWN);
      direction=DIRECTION_DOWN;
     }
   else if(x1-x2==-1 || x1-x2==COUNT_COLUMNS-1)
     {
      snake_head.BmpFileOn(IMG_FILE_NAME_SNAKE_HEAD_LEFT);
      direction=DIRECTION_LEFT;
     }
   else
     {
      snake_head.BmpFileOn(IMG_FILE_NAME_SNAKE_HEAD_UP);
      direction=DIRECTION_UP;
     }
   //getting the last element of the snake's body
   snake_body=snake_element_obj_arr.At(total-2);
   //saving coordinates of the body
   x1=snake_body.GetPosX();
   y1=snake_body.GetPosY();
   //getting the tail of the snake
   snake_tail=snake_element_obj_arr.At(total-1);
   //saving coordinates of the tail
   x2=snake_tail.GetPosX();
   y2=snake_tail.GetPosY();

   //choosing the file with an image depening on the position of the tail and the last body element relative to each other
   if(x1-x2==1 || x1-x2==-(COUNT_COLUMNS-1))    snake_tail.BmpFileOn(IMG_FILE_NAME_SNAKE_TAIL_RIGHT);
   else if(y1-y2==1 || y1-y2==-(COUNT_ROWS-1))  snake_tail.BmpFileOn(IMG_FILE_NAME_SNAKE_TAIL_DOWN);
   else if(x1-x2==-1 || x1-x2==COUNT_COLUMNS-1) snake_tail.BmpFileOn(IMG_FILE_NAME_SNAKE_TAIL_LEFT);
   else                                         snake_tail.BmpFileOn(IMG_FILE_NAME_SNAKE_TAIL_UP);
  }
//+------------------------------------------------------------------+
//| Check for snake's head collision with the playing field elements |
//+------------------------------------------------------------------+
int CSnakeGame::Check()
  {
   int i;
   CChartFieldElement *snake_head_obj,*snake_element_obj,*obstacle_obj,*food_obj;
   //getting the snake's head
   snake_head_obj=snake_element_obj_arr.At(0);
   i=0;
   //check for the head's collision with the obstacle
   while((obstacle_obj=obstacle_obj_arr.At(i))!=NULL)
     {
      if(snake_head_obj.GetPosX()==obstacle_obj.GetPosX() && snake_head_obj.GetPosY()==obstacle_obj.GetPosY())
        {
         EventChartCustom(0,1,0,0,"");
         return CRASH_OBSTACLE_OR_SNAKE;
        }
      i++;
     }
   i=0;
   //check for the collision of head with the food
   while((food_obj=food_obj_arr.At(i))!=NULL)
     {
      if(snake_head_obj.GetPosX()==food_obj.GetPosX() && snake_head_obj.GetPosY()==food_obj.GetPosY())
        {
         //hiding the food
         food_obj.Background(true);
         return(CRASH_FOOD+i);
        }
      i++;
     }
   i=3;
   //check for the collision of a head with the body and tail
   while((snake_element_obj=snake_element_obj_arr.At(i))!=NULL)
     {
      //we don't check for the collision with the last snake's element, because it hasn't been moved yet
      if(snake_element_obj_arr.At(i+1)==NULL)
         break;
      if(snake_head_obj.GetPosX()==snake_element_obj.GetPosX() && snake_head_obj.GetPosY()==snake_element_obj.GetPosY())
        {
         EventChartCustom(0,1,0,0,"");
         //hiding the snake's element we have collided
         snake_element_obj.Background(true);
         return CRASH_OBSTACLE_OR_SNAKE;
        }
      i++;
     }
   return CRASH_NO;
  }
//+------------------------------------------------------------------+
//| Snake deletion                                                   |
//+------------------------------------------------------------------+
void CSnakeGame::DeleteSnake()
  {
   delete snake_element_obj_arr;
   ChartRedraw();
  }

Une fois que la tête du serpent a été déplacée, il a vérifié la collision par la fonction Check(), qui renvoie l'identifiant de collision.

La fonction SetTrueSnake() est utilisée pour indiquer le dessin correct de la tête et de la queue du serpent, en fonction de la position de leurs éléments voisins.

La nourriture pour le serpent

//+------------------------------------------------------------------+
//| Food creation                                                    |
//+------------------------------------------------------------------+
void CSnakeGame::CreateFood()
  {
   int i;
   CChartFieldElement *food_obj;
   MathSrand(uint(TimeLocal()));
   //creating an object of CArrayObj class and assign it to the food_obj_arr properties of CSnakeGame class
   food_obj_arr=new CArrayObj();
   i=0;
   while(i<COUNT_FOOD)
     {
      //creating the food
      food_obj=new CChartFieldElement;
      food_obj.Create(0,StringFormat(FOOD_BMP_LABEL_NAME,i),0,0,0);
      food_obj.BmpFileOn(IMG_FILE_NAME_FOOD);
      food_obj_arr.Add(food_obj);
      //setting the field coordinates on the field and moving it on the playing field
      FoodMoveOnField(i);
      i++;
     }
  }
//+------------------------------------------------------------------+
//| Food movement method                                             |
//+------------------------------------------------------------------+
void CSnakeGame::FoodMoveOnChart()
  {
   CChartFieldElement *food_obj;
   int i;
   i=0;
   while((food_obj=food_obj_arr.At(i))!=NULL)
     {
      food_obj.Move(header_left,header_top+HEADER_HEIGHT);
      i++;
     }
   ChartRedraw();
  }
//+---------------------------------------------------------------------------+
//| A method to set coordinates of a food and to move it on the playing field |
//+---------------------------------------------------------------------------+
void CSnakeGame::FoodMoveOnField(int food_num)
  {
   int i,j,k,n,m;
   CChartFieldElement *snake_element_obj,*food_obj;
   CChartObjectEdit *edit_obj;
   //setting a new value for "Foods left" on the status panel
   edit_obj=status_panel_obj_arr.At(1);
   edit_obj.Description(StringFormat(spaces2+FOOD_LEFT_OVER_EDIT_TEXT,MAX_LENGTH_SNAKE-snake_element_obj_arr.Total()));
   bool b;
   b=false;
   k=0;
   //generating randomly the food coordinates until the we get the free cells
   while(true)
     {
      //generating a row number
      i=(int)(MathRand()/32767.0*(COUNT_ROWS-1));
      //generating a column number
      j=(int)(MathRand()/32767.0*(COUNT_COLUMNS-1));
      n=0;
      //check, if there are any elements of the snake
      while((snake_element_obj=snake_element_obj_arr.At(n))!=NULL)
        {
         if(j!=snake_element_obj.GetPosX() && i!=snake_element_obj.GetPosY())
            b=true;
         else
           {
            b=false;
            break;
           }
         n++;
        }
      //checking for the other food presence
      if(b==true)
        {
         n=0;
         while((food_obj=food_obj_arr.At(n))!=NULL)
           {
            if(j!=food_obj.GetPosX() && i!=food_obj.GetPosY())
               b=true;
            else
              {
               b=false;
               break;
              }
            n++;
           }
        }
      //checking for the presence of the obstacle
      if(b==true && game_level[current_level][i][j]!=9) break;
      k++;
     }
   food_obj=food_obj_arr.At(food_num);
   //show food
   food_obj.Background(false);
   //setting new coordinates
   food_obj.SetPos(j,i);
   //moving the food
   food_obj.Move(header_left,header_top+HEADER_HEIGHT);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Food deletion                                                    |
//+------------------------------------------------------------------+
void CSnakeGame::DeleteFood()
  {
   delete food_obj_arr;
   ChartRedraw();
  }

L'emplacement de la nourriture sur le terrain de jeu est déterminé de manière aléatoire, à condition que le champ de la cellule, où se trouvera la nourriture, ne comporte pas d'autres éléments.

Le Panneau d' Etat

//+------------------------------------------------------------------+
//| Status Panel Creation                                            |
//+------------------------------------------------------------------+
void CSnakeGame::CreateStatusPanel()
  {
   CChartObjectEdit *edit_obj;  
   //creating an object of CArrayObj class and assign it to the status_panel_obj_arr properties of CSnakeGame class
   status_panel_obj_arr=new CArrayObj();
   //creating the "Level" edit
   edit_obj=new CChartObjectEdit;
   edit_obj.Create(0,LEVEL_EDIT_NAME,0,0,0,CONTROL_WIDTH,CONTROL_HEIGHT);
   edit_obj.BackColor(STATUS_BACKGROUND);
   edit_obj.Color(STATUS_COLOR);
   edit_obj.Description(StringFormat(spaces6+LEVEL_EDIT_TEXT,current_level,MAX_LEVEL));
   edit_obj.Selectable(false);
   edit_obj.ReadOnly(true);
   status_panel_obj_arr.Add(edit_obj);
   //creating the "Food left over" edit
   edit_obj=new CChartObjectEdit;
   edit_obj.Create(0,FOOD_LEFT_OVER_EDIT_NAME,0,0,0,CONTROL_WIDTH,CONTROL_HEIGHT);
   edit_obj.BackColor(STATUS_BACKGROUND);
   edit_obj.Color(STATUS_COLOR);
   edit_obj.Description(StringFormat(spaces2+FOOD_LEFT_OVER_EDIT_TEXT,MAX_LENGTH_SNAKE-3));
   edit_obj.Selectable(false);
   edit_obj.ReadOnly(true);
   status_panel_obj_arr.Add(edit_obj);
   //creating the "Lives" edit
   edit_obj=new CChartObjectEdit;
   edit_obj.Create(0,LIVES_EDIT_NAME,0,0,0,CONTROL_WIDTH,CONTROL_HEIGHT);
   edit_obj.BackColor(STATUS_BACKGROUND);
   edit_obj.Color(STATUS_COLOR);
   edit_obj.Description(StringFormat(spaces8+LIVES_EDIT_TEXT,current_lives));
   edit_obj.Selectable(false);
   edit_obj.ReadOnly(true);
   status_panel_obj_arr.Add(edit_obj);
   //moving the status panel
   StatusPanelMoveOnChart();
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Status Panel movement method                                     |
//+------------------------------------------------------------------+
void CSnakeGame::StatusPanelMoveOnChart()
  {
   CChartObjectEdit *edit_obj;
   int x,y,i;
   x=header_left;
   y=header_top+HEADER_HEIGHT+COUNT_ROWS*(SQUARE_HEIGHT-1)+1;
   i=0;
   while((edit_obj=status_panel_obj_arr.At(i))!=NULL)
     {
      edit_obj.X_Distance(x+i*CONTROL_WIDTH);
      edit_obj.Y_Distance(y);
      i++;
     }
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Status Panel deletion method                                     |
//+------------------------------------------------------------------+
void CSnakeGame::DeleteStatusPanel()
  {
   delete status_panel_obj_arr;
   ChartRedraw();
  }

Panneau de Contrôle

//+------------------------------------------------------------------+
//| Control Panel creation method                                    |
//+------------------------------------------------------------------+
void CSnakeGame::CreateControlPanel()
  {
   CChartObjectButton *button_obj;
   //creating an object of CArrayObj class and assign it to the control_panel_obj_arr properties of CSnakeGame class
   control_panel_obj_arr=new CArrayObj();
   //creating the "Start" button
   button_obj=new CChartObjectButton;
   button_obj.Create(0,START_GAME_BUTTON_NAME,0,0,0,CONTROL_WIDTH,CONTROL_HEIGHT);
   button_obj.BackColor(CONTROL_BACKGROUND);
   button_obj.Color(CONTROL_COLOR);
   button_obj.Description(START_GAME_BUTTON_TEXT);
   button_obj.Selectable(false);
   control_panel_obj_arr.Add(button_obj);
   //creating the "Pause" button
   button_obj=new CChartObjectButton;
   button_obj.Create(0,PAUSE_GAME_BUTTON_NAME,0,0,0,CONTROL_WIDTH,CONTROL_HEIGHT);
   button_obj.BackColor(CONTROL_BACKGROUND);
   button_obj.Color(CONTROL_COLOR);
   button_obj.Description(PAUSE_GAME_BUTTON_TEXT);
   button_obj.Selectable(false);
   control_panel_obj_arr.Add(button_obj);
   //creating the "Stop" button
   button_obj=new CChartObjectButton;
   button_obj.Create(0,STOP_GAME_BUTTON_NAME,0,0,0,CONTROL_WIDTH,CONTROL_HEIGHT);
   button_obj.BackColor(CONTROL_BACKGROUND);
   button_obj.Color(CONTROL_COLOR);
   button_obj.Description(STOP_GAME_BUTTON_TEXT);
   button_obj.Selectable(false);
   control_panel_obj_arr.Add(button_obj);
   //moving the control panel
   ControlPanelMoveOnChart();
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Control Panel movement method                                    |
//+------------------------------------------------------------------+
void CSnakeGame::ControlPanelMoveOnChart()
  {
   CChartObjectButton *button_obj;
   int x,y,i;
   x=header_left;
   y=header_top+HEADER_HEIGHT+COUNT_ROWS*(SQUARE_HEIGHT-1)+1;
   i=0;
   while((button_obj=control_panel_obj_arr.At(i))!=NULL)
     {
      button_obj.X_Distance(x+i*CONTROL_WIDTH);
      button_obj.Y_Distance(y+CONTROL_HEIGHT);
      i++;
     }
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Control Panel deletion method                                    |
//+------------------------------------------------------------------+
void CSnakeGame::DeleteControlPanel()
  {
   delete control_panel_obj_arr;
   ChartRedraw();
  }

L'Initialisation du Jeu, la Désinitialisation et le Mouvement des Éléments de Jeu

//+------------------------------------------------------------------+
//| Game elements movement method                                    |
//+------------------------------------------------------------------+
void  CSnakeGame::AllMoveOnChart()
  {
   FieldMoveOnChart();
   StatusPanelMoveOnChart();
   ControlPanelMoveOnChart();
   ObstacleMoveOnChart();
   SnakeMoveOnChart();
   FoodMoveOnChart();
  }
//+------------------------------------------------------------------+
//| Game initialization                                              |
//+------------------------------------------------------------------+
void CSnakeGame::Init()
  {
   CreateHeader();
   CreateField();
   CreateStatusPanel();
   CreateControlPanel();
   CreateObstacle();
   CreateSnake();
   CreateFood();
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Game deinitialization                                            |
//+------------------------------------------------------------------+
void  CSnakeGame::Deinit()
  {
   DeleteFood();
   DeleteSnake();
   DeleteObstacle();
   DeleteControlPanel();
   DeleteStatusPanel();
   DeleteField();
   DeleteHeader();
  }

Le contrôle du jeu

//+------------------------------------------------------------------+
//| Dummy Start game method                                          |
//+------------------------------------------------------------------+
void CSnakeGame::StartGame()
  {
   return;
  }
//+------------------------------------------------------------------+
//| Dummy Pause game method                                          |
//+------------------------------------------------------------------+
void CSnakeGame::PauseGame()
  {
   return;
  }
//+------------------------------------------------------------------+
//| Stop game method                                                 |
//+------------------------------------------------------------------+
void CSnakeGame::StopGame()
  {
   CChartObjectEdit *edit_obj;
   current_level=0;
   current_lives=LIVES_SNAKE;
   //setting new value for the "Level" field of the status panel
   edit_obj=status_panel_obj_arr.At(0);
   edit_obj.Description(StringFormat(spaces6+LEVEL_EDIT_TEXT,current_level,MAX_LEVEL));
   //setting new value for the "Lives" field of the status panel
   edit_obj=status_panel_obj_arr.At(2);
   edit_obj.Description(StringFormat(spaces8+LIVES_EDIT_TEXT,current_lives));
   DeleteFood();
   DeleteSnake();
   DeleteObstacle();
   CreateObstacle();
   CreateSnake();
   CreateFood();
  }
//+------------------------------------------------------------------+
//| Level restart method                                             |
//+------------------------------------------------------------------+
void CSnakeGame::ResetGame()
  {
   CChartObjectEdit *edit_obj;
   if(current_lives-1==-1)StopGame();
   else
     {
      //decreasing the number of lives
      current_lives--;
      //updating it at the status panel
      edit_obj=status_panel_obj_arr.At(2);
      edit_obj.Description(StringFormat(spaces8+LIVES_EDIT_TEXT,current_lives));
      DeleteFood();
      DeleteSnake();
      CreateSnake();
      CreateFood();
     }
  }
//+------------------------------------------------------------------+
//| Next level method                                                |
//+------------------------------------------------------------------+
void CSnakeGame::NextLevel()
  {
   CChartObjectEdit *edit_obj;
   current_lives=LIVES_SNAKE;
   //to the initial level if there isn't next level
   if(current_level+1>MAX_LEVEL)StopGame();
   else
     {
      //else increasing the level and updating the startus panel contents
      current_level++;
      edit_obj=status_panel_obj_arr.At(0);
      edit_obj.Description(StringFormat(spaces6+LEVEL_EDIT_TEXT,current_level,MAX_LEVEL));
      edit_obj=status_panel_obj_arr.At(2);
      edit_obj.Description(StringFormat(spaces8+LIVES_EDIT_TEXT,current_lives));
      DeleteFood();
      DeleteSnake();
      DeleteObstacle();
      CreateObstacle();
      CreateSnake();
      CreateFood();
     }
  }

La Gestion des Événements (code final)

// Declaring and creating an object of CSnakeGame type at global level
CSnakeGame snake_field;    
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   snake_field.Init();
   EventSetTimer(1);
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   snake_field.Deinit();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTimer()
  {
   //setting the buttons unpressed
   if(ObjectFind(0,START_GAME_BUTTON_NAME)>=0 && ObjectGetInteger(0,START_GAME_BUTTON_NAME,OBJPROP_STATE)==true)
      ObjectSetInteger(0,START_GAME_BUTTON_NAME,OBJPROP_STATE,false);
   if(ObjectFind(0,PAUSE_GAME_BUTTON_NAME)>=0 && ObjectGetInteger(0,PAUSE_GAME_BUTTON_NAME,OBJPROP_STATE)==true)
      ObjectSetInteger(0,PAUSE_GAME_BUTTON_NAME,OBJPROP_STATE,false);
   if(ObjectFind(0,STOP_GAME_BUTTON_NAME)>=0 && ObjectGetInteger(0,STOP_GAME_BUTTON_NAME,OBJPROP_STATE)==true)
      ObjectSetInteger(0,STOP_GAME_BUTTON_NAME,OBJPROP_STATE,false);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   long x,y;
   static bool press_key=true;
   static bool press_button=false;
   static bool move=false;
   //if key has been pressed and the snake has moved, let's specify the new movement direction
   if(id==CHARTEVENT_KEYDOWN && press_key==false)
     {
      if((lparam==VK_LEFT) && (snake_field.GetDirection()!=DIRECTION_LEFT && snake_field.GetDirection()!=DIRECTION_RIGHT))
         snake_field.SetDirection(DIRECTION_LEFT);
      else if((lparam==VK_RIGHT) && (snake_field.GetDirection()!=DIRECTION_LEFT && snake_field.GetDirection()!=DIRECTION_RIGHT))
         snake_field.SetDirection(DIRECTION_RIGHT);
      else if((lparam==VK_DOWN) && (snake_field.GetDirection()!=DIRECTION_UP && snake_field.GetDirection()!=DIRECTION_DOWN))
         snake_field.SetDirection(DIRECTION_DOWN);
      else if((lparam==VK_UP) && (snake_field.GetDirection()!=DIRECTION_UP && snake_field.GetDirection()!=DIRECTION_DOWN))
         snake_field.SetDirection(DIRECTION_UP);
      press_key=true;
     }
   //if "Start" button has been pressed and press_button=false
   if(id==CHARTEVENT_OBJECT_CLICK && sparam==START_GAME_BUTTON_NAME && press_button==false)
     {
         //waiting 1 second
         Sleep(1000);
         //generating new event for snake movement
         EventChartCustom(0,0,0,0,"");
         press_button=true;
     }
   //if "Pause" button has been pressed
   else if(id==CHARTEVENT_OBJECT_CLICK && sparam==PAUSE_GAME_BUTTON_NAME)
     {
      press_button=false;
     }
   //if "Stop" button has been pressed
   else if(id==CHARTEVENT_OBJECT_CLICK && sparam==STOP_GAME_BUTTON_NAME)
     {
      snake_field.StopGame();
      press_key=true;
      press_button=false;
     }
   //processing of the snake movement event, if press_button=true
   else if(id==CHARTEVENT_CUSTOM && press_button==true)
     {
      snake_field.SnakeMoveOnField();
      press_key=false;
     }
   //processing of the game restart event
   else if(id==CHARTEVENT_CUSTOM+1)
     {
      snake_field.ResetGame();
      Sleep(1000);
      press_key=true;
      press_button=false;
     }
   //processing of the next level event
   else if(id==CHARTEVENT_CUSTOM+2)
     {
      snake_field.NextLevel();
      Sleep(1000);
      press_key=true;
      press_button=false;
     }
   //processing of the header movement event
   else if(id==CHARTEVENT_OBJECT_DRAG && sparam==HEADER_BUTTON_NAME)
     {
      x=ObjectGetInteger(0,sparam,OBJPROP_XDISTANCE);
      y=ObjectGetInteger(0,sparam,OBJPROP_YDISTANCE);
      snake_field.SetHeaderPos(x,y);
      snake_field.AllMoveOnChart();
     }
  }
//+------------------------------------------------------------------+

Press_key et press_button sont deux variables statiques, définies dans la fonction de gestionnaire d'événements OnChartEvent.

Le démarrage du jeu est autorisé si la variable press_button est fausse. Après le clic sur le bouton "Démarrer", la variable press_button est mise à true (elle interdit la ré-exécution du code qui démarre le jeu), cet état reste le même jusqu'à l'un des événements suivants :

  • Redémarrage du niveau actuel ;
  • Passer au niveau suivant ;
  • La pause du jeu (le bouton « Pause » a été enfoncé) ;
  • Le jeu s'arrête (le bouton "Stop" a été enfoncé).

Le changement de direction du mouvement du serpent est possible s'il est perpendiculaire à la direction actuelle, ainsi qu'après que le serpent s'est déplacé sur le terrain de jeu (la valeur de la variable press_key l'indique) Ces conditions sont prises en compte dans la fonction de traitement événementiel CHARTEVENT_KEYDOWN (événement appui touche).

Ensuite, vous déplacez l'en-tête, l'événement CHARTEVENT_OBJECT_DRAG est généré, les champs header_left et header_top de la classe CSnakeGame sont redéfinis. Le mouvement des autres éléments du jeu est déterminé par les valeurs de ces champs.

Le mouvement du terrain de jeu est mis en œuvre de la manière présentée dans le TradePad_Sample.

Conclusion

Dans cet article, nous avons considéré un exemple de rédaction de jeux en MQL5.

Nous avons introduit les classes de Bibliothèque Standard (les classes de contrôle), la classe CArrayObj, et avons également appris à effectuer l'appel de fonction périodique avec la gestion des événements.

Une archive avec les codes sources du jeu "Snake" peut être téléchargée depuis la référence ci-dessous.. L'archive doit être décompressée dans le dossier terminal_data_folder..

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/65

Fichiers joints |
snake-mql5-doc.zip (405.1 KB)
Transfert d'indicateurs de MQL4 vers MQL5 Transfert d'indicateurs de MQL4 vers MQL5
Cet article est dédié aux spécificités du transfert des constructions de prix rédigées en MQL4 vers MQL5. Pour rendre le processus de transfert des calculs d'indicateurs de MQL4 à MQL5 plus facile, la bibliothèque de fonctions mql4_2_mql5.mqh est suggérée. Son usage est décrit sur la base du transfert des indicateurs MACD, Stochastique et RSI.
Connexion de l'Expert Advisor avec ICQ en MQL5 Connexion de l'Expert Advisor avec ICQ en MQL5
Cet article décrit la méthode d'échange d'informations entre l'Expert Advisor et les utilisateurs d' ICQ, plusieurs exemples sont présentés. La documentation fournie sera intéressante pour ceux qui souhaitent recevoir des informations de trading à distance depuis un terminal client, via un client ICQ dans leur téléphone mobile ou PDA.
Application Pratique des Bases de Données pour l'Analyse des Marchés Application Pratique des Bases de Données pour l'Analyse des Marchés
Travailler avec des données est devenu la tâche principale des logiciels modernes, à la fois pour les applications autonomes et en réseau. Pour résoudre ce problème, un logiciel spécialisé a été créé. Ce sont des Systèmes de Gestion de Bases de Données (SGBD), qui peuvent structurer, systématiser et organiser les données pour leur stockage et leur traitement informatique. Quant au trading, la plupart des analystes n'utilisent pas de bases de données dans leur travail. Mais il y a des tâches où une telle solution devrait être pratique. Cet article fournit un exemple d'indicateurs, qui peuvent enregistrer et charger des données à partir de bases de données à la fois avec des architectures client-serveur et serveur de fichiers.
Création de Panneaux de Contrôle Actifs dans MQL5 pour le Trading Création de Panneaux de Contrôle Actifs dans MQL5 pour le Trading
L'article traite du problème d’élaboration de panneaux de contrôle actifs en MQL5. Les éléments d'interface sont gérés par le mécanisme de gestion des événements. En outre, l'option d'une configuration flexible des propriétés des éléments de contrôle est disponible. Le panneau de contrôle actif permet de travailler avec des positions, ainsi que de définir, modifier et supprimer des commandes de marché et en cours.