NovaMonoFix
Errores PHP
X
Usuario
Password
0 FPS

Tutorial WINAPI C++ 2.5 (Creación del ObjetoSnake_Nivel)

14 de Mayo del 2010 por Josep Antoni Bover, 22 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.5 (Creación del ObjetoSnake_Nivel)

En este tutorial no vamos a tocar la API prácticamente pero veremos cómo hacer un parsing de un fichero de texto para obtener datos que usaremos para definir un nivel/pantalla del juego.

Este objeto tiene por objetivo leer un archivo de texto e identificar y almacenar los valores que contiene dentro de forma que pueda crear paredes en la pantalla del juego donde nos encontremos. Para ello usaremos el ObjetoArchivo que creamos anteriormente en el tutorial 2.3

Veamos la declaración de ObjetoSnake_Nivel :

Archivo : ObjetoSnake_Nivel.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
// Clase que contiene todas las funciones para controlar el juego
class ObjetoSnake_Nivel {
public : //////////////////// Miembros publicos
// -Constructor
ObjetoSnake_Nivel(void);
// -Destructor
~ObjetoSnake_Nivel(void);
// -Función para leer un nivel del juego
BOOL CargarNivel(const UINT nNivel);
// -Variable que nos indica el numero de bolas a mostrar
UINT NumBolas;
// -Variable que nos indica la velocidad
// inicial en milisegundos
UINT VelocidadInicial;
// -Variable que nos indica la ampliación
// de velocidad en milisegundos
UINT AmpliacionVelocidad;
// -Variable que nos indica el tamaño inicial
// de la serpiente en cuadros
UINT TamInicialSerpiente;
// -Variable que nos indica el total de puntos
// que se necesitan para pasar de nivel
UINT PuntosParaPasar;
// -Variable que nos indica el ancho del tablero en pixeles
UINT AnchoTablero;
// -Variable que nos indica la altura del tablero en pixeles
UINT AltoTablero;
// -Vector donde guardamos las posiciones
// de los cuadros que son parte del muro
std::vector<POINT> Muro;
protected : ///////////////// Miembros protegidos
// -Función que obtiene datos de una linea y
// los almacena en su lugar correspondiente
BOOL EvaluarLinea(const char *Linea, const UINT TamLinea);
};

Como podéis observar la clase no tiene mucha complicación, existen 2 funciones CargarNivel y EvaluarLinea, y luego tenemos todos los parámetros que necesitamos para el nivel del juego.

La función CargarNivel carga en memoria todo el archivo txt, lo separa por líneas, y analiza cada línea con la función EvaluarLinea. Para cargar un nivel debemos especificar que numero de nivel queremos cargar. La idea es crear un archivo para cada nivel, por ejemplo “Nivel1.txt, Nivel2.txt, etc…”

Al usar un archivo txt debemos tener claro que hay que usar las funciones ANSI y no las funciones Unicode para realizar operaciones con el archivo.

Veamos la función CargarNivel :

Archivo : ObjetoSnake_Nivel.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
// Función para leer un nivel del juego
BOOL ObjetoSnake_Nivel::CargarNivel(const UINT nNivel) {
// Inicializo los datos
NumBolas = 0;
VelocidadInicial = 0;
AmpliacionVelocidad = 0;
TamInicialSerpiente = 0;
PuntosParaPasar = 0;
AnchoTablero = 0;
AltoTablero = 0;
Muro.resize(0);
ObjetoArchivo ArchivoNivel;
TCHAR RutaNivel[] = TEXT("Nivel?.txt");
char *Buffer = NULL;
DWORD Tam = 0;
DWORD n = 0;
char Linea[1024];
char NumeroNivel[16];
int TotalDatos = 0; // 7 + AltoTablero;
// Cambiamos el interrogante por el numero del nivel
_itoa_s(nNivel, NumeroNivel, 16, 10);
RutaNivel[5] = NumeroNivel[0];
TCHAR NN[MAX_PATH];
GetCurrentDirectory(MAX_PATH, NN);
wcscat_s(NN, MAX_PATH, TEXT("\\"));
wcscat_s(NN, MAX_PATH, RutaNivel);
// MessageBox(NULL, NN, RutaNivel, MB_OK);
// Leo todo el archivo en un buffer
if (ArchivoNivel.AbrirArchivoLectura(NN) == TRUE) {
Tam = ArchivoNivel.ObtenerLongitud32(NULL);
Buffer = new char[Tam];
ArchivoNivel.Leer(Buffer, Tam * sizeof(char));
ArchivoNivel.CerrarArchivo();
}
else {
return FALSE;
}
for (DWORD i = 0; i < Tam; i++) {
// Omitimos el caracter intro y el caracter 10 que siempre va detras del intro en Windows
if (Buffer[i] != 13 && Buffer[i] != 10) {
Linea[n] = Buffer[i];
n++;
}
if (Buffer[i] == ';' || Buffer[i] == '\n' && n != 0) {
Linea[n -1] = '\0';
if (EvaluarLinea(Linea, n) == TRUE) TotalDatos ++;
n = 0;
}
}
delete Buffer;
// Si el total de datos evaluados es 7 + el alto del tablero se ha cargado correctamente el archivo
if (TotalDatos == static_cast<int>(7 + AltoTablero)) return TRUE;
return FALSE;
}

Para empezar inicializamos todos los valores a cero por si las moscas, y creamos las variables que vamos a necesitar. Lo que hacemos después es leer todo el archivo en un buffer, y luego escaneamos todo el buffer para encontrar el carácter 13 (que es el intro), además también ignoramos todos los caracteres 10 ya que en Windows cada intro en un txt se simboliza con 2 caracteres, el 13 seguido del 10. Una vez tenemos la línea la pasamos a la función EvaluarLinea para que esta determine si es un valor valido y que valor es. Por último borramos el buffer de la memoria, y comprobamos que se han aceptado un total de lineas por la función EvaluarLinea que debe ser igual a 7 mas la altura del tablero. En el caso de que esa comprobación falle quiere decir que nos faltan datos del nivel, por lo cual retornaremos FALSE indicando un error al leer el nivel.

Ahora veamos la función EvaluarLinea que es la que realmente hace el parsing :

Archivo : ObjetoSnake_Nivel.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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Funcion para parsear una linea del archivo del nivel y obtener sus datos
BOOL ObjetoSnake_Nivel::EvaluarLinea(const char *Linea, const UINT TamLinea) {
// Declaración de variables
char Nombre[512];
char Valor[512];
char TableroY[64];
char Posicion[4];
UINT PosPosicion;
UINT TamNombre = 0;
UINT TamValor = 0;
bool NombreValor = false;
POINT PuntoMuro;
size_t i = 0;
// Quitamos todos los espacios y separamos el nombre del valor
for (i = 0; i < TamLinea; i++) {
// Cuando pasamos por el signo igual empezamos la parte del valor
if (Linea[i] == '=') {
NombreValor = true;
i ++;
}
// Si NombreValor es false, el caracter pertenece al Nombre
if (Linea[i] != ' ' && NombreValor == false) {
Nombre[TamNombre] = Linea[i];
TamNombre ++;
}
// Si NombreValor es true, el caracter pertenece al Valor
if (Linea[i] != ' ' && NombreValor == true) {
Valor[TamValor] = Linea[i];
TamValor ++;
}
}
// Si no se ha llegado a obtener valor
if (NombreValor == false)
return FALSE;
// Terminamos las cadenas con el caracter '\0'
Nombre[TamNombre] = '\0';
TamNombre ++;
Valor[TamValor] = '\0';
TamValor ++;
// Comprobamos el nombre del valor, y si coincide le asignamos el valor
if (_strcmpi(Nombre, "NumeroBolas") == 0) {
NumBolas = atoi(Valor);
return TRUE;
}
if (_strcmpi(Nombre, "VelocidadInicial") == 0) {
VelocidadInicial = atoi(Valor);
return TRUE;
}
if (_strcmpi(Nombre, "AmpliacionVelocidad") == 0) {
AmpliacionVelocidad = atoi(Valor);
return TRUE;
}
if (_strcmpi(Nombre, "TamañoInicialSerpiente") == 0) {
TamInicialSerpiente = atoi(Valor);
return TRUE;
}
if (_strcmpi(Nombre, "PuntosParaPasar") == 0) {
PuntosParaPasar = atoi(Valor) - 1;
return TRUE;
}
if (_strcmpi(Nombre, "AnchoTablero") == 0) {
AnchoTablero = atoi(Valor);
return TRUE;
}
if (_strcmpi(Nombre, "AltoTablero") == 0) {
AltoTablero = atoi(Valor);
return TRUE;
}
// Miramos si el nombre contiene 'Tablero['
if (TamNombre > 7) {
for (i = 0; i < 8; i++) TableroY[i] = Nombre[i];
TableroY[i] = '\0';
if (_strcmpi(TableroY, "Tablero[") == 0) {
// Obtenemos la posicion Y de la parte del tablero
PosPosicion = 0;
for (i = i; Nombre[i] != ']'; i++) {
Posicion[PosPosicion] = Nombre[i];
PosPosicion ++;
}
Posicion[PosPosicion] = '\0';
PuntoMuro.y = atoi(Posicion);
// Si el punto no corresponde a una posicion valida del tablero retornamos FALSE
if (PuntoMuro.y < 0 && PuntoMuro.y > static_cast<int>(AltoTablero)) return FALSE;
for (i = 0; i < AnchoTablero; i++) {
PuntoMuro.x = i;
if (Valor[i] != '.') Muro.push_back(PuntoMuro);
}
return TRUE;
}
}
return FALSE;
}

Lo primero que hacemos es quitar todos los espacios de la línea, de forma que nos quede NombreValor=Num; por muchos espacios que tuviera por el medio. Al mismo tiempo que quitamos los espacios almacenamos en el nombre los caracteres que vamos encontrando que no sean el símbolo =. En el momento que encontramos el símbolo = almacenaremos los siguientes caracteres como parte del valor en vez del nombre.

Una vez tenemos separado el nombre y el valor comparamos el nombre con los nombres que tengo asignados para cada valor del nivel, y cuando lo encontramos asignamos el valor de la línea a su ubicación correspondiente. Por último los valores del tablero son dinámicos, es decir no hay un número fijo definido, ya que lo definimos nosotros mismos en AltoTablero, por lo que necesitamos tener tantas líneas como la variable AltoTablero indique. Además esas líneas en sus valores solo podrán tener tantos caracteres como AnchoTablero indique.

Por último remarcar que en las variables Tablero[] se tomara como parte del muro cualquier carácter que no sea un punto.

En el ejemplo 2.5 utilizamos esta clase para cargar un nivel en memoria.

Y por fin llegamos al último tutorial de la segunda parte donde veremos todo el juego ensamblado y funcionando : 2.6 - Terminando el Snake (ObjetoSnake).

Descargar tutorial WinAPI completo Snake compilada