Desarrollo de Juegos

Aula Macedonia



Juegos de Aventuras
Diseñando una aventura conversacional


Artículo realizado por
Spellcaster.
T
raducción y adaptación de
Daniel Cárdenas.





Capítulo 7.
Monstruos

"Qué es un monstruo?". Esto quizá pueda parecer una pregunta estúpida, pero de ninguna manera lo es. En una conversacional un monstruo es algo que te puede hacer daño... Puede ser un robot o cualquier otra cosa... En FangLore pondremos tres monstruos:

  1. Un jardinero psicópata
  2. Un gusano
  3. Un vampiro
El jardinero tiene algo de especial: tiene asignado una ruta predefinida, mientras que el gusano y el vampiro permanecerán estáticos en cocina y biblioteca, respectivamente.

Definamos un record para los monstruos:

        Type TipoMonstruo=Record
                Nombre:String;
                Pos:Byte;
                Indice:Byte;
                Salud:Byte;
                Fuerza:Byte;
                Posiciones:Array[1..MaxPos] Of Byte;
        End;

Como el único monstruo que se mueve en el juego es el jardinero, restringiremos su movimiento al jardín. Así que, como hay 14 localidades en el jardín, definiremos MaxPos:
        Const MaxPos=14;
Veamos como implementaremos el movimiento. Inicializamos el array Posiciones con los valores de las localidades a las que queremos que se mueva el monstruo. Asignaremos a Indice el valor 1. Después, para cada unidad de tiempo que pase, incrementaremos Indice en 1 y cargaremos la variable Pos con el valor de la localidad indexado por Indice. Si esta localidad indexada es 0, entonces Indice es reseteado de nuevo a 1. Si el Indice es mayor que el tamaño del array, Indice también es reseteado a 1.

Definiremos una unidad de tiempo como el tiempo de entrada de cada comando. Esto se podría definir como 1 segundo, 1 minuto, cada cinco comandos, etc. Pero la elección ya ha sido tomada. Un poco de reprogramación sería necesaria para cada clase de 'temporizador'. La más compleja seria programar un temporizador real, que usaría una interrupción o algo así.

Es el momento de definir las variables para los monstruos:

        Var Monstruos:Array[1..MaxMons] Of TipoMonstruo;
donde MaxMons es el número de monstruos en la aventura:
        Const MaxMons=3;
Similar a lo que hice con los Objetos y Localidades, he realizado un programa que lee los valores de los records de los monstruos. Con él he creado un fichero llamado Monster.Dat, el cual leo mediante la rutina LeerDatosMonstruos, que es muy parecida a las rutinas LeerDatosLocalidades y LeerDatosObjetos:
Procedure LeerDatosMonstruos;     { Lee de disco los datos de los monstruos }
Var F:Text;
    A,B:Byte;
    Flag:Boolean;
Begin
     { Prepara el fichero de texto para el acceso }
     Assign(F,'Monster.Dat');
     Reset(F);
     { Para cada monstruo en la aventura }
     For A:=1 To MaxMons Do
     Begin
          { Lee el nombre del monstruo }
          ReadLn(F,Monstruos[A].Nombre);
          { Lee la posición inicial del monstruo }
          ReadLn(F,Monstruos[A].Pos);
          { Lee la salud del monstruo }
          ReadLn(F,Monstruos[A].Salud);
          { Lee la fuerza }
          ReadLn(F,Monstruos[A].Fuerza);
          { Limpia ruta }
          For B:=1 To 5 Do Monstruos[A].Posiciones[B]:=0;
          { Lee ruta }
          Flag:=True;
          B:=1;
          While Flag Do
          Begin
               ReadLn(F,Monstruos[A].Posiciones[B]);
               If (B=MaxPos) Or (Monstruos[A].Posiciones[B]=0) Then Flag:=False;
               Inc(B);
          End;
          Monstruos[A].Indice:=1;
     End;
     Close(F);
End;
No olvides llamar a esta rutina en Init.

Y ahora, tenemos que programar un nuevo procedimiento para ver el monstruo. ¿Por qué no basta con modificar Mirar? Porque el monstruo será examinado un montón de veces durante la batalla. ¡¡¡¡Y también haremos servir esta rutina para patear al jugador!!!! :)))

Cada vez que el ordenador 'mire' el monstruo, golpeará. Aquí añadiremos el factor suerte. El monstruo solo golpeará con éxito un porcentaje determinado de veces. Y solo cuando golpee con éxito infrinjirá daño.

Programemos la rutina:

Procedure VerMonstruo;                   { Ver monstruo }
Var A,B:Byte;
    Danyo:Byte;
Begin
     For A:=1 To MaxMons Do
     Begin
          { Comprueba si el monstruo está en la localidad }
          If Monstruos[A].Pos=NumeroLoc Then
          Begin
               { Describe el monstruo }
               TextColor(LightRed);
               WriteLn;
               WriteLn('¡¡¡¡ Veo un ',Monstruos[A].Nombre,' aquí !!!!');
               WriteLn('Le quedan ',Monstruos[A].Salud,' puntos de vida...');
               { Hagamos que golpee }
               WriteLn('¡ Te ha visto !');
               Write('Trata de golpearte...');
               { Comprueba si nos acierta. Dificultad es un margen predefinido.
                 Cuanto más alta, mayor será nuestra 'suerte' }
               B:=Random(100);
               If B>Dificultad Then
               Begin
                    { ¡¡¡ Acierta !!! }
                    WriteLn('¡¡¡¡ y te acierta !!!!');
                    { Asigna daños, teniendo en cuenta la buena
                      o la mala suerte }
                    Danyo:=Monstruos[A].Fuerza+(Random(8)-5);
                    Salud:=Salud-Danyo;
                    WriteLn('¡ Sólo te quedan ',Salud,' puntos de vida !');
                    { Comprueba si todavía está vivo }
                    If Salud<=0 Then
                    Begin
                         { Hemos muerto... :( }
                         WriteLn('Has muerto...');
                         Write('Las Valquirias llegan para reclamar tu cuerpo... Pero ');
                         WriteLn('no eres un guerrero, así que te dejan');
                         Writeln('para que te pudras... ');
                         Writeln;
                         TextColor(Magenta);
                         Writeln('Pulsa cualquier tecla para abandonar FangLore...');
                         Repeat Until Keypressed;
                         Halt(0);
                    End;
               End
               Else
               Begin
                    { ¡¡¡ Falla !!! }
                    Writeln('¡¡¡ y falla !!!');
                    WriteLn('¡ Sólo te quedan ',Salud,' puntos de vida !');
               End;
          End;
     End;
End;
Hemos tenido en cuenta la suerte, porque el juego será así más excitante. Nos queda definir el factor Dificultad:
        Const Dificultad=40;
Tan solo añade una llamada a VerMonstruo al comienzo de la rutina Play, y así conseguimos la unidad de tiempo de la que hablábamos más arriba.

También cambié la rutina Inventario ligeramente. Ahora, el comando de inventario también te informa de la salud. Esto se consigue añadiendo las líneas...

     WriteLn;
     TextColor(LightGreen);
     WriteLn('Te quedan ',Salud,' puntos de vida.');
.... a la rutina de Inventario.

Ahora hagamos que los monstruos se muevan... Como ya he explicado, el movimiento se consigue cambiando la posición del monstruo cada vez que es introducido un comando. Para lograrlo, añadí una llamada a la rutina MoverMonstruo al final del bucle de la rutina principal de juego Play. La rutina MoverMonstruo es bastante simple:

          Procedure MoverMonstruo;
          Var A:Byte;
          Begin
               { Mover cada monstruo }
               For A:=1 To MaxMons Do
               Begin
                    { Resetea el índice de posición,
                      si la posición apuntada es 0 }
                    If Monstruos[A].Posiciones[Monstruos[A].Indice]=0 Then
                      Monstruos[A].Indice:=1;
                    Monstruos[A].Pos:=Monstruos[A].Posiciones[Monstruos[A].Indice];
                    Inc(Monstruos[A].Indice);
               End;
          End;
Esto es lo básico para conseguir el movimiento. Pero se puede mejorar bastante. Una posible mejora es la siguiente: no queremos que el monstruo se mueva si se encuentra en la misma localidad que el jugador. Podemos lograrlo con solo añadir tres líneas:
          Procedure MoverMonstruo;
          Var A:Byte;
          Begin
               { Mover cada monstruo }
               For A:=1 To MaxMons Do
               Begin
                    { Comprueba si el monstruo se encuentra en la misma
                      localidad que el jugador }
                    If Monstruos[A].Pos<>LocalidadActual Then
                    Begin
                         { Resetea el índice de posición,
                           si la posición apuntada es 0 }
                         If Monstruos[A].Posiciones[Monstruos[A].Indice]=0 Then
                           Monstruos[A].Indice:=1;
                         Monstruos[A].Pos:=Monstruos[A].Posiciones[Monstruos[A].Indice];
                         Inc(Monstruos[A].Indice);
                    End;
               End;
          End;
Para añadir un poco de realismo al texto, podrímos dar un mensaje cuando un monstruo entra en la localidad del jugador. Escribamos unas cuantas líneas más.
          Procedure MoverMonstruo;
          Var A:Byte;
          Begin
               { Mover cada monstruo }
               For A:=1 To MaxMons Do
               Begin
                    { Comprueba si el monstruo se encuentra en la misma
                      localidad que el jugador }
                    If Monstruos[A].Pos<>LocalidadActual Then
                    Begin
                         { Resetea el índice de posición,
                           si la posición apuntada es 0 }
                         If Monstruos[A].Posiciones[Monstruos[A].Indice]=0 Then
                           Monstruos[A].Indice:=1;
                         Monstruos[A].Pos:=Monstruos[A].Posiciones[Monstruos[A].Indice];
                         Inc(Monstruos[A].Indice);
                         { Comprueba si el monstruo ha entrado en la 
                           localidad donde se encuentra el jugador }
                         If Monstruos[A].Pos=LocalidadActual Then
                         Begin
                              TextColor(LightCyan);
                              Write('¡El ',Monstruos[A].Nombre,' ha entrado ');
                              Writeln('en la habitación !');
                         End;
                    End;
               End;
          End;
La rutina ha quedado algo larga (comparada con la de la primera versión), pero añade bastante realismo. Ahora bien, también hay que dar la oportunidad de contraataque contra los monstruos. Lo lograremos mediante el uso de la espada que hay en el salón principal de FangLore. Entramos el comando 'USAR ESPADA' y nuestro personaje intenta golpear al monstruo con la espada (si realmente está en nuestro inventario). Y así, dependiendo de la suerte, acertará o fallará. El factor suerte se basa en el factor Dificultad que también empleábamos al determinar si el jugador esquivaba los ataques de los monstruos. Aha, necesitamos añadir las líneas siguientes a la rutina Usar, en la seccín que controla el uso de la espada:
               Flag:=True;
               Flag2:=False;
               { Comprueba si hay bichos en la localidad del jugador }
               For A:=1 To MaxMons Do If Monstruos[A].Pos=LocalidadActual Then
                                         Flag2:=True;
               WriteLn;
               If Flag2=False Then
               Begin
                    TextColor(Green);
                    WriteLn('¿¿¿ Usarla con quién ???');
                    WriteLn;
               End;
               For A:=1 To MaxMons Do
               Begin
                    { Comprueba si el monstruo está en la misma
                      localidad que el jugador }
                    If Monstruos[A].Pos=LocalidadActual Then
                    Begin
                         { Intenta acertar a monstruo }
                         Write('Intentas golpear con la espada a ',
                                Monstruos[A].Nombre,'...');
                         { Comprueba si acertamos. Dificultad es un margen 
                           predefinido. Cuanto más alta, mayor 
                           será nuestra 'suerte' }
                         B:=Random(100);
                         If B>(Dificultad Div 2) Then
                         Begin
                              { ¡¡¡ Aciertas !!! }
                              WriteLn('¡¡¡¡ y aciertas !!!!');
                              { Asigna daños, teniendo en cuenta
                                la posible buena o mala suerte }
                              Danyo:=12+(Random(8)-5);
                              Dec(Monstruos[A].Salud,Danyo);
                              WriteLn('¡ Al monstruo únicamente le restan ',
                                       Monstruos[A].Salud,
                                       ' puntos de vida !');
                              { Comprueba si todavía está vivo }
                              If Monstruos[A].Salud<=0 Then
                              Begin
                                   { El monstruo muere... :) }
                                   WriteLn('Has matado a ',
                                            Monstruos[A].Nombre,'!');
                                   Writeln;
                              End;
                         End
                         Else
                         Begin
                              { Fallas... :( }
                              Writeln('¡¡¡¡ y fallas !!!!');
                              WriteLn('¡ Al monstruo le quedan ',
                                       Monstruos[A].Salud,
                                       ' puntos de vida !');
                         End;
                    End;
               End;
          End;
Me he dado cuenta de algo que olvidé hacer en los capítulos anteriores de este cursillo. Se trata de que en la definición de TipoMonstruo hemos de declarar Salud como ShortInt en vez de Byte. El problema está en algo que ocurre a los bytes cuando se resta... Echa un vistazo a la sección Tricks and Tips de este número y sabrás a qué me estoy refiriendo.

Otra cosa que también se me olvidó es chequear la Salud de los monstruos en VerMonstruo. ¡¡¡¡¡¡El problema es que tal como está ahora el enemigo golpearía incluso muerto!!!!!! Lo que hay que hacer es añadir otras tres líneas a VerMonstruo y comprobar que el monstruo está vivo antes de que nos mande al otro barrio. Y, finalmente, podemos añadir algo en la rutina Mirar que nos diga si hay el cadáver de algún bicho en la localidad. Estudia el listado completo de FangLore para ver los cambios. Son muy simples.

Localidades Especiales

Una localidad especial, como su propio nombre indica, es una localidad que tiene algo especial, algo diferente de las otras localidades. En FangLore, la única localidad especial es la Sala de Gas. Esta localidad mata al jugador en cuanto pone los pies en ella, salvo si lleva puesta la máscara.

Para implementar la localidad especial, tan solo añadimos unas cuantas lineas a la rutina Mirar:

     { Comprueba si el jugador está en una localidad especial, la Sala de Gas }
     If NumeroLoc=9 Then
     Begin
          TextColor(Yellow);
          Write('Has entrado en la sala de gas, ');
          { Comprueba si el jugador lleva la máscara de gas }
          If Mascara=True Then WriteLn('pero resultas ileso gracias a la máscara de gas.')
          Else
          Begin
               WriteLn('y has muerto, a causa del gas envenenado...');
               Write('Las Valquirias llegan para reclamar tu cuerpo... Pero ');
               WriteLn('no eres un guerrero, así que te dejan');
               Writeln('para que te pudras... ');
               Writeln;
               TextColor(Magenta);
               Writeln('Pulsa una tecla para finalizar FangLore...');
               Repeat Until Keypressed;
               Halt(0);
          End;
          WriteLn;
     End;
Esto es todo por este número. En el siguiente tendremos un artículo sobre examinar y manipular el escenario, y casi terminaremos la aventura... ¡NO! :)))


Nota del traductor: En los primeros números de 'The Mag' se realizó un cursillo de Pascal la mar de majo. Como comprenderéis no es cuestión de que lo reproduzcamos aquí, después de todo esto es una revista sobre aventuras, y no de programación. A los que no conozcáis Pascal y sepáis inglés os remito directamente a 'The Mag'. A los demás buscaos un buen tutorial en castellano, que seguro que los habrá perdidos en algún rincón de la red o miraros alguno de esos CD-ROMS que regalan las revistas, que puede que venga algun tutorial o cursillo interactivo de Pascal. Si el Pascal no os va, o simplemente conocéis algún otro lenguaje (BASIC, no gracias) no os será nada complicado el pasarlo.

ÚLTIMA REVISIÓN EN ABRIL DE 1999




DESARROLLO DE VIDEOJUEGOS
a
MACEDONIA Magazine