Descargar MetaTrader 5

¿Le ha gustado el artículo?
Ponga el enlace al artículo -
que los demás también lo lean

Utilice nuevas posibilidades de MetaTrader 5

Crear un juego de la "Serpiente" en MQL5

18 febrero 2014, 14:48
Roman Martynyuk
0
646

Introducción

En este artículo trataremos un ejemplo de escritura de un juego de la "Serpiente" en MQL5.

Desde la 5ª versión de MQL, la programación de juegos se hizo posible principalmente a causa de sus herramientas de procesamiento de eventos, incluyendo los eventos personalizados. La programación orientada al objeto simplifica el diseño de estos programas, hace el código más claro y reduce el número de errores.

Tras leer este artículo, usted aprenderá sobre el procesamiento de eventos OnChart events, ejemplos de uso de las clases de la biblioteca estándar MQL5 y recetas para llamadas cíclicas de funciones tras un tiempo determinado para ejecutar cualquier cálculo.

Descripción del juego

El juego de la "Serpiente" se ha elegido como ejemplo principalmente por la simplicidad de su implementación. Todos lo que tengan interés en aprender programación podrán escribir este juego.

Según Wikipedia:

La Serpiente es un videojuego lanzado por primera vez durante mitades de la década de los 70 en salas de juego y ha mantenido su popularidad desde entonces, convirtiéndose en una especie de clásico.

El jugador controla una criatura larga y fina que se parece a una serpiente, que se desplaza por un plano por bordes recogiendo comida (u otros objetos), tratando de evitar tocar su propia cola o las "paredes" que rodean el área de juego. En algunas variaciones en el campo hay obstáculos adicionales. Cada vez que la serpiente come una pieza de comida, su cola se alarga, haciendo el juego cada vez más difícil. El usuario controla la dirección de la cabeza de la serpiente (arriba, abajo, izquierda o derecha), y el cuerpo de la serpiente le sigue. El jugador no puede detener a la serpiente durante el progreso de juego y no puede hacer que retroceda.

La implementación de la "Serpiente" en MQL5 tendrá algunas limitaciones y cualidades especiales.

El número de niveles será igual a 6 (de 0 a 5). El jugador tendrá 5 vidas disponibles en cada nivel. Tras el uso de todas las vidas, o después de que el jugador haya pasado todos los niveles, el juego volverá a su nivel inicial. Puede crear sus propios niveles. La velocidad de la serpiente y su longitud máxima será la misma para cada nivel.

El campo de juego constará de 4 elementos:

  1. Título del juego. Se usa para el posicionamiento del juego en el gráfico. Moviendo el título, se moverán todos los elementos del juego.
  2. Campo de juego. Es un array (tabla) de celdas con dimensiones de 20x20. Cada celda tiene un tamaño de 20x20 píxeles. Los elementos del campo de juego son:
    • La Serpiente. Consta de al menos tres elementos consecutivos: cabeza, cuerpo y cola. La cabeza se puede mover hacia la izquierda, derecha, arriba y abajo. Todos los demás elementos de la serpiente se mueven detrás de la cabeza. 
    • El Obstáculo. Se representa como un rectángulo gris. En el caso de que la cabeza de la serpiente choque con el obstáculo, se reinicia el nivel actual y el número de vidas se reduce en 1.
    • La Comida La comida se representa con una baya. En el caso de que la cabeza de la serpiente choque con la comida, el tamaño de la serpiente (longitud de su cuerpo) aumenta. Tras comer 12 piezas, la serpiente pasa al siguiente nivel.
  3. El Panel de Información (barra de estado del juego) Consta de tres elementos:
    • Nivel. Muestra el nivel actual.
    • Comida restante. Muestra cuántas bayas quedan por comer.
    • Vidas. Muestra el número de vidas disponible.
  4. Panel. Consta de tres botones:
    • Botón "Start" ("Inicio"). Inicia el nivel actual.
    • Botón "Pause" ("Pausa"). Pausa el juego.
    • Botón "Stop" ("Detención"). Detiene el juego, mientras la transición ocurre en el nivel inicial.

Todos estos elementos se pueden ver en la Figura 1:


Fig. 1. Elementos del juego de la "Serpiente"

El título del juego es un objeto del tipo "Button" ("Botón"). Todos los elementos del campo de juego son objetos del tipo "BmpLabel". El panel de información consta de tres objetos del tipo "Edit" ("Editar"), y el Panel de Control consta de tres objetos del tipo "Button". Todos los objetos están colocados por defecto con distancias en los ejes X e Y en píxeles relativas a la esquina superior izquierda del gráfico.

Se debería señalar que los bordes del campo de juego no son un obstáculo para el movimiento de la serpiente. Por ejemplo, cuando la serpiente llega al borde izquierdo, su cabeza aparece en el borde derecho. Se puede ver en la Figura 2:


Figura 2. Paso de la serpiente a través del borde del campo de juego

La cabeza de la serpiente y su cola se pueden rotar, al contrario que su cuerpo. La dirección de la cabeza se determina por la dirección del movimiento de la serpiente o por la posición de sus elementos vecinos. La dirección de la cola se determina solo por la posición del elemento vecino. 

Por ejemplo, si el elemento vecino de la cola está en el lado izquierdo, la cola se gira a la izquierda. El caso de la cabeza es algo diferente. La cabeza está girada a la izquierda si su elemento vecino se encuentra a la derecha. Los ejemplos de las direcciones de la cabeza y cola se presentan en las figuras a continuación. Preste atención al giro de la cabeza y cola en relación a sus elementos vecinos.





La cabeza y la cola están dirigidas hacia la izquierda. La cabeza y la cola están dirigidas hacia la derecha. La cabeza y la cola están dirigidas hacia abajo. La cabeza y la cola están dirigidas hacia arriba.


El movimiento de la serpiente se lleva a cabo en tres fases:

  1. Un movimiento de la celda de la cabeza hacia la derecha, izquierda, arriba o abajo, dependiendo de la dirección.
  2. El movimiento del último elemento del cuerpo de la serpiente en el anterior lugar donde estaba la cabeza.
  3. Mover la cola de la serpiente en el lugar anterior donde estaba el último elemento del cuerpo. El movimiento de la cola de la serpiente en el lugar anterior donde estaba el último elemento del cuerpo.

Si la serpiente come la comida, la cola no se mueve. En lugar de ello, se crea un nuevo elemento del cuerpo, que va hacia el último lugar donde estaba el último elemento del cuerpo de la serpiente. 

A continuación presentamos un ejemplo de movimiento de la serpiente hacia la izquierda:





Posición inicial  Movimiento de una celda hacia la izquierda
Movimiento del último elemento del cuerpo
al lugar anterior donde estaba la cabeza.
Movimiento de la cola en el último lugar
donde estaba el último elemento del cuerpo

Teoría 

A continuación trataremos las herramientas y técnicas que se usan en la escritura de juegos.

La Biblioteca Estándar MQL5

Es conveniente usar los arrays de objetos del mismo tipo (por ejemplo, celdas del campo de juego, elementos de la serpiente) para manipularlos (crear, mover, eliminar). Estos arrays y objetos se pueden implementar usando las clases de la Biblioteca Estándar MQL5.

El uso de las clases de la Biblioteca Estándar MQL5 permite simplificar el proceso de escribir programas. Para el juego, usaremos las siguientes clases de la biblioteca:

  1. La clase CArrayObj es para la organización de datos (array dinámico de señalizadores).
  2. CChartObjectEdit, CChartObjectButton y CChartObjectBmpLabel son clases de control, que representan "Edit", "Button" y "BmpLabel" respectivamente.

Para usar las clases de la Biblioteca Estándar MQL5 es necesario incluirlas usando la siguiente directiva de compilación:

#include <path_to_the_file_with_classes_description>

Por ejemplo, para el uso de objetos del tipo CChartObjectButton debemos escribir:

#include <ChartObjects\ChartObjectsTxtControls.mqh>

Las rutas de archivos se pueden encontrar en la documentación de referencia de MQL5.

Al trabajar con las clases de la Biblioteca Estándar MQL5 es importante entender que algunas de ellas son herederas de otras. Por ejemplo, la clase CChartObjectButton hereda la clase CChartObjectEdit, la clase CChartObjectEdit hereda la clase CChatObjectLabel, etc. Esto significa que las propiedades de la clase progenitora están disponibles para las clases derivadas.

Para entender las ventajas del uso de las clases de la Biblioteca Estándar MQL5, consideremos un ejemplo de creación de botón e implementémoslo de dos maneras (sin y con el uso de clases).

Aquí hay un ejemplo sin el uso de clases:

//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 ejemplo con el uso de clases:

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

Se puede observar que es más fácil trabajar con clases. Además, los objetos de clase se pueden almacenar en arrays y gestionar fácilmente.

Los métodos y propiedades de las clases de controles de Objeto se describen claramente en la documentación de referencia de MQL5 para las clases de la Biblioteca Estándar.

Usaremos la clase CArrayObj de la Biblioteca Estándar para distribuir el array de objetos. Esto evitará que el usuario tenga que llevar a cabo muchas operaciones rutinarias (como por ejemplo el cambio de tamaño de un array al añadir un nuevo elemento, la eliminación de objetos en el array, etc).

Cualidades de la clase CArrayObj

La clase CArrayObj permite la organización de un array dinámico de señalizadores a los objetos del tipo de clase CObject. CObject es una clase progenitora de todas las clases de la Biblioteca Estándar. Esto significa que podemos crear un array dinámico de señalizadores a los objetos de cualquier clase de la Biblioteca Estándar. Si necesita crear un array dinámico de objetos de su propia clase, debería heredarlo de la claseCObject.

En el siguiente ejemplo, el compilador no imprimirá errores porque la clase personalizada es la sucesora de la clase 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);

Para el siguiente caso, el compilador generará un error, porque my_obj no es un señalizador de la clase CObject o de una clase que herede la clase 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);

Al escribir el juego, usaremos los siguientes métodos de clase CArrayObj :

  • Add - Añade un elemento al final del array.
  • Insert - Inserta un elemento en la posición especificada del array.
  • Detach - Elimina el elemento en la posición especificada (el elemento se elimina del array).
  • Total - Obtiene el número de elementos en el array.
  • At - Obtiene el elemento en la posición especificada (el elemento no se elimina del array).

Este es un ejemplo de trabajo con la clase 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);
  }

En este ejemplo, la función OnInit crea un array dinámico con tres elementos. La impresión de los contenidos del array se lleva a cabo llamando a la función MyPrint.

Tras llenar el array usando el método Add, sus contenidos se pueden representar como (a, b, c). 

Tras aplicar el método Insert, los contenidos del array se pueden representar como (a, d, b, c).

Finalmente, tras aplicar el método Detach, el array se representará como (a, d, c).

Cuando el operador delete se aplica a la variable array_obj, se llama a la clase destructor CArrayObj que no solo elimina el array array_obj array, sino también todos los objetos cuyos señalizadores están guardados en él. Para evitar esto, antes de aplicar el comando delete, se debería establecer la flag de gestión de memoria de la clase CArrayObj como "false". Esta flag se establece con el método FreeMode.

Si no es necesario eliminar los objetos cuyos señalizadores están almacenados en el array dinámico al eliminar un array dinámico de señalizadores de objeto, deberá escribir el siguiente código:

array_obj.FreeMode(false);
delete array_obj;

Control de Eventos

Si se generan una serie de eventos, se acumulan en una cola, de la que van pasando consistentemente a la función de procesamiento de eventos.

Para la gestión de eventos generados al trabajar con un gráfico, así como los eventos personalizados, MQL5 tiene la función OnChartEvent. Cada evento tiene un identificador y parámetros que pasan a la función OnChartEvent

La función OnChartEvent se llama solo cuando ya no quedan funciones de programa en el proceso. Por tanto, en el siguiente ejemplo, OnChartEvent nunca conseguirá control.

#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 bucle infinito while no permite regresar de la función MyFunction. La función OnChartEvent no puede conseguir control. Por tanto, pulsando el botón no llamaremos a la función Alert.

Ejecución Periódica de Código con el Control de Eventos

En el juego se necesita la llamada periódica a la función de movimiento de la serpiente con la capacidad de control de eventos tras un determinado intervalo de tiempo. Pero tal y como se mostró arriba, un bucle infinito lleva al hecho de que la función OnChartEvent no recibe la llamada, y el control de eventos resulta imposible.

De modo que es necesario inventar otra forma de ejecución de código periódica.

Usar OnTimer

El lenguaje MQL5 tiene una función especial OnTimer a la que se llama periódicamente según un número de segundos predefinido. Para ello usaremos la función EventSetTimer.

El ejemplo anterior se puede reescribir de la siguiente manera:

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

En la función OnInit, el botón se creó y definió un período igual a un segundo para llamar a la función OnTimer. La llamada a la función OnTimer se realiza cada segundo, y la función OnTimer llama al código (MyFunction), que debería ejecutarse periódicamente.

Preste atención al hecho de que la llamada de la función OnTimer es múltiplo de segundos. Para llamar a la función tras un número específico de milisegundos se necesita el otro método. Este método es el uso de eventos personalizados.

Usar los Eventos Personalizados

Los eventos personalizados se generan con la función EventChartCustom. El ID del evento y sus parámetros se definen en los parámetros de entrada de la función EventChartCustom. El número de IDs definidos personalmente puede llegar a 65536 - de 0 a 65535. El compilador MQL5 añade automáticamente el identificador constante CHARTEVENT_CUSTOM al ID para distinguir los eventos personalizados de otros tipos de eventos. Por tanto, el margen actual de los IDs personalizados va de CHARTEVENT_CUSTOM a CHARTEVENT_CUSTOM+65535 ( CHARTEVENT_CUSTOM_LAST ).

A continuación puede ver un ejemplo de llamada periódica a la función MyFunction usando eventos personalizados:

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

En este ejemplo, antes de la función MyFunction hay un retraso de 200 ms (el tiempo de la llamada periódica de esta función), y se genera un evento personalizado. La función OnChartEvent controla todos los eventos. En el caso de un evento personalizado, llama de nuevo a la función MyFunction. Así, la llamada periódica a la función MyFunction se implementa de esta manera, y es posible establecer el periodo de llamada igual a milisegundos.

Parte Práctica

Consideremos un ejemplo de escritura de un juego de la "Serpiente".

Definir las Constantes y el Mapa de Niveles

El mapa de niveles se encuentra en un archivo titular separado, "Snake.mqh", y se representa como un nivel [6] [20] [20] de array tridimensional. El mapa de niveles se encuentra en un archivo titular separado, "Snake.mqh", y se representa como un nivel [6] [20] [20] de array tridimensional. En cada elemento de este array hay un array bidimensional que contiene la descripción del nivel individual. Si el valor de un elemento es igual a 9, es un obstáculo. Si el valor de un elemento de array es igual a 1,2 o 3, es la cabeza, cuerpo o cola de la serpiente respectivamente, que define su posición inicial en el campo de juego. Puede añadir nuevos niveles o modificar los que ya existen en el array de niveles.

Además, el archivo "Snake.mqh" contiene las constantes que se usan en el juego. Por ejemplo, al cambiar las constantes SPEED_SNAKE y MAX_LENGTH_SNAKE puede aumentar o reducir la velocidad de la serpiente y su longitud máxima en cada nivel. Todas las constantes tienen comentario.

//+------------------------------------------------------------------+
//|                                                        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(level,2)                 //Number of columns of playing field
#define COUNT_ROWS                        ArrayRange(level,1)                 //Number of rows of playing field
#define COUNT_LEVELS                      ArrayRange(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              "Games\\Snake\\square.bmp"          //Path to the "Square" image
#define IMG_FILE_NAME_OBSTACLE            "Games\\Snake\\obstacle.bmp"        //Path to the "Obstacle" image
#define IMG_FILE_NAME_SNAKE_HEAD_LEFT     "Games\\Snake\\head_left.bmp"       //Path to the snake's head (left) image
#define IMG_FILE_NAME_SNAKE_HEAD_UP       "Games\\Snake\\head_up.bmp"         //Path to the snake's head (up) image
#define IMG_FILE_NAME_SNAKE_HEAD_RIGHT    "Games\\Snake\\head_right.bmp"      //Path to the snake's head (right) image
#define IMG_FILE_NAME_SNAKE_HEAD_DOWN     "Games\\Snake\\head_down.bmp"       //Path to the snake's head (down) image
#define IMG_FILE_NAME_SNAKE_BODY          "Games\\Snake\\body.bmp"            //Path to the snake's body image
#define IMG_FILE_NAME_SNAKE_TAIL_LEFT     "Games\\Snake\\tail_left.bmp"       //Path to the snake's tail (left) image
#define IMG_FILE_NAME_SNAKE_TAIL_UP       "Games\\Snake\\tail_up.bmp"         //Path to the snake's tail (up) image
#define IMG_FILE_NAME_SNAKE_TAIL_RIGHT    "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 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}
     }
  };
//+------------------------------------------------------------------+

Note la definición de la constante #define SQUARE_BMP_LABEL_NAME "snake_square_% u_% U". Crearemos el campo de juego. En cada celda del campo de juego hay una etiqueta bitmap que debería tener un nombre único. El nombre de una celda se define con esta constante. Las especificaciones de formato de un nombre de celda es %u, que significa el dígito íntegro insignia.

Si especifica el nombre al crear el BmpLabel como: StringFormat (SQUARE_BMP_LABEL_NAME, 1,0), el nombre será igual a "snake_square_1_0".

Las Clases

Hay dos clases personalizadas que se han desarrollado para el juego. Están localizadas en el archivo "Snake.mq5.

La clase 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 clase CChartFiledElement hereda la clase CChartObjectBmpLabel, y por tanto la extiende. Todo el campo de juego, como la barrera de celda, cabeza, cuerpo y cola de la serpiente y la "comida", son objetos de esta clase. Las propiedades pos_x y pos_y son coordenadas relativas de elementos en el campo de juego: los índices de filas y columnas del elemento. El método SetPos configura estas coordenadas. El método Move convierte las coordenadas relativas a las distancias entre los ejes X e Y en píxeles, y mueve el elemento. Para ello llama a los métodos X_Distance y YDistance de la clase CChartObjectBmpLabel.

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

CSnakeGame es la principal clase del juego. Contiene los campos y métodos de creación, movimiento y eliminación de los elementos de juego. Como se puede observar, al principio de la descripción de la clase se declaran los campos para la organización de arrays dinámicos de señalizadores de elementos de juego. Por ejemplo, los señalizadores de los elementos de la serpiente se almacenan en el campo snake_element_obj_arr. El array de índice cero del array snake_element_obj_arr será la cabeza de la serpiente, y el último será su cola. Sabiendo esto, puede manipular fácilmente la serpiente en el campo de juego.

Consideremos todos los métodos de la clase CSnakeGame. Los métodos se implementan con base en la teoría presentada en el capítulo "Teoría" de este artículo.

El titular del juego


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

El campo de juego

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

Los Obstáculos

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

La Serpiente

//+------------------------------------------------------------------+
//| 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(level[current_level][i][j]==1 || level[current_level][i][j]==2 || level[current_level][i][j]==3)
           {
            snake_element_obj=new CChartFieldElement();
            snake_element_obj.Create(0,StringFormat(SNAKE_ELEMENT_BMP_LABEL_NAME,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[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();
  }

Tras mover la cabeza de la serpiente, se comprueba si hay una colisión con la función Check(), que devuelve el identificador de la colisión.

La función SetTrueSnake() se usa para especificar el dibujo correcto de la cabeza y cola de la serpiente, dependiendo de la posición de sus elementos vecinos.

La comida para la Serpiente

//+------------------------------------------------------------------+
//| 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 && 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 localización de la comida en el campo de juego es aleatoria, suponiendo que el campo de celdas en el que se coloca la comida no contiene ningún otro elemento.

El Panel de Estado

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

El Panel de Control

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

La Inicialización y Desinicialización del Juego y el Movimiento de los Elementos del Juego

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

El Control del Juego

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

El Control de Eventos (código 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 y press_button son dos variables estáticas definidas en la función controladora de eventos OnChartEvent.

El inicio de juego estará permitido si la variable press_button está en "false". Tras hacer click en el botón "Start", la variable press_button pasa a "true" (prohíbe la re-ejecución del código que inicia el juego). Este estado se mantiene igual hasta que sucede uno de los siguientes eventos:

  • Reinicio del nivel actual;
  • Transición al siguiente nivel;
  • Pausa en el juego (se ha pulsado el botón "Pause");
  • Detención de juego (se ha pulsado el botón "Stop").

El cambio de dirección en el movimiento de la serpiente es posible si es perpendicular a su dirección actual, así como después de que la serpiente se haya movido en el campo de juego (el valor de la variable press_key lo indica). Estas condiciones se tienen en cuenta en la función de procesamiento de eventos CHARTEVENT_KEYDOWN (evento de pulsación de tecla).

Al mover el titular se genera el evento CHARTEVENT_OBJECT_DRAG. Los campos header_left y header_top  de la clase CSnakeGame se redefinirán. El movimiento de los otros elementos del juego se determina por los valores de estos campos.

El movimiento del campo de juego se implementa de la forma presentada en TradePad_Sample.

Conclusión

En este artículo hemos considerado un ejemplo de escritura de juegos en MQL5.

Hemos presentado las clases de la Biblioteca Estándar (las clases de control), la clase CArrayObj, y también hemos aprendido a realizar la llamada de función periódica con control de eventos.

Más abajo podrá descargarse los códigos fuente del juego de la "Serpiente" en la documentación de referencia. El archivo debe descomprimirse en la carpeta client_terminal_folder\MQL5.

Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/65

Archivos adjuntos |
snake-mql5-doc.zip (405.1 KB)
Conexión del Asesor Experto con ICQ en MQL5 Conexión del Asesor Experto con ICQ en MQL5

Este artículo describe el método de intercambio de información entre el Asesor Experto y usuarios de ICQ, y presenta varios ejemplos. El material facilitado resultará interesante para aquellos que deseen recibir información de trading remotamente de un terminal de cliente, a través de un ICQ client en su teléfono móvil o PDA.

Crear Paneles de Control Activos en MQL5 para Trading Crear Paneles de Control Activos en MQL5 para Trading

Este artículo trata el tema del desarrollo de paneles de control activos en MQL5. Los elementos de la interfaz se gestionan con el mecanismo de control de eventos. Además, existe una opción para una configuración flexible de propiedades de elementos de control. El panel de control activo permite trabajar con posiciones, así como configuración, modificaciones y eliminaciones de mercado y órdenes pendientes.

Transferir Indicadores de MQL4 a MQL5 Transferir Indicadores de MQL4 a MQL5

Este artículo está dedicado a las peculiaridades de transferir construcciones de precio escritas en MQL4 a MQL5. Para facilitar el proceso de transferir cálculos de indicador de MQL4 a MQL5, se recomienda la biblioteca de funciones mql4_2_mql5.mqh. Su uso se decribe en la base de transferencia de los indicadores MACD, Stochastic y RSI.

Aplicación Práctica de Bases de Datos para Análisis de Mercados Aplicación Práctica de Bases de Datos para Análisis de Mercados

Trabajar con datos se ha convertido en la principal tarea para el software moderno, tanto para aplicaciones independientes como para aplicaciones de red. Para resolver este problema se creó un software especializado. Se trata de los Sistemas de Gestión de Bases de Datos (Database Management Systems o DBMS), que pueden estructurar datos para su almacenamiento en el ordenador y su procesamiento. En lo que se refiere a trading, la mayoría de analistas no usan bases de datos en su trabajo. Pero hay tareas donde esta solución resultaría muy práctica. Este artículo facilita un ejemplo de indicadores que puede guardar y cargar datos de bases de datos tanto con arquitecturas de servidor de cliente como de servidor de archivos.