Tutorial WINAPI C++ 2.5 (Creación del ObjetoSnake_Nivel)
Categorías : Windows, Programación, C y C++.

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 :
// Clase que contiene todas las funciones para controlar el juegoclass ObjetoSnake_Nivel {public : //////////////////// Miembros publicos// -ConstructorObjetoSnake_Nivel(void);// -Destructor~ObjetoSnake_Nivel(void);// -Función para leer un nivel del juegoBOOL CargarNivel(const UINT nNivel);// -Variable que nos indica el numero de bolas a mostrarUINT NumBolas;// -Variable que nos indica la velocidad// inicial en milisegundosUINT VelocidadInicial;// -Variable que nos indica la ampliación// de velocidad en milisegundosUINT AmpliacionVelocidad;// -Variable que nos indica el tamaño inicial// de la serpiente en cuadrosUINT TamInicialSerpiente;// -Variable que nos indica el total de puntos// que se necesitan para pasar de nivelUINT PuntosParaPasar;// -Variable que nos indica el ancho del tablero en pixelesUINT AnchoTablero;// -Variable que nos indica la altura del tablero en pixelesUINT AltoTablero;// -Vector donde guardamos las posiciones// de los cuadros que son parte del murostd::vector<POINT> Muro;protected : ///////////////// Miembros protegidos// -Función que obtiene datos de una linea y// los almacena en su lugar correspondienteBOOL 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 :
// Función para leer un nivel del juegoBOOL ObjetoSnake_Nivel::CargarNivel(const UINT nNivel) {// Inicializo los datosNumBolas = 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 bufferif (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 Windowsif (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 archivoif (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 :
// Funcion para parsear una linea del archivo del nivel y obtener sus datosBOOL ObjetoSnake_Nivel::EvaluarLinea(const char *Linea, const UINT TamLinea) {// Declaración de variableschar 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 valorfor (i = 0; i < TamLinea; i++) {// Cuando pasamos por el signo igual empezamos la parte del valorif (Linea[i] == '=') {NombreValor = true;i ++;}// Si NombreValor es false, el caracter pertenece al Nombreif (Linea[i] != ' ' && NombreValor == false) {Nombre[TamNombre] = Linea[i];TamNombre ++;}// Si NombreValor es true, el caracter pertenece al Valorif (Linea[i] != ' ' && NombreValor == true) {Valor[TamValor] = Linea[i];TamValor ++;}}// Si no se ha llegado a obtener valorif (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 valorif (_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 tableroPosPosicion = 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 FALSEif (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 |