viernes, 27 de febrero de 2009

New Super Mario Bros

¡Cómo programar un juego tipo New Super Mario Bros (NSMB)!

Pues no es muy difícil, pero tampoco es fácil. Ante la cantidad de elementos que intervienen en el juego yo me inclinaría más por programar con orientación a objetos. Por lo que se necesita un poco más de nivel de programación del que hemos empleado hasta ahora.

Pero no nos desanimemos, utilizaremos la orientación a objetos de una forma sencilla y didáctica y, seguramente, tomaremos decisiones para que resulte lo más sencillo posible.

Otra cosa, en algún comentario se apunta a la posibilidad de hacer pantallas desde donde se puedan elegir opciones, y de esta forma, dejar un acabado más elegante a los juegos. El problema para mi estriba en que todo lleva tiempo y el poco de que dispongo me gusta más emplearlo en hacer el motor propio del juego. Aún así, intentaremos hacer cosas en ese sentido.

Empecemos con con la Orientación a objetos.

A la hora de ir a compilar un fichero .cpp (c++) PALib puede que de algún que otro error de compilación. En mi caso esta fue la pantalla del error:



La solución fue poner lo siguiente en el fichero PA_Sound.h:

línea 212: FS_wav[PA_Channel] = (u32*)PA_Malloc(FS_wav[PA_Channel], PA_FSFile[PAFS_wav_number].Length+4);

línea 430: FS_mod = (u32*)PA_Malloc(FS_mod, PA_FSFile[PAFS_mod_number].Length+4);

Antes de hacer este cambio mirar si os da error al compilar c++.

Y ahora ¿Qué es la orientación a objetos?

Pues es una forma de describir el mundo que estamos programando. Por ejemplo, en el primer tutorial de este blog presentamos un juego muy sencillo, un sprite moviéndose por un pueblecito. En este caso sólo era necesario las coordenadas de nuestro héroe y el mapa para las colisiones. Todo esto era lo único necesario para describir nuestro mundo.
En el Tetris las cosas se complicaban un poco más, teníamos un tablero y unas fichas que giraban. Para describir ese mundo tendríamos dos clases, la del tablero y la de ficha.

Ahora tenemos el NSMB, donde todo es mucho más complejo, no sólo en cantidad, si no en la forma de interactuar entre los distintos elementos del juego, los enemigos, las plataformas, las monedas, incluso nuestro héroe, Mario, puede cambiar de tamaño, salta, etc.

La orientación a objetos permite describir cada parte de nuestro mundo en un fichero aparte, teniendo todo muy bien organizado. Nos permitirá tener personajes con distinto comportamiento moviéndose por el juego. Incluso las propias plataformas serán una clase, desde donde se describirá su aspecto, así como su comportamiento.

Algunas de las clases que presentaremos podrían pasar perfectamente a un proyecto que se denominase PALib++, pero eso ya es harina de otro costal.

Podéis acceder a un primer proyecto para probar el compilador de c++. Esto el lo que se verá si ponéis en marcha el proyecto:



Este código contiene una clase llamada Sprite (en sprite.h está la definición de la clase y en Sprite.cpp el código) que es la responsable de cargar un sprite en la pantalla. Es mejorable, repite paletas y se puede simplificar, aún más, su utilización, pero lo dejaremos para el siguiente post.

Una de las mejoras que nos da el uso de clases es el poder simplificar el algoritmo propio del juego. Main.cpp queda más sencillo, y eso mejora la legibilidad del código. Por ejemplo, si quiero añadir un nuevo Sprite, únicamente serían dos líneas de código.

//Con clases
Sprite koopa(SCREEN_TOP, BACKGROUND_THREE, OBJ_SIZE_32X32, (void*)koopa_Pal, (void*)koopa_Sprite, 128, 140);
koopa.rotar_horizontal();
koopa.animar(0, 15, 15);

//Sin clases
PA_LoadSpritePal(SCREEN_TOP, KOOPA_PALLETE,(void*)koopa_Pal);
PA_CreateSprite(SCREEN_TOP, KOOPA_SPRITE, (void*)koopa_Sprite, OBJ_SIZE_32X32, COLOR_MODE_256, KOOPA_PALLETE, 128, 140);
PA_SetSpritePrio(SCREEN_TOP, KOOPA_SPRITE, BACKGROUND_THREE);
PA_SetSpriteHflip(SCREEN_TOP, KOOPA_SPRITE, 1);
PA_StartSpriteAnim(SCREEN_TOP, KOOPA_SPRITE, 0, 15, 15);


En las siguientes entregas iremos mejorando la clase Sprite. Estoy pensando en hacerla en Inglés para que se pueda distinguir bien del algoritmo del juego.

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: