22. Un doble buffer para evitar parpadeos. Octavo juego (aproximación "e"): Marciano 6. (*)

Habíamos comentado en el tema 9 cómo podíamos hacer un "doble buffer", para dibujar en memoria, en vez de hacer directamente en pantalla, y volcar el resultado en el momento final, evitando parpadeos:

 
   BITMAP *bmp = create_bitmap(320, 200);    // Creamos el bitmap auxiliar en memoria 
   clear_bitmap(bmp);                        // Lo borramos 
   putpixel(bmp, x, y, color);               // Dibujamos en él 
   // ...
   blit(bmp, screen, 0, 0, 0, 0, 320, 200);  // Finalmente,volcamos a pantalla 
 

Nuestro "bitmap auxiliar" se lamará "pantallaOculta". Los cambios en el fuente son muy pocos:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*    ipj22c.c                  */
/*                              */
/*  Ejemplo:                    */
/*    Varios marcianos y una    */
/*    nave movindose de        */
/*    forma independiente; la   */
/*    nave puede disparar y     */
/*    "matar" a los marcianos   */
/*    Eliminados parpadeos      */
/*                              */
/*  Comprobado con:             */
/*  - Djgpp 2.03 (gcc 3.2)      */
/*    y Allegro 4.02 - MsDos    */
/*  - MinGW 2.0.0-3 (gcc 3.2)   */
/*    y Allegro 4.02 - WinXP    */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*  - Gcc 3.2.2 + All. 4.03     */
/*    en Mandrake Linux 9.1     */
/*------------------------------*/
 
 
#include <allegro.h>
 
/* -------------- Constantes globales ------------- */
#define ANCHOPANTALLA 320
#define ALTOPANTALLA 200
 
// Los dos siguientes valores son los retardos
// hasta mover la nave y el marciano, pero ambos
// en centsimas de segundo
#define RETARDOCN 8
#define RETARDOCM 30
// Y tambin el del disparo
#define RETARDOCD 12
 
#define MARGENSUP   20
#define MARGENDCHO  25
#define MARGENIZQDO 25
#define MARGENINF   30
#define INCREMX      3
#define INCREMY      7
 
#define FILASMARCIANOS 5
#define COLUMNASMARCIANOS 11
#define DISTANCIAXMARCIANOS 20
#define DISTANCIAYMARCIANOS 12
 
// Datos de cada marciano
typedef struct {
    int posX;
    int posY;
    int activo;
} marciano;
 
/* -------------- Variables globales -------------- */
PALETTE pal;
BITMAP *imagen;
BITMAP *nave;
BITMAP *marciano1;
BITMAP *marciano2;
BITMAP *disparo;
BITMAP *pantallaOculta;
 
// El array que controla a todos los marcianos
marciano marcianos[COLUMNASMARCIANOS][FILASMARCIANOS];
 
int fotograma = 1;
 
int xNave = ANCHOPANTALLA / 2;
int yNave = ALTOPANTALLA-16-MARGENINF;
int xDisparo = 0;
int yDisparo = 0;
 
int numMarcianos = FILASMARCIANOS*COLUMNASMARCIANOS;
 
int desplMarciano = INCREMX;
int desplNave = INCREMX;
 
int puedeMoverMarciano = 1;
int puedeMoverNave = 1;
int puedeMoverDisparo = 0;
int disparoActivo = 0;
int puntos = 0;
int tecla;
 
int salir = 0;
 
 
/* -------------- Rutinas de temporizacin -------- */
// Contadores para temporizacin
volatile int contadorN = 0;
volatile int contadorM = 0;
volatile int contadorD = 0;
 
// La rutina que los aumenta cada cierto tiempo
void aumentaContadores(void)
{
   contadorN++;
   contadorM++;
   contadorD++;
}
 
END_OF_FUNCTION(aumentaContadores);
 
 
 
 
/* -------------- Rutina de inicializacin -------- */
int inicializa()
{
    int i,j;
 
    allegro_init();        // Inicializamos Allegro
    install_keyboard();
    install_timer();
 
                           // Intentamos entrar a modo grafico
    if (set_gfx_mode(GFX_SAFE, ANCHOPANTALLA, ALTOPANTALLA, 0, 0) != 0) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message(
            "Incapaz de entrar a modo grafico\n%s\n",
            allegro_error);
        return 1;
    }
 
                           // e intentamos abrir imgenes
    imagen = load_pcx("spr_inv.pcx", pal);
    if (!imagen) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("No se ha podido abrir la imagen\n");
        return 1;
    }
    set_palette(pal);
 
    // Ahora reservo espacio para los otros sprites
    nave = create_bitmap(16, 16);
    marciano1 = create_bitmap(16, 16);
    marciano2 = create_bitmap(16, 16);
    disparo = create_bitmap(8, 8);
 
    // Y los extraigo de la imagen "grande"
    blit(imagen, nave    // bitmaps de origen y destino
      , 32, 32           // coordenadas de origen
      , 0, 0             // posicin de destino
      , 16, 16);         // anchura y altura
 
    blit(imagen, marciano1, 0, 32, 0, 0, 16, 16);
    blit(imagen, marciano2, 16, 32, 0, 0, 16, 16);
    blit(imagen, disparo, 72, 32, 0, 0, 8, 8);
 
    // Valores iniciales de los marcianos
    for (i=0; i<COLUMNASMARCIANOS; i++)
        for (j=0; j<FILASMARCIANOS; j++) {
            marcianos[i][j].posX = MARGENIZQDO + i*DISTANCIAXMARCIANOS;
            marcianos[i][j].posY = MARGENSUP + j*DISTANCIAYMARCIANOS;
            marcianos[i][j].activo = 1;
            }
 
    // Pantalla oculta para evitar parpadeos
    // (doble buffer)
    pantallaOculta = create_bitmap(320, 200);
 
 
   // Rutinas de temporizacin 
   // Bloqueamos las variables y la funcin del temporizador
   LOCK_VARIABLE( contadorN );
   LOCK_VARIABLE( contadorM );
   LOCK_VARIABLE( contadorD );
   LOCK_FUNCTION( aumentaContadores );
 
   // Y ponemos el temporizador en marcha: cada 10 milisegundos
   // (cada centsima de segundo)
   install_int(aumentaContadores, 10);
 
   // Y termino indicando que no ha habido errores
   return 0;
}
 
/* -------------- Rutina de mover marciano -------- */
int mueveMarciano()
{
    int primeraX = ANCHOPANTALLA;
    int ultimaX = 0;
    int i,j;
 
    if (fotograma==1) fotograma = 2;
        else fotograma = 1;
 
    // Calculo nueva posicin
    for (i=0; i<COLUMNASMARCIANOS; i++)
        for (j=0; j<FILASMARCIANOS; j++) {
            marcianos[i][j].posX += desplMarciano;
            // Y veo si es el ms a la derecha o a la izqda
            if ((marcianos[i][j].activo) && (marcianos[i][j].posX > ultimaX))
                ultimaX = marcianos[i][j].posX;
            if ((marcianos[i][j].activo) && (marcianos[i][j].posX < primeraX))
                primeraX = marcianos[i][j].posX;
            }
 
    // Compruebo si debe bajar
    if ((ultimaX > ANCHOPANTALLA-16-MARGENDCHO)
        || (primeraX < MARGENIZQDO))
    {
        desplMarciano = -desplMarciano; // Direccin contraria
        for (i=0; i<COLUMNASMARCIANOS; i++)
            for (j=0; j<FILASMARCIANOS; j++) {
                marcianos[i][j].posY += INCREMY;           // Y bajo una lnea
            }
    }
 
    // O si se ha "invadido", partida acabada)
    for (i=0; i<COLUMNASMARCIANOS; i++)
        for (j=0; j<FILASMARCIANOS; j++) {
            if ((marcianos[i][j].activo)
                    && (marcianos[i][j].posY > ALTOPANTALLA-20-MARGENINF)) {
                /*textprintf(screen, font, 10, 10,
                    palette_color[11], "Invadido!");*/
                salir = 1;            // Y se acab
            }
        }
}
 
/* -------------- Rutina de mover disparo -------- */
int mueveDisparo()
{
    int i,j;
 
    // Calculo nueva posicion
    yDisparo -= 4;
 
    // Compruebo si ha chocado
    for (i=0; i<COLUMNASMARCIANOS; i++)
        for (j=0; j<FILASMARCIANOS; j++)
            if (marcianos[i][j].activo) {
                if ((xDisparo >= marcianos[i][j].posX)
                      && (xDisparo <= marcianos[i][j].posX + 8)
                      && (yDisparo >= marcianos[i][j].posY)
                      && (yDisparo <= marcianos[i][j].posY + 8))
                    {
                    //textprintf(screen, font, xDisparo, yDisparo,
                    //    palette_color[11], "Boom!");
                    marcianos[i][j].activo = 0;
                    disparoActivo = 0;
                    puntos += 10;
                    numMarcianos --;
                    if (numMarcianos < 1) {
                    /*textprintf(screen, font, 10, 10,
                        palette_color[11], "Partida ganada!");*/
                        salir = 1;            // Y se acab
                        }
                    }
            }
 
    // O si ha llegado arriba
    if (yDisparo < MARGENSUP-8) {
        xDisparo = 0; yDisparo = 0;
        puedeMoverDisparo = 0;
        disparoActivo = 0;
    }
}
 
 
/* ------------------------------------------------ */
/*                                                  */
/* -------------- Cuerpo del programa ------------- */
 
int main()
{
 
    int i,j;
 
    // Intento inicializar
    if (inicializa() != 0)
        exit(1);
 
    do {  // Parte que se repite hasta pulsar tecla
 
        // Slo se pueden mover tras un cierto tiempo            
        if (contadorM >= RETARDOCM) {
            puedeMoverMarciano = 1;
            contadorM = 0;
        }
        if (contadorN >= RETARDOCN) {
            puedeMoverNave = 1;
            contadorN = 0;
        }
        if ((contadorD >= RETARDOCD) && (disparoActivo)) {
            puedeMoverDisparo = 1;
            contadorD = 0;
        }
 
        //if (puedeMoverMarciano || puedeMoverNave || puedeMoverDisparo)
 
 
        // Sincronizo con el barrido para evitar 
        // parpadeos, borro la pantalla y dibujo la nave
        clear_bitmap(pantallaOculta);
        draw_sprite(pantallaOculta, nave, xNave, yNave);
 
        // Dibujo un marciano u otro, alternando
        for (i=0; i<COLUMNASMARCIANOS; i++)
            for (j=0; j<FILASMARCIANOS; j++)
                if (marcianos[i][j].activo)
                    if (fotograma==1)
                        draw_sprite(pantallaOculta, marciano1,
                            marcianos[i][j].posX, marcianos[i][j].posY);
                    else
                        draw_sprite(pantallaOculta, marciano2,
                            marcianos[i][j].posX, marcianos[i][j].posY);
 
 
        // Dibujo el disparo
        if (disparoActivo)
          draw_sprite(pantallaOculta, disparo, xDisparo, yDisparo);
 
        // Escribo la puntuacin
        textprintf(pantallaOculta, font, 100, 1,
            palette_color[11], "Puntos: %d",puntos);
 
        // Sincronizo con el barrido para evitar parpadeos
        // y vuelco la pantalla oculta
        vsync();
        blit(pantallaOculta, screen, 0, 0, 0, 0,
          ANCHOPANTALLA, ALTOPANTALLA);
 
 
        // Compruebo teclas pulsadas para salir
        // o mover la nave
        if (keypressed()) {
            tecla = readkey() >> 8;
            if ( tecla == KEY_ESC)
                salir = 1;
            if (puedeMoverNave) {
                if (( tecla == KEY_RIGHT)
                        && (xNave < ANCHOPANTALLA-16-MARGENDCHO)) {
                    xNave += INCREMX;
                    puedeMoverNave = 0;
                    }
                if (( tecla == KEY_LEFT)
                        && (xNave > MARGENIZQDO+16)) {
                    xNave -= INCREMX;
                    puedeMoverNave = 0;
                    }
                if (( tecla == KEY_SPACE)
                        && (!disparoActivo)) {
                    disparoActivo = 1;
                    puedeMoverDisparo = 1;
                    contadorD = 0;
                    xDisparo = xNave;
                    yDisparo = yNave-2;
                    }
                clear_keybuf();
                }
        }
 
 
            // Y calculo la nueva posicin de los marcianos
            if (puedeMoverMarciano) {
                mueveMarciano();
                puedeMoverMarciano = 0;
            }
 
            // Y anlogo para el disparo
            if (puedeMoverDisparo)  {
                mueveDisparo();
                puedeMoverDisparo = 0;
            }
 
    } while (!salir);
 
    textprintf(screen, font, 1, 1,
            palette_color[11], "Partida terminada.       ");
    destroy_bitmap(imagen);
    destroy_bitmap(nave);
    destroy_bitmap(marciano1);
    destroy_bitmap(marciano2);
    destroy_bitmap(disparo);
    destroy_bitmap(pantallaOculta);
    readkey();
    rest(2000);
    return 0;
 
}
 
            /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();
 

Ahora añadamos disparos de los enemigos...