TLlop - Clon del Tetris

Llop Site Home > JAVA > TLlop > Cuadrícula

Cuadrícula:

Clase PanelCuadricula

package ejercicio_5;

import java.lang.*;
import java.awt.*;
import javax.swing.*;

/** Título: TLlop
    Descripción: Prácticas capítulo 11, ejercicio 5
    Copyright: Copyright (c) 2005
    Empresa: Casa_Llop
    @author Albert_Lobo
    @version 1.5  */

/** Clase que extiende 'JPanel' e implementa la interfaz 'Runnable'. 

    En realidad, es como una pantalla de tele porque se refresca contínuamente (gracias al método
    'run ()' que ejecuta su hilo). Este panel en el juego se utiliza como: 
	
        1 - Cuadrícula de juego - Esta cuadrícula tiene tres filas más por encima de las que se muestran
                              en el panel, para dejar un pequeño espacio donde las nuevas piezas se pegan
                              antes de salir a la vista. Sus dimensiones son variables -las puede elejir
                              el jugador. Puede bajar a plomo los bloques que quedan por encima de unas
                              líneas completadas (gravedad real), o limitarse a quitar esas líneas.
                              Puede también mostrar una animación en que explotan las líneas completas,
                              una en que un texto crece, y otra en que mengua. 
        2 - Cuadrícula con la siguiente pieza - El tamaño de ésta es fijo: 6 x 6, para que todas las
                                                 piezas estén anchas. Se escalará a la mitad de su
                                                 tamaño real por cuestión de presentación. 
												 
    Proporciona métodos para conocer y definir casi todas sus variables. 
	
    Contiene una tabla de iconos de 2 dimensiones (alto y ancho) que corresponde a la propia cuadrícula
    de juego; esta tabla se rellena con los iconos de las piezas durante la partida. Lógicamente,
    la clase también tiene métodos para 'limpiar' la tabla, saber si hay líneas llenas, cuántas hay,
    y cómo quitarlas -especial mención merece la 'gravedadReal', que permite tratar individualmente
    los bloques resultantes de un reventón de líneas, y depositarlos lo más abajo posible.
	
    Las dimensiones de este panel se calculan con el número de cuadradillos a lo ancho, y a lo alto;
    como el jugador puede definir estas dimensiones, la cuadrícula puede tener tamaños y proporciones
    muy diferentes. Para 'estandarizar', en la medida de lo posible, el tamaño del panel, se escala
    a la hora de pintarlo -así nunca quedará muy pequeño o demasiado grande. 
	
    La función que pinta este componente, gracias al hilo que refresca constantemente el panel,
    puede mostrar tres animaciones: 'líneas reventando', 'texto creciente', y 'texto menguante'
    (para los mensajes 'PAUSA', GAMOE OVER'...). */
public class PanelCuadricula extends JPanel implements Runnable
{
  /** Cadena constante con el nobre del archivo que contiene el fondo de cuadrícula 1. */
  public final static String FONDO_CUADRICULA1 = "fondoCuadricula1.jpg";
  /** Cadena constante con el nobre del archivo que contiene el fondo de cuadrícula 2. */
  public final static String FONDO_CUADRICULA2 = "fondoCuadricula2.jpg";
  /** Cadena constante con el nobre del archivo que contiene el fondo de cuadrícula 3. */
  public final static String FONDO_CUADRICULA3 = "fondoCuadricula3.jpg";
  /** Cadena constante con el nobre del archivo que contiene el fondo de cuadrícula 4. */
  public final static String FONDO_CUADRICULA4 = "fondoCuadricula4.jpg";

  /** Constante que representa el mensaje "Jugar", que mengua al pintarse en el panel. */
  public final static byte JUGAR = 0;
  /** Constante que representa el mensaje "Pausa" que crece al pintarse en el panel. */
  public final static byte PAUSA = 1;
  /** Constante que representa el mensaje "Pausa" que mengua al pintarse en el panel. */
  public final static byte DESPAUSA = 2;
  /** Constante que representa el mensaje "Game Over", que crece al pintarse en el panel. */
  public final static byte GAME_OVER = 3;
  /** Constante que representa el mensaje "¡'X' líneas!", que crece al pintarse en el panel. */
  public final static byte LINEAS = 4;

  /** Constante con el número de píxels de lado de los iconos que forman las piezas. */
  public final static byte LADO_CUADRADILLO = 20;
  /** Constante con el número de píxels del margen del panel. */
  public final static byte MARGEN = 10;

  // Variables de clase.
  // Número de cuadradillos a lo ancho de la cuadrícula.
  private byte anchura;
  // Número de cuadradillos a lo alto de la cuadrícula.
  private byte altura;
  // Número de filas ocultas sobre la parte visible de la cuadrícula.
  private byte recorteSuperior;
  // Cadena con el nombre del diseño de las piezas. Es necesaria para que los cuadradillos de relleno
  // -por las 'lineasRellenasInicio'- vayan a juego con los de las piezas.
  private String disenoPieza;
  // Array con el nombre de todos los colores disponibles para las piezas. De nuevo, es necesario para
  // dar un poco de variedad a las 'lineasRellenasInicio'.
  private String coloresDisponibles [];
  // Ruta absoluta de la imagen de fondo de la cuadrícula.
  private String fondo;
  // Imagen de fondo del panel.
  private Image imagenFondo;
  // Objeto para pausar la ejecución del programa mientras las imágenes se escalan.
  private MediaTracker seguidorEstado;
  // Valor identificador del mensaje que se puede pintar en el panel. Corresponde a uno de los enteros
  // constantes de esta clase.
  private byte mensaje;
  // Cadena con el texto real que debe poderse pintar en el panel.
  private String textoMensaje;
  // Cadena para un texto de felicitación adicional que se puede pintar -pero para verlo hay que reventar
  // al menos 4 líneas.
  private String textoFelicitacion;
  // La 'tabla de iconos' que tiene las mismas 'dimensiones en cuadradillos' que el panel (aparte de
  // las filas de 'recorteSuperior'). El panel se pinta con estos iconos.
  private ImageIcon tablaCuadradillos [][];
  // Tabla de bytes del tamaño de 'tablaCuadradillos'. Cuando está activa la gravedad real, este array
  // agrupa los distintos 'bloques' de cuadradillos (identificando las posiciones de un mismo bloque
  // con el mismo valor). De esta forma puede saber cuáles pueden bajar, y cuáles no.
  private byte tablaByte [][];
  // Entero que marca la primera fila que se ha completado en un reventón de líneas.
  private byte filaPivote;

  // Hilo que se encargará del refresco constante del panel.
  private Thread hiloPanel;
  // Variable para saber si debe aplicarse la gravedad real al tratar las líneas completadas.
  private boolean gravedadReal;
  // Variable que decide si el panel debe refrescarse constantemente o no.
  private boolean refrescoConstante;
  // Variable para activar la animación de 'reventar líneas'.
  private boolean destruyendoPiezas;
  // Cuando esté a 'true', los cuadradillos en las líneas rellenas mostrarán un icono de explosión.
  private boolean pintaReventon;
  // Número de llamadas sucesivas a 'paint ()' en que se pinta y no se pinta la explosión en las piezas.
  private byte numeroRepeticionesExplosion;
  // Decide si el 'textoMensaje' se pinta en el panel.
  private boolean pintaMensaje;
  // Decide si el 'textoFelicitación' se pinta en el panel.
  private boolean pintaFelicitacion;
  // Tamaño de la fuente de los mensajes que se pintarán en el panel.
  private byte tamanoFuenteMensaje;

  // Número real para escalar correctamente el panel.
  private float escala;

  /** Constructor. Se pueden construir cuadrículas muy distintas; así debe ser, para que se
      pueda distinguir bien la cuadrícula de juego de la otra.
      @param ancho byte                 : Nº de cuadradillos a lo ancho.
      @param alto byte                  : Nº de cuadradillos a lo alto.
      @param recorte byte               : Nº de filas ocultas por encima de lo visible del panel.
      @param lineas byte                : Nº de líneas parcialmente rellenas con que empezar una partida.
      @param diseno String              : Nombre del diseño de las piezas.
      @param nuevoFondo String          : Nombre de la imagen de fondo del panel.
      @param nuevaGravedadReal boolean  : Define si la 'gravedad real' se utilizará. */
  public PanelCuadricula (byte ancho, byte alto, byte recorte,
                         String diseno, String nuevoFondo, boolean nuevaGravedadReal)
  {
    super ();
    setBackground (Color.white);
    /** Se inicializan las variables con los argumentos del constructor. */
    anchura = ancho;
    altura = alto;
    recorteSuperior = recorte;
    disenoPieza = diseno;
    gravedadReal = nuevaGravedadReal;
    /** Se inicializa la imagen y su ruta, el 'MediaTracker', y los colores para los cuadradillos. */
    fondo = nuevoFondo;
    imagenFondo = Tetris.getImagen (nuevoFondo, "Fondo de la cuadrícula").getImage ();
    seguidorEstado = new MediaTracker (this);
    coloresDisponibles = Pieza.getColoresDisponibles ();
    /** Estas variables se utilizarán en el método 'paint ()'. */
    pintaReventon = true;
    numeroRepeticionesExplosion = 3;
    /** Se inicializa la variable del refresco constante, y el hilo que lo ejecutará. */
    refrescoConstante = false;
    hiloPanel = new Thread (this);
    /** Se inicializa la tabla de iconos,se ajusta la escala, y se da al panel un tamaño preferido. */
    ajustarTamano ();
  }

  /** Arranca el hilo que refresca el panel. */
  public void arrancaHilo ()
  {
    if (!hiloPanel.isAlive ())
      hiloPanel.start ();
  }

  /** Vacía la tabla de iconos, y repinta el panel para que se vea. */
  public void limpia ()
  {
    for (byte y = 0; y < altura; y++)
      for (byte x = 0; x < anchura; x++)
        tablaCuadradillos [y][x] = null;
    repaint ();
  }

  /** Rellena parcialmente con iconos de color aleatorio, a partir de la primera fila, tantas filas
      de la cuadrícula como indique el argumento. Sólo debe utilizarse al empezar una nueva partida.
      @param lineasRellenasInicio byte  : Número de líneas a rellenas a medias. */
  public void ponLineasRellenas (byte lineasRellenasInicio)
  {
    for (byte y = (byte) (altura - 1); y > altura - lineasRellenasInicio - 1; y--)
      for (byte x = 0; x < anchura; x++)
        if (Math.random () < .4)
          tablaCuadradillos [y][x] = null;
        else
          tablaCuadradillos [y][x] =
              Tetris.getImagen (disenoPieza + getColorDisponible ((byte) (Math.random () * 11))
                               + ".gif", "Lleno");
    repaint ();
  }

  /** Descubre si una posición de la tabla de iconos está vacía o no.
      @param y byte    : Coordenada X de la posición a examinar.
      @param x byte    : Coordenada Y de la posición a examinar.
      @return boolean  : 'true' si la posición está vacía, 'false' si hay un icono. */
  public boolean cuadradilloVacio (byte y, byte x)
  {
    return tablaCuadradillos [y][x] == null;
  }

  /** Descubre si una fila de la tabla de iconos está llena.
      @param y byte    : Número de la fila a examinar.
      @return boolean  : 'true' si la fila está rellena de iconos, 'false' si no. */
  public boolean lineaLlena (byte y)
  {
    for (byte x = 0; x < anchura; x++)
      if (cuadradilloVacio (y, x))
        return false;
    return true;
  }

  /** Descubre si una fila de la tabla de iconos está vacía.
      @param y byte    : Número de la fila a examinar.
      @return boolean  : 'true' si la fila está vacía, y 'false' si no. */
  public boolean lineaVacia (byte y)
  {
    for (byte x = 0; x < anchura; x++)
      if (!cuadradilloVacio (y, x))
        return false;
    return true;
  }

  /** Descubre si hay alguna fila de la tabla de iconos llena.
      @return boolean  : 'true' si se encuentra alguna línea llena, y 'false' si no. */
  public boolean hayLineasLlenas ()
  {
    for (byte y = 0; y < altura; y++)
      if (lineaLlena (y))
        return true;
    return false;
  }

  /** Define el valor por el que escalar los componentes gráficos del panel para dibujarlos.
      @param nuevaEscala float  : Nueva escala para pintar el panel a medida. */
  public void setEscala (float nuevaEscala)
  {
    escala = nuevaEscala;
  }

  /** Método que calcula el valor de 'escala' para que el panel ocupe el máximo espacio posible.
      @param ancho byte  : Anchura de la cuadrícula.
      @param alto byte   : Altura de la cuadrícula.
      @return float      : Nuevo valor por el que escalar los componentes gráficos del panel. */
  public float calculaEscala (byte ancho, byte alto)
  {
    /** Este primer valor de escala pintaría el panel con un ancho aproximado de 2/3 de media pantalla. */
    float escalaAnchura = (float) (Tetris.ANCHO / 2) / (ancho * LADO_CUADRADILLO);
    /** Este segundo valor de escala pintaría el panel con un alto aproximado de 4/5 de pantalla. */
    float escalaAltura = (float) Tetris.ALTO / (alto * LADO_CUADRADILLO);
    /** El menor de los valores es el que escalará el panel para que quepa todo. */
    return (escalaAnchura > escalaAltura) ? escalaAltura : escalaAnchura;
  }

  /** Método que define el tamaño de la tabla de iconos, la de bytes, calcula la escala, y establece
      el tamaño preferido del panel. */
  public void ajustarTamano ()
  {
    tablaCuadradillos = new ImageIcon [altura][anchura];
    tablaByte = new byte [altura][anchura];
    escala = calculaEscala (anchura, altura);
    setPreferredSize (new Dimension
                     ((int) (((anchura * LADO_CUADRADILLO) + MARGEN * 2) * escala),
                      (int) ((((altura - recorteSuperior) * LADO_CUADRADILLO) + MARGEN * 2) * escala)));
  }

  /** Define el texto de felicitación por reventar 4 líneas o más de golpe.
      @param valor byte  : Nº de líneas reventadas de golpe. Determina el 'textoFelicitacion'. */
  public void setTextoFelicitacion (byte valor)
  {
    switch (valor)
    {
      case 4:
        textoFelicitacion = "¡YEAH!";
        break;
      case 5:
        textoFelicitacion = "¡BOOM!";
        break;
      case 6:
        textoFelicitacion = "¡BONG!";
        break;
      default:
        textoFelicitacion = "¡REVENTÓN!";
    }
  }

  /** Define el diseño de los cuadradillos que forman las piezas (es parte del nobre de un icono).
      @param nuevoDiseno String  : Cadena con el nombre del nuevo diseño para los cuadradillos. */
  public void setDiseno (String nuevoDiseno)
  {
    disenoPieza = nuevoDiseno;
  }

  /** Método para conocer el diseño de los iconos de las piezas.
      @return String  : Nombre del diseño actual de los cuadradillos de las piezas. */
  public String getDiseno ()
  {
    return disenoPieza;
  }

  /** Define el tamaño a lo ancho de la tabla de iconos.
      @param cuadradillos byte  : Nuevo ancho de la tabla de iconos. */
  public void setAnchura (byte cuadradillos)
  {
    anchura = cuadradillos;
  }

  /** Para conocer la dimensión X de la tabla de iconos.
      @return byte  : Anchura de la tabla de iconos. */
  public byte getAnchura ()
  {
    return anchura;
  }

  /** Define el tamaño a lo alto de la tabla de iconos.
      @param cuadradillos byte  : Nueva altura de la tabla de iconos. */
  public void setAltura (byte cuadradillos)
  {
    altura = cuadradillos;
  }

  /** Para conocer la dimensión Y de la tabla de iconos.
      @return byte  : Altura de la tabla de iconos. */
  public byte getAltura ()
  {
    return altura;
  }

  /** Permite conocer si se está utilizando la simulación de 'gravedad real' para tratar las líneas llenas.
      @return boolean  : 'true' si se está utilizando 'gravedad real', y 'false' si no. */
  public boolean getGravedadReal ()
  {
    return gravedadReal;
  }

  /** Define si la cuadrícula utilizará la simulación de 'gravedad real' a la hora de tratar líneas llenas.
      @param nuevaGravedadReal boolean  : 'true' para simular 'gravedad real', y 'false' para no hacerlo. */
  public void setGravedadReal (boolean nuevaGravedadReal)
  {
    gravedadReal = nuevaGravedadReal;
  }

  /** Esta función permite activar la animación en que revientan los cuadradillos de las filas llenas.
      @param nuevoDestruyendoPiezas boolean  : 'true' para activar la mencionada animación, 'false'
                                               para desactivarla. */
  public void setDestruyendoPiezas (boolean nuevoDestruyendoPiezas)
  {
    destruyendoPiezas = nuevoDestruyendoPiezas;
  }

  /** Mediante esta función se determina si el 'texto de mensaje' se pintará en el panel o no.
      @param nuevoPintaMensaje boolean  : 'true' para pintar el 'texto de mensaje'. */
  public void setPintaMensaje (boolean nuevoPintaMensaje)
  {
    pintaMensaje = nuevoPintaMensaje;
  }

  /** Mediante esta función se determina si el 'texto de felicitación' se pintará en el panel o no.
      @param nuevoPintaFelicitacion boolean  : 'true' para pintar el 'texto de felicitación'. */
  public void setPintaFelicitacion (boolean nuevoPintaFelicitacion)
  {
    pintaFelicitacion = nuevoPintaFelicitacion;
  }

  /** Define el tamaño de la fuente del 'texto de mensaje' -aquél que a veces salta como una animación.
      @param nuevoTamanoFuenteMensaje byte  : Nuevo tamaño de la fuente. */
  public void setTamanoFuenteMensaje (byte nuevoTamanoFuenteMensaje)
  {
    tamanoFuenteMensaje = nuevoTamanoFuenteMensaje;
  }

  /** Devuelve una cadena uno de los colores disponibles de la tabla.
      @param indice byte  : Indica el índice del color que queremos obtener de la tabla.
      @return String      : Cadena que representa uno de los colores disponibles. */
  public String getColorDisponible (byte indice)
  {
    return coloresDisponibles [indice];
  }

  /** Define el icono en una posición determinada de la tabla de iconos.
      @param y byte           : Coordenada Y de la posición del icono a definir.
      @param x byte           : Coordenada X de la posición del icono a definir.
      @param icono ImageIcon  : Nuevo icono a ubicar en la posición de marras. */
  public void setIconoCuadradillo (byte y, byte x, ImageIcon icono)
  {
    tablaCuadradillos [y][x] = icono;
  }

  /** Permite conocer la ruta de la imagen de fondo de este panel.
      @return String  : Ruta absoluta de la imagen de fondo. */
  public String getFondo ()
  {
    return fondo;
  }

  /** Define la imagen que se muestra como fondo de la cuadrícula. Además, la escala para que tenga el
      tamaño justo para encajar en el marco del panel.
      @param nuevoFondo String  Ruta absoluta de la imagen de fondo de este panel. */
  public void setFondo (String nuevoFondo)
  {
    /** Se define la variable String con la ruta del fondo, y luego se inicializa la misma imagen. */
    fondo = nuevoFondo;
    imagenFondo = Tetris.getImagen (nuevoFondo, "Fondo de la cuadrícula").getImage ()
                                .getScaledInstance (anchura * LADO_CUADRADILLO,
                                                   (altura - recorteSuperior) * LADO_CUADRADILLO,
                                                   Image.SCALE_DEFAULT);
    seguidorEstado.addImage (imagenFondo, 0);
    /** El programa no seguirá su ejecución hasta que la imagen haya sido completamente escalada. */
    try
    {
      seguidorEstado.waitForID (0);
    } catch (InterruptedException e) {}
    repaint ();
  }

  /** Permite saber si este panel se está refrescando constantemente.
      @return boolean  : 'true' si el refresco constante está activado, 'false' si no lo está. */
  public boolean getRefrescoConstante ()
  {
    return refrescoConstante;
  }

  /** Define el refresco constante de este panel. Si esta variable es 'true', el panel se repintará
      cada muy poco tiempo. De esta forma se logran efectos de animación, y la pieza destructora se
      puede identificar por el parpadeo.
      @param nuevoRefrescoConstante boolean  : Nuevo valor que tomará 'refrescoConstante'. */
  public void setRefrescoConstante (boolean nuevoRefrescoConstante)
  {
    refrescoConstante = nuevoRefrescoConstante;
  }

  /** Define la cadena de texto que se puede mostrar en el panel como mensaje.
      @param nuevoMensaje byte  : Valor correspondiente a la cadena de texto para el mensaje.
                                  Debe usarse una de las 'constantes de mensaje' de la clase. */
  public void setMensaje (byte nuevoMensaje)
  {
    /** Define la variable numérica que representa el mensaje, y según ésta define la cadena
        'textoMensaje'. */
    mensaje = nuevoMensaje;

    switch (mensaje)
    {
      case 0:
        textoMensaje = "JUGAR";
        break;
      case 1:
        textoMensaje = "PAUSA";
        break;
      case 2:
        textoMensaje = "PAUSA";
        break;
      case 3:
        textoMensaje = "GAME OVER";
        break;
      default:
        byte lineasLlenas = cuentaLineasLlenas ();
        if (lineasLlenas > 3)
          textoMensaje = "¡" + lineasLlenas + " LÍNEAS!";
    }
  }

  /** Permite conocer cuántas filas rellenas hay en la tabla de iconos.
      @return byte  : Número de líneas llenas en la cuadrícula. */
  public byte cuentaLineasLlenas ()
  {
    byte retorno = 0;
    for (byte y = 0; y < altura; y++)
      if (lineaLlena (y))
        retorno++;
    return retorno;
  }

  /** Método que quita las líneas llenas que pueda haber en la cuadrícula, y según haya 'gravedad real'
      o no, tratará de una forma u otra la cuadrícula resultante. Permite conocer también cuántas
      líneas se han reventado.
      @return byte  : Número de líneas que se han completado. */
  public byte quitaLineasLlenas ()
  {
    byte retorno = 0;
    /** Se borran las líneas llenas, a la vez que se cuentan. */
    for (byte y = 0; y < altura; y++)
      if (lineaLlena (y))
      {
        borraLinea (y);
        filaPivote = y;
        retorno++;
      }
    /** Si hay gravedad real, los 'bloques' sobre las líneas reventadas caerán independientemente;
        si no la hay, la sección sobre las líneas llenas caerá tantos tantos espacios como líneas
        se han completado. */
    if (gravedadReal)
      trataBloques ();
    else
      bajaAPlomo (retorno);
    /** Se repinta el panel para que se vea qué ha pasado, y se manda el retorno. */
    repaint ();
    return retorno;
  }

  /** Método para terminar de tratar las líneas llenas cuando no hay 'gravedad real'.
      Busca las líneas que han quedado vacías en la jugada, y las quita, compactando la cuadrícula.
      @param lineas byte  : Número de líneas que se acaban de completar. */
  public void bajaAPlomo (byte lineas)
  {
    /** Las líneas que se acaban de completar se conocen por ser las primeras -empezando por abajo
        de la cuadrícula- en estar vacías. */
    for (byte y = (byte) (altura - 1); y > 0; y--)
      if (lineaVacia (y))
      {
        quitaLinea (y++);
        if (--lineas < 1)
          return;
      }
  }

  /** Vacía una fila de la tabla de iconos.
      @param y byte  : La fila a vaciar. */
  public void borraLinea (byte y)
  {
    for (byte x = 0; x < anchura; x++)
      tablaCuadradillos [y][x] = null;
  }

  /** Método que mueve una sección de la cuadrícula un espacio abajo. Esa sección va desde
      la fila superior hasta la designada en el argumento. Este método sólo se utiliza si
      la 'gravedad real' está desactivada.
      @param y byte  : Fila a partir marca la primera de la sección a bajar. */
  public void quitaLinea (byte y)
  {
    /** Cada fila se hace igual que la superior, y la última se borra. */
    for (; y > 0; y--)
      for (byte x = 0; x < anchura; x++)
        tablaCuadradillos [y][x] = tablaCuadradillos [y - 1][x];
    borraLinea (y);
  }

  /** Método que divide la sección de la cuadrícula por encima de la primera línea reventada en
      regiones conexas de cuadradillos, de ahora en adelante 'bloques'. Una vez hechos los bloques,
      los baja en paralelo hasta depositarlos lo más al fondo que se pueda. */
  public void trataBloques ()
  {
    byte bloque = 2;
    byte y;
    byte x;
    /** Se rellena la tabla de bytes; 0 identifica las posiciones vacías, y 1 las ocupadas por un icono. */
    for (y = 0; y < altura; y++)
      for (x = 0; x < anchura; x++)
        if (cuadradilloVacio (y, x))
          tablaByte [y][x] = 0;
        else
          tablaByte [y][x] = 1;
    /** Se examina toda la sección sobre la 1ª fila reventada para hacer los bloques. */
    for (y = (byte) (filaPivote - 1); y >= 0; y--)
      for (x = 0; x < anchura; x++)
        if (tablaByte [y][x] == 1)
          inundaCuadradillos (y, x, bloque++);
    /** Estas variables booleanas se encargan de decidir cuándo ninguno de los bloques puede bajar más. */
    boolean trampilla;
    boolean interruptores [] = new boolean [bloque - 2];
    for (x = 0; x < interruptores.length; x++)
      interruptores [x] = true;
    /** A cada vuelta del bucle se intenta bajar todos los bloques. El bucle se detendrá cuando ninguno
        de los bloques pueda bajar. */
    do
    {
      trampilla = false;
      for (x = 0; x < interruptores.length; x++)
        if (interruptores [x] == true)
        {
          interruptores [x] = bajaBloque ((byte) (x + 2));
          if (interruptores [x])
            trampilla = true;
        }
    } while (trampilla);
  }

  /** Método recursivo que sirve para identificar todos los cuadradillos en un bloque. La tabla de bytes
      está basada en la tabla de iconos, por lo que se conoce qué posiciones están ocupadas. El método
      'hermana' las posiciones colindantes ocupadas (en diagonal ya no), dando a esas nuevas posiciones
      el mismo número de bloque que la original.
      @param y byte       : Coordenada X del cuadradillo que se inentará unir al bloque.
      @param x byte       : Coordenada Y del cuadradillo que se inentará unir al bloque.
      @param bloque byte  : Número identificador del bloque al que se intentará unir el cuadradillo. */
  public void inundaCuadradillos (byte y, byte x, byte bloque)
  {
    /** El cuadradillo sólo se unirá al bloque si está dentro del área examinada, si no está
        vacío, y si aún no forma parte del bloque. */
    if ((dentroSeccion (y, x)) && (tablaByte [y][x] != 0) && (tablaByte [y][x] != bloque))
    {
      /** El cuadradillo se une al bloque, y se intenta unir también los cuadradillos colindantes. */
      tablaByte [y][x] = bloque;
      inundaCuadradillos ((byte) (y - 1), x, bloque);
      inundaCuadradillos ((byte) (y + 1), x, bloque);
      inundaCuadradillos (y, (byte) (x - 1), bloque);
      inundaCuadradillos (y, (byte) (x + 1), bloque);
    }
  }

  /** Comprueba que una posición de la cuadrícula esté: 
  
          - Dentro de los límites mismos de la cuadrícula. 
          - Por encima de la última línea destruida (de aquí arriba, pueden empezar a formarse
            bloques). 
			 
      @param y byte    : Coordenada X de la posición a examinar.
      @param x byte    : Coordenada Y de la posición a examinar.
      @return boolean  : 'true' si la posición está dentro de esa sección, 'false' si no. */
  private boolean dentroSeccion (byte y, byte x)
  {
    return ((y >= 0) && (y <= filaPivote) && (x >= 0) && (x < anchura));
  }

  /** Intenta mover un bloque de cuadradillos un espacio abajo.
      @param bloque byte  : Número identificador del bloque en la tabla de bytes.
      @return boolean     : 'true' si el bloque ha podido bajar, y 'false' si no. */
  private boolean bajaBloque (byte bloque)
  {
    /** Si el bloque toca el fondo de la cuadrícula, no va a bajar más. */
    for (byte x = 0; x < anchura; x++)
      if (tablaByte [altura - 1][x] == bloque)
        return false;
    /** Se comprueba que el bloque pueda bajar: el espacio bajo cada cuadradillo sólo puede estar
        vacío, o ser parte del bloque. */
    for (byte y = (byte) (altura - 2); y >= 0; y--)
      for (byte x = 0; x < anchura; x++)
        if (tablaByte [y][x] == bloque)
          if ((tablaByte [y + 1][x] != 0) && (tablaByte [y + 1][x] != bloque))
            return false;
    /** Llegado este punto, se desplazan los cuadradillos del bloque una posición abajo. */
    for (byte y = (byte) (altura - 2); y >= 0; y--)
      for (byte x = 0; x < anchura; x++)
        if (tablaByte [y][x] == bloque)
        {
          /** Se ajustan las dos posiciones en juego en la tabla de bytes. */
          tablaByte [y + 1][x] = bloque;
          tablaByte [y][x] = 0;
          /** Se ajustan las dos posiciones en juego en la tabla de iconos. */
          tablaCuadradillos [y + 1][x] = tablaCuadradillos [y][x];
          tablaCuadradillos [y][x] = null;
        }
    /** Todo ha ido bien. */
    return true;
  }

  /** Función sobrecargada para pintar el borde del panel.
      @param graficos Graphics  : Contexto 'Graphics' sobre el que pintar. */
  public void paintBorder (Graphics graficos)
  {
    /** Hay que convertir el objeto 'Graphics' a 'Graphics2D', ya que esta última clase es capaz
        de escalar los componentes gráficos que pinta, y definir su escala. */
    Graphics2D graficosPaint = (Graphics2D) graficos;
    graficosPaint.scale (escala, escala);
    /** Se pinta un borde, a modo de marco, de color azul. */
    graficosPaint.setColor (Color.blue);
    graficosPaint.fillRect (0, 0, anchura * LADO_CUADRADILLO + MARGEN * 2, MARGEN);
    graficosPaint.fillRect (0, MARGEN, MARGEN, (altura - recorteSuperior) * LADO_CUADRADILLO + MARGEN);
    graficosPaint.fillRect (anchura * LADO_CUADRADILLO + MARGEN, MARGEN,
                            MARGEN, (altura - recorteSuperior) * LADO_CUADRADILLO + MARGEN);
    graficosPaint.fillRect (0, (altura - recorteSuperior) * LADO_CUADRADILLO + MARGEN,
                            anchura * LADO_CUADRADILLO + MARGEN, MARGEN);
    /** Se añaden unas líneas blancas para dar relieve. */
    graficosPaint.setColor (Color.white);
    graficosPaint.fillRect (2, 2, anchura * LADO_CUADRADILLO + MARGEN * 2 - 6, 2);
    graficosPaint.fillRect (2, 4, 2, (altura - recorteSuperior) * LADO_CUADRADILLO + MARGEN * 2 - 6);
    graficosPaint.fillRect (anchura * LADO_CUADRADILLO + MARGEN + 2, MARGEN - 2,
                            2, (altura - recorteSuperior) * LADO_CUADRADILLO + 4);
    graficosPaint.fillRect (MARGEN - 4, (altura - recorteSuperior) * LADO_CUADRADILLO + MARGEN + 2,
                            anchura * LADO_CUADRADILLO + 8, 2);
    /** Se añaden más líneas, éstas azul oscuro, para dar relieve. */
    graficosPaint.setColor (Color.blue.darker ());
    graficosPaint.fillRect (MARGEN - 2, MARGEN - 4, anchura * LADO_CUADRADILLO + 6, 2);
    graficosPaint.fillRect (MARGEN - 4, MARGEN - 4, 2, (altura - recorteSuperior) * LADO_CUADRADILLO + 6);
    graficosPaint.fillRect (anchura * LADO_CUADRADILLO + MARGEN * 2 - 4, 2,
                            2, (altura - recorteSuperior) * LADO_CUADRADILLO + MARGEN * 2 - 6);
    graficosPaint.fillRect (4, (altura - recorteSuperior) * LADO_CUADRADILLO + MARGEN * 2 - 4,
                            anchura * LADO_CUADRADILLO + MARGEN * 2 - 6, 2);
  }

  /** Método sobrecargado y 'synchronized' para no interferir con el hilo de juego, ni el teclado.
      Pinta la imagen de fondo, luego las guias, luego los iconos de la tabla de iconos, y luego
      -si se tercia-, pinta filas destruyéndose y un mensaje.
      @param graficos Graphics  : Contexto 'Graphics' sobre el que se pinta. */
  public synchronized void paint (Graphics graficos)
  {
    super.paint (graficos);
    /** Hay que convertir el objeto 'Graphics' a 'Graphics2D', ya que esta última clase es capaz
        de escalar los componentes gráficos que pinta. */
    Graphics2D graficosPaint = (Graphics2D) graficos;
    /** Define la escala. */
    graficosPaint.scale (escala, escala);
    /** Pinta la imagen de fondo. */
    graficosPaint.drawImage (imagenFondo, MARGEN, MARGEN, null);
    /** Pinta las guias de color gris claro. */
    graficosPaint.setColor (Color.white.darker ());
    for (byte y = 1; y < altura - recorteSuperior; y++)
      graficosPaint.drawLine (MARGEN, MARGEN + y * LADO_CUADRADILLO,
                              MARGEN + anchura * LADO_CUADRADILLO, MARGEN + y * LADO_CUADRADILLO);
    for (byte x = 1; x < anchura; x++)
      graficosPaint.drawLine (MARGEN + x * LADO_CUADRADILLO, MARGEN, MARGEN + x * LADO_CUADRADILLO,
                              MARGEN + (altura  - recorteSuperior) * LADO_CUADRADILLO);
    /** Pinta los iconos de la tabla en su correspondiente lugar. */
    for (byte y = recorteSuperior; y < altura; y++)
      for (byte x = 0; x < anchura; x++)
        if (tablaCuadradillos [y][x] != null)
          graficosPaint.drawImage (tablaCuadradillos [y][x].getImage (),
                                   MARGEN + x * LADO_CUADRADILLO,
                                   MARGEN + (y - recorteSuperior) * LADO_CUADRADILLO, null);
    /** Si está indicado, se pinta un icono de explosión sobre los iconos de las filas completas. */
    if (destruyendoPiezas)
    {
      for (byte y = 0; y < altura; y++)
        /** No siempre que se llegue aquí se pintarán las explosiones. A veces sí, y a veces no, según
            el valor de 'pintaReventon' -de esta forma se consigue ese efecto de animación. */
        if ((lineaLlena (y)) && (pintaReventon))
          for (byte x = 0; x < anchura; x++)
            graficosPaint.drawImage (Tetris.getImagen ("explosion.gif", "Desapareciendo").getImage (),
                                     MARGEN + x * LADO_CUADRADILLO,
                                     MARGEN + (y - recorteSuperior) * LADO_CUADRADILLO, null);
      /** Cada 3 veces que pase el programa por este punto, cambiará el valor de 'pintaReventon'. */
      if (numeroRepeticionesExplosion-- < 0)
      {
        pintaReventon = !pintaReventon;
        numeroRepeticionesExplosion = 3;
      }
    }
    /** Si se tercia, se pintará el mensaje de turno. Los únicos mensajes que activan la animación de
        'texto menguante' son el de 'JUGAR', y el de 'DESPAUSAR'. */
    if (pintaMensaje)
    {
      if ((mensaje == JUGAR) || (mensaje == DESPAUSA))
        textoSeHunde (graficosPaint);
      else
        textoEmerge (graficosPaint);
    }
  }

  /** Método llamado cuando se está produciendo una animación de 'texto menguante'. A cada llamada, pinta
      el mensaje un poquito más pequeño.
      @param graficos Graphics2D  : Contexto 'Graphics' sobre el que se pinta. */
  private void textoSeHunde (Graphics2D graficos)
  {
    /** Se calcula dónde del panel hay que pintar el mensaje. Luego, se pinta de color negro. */
    Font fuente = new Font ("Showcard Gothic", Font.BOLD, tamanoFuenteMensaje);
    FontMetrics medida = graficos.getFontMetrics (fuente);
    short x = (short) ((getSize ().width - medida.stringWidth (textoMensaje)) / 2 - 20);
    short y = (short) ((getSize ().height / 2) - 20);
    graficos.setFont (fuente);
    graficos.setColor (Color.black);
    graficos.drawString (textoMensaje, x, y);
    /** Se vuelve a pintar el mensaje, ahora un poco más pequeño, y de color rojo. */
    fuente = new Font ("Showcard Gothic", Font.BOLD, tamanoFuenteMensaje - 10);
    medida = graficos.getFontMetrics (fuente);
    x = (short) ((getSize ().width - medida.stringWidth (textoMensaje)) / 2 - 20);
    graficos.setColor (Color.red);
    graficos.drawString (textoMensaje, x, y);
    /** Se reduce el tamaño de la fuente, pero no hay que permitir que mengüe demasiado. */
    tamanoFuenteMensaje -= 2;
    if (tamanoFuenteMensaje < 20)
      tamanoFuenteMensaje += 2;
  }

  /** Método llamado cuando se está produciendo una animación de 'texto creciente'. A cada llamada, pinta
      el mensaje un poquito más grande. Si la animación se ha activado por reventar más de 3 líneas,
      también se pinta el mensaje de felicitación.
      @param graficos Graphics2D  : Contexto 'Graphics' sobre el que se pinta. */
  public void textoEmerge (Graphics2D graficos)
  {
    /** Se calcula dónde del panel hay que pintar el mensaje. Luego, se pinta de color negro. */
    Font fuente = new Font ("Showcard Gothic", Font.BOLD, tamanoFuenteMensaje);
    FontMetrics medida = graficos.getFontMetrics (fuente);
    short x = (short) ((getSize ().width - medida.stringWidth (textoMensaje)) / 2 - 20);
    short y = (short) ((getSize ().height / 2) - 20);
    graficos.setFont (fuente);
    graficos.setColor (Color.black);
    graficos.drawString (textoMensaje, x, y);
    /** Si se han reventado muchas líneas, también saldrá el texto de felicitación. */
    if (pintaFelicitacion)
      graficos.drawString (textoFelicitacion, x, y + 60);
    /** Se vuelve a pintar el mensaje, ahora un poco más pequeño, y de color rojo. */
    fuente = new Font ("Showcard Gothic", Font.BOLD, tamanoFuenteMensaje - 10);
    medida = graficos.getFontMetrics (fuente);
    x = (short) ((getSize ().width - medida.stringWidth (textoMensaje)) / 2 - 20);
    graficos.setColor (Color.red);
    graficos.drawString (textoMensaje, x, y);
    /** La misma historia para el mensaje de felicitación. */
    if (pintaFelicitacion)
      graficos.drawString (textoFelicitacion, x, y + 60);
    /** Se aumenta el tamaño de la fuente, pero no hay que permitir que crezca demasiado. */
    tamanoFuenteMensaje += 2;
    if (tamanoFuenteMensaje > 60)
      tamanoFuenteMensaje -= 2;
  }

  /** Método sobrecargado que se llama al arrancar el hilo. Ejecuta un bucle interminable que,
      mientras haya 'refrescoConstante', repintará este 'PanelCuadricula', y se esperará
      75 milisegundos, para no acaparar los recurso que se dedican al programa. */
  public void run ()
  {
    while (true)
    {
      while (!refrescoConstante)
        try
        {
          Thread.sleep (10);
        } catch (InterruptedException e) {}

      repaint ();

      try
      {
        Thread.sleep (75);
      } catch (InterruptedException e) {}
    }
  }

  /** Método estático para conocer el nombre de los fondos de panel disponibles.
      @return String[]  : Array con el nombre de las 4 imágenes de fondo por defecto, y una cadena vacía
                          por si el jugador quiere poner de fondo una imagen suya. */
  public static String [] getFondosDisponibles ()
  {
    return new String [] {FONDO_CUADRICULA1, FONDO_CUADRICULA2, FONDO_CUADRICULA3, FONDO_CUADRICULA4};
  }
}

¿Comentarios, sugerencias?: llopsite.at.yahoo.es | © 2005-07 Albert Lobo

Última actualización: 18-Feb-2007

Hosted by www.Geocities.ws

1