21. Moviendo varios marcianos a la vez. Octavo juego(aproximación "e"): Marciano 5. (*)

Si se ha entendido la aproximación anterior al juego, la versión "completa" no aporta muchas dificultades. Apenas dos importantes:

  • Ahora no tendremos un único enemigo, sino varios. Además, no siempre habrá que redibujar todos los enemigos, sino que algunos todavía estarán "vivos", mientras que a otros ya los habremos "matado". El juego terminará cuando matemos a todos los enemigos (en un juego "real" se debería pasar a un nivel de dificultad mayor) o cuando se nos mate a nosotros (en la práctica debería ser cuando agotáramos un cierto número de "vidas", habitualmente 3).
  • Además, para que el juego tenga más aliciente, sería deseable que los enemigos también sean capaces de dispararnos a nosotros.

Por eso, haremos nuevamente varias aproximaciones sucesivas hasta tener un juego "razonablemente jugable". 

En este primer acercamiento, simplemente mostrará varios enemigos moviéndose a la vez.

Las diferencias entre tener un único enemigo o varios no son muchas, pero hay que tener varias cosas en cuenta:

  • Habrá que mover varios enemigos a la vez. Esto se puede hacer guardando la información de todos ellos dentro de un "array", tanto en nuestro caso, en el que todos los enemigos se moverán a la vez, como en el caso de que tuvieran movimientos relativamente independientes. Para cada marciano nos podría interesar (según el caso) guardar información como: posición actual, trayectoria actual, apariencia actual, etc.
  • Podemos ir "matando marcianos", de modo que no estarán siempre todos visibles. Podríamos eliminar realmente a los marcianos (pasaríamos de tener "n" a tener "n-1") o simplemente marcarlos como "no activos" (siempre tendríamos "n", pero algunos estarían activos y otros no).
  • El espacio que recorre cada marciano no es siempre el mismo: el "bloque" formado por todos los marcianos se mueve todo lo que pueda a la derecha y todo lo que pueda a la izquierda. A medida que desaparezcan los marcianos de los extremos, los centrales podrán avanzar más hacia cada lado.


Vamos poco a poco:

El hecho de dibujar varios marcianos se podría resolver simplemente con algo como

 
  for (i=0; i<COLUMNASMARCIANOS; i++)  
      for (j=0; j<FILASMARCIANOS; j++) { 
              draw_sprite(screen, marciano1,  
                  xMarciano + i*DISTANCIAXMARCIANOS, 
                  yMarciano + j*DISTANCIAYMARCIANOS); 
              ... 
 

Donde las constantes son las que indican el número de marcianos en vertical y en horizontal, así como la distancia entre ellos:

 
  #define FILASMARCIANOS 5 
  #define COLUMNASMARCIANOS 11 
  #define DISTANCIAXMARCIANOS 20 
  #define DISTANCIAYMARCIANOS 12
 

Además, los marcianos deberán avanzar menos hacia la derecha que si estuvieran solos, por ejemplo con:

 
  if ((xMarciano > ANCHOPANTALLA-16-MARGENDCHO-COLUMNASMARCIANOS*DISTANCIAXMARCIANOS)  
      || (xMarciano < MARGENIZQDO)) 
  { 
      desplMarciano = -desplMarciano; // Dirección contraria 
      yMarciano += INCREMY;           // Y bajo una línea 
  } 
 

Pero este planteamiento no hace que sea fácil calcular cuando se ha matado a un marciano concreto, ni cuando el bloque de marcianos debe avanzar más hacia la derecha o hacia la izquierda. Una opción más razonable sería crear una "estructura" (nos vamos acercando a los objetos) que representa a cada marciano, con datos como su posición y si está activo:

 
  // Datos de cada marciano 
  typedef struct { 
      int posX; 
      int posY; 
      int activo; 
  } marciano; 
 
  // El array que controla a todos los marcianos 
  marciano marcianos[COLUMNASMARCIANOS][FILASMARCIANOS]; 
 

Y apenas cambia un poco la forma de dibujar a los marcianos, que ahora se hará dentro de un doble bucle "for", para recorrer todos, y comprobando si están activos:

 
  // Dibujo un marciano u otro, alternando 
  if (puedeMoverMarciano) { 
      for (i=0; i<COLUMNASMARCIANOS; i++)  
          for (j=0; j<FILASMARCIANOS; j++) 
              if (marcianos[i][j].activo) 
              { 
                  if (fotograma==1) { 
                      draw_sprite(screen, marciano1,  
                          marcianos[i][j].posX, marcianos[i][j].posY); 
                      fotograma = 2; 
                  } else { 
                      draw_sprite(screen, marciano2, 
                          marcianos[i][j].posX, marcianos[i][j].posY); 
                      fotograma = 1;  
                  } 
              }  
      } 
 

o la de comprobar si un disparo ha acertado, con los mismos cambios:

 
  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 hasta donde deben moverse los marcianos, porque habrá que calcular la "primera x" (la del marciano más a la izquierda) y la "última x" (la del marciano más a la derecha):

 
  // Calculo nueva posición 
  for (i=0; i<COLUMNASMARCIANOS; i++)  
      for (j=0; j<FILASMARCIANOS; j++) { 
          marcianos[i][j].posX += desplMarciano; 
          // Y veo si es el más 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; // Dirección contraria 
      for (i=0; i<COLUMNASMARCIANOS; i++)  
          for (j=0; j<FILASMARCIANOS; j++) { 
              marcianos[i][j].posY += INCREMY;           // Y bajo una línea 
          } 
  } 
 

IPJ - Tema 21

De paso, he añadido un par de refinamientos: una puntuación (10 puntos por cada marciano eliminado) y el final de la partida si los marcianos nos invaden (si alcanzan la parte inferior de la pantalla).
 

El problema es que dibujamos unas 57 figuras en pantalla cada vez, lo que provoca un serio problema de parpadeo, que corregiremos en el próximo apartado. Aun así, para quien quiera ver cómo sería el fuente completo de esta versión preliminar, aquí está:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*    ipj21c.c                  */
/*                              */
/*  Ejemplo:                    */
/*    Varios marcianos y una    */
/*    nave movindose de        */
/*    forma independiente; la   */
/*    nave puede disparar y     */
/*    "matar" a los marcianos   */
/*    Problema: 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    */
/*------------------------------*/
 
 
 
#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 10
#define RETARDOCM 35
// Y tambin el del disparo
#define RETARDOCD 15
 
#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;
 
// 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 lo 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;
            }
 
   // Rutinas de temporizacin 
   // Bloqueamos las variables y la funcin del temporizador
   LOCK_VARIABLE( contadorN );
   LOCK_VARIABLE( contadorM );
   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;
        }
 
 
        // 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();
                }
        }
 
        if (puedeMoverMarciano || puedeMoverNave || puedeMoverDisparo)
        {
 
            // Sincronizo con el barrido para evitar 
            // parpadeos, borro la pantalla y dibujo la nave
            vsync();
            clear_bitmap(screen);
            draw_sprite(screen, nave, xNave, yNave);
 
            // Dibujo un marciano u otro, alternando
            if (puedeMoverMarciano) {
                for (i=0; i<COLUMNASMARCIANOS; i++)
                    for (j=0; j<FILASMARCIANOS; j++)
                        if (marcianos[i][j].activo)
                        {
                            if (fotograma==1)
                                draw_sprite(screen, marciano1,
                                    marcianos[i][j].posX, marcianos[i][j].posY);
                            else
                                draw_sprite(screen, marciano2,
                                    marcianos[i][j].posX, marcianos[i][j].posY);
                        }
                }
 
            // Y calculo su nueva posicin si corresponde
            if (puedeMoverMarciano) {
                mueveMarciano();
                puedeMoverMarciano = 0;
            }
 
            // Y anlogo para el disparo
            if (puedeMoverDisparo)  {
                mueveDisparo();
                puedeMoverDisparo = 0;
            }
            if (disparoActivo)
              draw_sprite(screen, disparo, xDisparo, yDisparo);
 
            textprintf(screen, font, 100, 1,
                palette_color[11], "Puntos: %d",puntos);
        }
 
    } 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);
    readkey();
    rest(2000);
    return 0;
 
}
 
            /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();
 

Eliminemos ese parpadeo...