Versión: 0.33, de 05-Oct-2009


25. Avanzando Columnas: primera parte de la lógica de juego.(*)

La entrega anterior mostraba la apariencia que suele tener el "bucle de juego", y lo aplicaba para un primer acercamiento al nuestro juego "Columnas". Hasta entonces, una pieza ficticia se podía mover de lado a lado. Ahora llega el momento de ampliar ese "esqueleto" con parte de la lógica que corresponde realmente a este juego.

Tendremos que hacer cosas como:

 
Vamos a ver cómo se podría hacer cada una de esas cosas:

Para que la pieza esté formada por tres fragmentos al azar, podemos leer la imagen que contiene todos los fragmentos posibles y "trocearla": columnas_pieza0.bmp

(Realmente, esta imagen contiene un séptimo fragmento, que no usaremos... todavía...)

 
#define NUMFRAGMENTOS 6
BITMAP *pieza[NUMFRAGMENTOS];
imagenFragmentos = load_bmp("columnas_piezas.bmp", pal);
// Ahora reservo espacio para los otros sprites
for (i=0; i<NUMFRAGMENTOS; i++)    
{
  pieza[i] = create_bitmap(32, 32);
 
  // Y los extraigo de la imagen "grande"
  blit(imagenFragmentos, pieza[i]    // bitmaps de origen y destino
    , 32*i, 0           // coordenadas de origen
    , 0, 0             // posición de destino
    , 32, 32);         // anchura y altura
}
 

Y luego podemos guardar en un "array" los 3 tipos de fragmento que contiene la pieza actualmente

 
// Tipo de imagen que corresponde a cada fragmento de pieza
int tipoFragmento[3];
 

Cuando se crea una pieza nueva, esos fragmentos se escogen al azar:

 
for (i=0; i<3; i++)
  tipoFragmento[i] = rand() % NUMFRAGMENTOS;
 

Y se consulta este array para dibujar la pieza:

 
  for (i=0; i<3; i++)
    draw_sprite(pantallaOculta, 
      pieza[ tipoFragmento[i] ], x, y+32*i);
 

Por otra parte, la forma de rotar colores sería asignar a la segunda casilla el color que tenía la primera, asignar a la tercera e que tenía la segunda y así sucesivamente:

 
  int auxiliar = tipoFragmento[0];
  tipoFragmento[0] = tipoFragmento[1];
  tipoFragmento[1] = tipoFragmento[2];
  tipoFragmento[2] = auxiliar;
 

Para que la pieza no se podrá mover tan libremente como en la primera aproximación, sino que sólo habrá unas pocas columnas verticales, los saltos (incrementos) verticales deberán ser algo mayores, y deberemos comprobar que no se salga por ninguno de los dos extremos de la "pantalla de juego":

 
#define TAMANYOPIEZA 32
int incrX = TAMANYOPIEZA;
...
void intentarMoverIzquierda() {
  if (x > MARGENIZQDO)
    x -= incrX;
}
 

La pieza debe ir bajando poco a poco.... asío que nuestro "moverElementos" ya tendrá que hacer algo aunque no pulsemos ninguna tecla. Si hacemos "y += incrY;" en cada pasada, la pieza se moverá demasiado deprisa, así que será ás jugable si llevamos un contador de fotogramas dibujados, de modo que sólo avance una vez cada cierto número de fotogramas (por ejemplo, 10):

 
void moverElementos() {
  contadorFotogramas ++;
  if (contadorFotogramas >= 10) {   
    y += incrY;
    contadorFotogramas = 0;
  }
}
 

También queremos que se pueda pulsar la flecha hacia abajo para que la pieza caiga un poco más rápido. Eso es muy fácil:

 
if ( tecla == KEY_DOWN )
  intentarBajar();
...
void intentarBajar() {
  y += incrY;
  if (y >= ALTOPANTALLA-MARGENINF-3*TAMANYOPIEZA)
    colocarPiezaEnFondo(); 
}
 

Cuando toca el suelo, haremos que las figuras que al componen pasen a ser parte del fondo, pero aplazamos para la siguiente entregar el comprobar si hay 3 o más piezas del mismo color en línea para eliminarlas. También aplazaremos por ahora la comprobación de choques en vertical y en horizontal, y el final de la partida (seguiremos teniendo que pulsar ESC). Para comprobar si ha llegado al suelo, podríamos añadir estas líneas al final de "intentarBajar" y de "moverElementos":

 
if (y >= ALTOPANTALLA-MARGENINF-3*TAMANYOPIEZA)
  colocarPiezaEnFondo(); 
 

Y esa función "colocarPiezaEnFondo" se encargaría de guardar en un array de dos dimensiones cada fragmento de la pieza actual, calculando la posición del "tablero" que le corresponde a cada fargmento (para lo que habrá que restar el margen y dividir entre el tamaño de la pieza):

 
  for (i=0; i<3; i++) {
    tablero
      [ (x-MARGENIZQDO)/TAMANYOPIEZA ]
      [ (y-MARGENSUP)/TAMANYOPIEZA + i] 
        = tipoFragmento[i];
  }
 

Con todo esto, la nueva versión del fuente podría ser así

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*    ipj25c.c                  */
/*                              */
/*  Ejemplo:                    */
/*    Segundo acercamiento a    */
/*    Columnas                  */
/*                              */
/*  Comprobado con:             */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*------------------------------*/
 
 
 
#include <allegro.h>
 
/* -------------- Constantes globales ------------- */
#define ANCHOPANTALLA 640
#define ALTOPANTALLA 480
 
#define ANCHOTABLERO 10
#define ALTOTABLERO  14
 
#define MARGENSUP   16
#define MARGENDCHO  160
#define MARGENIZQDO 160
#define MARGENINF   16
 
// Número de fragmentos distintos con los que
// formar las piezas
#define NUMFRAGMENTOS 6
 
// Ancho y alto de cada pieza
#define TAMANYOPIEZA 32
 
 
/* -------------- Variables globales -------------- */
PALETTE pal;
BITMAP *imagenFragmentos;
BITMAP *pantallaOculta;
 
BITMAP *pieza[NUMFRAGMENTOS];
 
int partidaTerminada;
int x = MARGENIZQDO + TAMANYOPIEZA*5;
int y = MARGENSUP;
int incrX = TAMANYOPIEZA;
int incrY = 4;
int tecla;
 
// Tipo de imagen que corresponde a cada fragmento de pieza
int tipoFragmento[3];
 
// El tablero de fondo
int tablero[ANCHOTABLERO][ALTOTABLERO];
 
// Contador de fotogramas, para regular la velocidad de juego
int contadorFotogramas = 0;
 
// Prototipos de las funciones que usaremos
void comprobarTeclas();
void moverElementos();
void comprobarColisiones();
void dibujarElementos();
void pausaFotograma();
void intentarMoverDerecha();
void intentarMoverIzquierda();
void intentarBajar();
void rotarColores();
void colocarPiezaEnFondo();
 
 
 
// --- Bucle principal del juego -----
void buclePrincipal() {
  partidaTerminada = FALSE;
  do {
    comprobarTeclas();
    moverElementos();
    comprobarColisiones();
    dibujarElementos();
    pausaFotograma();
  } while (partidaTerminada != TRUE);
}
 
 
// -- Comprobac de teclas para mover personaje o salir
void comprobarTeclas() {   
 
  if (keypressed()) {
    tecla = readkey() >> 8;
    if ( tecla == KEY_ESC )
      partidaTerminada = TRUE;
    if ( tecla == KEY_RIGHT )
       intentarMoverDerecha();   
    if ( tecla == KEY_LEFT )
       intentarMoverIzquierda();
    if ( tecla == KEY_DOWN )
       intentarBajar();
    if (( tecla == KEY_SPACE ) || ( tecla == KEY_UP ))
       rotarColores();
     clear_keybuf();
   }
}
 
 
// -- Intenta mover la "pieza" hacia la derecha
void intentarMoverDerecha() {
  if (x < ANCHOPANTALLA-MARGENDCHO-TAMANYOPIEZA)
    x += incrX;
}
 
 
// -- Intenta mover la "pieza" hacia la izquierda
void intentarMoverIzquierda() {
  if (x > MARGENIZQDO)
    x -= incrX;
}
 
 
// -- Intenta mover la "pieza" hacia abajo
void intentarBajar() {
  y += incrY;
  if (y >= ALTOPANTALLA-MARGENINF-3*TAMANYOPIEZA)
    colocarPiezaEnFondo(); 
}
 
 
// -- Rotar los colores de la "pieza"
void rotarColores() {
  int auxiliar = tipoFragmento[0];
  tipoFragmento[0] = tipoFragmento[1];
  tipoFragmento[1] = tipoFragmento[2];
  tipoFragmento[2] = auxiliar;
}
 
 
 
// -- Mover otros elementos del juego 
void moverElementos() {
  contadorFotogramas ++;
  if (contadorFotogramas >= 10) {   
    y += incrY;
    contadorFotogramas = 0;
    if (y >= ALTOPANTALLA-MARGENINF-3*TAMANYOPIEZA)
      colocarPiezaEnFondo(); 
  }
}
 
 
// -- Comprobar colisiones de nuestro elemento con otros, o disparos con enemigos, etc
void comprobarColisiones() {
  // Por ahora, no hay colisiones que comprobar
}
 
 
// -- Dibujar elementos en pantalla
void dibujarElementos() {
  int i,j;
 
  // Borro la pantalla y dibujo la pieza
  clear_bitmap(pantallaOculta);
 
  // Dibujo el "fondo", con los trozos de piezas anteriores
  for (i=0; i<ANCHOTABLERO; i++)
      for (j=0; j<ALTOTABLERO; j++)
        if (tablero[i][j] != -1)
          draw_sprite(pantallaOculta, 
            pieza[ tablero[i][j]  ], 
            MARGENIZQDO + TAMANYOPIEZA*i, 
            MARGENSUP + TAMANYOPIEZA*j);      
 
  // Dibujo la "pieza"
  for (i=0; i<3; i++)
    draw_sprite(pantallaOculta, 
      pieza[ tipoFragmento[i] ], x, y+32*i);
 
  // Sincronizo con el barrido para evitar parpadeos
  // y vuelco la pantalla oculta
  vsync();
  blit(pantallaOculta, screen, 0, 0, 0, 0,
    ANCHOPANTALLA, ALTOPANTALLA);
 
}
 
 
// -- Pausa hasta el siguiente fotograma
void pausaFotograma() {
  // Para 25 fps: 1000/25 = 40 milisegundos de pausa
  rest(40);
}
 
 
// -- Crea una nueva pieza con componentes al azar
void crearNuevaPieza() {
  int i;
  for (i=0; i<3; i++)
    tipoFragmento[i] = rand() % NUMFRAGMENTOS;
  x = MARGENIZQDO + TAMANYOPIEZA*5;
  y = MARGENSUP;  
}
 
 
// -- Coloca una pieza como parte del fondo, cuando
// llegamos a la parte inferior de la pantalla
void colocarPiezaEnFondo(){
  int i;
  for (i=0; i<3; i++) {
    tablero
      [ (x-MARGENIZQDO)/TAMANYOPIEZA ]
      [ (y-MARGENSUP)/TAMANYOPIEZA + i] 
        = tipoFragmento[i];
  }
  crearNuevaPieza();
}
 
 
/* -------------- Rutina de inicialización -------- */
int inicializa()
{
    int i,j;
 
    allegro_init();        // Inicializamos Allegro
    install_keyboard();
    install_timer();
 
                           // Intentamos entrar a modo grafico
    set_color_depth(32);
    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 imágenes
    imagenFragmentos = load_bmp("columnas_piezas.bmp", pal);
    if (!imagenFragmentos) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("No se ha podido abrir la imagen\n");
        return 1;
    }
 
        // Ahora reservo espacio para los otros sprites
    for (i=0; i<NUMFRAGMENTOS; i++)    
    {
      pieza[i] = create_bitmap(32, 32);
 
      // Y los extraigo de la imagen "grande"
      blit(imagenFragmentos, pieza[i]    // bitmaps de origen y destino
        , 32*i, 0           // coordenadas de origen
        , 0, 0             // posición de destino
        , 32, 32);         // anchura y altura
    }
 
    set_palette(pal);
 
    srand(time(0));
 
    // Pantalla oculta para evitar parpadeos
    // (doble buffer)
    pantallaOculta = create_bitmap(ANCHOPANTALLA, ALTOPANTALLA);
 
    // Vacío el tablero de fondo
    for (i=0; i<ANCHOTABLERO; i++)
      for (j=0; j<ALTOTABLERO; j++)
        tablero[i][j] = -1;
 
    crearNuevaPieza();
 
   // Y termino indicando que no ha habido errores
   return 0;
}
 
 
 
/* ------------------------------------------------ */
/*                                                  */
/* -------------- Cuerpo del programa ------------- */
 
int main()
{
 
    int i,j;
 
    // Intento inicializar
    if (inicializa() != 0)
        exit(1);
 
    // Bucle principal del juego
    buclePrincipal();
 
    // Libero memoria antes de salir
    for (i=0; i<NUMFRAGMENTOS; i++)
      destroy_bitmap(pieza[i]);
    destroy_bitmap(pantallaOculta);
 
    rest(1000);
    return 0;
 
}
 
            /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();
 
 

Anterior Siguiente