El trabajo propuesto

Nuestro laberinto sólo tiene dos pantallas de ancho y dos pantallas de alto. Claramente, un juego real debería tener bastantes más pantallas que recorrer...

En principio, se podría pensar que basta con añadir más pantallas al fichero que describe los niveles, pero normalmente no bastará con eso: tendremos también más tipos de casillas para construir cada nivel, de forma que el fondo parezca menos repetitivo. De momento estamos definiendo el mapa usando números como 1211121, que después se guardan cifra a cifra en un array de "bytes". Esta aproximación tiene el problema de que sólo podemos tener 10 tipos de casillas distintas. Será más razonable usar un array de "char", para poder usar muchos más símbolos que representen a muchos más tipos de casillas, así que también modificaremos un poco la clase "Mapa".

Forma de conseguirlo

En primer lugar, ampliaremos el mapa. Por una parte, lo describiremos con letras en vez de con números: por ejemplo, será más legible usar "-" en vez de "1" para una zona de suelo horizontal, o "|" en vez de "2" para una pared vertical. Por otra parte, ya que nuestro mapa es grande, podemos indicar las pantallas adyacentes de modo que aparezcan adyacentes incluso en el fichero, para que el mapa sea más fácil de actualizar. Así, el fichero podría quedar:

; "Filas" de habitaciones
3
; "Columnas" de habitaciones
6
 
''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''
======================___________________=
=============================H============
-----v--v---v---v--v--v---v--H------v---v-
|.|..........||...............||.........|
|H|H-----|H|H||H---v----H|---v-v-v--v-v-H|
|H.H.....|H.H||H|......|H|..............H|
|H|H|H||H|H|H||H|-v---H|H|H|---v-v--.---H|
|H|H|H||H|H|H||H.......|H|H||.......H|H|H|
|H|H|H||H|H-H||H----|---H|H||H--------H|H|
|H|H|H||H|H|H||H--v---v-H|H||H--------H|H|
|H.H|H||H.H|H||H|.......H|H||H|......|H|H|
|H|H|H||H|H|H||H|H|---H---H||H|H|--|H|H|H|
|H|H|H||H|H|H||H|H|.......H||H|H|H||H|H|H|
|H|H|H||H|H|H|--|H|H||H|H|H||H|H|H||H|H|H|
|H|H.H..H|H.H.....|H||H|H....H|H|H||H|H.H|
-----------------------------------------|
 

Casi todos los cambios de programación se centran en la clase Mapa. Por ejemplo, ahora la representación interna del mapa será usando un array de "char", y los símbolos para representar cada tipo de casilla serán ligeramente distintos:

public const char CASILLA_ESPACIO  = '.';
public const char CASILLA_HORIZ    = '-';
public const char CASILLA_HORIZAC  = 'v';
public const char CASILLA_VERT     = '|';
public const char CASILLA_ESCALERA = 'H';
public const char CASILLA_SUELOEXTINF = '=';
public const char CASILLA_SUELOEXTSUP = '_';
public const char CASILLA_CIELO    = '\'';
 
char [,,,] mapa;
 

En el constructor, preparamos las imágenes y leemos del fichero (de una forma ligeramente distinta a la de antes, porque ahora una misma línea del fichero contiene datos pertenecientes a varias pantallas contiguas):

imagenFondoHoriz = new ElemGrafico("imagenes/fondo_horiz.png");
imagenFondoHorizAcido = new ElemGrafico("imagenes/fondo_acido.png");
imagenFondoVert = new ElemGrafico("imagenes/fondo_vert.png");
imagenEscalera = new ElemGrafico("imagenes/fondo_escalera.png");
imagenCielo = new ElemGrafico("imagenes/fondo_cielo.png");
imagenSueloSup = new ElemGrafico("imagenes/fondo_suelosup.png");
imagenSueloInf = new ElemGrafico("imagenes/fondo_sueloinf.png");
 
// Leo los datos de fichero
StreamReader fichero;
fichero = File.OpenText("mapa.dat");
 
linea = fichero.ReadLine(); // Comentario      
linea = fichero.ReadLine(); // Cantidad de filas de niveles
maxFilaNiveles = System.Convert.ToInt32(linea) - 1;
 
linea = fichero.ReadLine(); // Comentario
linea = fichero.ReadLine(); // Cantidad de columnas de niveles      
maxColNiveles = System.Convert.ToInt32(linea) - 1;
 
linea = fichero.ReadLine(); // Linea en blanco
 
// Reservamos espacio en memoria para el mapa
mapa = new char[maxFilaNiveles+1,maxColNiveles+1,
    filasPantalla,columnasPantalla];
 
// Leo los datos de los niveles
for (int filaNivelActual = 0; filaNivelActual<=maxFilaNiveles; filaNivelActual++)
{
    for (int filaActual = 0; filaActual<filasPantalla; filaActual++)
    {
        linea = fichero.ReadLine(); // Linea de datos
        for (int colNivelActual = 0; colNivelActual<=maxColNiveles; colNivelActual++)
        {
            for (int colActual = 0; colActual<columnasPantalla; colActual++)
            {
                mapa[filaNivelActual, colNivelActual,filaActual, colActual] = 
                    linea[colNivelActual*columnasPantalla + colActual];
            }
        }
    }          
}
fichero.Close();    
 

Otros muchos métodos de la clase Mapa no cambian como el GetPosicion y los de desplazarnos a otra Habitación, el Dibujar apenas cambia para poder dibujar los nuevos tipos de casillas, y el EsPosibleMover debe comprobar un nuevo tipo de casilla pisable: el suelo de la parte exterior de la cueva:

public  bool EsPosibleMover(short x, short y, short xMax, short yMax)
{
    short posX = (short) (x/ANCHOCASILLA);
    short posY = (short) (y/ALTOCASILLA);
    short posXmax = (short) ((xMax-1)/ANCHOCASILLA);
    short posYmax = (short) ((yMax-1)/ALTOCASILLA);
 
    //Para que no se salga por los bordes de la pantalla
    if ((posX < 0) || (posY < 0) || (posX >= MAXCOLS) || (posY >= MAXFILAS))
        return false;
 
    //Controlar colision con otros bloques
    if ( ((GetPosicion(posX,posY) != CASILLA_ESPACIO) &&
          (GetPosicion(posX,posY) != CASILLA_ESCALERA) &&
          (GetPosicion(posX,posY) != CASILLA_SUELOEXTSUP) )
         ||
         ((GetPosicion(posX,posYmax) != CASILLA_ESPACIO) &&
          (GetPosicion(posX,posYmax) != CASILLA_ESCALERA) &&
          (GetPosicion(posX,posYmax) != CASILLA_SUELOEXTSUP) ) 
         ||
         ((GetPosicion(posXmax,posY) != CASILLA_ESPACIO) &&
          (GetPosicion(posXmax,posY) != CASILLA_ESCALERA) &&
          (GetPosicion(posXmax,posY) != CASILLA_SUELOEXTSUP) ) 
         ||
         ((GetPosicion(posXmax,posYmax) != CASILLA_ESPACIO) &&
          (GetPosicion(posXmax,posYmax) != CASILLA_ESCALERA) &&
          (GetPosicion(posXmax,posYmax) != CASILLA_SUELOEXTSUP) ) 
        )
 
              return false;
 
    // Si no hay obstaculos, se puede mover a esa posicion
    return true;
}  

Finalmente, en "Partida" simplemente indicamos una nueva posición de comienzo, dentro de "inicializarPartida". Sería más razonable que estos datos estuvieran dentro del mapa, para que cuando se diseña un mapa de juego alternativo, se pudiera hacer que el personaje comenzara en una posición concreta, pero eso lo aplazamos para una entrega posterior...

// --- Comienzo de un nueva partida: reiniciar variables ---
 void inicializarPartida()
{    
    puntos = 0;
    personaje.MoverA(70,50);
    personaje.SetVidas(5);
    miMapa.IrAHabitacion(4,1);        
    enemigo.Recolocar();
 
    partidaTerminada = false;
    mostrarVidasRestantes();
}
 

Puedes descargar todo el paquete de la versión 0.21, con todos los fuentes, el ejecutable (en la carpeta BIN/DEBUG), las imágenes, la documentación hasta la fecha y el proyecto de SharpDevelop1 y SharpDevelop3, pero SIN LAS DLL en un fichero ZIP de tamaño cercano a 800 Kb. Si no tienes las DLL de alguna versión anterior del proyecto, también puedes descargarlas en un fichero ZIP de tamaño cercano a 900 Kb.

Siguiente entrega...