Novedades en esta versión

Vamos con ello...

Añadir dos niveles más

Vamos a ampliar un poco la estructura del juego: ahora el Mapa ya no tendrá definidos los datos de un único nivel, sino que crearemos una nueva clase "Nivel" y el mapa contendrá un array de niveles.

El juego que imitamos tenía 20 niveles. Para los detalles de cada nivel concreto, no crearemos un nuevo objeto de la clase Nivel, que sería suficiente para este juego, sino que tomaremos la alternativa "avanzada", que es más trabajosa pero también mucho más versátil: crearemos una clase "Nivel01", otra "Nivel02" y así sucesivamente, lo que nos permitiría poder llegar a conseguir que la "lógica" de un nivel fuera totalmente distinta de la de otro (hasta el extremo de que un nivel podría ser de vista lateral, y otro de vista superiior, y otro en 3D...)

En nuestro caso "real", los niveles se diferenciarán unos de otros (por ahora) sólo por el contenido del array que lo describe, y por su nombre. Por ejemplo, el segundo nivel podría ser así:

public class Nivel02 : Nivel
{
    public Nivel02()
    {
        nombre = "The Cold Room";
        datosNivelIniciales[ 0] = "L                  LLLLLLLLLLLLL";
        datosNivelIniciales[ 1] = "L      V                V     TL";
        datosNivelIniciales[ 2] = "L                              L";
        datosNivelIniciales[ 3] = "L                    FFFF      L";
        datosNivelIniciales[ 4] = "L                              L";
        datosNivelIniciales[ 5] = "LSSSSSSSSSSSSSSSSSSS        L  L"; 
        datosNivelIniciales[ 6] = "L                    SSSSLFFL  L"; 
        datosNivelIniciales[ 7] = "LSFFFFF                  LV L  L"; 
        datosNivelIniciales[ 8] = "L                        LFFL  L"; 
        datosNivelIniciales[ 9] = "L  V     SSSSSSS         LFFL  L"; 
        datosNivelIniciales[10] = "L                  FFFF  LFFL  L"; 
        datosNivelIniciales[11] = "L  DDDD                  LFFL  L"; 
        datosNivelIniciales[12] = "L             SSSS V     LFFL  L"; 
        datosNivelIniciales[13] = "L       FFFF                 PPL";
        datosNivelIniciales[14] = "L                            PPL";
        datosNivelIniciales[15] = "LSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSL";
 
        Reiniciar();
    }      
} /* fin de la clase Nivel02 */
 

Y se vería así:

Apariencia del juego, versión 0.11

Y el tercer nivel así:

public class Nivel03 : Nivel
{
    public Nivel03()
    {
        nombre = "The Menagerie";
        datosNivelIniciales[ 0] = "M    V   T     V  T    V   T   M";
        datosNivelIniciales[ 1] = "M                              M";
        datosNivelIniciales[ 2] = "M                              M";
        datosNivelIniciales[ 3] = "M                              M";
        datosNivelIniciales[ 4] = "M                              M";
        datosNivelIniciales[ 5] = "MNNNNOOOOOOOOOOOOOOOOOOOOOO  OOM"; 
        datosNivelIniciales[ 6] = "M                    V        VM"; 
        datosNivelIniciales[ 7] = "MNNNNNN                    NNNNM"; 
        datosNivelIniciales[ 8] = "MT                             M"; 
        datosNivelIniciales[ 9] = "M     DDDDDD                   M"; 
        datosNivelIniciales[10] = "M                        NNNNNNM"; 
        datosNivelIniciales[11] = "M             NNNNN          PPM"; 
        datosNivelIniciales[12] = "M    NNNNNN                  PPM"; 
        datosNivelIniciales[13] = "M                    NNNNNNNNNNM";
        datosNivelIniciales[14] = "M                              M";
        datosNivelIniciales[15] = "MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM";
 
        Reiniciar();
    }  
} /* fin de la clase Nivel03 */
 

Que se vería:

Apariencia del juego, versión 0.11

Ahora la nueva clase "Nivel" sería la que tendría la lógica común a todos los niveles. Básicamente contendrá todo lo que antes estaba en "Mapa", junto con un nuevo "GetNombre" que permita saber el nombre del nivel actual:

public class Nivel
{
 
    // Datos del mapa del nivel actual
    Partida miPartida;
 
    private int altoMapa = 16, anchoMapa = 32;
    private int anchoTile = 24, altoTile = 24;
    private int margenIzqdo = 20, margenSuperior = 40;
    private int llavesRestantes = 4;   // Valor arbitrario (no 0: final de partida)
 
    ElemGrafico arbol, deslizante, ladrillo, ladrilloX, llave, puerta, 
        sueloFino, sueloFragil, sueloFragil2, sueloGrueso, techo;
 
 
    protected string nombre = "(Nonamed)";
 
    string[] datosNivel;  // Datos en el momento de juego
 
    protected string[] datosNivelIniciales =  // Datos para reiniciar
    {"LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL",
     "L                              L",
     "L                              L",
     "L                              L",
     "L                              L", 
     "L                              L", 
     "L                              L", 
     "L                              L", 
     "L                              L", 
     "L                              L", 
     "L                              L", 
     "L                              L", 
     "L                              L", 
     "L                            PPL",
     "L                            PPL",
     "LSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSL"};
 
    // Constructor
    public Nivel()
    {
        //miPartida = p;   // Para enlazar con el resto de componentes
 
        arbol = new ElemGrafico("imagenes/arbol.png");
        deslizante = new ElemGrafico("imagenes/deslizante.png");
        ladrillo = new ElemGrafico("imagenes/ladrillo.png");
        ladrilloX = new ElemGrafico("imagenes/ladrillo2.png");
        llave = new ElemGrafico("imagenes/llave.png");
        puerta = new ElemGrafico("imagenes/puerta.png");
        sueloFino = new ElemGrafico("imagenes/suelo.png");
        sueloGrueso = new ElemGrafico("imagenes/suelo2.png");
        sueloFragil = new ElemGrafico("imagenes/sueloFragil.png");
        sueloFragil2 = new ElemGrafico("imagenes/sueloFragil2.png");
        techo = new ElemGrafico("imagenes/techo.png");
 
        datosNivel = new string[altoMapa];
        Reiniciar();
  }
 
   public void Reiniciar()
   {
       for (int fila = 0; fila < altoMapa; fila++)
           datosNivel[fila] = datosNivelIniciales[fila];
   }
 
   public void DibujarOculta()
   {
      // Dibujo el fondo
      llavesRestantes = 0;
      for (int fila = 0; fila < altoMapa; fila++)
          for (int colum = 0; colum < anchoMapa; colum++)
          {
              int posX = colum * anchoTile + margenIzqdo;
              int posY = fila * altoTile + margenSuperior;
              switch (datosNivel[fila][colum])
              {
                  case 'A': arbol.DibujarOculta(posX, posY); break;
                  case 'D': deslizante.DibujarOculta(posX, posY); break;
                  case 'F': sueloFragil.DibujarOculta(posX, posY); break;
                  case 'L': ladrillo.DibujarOculta(posX, posY); break;
                  case 'M': ladrilloX.DibujarOculta(posX, posY); break;
                  case 'N': sueloGrueso.DibujarOculta(posX, posY); break;
                  case 'O': sueloFragil2.DibujarOculta(posX, posY); break;
                  case 'P': puerta.DibujarOculta(posX, posY); break;
                  case 'S': sueloFino.DibujarOculta(posX, posY); break;
                  case 'T': techo.DibujarOculta(posX, posY); break;
                  case 'V': llave.DibujarOculta(posX, posY); 
                            llavesRestantes++;  break;
              }
          }
   }
 
   public bool EsPosibleMover(int x, int y, int xmax, int ymax)
   {
 
       // Compruebo si choca con alguna casilla del fondo
       for (int fila = 0; fila < altoMapa; fila++)
              for (int colum = 0; colum < anchoMapa; colum++)
              {
                  int posX = colum * anchoTile + margenIzqdo;
                  int posY = fila * altoTile + margenSuperior;
                  // Si se solapa con la posic a la que queremos mover
                  if ((posX+anchoTile > x) && (posY+altoTile > y)  
                      && (xmax> posX) && (ymax > posY))
                      // Y no es espacio blanco, llave, puerta o arbol
                      if ((datosNivel[fila][colum] != ' ')                          
                          && (datosNivel[fila][colum] != 'V')  
                          && (datosNivel[fila][colum] != 'P')  
                          && (datosNivel[fila][colum] != 'A'))
                      {
                          return false;
                      }
               }
 
       return true;
   }
 
   public int ObtenerPuntosPosicion(int x, int y, int xmax, int ymax)
   {
 
       // Compruebo si choca con alguna casilla del fondo
       for (int fila = 0; fila < altoMapa; fila++)
           for (int colum = 0; colum < anchoMapa; colum++)
           {
               int posX = colum * anchoTile + margenIzqdo;
               int posY = fila * altoTile + margenSuperior;
 
               // Si choca con la casilla que estoy mirando
               if ( (posX + anchoTile > x) && (posY + altoTile > y)
                   && (xmax > posX) && (ymax > posY))
               {
                   // Si choca con el techo o con un arbol
                   if ((datosNivel[fila][colum] == 'T') 
                            || (datosNivel[fila][colum] == 'A'))
                       return -1; // (puntuacion -1: perder vida)
 
                   // Si toca una llave
                   if (datosNivel[fila][colum] == 'V')
                   {                       
                       // datosNivel[fila][colum] = ' '; (No valido en C#: 2 pasos)
                       datosNivel[fila] = datosNivel[fila].Remove(colum, 1);
                       datosNivel[fila] = datosNivel[fila].Insert(colum, " ");
                       return 10;
                   }
 
                   // Si toca la puerta y no quedan llaves, 50 puntos
                   // y (pronto) pasar al nivel siguiente
                   if ((datosNivel[fila][colum] == 'P')
                       && (llavesRestantes == 0))
                   {
                       return 50;
                   }
 
               } // Fin de comprobacion de casilla actual
           } // Fin de "for" que revisa todas las casillas
 
       // Si llego hasta aqui, es que no hay puntos que devolver
       return 0;
 
   }
 
   public string LeerNombre()
   {
       return nombre;
   }
 
} /* fin de la clase Mapa */
 

Y la clase "Mapa" ya no hace casi nada por ella misma: se limita a crear un array de niveles, y a preguntar al nivel actual cada dato que se le pida desde el cuerpo del programa, y también tendrá un método "Avanzar" que pase al siguiente nivel:

public class Mapa
{
    Nivel nivelActual;
    Nivel[] listaNiveles;
    const int MAX_NIVELES = 3;
    int numeroNivelActual = 0;
    Fuente fuente18;   // Tipo de letra para mensajes
 
    // Constructor
    public Mapa(Partida p)
    {
        listaNiveles = new Nivel[MAX_NIVELES];
        listaNiveles[0] = new Nivel01();
        listaNiveles[1] = new Nivel02();
        listaNiveles[2] = new Nivel03();
 
        nivelActual = listaNiveles[ numeroNivelActual ];
        fuente18 = new Fuente("FreeSansBold.ttf", 18);
  }
 
   public void Reiniciar()
   {
       nivelActual.Reiniciar();
   }
 
   public void DibujarOculta()
   {
       nivelActual.DibujarOculta();
   }
 
   public bool EsPosibleMover(int x, int y, int xmax, int ymax)
   {
       return nivelActual.EsPosibleMover(x, y, xmax, ymax); 
   }
 
   public int ObtenerPuntosPosicion(int x, int y, int xmax, int ymax)
   {
       return nivelActual.ObtenerPuntosPosicion(x, y, xmax, ymax); 
   }
 
   public void Avanzar()
   {
       numeroNivelActual++;
       if (numeroNivelActual >= MAX_NIVELES)
           numeroNivelActual = 0;
 
       // Rectángulo de fondo
       Hardware.RectanguloRellenoRGBA(
          200, 100, 600, 300,  // Posicion, ancho y alto de la pantalla
          200, 200, 200,   // Gris claro
          200);         // Con algo de transparencia
 
       // Y texto de aviso
       Hardware.EscribirTextoOculta(
               "Pasando al nivel "+(numeroNivelActual+1),
               300, 200, 0, 0, 0, fuente18);
 
       Hardware.VisualizarOculta();
       Hardware.Pausa(2000);
 
       nivelActual = listaNiveles[numeroNivelActual];
       nivelActual.Reiniciar();
   }
 
   public string GetNombre()
   {
       return nivelActual.LeerNombre();
   }
 
} /* fin de la clase Mapa */
 

Abrir la puerta que permite pasar de un nivel a otro cuando se recogen todas las llaves

Ya teníamos la infraestructura preparada: desde la función de "ComprobarColisiones" de la "Partida" ya mirábamos si se habían recogido todas las llaves y se tocaba la puerta, pero nos limitábamos a dar 50 puntos al jugador. Ahora también avisaremos de que hay que avanzar al siguiente nivel:

 
// Si ademas es una puerta, avanzamos de nivel
if (puntosMovimiento == 50)
    miPantallaJuego.Avanzar();
 

Pasar de un nivel a otro pulsando T+N (truco)

Sencillo: una vez que tenemos preparada la orden que nos permite avanzar de nivel, basta comprobar si se pulsa cierta combinación de teclas para activarla:

 
if ( (Hardware.TeclaPulsada(Hardware.TECLA_T)) &&
    (Hardware.TeclaPulsada(Hardware.TECLA_N)) )
  miPantallaJuego.Avanzar();
 

Otros cambios menores

No necesitamos casi nada más, pero ya que hemos añadido un "nombre" a cada nivel, podemos ampliar un poco la clase Marcador para que lo muestre en pantalla.


Como siempre, puedes encontrar la versión 0.11 completa en la página del proyecto en Google Code.

Siguiente entrega...