![]() | ![]() | ![]() | Respuestas a los ejercicios |
despues [;
Coger: if (seta hasnt general)
{
give seta general;
"Coges la seta cortando con cuidado su largo tallo";
}
"Coges la seta, que está cada vez en peor estado.";
Dejar: "La seta cae al suelo, ligeramente machacada.";
],
|
Como ya se mencionó, general es un atributo que la librería no usa, y está libre para que el programador le de el significado que prefiera. En el caso de la seta lo hemos usado para significar "la seta ya ha sido cogida al menos una vez". Observar que la primera vez que se coja, la seta no tiene el atributo general (pues inicialmente solo tiene el atributo comestible, tal como la habíamos programado), esto hará que se ejecute lo que hay dentro del if, donde le damos ese atributo mediante el comando give. Por tanto las sucesivas veces ya no volverá a entrar en el if porque la condición no se cumpliría (if (seta hasnt general) se traduciría por "si la seta no tiene el atributo general...")
Dentro de las rutinas que forman parte de la seta podemos referirnos a la propia seta con la palabra self, por lo que podríamos haber escrito también "if (self hasnt general) ..."
Object Medicina "botella de medicamentos con cierre a prueba de niños"
mesita
with nombre 'botella',
adjetivos 'medicamentos' 'medicina',
descripcion "~Sólo vale como antídoto. No tiene efecto
preventivo.~",
abrirse [;
give Medicina abierto ~cerrojoechado;
"Por fin la botella se abre con un crujido.";
],
has recipiente abrible cerrojoechado;
|
Cualquier otro objeto del juego puede enviar el mensaje Medicina.abrirse(); para causar la apertura de la botella y que el texto aparezca en pantalla. Para simplificar esta solución supone que la botella siempre está visible para el jugador en el momento en que es abierta, si no el texto no debería mostrarse.
En pocas palabras: Programa una rutina llamada RutinaPreJuego (es una especie de regla "super-antes" que se ejecuta antes que la de los objetos, para cualquier acción en el juego). Esta rutina deberá comprobar en primer lugar si otro contiene realmente un objeto (en lugar de contener nothing o un número). Si es así, deberá comprobar a continuación si ese objeto tiene una rutina llamada otro_antes (es un nombre de ejemplo, en realidad puedes llamar a esta rutina como prefieras, ya que no existe originalmente en la librería). Si la tiene, enviar el mensaje otro_antes al objeto otro y lo que responda el objeto es lo que devolverá la rutina RutinaPreJuego para terminar.
De este modo, la rutina otro_antes de la diana podría reaccionar ante la acción Lanzar, y la acción falsa RecibirLanzamiento ya no sería necesaria.
[ RutinaPreJuego;
if (accion == ##Dejar && uno notin jugador)
"No tienes ", (el) uno, ".";
rfalse;
];
|
Object niebla_naranja "niebla naranja"
with nombre 'niebla',
adjetivos 'naranja',
reaccionar_antes [;
Mirar: "No ves nada, con esta niebla naranja alrededor.";
Ir, Salir: "Caminas sin rumbo, tosiendo.";
Oler: if (uno==0) "¿Canela? No, nuez moscada.";
],
has escenario femenino;
|
Object obj_blanco "lado blanco" Brujula
with nombre 'blanco' 'sac' 'lado',
direcc_puerta al_n,
has escenario;
|
De igual modo definirías el objeto rojo, amarillo y negro. Observa cómo cada objeto "punto cardinal" ha de tener una propiedad llamada direcc_puerta que indica la dirección asociada con ese objeto. En este caso, el objeto blanco tiene asociada la dirección al_n, de igual modo el objeto amarillo tendría la dirección al_s, etc. No basta con definir los objetos anteriores, tenemos que quitar a los antiguos (de modo que el juego ya no comprenda más la orden "norte", sino solo "blanco" como equivalente). Para eliminar los antiguos podemos poner el siguiente código dentro de la función Inicializar:
remove obj_n; remove obj_s; remove obj_e; remove obj_o; |
En nuestras habitaciones, de todas formas, las salidas deberían escribirse usando las propiedades al_n, al_s, etc. Si quisiéramos ser más finos aún de modo que en las habitaciones podamos usar las direcciones al_blanco, al_rojo, etc. habría que definir estas nuevas direcciones como alias de las antiguas. Es decir, por ejemplo: Property al_blanco alias al_n;
Finalmente, como un detalle de estilo extra, digamos que los Mayas usaban el color turquesa yax para indicar la dirección "aquí", por lo que quedaría perfecto si el verbo "turquesa" causase la acción Mirar, lo cual se lograría así:
Verb 'turquesa' 'yax' * -> Mirar; |
[ IntercambiaDirecciones bruju1 buju2 aux; aux=bruju1.direcc_puerta; bruju1.direcc_puerta=bruju2.direcc_puerta; bruju2.direcc_puerta=aux; ]; |
Usando esta función es fácil hacer una que refleje el mundo en la dirección este-oeste. Basta cambiar este por oeste y no olvidar cambiar también sureste, y nordeste:
[ ReflejarElMundo; IntercambiaDirecciones(obj_e, obj_o); IntercambiaDirecciones(obj_ne, obj_no); IntercambiaDirecciones(obj_se, obj_so); ]; |
Para no alargarnos demasiado, la solución sería añadir justo al principio del juego:
Lowstring cadena_este "este"; Lowstring cadena_oeste "oeste"; [ MundoNormal; String 0 cadena_este; String 1 cadena_oeste; ]; [ MundoReflejado; String 0 cadena_oeste; String 1 cadena_este; ]; |
En la rutina Inicializar habría que llamar a MundoNormal y después cuando el mundo se reflejara llamar a MundoReflejado. Llamando a MundoNormal de nuevo las cosas volverían a estar como al principio. Además, por supuesto, hay que poner cuidado de nunca mencionar las palabras "este" ni "oeste" al describir las habitaciones, sino usar en su lugar "@00" y "@01", respectivamente. (Inform proporciona 32 cadenas variables de éstas).
Hemos añadido a la bolsa un mensaje para la acción Cerrar, aunque esto no se pedía en el enunciado del ejercicio.
Object bolsa "bolsa con dientes"
with nombre 'bolsa' 'con' 'dientes' 'boca' 'dentada',
descripcion "Una amplia bolsa, con una boca dentada.",
antes [;
DejarSalir:
"La bolsa, desafiante, se cierra y aprieta la boca ante tu
mano, hasta que desistes.";
Cerrar: "La bolsa se resiste a todo intento de ser cerrada.";
],
despues [;
Recibir:
"La bolsa se contorsiona de forma odiosa mientras
engulle", (el) uno, ".";
],
has recipiente abierto femenino;
|
Observar que no hemos capturado la acción BuscarEn, por lo que si el jugador pone "MIRA EN LA BOLSA", la librería se comportará de la forma estándar, que consiste en listar todos los objetos que hay en la bolsa (los que el jugador haya introducido, aunque no pueda sacarlos, puede todavía verlos).
Object televisor "televisor portátil" Salon
with nombre 'televisor' 'tele' 'television' 'portatil' 'tv',
antes [;
Encender: <<Encender boton>>;
Apagar: <<Apagar boton>>;
Examinar: <<Examinar pantalla>>;
],
has transparente conmutable;
Object boton "botón de encendido" televisor
with nombre 'boton' 'encendido' 'interruptor',
antes [;
Empujar:
if (self has encendido) <<Apagar self>>;
else <<Encender self>>;
],
despues [;
Encender, Apagar: <<Examinar pantalla>>;
],
has conmutable;
Object pantalla "pantalla del televisor" televisor
with nombre 'pantalla',
antes [;
Examinar: if (boton hasnt encendido) "La pantalla está
negra.";
"En la pantalla se retuercen unos dibujos manga.";
],
has femenino;
|
![]() |
Nota: El televisor, en rigor, no es un elemento conmutable,
ya que nunca cambia su estado encendido (siempre convierte
la acción Encender televisor en Encender boton. No
obstante, es necesario ponerle el atributo conmutable para
que la librería entienda que el verbo "Enciende" debe convertirse
en la acción Encender. Si el televisor no tuviera el
atributo conmutable la librería convertiría el comando
"ENCIENDE TELEVISOR" en la acción Quemar televisor. Esto
es así para permitir comandos como "enciende hoguera", "enciende
antorcha", etc. En general el verbo "enciende" se convierte en la
acción Quemar, a menos que el objeto tenga el atributo
conmutable, en cuyo caso se convierte en la acción
Encender.
Si queremos ser rigurosos y no darle al televisor el atributo conmutable (puesto que no conmuta realmente), entonces la solución sería capturar la acción Quemar en lugar de Encender dentro de la rutina antes del televisor (no obstante esto puede quedar muy extraño). Por cierto, el verbo "Quema televisor", causará siempre la orden Quemar televisor, con independencia de que éste sea conmutable o no. |
Object caja_cristal "caja de cristal con tapa" with nombre 'caja' 'cristal' 'tapa', has femenino recipiente abrible abierta transparente; Object caja_metal "caja metálica con tapa" with nombre 'caja' 'metalica' 'metal' 'tapa' has femenino recipiente abrible abierta; |
Object bolsa_redecilla "bolsa de redecilla" Bosque
with nombre 'bolsa' 'redecilla' 'red' 'saco',
reaccionar_antes [;
Examinar, Escuchar, BuscarEn, Oler:
rfalse;
default:
if (uno in self) print_ret (_El) uno, " está escondido en
la bolsa de redecilla.";
if (otro in self) print_ret (_El) otro, " está escondido en
la bolsa de redecilla.";
],
has recipiente transparente femenino;
Object "reloj de oro" bolsa_redecilla
with nombre 'reloj' 'oro',
descripcion "Curiosamente el reloj no tiene manecillas.",
reaccionar_antes [;
Escuchar:
if (uno == 0 or self) "El reloj hace un ruidoso tic-tac.";
];
|
Observa cómo la redecilla captura en reaccionar_antes cualquier acción que el jugador intente, para comprobar seguidamente si la acción era una de las permitidas (en cuyo caso retorna false para indicar a la librería que continúe como si nada). Si no era una de las acciones permitidas, lo siguiente será comprobar si esa acción involucra algún objeto que se halle dentro de la bolsa. Hay que verificar que ni uno ni otro están en la bolsa (el jugador podría intentar "ABRIR COFRE CON RELOJ", y debería obtener la respuesta de que el reloj está escondido en la bolsa.
No obstante el código anterior podría no funcionar bien si uno u otro fueran números (como en "PON EL REGULADOR A 25"). De acuerdo, es un caso poco frecuente, pero si queremos estar seguros habría que cambiar el código de la bolsa para que en lugar de consultar uno y otro consulte las variables dat1 y dat2, que son equivalentes salvo en el caso especial de que contengan un 1, lo que significaría que los objetos mencionados no eran tales objetos, sino números. Aprovechamos la modificación para añadir una rutina inicializar que mostrará la descripción de la bolsa al entrar en la habitación en que ésta se halla:
Object bolsa_redecilla "bolsa de redecilla" Bosque
with nombre 'bolsa' 'redecilla' 'red' 'saco',
reaccionar_antes [;
Examinar, Escuchar, BuscarEn, Oler:
rfalse;
default:
if (dat1~=1 && dat1 in self)
print_ret (_El) dat1, " está escondido en
la bolsa de redecilla.";
if (dat2~=1 && dat2 in self)
print_ret (_El) dat2, " está escondido en
la bolsa de redecilla.";
],
inicial [;
print "Del techo cuelga una bolsa de redecilla, muy
apretada";
if (children(self) ==0) ".";
print ". Dentro de ella puedes distinguir ";
EscribirListaDesde(child(self), ESPANOL_BIT);
".";
],
has recipiente transparente femenino;
|
La función children(self) nos devuelve el número de objetos que hay dentro de la bolsa. Comprobamos antes si es cero, pues en este caso no intentamos mostrar la lista de sus contenidos. La función EscribirListaDesde se usa para que el juego imprima una lista de objetos. Suele usarse como se muestra en el ejemplo anterior para listar los contenidos de un recipiente. En este ejemplo el resultado sería "Dentro de ella puedes distinguir un reloj de oro."
Object puente_colgante "puente colgante"
with nombre 'puente' 'colgante' 'madera' 'fragil' 'precario',
descripcion "Parece extremadamente frágil y precario.",
si_abierto "Un precario puente colgante salva el abismo.",
puerta_a [;
if (children(jugador)>0)
{
banderafin=1;
"Das un paso ansioso sobre el puente, que se
balancea con tu peso. ¡Pero el peso de lo que llevas
contigo es la gota que colma el vaso! Se oye un
horrendo crak...";
}
print "Das un paso ansioso sobre el puente, dando
gracias de que no ir cargado.^";
if (localizacion==LadoCercano) return LadoLejano;
else return LadoCercano;
],
direcc_puerta [;
if (localizacion==LadoCercano) return al_s;
else return al_n;
],
esta_en LadoCercano LadoLejano,
has estatico puerta abierta;
|
Esta solución no es lo bastante general. Si tu juego contiene PNJs (Personajes No Jugadores) que pueden caminar de un lado a otro y son lo bastante listos como para ejecutar las rutinas puerta_a de las puertas que encuentran, entonces no es correcto comprobar la variable localizacion ya que esta sólo dice dónde está el jugador, y no dónde está el PNJ. Lo más sencillo sería modificar el código anterior para que se ejecute sólo si la variable actor es igual a jugador. La solución más complicada sería que el código anterior comprobara quién es el actor y se comporte adecuadamente para cada actor.
Otra solución sería usar el módulo extra PNJMovil (el cual contiene documentación de cómo programar puertas que puedan ser atravesadas por los PNJ).
Object jaula "jaula de hierro" Bosque
with nombre 'jaula' 'hierro' 'barrote' 'barrotes',
si_abierta
"Una jaula de barrotes de hierro, lo bastante grande como
para entrar, se alza ominosa.",
si_cerrada "La jaula está cerrada.",
descripcion_dentro "Miras a través de los barrotes.",
has femenino entrable recipiente abrible abierto transparente
estatico;
|
antes [;
Ir: if (uno==obj_o)
{
print "El cochecito no cabe por la puerta.^";
return 2;
}
if (cochecito has encendido) "¡Brumm, Brummmm!";
else print "(El motor está apagado.)^";
],
|
if (otro==obj_arriba) <<EmpujarDir self obj_n>>; if (otro==obj_abajo) <<EmpujarDir self obj_s>>; |
Object Biblia "Biblia" objjugador
with nombre 'biblia' 'sagradas' 'escrituras' 'nuevo' 'testamento',
descripcion "Una edición de coleccionista, que sobrevivió a
los incendios de 1520.",
antes [ evangelista evangelio versiculo;
Consultar:
np=consultar_desde;
evangelista=SiguientePalabra();
if (evangelista=='san')
{
evangelista=SiguientePalabra();
consultar_num_palabras--;
}
switch(evangelista)
{
'lucas': evangelio= "Evangelio de San Lucas";
'juan': evangelio= "Evangelio de San Juan";
'marcos':evangelio= "Evangelio de San Marcos";
'mateo': evangelio= "Evangelio de San Mateo";
default: "Sólo contiene los cuatro Evangelios";
}
if (consultar_num_palabras==1)
"Lees el ", (string) evangelio, " de principio a
fin.";
versiculo=IntentarNumero(np);
if (versiculo==-1000)
"Esperaba el número del versículo.";
"El versículo ", (numero) versiculo, " del ", (string)
evangelio, " es demasiado sagrado para que puedas
comprenderlo ahora.";
],
has femenino;
|
Object psiquiatra "psiquiatra barbudo"
with nombre 'siquiatra' 'psiquiatra' 'barbudo' 'sicologo'
'psicologo' 'doctor',
inicial "Un psiquiatra barbudo te observa atentamente.",
vida [;
"Está fascinado por tu conducta, pero no quiere interferir
con ella en forma alguna.";
],
reaccionar_antes [;
Meter:
print "~El sujeto pone ", (el) uno, " en ",
(el) otro, ". Interesante.~^^";
Mirar:
print "~Haga como que no estoy,~ dice el psiquiatra^";
],
reaccionar_despues [;
Coger, Sacar:
print "~El sujeto echa de menos ", (un) uno, ". ¿Posible
complejo de Edipo? Mmm.~^";
],
has animado;
|
[ MejorDecirloAsi; "[Para hablar con alguien ponlo así: ~Alguien, algo~ o bien ~Pregunta a alguien sobre algo~.]"; ]; Extend "responde" replace * topic -> MejorDecirloAsi; Extend "habla" replace * topic -> MejorDecirloAsi; |
Un problema de esta solución es que si el jugador pone "Orco, hablame de lo que sea", no funcionará. Normalmente la librería convertiría esta acción en Preguntar, pero en este caso no puede porque hemos cambiado la gramática del verbo "habla".
Este problema puede corregirse si en vez de redefinir la gramática redefiniéramos la rutina HablarSub (usando Replace).
Object -> computador "computador"
with nombre 'computador' 'ordenador',
ordenes [;
Alfa: "~Asignado el valor ", uno, " a alfa.~";
default: "~Orden no comprendida.~";
],
has hablable;
...
[ AlfaSub;
"Esto tienes que decírselo a tu computador.";
];
Verb 'alfa'
* 'es' number -> Alfa;
|
Object -> Carlota "Carlota"
with nombre 'carlota',
gramatica [;
give self ~general;
np=palabra_verbonum;
if (SiguientePalabra()=='simon' &&
SiguientePalabra()=='dice')
{
give self general; ! Debe obedecer
palabra_verbonum=palabra_verbonum+2;
}
],
ordenes [;
if (self hasnt general) "Carlota te saca la lengua.";
Saltar: "Carlota salta con entusiasmo.";
default: "Carlota dice, ~Lo haría, pero no sé como.~";
],
inicial "Carlota quiere jugar a Simón dice.",
has animado propio femenino;
|
Primero necesitamos añadir el verbo "aplaudir" a la gramática (esto es fácil). Después daremos a Carlota la propiedad cantidad, inicialmente con valor 0, por ejemplo. Finalmente añadiremos las siguientes líneas al final de la rutina gramatica de Carlota:
self.cantidad=IntentarNumero(palabra_verbonum);
if (self.cantidad~=-1000)
! Si es -1000 indica que no era realmente un número
{
accion=##Aplaudir;
uno = 0;
otro = 0;
rtrue; ! La gramatica de Carlota ha interpretado
! la linea, el parser no debe hacer más
}
|
Ahora la rutina ordenes de Carlota deberá contener un caso más para la acción Aplaudir (el código siguiente usa una variable local i que habría que declarar en la cabecera de ordenes, entre el [ y el ;). El nuevo caso podría ser como esto:
Aplaudir:
if (self.cantidad==0) "Carlota se cruza de brazos.";
for (i=0:i<self.cantidad:i++)
{
print "¡Clap! ";
if (i==100)
print "(Ya te estás arrepintiendo de esto.) ";
if (i==200)
print "(Qué chica más tenaz.) ";
}
if (self.cantidad>100)
"^^Carlota casi se queda sin respiración.";
"^^~¡Fácil!~ dice Carlota.";
|
Nota: si el código anterior es simplemente añadido como se indica a la versión previa de Carlota, entonces Carlota se negará a aplaudir ante el comando "Carlota, tres" por ejemplo, y debes poner en su lugar "Carlota, simón dice tres" para que aplauda. Para que acepte "Carlota, tres"la solución sería ligeramente diferente.
Object Dan "Dan Dislexic"
with nombre 'dan' 'dislexic',
gramatica [;
if (palabra_verbo=='coge' or 'toma')
{
palabra_verbonum++;
return 'deja';
}
if (palabra_verbo=='deja')
{
palabra_verbonum++;
return 'toma';
}
],
ordenes [;
Coger:
PreguntaSiNo=1;
print "~¿Qué?~ dice Dan, ~¿Quieres que ";
if (dialecto_sudamericano) print "tome ";
else print "coja ";
print_ret (el) uno, "?~";
Dejar:
PreguntaSiNo=1;
"~¿Qué?~ dice Dan, ~¿Quieres que deje ", (el) uno,
"?~";
Inv: "~Eso es fácil,~ dice Dan. ~No llevo nada.~";
No: "~Como pues prefieras.~";
Si: "~Tendría que pensar sobre ello.~";
default: "~No sé hacerlo,~ dice Dan.";
],
inicial "Aquí está Dan Dislexic.",
has animado propio;
|
![]() |
El código anterior usa algunas características específicas de
Informate, para manejar el caso del
dialecto_sudamericano. Como probablemente sabrás, en la
mayoría de los países sudamericanos el verbo "coger" es una
palabra malsonante (que significa "joder") y en su lugar se
utiliza "tomar". El juego puede estar funcionando en dialecto
sudamericano o no (esto lo elige el propio jugador mediante un
comando, por lo que puede cambiar durante el juego). Para
saber si el dialecto sudamericano está activado basta consultar la
variable dialecto_sudamericano. En el código anterior
usamos esa variable para que Dan hable correctamente. Observar que
retornamos 'toma' en vez de 'coge' pues
'coge' tiene diferente significado según el dialecto,
mientras que 'toma' significa siempre lo mismo (genera la
acción Coger).
Otra característica que vemos en el código anterior es el uso de la variable PreguntaSiNo. Esta variable debe ponerse a 1 "a mano" cada vez que el juego imprima una frase que pueda ser interpretada como una pregunta. Por ejemplo, si el juego pone algo como "¿Qué? ¿Pretendes abrir la puerta sin llave?" la variable PreguntaSiNo debería ser puesta a 1 antes de mostrar dicho texto. Si esta variable está activada y el jugador escribe "NO", se generará la acción No, lo cual puede ser capturado por el juego para hacer algo, aunque lo normal es que simplemente cause la respuesta estándar "Solo era una pregunta retórica". La variable se vuelve a poner a cero automáticamente en el turno siguiente. Cuando la variable PreguntaSiNo vale cero, el comando "NO" genera la acción Ir obj_no (ir al noroeste). Observar que lo anterior se refiere a preguntas realizadas por personajes del juego, o implícitas en algunas descripciones o respuestas estándares, pero no tiene nada qué ver con las preguntas del estilo "¿Estás seguro de que quieres abandonar?". Estas son "meta-preguntas" que no generan acciones. |
if (palabra_verbo=='examina' or 'x')
{
palabra_verbonum++;
return -'danx,';
}
|
Observa lo crudo de la solución, hay que comparar palabra_verbo con sus posibles valores "a mano", es decir, tienes que chequear tú mismo todos los posibles sinónimos. El verbo danx, debería ser declarado más adelante (en la zona de la gramática):
Verb 'danx,' * 'pertenencias' -> Inv; |
De este modo "Dan, examina pertenencias" generará la acción Inv, y sin embargo "Dan, examina espada" generará Examinar espada (gracias a que la rutina gramatica de Dan retorna un valor negativo, que indica al parser que si no logra encajar lo que ha escrito el usuario con el verbo 'danx,' debe intentar encajarlo con la gramática estándar).
[ ImprimirHora x;
print (x/60), ":", (x%60)/10, (x%60)%10;
];
Object reloj_alarma "reloj con alarma" jaula
with nombre 'reloj',
adjetivos 'con' 'alarma',
cantidad 480,
descripcion [;
print "La alarma está ";
if (self has general) print "activada, ";
else print "desactivada, pero ";
"el reloj marca las ", (ImprimirHora) la_hora,
" y la alarma está fijada para las ",
(ImprimirHora) self.cantidad, ".";
],
reaccionar_despues
[;
Inv: if (self in jugador) { new_line; <<Examinar self>>; }
Mirar: if (self in localizacion) { new_line; <<Examinar self>>; }
],
daemon
[; if (la_hora >= self.cantidad && la_hora <= self.cantidad+3
&& self has general)
"^¡Pip! ¡Pip! La alarma suena.";
],
gramatica [; return 'alarma,'; ],
ordenes [;
Encender:
give self general; ArrancarDaemon(self);
"~Alarma activada.~";
Apagar:
give self ~general;
PararDaemon(self);
"~Alarma desactivada.~";
PonerA:
self.cantidad=uno;
<<Examinar self>>;
default:
"~Los comandos que comprendo son ON, OFF o una
hora del día, graciass.~";
],
vida [;
Preguntar, Responder, Hablar:
"[Intenta, ~reloj, lo que sea~ para dirigirte al reloj.]";
],
has hablable conmutable;
|
Cuando el jugador intenta dirigirse a la alarma o al reloj (son sinónimos) se genera el verbo falso 'alarma,'. Así que hay que definir una gramática para este verbo. Sería como sigue:
Verb 'alarma,'
* 'on' -> Encender
* 'off' -> Apagar
* HoraDelDia -> PonerA;
|
De este modo el parser admitirá la palabra 'on', la palabra 'off' o bien algo que pueda interpretarse como una hora. El parser no sabe qué es lo que puede interpretarse como hora (pueden ser cosas tan variadas como "20:43" o "la una menos diez"). En este caso le estamos diciendo que llame a la función HoraDelDia para determinarlo. Esta función no forma parte de la librería, sino que es la respuesta a un ejercicio futuro (bastante difícil, por cierto). El objetivo de esta función sería examinar lo que ha escrito el jugador y determinar si puede interpretarlo como si fuera una hora. Si lo logra, pondrá en la variable numero_interpretado el valor resultante (el número de minutos desde medianoche equivalente a la hora descifrada). Si no lo logra retorna -1. En este caso el parser enviará a la alarma la acción NoComprendido.
Object tricorder "tricorder"
with nombre 'tricorder',
gramatica [; return 'tc,'; ],
ordenes [;
Examinar: if (uno==jugador)
"~Emites signos de vida.~";
print "~", (_El) uno;
if (uno hasnt animado) print " no";
" emite signos de vida.~";
default: "El tricorder chirría.";
],
vida [;
Preguntar, Responder, Hablar:
"El tricorder es demasiado simple.";
],
has hablable;
...
Verb "tc,"
* noun -> Examinar;
|
Object replicador "replicador" jaula
with nombre 'replicador',
gramatica [; return 'rc,'; ],
ordenes [;
Dar:
if (uno in self)
"El replicador sirve una copa ", (del) uno, " que bebes
ansiosamente.";
"~No puedo replicar eso.~";
default: "El replicador es incapaz de obedecer.";
],
vida
[;
Preguntar, Responder, Hablar:
"El replicador no tiene habilidades de conversación.";
],
has hablable;
Object "té gris de Jupiter" replicador
with nombre 'te' 'gris' 'jupiter',
has propio;
Object "brandy de Aldebaran" replicador
with nombre 'brandy' 'aldebaran',
has propio;
Object "agua destilada" replicador
with nombre 'agua' 'destilada',
has propio;
Verb 'rc,'
* held -> Dar;
|
Aquí la gracia está en la gramática, que usa el token llamado held. Este token indica que el comando debe aplicarse preferiblemente a una de las posesiones del actor, en este caso del replicador. Y ya que hemos puesto dentro del replicador los tres objetos que es capaz de replicar, el parser intentará encajar siempre con estos. Esto significa que si decimos "Replicador, agua", el objeto resultante será el "agua destilada" que contiene el replicador, incluso aunque hubiera otros objetos llamados "agua" cerca. Si no consigue encajar con ninguna de las pertenencias del replicador, entonces intentará encajar con otros objetos cercanos. Por eso en el replicador debemos comprobar si el objeto pedido (uno) realmente está en el replicador (uno in self) antes de servirlo.
ordenes [;
Examinar:
if (parent(uno)==0)
"~", (_El) uno, " ya no está a bordo de este juego.~";
"~", (_El) uno, " está en ", (el) parent(uno), "~.";
default:
"El computador realmente sólo puede localizar a los miembros de
la tripulación.";
],
|
La rutina gramatica del comunicador simplemente retorna 'com,' y este verbo se define como sigue. (Nota, hay que cambiar las reglas del alcance para este verbo, ya que el jugador nombrará objetos que no están cerca o a la vista, esto obliga a escribir una rutina para determinar qué objetos están accesibles para este verbo y cuáles no, este es el cometido de la rutina Tripulacion que vemos aquí).
[ Tripulacion i;
switch(estadio_alcance)
{
1: rfalse;
2: objectloop (i has MiembroTripulacion) PonerAlAlcance(i);
rtrue;
}
];
Verb 'comm,'
* 'donde' 'esta' scope=Tripulacion -> Examinar;
|
Observar que la rutina Tripulacion no necesita hacer nada en el estadio 3 (que es usado normalmente para imprimir errores), dado que este estadio no será alcanzado nunca. Si el jugador pone algo como "Computador, donde esta asdfg", el parser generará la acción NoComprendido que se enviará a la rutina ordenes del comunicador.
Object Zen "Zen" CabinaControl
with nombre 'zen' 'computador' 'vuelo' 'ordenador',
inicial
"Cuadrados de luz parpadean de forma impredecible a lo
largo de un mosaico hexagonal en una pared, indicando que
Zen está encendido.",
gramatica [; return -'zen,'; ],
ordenes [;
Mostrar:
"La pantalla principal muestra un campo de estrellas,
girando ", uno, " grados.";
Ir:
"~Confirmado.~ La nave gira hacia un nuevo objetivo.";
PonerA:
if (uno==0) "~Confirmado.~ La nave se detiene.";
if (uno>12) "~Velocidad estándar por ", (numero) uno, "
excede los límites del diseño.~";
"~Confirmado.~ Los motores de la nave la impulsan a
velocidad estándar por ", (numero) uno, ".";
Coger:
if (uno~=muro_fuerza) "~Por favor, clarifique.~";
"~Muro de fuerza levantado.~";
Dejar:
if (uno~=blasters) "~Por favor, clarifique.~";
"~Computadores de batalla activados. Cañones de neutrones
listos para disparar.~";
NoComprendido: "~Bancos de lenguaje incapaces de interpretar
comando.~";
default: "~Información. Esa función no está disponible.~";
],
has hablable propio estatico;
Object muro_fuerza "muro de fuerza" zen
with nombre 'muro' 'fuerza' 'escudos';
Object blasters "cañones de neutrones" zen
with nombre 'cañones' 'neutrones' 'blasters',
has nombreplural;
Verb 'zen,'
* 'rastrea' 'orbita' number -> Mostrar
* 'pon' 'rumbo' 'a//' Planeta -> Ir
* 'velocidad' 'estandar' number -> PonerA
* 'activa' held -> Coger
* 'prepara' held -> Dejar;
|
La palabra Planeta en la gramática indica una rutina llamada Planeta que deberá ocuparse de comprobar si lo que ha escrito el jugador es aplicable a un nombre de planeta. Esta rutina no se muestra. Tampoco se ha programado ninguna regla en la propiedad vida (en particular, para las capacidades de conversación del computador).
[ AlAlcance;
if (accion_que_seria == ##Examinar or ##Mostrar)
PonerAlAlcance(noslen_maharg);
if (razon_alcance == RAZON_HABLAR)
PonerAlAlcance(noslen_maharg);
];
|
La variable accion_que_seria contiene la acción que está considerando el parser. Si consigue interpretar correctamente el resto de la orden del jugador esta acción se guardará en la variable accion, pero mientras se está a medias en las deducciones se mantiene en accion_que_seria, por tanto es ésta la que hay que consultar en este momento.
En este caso hay que poner a Marta al alcance del jugador, a efectos de conversación, pero también hay que poner al jugador al alcance de Marta, para que ésta pueda darle cosas (si no lo hiciéramos Marta no podría comprender la orden "Marta, dame la pelota", ya que esto se convierte internamente en "Marta, da -me la pelota", siendo "-me" el nombre del objeto jugador que debe estar en el alcance de Marta para poder interpretar esa orden). La rutina AlAlcance sería en este caso:
[ AlAlcance actor;
if (actor==marta) PonerAlAlcance(jugador);
if (actor==jugador && razon_alcance==RAZON_HABLAR)
PonerAlAlcance(marta);
rfalse;
];
|
En cuanto al código de Marta (encerrada en una habitación sellada que puede describir si el jugador le da la orden "Marta, mira" y con una pelota roja que puede "teleportar" si el jugador se lo pide), sería como sigue:
Object habitacion_sellada "Habitación Sellada"
with descripcion
"Estoy en una habitación sellada, como un patio cuadrado,
sin puertas, de unos cinco o seis metros de ancho",
has luz;
Object -> pelota "pelota roja"
with nombre 'pelota' 'roja',
has femenino;
Object -> marta "Marta"
with nombre 'marta',
ordenes [r;
r=parent(self);
Dar:
if (uno notin r)
"~Eso excede mis poderes de telekinesia.~";
if (uno==self)
"~Teleportarme sería demasiado difícil para mi.~";
move uno to jugador;
"~Allá va...~ y los poderes telekinésicos de Marta
mágicamente hacen aparecer ", (el) uno,
" en tus manos.";
Mirar:
print "~", (string) r.descripcion;
if (children(r)==1)
". Aquí no hay nada, salvo yo misma.~";
print ". Puedo ver también ";
EscribirListaDesde(child(r),OCULTAR_BIT+ESPANOL_BIT);
".~";
default:
"~Siento no poder ayudarte en eso.~";
],
vida [;
Preguntar: "~Tendrás que apañartelas solo con eso.~";
Hablar: "Marta escucha comprensiva.";
Responder: "~Caramba,~ responde Marta.";
],
has animado femenino oculto propio;
|
El atributo oculto de Marta es para que no aparezca en la descripción de la habitación que ella misma hace, cuando utiliza EscribirListaDesde (para ello especifica OCULTAR_BIT que indica precisamente que no se mencionen los objetos con el atributo oculto).
laoscuridad.inicial=VuelaMurcielagoVuela; |
Y añade la rutina:
[ VuelaMurcielagoVuela;
if (murcielago in player)
{
remove murcielago;
"Mientras tus ojos tratan de adaptarse, notas que algo de lo que
llevas se sacude, y después un sonido aleteante.";
}
];
|
Lo que sigue es una programación muy elemental, para ser breve (el verdadero ladrón de Zork tiene un enorme conjunto de mensajes asociados). La rutina vida se ha omitido y además este ladrón no roba realmente nada. Si quieres ver una implementación muchísimo más detallada y anotada consulta el listado del juego-demostración "El ladrón". También puedes usar la librería PNJMovil.h que te permitirá crear personajes que caminan a sus anchas por el juego de una forma mucho más sencilla.
Object -> ladron "ladrón"
with nombre 'ladron' 'caballero' 'hombre' 'modo' 'mahu',
cada_turno "^El ladrón gruñe amenazador.",
daemon
[ i p j n k; ! Las variables locales necesarias para calcular
! a dónde puede ir
if (random(3)~=1) rfalse; ! no se mueve
p=parent(self); ! p=habitación donde está
objectloop (i in brujula) ! i= obj_n, por ejemplo
{
j=p.(i.direcc_puerta); ! i.direcc_puerta = al_n
! j=a dónde lleva la salida
! correspondiente en la
! habitación p
if (j ofclass Object && j hasnt puerta)
n++; ! La contamos como posible salida
! (no contamos las puertas, ni las
! salidas que no llevan a otro lugar)
}
if (n==0) rfalse; ! No hay salidas!
! Elegimos una al azar entre las n encontradas
k=random(n); n=0;
objectloop (i in Brujula)
{
j=p.(i.direcc_puerta);
if (j ofclass Object && j hasnt puerta) n++;
if (n==k) ! Elegida la salida k-ésima
{
move self to j;
if (p==localizacion) "^¡El ladrón se marcha!";
if (j==localizacion) "^¡Ha entrado el ladrón!";
rfalse;
}
}
],
has animado;
|
Debes acordarte de ejecutar ArrancarDaemon(ladron) en algún punto del juego, por ejemplo en la rutina Inicializar. El ladrón camina al azar, pero nunca atraviesa puertas (incluso aunque estén abiertas), ni puentes o similares. Tampoco irá por salidas que tengan asociada una rutina. Solo tomará las salidas que lleven a otra habitación directamente; es una primera aproximación. En un buen juego debería ser posible ver ocasionalmente cómo el ladrón hace algo sorprendente, como abrir una puerta secreta. En cuanto a los sinónimos del nombre, observa que "El príncipe de las tinieblas es un caballero. Modo es llamado, y Mahu" (William Shakespeare, King Lear III iv).
[ CuantoPesa obj t i; if (obj provides peso) t = obj.peso; else t = 10; objectloop (i in obj) t = t + CuantoPesa(i); return t; ]; |
Al final de cada turno debemos comprobar cuánto peso lleva el jugador y actualizar su nivel de fatiga según lo que lleva y lo que ha estado llevando en turnos anteriores. Habría muchas fórmulas posibles para hacer esto pero en este ejemplo usaremos lo siguiente: empezamos por definir dos constantes:
Constant FUERZA_DE_ACARREO = 500; Constant UMBRAL_DE_PESO = 100; |
Inicialmente ponemos la fuerza del jugador al máximo posible, que será 500 (la primera contante). Al final de cada turno restaremos a la fuerza del jugador el peso que lleva y le sumaremos 100 (la otra constante) dejando el resultado en 500 si excede ese valor. De este modo, mientras el jugador lleve peso por debajo de 100 su fuerza se mantendrá en 500. Si lleva peso mayor de 100 su fuerza irá bajando en cada turno, proporcionalmente al exceso de peso. Si deja objetos de modo que el peso transportado vuelva a estar por debajo de 100 su fuerza irá aumentando de nuevo en cada turno hasta llegar a 500 otra vez. Observa que si suelta todos los objetos que transporta, recobrará toda su fuerza tras 5 turnos como máximo.
Si su fuerza llega a 0, se encontrará exhausto y le obligaremos a soltar algo (el objeto más pesado). Aquí tienes la implementación de todas estas ideas, en forma de un objeto "imaginario" (que nunca será encontrado en el juego, pero cuyo daemon se ejecutará en cada turno durante todo el juego, suponiendo que ha sido activado en Inicializar mediante la orden monitor_de_peso.activar()):
Object vigilante_del_peso
with fuerza_del_jugador,
nivel_de_avisos 5,
activar
[;
self.fuerza_del_jugador = FUERZA_DE_ACARREO;
ArrancarDaemon(self);
],
daemon
[ w f b bw; ! Variables locales
f = self.fuerza_del_jugador
- CuantoPesa(jugador) + UMBRAL_DE_PESO;
if (f<0)
f=0;
if (f>FUERZA_DE_ACARREO)
f=FUERZA_DE_ACARREO;
self.fuerza_del_jugador = f;
if (f==0)
{
! Vamos a buscar el mas pesado
bw=-1;
objectloop(b in jugador)
if (CuantoPesa(b) > bw) {
bw = CuantoPesa(b); w=b;
}
self.fuerza_jugador = self.fuerza_jugador + bw;
print "^Exhausto por llevar tanto peso, decides
dejar ", (el) w, ": ";
<<Dejar w>>;
}
w=f/100;
if (w==self.nivel_de_aviso)
return;
self.nivel_de_aviso = w;
switch(w)
{
3: "^Empiezas a sentirte cansado.";
2: "^Lo que llevas pesa mucho.";
1: "^El llevar tanto peso te está agotando.";
0: "^Estás tan exhausto que podrías soltar algo en el
momento más inoportuno.";
}
];
|
Observar que para dejar los objetos generamos una acción Dejar. Esto es lo mejor ya que el objeto dejado podría reaccionar ante esta acción (por ejemplo, podría ser un animal que huya al ser soltado).
Ver la respuesta siguiente
Object pisadas "sonido de diminutas pisadas" laoscuridad
with nombre 'diminutas' 'pisadas' 'sonido' 'cosas' 'criaturas'
'monstruos' 'insectos',
inicial "Unas diminutas pisadas se arrastran por algún
sitio.",
antes [;
Escuchar: "Qué inteligentes suenan, para ser meros insectos.";
Tocar, Probar: "No, realmente no lo deseas.";
Oler: "Tan solo hueles tu propio miedo.";
Atacar: "Fácilmente esquivan tus intentos de ataque en la
oscuridad.";
default: "Las criaturas te esquivan, haciendo ruiditos.";
],
cada_turno [; ArrancarDaemon(self); ],
cantidad 0,
daemon
[;
if (localizacion~=laoscuridad) {
self.cantidad=0;
PararDaemon(self);
rtrue;
}
switch(++(self.cantidad))
{
1: "^Las pisadas se arrastran un poco más cerca, y tu
respiración se hace más pesada y ronca.";
2: "^La transpiración del terror te corre por las
cejas. ¡Las criaturas están casi aquí!";
3: "^Sientes unos tirones en tus extremidades y das una
patada al aire, sacudiendo algo gomoso. Su sonido
es un chirrido amenazante.";
4: banderafin=1;
"^De pronto hay un diminuto dolor, como una aguja
hipodérmica hundiéndose en tu pantorrilla. Casi
simultáneamente tus piernas se sacuden, tus hombros y
rodillas se juntan, tu lengua se retuerce...";
}
];
|
Puedes hacer que un daemon consulte continuamente la variable la_hora comparando su nuevo valor con el anterior y detectando cuando baja bruscamente. También puedes hacer esto mismo desde la función PasaElTiempo.
Una solución mínima:
Constant AMANECER 360; ! es decir, las seis de la mañana
Constant OCASO 1140; ! es decir, las siete de la tarde
Attribute airelibre; ! Este atributo hay que ponerlo en los
! lugares al aire libre
Attribute iluminado; ! Y este a los que tienen iluminación
! artificial
Global estado_del_dia = 2;
[ PasaElTiempo f obj;
if (la_hora >= AMANECER && la_hora < OCASO) f=1;
if (estado_del_dia == f) rfalse; ! No cambia el estado
objectloop (obj)
{
if (obj has iluminado) give obj luz;
if (obj has airelibre && obj hasnt iluminado)
{
if (f==0) give obj ~luz;
else give obj luz;
}
}
if (estado_del_dia==2) {
estado_del_dia = f;
return;
}
estado_del_dia = f;
if (localizacion hasnt airelibre) return;
if (f==1)
"^¡Sale el sol, iluminando el paisaje!";
"^Cuando el sol se va el paisaje queda sumergido en la oscuridad.";
];
|
En la rutina Inicializar debes llamar a PonerLaHora para fijar la hora inicial a la que empieza el juego y después llamar a PasaElTiempo para que ilumine todos los lugares de forma adecuada. Observa que con este sistema no necesitas poner a mano el atributo luz al programar los lugares, todo es automático.
No podrías usar las funciones relacionadas con la hora proporcionadas por la librería, sino que tendrías que programar tus propias funciones para manejar el paso del tiempo. Esto puede hacerse a través de la rutina PasaElTiempo(). También sería conveniente que dieras al jugador los verbos "hora" o incluso "fecha" para que éste pueda conocer la hora y fecha en que se halla el juego.
Por dos razones. Primero, hay veces en las que querrás capturar órdenes de otros personajes, lo cual no es posible con reaccionar_antes. Segundo, la rutina reaccionar_antes del jugador no es necesariamente la primera en reaccionar. En el caso de la sordera del jugador, un reloj de cuco podría haber usado ya su rutina reaccionar_antes para cantar. Usar la función RutinaPreJuego sería lo más adecuado, aunque el código quedaría un poco más confuso ya que un código relacionado con el jugador no estaría en cambio formando parte de la definición del mismo. En la sección sobre acciones y reacciones tienes la secuencia exacta de de eventos que se genera cuando se procesa una acción.
ordenes
[; if (mascara_de_gas hasnt puesto) rfalse;
if (actor==self
&& accion~=##Responder or ##Hablar or ##Preguntar)
rfalse;
"Tu discurso es convertido en silencio por la máscara de
gas.";
],
|
El wayhel más corriente suele ser un ratoncito. Pero como nuestro concepto del jugador es mucho más elevado, lo convertiremos en un:
Object jabali "Jabalí verrugoso" Caldera
with nombre 'jabali' 'verrugoso',
descripcion "Embarrado y gruñendo.",
cantidad 0,
inicial "Un jabalí verrugoso olfatea y gruñe entre las cenizas.",
ordenes [;
Ir, Mirar, Examinar, Comer, Oler, Probar, Tocar: rfalse;
default: "¡Los jabalíes verrugosos no pueden hacer cosas tan
complicadas!";
],
has animado;
|
Podemos cambiar al jugador por este animal simplemente con CambiarJugador(jabali). Observar que las reglas que hay en la propiedad ordenes se aplican tanto para el jugador-humano que pone "jabali, huele",como para el jugador-jabalí que pone "huele".
ordenes [;
if (jugador==self)
{
if (actor~=self)
"Sólo consigues enredarte la lengua y balbucear.";
rfalse;
}
Atacar: "El gigante te mira con ojos tristes. ~¡Yo no ser
tan malo!~'';
default: "El Gigante es incapaz de comprender
tus instrucciones.";
],
|
Pon en el objeto "tablero de ajedrez" una rutina nombre_corto (seguramente ya tendrá una para imprimir mensajes como "casilla A7" según la casilla que ocupe el jugador). Haz que esta rutina escriba "El tablero de ajedrez gigantesco" en el caso de que la variable accion tenga el valor ##Lugares.
Pon lo siguiente entre la inclusión de "EParser" y la de "Acciones":
Object MensajesLibreria
with antes [;
Prompt: if (turnos==1)
print "¿Qué harás ahora, detective?^>";
else
print "¿Ahora qué?^>";
rtrue;
];
|
Tendrías que leerte el Inform Translator's Manual. En él se describe como extender Inform para que "comprenda" otros lenguajes distintos del inglés. No obstante, ese manual está escrito para la librería original y no para InformATE.
Para el caso de InformATE tendrías que reescribir el fichero Espanol.h y el fichero Gramatica.h. Las modificaciones que tendrías que hacer se indican en el Translator's Manual pero los nombres de variables y funciones que allí se mencionan están en inglés. Necesitarás usar también el manual de referencia InformATE para ver cuáles son sus equivalentes en español (en InformATE).
Es importante que comprendas que una cosa es el lenguaje en que están escritas las variables y rutinas (por ejemplo la acción Mirar) y otra cosa diferente es el lenguaje que "habla" el juego (la acción Mirar podría generarse con el verbo "mira" si habla español, o con el verbo "regardez" si habla francés).
El trabajo para adaptar Inform a otros lenguajes es muy grande, pero puede hacerse, como demuestra la existencia de InformATE :-)
pronombre como esta:
[ pronombre obj; if (obj has femenino) print "ella"; else print "él"; ]; |
Recuerda que, después de ejecutar listarse, si la rutina retorna
false, la librería intentará imprimir el nombre del objeto precedido
de su artículo indefinido, para lo cual usará las propiedades
articulo y nombre_corto si las hay. Por tanto, la solución es
imprimir el texto "una odiosa" en la rutina articulo y "caja" en
nombre_corto.
Ahora bien, si queremos que este nombre alterado sólo salga en el
inventario y no en las listas de objetos de las habitaciones, el truco
estará en usar la rutina listarse para activar algún atributo que le
indique a articulo que tiene que escribir algo diferente. Ese
atributo debe desactivarse una vez impresa la línea de inventario de
este objeto. Por ejemplo, usando general como atributo:
listarse [;
if (etapa_inventario==1) give self general;
else give self ~general;
],
articulo [;
if (self has general) {
print "una odiosa";
rtrue;
}
else print "una";
],
nombre_corto [;
if (self has general) {
print "caja";
rtrue;
}
],
|
La solución hace un poco de trampa. Hay que saber que existe la
variable de librería modomirar (vale 1 en modo normal, 2 en modo
largo y 3 en modo superbreve). Simplemente añade a tu programa:
[ PasaElTiempo ; if (accion~=##Mirar && modomirar==2) <Mirar>; ]; |
[ InventarioDobleSub i cuenta1 cuenta2;
objectloop (i in jugador)
{ if (i hasnt puesto) { give i banderaux; cuenta1++; }
else { give i ~banderaux; cuenta2++; }
}
if (cuenta1==0) print "No llevas nada";
else{
print "Llevas ";
EscribirListaDesde(child(jugador),
INFOTOTAL_BIT + ESPANOL_BIT + RECURSIVO_BIT + BANDERAUX_BIT);
if (cuenta2==0) ".";
print ". Además, vas vestido con ";
objectloop (i in jugador)
{ if (i hasnt puesto) give i ~banderaux; else give i banderaux;
}
EscribirListaDesde(child(jugador),
ESPANOL_BIT + RECURSIVO_BIT + BANDERAUX_BIT);
".";
];
|
Class Letra
with listar_juntos [;
if (etapa_inventario==1)
{ print "las letras ";
! Modificamos el estilo temporalmente
estilo_ac = estilo_ac | ESPANOL_BIT | SINARTICULO_BIT;
estilo_ac = estilo_ac & (~NUEVALINEA_BIT) & (~INDENTAR_BIT);
}
else print " de un juego de Scrabble";
],
nombre_corto [;
if (listando_junto ofclass Letra) rfalse;
print "letra ", (object) self, " de un juego de Scrabble"; rtrue;
],
articulo "la",
has femenino;
|
Y puedes crear cuantas letras quieras con un código así:
Letra -> "X" with nombre 'x//'; |
Class Moneda
with nombre 'moneda' 'monedas//p',
descripcion "Un disco redondo, sin acuñar, probablemente la
moneda local.",
listar_juntos "monedas",
plural [;
if (~~(listando_junto ofclass Moneda)) print "monedas";
print " de ", (address) (self.&nombre)-->0;
],
nombre_corto [;
if (listando_junto ofclass Moneda)
{ print "de ", (address) (self.&nombre)-->0; rtrue; }
],
has femenino;
Class Moneda_Oro class Moneda with nombre 'oro';
Class Moneda_Plata class Moneda with nombre 'plata';
Class Moneda_Bronce class Moneda with nombre 'bronce';
|
Y las monedas individuales se crean así:
Moneda_Plata -> "moneda de plata"; |
...etc.
parse_nombre [ i j k;
if (self has general) j='rojo'; else j='verde';
if (SiguientePalabra()=='tomate') {
i++;
k=SiguientePalabra();
if (k==j) {
i++;
k=SiguientePalabra();
}
if (k=='frito') i++;
}
return i;
],
|
Object -> princess "/.%./ (la artista antes conocida como Princess)"
with nombre 'princess' 'artista' 'antes' 'conocida' 'como',
nombre_corto [;
if (self hasnt general) { print "Princess"; rtrue; }
],
reaccionar_antes [;
Escuchar: print_ret (_nombre_) self, " entona un suave canto de sirena.";
],
inicial [;
print_ret (_nombre_) self, " está cantando suavemente.";
],
parse_nombre [ x n;
if (self hasnt general)
{ if (SiguientePalabra()=='princess') return 1;
return 0;
}
x=DireccionDePalabra(np);
if (x->0 == '/' && x->1 == '.' && x->2 == '%'
&& x->3 == '.' && x->4 == '/')
{ while (np<=parse->1 && DireccionDePalabra(np++)<x+5) n++;
return n;
}
return -1;
],
vida [;
Besar: give self general;
self.vida = NULL;
"En una mágica transformación, Princess retrocede un
paso y asombra al mundo entero anunciando que, a
partir de ahora será conocida como ~/.%./~.";
],
has animado propio femenino;
|
Hay que señalar que el botón no puede llamarse simplemente "café" cuando el jugador esté sosteniendo una taza de café, el juego tiene que responder correctamente a la secuencia de comandos "PULSA CAFE" y "BEBE CAFE" (la primera se referirá al botón, la segunda a la bebida). Observa también cómo se hace para actualizar el pronombre -LO de modo que sea la bebida, y así BEBELO funcione correctamente.
Object -> expendedor "máquina expendedora",
with nombre 'maquina' 'expendedora',
inicial
"Hay una máquina expendedora de bebidas, con botones para
Cola, Café y Té.",
has estatico femenino;
Object -> elboton "botón de la máquina"
with cantidad 0, ! Para almacenar el tipo de bebida deseada
parse_nombre [ i flag tipo;
for (: flag == 0: i++)
{ flag = 1;
switch(SiguientePalabra())
{
'boton', 'para', 'del', 'de': flag = 0;
'cafe': if (tipo == 0) { flag = 0; tipo = 1; }
'te': if (tipo == 0) { flag = 0; tipo = 2; }
'cola': if (tipo == 0) { flag = 0; tipo = 3; }
}
}
if (tipo==bebida.cantidad && i==2 && tipo~=0 && bebida in jugador)
return 0;
self.cantidad=tipo;
return i-1;
],
antes [;
Empujar:
if (self.cantidad == 0)
"Tienes que especificar qué botón quieres pulsar.";
if (parent(bebida) ~= 0)
"Ahora parece que no funciona.";
bebida.cantidad = self.cantidad;
move bebida to jugador;
ActualizarPronombre(bebida);
print_ret "¡Wuuurrrr! La máquina pone ", (un) bebida,
" en tus ansiosas manos.";
Atacar: "La máquina da un respingo y te escupe cola.";
Beber: "No puedes beber eso hasta que hayas operado con la
máquina.";
],
has escenario;
Object bebida "bebida"
with cantidad 0,
parse_nombre [ i flag tipo;
for (: flag == 0: i++)
{ flag = 1;
switch(SiguientePalabra())
{ 'bebida', 'taza', 'vaso', 'de': flag = 0;
'cafe': if (tipo == 0) { flag = 0; tipo = 1; }
'te': if (tipo == 0) { flag = 0; tipo = 2; }
'cola': if (tipo == 0) { flag = 0; tipo = 3; }
}
}
if (tipo ~= 0 && tipo ~= self.cantidad) return 0;
return i-1;
],
nombre_corto [;
print "vaso de ";
switch (self.cantidad)
{ 1: print "café"; 2: print "té"; 3: print "cola"; }
rtrue;
],
antes [;
Beber: remove self;
"Buagh, estaba horrible. Arrugas el vaso y te deshaces de él.";
];
|
[ InterpretarNombre obj n m; while (PalabraEnPropiedad(SiguientePalabra(), obj, adjetivos)) n++; np--; while (PalabraEnPropiedad(SiguientePalabra(), obj, nombre) m++; if (m==0) return 0; ! Si no hay nombres return n+m; ! Si hay, contar tambien los adjetivos hallados ]; |
[ InterpretarNombre obj; if (SiguientePalabra() == 'objeto' && IntentarNumero(np) == obj) return 2; np--; return -1; ! Para que el parser intente su método por defecto ]; |
Observa que, si el jugador no pone algo que sea del tipo OBJETO
NUMERO, entonces se retorna -1, con lo que la librería intentará su
método por defecto, que consiste en buscar sólo en la propiedad
nombre.
[ InterpretarNombre ; ! No usamos el parametro obj if (LongitudDePalabra(np)==1 && DireccionDePalabra(np)->0 == '#') return 1; return -1; ]; |
[ InterpretarNombre;
if (LongitudDePalabra(np)==1 && DireccionDePalabra(np)->0 == '#') return 1;
if (LongitudDePalabra(np)==1 && DireccionDePalabra(np)->0 == '*')
{ accion_parser= ##HalladoPlural; return 1; }
return -1;
];
|
parse_nombre
que encaje cualquier secuencia de las palabras 'pescado' y
'salsa', admitiendo también la aparición de 'en' entre estas
palabras, pero no fuera de ellas.
El mecanismo es similar al que usa la librería para "saltarse" todas
las apariciones de 'de' que haya entre las palabras referidas al
objeto. Se basa en que cada vez que encuentra una palabra
"ignorable" (como 'de'), aumenta un contador de palabras dudosas. Si
finalmente encuentra una palabra válida para el objeto (esto es,
contenida en su propiedad nombre), el contador de palabras dudosas
pasa a ser contador de palabras válidas, pero si no encuentra ninguna
palabra válida, el contador se descarta.
Object -> Pescado "pescado en salsa"
with nombre 'pescado' 'salsa',
parse_nombre [ w dudosas seguir validas;
seguir=true; validas=0; dudosas=0;
while (seguir) {
w=SiguientePalabra();
if (w=='en') {dudosas++; continue;}
if (PalabraEnPropiedad(w, self, nombre)) {
validas++; validas=validas+dudosas; dudosas=0;
} else {seguir=0;}
}
return validas;
];
|
Global avisado_curricula = false;
Class Curriculum
with parse_nombre [ i j flag;
for (flag=true:flag:flag=false)
{ j=SiguientePalabra();
if (j=='curriculum' or j==self.name) flag=true;
if (j=='curriculums' && (~~avisado_curricula))
{ avisado_curricula=true;
accion_parser=##HalladoPlural; flag=true;
print "(Lo dejaré pasar por esta vez, pero el plural
de curriculum es curricula.)^";
}
if (j=='curricula')
{ accion_parser=##HalladoPlural; flag=true; }
i++;
}
return i-1;
];
|
Observar que, aunque el código anterior sería lo correcto, ¡en realidad no funcionará! Ello se debe a la limitación de 9 letras en las palabras de diccionario, con lo que 'curriculum' y 'curriculums' son la misma palabra para el parser. De modo que el mensaje de advertencia saldría incluso si el jugador había usado la palabra en singular.
Tratar de corregir esto implicaría acceder al buffer de letras,
mediante el uso de las funciones DireccionDePalabra y
LongitudDePalabra, lo cual se deja como un ejercicio para el lector.
![]() | ![]() | ![]() | Respuestas a los ejercicios |