Mostrando entradas con la etiqueta tetris. Mostrar todas las entradas
Mostrando entradas con la etiqueta tetris. Mostrar todas las entradas

viernes, 22 de enero de 2010

Tetris para la Wii

Como ya comentamos en una entrada anterior vamos a ver como pasar el Tetris de la DS a la Wii.

Lo primero de todo es bajar el Tetris para la DS, y ponernos a cambiar las llamadas a la librería PAlib por las de la Wii. Para la Wii hay varias librerías disponibles, en nuestro caso hemos usado las librerías libwiisprite, para la parte gráfica, fat, para le acceso al disco y wpad para manejar el wiimote.

Por ejemplo el #include se debe cambiar por #include , #include y #include .

Por ejemplo las llamadas de tipo PA_Init, PA_InitVBL() y PA_InitText por gwd.InitVideo, donde se ha definido gwd de tipo GameWindow, fatInitDefault y WPAD_Init();.

Otra cosa, las librería del Wii que he usado están en C++, por lo que hay que pasar de tetris.c a tetris.cpp. He utilizado la nomenclatura de objetos con . en vez de con ->. Esto es debido a que todos los ejemplo venían con . y como he hecho Copy/Paste pues así es como ha quedado.

Este es el código para cargar el fondo:

Image img_fondo;
 if (img_fondo.LoadImage("fondo.png") != IMG_LOAD_ERROR_NONE) exit(0);
  Sprite fondo;
  fondo.SetImage(&img_fondo);
  fondo.SetPosition(0, 0);

La explicación del código sería muy fácil: primero definimos la variable img_fondo de tipo Imagen y cargamos el fondo en este objeto, con la función LoadImage. Se crea un objeto de tipo Sprite y se le asocia la imagen. Finalmente lo colocamos en la posición 0, 0 y Ya tenemos el fondo.

Ahora toca cargar los tilset para las fichas y el tablero. Esto, si se ha seguido la programación del tetris de la DS, no sería necesario explicarlo mucho. Sólo comentaremos que un tileset es una zona gráfica que se puede rellenar con pequeñas imágenes para crear la imagen completa, a modo de ladrillos.

Para crear una zona de tilesets (el tablero del tetris y la fichas siguiente) usaremos
el objeto TiledLayer:

TiledLayer tablero_layer(TABLERO_ANCHO, TABLERO_ALTO, 8);
TiledLayer ficha_siguiente_layer(FICHA_ANCHO, FICHA_ALTO, 8);

Image tiles;
if (tiles.LoadImage("tiles16.png") != IMG_LOAD_ERROR_NONE) exit(0);
tablero_layer.SetStaticTileset(&tiles, 16, 16);
tablero_layer.SetPosition(160, 50);

ficha_siguiente_layer.SetStaticTileset(&tiles, 16, 16);
ficha_siguiente_layer.SetPosition(350, 50);

La explicación sería muy parecida a la de cargar el fondo. Se han creado dos objetos de tipo TiledLayer y a ambos se les asocia una imagen como StaticTileset. Esta será la que contendrá los ladrillos. Finalmente posicionamos los dos tilesets en la pantalla.

Ahora vamos a cargar todo en pantalla. Para esto se usa un LayerManager, al cual hay que asignarle los tres componentes que queremos que muestre:

LayerManager tablero_manager(3);
 tablero_manager.Append(&tablero_layer);
 tablero_manager.Append(&ficha_siguiente_layer);
 tablero_manager.Append(&fondo);
El orden es importante, el fondo debe ir al final.

En el próximo post veremos como utilizar el mando el wiimote como un mando clásico.

Saludos y feliz año.

P.D. Este es el primer y único desarrollo que he hecho para la Wii, así que no puedo decir si como se ha hecho es la mejor forma posible. Cargar un fondo en un objeto de tipo Sprite no parece muy correcto, pero es que no he encontrado otra forma de hacerlo. Me gustaría estudiar más a fondo la librería libogc pero, como siempre, la falta de tiempo...

viernes, 20 de marzo de 2009

Nueva versión del Tetris

Hacemos un paréntesis en la programación del NSMB y vamos a presentar la última versión del Tetris.

Se ha creado un menú en la pantalla táctil, por lo que usaremos el stylus por primera vez en este blog. La imagen es la siguiente:



Posibilita la selección de tres tipos de juegos.

PALib controla cuando se pulsa en la pantalla táctil y devuelve las coordenadas (x, y) donde se ha pulsado.

if (Stylus.Held) {
    tipo_juego = obtener_tipo_juego(Stylus.X, Stylus.Y);

El Stylus.Held indica que se está pulsando en la pantalla y las coordenadas quedan en Stylus.X y en Stylus.Y. Ahora para saber si se ha pulsado un botón u otro se ha creado esta función:


u8 obtener_tipo_juego(s32 xStylus, s32 yStylus) {
if (pulsado_boton_con_stylus(xStylus, yStylus, 60, 37, 205, 79)) {
return JUEGO_MARATON;
} else if (pulsado_boton_con_stylus(xStylus, yStylus, 74, 83, 224, 125)) {
return JUEGO_EXPERTO;
} else if (pulsado_boton_con_stylus(xStylus, yStylus, 61, 133, 226, 170)) {
return JUEGO_SUPER_BLOQUES;
} else {
return 0;
}
}

bool pulsado_boton_con_stylus(s32 xStylus, s32 yStylus, s32 x1, s32 y1, s32 x2, s32 y2) {
return (xStylus > x1 && xStylus <> y1 && yStylus < y2);
}


Esta última función es la que uso en todos los proyectos. Sirve para saber si un punto (x, y) esta dentro de un rectángulo (x1, y1) - (x2, y2).
Los rectángulos los sacamos de la imagen, y aunque los botones estén un poco torcidos más o menos he trazado unos rectángulos y son esas coordenadas las que he usado:


El modo superbloques muestra fichas de distintas formas, posibilidad que nos da el haber creado las fichas como matrices de 4x4. Y tanto este modo como el experto van incrementando las velocidad a medida que se consigue aumentar la puntuación.

Veamos la función actualizar_puntuacion:


if (tipo_juego == JUEGO_EXPERTO || tipo_juego == JUEGO_SUPER_BLOQUES) {
if (nivel != puntos / 100) {
nivel = puntos / 100; velocidad -= nivel;
}
}

Cada cien puntos aumentamos la velocidad (o disminuimos el tiempo de espera).

Os dejo todo el código en el lugar habitual para que echéis un vistazo. Como siempre nos vemos en los comentarios.

Saludos

viernes, 20 de febrero de 2009

La ficha siguiente

Para poder mostrar la siguiente ficha que va a salir tendremos que tener dos variables 'ficha' y 'ficha_siguiente'.

Modificaremos la función 'obtener_ficha' para que en vez de asignar a la variable 'ficha' la ficha buscada de forma aleatoria, se le asigne a la variable 'ficha_siguiente'. La función 'obtener_ficha' quedará de la siguiente manera:

u8 obtener_ficha() {
    u8 tipo = PA_RandMax(6); //0..6
    u8 color = PA_RandMinMax(2, 7); //2..7
    ficha.y = 0;
    ficha.x = TABLERO_ANCHO / 2;
    copiar_ficha_siguiente();
    if (tipo == 0) crear_ficha_t(color);
    else if (tipo == 1) crear_ficha_l(color);
    else if (tipo == 2) crear_ficha_o(color);
    else if (tipo == 3) crear_ficha_z(color);
    else if (tipo == 4) crear_ficha_s(color);
    else if (tipo == 5) crear_ficha_j(color);
    else crear_ficha_i(color);
    dibujar_ficha_siguiente();
    return existe_colision(ficha.x, ficha.y);
}

void copiar_ficha_siguiente() {
    u8 i, j;
    ficha.radio = ficha_siguiente.radio;
    for(i = 0; i < FICHA_ANCHO; i++) 
        for(j = 0; j < FICHA_ALTO; j++)
            ficha.ficha[i][j] = ficha_siguiente.ficha[i][j];
}


Con esto ya dejamos más o menos el juego terminado. En está página está disponible el código completo del proyecto.

Hay un tema en cuanto a la forma de programar.

En los dos juegos que hemos programado hasta la fecha, hemos utilizado una serie de variables globales y funciones que trabajan sobre ellas, siendo esto válido para juegos sencillos pero no tanto para juegos más complicados.

Conceptos como el ámbito de las variables han de ser considerados a la hora de realizar este tipo de programas. Por ejemplo, la función obtener_ficha, así como las de rotar deberían de trabajar sobre un parámetro ficha, y no sobre las variables globales. Esto dejaría el código más limpio y daría mayor flexibilidad si se quisieran introducir más funcionalidades.

Por ahora lo dejaremos así, pero si queremos hacer el New Super Mario Bros, deberemos empezar a hacer las cosas más limpias.

Saludos

viernes, 13 de febrero de 2009

Volcar las fichas al tablero

En este post vamos a dar forma completamente al motor del juego. Lo que nos queda es pasar las fichas al tablero cuando estas colisiones. Una vez hecho esto mirar si se ha hecho una línea y posteriormente quitarla.

Así que manos a la obra.

Para pasar las fichas al tablero necesitaremos esta función:
void copiar_ficha_a_tablero() {
    u8 i, j;

    for(i = 0; i < FICHA_ANCHO; i++) 
        for(j = 0; j < FICHA_ALTO; j++)
            if (ficha.ficha[i][j] != CASILLA_VACIA)
                tablero[ficha.x + i][ficha.y + j] = ficha.ficha[i][j];
}


Para saber si se ha hecho una o más líneas:
u8 es_linea_completa(u8 fila) {
    u8 i;

    for(i = 0; i < TABLERO_ANCHO; i++) 
        if (tablero[i][fila] == CASILLA_VACIA) return 0;
    return 1;
}


Para quitar esa línea que se hallado:
void quitar_linea(u8 fila) {
    u8 i, j;

    for(i = 0; i < TABLERO_ANCHO; i++) 
        for(j = fila; j > 0; j--)
            tablero[i][j] = tablero[i][j - 1];
}


Por último, actualizaremos la puntuación, dependiendo del número de lineas que se hagan de una sola vez:

void actualizar_puntuacion(u8 lineas) {
    if (lineas == 1) puntos += 10;
    else if (lineas == 2) puntos += 30;
    else if (lineas == 3) puntos += 60;
    else if (lineas == 4) puntos += 100;

    PA_OutputText(SCREEN_TOP, 25, 12, "%d   ", puntos);
}


El último código lo podeis descargar, junto a todo el proyecto, desde esta página.

En esta versión he puesto una pantalla de entrada, para ir decorando el juego. Se nota que no soy diseñador gráfico!!!

Saludos

viernes, 6 de febrero de 2009

Controlar las fichas

El siguiente paso es crear las fichas y que empiecen a caer por el tablero. Las funciones ya las presentamos en los primeros post de este tutorial así que aquí se presentan todas las de las fichas.

Hemos introducido el control el teclado, donde se puede ver la diferencia entre pulsar una tecla y tenerla pulsada.

Usaremos Pad.Newpress.Right/Left para controlar que pulsen, y suelten, la tecla de ir a la derecha. Esto es para controlar el movimiento horizontal de la ficha de un modo más preciso. Sin embargo, la tecla de bajar se quiere que sea algo rápido, por lo que usaremos Pad.Held.Down (estar pulsado).

Por último, merece la pena ver la función de existe_colision:

01.u8 existe_colision(int x, int y) {
02.    u8 i, j;

03.    for(i = 0; i < FICHA_ANCHO; i++) 
04.        for(j = 0; j < FICHA_ALTO; j++)
05.            if (ficha.ficha[i][j] != CASILLA_VACIA) {
06.                if (x + i < 0) return 1;
07.                if (x + i >= TABLERO_ANCHO) return 1;
08.                if (y + j >= TABLERO_ALTO) return 1;
09.                if (tablero[x + i][y + j] != CASILLA_VACIA) return 1;
10.            }
11.    return 0; //false
12}


Se utiliza el tipo 'int' porque x puede ser negativo.
En la línea 5 se puede ver que únicamente se mirará si alguna casilla de la ficha produce colisión si no está vacía.
Las líneas 6, 7 y 8 miran los bordes del tablero y por fin la línea 9 es la que mira que si en el tablero hay ocupada una casilla se produzca colición.

Otro tema es el de la variable ficha_timer y la constante VELOCIDAD_MINIMA. Se utilizan para que las fichas caigan a una velocidad controlada.

El código:

#include <PA9.h>

#include "gfx/all_gfx.h"
#include "gfx/all_gfx.c"

#include "devnintendods.h"

#define TABLERO_ANCHO 10
#define TABLERO_ALTO 21

#define FICHA_ANCHO 4
#define FICHA_ALTO 4

#define CASILLA_VACIA 1
#define CASILLA_ROJA 2
#define CASILLA_AZUL 3
#define CASILLA_VERDE 4
#define VELOCIDAD_MINIMA 25
#define BORDE_IZQUIERDO 1

//La fila 0 es la de más arriba y la 16 la de la base. La columna 0 es la de la izquierda.
u8 tablero[TABLERO_ANCHO][TABLERO_ALTO]; 

typedef struct {
    int x, y;
    u8 radio;
    u8 ficha[FICHA_ANCHO][FICHA_ALTO];
} tipo_ficha;

tipo_ficha ficha;

u8 ficha_timer = 0;

void inicializar_tablero();
void dibujar_tablero();
void dibujar_casilla(int x, int y, u8 ficha);

void dibujar_ficha();

u8 existe_colision(int x, int y);
u8 obtener_ficha();

void crear_ficha_t(u8 color);
void crear_ficha_l(u8 color);
void crear_ficha_o(u8 color);
void crear_ficha_z(u8 color);
void crear_ficha_i(u8 color);
void crear_ficha_s(u8 color);
void crear_ficha_j(u8 color);
void girar_ficha_derecha();
void girar_ficha_izquierda();

int main(int argc, char ** argv) {
    PA_Init();
    PA_InitVBL();
    PA_InitText(SCREEN_TOP, BACKGROUND_TWO);

    PA_DualLoadBgPal(BACKGROUND_THREE, (void *)tiles_Pal);
    PA_DualLoadSimpleBg(BACKGROUND_THREE, tiles_Tiles, Blank, BG_256X256, 0, 1);
    PA_EasyBgScrollXY(SCREEN_BOTTOM, BACKGROUND_THREE, 0, 0);

    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_ONE, fondo_top);
    PA_EasyBgLoad(SCREEN_BOTTOM, BACKGROUND_ONE, fondo_bottom);

    inicializar_tablero();
    dibujar_tablero();
    obtener_ficha();

    while  (1) {
        if (Pad.Newpress.Left) {
            if (!existe_colision(ficha.x - 1, ficha.y)) ficha.x--;
        } else if (Pad.Newpress.Right) {
            if (!existe_colision(ficha.x + 1, ficha.y)) ficha.x++;
        } else if (Pad.Held.Down) {
            if (!existe_colision(ficha.x, ficha.y + 1)) ficha.y++;
        } else if (Pad.Newpress.Y) {//girar izquierda
            girar_ficha_izquierda();
            if (existe_colision(ficha.x, ficha.y)) girar_ficha_derecha();
        } else if (Pad.Newpress.A) {//girar derecha
            girar_ficha_derecha();
            if (existe_colision(ficha.x, ficha.y)) girar_ficha_izquierda();
        } else {
            ficha_timer++;
            if (ficha_timer > VELOCIDAD_MINIMA) {
                ficha_timer = 0;
                if (!existe_colision(ficha.x, ficha.y + 1)) {
                    ficha.y++;    
                } else {
                    obtener_ficha();
                }
            }
        }
        dibujar_tablero();
        dibujar_ficha();
        PA_WaitForVBL();
    }
}

void inicializar_tablero() {
    u8 i, j;

    for(i = 0; i < TABLERO_ANCHO; i++) 
        for(j = 0; j < TABLERO_ALTO; j++) 
            tablero[i][j] = CASILLA_VACIA;
}

void dibujar_tablero() {
    u8 i, j;

    for(i = 0; i < TABLERO_ANCHO; i++) 
        for(j = 0; j < TABLERO_ALTO; j++)
            dibujar_casilla(i, j, tablero[i][j]);
}

void dibujar_casilla(int x, int y, u8 ficha) {
    u8 x1 = x * 2;
    u8 y1 = y;
    u8 screen = SCREEN_TOP;
    if (y1 >= 12) {
        screen = SCREEN_BOTTOM;
        y1 = y1 - 12;
    }
    y1 = y1 * 2;
    x1 += BORDE_IZQUIERDO;
    PA_SetMapTileEx(screen, BACKGROUND_THREE, x1, y1, ficha, 0, 0, 0);
    PA_SetMapTileEx(screen, BACKGROUND_THREE, x1, y1 + 1, ficha, 0, 1, 0);
    PA_SetMapTileEx(screen, BACKGROUND_THREE, x1 + 1, y1, ficha, 1, 0, 0);
    PA_SetMapTileEx(screen, BACKGROUND_THREE, x1 + 1, y1 + 1, ficha, 1, 1, 0);
}

void dibujar_ficha() {
    u8 i, j;

    for(i = 0; i < FICHA_ANCHO; i++) 
        for(j = 0; j < FICHA_ALTO; j++)
            if (ficha.ficha[i][j] != CASILLA_VACIA) dibujar_casilla(ficha.x + i, ficha.y + j, ficha.ficha[i][j]);
}

u8 existe_colision(int x, int y) {
    u8 i, j;

    for(i = 0; i < FICHA_ANCHO; i++) 
        for(j = 0; j < FICHA_ALTO; j++)
            if (ficha.ficha[i][j] != CASILLA_VACIA) {
                if (x + i < 0) return 1;
                if (x + i >= TABLERO_ANCHO) return 1;
                if (y + j >= TABLERO_ALTO) return 1;
                if (tablero[x + i][y + j] != CASILLA_VACIA) return 1;
            }
    return 0; //false
}

u8 obtener_ficha() {
    u8 tipo = PA_RandMax(6); //0..6
    u8 color = PA_RandMinMax(2, 7); //2..7
    ficha.y = 0;
    ficha.x = TABLERO_ANCHO / 2;
    if (tipo == 0) crear_ficha_t(color);
    else if (tipo == 1) crear_ficha_l(color);
    else if (tipo == 2) crear_ficha_o(color);
    else if (tipo == 3) crear_ficha_z(color);
    else if (tipo == 4) crear_ficha_s(color);
    else if (tipo == 5) crear_ficha_j(color);
    else crear_ficha_i(color);
    return existe_colision(ficha.x, ficha.y);
}

void crear_ficha_t(u8 color) {
    ficha.radio = 3;
    ficha.ficha[0][0] = color;
    ficha.ficha[1][0] = color;
    ficha.ficha[2][0] = color;
    ficha.ficha[3][0] = CASILLA_VACIA;
    ficha.ficha[0][1] = CASILLA_VACIA;
    ficha.ficha[1][1] = color;
    ficha.ficha[2][1] = CASILLA_VACIA;
    ficha.ficha[3][1] = CASILLA_VACIA;
    ficha.ficha[0][2] = CASILLA_VACIA;
    ficha.ficha[1][2] = CASILLA_VACIA;
    ficha.ficha[2][2] = CASILLA_VACIA;
    ficha.ficha[3][2] = CASILLA_VACIA;
    ficha.ficha[0][3] = CASILLA_VACIA;
    ficha.ficha[1][3] = CASILLA_VACIA;
    ficha.ficha[2][3] = CASILLA_VACIA;
    ficha.ficha[3][3] = CASILLA_VACIA;
}

void crear_ficha_l(u8 color) {
    ficha.radio = 3;
    ficha.ficha[0][0] = color;
    ficha.ficha[1][0] = CASILLA_VACIA;
    ficha.ficha[2][0] = CASILLA_VACIA;
    ficha.ficha[3][0] = CASILLA_VACIA;
    ficha.ficha[0][1] = color;
    ficha.ficha[1][1] = CASILLA_VACIA;
    ficha.ficha[2][1] = CASILLA_VACIA;
    ficha.ficha[3][1] = CASILLA_VACIA;
    ficha.ficha[0][2] = color;
    ficha.ficha[1][2] = color;
    ficha.ficha[2][2] = CASILLA_VACIA;
    ficha.ficha[3][2] = CASILLA_VACIA;
    ficha.ficha[0][3] = CASILLA_VACIA;
    ficha.ficha[1][3] = CASILLA_VACIA;
    ficha.ficha[2][3] = CASILLA_VACIA;
    ficha.ficha[3][3] = CASILLA_VACIA;
}

void crear_ficha_j(u8 color) {
    ficha.radio = 3;
    ficha.ficha[0][0] = CASILLA_VACIA;
    ficha.ficha[1][0] = color;
    ficha.ficha[2][0] = CASILLA_VACIA;
    ficha.ficha[3][0] = CASILLA_VACIA;
    ficha.ficha[0][1] = CASILLA_VACIA;
    ficha.ficha[1][1] = color;
    ficha.ficha[2][1] = CASILLA_VACIA;
    ficha.ficha[3][1] = CASILLA_VACIA;
    ficha.ficha[0][2] = color;
    ficha.ficha[1][2] = color;
    ficha.ficha[2][2] = CASILLA_VACIA;
    ficha.ficha[3][2] = CASILLA_VACIA;
    ficha.ficha[0][3] = CASILLA_VACIA;
    ficha.ficha[1][3] = CASILLA_VACIA;
    ficha.ficha[2][3] = CASILLA_VACIA;
    ficha.ficha[3][3] = CASILLA_VACIA;
}

void crear_ficha_o(u8 color) {
    ficha.radio = 2;
    ficha.ficha[0][0] = color;
    ficha.ficha[1][0] = color;
    ficha.ficha[2][0] = CASILLA_VACIA;
    ficha.ficha[3][0] = CASILLA_VACIA;
    ficha.ficha[0][1] = color;
    ficha.ficha[1][1] = color;
    ficha.ficha[2][1] = CASILLA_VACIA;
    ficha.ficha[3][1] = CASILLA_VACIA;
    ficha.ficha[0][2] = CASILLA_VACIA;
    ficha.ficha[1][2] = CASILLA_VACIA;
    ficha.ficha[2][2] = CASILLA_VACIA;
    ficha.ficha[3][2] = CASILLA_VACIA;
    ficha.ficha[0][3] = CASILLA_VACIA;
    ficha.ficha[1][3] = CASILLA_VACIA;
    ficha.ficha[2][3] = CASILLA_VACIA;
    ficha.ficha[3][3] = CASILLA_VACIA;
}

void crear_ficha_z(u8 color) {
    ficha.radio = 3;
    ficha.ficha[0][0] = color;
    ficha.ficha[1][0] = color;
    ficha.ficha[2][0] = CASILLA_VACIA;
    ficha.ficha[3][0] = CASILLA_VACIA;
    ficha.ficha[0][1] = CASILLA_VACIA;
    ficha.ficha[1][1] = color;
    ficha.ficha[2][1] = color;
    ficha.ficha[3][1] = CASILLA_VACIA;
    ficha.ficha[0][2] = CASILLA_VACIA;
    ficha.ficha[1][2] = CASILLA_VACIA;
    ficha.ficha[2][2] = CASILLA_VACIA;
    ficha.ficha[3][2] = CASILLA_VACIA;
    ficha.ficha[0][3] = CASILLA_VACIA;
    ficha.ficha[1][3] = CASILLA_VACIA;
    ficha.ficha[2][3] = CASILLA_VACIA;
    ficha.ficha[3][3] = CASILLA_VACIA;
}

void crear_ficha_s(u8 color) {
    ficha.radio = 3;
    ficha.ficha[0][0] = CASILLA_VACIA;
    ficha.ficha[1][0] = color;
    ficha.ficha[2][0] = color;
    ficha.ficha[3][0] = CASILLA_VACIA;
    ficha.ficha[0][1] = color;
    ficha.ficha[1][1] = color;
    ficha.ficha[2][1] = CASILLA_VACIA;
    ficha.ficha[3][1] = CASILLA_VACIA;
    ficha.ficha[0][2] = CASILLA_VACIA;
    ficha.ficha[1][2] = CASILLA_VACIA;
    ficha.ficha[2][2] = CASILLA_VACIA;
    ficha.ficha[3][2] = CASILLA_VACIA;
    ficha.ficha[0][3] = CASILLA_VACIA;
    ficha.ficha[1][3] = CASILLA_VACIA;
    ficha.ficha[2][3] = CASILLA_VACIA;
    ficha.ficha[3][3] = CASILLA_VACIA;
}

void crear_ficha_i(u8 color) {
    ficha.radio = 4;
    ficha.ficha[0][0] = color;
    ficha.ficha[1][0] = CASILLA_VACIA;
    ficha.ficha[2][0] = CASILLA_VACIA;
    ficha.ficha[3][0] = CASILLA_VACIA;
    ficha.ficha[0][1] = color;
    ficha.ficha[1][1] = CASILLA_VACIA;
    ficha.ficha[2][1] = CASILLA_VACIA;
    ficha.ficha[3][1] = CASILLA_VACIA;
    ficha.ficha[0][2] = color;
    ficha.ficha[1][2] = CASILLA_VACIA;
    ficha.ficha[2][2] = CASILLA_VACIA;
    ficha.ficha[3][2] = CASILLA_VACIA;
    ficha.ficha[0][3] = color;
    ficha.ficha[1][3] = CASILLA_VACIA;
    ficha.ficha[2][3] = CASILLA_VACIA;
    ficha.ficha[3][3] = CASILLA_VACIA;
}

void girar_ficha_derecha() {
    u8 nueva_ficha[FICHA_ANCHO][FICHA_ALTO];
    u8 i, j;

    for(i = 0; i < FICHA_ANCHO; i++) 
        for(j = 0; j < FICHA_ALTO; j++)
            nueva_ficha[i][j] = ficha.ficha[i][j];
    if (ficha.radio == 3) {
        ficha.ficha[0][0] = nueva_ficha[0][2];
        ficha.ficha[1][0] = nueva_ficha[0][1];
        ficha.ficha[2][0] = nueva_ficha[0][0];
        ficha.ficha[0][1] = nueva_ficha[1][2];
        //ficha.ficha[1][1] = nueva_ficha[1][1];
        ficha.ficha[2][1] = nueva_ficha[1][0];
        ficha.ficha[0][2] = nueva_ficha[2][2];
        ficha.ficha[1][2] = nueva_ficha[2][1];
        ficha.ficha[2][2] = nueva_ficha[2][0];
    } else if (ficha.radio == 4) {
        ficha.ficha[0][0] = nueva_ficha[0][3];
        ficha.ficha[1][0] = nueva_ficha[0][2];
        ficha.ficha[2][0] = nueva_ficha[0][1];
        ficha.ficha[3][0] = nueva_ficha[0][0];
        ficha.ficha[0][1] = nueva_ficha[1][3];
        ficha.ficha[1][1] = nueva_ficha[1][2];
        ficha.ficha[2][1] = nueva_ficha[1][1];
        ficha.ficha[3][1] = nueva_ficha[1][0];
        ficha.ficha[0][2] = nueva_ficha[2][3];
        ficha.ficha[1][2] = nueva_ficha[2][2];
        ficha.ficha[2][2] = nueva_ficha[2][1];
        ficha.ficha[3][2] = nueva_ficha[2][0];
        ficha.ficha[0][3] = nueva_ficha[3][3];
        ficha.ficha[1][3] = nueva_ficha[3][2];
        ficha.ficha[2][3] = nueva_ficha[3][1];
        ficha.ficha[3][3] = nueva_ficha[3][0];
    }
}

void girar_ficha_izquierda() {
    u8 nueva_ficha[FICHA_ANCHO][FICHA_ALTO];
    u8 i, j;

    for(i = 0; i < FICHA_ANCHO; i++) 
        for(j = 0; j < FICHA_ALTO; j++)
            nueva_ficha[i][j] = ficha.ficha[i][j];
    if (ficha.radio == 3) {
        ficha.ficha[0][0] = nueva_ficha[2][0];
        ficha.ficha[1][0] = nueva_ficha[2][1];
        ficha.ficha[2][0] = nueva_ficha[2][2];
        ficha.ficha[0][1] = nueva_ficha[1][0];
        //ficha.ficha[1][1] = nueva_ficha[1][1];
        ficha.ficha[2][1] = nueva_ficha[1][2];
        ficha.ficha[0][2] = nueva_ficha[0][0];
        ficha.ficha[1][2] = nueva_ficha[0][1];
        ficha.ficha[2][2] = nueva_ficha[0][2];
    } else if (ficha.radio == 4) {
        ficha.ficha[0][0] = nueva_ficha[3][0];
        ficha.ficha[1][0] = nueva_ficha[3][1];
        ficha.ficha[2][0] = nueva_ficha[3][2];
        ficha.ficha[3][0] = nueva_ficha[3][3];
        ficha.ficha[0][1] = nueva_ficha[2][0];
        ficha.ficha[1][1] = nueva_ficha[2][1];
        ficha.ficha[2][1] = nueva_ficha[2][2];
        ficha.ficha[3][1] = nueva_ficha[2][3];
        ficha.ficha[0][2] = nueva_ficha[2][0];
        ficha.ficha[1][2] = nueva_ficha[1][1];
        ficha.ficha[2][2] = nueva_ficha[1][2];
        ficha.ficha[3][2] = nueva_ficha[1][3];
        ficha.ficha[0][3] = nueva_ficha[0][0];
        ficha.ficha[1][3] = nueva_ficha[0][1];
        ficha.ficha[2][3] = nueva_ficha[0][2];
        ficha.ficha[3][3] = nueva_ficha[0][3];
    }
}


En el próximo post hablaremos de como manejar el tablero.

Saludos

P.D.: Supongo que os estáis dando cuenta que el código se está haciendo muy grande. En el próximo post publicaremos un zip con todo el proyecto.

Enlaces patrocinados:

miércoles, 28 de enero de 2009

Los tiles y cómo dibujar el Tablero

Por fin vamos a ver una nueva técnica que nos ofrece las librerías PALib.

A la hora de programar juegos de tablero, o a la hora de dibujar nuestros mundos, no es necesario cargar unos fondos enormes. PALib ofrece la posibilidad de poder dibujar tiles.

Los tiles son pequeñas casillas que se pueden dibujar en la pantalla tantas veces como se quiera. Son casillas de 8x8 pixeles, por lo que una pantalla de la DS que tiene 256x192 tendrá 32x24 tiles.

La siguiente imágen nos carga los tiles que vamos a usar en esta primera versión del Tetris:



Si os fijáis bien, se han creado una serie de cuadraditos de 8x8 pixeles que serán las posibles casillas a dibujar. Cómo las fichas del Tetris tienen distintos colores pues hemos hecho casillas de diferentes colores.

Ahora veamos el código poco a poco para ver cómo funciona todo:

En primer lugar:

Cargamos la imagen de los tiles en las dos pantallas mediante estas dos funciones de la API de PALib.
PA_DualLoadBgPal(BACKGROUND_TWO, (void *)tiles_Pal);
PA_DualLoadSimpleBg(BACKGROUND_TWO, tiles_Tiles, Blank, BG_256X256, 0, 1);

La primera carga la paleta de la imagen, y la segunda carga, en las dos pantallas, la imagen como si fueran tiles y lo muestra en pantalla.
¿Pero que muestra? Un array de ceros, Blank, de 32x24 en cada una de las pantallas. Como la primera casilla de la imagen (la casilla cero) de tiles es negra, mostrará una pantalla negra.

Para dibujar por la pantalla los distintos tiles se puede utilizar la función PALib:
PA_SetMapTileEx(screen, BACKGROUND_TWO, x1, y1, ficha, 0, 0, 0);
donde x1,y1 serán las coordenadas del array de 32x24 en que está dividida cada pantalla de la DS. Los tres últimos parámetros ya los veremos más adelante.

Para poder ver todo lo que se ha dicho aquí, probar el siguiente código quitando las dos líneas que cargan los fondos: PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_ONE, fondo_top); y PA_EasyBgLoad(SCREEN_BOTTOM, BACKGROUND_ONE, fondo_bottom);.

Dibujar_casilla:

Esta función dibuja cada casilla del tablero y cada bloque de una ficha del tetris. Como 8x8 pixeles es muy pequeño, vamos a dibujar casillas de 16x16, es decir cuatro veces la casilla de 8x8.
PA_SetMapTileEx(screen, BACKGROUND_THREE, x1, y1, ficha, 0, 0, 0);
PA_SetMapTileEx(screen, BACKGROUND_THREE, x1, y1 + 1, ficha, 0, 1, 0);
PA_SetMapTileEx(screen, BACKGROUND_THREE, x1 + 1, y1, ficha, 1, 0, 0);
PA_SetMapTileEx(screen, BACKGROUND_THREE, x1 + 1, y1 + 1, ficha, 1, 1, 0);

Los parámetros son:
PA_SetMapTileEx(screen, background, x, y, tile, girar_horizontal, girar_vertical, paleta);

Debido a un pequeño problema que ocurre en la pantalla inferior (la posición 0,0 se baja unos 16 pixeles), hay que poner el scroll de esta pantalla a 0 (!?!?!?!?!?) :

PA_EasyBgScrollXY(SCREEN_BOTTOM, BACKGROUND_THREE, 0, 0);

Por último dibujaremos la pantalla de la aplicación. Por ahora he usado estos dos fondos (fondo_top y fondo_bottom)






El código es el siguiente:
#include <PA9.h>

#include "gfx/all_gfx.h"
#include "gfx/all_gfx.c"

#include "devnintendods.h"

#define TABLERO_ANCHO 10
#define TABLERO_ALTO 21

#define FICHA_ANCHO 4
#define FICHA_ALTO 4

#define CASILLA_VACIA 1
#define CASILLA_ROJA 2
#define CASILLA_AZUL 3
#define CASILLA_VERDE 4
#define VELOCIDAD_MINIMA 25
#define BORDE_IZQUIERDO 1

//La fila 0 es la de más arriba y la 16 la de la base. La columna 0 es la de la izquierda.
u8 tablero[TABLERO_ANCHO][TABLERO_ALTO]; 

typedef struct {
    int x, y;
    u8 radio;
    u8 ficha[FICHA_ANCHO][FICHA_ALTO];
} tipo_ficha;

tipo_ficha ficha;

void inicializar_tablero();
void dibujar_tablero();
void dibujar_casilla(int x, int y, u8 ficha);

int main(int argc, char ** argv) {
    PA_Init();
    PA_InitVBL();
    PA_InitText(SCREEN_TOP, BACKGROUND_TWO);

    PA_DualLoadBgPal(BACKGROUND_THREE, (void *)tiles_Pal);
    PA_DualLoadSimpleBg(BACKGROUND_THREE, tiles_Tiles, Blank, BG_256X256, 0, 1);
    PA_EasyBgScrollXY(SCREEN_BOTTOM, BACKGROUND_THREE, 0, 0);

    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_ONE, fondo_top);
    PA_EasyBgLoad(SCREEN_BOTTOM, BACKGROUND_ONE, fondo_bottom);

    inicializar_tablero();
    dibujar_tablero();

    while  (1) {
        PA_WaitForVBL();
    }
}

void inicializar_tablero() {
    u8 i, j;

    for(i = 0; i < TABLERO_ANCHO; i++) 
        for(j = 0; j < TABLERO_ALTO; j++) 
            tablero[i][j] = CASILLA_VACIA;
}

void dibujar_tablero() {
    u8 i, j;

    for(i = 0; i < TABLERO_ANCHO; i++) 
        for(j = 0; j < TABLERO_ALTO; j++)
            dibujar_casilla(i, j, tablero[i][j]);
}

void dibujar_casilla(int x, int y, u8 ficha) {
    u8 x1 = x * 2;
    u8 y1 = y;
    u8 screen = SCREEN_TOP;
    if (y1 >= 12) {
        screen = SCREEN_BOTTOM;
        y1 = y1 - 12;
    }
    y1 = y1 * 2;
    x1 += BORDE_IZQUIERDO;
    PA_SetMapTileEx(screen, BACKGROUND_THREE, x1, y1, ficha, 0, 0, 0);
    PA_SetMapTileEx(screen, BACKGROUND_THREE, x1, y1 + 1, ficha, 0, 1, 0);
    PA_SetMapTileEx(screen, BACKGROUND_THREE, x1 + 1, y1, ficha, 1, 0, 0);
    PA_SetMapTileEx(screen, BACKGROUND_THREE, x1 + 1, y1 + 1, ficha, 1, 1, 0);
}


La programación con 'tiles' nos permitirá crear el juego de plataformas. Si nos da tiempo volveremos sobre el juego de Pokemon para poder hacer mundos más grandes, sin ocupar demasiado espacio en memoria.

Saludos

viernes, 23 de enero de 2009

Empezamos a crear el Tetris

Con los post anteriores veíamos cosas de la programación pero nada tangible. Así que en este post empezaremos a crear el juego.

Lo primero el fondo del juego. Por ahora no vamos a hacer nada de puntuación y ficha siguiente, así que por ahora el fondo serán estas dos imágenes:


fondo_top y

fondo_bottom

Las imágenes deben pasar por el PAGX, como se indica aquí.

PALib nos da estas dos funciones para cargar dichos fondos:
PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_ONE, fondo_top);
PA_EasyBgLoad(SCREEN_BOTTOM, BACKGROUND_ONE, fondo_bottom);

El código sería:
#include <PA9.h>

#include "gfx/all_gfx.h"
#include "gfx/all_gfx.c"

#include "devnintendods.h"

#define TABLERO_ANCHO 10
#define TABLERO_ALTO 20

#define FICHA_ANCHO 4
#define FICHA_ALTO 4

u8 tablero[TABLERO_ANCHO][TABLERO_ALTO]; 

typedef struct {
    u8 x, y, radio;
    u8 ficha[FICHA_ANCHO][FICHA_ALTO];
} tipo_ficha;

tipo_ficha ficha;

u8 ficha_timer = 0;

int main(int argc, char ** argv) {
    PA_Init();
    PA_InitVBL();

    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_ONE, fondo_top);
    PA_EasyBgLoad(SCREEN_BOTTOM, BACKGROUND_ONE, fondo_bottom);

    while  (1) {
        PA_WaitForVBL();
    }
    return 0;
}


Para los que no han seguido el tutorial desde el principio devnintendods.h lo podeís encontrar aquí, y se debe guardar en la carpeta source.

Bueno, esto va tomando forma. En el próximo post veremos como programar el pintado del tablero en pantalla.

Saludos

domingo, 18 de enero de 2009

Más teoría sobre el Tetris

Seguimos con la programación del Tetris para la nintendo DS.

Tenemos que continuar programando funciones sobre los arrays que definimos en la anterior entrada del blog. Por ejemplo:

void crear_ficha_l(u8 color) {
ficha.radio = 3;
ficha.ficha[0][0] = color;
ficha.ficha[1][0] = CASILLA_VACIA;
ficha.ficha[2][0] = CASILLA_VACIA;
ficha.ficha[3][0] = CASILLA_VACIA;
ficha.ficha[0][1] = color;
ficha.ficha[1][1] = CASILLA_VACIA;
ficha.ficha[2][1] = CASILLA_VACIA;
ficha.ficha[3][1] = CASILLA_VACIA;
ficha.ficha[0][2] = color;
ficha.ficha[1][2] = color;
ficha.ficha[2][2] = CASILLA_VACIA;
ficha.ficha[3][2] = CASILLA_VACIA;
ficha.ficha[0][3] = CASILLA_VACIA;
ficha.ficha[1][3] = CASILLA_VACIA;
ficha.ficha[2][3] = CASILLA_VACIA;
ficha.ficha[3][3] = CASILLA_VACIA;
}
y así todas las fichas!!

Pensando en la forma como hemos solucionado el tema de las fichas, se podría hacer de otra manera.
Esta se basaría en que todas las fichas tienen 4 piezas, con lo que con un array de cuatro elementos sería suficiente, y cada elemento del array guardaría la posición (x, y) donde va esa pieza.

Por ejemplo:
  1. La pieza L sería (0, 0), (0, 1), (0, 2), (1, 2)
  2. La pieza J sería (1, 0), (1, 1), (0, 2), (1, 2)
  3. La pieza o (el cuadrado) sería (0, 0), (0, 1), (1, 0), (1, 1)
A la hora de rotar se podrían guardar las distintas posiciones o rotar sobre la casilla 1,1. Tendríamos que guardar en otra variable el color de las piezas, pero me parece un poco rollo.

Más funciones, la de colisión:
u8 existe_colision(u8 x, u8 y) {
u8 i, j;

for(i = 0; i < j =" 0;">= TABLERO_ANCHO) return 1;
if (y + j >= TABLERO_ALTO) return 1;
if (tablero[x + i][y + j] != CASILLA_VACIA) return 1;
}
return 0; //false
}

Esta la vamos a explicar con más detenimiento.

La función mira si la ficha 'ficha' en las posiciones 'x' e 'y', pasadas como parámetro, está sobre un obstáculo. Se mira que no se salga por los bordes izquierdo o derecho ( if (x + i <>= TABLERO_ANCHO) return 1;), que no llegue al fondo ( if (y + j >= TABLERO_ALTO) return 1;) o que no encuentre alguna casilla del tablero no vacía (if (tablero[x + i][y + j] != CASILLA_VACIA) return 1;), por supuesto todo esto se hará si la casilla de la ficha (i, j) no está vacía ( if (ficha.ficha[i][j] != CASILLA_VACIA) {)

Saludos

martes, 13 de enero de 2009

Tetris para la Nintendo DS

Buenas

Después del primer tutorial de este blog, un juego de Pokemon vamos a programar otro juego más conocido, el Tetris.

Seguiremos utilizando las librerías PALib, aunque vamos a utilizar otros conceptos distintos a los hasta ahora vistos, que luego nos servirán para crear más juegos. Si el primer programa se utilizaron los Sprites a tope, en este vamos a utilizar los Tiles.

Entre los comentarios que aparecen en el blog, los cuales agradezco muchísimo, había uno donde se me animaba a hacer un Super Mario Bros. Lo haremos, primero el Tetris, y con los conceptos que vamos a introducir con él, podremos construir este juego de plataformas. He encontrado un tutorial donde explican un juego de plataformas, pero creo que podemos hacerlo algo más profesional.

Un par de comentarios me indicaban que el tutorial de Pokemon ha quedado bien como tutorial y algo cojo como juego. Pues bien, estoy pensando una serie de modificaciones para hacerlo, también, más 'profesional'.

Por mi parte, intentaré ser más constante con las publicaciones. Me gusta tener el juego más o menos acabado antes de empezar con los tutoriales, y eso me lleva tiempo.

Pero cada cosa a su tiempo, primero el Tetris, que vereís es sencillísimo.

Como siempre, lo primero es copiar el template de PALib (C:\devkitPro\PAlibTemplate) a la ubicación que deseemos, en mi caso C:\devDS\TetrisDS, y ya podemos empezar.

En primer lugar las estructuras que vamos a usar.

El tetris es un juego de tablero, por lo que haremos un array con las casillas del tablero, es decir una matriz de 10x20, 10 columnas por 20 filas.

#define TABLERO_ANCHO 10
#define TABLERO_ALTO 20

u8 tablero[TABLERO_ANCHO][TABLERO_ALTO];

Y las fichas del tetris son fichas de 4x4 casillas. La verdad es que todas las fichas son de 3x3 menos dos: el cuadrado que es de 2x2 y la ficha larga que es de 4x1 (o 1x4).
En resumen, todas entrarán en una estructura de 4x4, pero a la hora de hacerlas girar, cada una deberá hacerlo dentro de su tamaño, es decir, necesitaremos un campo que denominaremos radio.

#define FICHA_ANCHO 4
#define FICHA_ALTO 4

typedef struct {
  u8 x, y, radio;
  u8 ficha[FICHA_ANCHO][FICHA_ALTO];
} tipo_ficha;

tipo_ficha ficha;

Cómo podéis observar, también he metido las coordenadas del tablero por donde andará la ficha (x, y).

Bueno, por ahora no hemos visto mucho de las PALib. Esto es por que el Tetris tiene mucho trabajo de arrays, y hasta que acabemos con todas estas funciones no podremos empezar ha programar el resto del juego.

Por ahora vamos a presentar alguna función más, por ejemplo la de crear algún tipo de ficha:

#define CASILLA_VACIA 1

void crear_ficha_z(u8 color) {
  ficha.radio = 3;
  ficha.ficha[0][0] = color;
  ficha.ficha[1][0] = color;
  ficha.ficha[2][0] = CASILLA_VACIA;
  ficha.ficha[3][0] = CASILLA_VACIA;
  ficha.ficha[0][1] = CASILLA_VACIA;
  ficha.ficha[1][1] = color;
  ficha.ficha[2][1] = color
  ficha.ficha[3][1] = CASILLA_VACIA;
  ficha.ficha[0][2] = CASILLA_VACIA;
  ficha.ficha[1][2] = CASILLA_VACIA;
  ficha.ficha[2][2] = CASILLA_VACIA;
  ficha.ficha[3][2] = CASILLA_VACIA;
  ficha.ficha[0][3] = CASILLA_VACIA;
  ficha.ficha[1][3] = CASILLA_VACIA;
  ficha.ficha[2][3] = CASILLA_VACIA;
  ficha.ficha[3][3] = CASILLA_VACIA;
}

Seguiremos. El juego es muy sencillo, pero tiene un montón de funciones de manejo de arrays.

Saludos