SNAKE
EN C++/SDL
EN C++/SDL
Hola a todos
He creado este manual, por petición de Dato000 miembro del grupo de desarrollo de videojuegos al que pertenezco, en el voy a realizar una versión simple del videojuego Snake(o serpiente), usando C++ y la librería gráfica SDL, veréis que con apenas 280 lineas de código, contando espacios y comentarios se puede hacer el videojuego Snake(o serpiente) y que no es tan difícil como parece.
Espero que este manual le sea de ayuda a la gente que se apunto al reto y no pudo resolverlo, o para todo aquel que alguna vez a querido realizar el videojuego Snake(o serpiente) y no ha sido capaz.
Voy a empezar poniendo el código completo, para después ir explicándolo linea a linea, doy por sentado que el lector de este manual tiene una mínima base de programación en C o C++.
Código
//Juego Snake realizado por kaltorak para el manual Snake en SDL/C++ #include <cstdlib> #include <vector> #include <time.h> #include <SDL/SDL.h> //Prototipo de la funcion Colision. int Colision(SDL_Rect,SDL_Rect); //Prototipo de la funcion Iniciar. void Iniciar(void); /*definimos la direcciones que pude tomas la seriente con esto conseguimos que el codigo sea mucho mas mas comprensible*/ #define Arriba 1 #define Derecha 2 #define Abajo 3 #define Izquierda 4 /*Declaramos e inicializamos como constantes el ancho, el alto y la profundidad de color de la ventana principal.*/ const int ResolucionX = 640; const int ResolucionY = 480; const int PColor = 32; const int DELAY = 85; //Declaramos e inicializamos la varible LongitudSerpiente la cual contendra el tamaño de la serpiente. int LongitudSerpiente = 0; /*Declaramos e inicializamos la variable Direccion que contendra la direccion en la que se esta moviendo la Serpiente.*/ int Direccion = 0; //Declaramos e inicializamos la variable control para controlar que la manzana no coincida con al cola. int Control = 0; /*Declaramos la variable memoria del tipoc SDL_Rect donde almacenaremos la posion anterior anterior de la cabeza de la serpiente para poder mover la cola*/ SDL_Rect Ultimo; //Declaramos e inicializamos la variable Puntos que contendra los puntos del juego. int Puntos = 0; //Delcaramos e inicializamos la variable Titulo que contendra el titulo de la ventana y la puntuacion. char Titulo[255]; using namespace std; /*Declaramos la estructura Cuadro que sera la encargada de almacenar la posicion y el tamaño de cada una de las partes que formaran el cuerpo de la Serpiente*/ struct Cuadro { SDL_Rect Posicion; Cuadro(){Posicion.x = 0;Posicion.y = 0;Posicion.w = 20;Posicion.h = 20;} }Manzana; //Declaramos el Vector Serpiente del tipo Cuadro. vector <Cuadro> Serpiente; int main ( int argc, char** argv ) { srand(time(NULL)); //Inicializamos el modo de video de las SDL y comprobamos que se inicialize bien. if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { printf( "Imposible iniciar la libreria SDL: %s\n", SDL_GetError() ); return 1; } //Itroducimos SDL_Quit en atexit para que se inice al finalizar el programa. atexit(SDL_Quit); /*Creamos la Surface principal del juego, la que se va a mostrar en pantalla. y comprobamos que se inicie correctamente.*/ SDL_Surface* PantallaV = SDL_SetVideoMode(ResolucionX, ResolucionY, PColor,SDL_HWSURFACE|SDL_DOUBLEBUF); if ( !PantallaV ) { printf("Imposible crear la ventana Principal: %s\n", SDL_GetError()); return 1; } //Llamamos a la funcion Iniciar par poner todos los valores a cero. Iniciar(); // program main loop bool done = false; while (!done) { // message processing loop SDL_Event event; while (SDL_PollEvent(&event)) { // check for messages switch (event.type) { // exit if the window is closed case SDL_QUIT: done = true; break; // check for keypresses case SDL_KEYDOWN: { // exit if ESCAPE is pressed if (event.key.keysym.sym == SDLK_ESCAPE) { done = true; break; } if(event.key.keysym.sym == SDLK_UP && Direccion != Abajo) { Direccion = Arriba; break; } if(event.key.keysym.sym == SDLK_DOWN && Direccion != Arriba) { Direccion = Abajo; break; } if(event.key.keysym.sym == SDLK_LEFT && Direccion != Derecha) { Direccion = Izquierda; break; } if(event.key.keysym.sym == SDLK_RIGHT && Direccion != Izquierda) { Direccion = Derecha; break; } } } // end switch } // end of message processing // DRAWING STARTS HERE if(Direccion != 0) { //Almacenamos la posicion del ultimo de los elementos que forman la cola en la variable Ultimo. Ultimo.x = Serpiente[LongitudSerpiente].Posicion.x; Ultimo.y = Serpiente[LongitudSerpiente].Posicion.y; //Movemos la cola de la serpiente. for(int I = LongitudSerpiente; I >= 1; I--) { Serpiente[I].Posicion.x = Serpiente[I-1].Posicion.x; Serpiente[I].Posicion.y = Serpiente[I-1].Posicion.y; } } if(Direccion == Arriba) { Serpiente[0].Posicion.y -= Serpiente[0].Posicion.h; } else if(Direccion == Abajo) { Serpiente[0].Posicion.y += Serpiente[0].Posicion.h; } else if(Direccion == Derecha) { Serpiente[0].Posicion.x += Serpiente[0].Posicion.w; } else if(Direccion == Izquierda) { Serpiente[0].Posicion.x -= Serpiente[0].Posicion.w; } //Comprobamos la colision de la serpiente con el borde. if(((Serpiente[0].Posicion.x + Serpiente[0].Posicion.w) > ResolucionX) || (Serpiente[0].Posicion.x < 0) || ((Serpiente[0].Posicion.y + Serpiente[0].Posicion.h) > ResolucionY) || (Serpiente[0].Posicion.y < 0)) { Iniciar(); } //Comprobamos la colision de la serpiente con la cola. for(int I = 1; I <= LongitudSerpiente; I++) { if(Colision(Serpiente[0].Posicion,Serpiente[I].Posicion)) { Iniciar(); } } //Comprobamos la colision de la serpiente con la Manzana. if(Colision(Serpiente[0].Posicion,Manzana.Posicion)) { LongitudSerpiente++; Puntos += 10; Serpiente.push_back(Cuadro()); Serpiente[LongitudSerpiente].Posicion.x = Ultimo.x; Serpiente[LongitudSerpiente].Posicion.y = Ultimo.y; //Metemos en la varible Titulo el titulo de la ventana seguido de la puntuacion del juego. sprintf(Titulo,"Snake -- Puntos: %d",Puntos); /*Posicionamos denuevo la manazana en pantalla y comprobamos que la nueva ubicacion no este ocupada por la cola de la Serpiente.*/ do { Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w); Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h); Control = 0; for(int I = 1; I <= LongitudSerpiente; I++) { if(Colision(Manzana.Posicion,Serpiente[I].Posicion)) { Control = 1; I = LongitudSerpiente; } } } while(Control == 1); } //Borramos la surface principal SDL_FillRect(PantallaV, 0, SDL_MapRGB(PantallaV->format, 0, 0, 0)); //Pintamos la serpiente en la Surface principal for(int I = 0; I <= LongitudSerpiente; I++) { SDL_FillRect(PantallaV,&Serpiente[I].Posicion,SDL_MapRGB(PantallaV->format, 255, 255, 255)); } //Pintamos la manzana en la Surface principal SDL_FillRect(PantallaV,&Manzana.Posicion,SDL_MapRGB(PantallaV->format, 0, 255, 0)); //Cambiamos el titulo de la ventana por Snake. SDL_WM_SetCaption (Titulo, NULL); //Mostramos la Surface principal en pantalla. SDL_Flip(PantallaV); SDL_Delay(DELAY); } // end main loop return 0; } //Funcion que compruba las colisiones entre los elementos de juego. int Colision(SDL_Rect H,SDL_Rect M) { if (((H.x + H.w) > M.x) && ((H.y + H.h) > M.y) && ((M.x + M.w) > H.x) && ((M.y + M.h) > H.y)) { return 1; } else { return 0; } } //Funcion que reinicia el juego cuando la serpiente colisiona con ella misma o el borde. void Iniciar(void) { /*Ponemos el valor de la variable longitudSerpiente a 0 para que la serpiente solo este formada por la cabeza.*/ LongitudSerpiente = 0; //Ponemos el valor de la variable Direccion a 0 para que la serpiente aparezca parada al comenzar el juego. Direccion = 0; //Ponemos el valor de la variable Puntos a 0 para reiniciar el marcador de puntos. Puntos = 0; //Metemos en la varible Titulo el titulo de la ventana seguido de la puntuacion del juego. sprintf(Titulo,"Snake -- Puntos: %d",Puntos); //Borramos todos los miembros que forman el vector Serpiente Serpiente.clear(); //Añadimos un miembro en el vector serpiente que contendra la cabeza de la serpiente. Serpiente.push_back(Cuadro()); //Posicionamos aleatoriamente la manzana en la pantalla. Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w); Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h); //Posicionamos la cabeza de la serpiente en el centro de la pantalla. Serpiente[0].Posicion.x = (((ResolucionX/Serpiente[0].Posicion.w)/2)*Serpiente[0].Posicion.w); Serpiente[0].Posicion.y = (((ResolucionY/Serpiente[0].Posicion.h)/2)*Serpiente[0].Posicion.h); }
Para el que no lo conozca el videojuego Snake (o serpiente) comentaros que fue lanzado a mediados de los 70 y ha mantenido su popularidad desde entonces, convirtiéndose en un clásico tras su salida en 1998 en todos los dispositivos móviles de Nokia.
Su argumento es muy sencillo pero enormemente adictivo, el jugador controla una larga y delgada criatura semejante a una serpiente, de hay el nombre del videojuego, que vaga por un plano delimitado por paredes, donde debe evitar a toda costa chocar contra las paredes o consigo misma mientras come manzanas las cuales la hacen crecer, lo cual complica el juego a medida que la serpiente va creciendo debido a la ingesta de las mencionadas manzanas, si esto no fuera suficiente la serpiente una vez que comienza a moverse no puede ser para por el jugador, este se tiene que limitar a cambiar el sentido de la marcha de la suso dicha serpiente mediante las flechas de dirección, para que se coma las manzanas y evite chocar.
Comencemos a analizar el código.
Comenzamos incluyendo las librerías que vamos a necesitar para el buen funcionamiento del código.
La librería estándar "cstdlib "
Código
#include <cstdlib>
La librería “vector” para la creación de una lista de vectores, que controlen el tamaño de serpiente.
Código
#include <vector>
La librería “time.h” para generar aleatoriamente la posición en la que va a aparecer la manzana en el juego, cada vez que esta sea devorada por la serpiente.
Código
#include <time.h>
Y por ultimo la librería “SDL.h” la cual vamos usar para manejar el entorno gráfico y las pulsaciones del teclado.
Código
#include <SDL/SDL.h>
Las siguiente lineas de código son los prototipos de las funciones “Colision” y “Iniciar”, de las cuales explicare su funcionamiento mas adelante.
Código
int Colision(SDL_Rect,SDL_Rect); void Iniciar(void);
Ahora vamos a usar la directiva “#define” para crear unas macros, las cuales nos ayudaran a entender mejor el código.
Lo que estas macros hacen es que trabajemos con las direcciones reales en las que se mueve la serpiente y no números pues tras un tiempo sin usar el código no sabríamos que significaba cada numero.
Mirar a la dirección Arriba le adjudicamos el numero 1, lo que hace esta macro es que en el código podemos poner Arriba y sabremos que significa 1, después el preprocesador cambiara las palabra Arriba por un 1 antes de compilar el código, como bien he dicho esto es solo por limpieza y para mejorar la compresión del código para futuras modificaciones o para que sea comprensible para otra persona que no sea el programador del mismo.
Código
#define Arriba 1 #define Derecha 2 #define Abajo 3 #define Izquierda 4
Tras estas lineas vamos a definir el ancho y alto de la pantalla principal así como la profundidad de color de la misma.
Código
const int ResolucionX = 640; const int ResolucionY = 480; const int PColor = 32;
Ahora vamos a definir el retardo en milisegundos que usaremos para que el programa funcione mas o menos igual en todas las maquinas aunque sean las rápidas
Código
const int DELAY = 85;
Declaramos e inicializamos la variable LongitudSerpiente la cual contendrá el tamaño de la serpiente, la iniciamos con valor 0 para que en un principio solo contenga la cabeza de la serpiente.
Código
int LongitudSerpiente = 0;
Declaramos e inicializamos la variable Dirección, que contendrá la dirección en la que se esta
moviendo la Serpiente en cada momento, principalmente la inicializamos a 0 para que la serpiente aparezca parada al comenzar el juego.
Código
int Direccion = 0;
Declaramos e inicializamos la variable control para controlar la permanecía o la salida del bucle que controla el chequeo de la manzana al ser creada, para que no coincida en una posición de la pantalla en la cual se encuentre la serpiente.
Código
int Control = 0;
Declaramos la variable Ultimo del tipo SDL_Rect donde almacenaremos la posición del ultimo cuadro que forma la cola de la serpiente, para cuando tengamos que añadir un nuevo cuadro a la cola tras la ingesta de una manzana, sepamos donde posicionarlo con respecto al resto de la serpiente.
Código
SDL_Rect Ultimo;
Declaramos e inicializamos la variable Puntos que contendrá los puntos del juego.
Código
int Puntos = 0;
Declaramos la variable Titulo que contendrá el titulo de la ventana y la puntuación.
Código
char Titulo[255];
la siguiente linea es la encargada de usar el ámbito std como namespace, esto se traduce en que no tendremos que usar std:: delante de las funciones estándar de C++
Código
using namespace std;
Declaramos la estructura Cuadro que sera la encargada de almacenar la posición y el tamaño, de cada una de las partes que formaran el cuerpo de la Serpiente y la Manzana.
Si os fijáis también he creado un constructor para que la primera vez que creemos uno de los cuadros que formaran la serpiente o la manzana, se inicialicen con un tamaño de 20 píxeles.
Código
struct Cuadro { SDL_Rect Posicion; Cuadro(){Posicion.x = 0;Posicion.y = 0;Posicion.w = 20;Posicion.h = 20;} }Manzana;
Declaramos el vector Serpiente del tipo Cuadro, un vector es muy similar a un array o lista, lo único que tendremos control total cobre los miembros que forman el vector, podremos incluir miembros nuevos cuando queramos o eliminar algún miembro que no no interese, como no sabremos que tamaño va a alcanzar nuestra serpiente en cada momento, la mejor forma de manejarlo es usando vector pues como os he comentado nos genera una array o lista, que podemos modificar libremente, otra manera de hacerlo seria dividir el ancho y el alto de la pantalla por el tamaño de un cuadro y multiplicar los resultados, después le restamos 1 para poder posicionar la manzana y tendremos el tamaño total que podrá tener nuestra serpiente.
Por ejemplo si la pantalla midiera 640x480 y cada cuadro que forma la serpiente midiera 20 píxeles, la forma de saber el tamaño máximo que podrá alcanzar nuestra serpiente sera así:
640/20 = 32
480/20 = 24
32 * 24 = 768
768 – 1 = 767
Nuestra serpiente podría tener un tamaño máximo de 767 cuadros y podríamos generar un array de 767 elementos y evitar usar vectores, pero de esta manera estaríamos desperdiciando mucha memoria que mas de un 90% de las veces no sera usada, por este motivo y como es una buena practica de programación ahorrar memoria vamos a usar vectores.
Código
vector <Cuadro> Serpiente;
la función main no necesita presentación
Código
int main ( int argc, char** argv ) {
Usaremos la función srand con time como semilla para la generación de numeros aleatorios para posicionar la manzana en pantalla.
Código
srand(time(NULL));
Inicializamos el modo de vídeo de las SDL y comprobamos que se inicialice bien.
Código
if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { printf( "Imposible iniciar la libreria SDL: %s\n", SDL_GetError() ); return 1; }
Introducimos SDL_Quit en atexit para que se inicie al finalizar el programa, saliendo así de la librería SDL siempre que el programa finalice.
Código
atexit(SDL_Quit);
Creamos la Surface principal del juego, la que se va a mostrar en pantalla y comprobamos que se inicie correctamente.
Código
SDL_Surface* PantallaV = SDL_SetVideoMode(ResolucionX, ResolucionY,PColor,SDL_HWSURFACE|SDL_DOUBLEBUF); if ( !PantallaV ) { printf("Imposible crear la ventana Principal: %s\n", SDL_GetError()); return 1; }
Llamamos a la función Iniciar para poner todos los valores a cero.
Código
Iniciar();
A continuación voy a pasar a explicar la función Iniciar y después regresare al código en la linea siguiente a Iniciar();.
La función Iniciar es la encargada de poner todas las variables con los valores originales del juego para de esta manera poder reiniciarlo cuando colisionemos y la serpiente muera.
Código
void Iniciar(void) { /*Ponemos el valor de la variable longitudSerpiente a 0 para que la serpiente solo este formada por la cabeza.*/ LongitudSerpiente = 0; /*Ponemos el valor de la variable Direccion a 0 para que la serpiente aparezca parada al comenzar el juego.*/ Direccion = 0; //Ponemos el valor de la variable Puntos a 0 para reiniciar el marcador de puntos. Puntos = 0; //Metemos en la variable Titulo el titulo de la ventana seguido de la puntuación del juego. sprintf(Titulo,"Snake -- Puntos: %d",Puntos); //Borramos todos los miembros que forman el vector Serpiente Serpiente.clear(); //Añadimos un miembro en el vector serpiente que contendrá la cabeza de la serpiente. Serpiente.push_back(Cuadro()); //Posicionamos aleatoriamente la manzana en la pantalla. Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w); Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h); //Posicionamos la cabeza de la serpiente en el centro de la pantalla. Serpiente[0].Posicion.x = (((ResolucionX/Serpiente[0].Posicion.w)/2)*Serpiente[0].Posicion.w); Serpiente[0].Posicion.y = (((ResolucionY/Serpiente[0].Posicion.h)/2)*Serpiente[0].Posicion.h); }
Analicemos mas profundamente la manera de posicionar la manzana aleatoriamente en la pantalla
Código
Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w); Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h);
Sabemos que las medidas de la pantalla están almacenadas dentro de las variables ResolucionX para el ancho y ResolucionY para el alto, las coordenadas que definen la posición de la manzana se posicionan en la esquina superior izquierda de la misma.
Así que si por un casual el ancho aleatorio que nos saliera fuera 640, la manzana se colocaría fuera de la pantalla por el lado derecho de la misma o si el valor para el alto generado aleatoriamente coincidiera con 480, pasaría lo mismo que en el caso anterior pero esta vez la manzana se dibujaría fuera de la pantalla por el lado inferior de la misma, para solucionar este problema debemos restarle el ancho y el alto de la manzana a las dimensiones de la pantalla para asegurarnos de que dicha manzana cuando sea dibujada no se muestre fuera de los limites de la pantalla, el ancho de la manzana se encuentran en Manzana.Posicion.w y el alto en Manzana.Posicion.h.
Código
(ResolucionX-Manzana.Posicion.w) (ResolucionY-Manzana.Posicion.h)
Para asegurarnos que el numero aleatorio que va a ser generado para colocar la manzana esta centrado con respecto a la pantalla y al movimiento de la serpiente, debemos dividir el resultado nuevamente por el ancho o el alto de la manzana de esta manera la manzana solo se podrá colocar en posiciones multiplicas de 20 que es el ancho y al alto de la manzana, quedando siempre centrada con el movimiento de la serpiente.
Código
(ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w) (ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h)
Y por ultimo ya solo nos queda multiplicar el resultado de la operación aleatoria por el ancho y el alto de la manzana para sacar las coordenadas reales de la pantalla en que va a ser dibujada la manzana.
Código
Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w); Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h);
Para posicionar la serpiente en el centro de la pantalla usamos el mismo método que para posicionar la manzana, pero lógicamente sin usar números aleatorios, así nos aseguramos que tanto la manzana como la serpiente se encuentren centradas una con respecto a la otra, de esta manera cuando la serpiente se coma la manzana la posición de la cabeza y la manzana corresponderán perfectamente.
Código
Serpiente[0].Posicion.x = (((ResolucionX/Serpiente[0].Posicion.w)/2)*Serpiente[0].Posicion.w); Serpiente[0].Posicion.y = (((ResolucionY/Serpiente[0].Posicion.h)/2)*Serpiente[0].Posicion.h);
Una vez explicada la función Iniciar vamos a continuar con el código.
Ahora Declaramos e inicializamos la variable done del tipo bool la cual usaremos como bandera de control para abandonar el bucle principal del juego una vez que pulsemos la tecla Escape o la X que cierra la ventana, tras la declaración creamos el bucle principal del juego que se repetirá siempre que el valor de la variable done se a false (o 0).
Código
bool done = false; while (!done) {
Lo primero que vamos a encontrar dentro del bucle principal del juego es el método para leer los mensajes que el programa recibe del exterior, como pueden ser las teclas que han sido pulsadas o los mensajes recibidos de la ventana.
Código
SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: done = true; break; case SDL_KEYDOWN: { if (event.key.keysym.sym == SDLK_ESCAPE) { done = true; break; } if(event.key.keysym.sym == SDLK_UP && Direccion != Abajo) { Direccion = Arriba; break; } if(event.key.keysym.sym == SDLK_DOWN && Direccion != Arriba) { Direccion = Abajo; break; } if(event.key.keysym.sym == SDLK_LEFT && Direccion != Derecha) { Direccion = Izquierda; break; } if(event.key.keysym.sym == SDLK_RIGHT && Direccion != Izquierda) { Direccion = Derecha; break; } } } }
Declaramos event del tipo SDL_Event, que es donde almacenaremos los mensajes que recibamos del sistema.
Código
SDL_Event event;
Después crearemos un bucle que se repetirá siempre que queden mensajes del sistema por procesar, al mismo tiempo almacenamos dichos mensajes en la variable event creada anteriormente.
Código
while (SDL_PollEvent(&event)) {
Tras lo cual procederemos a leer el mensaje del sistema almacenado en la variable event
Código
switch (event.type) {
Si el mensaje almacenado en la variable event es SDL_QUIT, esto nos informa de que la X de la ventana a sido pulsado y por tanto el programa debe finalizar, así que ponemos el valor de la variable done a true(o 1) para salir del bucle principal de programa y de este modo finalizar el mismo.
Código
case SDL_QUIT: done = true; break;
Otro de los mensajes del sistema que vamos a monitorizar, sera si una tecla a sido pulsada para ello usaremos
Código
case SDL_KEYDOWN: {
Y tras saber que una tecla a sido pulsada debemos ver cual y si nos interesa para que el programa reaccione en concordancia a la tecla pulsada, la primera tecla que vamos a comprobar si ha sido pulsada es Escape y lo haremos mediante.
Código
if (event.key.keysym.sym == SDLK_ESCAPE) {
En caso de que el resultado sea 1 o mayor de 1 eso quiere decir que la tecla Escape a sido pulsada y se procederá a realizar lo que hay en el interior del bloque que no es otra cosa que poner el valor de la variable done a true (o 1) para salir del bucle principal de programa y de este modo finalizar el mismo.
Código
done = true; break; }
Después realizaremos la comprobación con las teclas de dirección, empezando por la tecla de dirección Arriba o lo que es lo mismo SDLK_UP, pero además de comprobar que la tecla haya sido pulsada podremos ver en el código que se realiza otra comprobación,Esta comprobación es que la variable Dirección no contenga el valor Abajo, esto lo hacemos por que la serpiente no puede andar hacia atrás y si la variable Dirección contiene el valor Abajo, la serpiente esta yendo hacia abajo en la pantalla, por lo tanto la tecla de dirección Arriba no tiene que tener efecto; Para que entremos en el bloque del if ambas comprobaciones deben ser correctas, en caso de que las dos comprobaciones sean correctas cambiamos el valor de la variable Dirección por Arriba y de este modo le decimos a la serpiente la dirección que debe tomar a partir de ahora.
Código
if(event.key.keysym.sym == SDLK_UP && Direccion != Abajo) { Direccion = Arriba; break; }
Explicado esto el resto de las comprobaciones que se realizan a las teclas de dirección son iguales a la comprobación de la tecla de dirección Arriba, pero obviamente cambiando la dirección del movimiento de la serpiente.
Código
if(event.key.keysym.sym == SDLK_DOWN && Direccion != Arriba) { Direccion = Abajo; break; } if(event.key.keysym.sym == SDLK_LEFT && Direccion != Derecha) { Direccion = Izquierda; break; } if(event.key.keysym.sym == SDLK_RIGHT && Direccion != Izquierda) { Direccion = Derecha; break; } } } }
Una vez hemos comprobado todos los mensajes del sistema y realizados las acciones que mejor se ajustan a dichos mensajes, salimos del bucle que procesa los mensajes y continuamos.
Si la serpiente esta en movimiento quiere decir que la variable Dirección no vale 0 por tanto tenemos que empezar a mover dicha serpiente por la pantalla y para hacer esto lo primero que vamos a hacer es almacenar en la variable Ultimo del tipo SDL_Rect la posición del ultimo cuadro que forma la cola de la serpiente antes de que este sea movido de su posición actual esto lo hacemos para saber la posición en la que tendremos que colocar un nuevo cuadro si la serpiente se come la manzana
Código
if(Direccion != 0) { Ultimo.x = Serpiente[LongitudSerpiente].Posicion.x; Ultimo.y = Serpiente[LongitudSerpiente].Posicion.y;
Después de almacenar la posición del ultimo cuadro que forma la cola de la serpiente, vamos a mover la posición de los cuadros que forman la cola de la serpiente mediante el siguiente for, empezando por el ultimo a la posición del cuadro que se encuentra una posición mas arriba en la lista de elementos del vector Serpiente, como podemos ver Declaramos la variable I la cual vamos a usar como contador y la Inicializamos con el valor de la variable LongitudSerpiente que contiene el tamaño actual de la serpiente y recorreremos los elementos que forman el vector Serpiente, hasta alcanzar el cuadro mas próximo a la cabeza de la serpiente que no es otro que el elemento del vector 1, pues como vimos antes la cabeza de la serpiente se encuentra en el elemento del vector 0.con esto conseguimos que la cola de la serpiente avance una posición o lo que es lo mismo 20 píxeles por la pantalla.
Código
//Movemos la cola de la serpiente. for(int I = LongitudSerpiente; I >= 1; I--) { Serpiente[I].Posicion.x = Serpiente[I-1].Posicion.x; Serpiente[I].Posicion.y = Serpiente[I-1].Posicion.y; } }
Una vez llegados a este punto nos tiene que surgir una duda, bien si movemos todos los cuadros que forman la cola de la serpiente a la posición inmediata siguiente que pasa con la cabeza de la serpiente?
Pues vamos a resolver esta duda ahora mismo, como la cabeza de la serpiente es la que dirige al resto de la serpiente es la que tiene que moverse en concordancia con la dirección que le digamos usando el teclado por ese motivo la movemos con los siguientes if,
Código
if(Direccion == Arriba) { Serpiente[0].Posicion.y -= Serpiente[0].Posicion.h; } else if(Direccion == Abajo) { Serpiente[0].Posicion.y += Serpiente[0].Posicion.h; } else if(Direccion == Derecha) { Serpiente[0].Posicion.x += Serpiente[0].Posicion.w; } else if(Direccion == Izquierda) { Serpiente[0].Posicion.x -= Serpiente[0].Posicion.w; }
Si la variable Dirección contiene Arriba eso significa por lo que vimos antes que hemos usado la tecla de dirección Arriba entonces debemos mover la serpiente por la pantalla hacia arriba esto lo hacemos modificando la variable que contiene la dirección y de la cabeza de la serpiente que no es otra que Serpiente[0].Posicion.y y para ello le restamos el alto del cuadro que forma la cabeza de la serpiente que se encuentra en la variable Serpiente[0].Posicion.h, que son 20 pixeles.
Código
if(Direccion == Arriba) { Serpiente[0].Posicion.y -= Serpiente[0].Posicion.h; }
El resto de los else if son exactamente iguales pero lógicamente alterando la los variables en concordancia en la dirección en que deba ser movida la serpiente
Código
else if(Direccion == Abajo) { Serpiente[0].Posicion.y += Serpiente[0].Posicion.h; } else if(Direccion == Derecha) { Serpiente[0].Posicion.x += Serpiente[0].Posicion.w; } else if(Direccion == Izquierda) { Serpiente[0].Posicion.x -= Serpiente[0].Posicion.w; }
Ahora vamos a comprobar la colisión de la cabeza de la serpiente con el borde, para ello tenemos que comprobar que la posición x e y de la serpiente se encuentre dentro de los limites de la pantalla, fijaros bien pues pasa lo mismo que cuando queríamos colocar la manzana dentro de los limites de la pantalla, como los puntos de posición del cuadro que forma la cabeza de la serpiente están en la esquina superior izquierda del cuadro, para comprobar que la colisión se realiza correctamente con la parte inferior y la parte derecha de la pantalla, tenemos que sumar el ancho y el alto del cuadro que forma la cabeza de la serpiente, a la variable respectiva, en caso de que se produzca la colisión reiniciaremos el juego llamando a la función Iniciar la cual os explique su funcionamiento anteriormente.
Código
if(((Serpiente[0].Posicion.x + Serpiente[0].Posicion.w) > ResolucionX) || (Serpiente[0].Posicion.x < 0) || ((Serpiente[0].Posicion.y + Serpiente[0].Posicion.h) > ResolucionY) || (Serpiente[0].Posicion.y < 0)) { Iniciar(); }
Una vez hemos realizado la comprobación de la colisión con el borde de la pantalla, vamos a realizar la comprobación de la colisión de la cabeza de la serpiente con la cola, para ello vamos a usar la función Colision la cual os explicare a continuación y después seguiremos viendo el código
Como podéis ver la función Colision es muy simple lo que hace es comprobar si el cuadro que le pasamos como primer miembro de la función se encuentra dentro o en contacto con el cuadro que le pasamos como segundo miembro de la función, si están en contacto devuelve 1 y en caso contrario devuelve 0.
Código
int Colision(SDL_Rect H,SDL_Rect M) { if (((H.x + H.w) > M.x) && ((H.y + H.h) > M.y) && ((M.x + M.w) > H.x) && ((M.y + M.h) > H.y)) { return 1; } else { return 0; } }
Vamos a recorrer los elementos que forman el vector Serpiente usando un for, empezaremos por el elemento 1 del vector Serpiente, de este modo nos saltamos la cabeza de la serpiente pues esta es la que colisiona con la cola, y lógicamente no puede colisionar consigo misma, cuando Declaramos la variable I que vamos a usar como contador, la Inicializamos con el valor 1, de este modo recorreremos todos los elementos que forman el vector Serpiente saltándonos la cabeza de la serpiente y comprobaremos mediante la función Colision si existe colisión con la cabeza de la serpiente que es el elemento 0 del vector, en caso de que la función Colision devuelva 1 quiere decir como vimos antes que la colisión se a producido y entramos dentro del bloque del if donde mediante la función Iniciar reiniciamos el juego .
Código
//Comprobamos la colision de la serpiente con la cola. for(int I = 1; I <= LongitudSerpiente; I++) { if(Colision(Serpiente[0].Posicion,Serpiente[I].Posicion)) { Iniciar(); } }
Ya solo nos queda comprobar si la serpiente se a comido la manzana lo cual haremos nuevamente con la función Colision y un if, esta vez comprobaremos la cabeza de la serpiente y la manzana como podemos ver , en caso de que la colisión se produzca entraremos en el bloque del if.
Código
if(Colision(Serpiente[0].Posicion,Manzana.Posicion)) {
Como sabemos que cuando la serpiente se come un manzana su cola crece un cuadro debemos incrementar en uno el valor de la variable LongitudSerpiente y lo haremos así.
Código
LongitudSerpiente++;
También sabemos que cuando nos comemos una manzana debemos aumentar los puntos, yo en este caso he decidido que cada manzana valga 10 puntos, así que aumento en 10 el valor de la variable Puntos.
Código
Puntos += 10;
Al aumentar el tamaño de la serpiente, debemos incluir un nuevo elemento en el vector Serpiente y lo haremos con la siguiente linea.
Código
Serpiente.push_back(Cuadro());
Una vez hemos creado el nuevo elemento en el vector Serpiente, debemos darle una posición en la pantalla y para eso usaremos la posición del ultimo elemento de la cola de la serpiente, que si recordáis habíamos almacenado en la variable Ultimo.
Código
Serpiente[LongitudSerpiente].Posicion.x = Ultimo.x; Serpiente[LongitudSerpiente].Posicion.y = Ultimo.y;
Como hemos modificado el valor de la variable Puntos debemos actualizar el titulo de ventana para que nos muestre la nueva puntuación.
Código
sprintf(Titulo,"Snake -- Puntos: %d",Puntos);
Y finalmente ya solo nos queda posicionar la manzana nuevamente en una posición aleatoria de la pantalla, como podemos ver aparte de colocar la manzana, debemos comprobar que no la ponemos sobre ninguno de los elementos que forman la serpiente, lo cual comprobaremos nuevamente con la función Colision, y en caso de que la colisión se produzca introduciremos 1 como valor de la Variable Control para que el bucle se vuelva a realizar y se genere otra posición aleatoria para la manzana.
Código
do { Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w); Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h); Control = 0; for(int I = 1; I <= LongitudSerpiente; I++) { if(Colision(Manzana.Posicion,Serpiente[I].Posicion)) { Control = 1; I = LongitudSerpiente; } } } while(Control == 1); }
Ya casi hemos terminado solo nos queda el tema gráfico y lo primero que vamos a hacer es limpiar la surface PantallaV pintándola del color del fondo en este caso negro.
Código
SDL_FillRect(PantallaV, 0, SDL_MapRGB(PantallaV->format, 0, 0, 0));
Después posicionamos el dibujo de cada uno de los elementos que forman la serpiente en la surface PantallaV.
Código
for(int I = 0; I <= LongitudSerpiente; I++) { SDL_FillRect(PantallaV,&Serpiente[I].Posicion,SDL_MapRGB(PantallaV->format, 255, 255, 255)); }
Posicionamos el dibujo de la manzana en la surface PantallaV.
Código
SDL_FillRect(PantallaV,&Manzana.Posicion,SDL_MapRGB(PantallaV->format, 0, 255, 0));
Refrescamos el titulo de la ventana, por si la puntuación a sido modificada.
Código
SDL_WM_SetCaption (Titulo, NULL);
Ahora mostramos la surface PantallaV en la pantalla y esperamos los milisegundos necesarios para que el programa funcione mas o menos igual en todas las maquinas aunque sean mas rápidas.
Código
SDL_Flip(PantallaV); SDL_Delay(DELAY); }
Y por ultimo cuando el programa finalice devolvemos 0, esto hoy en día casi no se usa pero es una buena practica de programación y nos puede ayudar a mantener la compatibilidad del código con otros sistemas.
Código
return 0; }
Bueno con esto terminamos el manual espero que os haya gustado y os sirva.
Para cualquier duda os podéis poner en contacto conmigo en el foro o en mi email.
kaltorak_@hotmail.com
La versión en pdf del manual la tenis en el siguiente enlace, junto con el archivo compilado para windows y linux, así como el código fuente:
http://ultrashare.net/hosting/fl/6a05538c64
Un saludo
Kaltorak.