Archive for October, 2006

Eileen, programando el tetris

Sunday, October 15th, 2006

Esta semana me he centrado en Eileen, de cara a dejarla más o menos terminada. Debido a la cantidad de curro por hacer en otros proyectos, me temo que se quedará en este estado durante un tiempecillo. (Lo lamento por Rosa y compañía, viciad@s de tales juegos).

 

Una de cambios

En el post anterior, comentaba que me decidí por usar un sprite por cada pieza. Bien, debido a problemillas varios con el giro de las piezas, opté por volver al planteamiento de formar las piezas a través de cuadritos. Con ello conseguí formar cualquier pieza (antes necesitaba que la pieza comenzara por un cuadro arriba a la derecha que me servía como guía) y tener más control en el giro. Además, a la hora de quitar las líneas no tengo que hacer ninguna conversión rara, simplemente quito las líneas de Matrix (los elementos de la fila que desaparece se resetean a 0) y muevo progresivamente todas las líneas. (El progresivamente es un decir, lo hago todo de un plumazo… pero la idea es que en la 1.0 haya una especie de animación por cada línea que desaparece).

 

Niveles, puntos y game over

Una vez que el esqueleto del programa quedaba operativo.. había que implementar detalles tales como los niveles o la puntuación.

Para el control de niveles me creé otra matriz, declarada como int Levels[2][2];. El 2 hace referencia al número de niveles y, el hecho de su bidimensionalidad se debe a que en cada fila guardo por un lado el número de piezas necesarias para cambiar de nivel (tras generar ese número de piezas, se cambia de nivel) y por otro lado la velocidad de bajada de las piezas. Teniendo una función que setee los valores, en el game loop sólo queda tomar esos valores donde sea necesario.

Para la carga de niveles, utilizo un switch o interruptor… si al entrar en el game loop vale false, cargo el nivel según una variable que me indica el nivel actual; si vale true, no cargo nada.. ;)

En cuanto a la puntuación y el número de piezas generadas, son simples contadores.

Por último, el gamer over sólo consiste en comprobar si la función que valida el movimiento hacia abajo devuelve true o false. Si la pieza no puede moverse hacia abajo (tras ser generada) es que el juego ha terminado, mostrando game over durante unos segundos y volviendo al menú.

 

Siguiente pieza

Otro de los detalles a implementar consistía en mostrar las piezas siguientes. Para ello, de nuevo uso una matriz, esta vez de dimensiones de 4×4 (número máximo de cuadraditos que tiene una pieza). En cada vuelta al game loop, actualizo esa matriz en función de la siguiente pieza a generar, que previamente guardo en una variable. Luego es mostrar el contenido de esa matriz y dibujar de forma pertinente.

 

El menú

Para el menú necesité ampliar Nessi con una clase que no tenía pensada, pero que sin duda ha sido y será muy útil. Nessi_XT_Menu carga los sprites o botones del menú, teniendo soporte para dos estados, el de seleccionado y en reposo. Con funciones miembro, se lleva el control de la opción seleccionada, el paso de una a otra… así que desde el programa sólo hay que cargar los sprites y poquillo más. :D

 

Versión 0.80, en fase beta

La versión subida tiene algunas cosillas que no me ha dado tiempo a implementar i seguro que algún que otro bug no detectado. (El coding express es lo que tiene… je,je,je..). Por ejemplo, las piezas no son generadas de forma aleatoria (o medianamente aleatoria), hay pocas piezas (sólo 5) y como he comentado, sólo dos niveles, muy facilitos. Pero bueno, para hacerse una idea de lo que en un futuro será… ;) :) Está por la sección de software… con un programa llamado installer2go, he generado un instalador, que siempre igual se agradece. ;) :)

 

Versión 0.83, varias mejoras…

Por petición de una de esas amigas aficionada a los puzzles, a noche de 17 de octubre he añadido varias mejoras. En primer lugar he solucionado un bug al término de todos los niveles, que se me había olvidado controlar. En relación a ellos, he ampliado los niveles a 4, aunque sigue habiendo sólo dos mapas (que se van alternando, pero con la velocidad en aumento). Por último, he mejorado la generación aleatoria de piezas… ya sí se puede decir que es aleatoria (los entendidos sabrán que no es del todo cierto, pero el juego generará piezas diferentes en cada ejecución. ;) :)

Eileen, comenzando…

Tuesday, October 3rd, 2006

Este fin de semana he estado ampliando y mejorando a Nessi. Para poner a prueba esas nuevas funcionalidades y con cierta mosca detrás de la oreja (nunca he hecho un tetris), he comenzado Eileen. A parte de los conceptos que todo juego ha de tener, como el de game loop, timing… para este juego tenía que pensar cómo gestionar las piezas y el tablero de juego (el rectángulo que va conteniendo las piezas que se van generando…). Bien…

La matriz Matrix

Pensando un poco sobre ello… una ruta a tomar podría ser basarlo en coordenadas y colisiones de los sprites (o piezas). Sin profundizar demasiado, me viene a la mente alguna de las limitaciones de tal planteamiento… como por ejemplo la eliminación de una fila tras hacer línea. Igual se podría hacer algún tipo de cálculo para solucionar cosas así, pero teniendo la experiencia exitosa de una matriz (en el buscaminas, el ajedrez y otros programillas que tengo por ahí con el interface más que simplón…) por debajo que controle todo… me decanté por ella.

Matrix quedó definida con 20 filas y 14 columnas… (el espacio del tablero dibujado en photoshop entre 25, tamaño del cuadro base). A través de una función de Reset, todos sus elementos son inicializados a cero, código que he tomado como marca de que esa casilla está vacía.

Las piezas

Otro aspecto importante, quizás de lo más importante, son las piezas. Antes he hablado del tamaño del cuadro base. En el tetris las piezas se desplazan por una especie de casillero con celdas (que no se ven, pero cada cuadro que forma una pieza, al desplazarse, se mueve de celda a celda). A donde quiero llegar es a cómo estarán hechas las piezas. Una pieza que sólo ocupe un cuadro o celda en cada movimiento (llamémosla A), no tiene complicación. Pero como es obvio, un tetris de sólo esa pieza quedaría muy pobre y poco jugable. Si meto una pieza que ocupe dos cuadros horizontales.. los problemas surgen. Una de las ideas era formar las piezas por unión de sprites únicos (la citada A): generar el tipo de pieza y en funcion de ello dibujar en pantalla la forma correspondiente, actualizando las posiciones de todos los A de la pieza y de todas las piezas. Mi principal problema con esto era gráfico. Me gustaba que las piezas tuvieran un borde alrededor, así que uniendo A con otra A, en el medio de la unión, tendría un borde algo feote. Como solución a esto podría haberme currado la pieza A sin borde una vez en cada lado… pero decidí optar por otra forma de hacerlo.

Cada pieza es un gráfico. En total, de momento, hay 10 piezas disponibles, que se irán generando con un random y moviéndose por Matrix. A tales efectos, cada pieza irá actualizando los elementos de Matrix que ocupe / libere con cada movimiento. Es decir, la pieza A comenzará en el elemento 0,0. Un ciclo despues… si el usuario no mueve hacia los lados, ocupará la posición 1,0. La manera de indicar ese ocupamiento es mediante números. (El número de pieza (1,2,3… hasta 10)) Para entender mejor a lo que me refiero… mira esta captura. En ella se ve Matrix y todos sus valores. Los números negativos indican que esa posición está ocupada por ese pieza. (-2, por la 2, -4, por la 4…).

Dibujando las piezas

Como comentaba en algún párrafo anterior, Eileen se basa en Matrix. A través del valor de sus elementos, el procedimiento void DrawMatrix(int Matrix[20][14]); dibuja el juego. En una instancia de Nessi_Sprite llamada piece, cargo todas las piezas, con el orden de piezas ya comentado. (la pieza uno la cargo primera, la pieza 2, segunda…). Hecho esto con dos bucles recorro cada elemento de la matriz. Si el elemento es mayor que cero, dibujo la pieza, seteando el frame activo a Matrix[j][k]-1. (El número de frames de un sprite comienza en cero).

/* Dibuja la matrix en pantalla */
void DrawMatrix(int Matrix[20][14])
{
int j,k;

int inicio_row=88;
int inicio_col=15;

Nessi_Sprite piece(9);
piece.AddFrame(”graphics/piece01.png”);
piece.AddFrame(”graphics/piece02.png”);
piece.AddFrame(”graphics/piece03.png”);
piece.AddFrame(”graphics/piece04.png”);
piece.AddFrame(”graphics/piece05.png”);
piece.AddFrame(”graphics/piece06.png”);
piece.AddFrame(”graphics/piece07.png”);
piece.AddFrame(”graphics/piece08.png”);
piece.AddFrame(”graphics/piece09.png”);

for(j=0;j<20;j++)
{
for (k=0;k<14;k++)
{
if (Matrix[j][k]>0)
{
piece.SetCurrentFrame(Matrix[j][k]-1);
piece.SetX(inicio_col+(25*k));
piece.SetY(inicio_row+(25*j));
piece.Draw();
}
}
}
}

Las dos variables int de inicio, es la referencia del width y del height en donde comienza el tablero de juego y a partir del cual basarse en el dibujado de piezas. A pesar de que cada pieza que ocupa varias celdas es un gráfico, a efectos de Matrix cuenta como si fueran varios A, pero en lugar de tomar el valor 1 para todos los cuadros, el primer cuadro de arriba a la izquierda vale el número de pieza y, el resto, el número de pieza en negativo. De ahí el if del segundo bucle. Si por ejemplo se encuentra un 4, con el setframe sabe que ha de dibujar la pieza 4 (la que tiene índice 3 en el array de frames) en la posición x e y calculada.

En la galería he subido varias dos caps del juego y una cap de todas las piezas, para que se entienda mejor la filosofía utilizada. Observando las piezas, es curioso que todas ellas, las disponibles en el juego, tiene el citado cuadrado arriba izquierdo dibujado. Esto es así por el motivo explicado antes: para el dibujado tengo que tener una referencia que me indique qué pieza es. Si el cuadro está vacío… tendría que indicar un cero en lugar del número de pieza, perdiendo esa referencia. Ya se me ocurren algunas cosillas como solución… que para no hacer un post kilométrico comentaré en otra ocasión. ;) :)

Por otro lado, comentar que para el cambio de sentido, como se verá en la cap de piezas, hago un cambio de sprite. Es decir, si tengo una pieza 6… y su pieza girada es, por ejemplo, la número 15… sólo hay que cambiar el id de la pieza actual ajustando el valor de los elementos de Matrix afectados.

Validaciones

Para que el juego de tetris sea tal, es necesario implementar un sistema de validaciones. Que la pieza no se salga del tablero por ninguno de los lados, que una pieza no ocupe una posición o alguna de las posiciones que esté ocupando otra pieza… con Matrix y los valores de sus elementos, se comprueba todo ello según la pieza actual. (básicamente, si hay cero, la celda está libre y se permite el movimiento).

Haciendo línea

Hay que tener en cuenta que cuando una línea esté totalmente llena, hay que borrar esa línea y bajar las superiores a ella. Esto lo logré en tres pasos…

  1. Convierto todas las celdas ocupadas a 1, menos las de la fila que tiene la linea.
  2. Seteo a cero las celdas de la fila que tiene línea.
  3. Muevo progresivamente las celdas hacia abajo. (Matrix[j+1][k]=Matrix[j][k]; ) Para que sea eficiente, una vez una fila es toda de ceros, dejo de mover hacia abajo.

Puntos, velocidad….

Ahora queda llevar el control de puntos, de la velocidad del juego según el nivel… y todos los detalles deseados. Quizá sea la parte menos complicada… así que tampoco entraré en detalles… ;) Todavía me quedan cosillas de esas y algún bug que otro por resolver y mejorar… así que tardaré algo má en publicar el .exe… :roll: