NovaMonoFix
Errores PHP
X
Usuario
Password
0 FPS

Tutorial WINAPI C++ 2.6 (Terminando el Snake)

14 de Mayo del 2010 por Josep Antoni Bover, 0 visitas, 0 comentarios, 0 votos
Categorías : Windows, Programación, C y C++.
La web está en segundo plano, animación en pausa.
Cargando animación...
Tutorial WINAPI C++ 2.6 (Terminando el Snake)

Este tutorial nos mostrara como ensamblar todos los tutoriales anteriores con todos sus objetos.

Ademas en este tutorial vamos a tener que diseñar la funcion main del juego, es decir que vamos a tener que pensar como mover la serpiente, como saber si se ha comido una bola, como hacer crecer la serpiente, y como saber si ha chocado con una pared o con ella misma.

También habra que tener en cuenta la velocidad de la serpiente, que va aumentando a medida que crece, y varios aspectos mas. En definitiva veremos corazón de la aplicación que hace correr todos los objetos que creamos en los anteriores tutoriales.

Lo primero que debemos hacer es enumerar todos los estados posibles para el juego, para ello crearemos un enum como el siguiente :

Archivo : ObjetoSnake.h
1
2
3
4
5
6
7
8
9
// Enumeracion de los estados del juego
enum EstadoSnake {
EnJuego = 0,
EnPausa = 1,
EnRecords = 2,
EnRecords_NuevoRecord = 3,
EnSiguienteNivel = 4,
EnError = 5
};

Como podéis ver hay 6 estados :
El estado EnJuego se usara cuando estamos jugando.
El estado EnPausa se usara cuando hagamos una pausa en el juego (con la tecla ESC).
El estado EnRecords se usara para VISUALIZAR los records.
El estado EnRecords_NuevoRecord se usara para GUARDAR un nuevo record.
El estado EnSiguienteNivel se usara mientras tenemos un mensaje advirtiendo que el siguiente nivel va a empezar.
Y por último el estado EnError se usara cuando tengamos que mostrar algún error.

Ahora que ya tenemos mas o menos claros los estados del juego, podemos echar un vistazo a la declaración de ObjetoSnake :

Archivo : ObjetoSnake.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Clase que contiene todas las funciones para controlar el juego
class ObjetoSnake {
public : //////////////////// Miembros publicos
// -Constructor
ObjetoSnake(void);
// -Destructor
~ObjetoSnake(void);
// -Función que inicia el juego y se enlaza con el marcador,
// el tablero, el mensaje y los records
void Enlazar( ObjetoEscena_Marcador *nMarcador,
ObjetoEscena_Tablero *nTablero,
ObjetoEscena_Mensaje *nMensaje,
ObjetoEscena_Records *nRecords );
// -Función para empezar el juego
void EmpezarJuego(void);
// -Funcion que carga en memoria los datos del nivel
BOOL CargarNivel(const UINT nNivel);
// -Función que retorna una posición que este libre para
// poder insertar una nueva bola
POINT BolaAleatoria(void);
// -Función que hace avanzar la serpiente una posición
// Tambien comprueba si se ha comido bola en el movimiento,
// en ese caso agranda la serpiente.
void Movimiento(void);
// -Función que mira el teclado
void Teclado(void);
// -Variable que determina el estado de juego
EstadoSnake Estado;
// -Objeto que carga datos de los niveles
ObjetoSnake_Nivel Nivel;
// -Numero del nivel actual
UINT NivelActual;
private:
int Direccion;
ObjetoEscena_Marcador *Marcador;
ObjetoEscena_Mensaje *Mensaje;
ObjetoEscena_Tablero *Tablero;
ObjetoEscena_Records *Records;
};

Para empezar tenemos la función Enlazar la cual utilizaremos para enlazar todos los ObjetoEscena_* que intervienen en el juego con esta clase, y debe usarse solo una vez al crear las ventanas translucidas.

En segundo lugar tenemos la función EmpezarJuego que se usara para inicializar el juego de forma que empieze por el nivel 1. Luego tenemos la función CargarNivel que será la encargada de utilizar el ObjetoSnake_Nivel para obtener los datos de un nivel , y crearlo mandándole los datos necesarios tanto al marcador como al tablero. Y después tenemos la función BolaAleatoria que se usara para crear una bola en una posición donde no haya ni una porción de la serpiente ni del muro.

Por último tenemos las funciones Movimiento y Teclado. La función Movimiento es la que mueve la serpiente y se asegura de que el juego no ha terminado. Y la función Teclado es la encargada de obtener las pulsaciones de teclado y asignar la dirección de la serpiente entre otras cosas.

Veamos la función EmpezarJuego :

Archivo : ObjetoSnake.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// -Función que inicia el juego y se enlaza con el marcador y el tablero
void ObjetoSnake::EmpezarJuego(void) {
Estado = EnJuego;
NivelActual = 1;
// Asigno los valores del marcador
Marcador->Velocidad = 100;
Marcador->Recorrido = 0;
Marcador->Puntuacion = 0;
Marcador->ColorFondo = RGB(20, 20, 120);
// Cargo el nivel y si no existe mostramos un error.
if (CargarNivel(NivelActual) == FALSE) {
Estado = EnError;
Mensaje->MostrarMensaje(TEXT("ERROR!! no se puede cargar el primer nivel."), true);
}
Tablero->ColorFondo = RGB(20, 20, 120);
}

Esta función en esencia inicializa los valores del juego para empezar por el primer nivel, hay que destacar que si la función CargarNivel retorna FALSE es porque no encuentra el nivel, y en ese caso el juego no podrá empezar y mostraremos un error.

Veamos ahora la función CargarNivel :

Archivo : ObjetoSnake.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// -Función que carga los datos de un nivel
BOOL ObjetoSnake::CargarNivel(const UINT nNivel) {
UINT i = 0;
BOOL ExisteNivel = Nivel.CargarNivel(nNivel);
if (ExisteNivel == FALSE) return FALSE;
Tablero->MostrarTablero(Nivel.AnchoTablero, Nivel.AltoTablero);
Marcador->MostrarMarcador();
Marcador->Velocidad = Nivel.VelocidadInicial;
Direccion = 6;
// Añado los muros
Tablero->Muro.resize(0);
for (i = 0; i < Nivel.Muro.size(); i++) {
Tablero->Muro.push_back(Nivel.Muro[i]);
Tablero->Muro[Tablero->Muro.size() -1].x ++;
Tablero->Muro[Tablero->Muro.size() -1].y ++;
}
// Añado la serpiente
Tablero->Serpiente.resize(0);
POINT Serpiente = { Nivel.AnchoTablero / 2, Nivel.AltoTablero / 2 };
for (i = 0; i < Nivel.TamInicialSerpiente; i++) {
Serpiente.x --;
Tablero->Serpiente.push_back(Serpiente);
}
// Añado las bolas
Tablero->Bolas.resize(0);
for (i = 0; i < Nivel.NumBolas; i++) {
Tablero->Bolas.push_back(BolaAleatoria());
}
return TRUE;
}

Lo primero que hacemos en esta función es utilizar el ObjetoSnake_Nivel para intentar cargar el nivel. Si el nivel se ha cargado inicializamos el tablero y el marcador con sus funciones mostrar (recordad que el tablero es el que establece el tamaño de toda la escena asi que debe inicializarse el primero SIEMPRE). Luego añadimos los muros al tablero, creamos la serpiente, y creamos las bolas necesarias.

Pasemos a la función BolaAleatoria :

Archivo : ObjetoSnake.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// -Función que retorna una posición que este libre para una bola
POINT ObjetoSnake::BolaAleatoria(void) {
bool BolaValida = false;
POINT R;
// Mientras la bola no este en una posicion valida...
while (BolaValida == false) {
R.x = (rand()%(Nivel.AnchoTablero -1)) +1;
R.y = (rand()%(Nivel.AltoTablero -1)) +1;
BolaValida = true;
// Miramos si la bola esta en una posición donde hay una porción de la serpiente
for (size_t i = 0; i < Tablero->Serpiente.size(); i++) {
if (R.x == Tablero->Serpiente[i].x && R.y == Tablero->Serpiente[i].y) BolaValida = false;
}
// Miramos si la bola esta en una posición donde hay una porción del muro
for (size_t i = 0; i < Tablero->Muro.size(); i++) {
if (R.x == Tablero->Muro[i].x && R.y == Tablero->Muro[i].y) BolaValida = false;
}
}
return R;
}

Esta función básicamente calcula una posición aleatoria y luego mira si esa posición esta ocupada por la serpiente o por el muro, y en ese caso vuelve a calcular una posición aleatoria hasta que la bola no esté en una posición ocupada.

Ahora veamos la función Movimiento :

Archivo : ObjetoSnake.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// -Función que hace avanzar la serpiente una posición
// Tambien comprueba si se ha comido bola en el movimiento, en ese caso agranda la serpiente.
void ObjetoSnake::Movimiento(void) {
size_t i = 0;
Marcador->Recorrido ++;
bool Fin = false;
// 1 Miramos si la cabeza de la serpiente esta encima de una bola
for (i = 0; i < Tablero->Bolas.size(); i++) {
if (Tablero->Serpiente[0].x == Tablero->Bolas[i].x && Tablero->Serpiente[0].y == Tablero->Bolas[i].y) {
// Se ha comido la bola, creamos una nueva bola, ampliamos la puntuación y subimos la velocidad.
Marcador->Puntuacion ++;
Tablero->Serpiente.push_back(Tablero->Serpiente[Tablero->Serpiente.size() -1]);
if (Marcador->Velocidad > 4) Marcador->Velocidad -= Nivel.AmpliacionVelocidad;
Tablero->Bolas[i] = BolaAleatoria();
}
}
// 2.1 Miramos si la cabeza de la serpiente ha chocado con el resto de la serpiente
for (i = 2; i < Tablero->Serpiente.size(); i++) {
if (Tablero->Serpiente[i].x == Tablero->Serpiente[0].x && Tablero->Serpiente[i].y == Tablero->Serpiente[0].y) {
Fin = true;
break;
}
}
// 2.2 Miramos si la cabeza de la serpiente ha chocado con el muro
for (i = 0; i < Tablero->Muro.size(); i++) {
if (Tablero->Serpiente[0].x == Tablero->Muro[i].x && Tablero->Serpiente[0].y == Tablero->Muro[i].y) {
Fin = true;
break;
}
}
// 3 FINAL DEL NIVEL
if (Fin == true) {
// Se ha terminado el nivel
if (Nivel.PuntosParaPasar >= Marcador->Puntuacion) { // Puntuacion insuficiente
if (Records->MostrarRecords(Marcador->Puntuacion, Marcador->Recorrido, NivelActual) == TRUE)
Estado = EnRecords_NuevoRecord;
else
Estado = EnRecords;
}
else { // La puntuacion es valida, miramos si existe otro nivel superior
if (CargarNivel(NivelActual +1) == TRUE) {
NivelActual ++;
Estado = EnSiguienteNivel;
Mensaje->MostrarMensaje(TEXT("Presiona ESPACIO para continuar."));
}
else { // No hay mas niveles
if (Records->MostrarRecords(Marcador->Puntuacion, Marcador->Recorrido, NivelActual) == TRUE)
Estado = EnRecords_NuevoRecord;
else
Estado = EnRecords;
}
}
return;
}
// 4 Desplazamos la serpiente
for (i = Tablero->Serpiente.size() -1; i > 0; i--) {
Tablero->Serpiente[i] = Tablero->Serpiente[i - 1];
}
// 5 Miramos la nueva dirección de la serpiente
switch (Direccion) {
case 4 : // Izquierda
if (Tablero->Serpiente[0].x > 1) Tablero->Serpiente[0].x --;
else Tablero->Serpiente[0].x = Nivel.AnchoTablero;
break;
case 6 : // Derecha
if (Tablero->Serpiente[0].x < static_cast<int>(Nivel.AnchoTablero)) Tablero->Serpiente[0].x ++;
else Tablero->Serpiente[0].x = 1;
break;
case 8 : // Arriba
if (Tablero->Serpiente[0].y > 1) Tablero->Serpiente[0].y --;
else Tablero->Serpiente[0].y = Nivel.AltoTablero;
break;
case 2 : // Abajo
if (Tablero->Serpiente[0].y < static_cast<int>(Nivel.AltoTablero)) Tablero->Serpiente[0].y ++;
else Tablero->Serpiente[0].y = 1;
break;
}
}

Esta función esencialmente comprueba que la serpiente no haya comido bolas, que no haya chocado con ella misma o con el muro, y en ese caso hace avanzar la serpiente. Mejor veámoslo por partes :

1 Miramos si la cabeza de la serpiente está en la misma posición que una bola, en ese caso sumamos un punto, hacemos mas grande la serpiente, ampliamos la velocidad y creamos una nueva bola.

2.1 Miramos si la cabeza de la serpiente está en la misma posición que cualquier parte del resto de la serpiente, en ese caso asignamos la variable Fin a true.

2.2 Miramos si la cabeza de la serpiente esta en la misma posición que cualquier parte del muro, en ese caso asignamos la variable Fin a true.

3 Miramos si la variable Fin es true, en ese caso miramos si podemos cargar el siguiente nivel, y si se ha podido cargar seguimos con el juego. En caso de no poder cargar un nivel es que no hay mas, y mostraremos los records. La función MostrarRecords retorna TRUE si el record actual ha superado algún record, en ese caso asignaremos el estado a EnRecords_NuevoRecord, en caso contrario asignaremos el estado a EnRecords.

4 Desplazamos la serpiente, para ello hacemos un bucle que desplaza las posiciones del vector de forma que la última posición se descarta. Para verlo mas claro observad las siguientes ilustraciones :
Figura 1
         
       
X= 10
Y= 9
 
       
X= 10
Y= 10
 
    P= 3
X= 7
Y= 11
P= 2
X= 8
Y= 11
P= 1
X= 9
Y= 11
P= 0
X= 10
Y= 11
 
           
Figura 2
       
       
X= 10
Y= 9
 
        P= 0
X= 10
Y= 10
   
   
X= 7
Y= 11
P= 3
X= 8
Y= 11
P= 2
X= 9
Y= 11
P= 1
X= 10
Y= 11
 
           
Figura 3
             
        P= 0
X= 10
Y= 9
 
        P= 1
X= 10
Y= 10
 
 
X= 7
Y= 11

X= 8
Y= 11
P= 3
X= 9
Y= 11
P= 2
X= 10
Y= 11
 
             

P es la posición en el vector Serpiente. X y Y son la posición en el tablero.

Que sacamos en claro de esto? siempre que la serpiente avanza, el último cuadro no es necesario, por lo que creamos una nueva posición para la cabeza con Y-1 (Y-1 porque la serpiente va hacia arriba en este ejemplo, si fuera hacia abajo seria Y+1), y luego hacemos correr el resto del array 1 posición hacia atrás.

5 Asignamos la nueva posición de la cabeza de la serpiente, para ello miramos la ultima dirección que se asigno a la serpiente y desplazamos la cabeza a dicha posición. Hay que remarcar que también comprobamos que la serpiente no se salga del tablero, y en ese caso corregimos la posición de la cabeza para que salga en el lado opuesto del tablero.
Y otra cosa a destacar es que los valores de las direcciones corresponden a las flechas del teclado numérico.

Vista la función Movimiento ya solo nos queda la función Teclado de esta clase :

Archivo : ObjetoSnake.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// -Función que mira el teclado
void ObjetoSnake::Teclado(void) {
switch (Estado) {
// Mientras esta en juego miramos los cursores y la tecla ESC
case EnJuego :
if (GetAsyncKeyState(VK_LEFT) != 0) {
if (Tablero->Serpiente[1].x != Tablero->Serpiente[0].x + 1 && Tablero->Serpiente[1].y != Tablero->Serpiente[0].y)
Direccion = 4;
}
if (GetAsyncKeyState(VK_RIGHT) != 0) {
if (Tablero->Serpiente[1].x != Tablero->Serpiente[0].x - 1 && Tablero->Serpiente[1].y != Tablero->Serpiente[0].y)
Direccion = 6;
}
if (GetAsyncKeyState(VK_UP) != 0) {
if (Tablero->Serpiente[1].x != Tablero->Serpiente[0].x && Tablero->Serpiente[1].y != Tablero->Serpiente[0].y + 1)
Direccion = 8;
}
if (GetAsyncKeyState(VK_DOWN) != 0) {
if (Tablero->Serpiente[1].x != Tablero->Serpiente[0].x && Tablero->Serpiente[1].y != Tablero->Serpiente[0].y - 1)
Direccion = 2;
}
if (GetAsyncKeyState(VK_ESCAPE) != 0) {
Mensaje->MostrarMensaje(TEXT("Juego en pausa.\nPresiona ESPACIO para reanudar el juego."));
Estado = EnPausa;
}
break;
// Cuando esta mirando records miramos si presiona espacio
case EnRecords :
if (GetAsyncKeyState(VK_SPACE) != 0) {
Mensaje->OcultarMensaje();
Records->OcultarRecords();
EmpezarJuego();
}
break;
// Cuando esta en pausa o en el inicio del siguiente nivel miramos si presiona espacio
case EnSiguienteNivel :
Direccion = 6;
case EnPausa :
if (GetAsyncKeyState(VK_SPACE) != 0) {
Estado = EnJuego;
Mensaje->OcultarMensaje();
}
break;
}
}

Esta función como su nombre indica se utiliza para mirar el teclado, básicamente actua de una forma u otra según el estado actual. Mientras esta en juego mira los 4 cursores y la tecla ESC (para la pausa), mientras esta en records mira si se presiona espacio para volver a empezar el juego, y mientras esta en pausa o en el inicio del siguiente nivel mira si se ha presionado espacio para reanudar el juego. Hay que remarcar que asignamos Direccion a 6 solo en el estado EnSiguienteNivel, porque mientras no se ha presionado el espacio para continuar podemos cambiar la direccion inicial, y esto puede terminar con que la serpiente choque nada mas empezar.

Para determinar si una tecla se ha presionado, en el juego se usa la API GetAsyncKeyState a la cual debemos especificar la tecla virtual que queremos mirar.

Perfecto con esto ya hemos visto en gran parte el ObjetoSnake, ahora veamos como quedara el ObjetoEscena final al que llamaremos MiEscena :

Archivo : ObjetoSnake.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Objeto final : ObjetoHWND -> PlantillaEventos -> ObjetoVentana -> ObjetoEscena -> MiEscena
class MiEscena : public ObjetoEscena {
public :
MiEscena(void) {
Crear();
};
~MiEscena(void) {
};
// Objeto que controla el tablero
ObjetoEscena_Tablero *Tablero;
// Objeto que controla el marcador
ObjetoEscena_Marcador *Marcador;
// Objeto que controla el mensaje
ObjetoEscena_Mensaje *Mensaje;
// Objeto que controla los records
ObjetoEscena_Records *Records;
// Objeto que contiene los datos del juego y lo controla
ObjetoSnake Snake;
// Re-emplazamos el mensaje WM_CHAR para poder pasarle pulsaciones de teclado a la ventana de los records
LRESULT Evento_Teclado_Caracter(const UINT Caracter, const UINT Repeticion, const UINT Params) {
if (Records->IntroduciendoNuevoRecord() == TRUE) {
Records->Agregar_Caracter(Caracter);
}
return 0;
};
// Re-emplazamos el evento cerrar y añadimos la API PostQuitMessage
// De esta forma cuando se cierre la ventana se cerrara la aplicación
LRESULT Evento_Cerrar(void) {
PostQuitMessage(0);
return ObjetoVentana::Evento_Cerrar();
};
protected:
// Función que crea la ventana y añade 2 ventanas translucidas y una imagen de fondo
void Crear(void) {
Marcador = new ObjetoEscena_Marcador;
Tablero = new ObjetoEscena_Tablero;
Mensaje = new ObjetoEscena_Mensaje;
Records = new ObjetoEscena_Records;
Escena_AgregarVentana(Records);
Escena_AgregarVentana(Mensaje);
Escena_AgregarVentana(Marcador);
Escena_AgregarVentana(Tablero);
Escena_ImagenFondo(IDB_BITMAP1); // Asignamos la imagen de los recursos
ColorFondo = RGB(198, 219, 240);
CrearEscena( NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE,
TEXT("Ejemplo tutorial 2.6"), 100, 100,
340, 415, NULL );
};
};

En esencia esta clase contiene todas las ventanas translucidas necesarias para el juego y las inicializa. Ademas se ha remplazado el Evento_Cerrar para que cuando se cierre la ventana se termine la aplicación, y el Evento_Teclado_Caracter para mandar las pulsaciones del teclado SOLO cuando se esté introduciendo un record.

Por lo demás remarcar que la función Crear se utiliza en el constructor, asi que al crear el objeto se creara la VentanaEscena directamente. Y otra cosa es que inicializamos con new las ventanas translucidas, dichas ventanas se borraran por el destructor de ObjetoEscena cuando esta clase sea destruida.

Y por último veamos el WinMain :

Archivo : ObjetoSnake.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// WinMain
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DWORD TiempoMS = 0;
DWORD TiempoFPS = 0;
DWORD Frames = 0;
// Creamos la clase MiEscena que en el contructor crea la ventana.
MiEscena Ventana;
// Iniciamos el juego
Ventana.Snake.Enlazar(Ventana.Marcador, Ventana.Tablero, Ventana.Mensaje, Ventana.Records);
Ventana.Snake.EmpezarJuego();
bool FinApp = false;
MSG Mensaje;
HDC hDC;
// Mientras no sea el final de la aplicación
while (FinApp == false) {
// 1 Miramos el estado
switch (Ventana.Snake.Estado) {
// Se esta jugando
case EnJuego :
Ventana.Snake.Teclado();
if (TiempoMS + Ventana.Marcador->Velocidad < GetTickCount()) {
Ventana.Snake.Movimiento();
TiempoMS = GetTickCount();
}
break;
// Hay una ventana informativa
case EnPausa :
case EnRecords :
case EnSiguienteNivel :
Ventana.Snake.Teclado();
break;
// Se esta introcuciendo un nuevo record
case EnRecords_NuevoRecord :
if (Ventana.Records->IntroduciendoNuevoRecord() == FALSE) {
Ventana.Records->GuardarRecords();
Ventana.Snake.Estado = EnRecords;
}
break;
}
// 2 Calculamos los frames por segundo
if (TiempoFPS + 1000 < GetTickCount()) {
Ventana.Marcador->FPS = Frames;
Frames = 0;
TiempoFPS = GetTickCount();
}
Frames ++;
// 3 Repintamos la escena
hDC = GetDC(Ventana.hWnd());
Ventana.Escena_Pintar(hDC);
ReleaseDC(Ventana.hWnd(), hDC);
// 4 Miramos mensajes de la ventana
if (PeekMessage(&Mensaje, NULL, 0, 0, PM_REMOVE)) {
if (Mensaje.message == WM_QUIT)
FinApp = true;
TranslateMessage(&Mensaje);
DispatchMessage(&Mensaje);
}
}
return 0;
}

Como podeis observar al principio creamos una clase MiEscena, enlazamos el ObjetoSnake a las ventanas translucidas y llamamos a la función EmpezarJuego.

El bucle principal se basa en que FinApp sea false para continuar. Veamos el resto del bucle principal por partes :

1 Miramos el estado del juego : Si se está jugando miramos el teclado y luego miramos que haya pasado el tiempo mínimo para hacer otro movimiento, y en ese caso hacemos el movimiento y volvemos a guardar el tiempo actual con la API GetTickCount. Si hay una ventana informativa, únicamente miraremos el teclado. Y si se está introduciendo un nuevo record miraremos el miembro IntroduciendoNuevoRecord para determinar cuándo se termina de introducir el record, y cuando sea FALSE guardaremos los records y cambiaremos el estado a EnRecords.

2 Hacemos un cálculo de los frames por segundo. Para hacer este cálculo se usa un contador llamado Frames al que se le suma 1 por cada vez que se pinta la ventana, y al pasar 1000 milisegundos ese valor nos dira cuantas veces se ha pintado la escena.

3 Repintamos la escena, obtenemos el DC de la ventana con la API GetDC, utilizamos Ventana.Escena_Pintar para pintar la escena y utilizamos la API ReleaseDC para liberar el DC de la ventana.

4 Miramos posibles eventos que se puedan haber mandado. Para ello utilizamos la API PeekMessage que mira si hay algún evento, y en el caso de retornar TRUE utilizamos las API's TranslateMessage y DispatchMessage para obtener dicho evento. Hay que remarcar que si el mensaje es WM_QUIT asignaremos FinApp a false con lo que terminara la aplicación.

Con esto terminamos la segunda fase del tutorial, podéis continuar con el tutorial 3.0 Introducción al Instalador y el Ensamblador.

Descargar tutorial WinAPI completo Snake compilada