ArkaLlop - Clon del Arkanoid

Llop Site Home > JAVA > ArkaLlop > Pantalla de juego

Pantalla de juego:

Clase PantallaJuego

package arkallop;

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

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

/** Clase que extiende 'JPanel', y que sirve como pantalla de juego. */
public class PantallaJuego extends JPanel implements Runnable, MouseListener,
                                                     MouseMotionListener, KeyListener
{
  // Hilo que se encargará del refresco constante del panel.
  private Thread hilo;
  // Variable que decide si el panel debe refrescarse constantemente o no.
  private boolean rfrCns;

  // Los 'sprites' que intervendrán en el juego.
  private Pala pala;
  private Bola bola;
  private Aliens aliens;
  private Bonos bonos;

  // Los 'niveles' o escenarios.
  private Nivel nivel;

  // Esta partida.
  private Partida partida;

  // La fuente y los bordes de la pantalla.
  private Font fuente;
  private BordesPantalla bordes;

  // Las etiquetas para la información de la partida:
  // la puntuación, la cuenta de los escenarios completados, el total de escenarios a completar,
  // el escenario actual, y el número de vidas que quedan.
  private ArkaLabel etqPunt;
  private ArkaLabel etqNivAct;
  private ArkaLabel etqTotNiv;
  private ArkaLabel etqEsc;
  private ArkaLabel etqVds;

  // Botón que permitirá volver de la pantalla de juego a la de menú.
  private ArkaButton botonFin;

  // Cuándo se disparó el último tiro -no queremos demasiados tiros de golpe.
  private long ultimoTiroTimeStamp;

  // Un array de letras, y su índice. Para detectar los trucos, que se entran por el teclado.
  private byte indiceLetraTrucoActual;
  private char trucoInput [];

  // Variables para conocer: si la pala se mueve, y cuánto se ha movido (conociendo dónde estaba).
  private boolean rtnPls;
  private int ultXRtn;

  // Contexto gráfico que se utilizará para pintar los 'sprites'. Esta clase permite renderizar la
  // imagen usando un suavizado (anti-aliasing), que mejora el efecto de animación.
  private Graphics2D graficos;

  /** Constructor. Sólo necesita que se le pase una instancia de la partida actual.
      @param nuevaPartida Partida  Esta partida. */
  public PantallaJuego (Partida nuevaPartida)
  {
    // Llamamos al constructor de 'JPanel',
    // lo hacemos 'enfocable' (para que detecte eventos, por ejemplo de teclado y de ratón),
    // le asignamos un gestor de diseño nulo (para poder ubicar cada componente en su sitio),
    // y le ponemos un cursor personalizado.
    super ();
    setFocusable (true);
    setLayout (null);
    setCursor (Toolkit.getDefaultToolkit ().createCustomCursor
               (ArkaLlop.getImagen ("cursor.gif"), new Point (16, 16), "CursorLlop"));

    // Inicicaliza las variables y los componentes.
    partida = nuevaPartida;
    inicializaVariables ();
    inicializaComponentes ();

    // Añade los oyentes.
    addKeyListener (this);
    addMouseListener (this);
    addMouseMotionListener (this);

    // Arranca los hilos de los 'sprites' y el de la pantalla misma.
    partida.arrancaHilosSprites ();
    arrancaHilo ();
  }

  /** Inicializa las variables de clase. */
  private void inicializaVariables ()
  {
    // Inicializamos la fuente, y los bordes.
    fuente = ArkaLlop.getFuenteBeyondWonderland ();
    bordes = new BordesPantalla ();

    // Las variables para el control de la entrada de trucos.
    trucoInput = new char [20];
    for (byte i = 0; i < trucoInput.length; i++)
      trucoInput [i] = ' ';
    indiceLetraTrucoActual = 10;

    // El refresco constante y el hilo.
    rfrCns = false;
    hilo = new Thread (this);

    // El último disparo.
    ultimoTiroTimeStamp = System.currentTimeMillis ();
  }

  /** Inicializa los componentes. */
  private void inicializaComponentes ()
  {
    // Inicializamos los 'sprites'.
    pala = partida.getPala ();
    aliens = partida.getAliens ();
    bola = partida.getBola ();
    nivel = partida.getNivel ();
    bonos = partida.getBonos ();

    // Las etiquetas.
    etqPunt = new ArkaLabel ();
    etqPunt.setLocation (10, 550);
    etqPunt.setSize (160, 40);
    etqNivAct = new ArkaLabel ();
    etqNivAct.setLocation (262, 520);
    etqNivAct.setSize (35, 40);
    etqTotNiv = new ArkaLabel ();
    etqTotNiv.setLocation (325, 520);
    etqTotNiv.setSize (35, 40);
    etqEsc = new ArkaLabel ();
    etqEsc.setLocation (310, 550);
    etqEsc.setSize (35, 40);
    etqVds = new ArkaLabel ();
    etqVds.setLocation (450, 550);
    etqVds.setSize (40, 40);

    // Añadimos las etiquetas al panel.
    add (etqPunt);
    add (etqNivAct);
    add (etqTotNiv);
    add (etqEsc);
    add (etqVds);

    // El botón para volver a la pantalla de menú.
    botonFin = new ArkaButton ();
    botonFin.setAction (new AbstractAction ("OK")
    {
      public void actionPerformed (ActionEvent evento)
      {
        remove (botonFin);
        partida.cambioAMenu ();
      }
    });
    botonFin.setLocation (200, 250);
    botonFin.setSize (100, 60);
  }

  /** Añade al panel el botón que permite volver a la pantalla de menú. */
  public void anadeBotonFin ()
  {
    add (botonFin);
    botonFin.requestFocus ();
  }

  /** Añade una nueva letra al array de los trucos, y comprueba si se ha completado la
      introducción de un truco.
      @param nuevaLetra char  Letra a introducir en el array. */
  private void nuevaLetraTruco (char nuevaLetra)
  {
    // Si el array está lleno, hacemos hueco para la nueva letra.
    if (indiceLetraTrucoActual == 20)
      reordenaTrucoInput ();

    // Metemos la nueva letra.
    trucoInput [indiceLetraTrucoActual++] = nuevaLetra;

    // Comprobamos si acaba de introducirse un truco válido.
    // Los trucos -una palabra- tienen una longitud máxima de 10 letras; por eso, pasamos a la
    // función que valida los trucos una cadena con las últimas 10 letras del array 'trucoInput'.
    int offset = indiceLetraTrucoActual - 10;
    String cadenaTruco = new String (trucoInput, offset, 10);
    trataCadenaTruco (cadenaTruco);
  }

  /** Comprueba si una cadena termina en uno de los trucos.
      En caso afirmativo, se aplica el truco.
      @param nuevaCadenaTruco String  Cadena en la que puede haber un truco. */
  private void trataCadenaTruco (String nuevaCadenaTruco)
  {
    if (nuevaCadenaTruco.endsWith ("schumacher"))
      aplicaTruco (Bonos.ACELERA_BOLAS);
    else if (nuevaCadenaTruco.endsWith ("caracoles"))
      aplicaTruco (Bonos.DECELERA_BOLAS);
    else if (nuevaCadenaTruco.endsWith ("tripartito"))
      aplicaTruco (Bonos.TRES_BOLAS);
    else if (nuevaCadenaTruco.endsWith ("eastwood"))
      aplicaTruco (Bonos.TAMANO_BOLAS);
    else if (nuevaCadenaTruco.endsWith ("matanza"))
      aplicaTruco (Bonos.DISPAROS_PALA);
    else if (nuevaCadenaTruco.endsWith ("bandeja"))
      aplicaTruco (Bonos.ANCHURA_PALA);
    else if (nuevaCadenaTruco.endsWith ("cianuro"))
      aplicaTruco (Bonos.IMAN_PALA);
    else if (nuevaCadenaTruco.endsWith ("carpediem"))
      aplicaTruco (Bonos.VIDA);
    else if (nuevaCadenaTruco.endsWith ("gusano"))
      aplicaTruco (Bonos.SIGUIENTE_NIVEL);
  }

  /** Aplica un truco. Por ser trampa, tiene el coste de 10000 puntos.
      @param bono byte  Truco a aplicar -una de las conastantes de la clase 'Bonos'. */
  private void aplicaTruco (byte bono)
  {
    partida.suena (Sonido.TRUCO);  // Suena un pedo.
    partida.absorbeBono (bono);
    partida.sumaPuntos (-10000);
  }

  /** Adelanta 10 posiciones las últimas 10 letras del array para los trucos. */
  private void reordenaTrucoInput ()
  {
    indiceLetraTrucoActual = 0;
    for (byte i = 10; i < 20; i++)
    {
      trucoInput [indiceLetraTrucoActual++] = trucoInput [i];
      trucoInput [i] = ' ';
    }
  }

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

  /** 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 rfrCns;
  }

  /** 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.
      @param nuevoRefrescoConstante boolean  Nuevo valor que tomará 'refrescoConstante'. */
  public void setRefrescoConstante (boolean nuevoRefrescoConstante)
  {
    rfrCns = nuevoRefrescoConstante;
  }

  /** Función sobrecargada para pintar los bordes del panel.
      @param graficos Graphics  Contexto 'Graphics' sobre el que pintar. */
  public void paintBorder (Graphics graficos)
  {
    graficos.drawImage (BordesPantalla.getImagenBorde (BordesPantalla.BRD_SUP), 0, 0, this);
    graficos.drawImage (BordesPantalla.getImagenBorde (BordesPantalla.BRD_LAD), 0, 40, this);
    graficos.drawImage (BordesPantalla.getImagenBorde (BordesPantalla.PUERTA), 0, 480, this);
    graficos.drawImage (BordesPantalla.getImagenBorde (BordesPantalla.BRD_LAD), 470, 40, this);
    graficos.drawImage (BordesPantalla.getImagenBorde (BordesPantalla.BRD_INF), 0, 520, this);
    if (partida.getPuedeAvanzarNivel ())
      graficos.drawImage (bordes.getPuerta (), 470, 480, this);
    else
      graficos.drawImage (BordesPantalla.getImagenBorde (BordesPantalla.PUERTA), 470, 480, this);
    for (byte i = 0; i < 3; i++)
      graficos.drawImage (bordes.getVaina(), 90 + i * 140, 0, this);
  }

  /** Método sobrecargado. Pinta la imagen de fondo y los 'sprites'.
      @param graficos Graphics  Contexto 'Graphics' sobre el que se pinta. */
  public void paintComponent (Graphics grfcs)
  {
    byte i;
    // El contexto 'Graphics2D' que pintará los 'sprites'. Renderizará las imágenes con 'suavizado'.
    graficos = (Graphics2D) grfcs;
    graficos.addRenderingHints (new RenderingHints (RenderingHints.KEY_ANTIALIASING,
                                                    RenderingHints.VALUE_ANTIALIAS_ON));

    super.paintComponent (graficos);

    // Pintar la imagen de fondo, la pala, las bolas, los aliens, los bonos, los tiros, y los ladrillos.
    graficos.drawImage (FondosPantalla.getImagenFondo (partida.getFondoEscenario ()), 0, 0, this);
    graficos.drawImage (pala.getImagenPala (), pala.getCoordX (), 510, this);
    for (i = 0; i < bola.getNumAlmsAnim (); i++)
      graficos.drawImage (bola.getImagen (i), (int) bola.getCoordX (i),
                               (int) bola.getCoordY (i), this);
    for (i = 0; i < aliens.getNumAliensAnim (); i++)
      graficos.drawImage (aliens.getImagen (i), (int) aliens.getCoordX (i),
                               (int) aliens.getCoordY (i), this);
    for (i = 0; i < bonos.getNumBonosAnim (); i++)
      graficos.drawImage (bonos.getImagenBono (i), (int) bonos.getCoordX (i),
                               (int) bonos.getCoordY (i), this);
    for (i = 0; i < pala.getNumTirosAnim (); i++)
      graficos.drawImage (pala.getImagenTiro (i), (int) pala.getCoordXTiro (i),
                               (int) pala.getCoordYTiro (i), this);
    for (i = 0; i < 11; i++)
      for (byte j = 0; j < 13; j++)
        if (nivel.ladrilloEsVisible (i, j))
          graficos.drawImage (nivel.getImagen (i, j), 30 + (40 * i), 100 + (20 * j), this);

    // Si la partida está terminada, se está cargando un nivel, o la partida aún no ha empezado
    // o está pausada, habrá que pintar un recuadro con un mensaje sobre la pantalla.
    if (partida.getFinalizada ())
    {
      graficos.setColor (Color.black);
      graficos.fillRect (70, 200, 360, 120);
      graficos.setFont (fuente);
      graficos.setColor (Color.red);
      if (partida.getResultadoPartida () == Partida.DERROTA)
        graficos.drawString ("Game Over", 170, 235);
      else
        graficos.drawString ("Ha completado el juego!", 90, 235);
    }
    else if (partida.getCargandoNivel ())
    {
      graficos.setColor (Color.black);
      graficos.fillRect (130, 240, 240, 50);
      graficos.setFont (fuente);
      graficos.setColor (Color.red);
      graficos.drawString ("Cargando Nivel...", 140, 275);
    }
    else if (partida.getPrePartida ())
    {
      graficos.setColor (Color.black);
      graficos.fillRect (75, 240, 350, 50);
      graficos.setFont (fuente);
      graficos.setColor (Color.red);
      graficos.drawString ("Pulse 'Enter' para jugar.", 85, 275);
    }
    else if (partida.getPausa ())
    {
      graficos.setColor (Color.black);
      graficos.fillRect (130, 240, 240, 50);
      graficos.setFont (fuente);
      graficos.setColor (Color.red);
      graficos.drawString ("Pausa", 200, 275);
    }
  }

  /** Método sobrecargado que se llama al arrancar el hilo. Ejecuta un bucle interminable que,
      mientras haya 'refrescoConstante', repintará la 'PantallaJuego', y se esperará
      40 milésimas, para no acaparar los recurso que se dedican al programa. */
  public void run ()
  {
    // Variable que almacenará los milisegundos entre vuelta y vuelta del bucle.
    long tmpOffset = System.currentTimeMillis ();
    while (true)
    {
      // Mientras no se necesite el refresco constante, que duerma un poco el hilo.
      while (!rfrCns)
        try
        {
          hilo.sleep (100);
        } catch (InterruptedException e) {}

      // Escribimos el texto de las etiquetas.
      etqPunt.setText (String.valueOf (partida.getPuntuacion ()));
      etqNivAct.setText (String.valueOf (partida.getEscenarioActual () + 1));
      etqTotNiv.setText (String.valueOf (partida.getTotalEscenarios ()));
      etqEsc.setText (partida.getNombreEscenario ());
      etqVds.setText (String.valueOf (partida.getVidas () + 1));

      // Repintar el panel.
      repaint ();

      // Se calcula cuánto ha pasado desde que empezó la vuelta del bucle.
      // Este valor se descontará del tiempo de descanso del hilo.
      // El hilo descansaría 40 milisegundos, así que si 'tmpOffset' es demasiado,
      // lo ajustamos para que el hilo duerma al menos 10 milésimas.
      tmpOffset = System.currentTimeMillis () - tmpOffset;
      if (tmpOffset > 30)
        tmpOffset = 30;
      try
      {
        Thread.sleep (40 - tmpOffset);
      } catch (InterruptedException e) {}
      tmpOffset = System.currentTimeMillis ();
    }
  }

  /** Método propio de la interfaz 'KeyListener'. Capta las teclas de control.
      @param evento KeyEvent  Evento del teclado. */
  public synchronized void keyPressed (KeyEvent evento)
  {
    // Si se está cargando un nivel, no hacer nada.
    if (partida.getCargandoNivel ())
      return;

    // La tecla pulsada.
    int tcl = evento.getKeyCode ();

    // 'Enter' arranca la partida, y también despega las bolas que la pala ha atrapado.
    if (tcl == KeyEvent.VK_ENTER)
    {
      if (partida.getPrePartida ())
      {
        partida.setPrePartida (false);
        partida.setPausa (!partida.getPausa ());
        partida.suena (Sonido.BOLA_DESPEGA);
      }
      else if (pala.estaImantada () && !partida.getPausa ())
        for (byte i = 0; i < bola.getNumAlmsAnim (); i++)
          if (bola.pegadaPala (i))
            bola.despegaPala (i);
    }
    // 'Control' pausa y despausa el juego.
    else if (tcl == KeyEvent.VK_CONTROL)
    {
      if (!partida.getPrePartida ())
      {
        if (partida.getPausa ())
        {
          partida.suena (Sonido.PAUSA);
          addMouseListener (this);
          addMouseMotionListener (this);
          partida.setPausa (!partida.getPausa ());
        }
        else
        {
          partida.setPausa (!partida.getPausa ());
          removeMouseListener (this);
          removeMouseMotionListener (this);
          partida.suena (Sonido.PAUSA);
        }
      }
    }
    // 'Escape' aborta la partida.
    else if (tcl == KeyEvent.VK_ESCAPE)
    {
      partida.suena (Sonido.GAME_OVER);
      partida.terminaPartida (Partida.DERROTA);
    }
    // La barra de espacio dispara los cañones de la pala -si los hay-;
    // en ese caso, también puede despegar las bolas que la pala tenga atrapadas.
    else if (tcl == KeyEvent.VK_SPACE)
    {
      if (pala.estaArmada () && !partida.getPausa ())
      {
        if (evento.getWhen () > ultimoTiroTimeStamp + 250)
        {
          ultimoTiroTimeStamp = evento.getWhen ();
          pala.dispara ();
        }
        if (pala.estaImantada ())
          for (byte i = 0; i < bola.getNumAlmsAnim (); i++)
            if (bola.pegadaPala (i))
              bola.despegaPala (i);
      }
    }
  }
  /** Método propio de la interfaz 'KeyListener'.
      @param evento KeyEvent  Evento del teclado. */
  public void  keyReleased (KeyEvent evento) {}
  /** Método propio de la interfaz 'KeyListener'. Lleva el control de la entrada de trucos.
      @param evento KeyEvent  Evento del teclado. */
  public void keyTyped (KeyEvent evento)
  {
    char letra = evento.getKeyChar ();
    // Filtramos los eventos que no contengan letras.
    if (letra != KeyEvent.CHAR_UNDEFINED)
      nuevaLetraTruco (Character.toLowerCase (letra));
  }

  /** Método para la interfaz 'MouseListener'. Pide el foco para este panel.
      @param e MouseEvent  Evento del ratón. */
  public void mouseClicked (MouseEvent e) { requestFocus (); }
  /** Método para la interfaz 'MouseListener'.
      @param e MouseEvent  Evento del ratón. */
  public void mouseEntered (MouseEvent e) {}
  /** Método para la interfaz 'MouseListener'.
      @param e MouseEvent  Evento del ratón. */
  public void mouseExited (MouseEvent e) {}
  /** Método para la interfaz 'MouseListener'.
      Mira si se ha pulsado el ratón sobre la pala o cerca de ella.
      En tal caso, permite mover la pala moviendo el ratón.
      @param e MouseEvent  Evento del ratón. */
  public void mousePressed (MouseEvent e)
  {
    int x = e.getX ();
    int y = e.getY ();
    if ((x >= pala.getCoordX () - 6) && (x <= pala.getCoordX () + pala.getAnchura () + 6) &&
        (y >= 506) && (y <= 526))
    {
      ultXRtn = x;
      rtnPls = true;
    }
  }
  /** Método para la interfaz 'MouseListener'.
      Al despulsar el ratón, ya no podremos mover la pala -si la teníamos agarrada.
      @param e MouseEvent  Evento del ratón. */
  public void mouseReleased (MouseEvent e)
  {
    if (rtnPls)
    {
      rtnPls = false;
      pala.setVectX ((short) 0);
    }
  }

  /** Método para la interfaz 'MouseMotionListener'. Mientras mantengamos pulsado el ratón sobre la pala,
      podremos mover a ésta de un lado a otro moviendo el ratón.
      @param e MouseEvent  Evento del ratón. */
  public synchronized void mouseDragged (MouseEvent e)
  {
    // El botón del ratón debe estar pulsado.
    if (rtnPls)
    {
      // Distancia que se ha movido el puntero.
      short dist = (short) (e.getX () - ultXRtn);
      // Según esta distancia, establecemos el vector de la pala.
      if (dist > 18)
        pala.setVectX ((short) 3);
      else if (dist < -18)
        pala.setVectX ((short) -3);
      else
        pala.setVectX (dist / 6F);

      // Hay que vigilar para que la pala no se introduzca -gracias a un presto golpe de ratón- en
      // alguna de las bolas.
      for (byte i = 0; i < bola.getNumAlmsAnim (); i++)
      {
        // Pasar de las bolas pegadas a la pala.
        if (bola.pegadaPala (i))
          continue;
        // Pasar de las bolas que no estén lo bastante abajo en la pantalla.
        if (bola.getCoordY (i) + bola.getDiam () > 510)
        {
          if (bola.getCoordX (i) + bola.getRad () < pala.getCoordX ())
          {
            // La bola está a la izquierda de la pala.
            if (pala.getCoordX () + dist < bola.getCoordX (i) + bola.getRad ())
            {
              dist = (short) (bola.getCoordX (i) + bola.getVectX (i) - pala.getCoordX ());
              break;
            }
          }
          else if (bola.getCoordX (i) > pala.getCoordX () + pala.getAnchura () - bola.getRad ())
          {
            // La bola está a la derecha de la pala.
            if (pala.getCoordX () + pala.getAnchura () + dist > bola.getCoordX (i) + bola.getRad ())
            {
              dist = (short) -(bola.getCoordX (i) + bola.getVectX (i) -
                              (pala.getCoordX () + pala.getAnchura ()));
              break;
            }
          }
        }
      }

      // Ahora miramos que la pala no se incruste en la pared.
      int nuevaCoordX = pala.getCoordX () + dist;
      if ((nuevaCoordX >= 30) && (nuevaCoordX + pala.getAnchura () <= 470))
      {
        pala.mueve (dist);
        // Movemos las bolas que estén pegadas a la pala.
        if (partida.getPrePartida () || pala.estaImantada ())
          for (byte i = 0; i < bola.getNumAlmsAnim (); i++)
            if (partida.getPrePartida () || bola.pegadaPala (i))
              bola.mueveHorizontalmente (i, dist);
      }
      // Si la puerta al siguiente nivel está abierta, aquí se controla si la pala la atraviesa.
      if ((partida.getPuedeAvanzarNivel ()) && (!partida.getCargandoNivel ())
          && (nuevaCoordX + pala.getAnchura () >= 470))
        partida.completaNivel ();

      // Actualizamos la variable para poder volver a calcular el movimiento la próxima vez.
      ultXRtn = e.getX ();
    }
  }
  /** Método para la interfaz 'MouseMotionListener'.
      @param e MouseEvent  Evento del ratón. */
  public void mouseMoved (MouseEvent e) {}
}

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

Última actualización: 18-Feb-2007

Hosted by www.Geocities.ws

1