Introducción
Hace unos años decidí comprar en el mercado de segunda mano la Mega Drive, consola que disfruté de pequeño y que por desgracia dejé de tener en época de los 3D, cuando a casi nadie le importaban ya los 16 bits. Con ella, adquirí un clon chino del Everdrive con la intención de poder recuperar mi colección y jugar a todos los juegos del catalogo.
Para quien no lo conozca y en resumen, es un cartucho que consiste de dos partes, la tarjeta SD donde se almacenan los juegos (en concreto ROMS en formato .bin) y el cartucho en si, con su memoria ROM donde se vuelca el juego elegido a través de la pequeña interface que aparece al encender la consola. Este cartucho te permite jugar a cualquier juego de la plataforma y permite revivir la experiencia al 100% del juego original.
Con todo esto y debido a mi necesidad de aprender cómo funcionan las cosas, se me ocurrió realizara un pequeño juego para la Megadrive, un port del juego Tetris de la NES.
Antes de empezar
SGDK es un kit de desarrollo gratuito y abierto para Sega Megadrive, el cual a partir de código en C compila el videojuego en una imagen ROM. Para desarrollar con SGDK, se pueden utilizar varios entornos de desarrollo, pero por experiencia personal recomiendo Code::Blocks, ya que por la cantidad de información disponible en la red, es el más indicado para este menester.
Para agilizar el proceso de desarrollo y debug, recomiendo el uso de algún emulador, ya que con la creación de un script (ver start.bat del código fuente), podemos ejecutar fácilmente el juego tras cada compilación.
Encajando piezas
El juego de Tetris tiene un conjunto de siete piezas formadas por cuadrados (tetrominós), donde cada una de las piezas tiene 4 posibles rotaciones.
Para implementar el sistema de piezas utilizo un array de una dimensión, en el que el valor cero indica un espacio vacío, y un valor mayor que cero indica el ‘tile’ que se mostrará en pantalla.
const u8 piece[64] = {
0,0,0,0,
0,0,0,0,
2,2,2,0,
0,0,2,0,
0,0,0,0,
0,2,0,0,
0,2,0,0,
2,2,0,0,
0,0,0,0,
2,0,0,0,
2,2,2,0,
0,0,0,0,
0,0,0,0,
0,2,2,0,
0,2,0,0,
0,2,0,0
};
En el Array 1D del código anterior se almacenan los datos de la pieza «L». La longitud del mismo es de 64 (4 columnas x 4 filas x 4 rotaciones). Para acceder a cada una de las celdas en un espacio de 2 dimensiones, la estrategia a seguir es la siguiente:
rot; // Rotación de la pieza (0-4)
...
int x, y, v, off = rot * 16;
for(x = 0; x < 4; ++x)
for(y = 0; y < y; ++y)
{
v = piece[x * 4 + y + off];
...
}
Donde la variable ‘rot‘ con valor de 0 a 4, indica la rotación de la pieza y se usa para calcular el desplazamiento dentro del array en saltos 16 espacios (4 x 4). Al iterar las 4 filas y columnas obtenemos el valor ‘v‘, recogido de la posición obtenida por la operación ‘x * 4 + y + off‘, en el que podremos saber si se debe dibujar un cuadrado y de que color (en este caso el tile) será.
Tablero y colisiones
Una vez más el tablero esta formado por un Array de una dimensión de longitud 200 (10 filas y 20 columnas), en el que se irán almacenando las piezas tras su inserción.
Para la detección de colisiones de la pieza actual realizo la siguientes estrategia:
// Movimiento horizontal
if (left pulsado && !get_collision(x - 1, y)) // si se mueve a la izquierda
x--;
if (right pulsado && !get_collision(x + 1, y)) // si se mueve a la derecha
x++;
// Movimiento vertical
if(tiempo_gravedad || down pulsado) { // si desciende una posición
tiempo_gravedad = 0;
if(get_collision(x, y + 1)) {
y++;
} else { // la pieza ya no puede avanzar y se inserta
inserta_pieza();
crea_nueva_pieza();
}
}
El método para la detección de colisiones itera los elementos de la pieza teniendo en cuenta la posición y rotación de la misma y los compara con el contenido del tablero, como se muestra a continuación:
int get_collision(int posx, int posy, int rot) {
const u8* piece = pieces[current_piece];
int local_x, local_y;
int off = piece_off * rot;
int i;
for(i = off; i < off + piece_off; ++i) {
if(piece[i]) {
local_y = (i - off) / piece_size + posy;
if(local_y > 19) return 1; // bottom collision
local_x = (i % piece_size) + posx;
if(local_x < 0) return 2; // left collision
if(local_x > 9) return 3; // right collision
if(board[local_y * 10 + local_x]) { // board collision
return 4;
}
}
}
return 0;
}
Conclusiones
Sin entrar en muchos detalles se ha explicado las claves de la implementación del Tetris que realizado para la Megadrive usando el kit de desarrollo SGDK. Como la naturaleza de este post es de carácter orientativo, se recomienda la visualización y manipulación del código fuente que dejo a continuación. Un saludo y larga vida a Sega.
Código fuente
A continuación dejo el enlace de github para quien desee trastear con el código fuente: