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:
- Un jardinero psicópata
- Un gusano
- 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;
- Nombre es el nombre o tipo del bicho.
- Pos es la localidad actual del monstruo.
- Indice y Posiciones son variables que controlan el movimiento.
Las veremos a continuación.
- Salud son los puntos de vida del monstruo. Digamos que la espada
(la única forma de acabar con los enemigos en FangLore) quita 20 puntos
de energía por toque (más un valor aleatorio, la suerte...
Lo veremos después).
- Fuerza es el indicador de cuán fuerte golpea el monstruo. Tendremos
que definir:
Var Salud:Integer;
La cual mantiene el estado de salud del jugador. Se empieza con 70 puntos,
definidos en:
Const SaludDePartida=70;
No te olvides de inicializar Salud con el valor de SaludDePartida, en la
rutina Init.
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