11. Cuarto juego (completo): Serpiente.

Contenido de este apartado:


11.1. Idea básica: cambio de paleta, serpiente creciente.

Con muy pocas modificaciones, podemos mejorar el juego de la "Mini-serpiente" que acabamos de hacer:

  • Por una parte, ya sabemos cómo cambiar la paleta de colores estándar, para conseguir un apariencia algo más vistosa.
  • Por otra parte, podemos hacer que la serpiente sea más larga, en vez de ocupar una única posición. Para ser más concretos, haremos que su tamaño vaya aumentando cada vez que coma una fruta.


La primera parte (cambiar colores) no debería suponer ningún problema. Por ejemplo, si queremos que los ladrillos de la pared sean de tres tonos distintos de gris, podríamos redefinir los colores 1, 2 y 3 haciendo:

  RGB gris30 =       { 30,  30,  30  };
  RGB gris40       = { 40,  40,  40  };
  RGB gris40verde45 = { 40,  45,  40  };

  set_color( 1, &gris40 ); 
  set_color( 2, &gris30 ); 
  set_color( 3, &gris40verde45 );

Lo de cambiar el tamaño de la serpiente puede sonar más complicado, pero no lo es: podemos hacerlo de una forma bastante sencilla: definimos la serpiente como un "array", para el que reservaremos tanto espacio como el máximo que vayamos a permitir, En la posición 0 de este array memorizaremos el punto de la pantalla en el que se encuentra el primer segmento de la serpiente. Cuando coma una fruta, la longitud aumenta a 2: en la posición 0 sigue estando la "cabeza" de la serpiente, y en la posición 1 está el segundo segmento. Cuando coma otra fruta, usaremos las posiciones 0, 1 y 2 del array, y así sucesivamente.

La única dificultad es plantear cómo podemos hacer que "se mueva" toda la serpiente de manera conjuntada. Lo que haremos es que la "cabeza" de la serpiente (la posición 0) se mueva tal y como lo hacía la mini-serpiente: hacia la dirección que le indiquemos al pulsar una tecla, o continuará moviéndose en la misma dirección si no pulsamos ninguna tecla. Por lo que respecta al resto de la serpiente, irá siguiendo a la cabeza: la posición 1 pasará a ocupar el punto de pantalla que antes ocupaba la posición 0, la 2 pasará a ocupar el de la 1 y así sucesivamente. Se podría conseguir así:

  for (i=longSerpiente-1; i>=1; i--) {
    trozoSerpiente[i].x = trozoSerpiente[i-1].x;
    trozoSerpiente[i].y = trozoSerpiente[i-1].y;
  }

  trozoSerpiente[0].x = posX;
  trozoSerpiente[0].y = posY;

La apariencia que obtendremos será algo así:

 

Serpiente - apariencia

 

Por supuesto, para llegar a un juego "de verdad" todavía queda mucho: distintos niveles de dificultad, varias "vidas", puntuación y tabla de records... pero todo irá llegando.

11.2 Serpiente en C. 

Los cambios con la versión anterior no son grandes...
 

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*    ipj11c.c                  */
/*                              */
/*  Undecimo ejemplo: juego de  */
/*  "la Serpiente"              */
/*                              */
/*  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 - Win      */
/*  - Gcc 3.2.2 + All. 4.03     */
/*    en Mandrake Linux 9.1     */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - Win XP   */
/*------------------------------*/
 
#include <allegro.h>
 
 
  /* Posiciones X e Y iniciales */
#define POS_X_INI 16
#define POS_Y_INI 10
 
#define INC_X_INI  1
#define INC_Y_INI  0
 
  /* Pausa en milisegundos entre un "fotograma" y otro */
#define PAUSA 350
 
  /* Teclas predefinidas */
#define TEC_ARRIBA KEY_E
#define TEC_ABAJO  KEY_X
#define TEC_IZQDA  KEY_S
#define TEC_DCHA   KEY_D
 
int posX, posY;  /* Posicion actual */
int incX, incY;  /* Incremento de la posicion */
 
  /* Terminado: Si ha chocado o comida todas las frutas */
int terminado;
 
  /* La tecla pulsada */
int tecla;
 
  /* Escala: relacion entre tamao de mapa y de pantalla */
#define ESCALA 10
 
  /* Ancho y alto de los sprites */
#define ANCHOSPRITE 10
#define ALTOSPRITE  10
 
  /* Y el mapa que representa a la pantalla */
  /* Como usaremos modo grafico de 320x200 puntos */
  /* y una escala de 10, el tablero medira 32x20 */
#define MAXFILAS 20
#define MAXCOLS  32
 
char mapa[MAXFILAS][MAXCOLS]={
  "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "X                      X     X",
  "X    F                 X     X",
  "X               F      X  F  X",
  "X     XXXXX            X     X",
  "X     X                X     X",
  "X     X        X       X     X",
  "X     X        X       X     X",
  "X              X       X     X",
  "X              X       X     X",
  "X              X       X     X",
  "X   F          X             X",
  "X              X             X",
  "X              X         F   X",
  "X      X            X        X",
  "X      X            X        X",
  "X      X       F     X       X",
  "X  F   X              X      X",
  "X      X              F      X",
  "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
};
 
int numFrutas = 8;
 
  /* Nuestros sprites */
BITMAP *ladrilloFondo, *comida, *jugador;
 
 
typedef
  char tipoSprite[ANCHOSPRITE][ALTOSPRITE];
                         /* El sprite en si: matriz de 30x30 bytes */
 
tipoSprite spriteLadrillo =
   {{0,2,2,2,2,2,2,2,2,0},
    {2,1,1,1,1,2,2,1,1,2},
    {2,2,1,1,1,1,1,1,1,2},
    {2,2,1,1,1,1,1,1,1,2},
    {2,1,3,1,1,1,1,1,1,2},
    {2,1,1,1,1,1,1,1,3,2},
    {2,1,1,1,1,1,1,3,3,2},
    {2,2,1,1,1,1,3,3,2,2},
    {0,2,2,2,2,2,2,2,2,0}
   };
 
tipoSprite spriteComida =
   {{00,00,00,12,00,00,00,00,00,00},
    {00,00,11,12,11,00,12,11,00,00},
    {00,14,14,14,12,12,14,14,00,00},
    {14,13,14,14,14,11,14,14,14,00},
    {14,14,14,14,14,14,14,13,14,00},
    {14,14,14,14,14,14,13,13,14,00},
    {14,14,14,14,14,14,13,14,14,00},
    {14,14,14,14,14,14,14,14,14,00},
    {00,14,14,14,14,14,14,14,00,00}
   };
 
tipoSprite spriteJugador =
   {{0,0,4,4,4,4,4,0,0,0},
    {0,4,5,5,5,5,5,4,0,0},
    {4,5,6,6,6,6,6,5,4,0},
    {4,5,6,5,6,6,6,5,4,0},
    {4,5,6,6,6,6,6,5,4,0},
    {4,5,6,6,6,6,6,5,4,0},
    {0,4,5,5,5,5,5,4,0,0},
    {0,0,4,4,4,4,4,0,0,0}
   };
 
 
/* -------------- Dato de la serpiente -------------------- */
 
int longSerpiente=1;
 
struct {
     int x;
     int y;
  }
  trozoSerpiente[10];
 
 
 
/* -------------- Rutina de crear los sprites ------------- */
 
 
void creaSprites()
{
  int i, j;
 
  /* Los ladrillos sern grises */
 
  RGB gris30 =       { 30,  30,  30  };
  RGB gris40       = { 40,  40,  40  };
  RGB gris40verde45 = { 40,  45,  40  };
 
  set_color( 1, &gris40 );
  set_color( 2, &gris30 );
  set_color( 3, &gris40verde45 );
 
  ladrilloFondo = create_bitmap(10, 10);
  clear_bitmap(ladrilloFondo);
  for(i=0; i<ANCHOSPRITE; i++)
    for (j=0; j<ALTOSPRITE; j++)
      putpixel(ladrilloFondo, i, j,
        palette_color[ spriteLadrillo[j][i]  ]);
 
 
  /* Los comida ser roja (y verde) */
 
  RGB rojo60 =        { 60,   0,   0  };
  RGB rojo50       =  { 50,   0,   0  };
  RGB verde40  =      {  0,  40,   0  };
  RGB verde60  =      {  0,  60,   0  };
 
  set_color( 11, &verde40 );
  set_color( 12, &verde60 );
  set_color( 13, &rojo60 );
  set_color( 14, &rojo50 );
 
  comida = create_bitmap(10, 10);
  clear_bitmap(comida);
  for(i=0; i<ANCHOSPRITE; i++)
    for (j=0; j<ALTOSPRITE; j++)
      putpixel(comida, i, j,
        palette_color[ spriteComida[j][i]  ]);
 
 
  /* La serpiente ser azul */
 
  RGB azul60 =       {  0,   0,  60  };
  RGB azul60gris10 = { 10,  10,  60  };
  RGB azul60gris20 = { 20,  20,  60  };
 
  set_color( 4, &azul60gris20 );
  set_color( 5, &azul60gris10);
  set_color( 6, &azul60 );
 
  jugador = create_bitmap(10, 10);
  clear_bitmap(jugador);
  for(i=0; i<ANCHOSPRITE; i++)
    for (j=0; j<ALTOSPRITE; j++)
      putpixel(jugador, i, j,
        palette_color[ spriteJugador[j][i]  ]);
 
}
 
 
/* -------------- Rutina de dibujar el fondo ------------- */
 
void dibujaFondo()
{
  int i, j;
 
  clear_bitmap(screen);
 
    for(i=0; i<MAXCOLS; i++)
    for (j=0; j<MAXFILAS; j++) {
      if (mapa[j][i] == 'X')
        draw_sprite(screen, ladrilloFondo, i*ESCALA, j*ESCALA);
      if (mapa[j][i] == 'F')
        draw_sprite(screen, comida, i*ESCALA, j*ESCALA);
      }
 
}
 
 
/* ------------------------------------------------ */
/*                                                  */
/* -------------- Cuerpo del programa ------------- */
 
int main()
{
  int i;
 
  allegro_init();              /* Inicializamos Allegro */
  install_keyboard();
  install_timer();
 
                               /* Intentamos entrar a modo grafico */
  if (set_gfx_mode(GFX_SAFE, 320, 200, 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;
  }
 
  /* ----------------------- Si todo ha ido bien: empezamos */
 
  creaSprites();
  dibujaFondo();
 
            /* Valores iniciales */
  posX = POS_X_INI;
  posY = POS_Y_INI;
 
  incX = INC_X_INI;
  incY = INC_Y_INI;
 
 
            /* Parte repetitiva: */
  do {
    dibujaFondo();
 
    /* Aadido en la versin completa: dibuja todos los segmentos de la serpiente */
    for (i=1; i<=longSerpiente; i++)
      draw_sprite (screen, jugador, trozoSerpiente[i-1].x*ESCALA,
        trozoSerpiente[i-1].y*ESCALA);
 
 
    terminado = FALSE;
 
    /* Si paso por una fruta: la borro y falta una menos */
    if (mapa[posY][posX] == 'F') {
      mapa[posY][posX] = ' ';
      numFrutas --;
      if (numFrutas == 0) {
        textout(screen, font,
          "Ganaste!", 100, 90, palette_color[14]);
        terminado = TRUE;
      }
      /* Esta es la novedad en la version "completa": aumenta la serpiente */
      longSerpiente ++;
 
    }
 
 
    /* Si choco con la pared, se acabo */
    if (mapa[posY][posX] == 'X') {
      textout(screen, font,
        "Chocaste!", 100, 90, palette_color[13]);
      terminado = TRUE;
    }
 
    if (terminado) break;
 
    /* Compruebo si se ha pulsado alguna tecla */
    if ( keypressed() ) {
        tecla = readkey() >> 8;
 
        switch (tecla) {
          case TEC_ARRIBA:
            incX =  0; incY = -1;  break;
          case TEC_ABAJO:
 
            incX =  0; incY =  1;  break;
          case TEC_IZQDA:
            incX = -1; incY =  0;  break;
          case TEC_DCHA:
            incX =  1; incY =  0;  break;
        }
 
    }
 
    posX += incX;
    posY += incY;
 
 
    /* Esta es la novedad en la version "completa": aumenta la serpiente */
    for (i=longSerpiente-1; i>=1; i--) {
      trozoSerpiente[i].x = trozoSerpiente[i-1].x;
      trozoSerpiente[i].y = trozoSerpiente[i-1].y;
    }
    trozoSerpiente[0].x = posX;
    trozoSerpiente[0].y = posY;
 
 
    /* Pequea pausa antes de seguir */
    rest ( PAUSA );
 
  }
  while (TRUE);  /* Repetimos indefininamente */
                 /* (la condicin de salida la comprobamos "dentro") */
 
  readkey();
  return 0;
 
}
 
                     /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();
 




11.3. Serpiente en Pascal.

(Todavía no disponible)





11.4. Serpiente en Java.

(Todavía no disponible; pronto lo estará)