Créer un jeu "Serpent" en MQL5
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 :
- 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.
- 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.
- 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.
- 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 :
- Le mouvement de la tête de cellule vers la droite, la gauche, le haut ou le bas selon la direction.
- Le mouvement du dernier élément d'un corps de serpent sur la place de la tête précédente.
- 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 :
- Le CArrayObj est la classe pour l'organisation des données (tableau dynamique de pointeurs).
- 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
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation