7. Mapas. Cuarto juego (aproximación "a"): MiniSerpiente 1.

Contenido de este apartado:


7.1. Ideas generales.

El juego de la serpiente es casi tan sencillo de crear como el de las motos de luz: una figura va avanzando por la pantalla; si choca con la pared exterior, con su propia "cola" o con algún otro obstáculo, muere. La única complicación adicional es que en la mayoría de versiones de este juego también va apareciendo comida, que debemos atrapar; esto nos dará puntos, pero también hará que la serpiente sea más grande, y por eso, más fácil chocar con nuestra propia cola.

Esta novedad hace que sea algo más difícil de programar. En primer lugar porque la serpiente va creciendo, y en segundo lugar porque puede que en una posición de la pantalla exista un objeto contra el que podemos chocar pero no debemos morir, sino aumentar nuestra puntuación.

Podríamos volver a usar el "truco" de mirar en los puntos de la pantalla, y distinguir la comida usando un color distinto al de los obstáculos. Pero esto no es lo que se suele hacer. No serviría si nuestro fondo fuera un poco más vistoso, en vez de ser una pantalla negra. En lugar de eso, es más cómodo memorizar un "mapa" con las posibles posiciones de la pantalla, e indicando cuales están vacías, cuales ocupadas por obstáculos y cuales ocupadas por comida.

Podría ser algo así:


 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 
 X                        X     X 
 X     X                  X     X 
 X   XXX    F     X       X     X 
 X                X             X 
 X                X      F      X 
 X       X        X             X 
 X   F   X              XXX     X 
 X       X                      X 
 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 

En este ejemplo, las X serían las casillas "peligrosas", con las que no debemos chocar si no queremos morir; las F serían las frutas que podemos recoger para obtener puntos extra.

El hecho de consultar este mapa en vez del contenido de la pantalla nos permite dibujar esos obstáculos, esas frutas, el fondo y nuestra propia serpiente con otras imágenes más vistosas.

Vamos a aplicarlo. Tomaremos la base del juego de las motos de luz, porque la idea básica coincide: el objeto se debe seguir moviendo aunque no toquemos ninguna tecla, y en cada paso se debe comprobar si hemos chocado con algún obstáculo. A esta base le añadiremos el uso de un mapa, aunque todavía será poco vistoso... 

La apariencia del juego, todavía muy pobre, será:


7.2 Miniserpiente 1 en C.

Sencillito. Muy parecido al anterior, pero no miramos en pantalla sino en nuestro "mapa":
 

/*----------------------------*/
/*  Intro a la programac de   */
/*  juegos, por Nacho Cabanes */
/*                            */
/*    ipj07c.c                */
/*                            */
/*  Septimo ejemplo: juego de */
/*  "miniSerpiente"           */
/*                            */
/*  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    */
/*----------------------------*/
 
#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 = 7;
 
  /* 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,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;
 
    /* 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();
 





7.3. Miniserpiente 1 en Pascal.

El único comentario es que en Free Pascal no existen rutinas incorporadas para manejo de Sprites. Se podrían imitar usando las órdenes "getImage" y "putImage", de la librería Graph, pero nosotros lo haremos directamente imitando la idea básica del funcionamiento de un sprite: para cada punto de la figura, se dibuja dicho punto en caso de que no sea transparente (color 0). Además lo haremos en Pascal puro, sin usar ni siquiera lenguaje ensamblador, que haría nuestro programa más rápido pero también menos legible. El resultado es que nuestro programa será mucho más lento que si tuviéramos rutinas preparadas para manejar Sprites o si empleáramos ensamblador, a cambio de que sea muy fácil de entender.


(*----------------------------*)
(*  Intro a la programac de   *)
(*  juegos, por Nacho Cabanes *)
(*                            *)
(*    ipj07p.pas              *)
(*                            *)
(*  Septimo ejemplo: juego de *)
(*  'miniSerpiente'           *)
(*                            *)
(*  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 = (
  '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'
  );
 
 
const numFrutas:word  = 8;
 
  (* Nuestros sprites *)
(*BITMAP *ladrilloFondo, *comida, *jugador;###*)
 
 
type
  tipoSprite = array[1..ANCHOSPRITE,1..ALTOSPRITE] of byte;
                         (* El sprite en si: matriz de 30x30 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, '');
 
                               (* 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.
 




7.4. Miniserpiente 1 en Java.

Eso de que el juego no se pare... en Java es algo más complicado: deberemos usar un "Thread", un hilo, que es la parte que sí podremos parar cuando queramos, durante un cierto tiempo o por completo. Vamos a resumir los cambios más importantes:

  • En la declaración de la clase deberemos añadir "implements Runnable"
  • Tendremos nuevos métodos (funciones): "start" pondrá en marcha la ejecución del hilo, "stop" lo parará, y "run" indicará lo que se debe hacer durante la ejecución del hilo.
  • Dentro de este "run" haremos la pausa ("sleep") que necesitamos en cada "fotograma" del juego, y redibujaremos después, así:
  try {
Thread.sleep(PAUSA);
} catch (InterruptedException e){
}

Los demás cambios, que no son muchos, son los debidos a la forma de trabajar de los Applets, que ya conocemos (funciones init, paint y las de manejo de teclado), y las propias del lenguaje Java (por ejemplo, va a ser más cómodo definir el mapa como un array de Strings que como un array bidimensional de caracteres).

La apariencia será casi idéntica a las anteriores:

/*----------------------------*/ 
/*  Intro a la programac de   */ 
/*  juegos, por Nacho Cabanes */ 
/*                            */ 
/*    ipj07j.java             */ 
/*                            */ 
/*  Septimo ejemplo: juego de */
/*  "miniSerpiente" (aprox A) */ 
/*                            */ 
/*  Comprobado con:           */ 
/*  - JDK 1.5.0               */  
/*----------------------------*/ 
 
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
 
 
public class ipj07j 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
    final String LADRILLO = "#";
    final String COMIDA = "X";
    final String JUGADOR ="O";
 
    // Escala: relacion entre tamao 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"
    };
 
    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;
 
 
    // 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);
    }
 
 
    // 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++) {
              g.setColor( Color.blue );
              if (mapa[j].charAt(i) == 'X')
                g.drawString(LADRILLO, i*ESCALA, j*ESCALA+10);
              g.setColor( Color.green );
              if (mapa[j].charAt(i) == 'F')
                g.drawString(COMIDA, i*ESCALA, j*ESCALA+10);
          }
 
        // Finalmente, el jugador
        g.setColor( Color.white );
        g.drawString(JUGADOR, posX*ESCALA, posY*ESCALA+10);
 
        // 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) {
    }
}