8. Cómo crear figuras multicolor que se muevan. Cuarto juego (aproximación "b"): Miniserpiente 2.

Contenido de este apartado:


8.1. Ideas generales.

Ya sabemos como usar "Mapas" para memorizar los obstáculos y "premios" que el personaje de nuestro juego debe esquivar o alcanzar. Pero nuestros personajes son "pobres", con una presentación muy poco lucida. Va llegando el momento de hacer personajes un poco más vistosos.

Aprenderemos a crear figuras multicolor que se muevan. Además estas figuras serán "transparentes": podrán tener "huecos" alrededor o en su interior y a través de estos huecos deberá verse el fondo. Estas figuras reciben el nombre de "sprites".

En Allegro tenemos rutinas para el manejo de Sprites:

  draw_sprite(screen, figura, posicionX, posicionY);

En Pascal también tendríamos algo parecido con la orden "putimage", pero seremos más atrevidos: imitaremos lo que haría la orden "draw_sprite" con una creada por nosotros, para que se entienda mejor el funcionamiento, a cambio de que el resultado sea algo más lento (no tendremos muchas figuras en pantalla, no será grave). 

El pseudocódigo de lo que hará esta función (muy parecido a lo que haremos en Pascal) sería:

procedimiento dibujar_sprite(imagen, posicX, posicY);
  para i = 1 hasta ANCHOSPRITE
    para j = 1 hasta ALTOSPRITE 
      si imagen[j,i] <> 0 entonces
        dibujar_punto(x+i-1, y+j-1, imagen[j,i]);

Simplemente dibujamos todos los puntos menos los que sean del color que hayamos considerado transparente (en nuestro caso, el 0).

Y los sprites los vamos a crear desde dentro de nuestro programa (más adelante veremos cómo leer imágenes desde ficheros). Simplemente será un array de dos dimensiones, que indicará el color de cada uno de los puntos que forman la figura:

spriteLadrillo =
   {{0,2,2,2,2,2,2,2,2,0},
    {2,1,1,1,1,1,1,1,1,2},
    {2,1,1,1,1,1,1,1,1,2},
    {2,1,1,1,1,1,1,1,1,2},
    {2,1,1,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,1,1,1,1,1,3,3,2,2},
    {2,2,2,2,2,2,2,2,2,0}
   };

El resto del fuente es idéntico al que hicimos en el apartado anterior. La apariencia que buscamos (algo mejor que la anterior, pero todavía nada espectacular) es


8.2 Miniserpiente 2 en C. 

La única diferencia importante con la versión 1 es el manejo de los sprites:
 

/*----------------------------*/
/*  Intro a la programac de   */
/*  juegos, por Nacho Cabanes */
/*                            */
/*    ipj08c.c                */
/*                            */
/*  Octavo ejemplo: juego de  */
/*  "miniSerpiente" (aprox B) */
/*                            */
/*  Comprobado con:           */
/*  - MinGW DevStudio 2.05    */
/*    (gcc 3.4.2) y Allegro   */
/*    4.03, Windows 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 tamaño 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  33
 
char mapa[MAXFILAS][MAXCOLS]={
      "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "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    XXXX",
      "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",
      "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
};
 
int numFrutas = 8;
 
  /* Nuestros sprites */
BITMAP *ladrilloFondo, *comida, *jugador;
 
 
typedef
  char tipoSprite[ANCHOSPRITE][ALTOSPRITE];
                         /* El sprite en si: matriz de 10x10 bytes */
 
tipoSprite spriteLadrillo =
   {{0,2,2,2,2,2,2,2,2,0},
    {2,1,1,1,1,1,1,1,1,2},
    {2,1,1,1,1,1,1,1,1,2},
    {2,1,1,1,1,1,1,1,1,2},
    {2,1,1,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,1,1,1,1,1,3,3,2,2},
    {2,2,2,2,2,2,2,2,2,0}
   };
 
tipoSprite spriteComida =
   {{0,0,0,2,0,0,0,0,0,0},
    {0,0,2,2,0,0,2,2,0,0},
    {0,4,4,4,2,2,4,4,0,0},
    {4,4,4,4,4,2,4,4,4,0},
    {4,4,4,4,4,4,4,4,4,0},
    {4,4,4,4,4,4,4,4,4,0},
    {4,4,4,4,4,4,4,4,4,0},
    {4,4,4,4,4,4,4,4,4,0},
    {0,4,4,4,4,4,4,4,0,0}
   };
 
tipoSprite spriteJugador =
   {{0,0,3,3,3,3,3,0,0,0},
    {0,3,1,1,1,1,1,3,0,0},
    {3,1,1,1,1,1,1,1,3,0},
    {3,1,1,1,1,1,1,1,3,0},
    {3,1,1,1,1,1,1,1,3,0},
    {3,1,1,1,1,1,1,1,3,0},
    {0,3,1,1,1,1,1,3,0,0},
    {0,0,3,3,3,3,3,0,0,0}
   };
 
/* -------------- Rutina de crear los sprites ------------- */
 
 
void creaSprites()
{
  int i, j;
 
  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]  ]);
 
  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]  ]);
 
  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()
{
 
  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();
    draw_sprite (screen, jugador, posX*ESCALA, posY*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;
      }
    }
 
 
    /* 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;
 
    /* Pequeña pausa antes de seguir */
    rest ( PAUSA );
 
  }
  while (TRUE);  /* Repetimos indefininamente */
                 /* (la condición de salida la comprobamos "dentro") */
 
  readkey();
  return 0;
 
}
 
                     /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();
 




8.3. Miniserpiente 2 en Pascal.

Similar, pero con la rutina de dibujar sprites desarrollada, y con la misma apariencia:



(*----------------------------*)
(*  Intro a la programac de   *)
(*  juegos, por Nacho Cabanes *)
(*                            *)
(*    IPJ08P.PAS              *)
(*                            *)
(*  Octavo ejemplo: juego de  *)
(*  'miniSerpiente'2          *)
(*                            *)
(*  Comprobado con:           *)
(*  - FreePascal 1.06 (Dos)   *)
(*  - FreePascal 2.0 -Windows *)
(*----------------------------*)
 
uses graph, crt;
  (* Cambiar por "uses wincrt, ..." bajo Windows *)
 
  (* Posiciones X e Y iniciales *)
const
  POS_X_INI = 17;
  POS_Y_INI = 11;
 
  INC_X_INI  = 1;
  INC_Y_INI  = 0;
 
  (* Pausa en milisegundos entre un 'fotograma' y otro *)
  PAUSA = 350;
 
  (* Teclas predefinidas *)
  TEC_ARRIBA = 'E';
  TEC_ABAJO  = 'X';
  TEC_IZQDA  = 'S';
  TEC_DCHA   = 'D';
 
var
  posX, posY: word;     (* Posicion actual *)
  incX, incY: integer;  (* Incremento de la posicion *)
 
  (* Terminado: Si ha chocado o comida todas las frutas *)
  terminado: boolean;
 
  (* La tecla pulsada *)
  tecla: char;
 
  (* Escala: relacion entre tamao de mapa y de pantalla *)
const
  ESCALA = 10;
 
  (* Ancho y alto de los sprites *)
  ANCHOSPRITE = 10;
  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 *)
  MAXFILAS = 20;
  MAXCOLS = 32;
 
  mapa: array[1..MAXFILAS, 1..MAXCOLS] of char = (
  'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
  '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     XXXX',
  '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',
  'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
  );
 
 
const numFrutas:word  = 8;
 
 
  (* Nuestros sprites *)
 
type
  tipoSprite = array[1..ANCHOSPRITE,1..ALTOSPRITE] of byte;
                         (* El sprite en si: matriz de 10x10 bytes *)
 
const spriteLadrillo: tipoSprite =
   ((0,2,2,2,2,2,2,2,2,0),
    (2,1,1,1,1,1,1,1,1,2),
    (2,1,1,1,1,1,1,1,1,2),
    (2,1,1,1,1,1,1,1,1,2),
    (2,1,1,1,1,1,1,1,1,2),
    (2,1,1,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,1,1,1,1,1,3,3,2,2),
    (2,2,2,2,2,2,2,2,2,0)
   );
 
 const spriteComida: tipoSprite =
   ((0,0,0,2,0,0,0,0,0,0),
 
    (0,0,2,2,0,0,2,2,0,0),
    (0,4,4,4,2,2,4,4,0,0),
    (4,4,4,4,4,2,4,4,4,0),
    (4,4,4,4,4,4,4,4,4,0),
    (4,4,4,4,4,4,4,4,4,0),
    (4,4,4,4,4,4,4,4,4,0),
    (4,4,4,4,4,4,4,4,4,0),
    (4,4,4,4,4,4,4,4,4,0),
    (0,4,4,4,4,4,4,4,0,0)
   );
 
const spriteJugador: tipoSprite =
   ((0,0,3,3,3,3,3,0,0,0),
    (0,3,1,1,1,1,1,3,0,0),
    (3,1,1,1,1,1,1,1,3,0),
    (3,1,1,1,1,1,1,1,3,0),
    (3,1,1,1,1,1,1,1,3,0),
    (3,1,1,1,1,1,1,1,3,0),
    (3,1,1,1,1,1,1,1,3,0),
    (3,1,1,1,1,1,1,1,3,0),
    (0,3,1,1,1,1,1,3,0,0),
    (0,0,3,3,3,3,3,0,0,0)
   );
 
 
(* -------------- Rutina de dibujar sprites -------------- *)
(* Simplemente dibuja un sprite definido anteriormente.    *)
(* Copia la sintaxis de "draw_sprite" de Allegro, pero es  *)
(* una rutina lenta, que solo usa Pascal puro, no seria    *)
(* adecuada en programas con muchos sprites                *)
procedure draw_sprite(imagen: tipoSprite; x, y: word);
var
  i,j: word;
begin
  for i := 1 to ANCHOSPRITE do
    for j := 1 to ALTOSPRITE do
    begin
    if imagen[j,i] <> 0 then
      putpixel(x+i-1, y+j-1, imagen[j,i]);
    end;
 
end;
 
 
 
(* -------------- Rutina de dibujar el fondo ------------- *)
 
procedure dibujaFondo;
var
  i, j: word;
begin
 
  clearDevice;
 
    for i:= 1 to MAXCOLS do
    for j := 1 to MAXFILAS do
      begin
      if mapa[j,i] = 'X' then
        draw_sprite(spriteLadrillo, (i-1)*ESCALA, (j-1)*ESCALA);
      if mapa[j,i] = 'F' then
        draw_sprite(spriteComida, (i-1)*ESCALA, (j-1)*ESCALA);
      end;
 
end;
 
 
(* ------------------------------------------------ *)
(*                                                  *)
(* -------------- Cuerpo del programa ------------- *)
var
  gd,gm, error : integer;
 
 
BEGIN
  gd := D8bit;
  gm := m320x200;
  initgraph(gd, gm, '');
   (* Si falla bajo Windows, probar gd:=0; gm:=0; *)
 
                               (* Intentamos entrar a modo grafico *)
  error := graphResult;
  if error <> grOk then
    begin
    writeLn('No se pudo entrar a modo grafico');
    writeLn('Error encontrado: '+ graphErrorMsg(error) );
    halt(1);
    end;
 
 
  (* ----------------------- Si todo ha ido bien: empezamos *)
 
  dibujaFondo;
 
            (* Valores iniciales *)
  posX := POS_X_INI;
  posY := POS_Y_INI;
 
  incX := INC_X_INI;
  incY := INC_Y_INI;
 
 
            (* Parte repetitiva: *)
  repeat
    dibujaFondo;
    draw_sprite (spriteJugador, (posX-1)*ESCALA, (posY-1)*ESCALA);
 
    terminado := FALSE;
 
    (* Si paso por una fruta: la borro y falta una menos *)
    if (mapa[posY,posX] = 'F') then
      begin
      mapa[posY,posX] := ' ';
      numFrutas := numFrutas - 1;
      if (numFrutas = 0) then
      begin
        setColor(14);
        outTextXY( 100, 90, 'Ganaste!' );
        terminado := TRUE;
      end;
    end;
 
 
    (* Si choco con la pared, se acabo *)
    if (mapa[posY,posX] = 'X') then
      begin
      setColor(13);
      outTextXY( 100, 90, 'Chocaste!' );
      terminado := TRUE;
    end;
 
    if terminado then break;
 
    (* Compruebo si se ha pulsado alguna tecla *)
    if keypressed then
        begin
        tecla := upcase(readkey);
 
        case tecla of
          TEC_ARRIBA:
              begin incX :=  0; incY := -1;  end;
          TEC_ABAJO:
              begin incX :=  0; incY :=  1;  end;
          TEC_IZQDA:
              begin incX := -1; incY :=  0;  end;
          TEC_DCHA:
              begin incX :=  1; incY :=  0;  end;
        end;
 
    end;
 
    posX := posX + incX;
    posY := posY + incY;
 
    (* Pequea pausa antes de seguir *)
    delay ( PAUSA );
 
  until FALSE;  (* Repetimos indefininamente *)
                 (* (la condicin de salida la comprobamos 'dentro') *)
 
  readkey;
  closegraph;
end.
 



8.4. Miniserpiente 2 en Java.

Al igual que en las versiones en C y Pascal, los únicos cambios en Java se refieren a que en pantalla dibujaremos un Sprite en vez de una letra y a que debemos crear esos Sprites.

De hecho, en Java tenemos incluso un componente llamado Image (imagen), que es el equivalente directo de nuestro Sprite. La única complicación es que resulta muy fácil crear un Image a partir de un fichero de imagen, pero es bastante más difícil crearlo a partir de un "array", como hemos hecho en el caso de C y Pascal. Aun así, veamos los pasos:

Crearemos un "array" que contenga la información de los colores de cada punto:

int spriteLadrillo[] =
   {0,2,2,2,2,2,2,2,2,0,
    2,1,1,1,1,1,1,1,1,2,
    2,1,1,1,1,1,1,1,1,2,
    2,1,1,1,1,1,1,1,1,2,
    2,1,1,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,1,1,1,1,1,3,3,2,2,
    2,2,2,2,2,2,2,2,2,0
   };

Pero en Java no existe "paleta de colores Pc estándar" ;-), así que tendremos que convertir esos colores antes de pasarlos al objeto Image. Por ejemplo, el color 1 querremos que sea azul, así que tendremos que darle el nuevo valor (255 << 24) | 255 (son 4 bytes, el primero es el canal Alpha, que deberá ser 255 para que nuestra figura sea totalmente opaca; los tres siguientes son RGB (rojo, verde y azul, en ese orden), en nuestro caso 0, 0,255. De igual modo, el color 2, que será el verde, debería ser (255 << 24) | (255 << 8), el color 4, rojo, sería (255 << 24) | (255 << 16), y el color 3, un azul claro (cyan) podría ser algo como (255 << 24) | (127 << 16) | (127 << 8) | 255. 

Por tanto, para "corregir" los colores de cada punto haríamos algo como:

    for(i=0; i<ANCHOSPRITE; i++)
      for (j=0; j<ALTOSPRITE; j++)
      {
        // El color 1 sera azul
        if (spriteLadrillo[i+j*ANCHOSPRITE]==1) 
            spriteLadrillo[i+j*ANCHOSPRITE] = (255 << 24) | 255;
        // El color 2 sera verde
        if (spriteLadrillo[i+j*ANCHOSPRITE]==2) 
            spriteLadrillo[i+j*ANCHOSPRITE] = (255 << 24) | (255 << 8);
        // El color 3 sera cyan
        if (spriteLadrillo[i+j*ANCHOSPRITE]==3) 
            spriteLadrillo[i+j*ANCHOSPRITE] = (255 << 24) | (127 << 16) | (127 << 8) | 255;
        // El color 4 sera rojo
        if (spriteLadrillo[i+j*ANCHOSPRITE]==4) 
            spriteLadrillo[i+j*ANCHOSPRITE] = (255 << 24) | (255 << 16);

Y ya sólo nos queda pasar toda esa información al componente "Image", que se hace usando un "MemoryImageSource", al que le indicamos el ancho y el alto y desde dónde queremos leer los datos:

ladrilloFondo = createImage(new MemoryImageSource(ANCHOSPRITE, ALTOSPRITE,
       spriteLadrillo, 0, ANCHOSPRITE));

Para dibujar sí que no hay complicación

if (mapa[j].charAt(i) == 'X')
        g.drawImage(ladrilloFondo, i*ESCALA, j*ESCALA, this);

La apariencia sería esta

Y el fuente podría ser:

/*----------------------------*/
/*  Intro a la programac de   */
/*  juegos, por Nacho Cabanes */
/*                            */
/*    ipj08j.java             */
/*                            */
/*  Octavo ejemplo: juego de  */
/*  "miniSerpiente" (aprox B) */
/*                            */
/*  Comprobado con:           */
/*  - JDK 1.5.0               */
/*----------------------------*/
 
import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
 
 
public class ipj08j extends Applet
    implements Runnable, KeyListener
{
    // Posiciones X e Y iniciales
    final int POS_X_INI = 16;
    final int POS_Y_INI = 10;
 
    final int INC_X_INI = 1;
    final int INC_Y_INI = 0;
 
    // Pausa en milisegundos entre un "fotograma" y otro
    final int PAUSA = 350;
 
    // Ahora las imagenes de cada elemento
    Image ladrilloFondo;
    Image comida;
    Image jugador;
 
    // Escala: relacion entre tamaño de mapa y de pantalla
    final int ESCALA = 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
    final int MAXFILAS = 20;
    final int MAXCOLS  = 32;
 
    String mapa[]={
      "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "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    XXXX",
      "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",
      "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    };
 
  /* Ancho y alto de los sprites */
final int ANCHOSPRITE = 10;
final int ALTOSPRITE  = 9;
 
 
int spriteLadrillo[] =
   {0,2,2,2,2,2,2,2,2,0,
    2,1,1,1,1,1,1,1,1,2,
    2,1,1,1,1,1,1,1,1,2,
    2,1,1,1,1,1,1,1,1,2,
    2,1,1,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,1,1,1,1,1,3,3,2,2,
    2,2,2,2,2,2,2,2,2,0
   };
 
int spriteComida []=
   {0,0,0,2,0,0,0,0,0,0,
    0,0,2,2,0,0,2,2,0,0,
    0,4,4,4,2,2,4,4,0,0,
    4,4,4,4,4,2,4,4,4,0,
    4,4,4,4,4,4,4,4,4,0,
    4,4,4,4,4,4,4,4,4,0,
    4,4,4,4,4,4,4,4,4,0,
    4,4,4,4,4,4,4,4,4,0,
    0,4,4,4,4,4,4,4,0,0
   };
 
int spriteJugador []=
   {0,0,3,3,3,3,3,0,0,0,
    0,3,1,1,1,1,1,3,0,0,
    3,1,1,1,1,1,1,1,3,0,
    3,1,1,1,1,1,1,1,3,0,
    3,1,1,1,1,1,1,1,3,0,
    3,1,1,1,1,1,1,1,3,0,
    3,1,1,1,1,1,1,1,3,0,
    0,3,1,1,1,1,1,3,0,0,
    0,0,3,3,3,3,3,0,0,0
   };
 
 
    Thread hilo = null;   // El "hilo" de la animacion
 
    int posX, posY;  // Posicion actual
    int incX, incY;  // Incremento de la posicion
 
    // Terminado: Si ha chocado o comido todas las frutas
    boolean terminado;
 
    int tecla;      // La tecla pulsada
 
    // Y las teclas por defecto
    final char TEC_ARRIBA = 'e';
    final char TEC_ABAJO  = 'x';
    final char TEC_IZQDA  = 's';
    final char TEC_DCHA   = 'd';
 
    int numFrutas = 8;
 
    // Rutina de crear los sprites
     void creaSprites()
    {
    int i, j;
 
    // Doy colores más adecuados a cada figura: ladrillos
    for(i=0; i<ANCHOSPRITE; i++)
      for (j=0; j<ALTOSPRITE; j++)
      {
        // El color 1 sera azul
        if (spriteLadrillo[i+j*ANCHOSPRITE]==1)
            spriteLadrillo[i+j*ANCHOSPRITE] = (255 << 24) | 255;
        // El color 2 sera verde
        if (spriteLadrillo[i+j*ANCHOSPRITE]==2)
            spriteLadrillo[i+j*ANCHOSPRITE] = (255 << 24) | (255 << 8);
        // El color 3 sera cyan
        if (spriteLadrillo[i+j*ANCHOSPRITE]==3)
            spriteLadrillo[i+j*ANCHOSPRITE] = (255 << 24) | (127 << 16) | (127 << 8) | 255;
        // El color 4 sera rojo
        if (spriteLadrillo[i+j*ANCHOSPRITE]==4)
            spriteLadrillo[i+j*ANCHOSPRITE] = (255 << 24) | (255 << 16);
      }
    // y al final los asigno
    ladrilloFondo = createImage(new MemoryImageSource(ANCHOSPRITE, ALTOSPRITE,
       spriteLadrillo, 0, ANCHOSPRITE));
 
    // Lo mismo para la comida
    for(i=0; i<ANCHOSPRITE; i++)
      for (j=0; j<ALTOSPRITE; j++)
      {
        // El color 1 sera azul
        if (spriteComida[i+j*ANCHOSPRITE]==1)
            spriteComida[i+j*ANCHOSPRITE] = (255 << 24) | 255;
        // El color 2 sera verde
        if (spriteComida[i+j*ANCHOSPRITE]==2)
            spriteComida[i+j*ANCHOSPRITE] = (255 << 24) | (255 << 8);
        // El color 3 sera cyan
        if (spriteComida[i+j*ANCHOSPRITE]==3)
            spriteComida[i+j*ANCHOSPRITE] = (255 << 24) | (127 << 16) | (127 << 8) | 255;
        // El color 4 sera rojo
        if (spriteComida[i+j*ANCHOSPRITE]==4)
            spriteComida[i+j*ANCHOSPRITE] = (255 << 24) | (255 << 16);
      }
      comida = createImage(new MemoryImageSource(ANCHOSPRITE, ALTOSPRITE,
        spriteComida, 0, ANCHOSPRITE));
 
    // Y lo mismo para el jugador
    for(i=0; i<ANCHOSPRITE; i++)
      for (j=0; j<ALTOSPRITE; j++)
      {
        // El color 1 sera azul
        if (spriteJugador[i+j*ANCHOSPRITE]==1)
            spriteJugador[i+j*ANCHOSPRITE] = (255 << 24) | 255;
        // El color 2 sera verde
        if (spriteJugador[i+j*ANCHOSPRITE]==2)
            spriteJugador[i+j*ANCHOSPRITE] = (255 << 24) | (255 << 8);
        // El color 3 sera cyan
        if (spriteJugador[i+j*ANCHOSPRITE]==3)
            spriteJugador[i+j*ANCHOSPRITE] = (255 << 24) | (127 << 16) | (127 << 8) | 255;
        // El color 4 sera rojo
        if (spriteJugador[i+j*ANCHOSPRITE]==4)
            spriteJugador[i+j*ANCHOSPRITE] = (255 << 24) | (255 << 16);
      }
      jugador = createImage(new MemoryImageSource(ANCHOSPRITE, ALTOSPRITE,
         spriteJugador, 0, ANCHOSPRITE));
    }
 
 
    // Inicializacion
    public void init() {
 
        // Valores iniciales
        posX = POS_X_INI;
        posY = POS_Y_INI;
 
        incX = INC_X_INI;
        incY = INC_Y_INI;
 
        terminado = false;
 
        requestFocus();
        addKeyListener(this);
        creaSprites();
    }
 
 
    // Escritura en pantalla
    public void paint(Graphics g) {
 
        int i, j;
 
        // Primero borro el fondo en negro
        g.setColor( Color.black );
        g.fillRect( 0, 0, 639, 479 );
 
        // Ahora dibujo paredes y comida
        for(i=0; i<MAXCOLS; i++)
          for (j=0; j<MAXFILAS; j++) {
            if (mapa[j].charAt(i) == 'X')
    g.drawImage(ladrilloFondo, i*ESCALA, j*ESCALA, this);
            if (mapa[j].charAt(i) == 'F')
    g.drawImage(comida, i*ESCALA, j*ESCALA, this);
        }
 
        // Finalmente, el jugador
        g.drawImage(jugador, posX*ESCALA, posY*ESCALA, this);
 
        // Si no quedan frutas, se acabo
        g.setColor( Color.yellow );
        if (numFrutas == 0) {
            g.drawString("Ganaste!", 100, 90);
        terminado = true;
        }
 
        // Si choco con la pared, se acabo
        g.setColor( Color.magenta );
        if (mapa[posY].charAt(posX) == 'X') {
            g.drawString("Chocaste!", 100, 90);
            terminado = true;
        }
 
        if (terminado) hilo=null;
    }
 
    // La rutina que comienza el "Thread"
    public void start() {
  hilo = new Thread(this);
  hilo.start();
    }
 
    // La rutina que para el "Thread"
    public synchronized void stop() {
  hilo = null;
    }
 
    // Y lo que hay que hacer cada cierto tiempo
    public void run() {
        Thread yo = Thread.currentThread();
        while (hilo == yo) {
            try {
                Thread.sleep(PAUSA);
            } catch (InterruptedException e){
            }
            posX += incX;
            posY += incY;
 
            // Si paso por una fruta: la borro y falta una menos
            if (mapa[posY].charAt(posX) == 'F') {
                // La borro en el mapa
                StringBuffer temp = new StringBuffer(mapa[posY]);
                temp.setCharAt(posX, ' ');
                mapa[posY] = temp.toString();
                // y en el contador
                numFrutas --;
                }
 
            // En cualquier caso, redibujo
            repaint();
        }
    }
 
 
    // Comprobacion de teclado
    public void keyTyped(KeyEvent e) {
        tecla=e.getKeyChar();
        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;
        }
        repaint();
        e.consume();
    }
 
    public void keyReleased(KeyEvent e) {
    }
 
    public void keyPressed(KeyEvent e) {
    }
 
}