English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Creazione di un Gioco "Snack" in MQL5

Creazione di un Gioco "Snack" in MQL5

MetaTrader 5Expert Advisors | 16 dicembre 2021, 10:18
63 0
MRoVas
[Eliminato]

Introduzione

In questo articolo considereremo un esempio di scrittura di un gioco "Snake" in MQL5.

Dalla quinta versione di MQL, la programmazione del gioco è diventata possibile, principalmente grazie alle funzionalità di elaborazione degli eventi, inclusi gli eventi personalizzati. La programmazione orientata agli oggetti semplifica la progettazione di tali programmi, rende il codice più chiaro e riduce il numero di errori.

Dopo la lettura di questo articolo, imparerai a conoscere l'elaborazione degli eventi OnChart, esempi di utilizzo delle classi della libreria standard MQL5 e ricette per chiamate cicliche della funzione dopo un certo tempo per eseguire eventuali calcoli.

Descrizione del Gioco

Il gioco "Snake" è stato scelto come esempio, principalmente, per la semplicità della sua implementazione. Tutti coloro che hanno avuto un grande interesse per la programmazione sono in grado di scrivere questo gioco.

Secondo Wikipedia:

Snake è un videogioco uscito per la prima volta a metà degli anni '70 nelle sale giochi e da allora ha mantenuto la popolarità, diventando un po' un classico.

Il giocatore controlla una creatura lunga e sottile, simile a un serpente, che si aggira su un piano delimitato, raccogliendo cibo (o qualche altro oggetto), cercando di evitare di colpire la propria coda o le "pareti" che circondano l'area di gioco. In alcune varianti sul campo ci sono ostacoli aggiuntivi. Ogni volta che il serpente mangia un pezzo di cibo, la sua coda si allunga, rendendo il gioco sempre più difficile. L'utente controlla la direzione della testa del serpente (su, giù, sinistra o destra) e il corpo del serpente segue. Il giocatore non può impedire al serpente di muoversi mentre il gioco è in corso e non può far andare il serpente al contrario.

Questa implementazione di "Snake" in MQL5 avrà alcune limitazioni e funzionalità.

Il numero di livelli è pari a 6 (da 0 a 5). Ci sono 5 vite disponibili per ogni livello. Dopo aver utilizzato tutte le vite o dopo aver superato con successo tutti i livelli, il gioco tornerà al livello iniziale. Puoi creare i tuoi livelli. La velocità del serpente e la sua lunghezza massima sono le stesse per ogni livello.

Il campo di gioco è composto da 4 elementi:

  1. Titolo del gioco. Viene utilizzato per il posizionamento del gioco sul grafico. Spostando il titolo, vengono spostati tutti gli elementi del gioco.
  2. Campo da gioco. È un array (tabella) di celle con dimensioni 20x20. Ogni cella ha una dimensione di 20x20 pixel. Gli elementi sul campo di gioco sono:
    • Il Serpente. Consiste di almeno tre elementi consecutivi: testa, corpo e coda. La testa può essere spostata a sinistra, a destra, in alto e in basso. Tutti gli altri elementi serpente vengono spostati dopo la testa. 
    • L'Ostacolo. È rappresentato come un rettangolo grigio, in caso di collisione della testa di serpente con l'ostacolo, il livello corrente viene riavviato e il numero di vite viene ridotto di 1.
    • Il Cibo. Il cibo è presentato dalla bacca, in caso di collisione della testa del serpente con il cibo, le dimensioni del snake (lunghezza del suo corpo) sono aumentate. Dopo aver mangiato 12 pezzi, il snake passa al livello successivo.
  3. Il Pannello delle Informazioni (barra di stato del gioco). Si compone di tre elementi:
    • Livello. Mostra il livello attuale.
    • Cibo avanzato. Mostra la quantità di bacche da mangiare.
    • Vite. Mostra il numero di vite disponibili.
  4. Pannello Si compone di tre pulsanti:
    • Pulsante "Avvia". Avvia il livello corrente.
    • Il pulsante "Pausa". Mette in pausa il gioco.
    • Il pulsante "Stop". Interrompe il gioco, mentre la transizione avviene al livello iniziale.

Tutti questi elementi possono essere visti in Figura 1:


Fig. 1. Elementi del gioco "Snake"

Il titolo del gioco è un oggetto di tipo "Pulsante". Tutti gli elementi del campo di gioco sono oggetti di tipo "BmpLabel". Il pannello Informazioni è composto da tre oggetti di tipo "Modifica", il Pannello di controllo è composto da tre oggetti di tipo "Pulsante". Tutti gli oggetti sono posizionati per definizione delle distanze lungo X e Y in pixel rispetto all'angolo superiore sinistro del grafico.

Va notato che i bordi del campo di gioco non sono un ostacolo per il movimento del serpente. Ad esempio, il serpente passa attraverso il bordo sinistro, appare a destra. Si può vedere in Figura 2:


Figura 2. Passaggio del serpente attraverso il bordo del campo di gioco

La testa e la coda del serpente, a differenza del corpo, possono essere ruotate. La direzione della testa è determinata dalla direzione del movimento del serpente o dalla posizione dei suoi elementi vicini. La direzione della coda è determinata solo dalla posizione dell'elemento vicino. 

Ad esempio, se l'elemento di coda vicino si trova sul lato sinistro, la coda viene ruotata a sinistra. Un po' diversamente è con la testa. La testa è girata a sinistra, se il suo elemento vicino è al lato destro. Gli esempi di direzione della testa e della coda sono presentati nelle figure seguenti. Presta attenzione al giro della testa e della coda rispetto ai loro elementi vicini.





La testa e la coda sono dirette a sinistra La testa e la coda sono dirette a destra La testa e la coda sono dirette verso il basso La testa e la coda sono dirette verso l'alto


Il movimento del serpente si svolge in tre fasi:

  1. Il movimento della testa di una cella a destra, a sinistra, in alto o in basso a seconda della direzione.
  2. Il movimento dell'ultimo elemento di un corpo di serpente sul precedente posto di testa.
  3. Spostando la coda del serpente sul punto precedente dell'ultimo elemento del corpo. Il movimento della coda del serpente sul punto precedente dell'ultimo elemento del corpo del serpente.

Se il serpente mangia il cibo, la coda non si muove. Invece, viene creato un nuovo elemento del corpo, che si è spostato sul posto passato dell'ultimo elemento del corpo del serpente. 

Un esempio di movimento del serpente a sinistra è presentato nelle figure seguenti:





Posizione iniziale  Una cella al movimento della testa a sinistra
Movimento dell'ultimo elemento del corpo
al posto precedente della testa
Movimento della coda sul posto passato
dell'ultimo elemento del corpo

Teoria 

Successivamente discuteremo degli strumenti e delle tecniche che vengono utilizzati durante la scrittura dei giochi.

La Libreria Standard MQL5

È conveniente utilizzare gli array di oggetti dello stesso tipo (ad esempio, celle del campo di gioco, elementi serpente) per manipolarli (creare, spostare, eliminare). Questi array e oggetti possono essere implementati utilizzando le classi della libreria standard MQL5.

L'utilizzo delle classi della Libreria Standard MQL5 permette di semplificare il processo di scrittura dei programmi. Per il gioco, utilizzeremo le seguenti classi di libreria:

  1. Il CArrayObj è una classe per l'organizzazione dei dati (array dinamico di puntatori).
  2. Le CChartObjectEdit , CChartObjectButton , CChartObjectBmpLabel - sono classi di controllo, che rappresentano rispettivamente "Edit", "Button" e "BmpLabel".

Per utilizzare le classi della Libreria Standard MQL5, è necessario includerle utilizzando la seguente direttiva del compilatore:

#include <path_to_the_file_with_classes_description>

Ad esempio, per l'utilizzo di oggetti di tipo CChartObjectButton dobbiamo scrivere:

#include <ChartObjects\ChartObjectsTxtControls.mqh>

I percorsi dei file possono essere trovati nel Reference MQL5.

Quando si lavora con le classi della Libreria Standard MQL5 è importante capire che alcune di esse si ereditano a vicenda. Ad esempio, la classe CChartObjectButton eredita la classe CChartObjectEdit, a sua volta la classe CChartObjectEdit eredita la classe CChatObjectLabel, ecc. Ciò significa che per le classi derivate sono disponibili le proprietà e i metodi della classe padre.

Per comprendere i vantaggi dell'utilizzo delle classi della Libreria Standard MQL5, consideriamo un esempio di creazione di pulsanti e lo implementiamo in due modi (senza e con l'uso delle classi).

Ecco un esempio senza l'uso delle classi:

//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 esempio con l'uso delle classi:

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");

Si può vedere, è più semplice lavorare con le classi. Inoltre, gli oggetti classe possono essere archiviati in array e gestiti facilmente.

I metodi e le proprietà delle classi dei controlli Object sono buoni e chiaramente descritto nelle classi MQL5 Reference to the Standard Library.

Utilizzeremo la classe CArrayObj della Standard Library per organizzare l'array di oggetti, permette di sgravare l'utente da molte operazioni di routine (come il ridimensionamento dell'array quando si aggiunge un nuovo elemento, la cancellazione di oggetti nell'array, ecc.)

Caratteristiche della Classe CARrayObj

La classe CArrayObj permette di organizzare un array dinamico di puntatori agli oggetti di tipo classe CObject. Il CObject è una classe padre per tutte le classi della Libreria Standard. Significa che possiamo creare un array dinamico di puntatori agli oggetti di qualsiasi classe delle classi della Libreria Standard tge. Se è necessario creare un array dinamico di oggetti della propria classe, dovrebbe essere ereditato dalla classe CObject.

Nell'esempio seguente, il compilatore non stamperà errori, perché la classe personalizzata è il successore della 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);

Per il caso successivo, il compilatore genererà un errore, perché my_obj non è un puntatore alla classe CObject o una classe che eredita 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);

Durante la scrittura il gioco utilizzerà i seguenti metodi della classe CArrayObj:

  • Aggiungi: aggiunge un elemento alla fine dell'array.
  • Inserisci - Inserisce un elemento nella posizione specificata dell’array.
  • Detach: elimina l'elemento nella posizione specificata (l'elemento viene rimosso dall'array).
  • Totale - Ottiene il numero di elementi nell'array.
  • A- Ottiene l'elemento nella posizione specificata (l'elemento non viene rimosso dall'array).

Ecco un esempio di lavoro con 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);
  }

In questo esempio, la funzione OnInit crea un array dinamico con tre elementi. L'output del contenuto dell'array viene eseguito dalla chiamata della funzione MyPrint.

Dopo aver riempito l'array utilizzando il metodo Aggiungi, il suo contenuto può essere rappresentato come (a, b, c). 

Dopo aver applicato il metodo Inserisci, il contenuto dell'array può essere rappresentato come (a, d, b, c).

Infine, dopo aver applicato il metodo Detach, l'array apparirà come (a, d, c).

Quando l'operator delete viene applicato alla variabile array_obj, viene chiamato il desctructor di classe CArrayObj, che rimuove non solo l'array array_obj, ma anche gli oggetti i cui puntatori sono memorizzati in esso. Per evitarlo, prima di applicare il comando delete, il flag di gestione della memoria della classe CArrayObj dovrebbe essere impostato su false. Questo flag è impostato dal metodo FreeMode.

Se non è necessario eliminare gli oggetti, i cui puntatori sono memorizzati nell'array dinamico quando si rimuove un array dinamico di puntatori agli oggetti, è necessario scrivere il codice seguente:

array_obj.FreeMode(false);
delete array_obj;

Gestione degli Eventi

Se viene generato un insieme di eventi, si accumulano nella coda, quindi arrivano in modo coerente alla funzione di elaborazione degli eventi.

Per la gestione degli eventi generati quando si lavora con il grafico, così come gli eventi personalizzati, MQL5 ha la funzione OnChartEvent. Ogni evento ha un identificatore e parametri, passati alla funzione OnChartEvent

La funzione OnChartEvent viene chiamata solo quando il thread è fuori da tutte le altre funzioni del programma. Pertanto, nell'esempio seguente, OnChartEvent non otterrà mai il controllo.

#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");
  }

Un ciclo while infinito non consente il ritorno dalla funzione MyFunction. La funzione OnChartEvent non può ottenere il controllo. Pertanto, la pressione del pulsante non richiama la funzione Alert.

Esecuzione Periodica del Codice con la Gestione degli Eventi

Nel gioco, è necessaria la chiamata periodica della funzione di movimento del serpente con la capacità di gestire gli eventi dopo un certo intervallo di tempo. Ma come è stato mostrato sopra, un ciclo infinito porta al fatto che la funzione OnChartEvent non viene chiamata e la gestione degli eventi diventa impossibile.

Quindi è necessario inventare un altro modo di esecuzione periodica del codice.

Utilizzo di OnTimer

Il linguaggio MQL5 dispone di una speciale funzione OnTimer, che viene richiamata periodicamente in base al numero di secondi predefinito. Per farlo, utilizzeremo la funzione EventSetTimer.

L'esempio precedente può essere riscritto come:

#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");
  }

Nella funzione OnInit, il pulsante ha creato e definito un punto pari a un secondo, per la chiamata della funzione OnTimer, La chiamata della funzione OnTimer viene eseguita ogni secondo, la funzione OnTimer chiama il codice (MyFunction), che dovrebbe essere eseguito periodicamente.

Prestare attenzione che la chiamata della funzione OnTimer è multipla di secondi. Per chiamare la funzione dopo un numero specificato di millisecondi, è necessario l'altro metodo. Questo metodo è l'uso di eventi personalizzati.

Utilizzo degli Eventi Personalizzati

L'evento personalizzato è generato dalla funzione EventChartCustom, l'ID evento e i relativi parametri sono definiti nei parametri di input della funzione EventChartCustom. Il numero di ID personalizzati può essere fino a 65536 - da 0 a 65535. Il compilatore MQL5 aggiunge automaticamente l'identificatore costante CHARTEVENT_CUSTOM all'ID per distinguere gli eventi personalizzati dagli altri tipi di eventi. Pertanto, l'intervallo effettivo degli ID personalizzati va da CHARTEVENT_CUSTOM a CHARTEVENT_CUSTOM +65535 ( CHARTEVENT_CUSTOM_LAST ).

Di seguito viene presentato un esempio di chiamata periodica di MyFunction utilizzando gli eventi personalizzati:

#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();
  }

In questo esempio, prima della funzione MyFunction c'è un ritardo di 200 ms (il tempo della chiamata periodica di questa funzione) e viene generato un evento personalizzato. La funzione OnChartEvent gestisce tutti gli eventi, nel caso dell'evento custom richiama nuovamente la funzione MyFunction. Pertanto, la chiamata periodica della funzione MyFunction viene implementata in questo modo ed è possibile impostare il periodo di chiamata pari a millisecondi.

Parte Pratica

Consideriamo un esempio di scrittura di un gioco "Snake".

Definizione della mappa delle costanti e dei livelli

Mappa i livelli in un file include (intestazione) separato "Snake.mqh" ed è un livello di array tridimensionale [6] [20] [20]. La mappa dei livelli si trova in un file di intestazione separato "Snake.mqh" ed è rappresentata come un array tridimensionale game_level[6][20][20]. Ogni elemento di questo array è un array bidimensionale, che contiene la descrizione del singolo livello. Se il valore di un elemento è uguale a 9, è un ostacolo. Se il valore di un elemento dell'array è uguale a 1,2 o 3, è rispettivamente la testa, il corpo o la coda del serpente che definisce la sua posizione iniziale sul campo di gioco. È possibile aggiungere nuovi livelli o modificare quelli esistenti all'array di livelli.

Inoltre, il file "Snake.mqh" contiene le costanti utilizzate nel gioco. Ad esempio, modificando i costanti SPEED_SNAKE e MAX_LENGTH_SNAKE, puoi aumentare/diminuire la velocità del serpente e la sua lunghezza massima ad ogni livello. Tutte le costanti sono commentate.

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

Nota, la definizione della costante #define SQUARE_BMP_LABEL_NAME "snake_square_% u_% U". Creeremo il campo da gioco. Ogni cella del campo di gioco è un'etichetta bitmap, dovrebbe avere un nome univoco. Il nome di una cella è definito da questa costante, la specifica del formato di un nome di cella è %u, significa l'intero senza segno.

Se specificherai il nome durante la creazione di BmpLabel come: StringFormat (SQUARE_BMP_LABEL_NAME, 1,0), il nome sarà uguale a "snake_square_1_0".

Le Classi

Sono state sviluppate due classi personalizzate per il gioco, si trovano nel file "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 eredita la classe CChartObjectBmpLabel, quindi la estende. Tutto il campo di gioco, come barriera cellulare, testa, corpo e coda del serpente, e il "cibo" sono gli oggetti di questa classe. Le proprietà pos_x e pos_y sono coordinate relative degli elementi sul campo di gioco, ovvero gli indici di riga e colonna dell'elemento. Il metodo SetPos imposta queste coordinate. Il metodo Move converte le coordinate relative alle distanze lungo gli assi X e Y in pixel e sposta l'elemento. Per farlo, chiama i metodi X_Distance e YDistancedella classe 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();
  };

Il CSnakeGame è la classe principale del gioco, contiene i campi ei metodi di creazione, spostamento e rimozione degli elementi di gioco. Si possono vedere, all'inizio della descrizione della classe, i campi per cui viene dichiarata l'organizzazione di array dinamici di puntatori degli elementi di gioco. Ad esempio, i puntatori degli elementi snake sono memorizzati nel campo snake_element_obj_arr. L'indice dell'array zero dell'array snake_element_obj_arr sarà una testa di serpente e l'ultimo - la sua coda. Sapendolo, puoi facilmente manipolare il serpente sul campo di gioco.

Consideriamo tutti i metodi della classe CSnakeGame. I metodi sono implementati sulla base della teoria, presentata nel capitolo "Teoria" di questo articolo.

L'intestazione del gioco


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

Il campo da gioco

//+------------------------------------------------------------------+
//| 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();
  }

Gli Ostacoli

//+------------------------------------------------------------------+
//| 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();
  }

Il Serpente

//+------------------------------------------------------------------+
//| 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();
  }

Dopo che la testa del serpente è stata spostata, ha verificato la collisione con la funzione Check(), che restituisce l'identificatore di collisione.

La funzione SetTrueSnake() viene utilizzata per specificare il disegno corretto della testa e della coda del serpente, a seconda della posizione degli elementi vicini.

Il Cibo per il Serpente

//+------------------------------------------------------------------+
//| 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();
  }

La posizione del cibo sul campo di gioco è stabilita in modo casuale, a condizione che il campo della cella, in cui verrà posizionato il cibo, non contenga altri elementi.

Il Pannello di Stato

//+------------------------------------------------------------------+
//| 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();
  }

Pannello di Controllo

//+------------------------------------------------------------------+
//| 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'inizializzazione del Gioco, la Reinizializzazione e il Movimento degli Elementi di Gioco

//+------------------------------------------------------------------+
//| 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();
  }

Il Controllo del Gioco

//+------------------------------------------------------------------+
//| 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();
     }
  }

L'Event Handling (codice finale)

// 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();
     }
  }
//+------------------------------------------------------------------+

Le Press_key e press_button sono due variabili statiche, definite nella funzione del gestore di eventi OnChartEvent.

L'inizio del gioco è consentito se la variabile press_buttonè falsa. Dopo il clic sul pulsante "Start", la variabile press_button viene impostata su true (proibisce la riesecuzione del codice che avvia il gioco), questo stato rimane lo stesso fino a uno dei seguenti eventi:

  • Riavvio del livello corrente;
  • Transizione al livello successivo;
  • Il gioco è in pausa (è stato premuto il pulsante "Pausa");
  • Il gioco si ferma (è stato premuto il pulsante "Stop").

Il cambio della direzione del movimento del serpente è possibile se è perpendicolare alla direzione corrente, così come dopo che il serpente si è mosso sul campo di gioco (il valore della variabile press_key lo indica) Queste condizioni vengono prese in considerazione nella funzione di elaborazione dell'evento CHARTEVENT_KEYDOWN (evento di pressione dei tasti).

Quindi si sposta l'intestazione, viene generato l'evento CHARTEVENT_OBJECT_DRAG, vengono ridefiniti i campi header_left e header_top della classe CSnakeGame. Il movimento degli altri elementi di gioco è determinato dai valori di questi campi.

Il movimento del campo di gioco è implementato nel modo, presentato nel TradePad_Sample.

Conclusione

In questo articolo abbiamo considerato un esempio di scrittura dei giochi in MQL5.

Abbiamo introdotto le classi della Libreria Standard (le classi di controllo), la classe CArrayObj e abbiamo anche imparato come eseguire la chiamata di funzione periodica con la gestione degli eventi.

Un archivio con i codici sorgente del gioco "Snake" può essere scaricato al riferimento sottostante. L'archivio dovrebbe essere decompresso nella cartella terminal_data_folder.

Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/65

File allegati |
snake-mql5-doc.zip (405.1 KB)
Trasferimento di Indicatori da MQL4 a MQL5 Trasferimento di Indicatori da MQL4 a MQL5
Questo articolo è dedicato alle peculiarità del trasferimento delle costruzioni di prezzo scritte in MQL4 a MQL5. Per facilitare il processo di trasferimento dei calcoli degli indicatori da MQL4 a MQL5, si suggerisce la libreria di funzioni mql4_2_mql5.mqh. Il suo utilizzo è descritto sulla base del trasferimento degli indicatori MACD, Stocastico e RSI.
Collegamento di Expert Advisor con ICQ in MQL5 Collegamento di Expert Advisor con ICQ in MQL5
Questo articolo descrive il metodo di scambio di informazioni tra l'Expert Advisor e gli utenti ICQ, vengono presentati diversi esempi. Il materiale fornito sarà interessante per coloro che desiderano ricevere informazioni di trading in remoto da un terminale del cliente, tramite un client ICQ nel proprio telefono cellulare o PDA.
Applicazione Pratica dei Database per l'Analisi dei Mercati Applicazione Pratica dei Database per l'Analisi dei Mercati
Lavorare con i dati è diventato il compito principale del software moderno, sia per applicazioni standalone che di rete. Per risolvere questo problema è stato creato un software specializzato. Si tratta di Database Management Systems (DBMS), in grado di strutturare, sistematizzare e organizzare i dati per l'archiviazione e l'elaborazione del proprio computer. Per quanto riguarda il trading, la maggior parte degli analisti non utilizza database nel proprio lavoro. Ma ci sono compiti in cui una soluzione del genere dovrebbe essere utile. Questo articolo fornisce un esempio di indicatori, in grado di salvare e caricare dati da database sia con architetture client-server che file-server.
Creazione di Pannelli di Controllo Attivi in MQL5 per il Trading Creazione di Pannelli di Controllo Attivi in MQL5 per il Trading
L'articolo tratta il problema dello sviluppo dei pannelli di controllo attivi in MQL5. Gli elementi dell'interfaccia sono gestiti dal meccanismo di gestione degli eventi. Inoltre, è disponibile l'opzione di un'impostazione flessibile delle proprietà degli elementi di controllo. Il pannello di controllo attivo consente di lavorare con le posizioni, nonché di impostare, modificare ed eliminare ordini di mercato e in sospeso.