![]() |
![]() |
![]() |
Rompecabezas clásicos |
El rompecabezas más viejo y más usado es aquel en el que un objeto está oculto por otro, y sólo se revela al examinar el segundo. Tenemos un par de rompecabezas así en nuestra aventura. En la mazmorra hay un esqueleto que al ser examinado revela un cuchillo, y en el dormitorio hay unas camas que al ser examinadas revelan unas fundas. También vamos a esconder un trozo de carbón en la chimenea, aunque no sirva para nada en el juego.
Hemos visto que Inform tiene un atributo engañosamente
llamado concealed
, que parece invitar a ser usado para esto. Sin
embargo hemos
visto también que concealed
simplemente hace que la
librería no
mencione el objeto, pero no impide que el jugador se refiera a
él.
Es decir, si pusieramos en la mazmorra un objeto Esqueleto
y un
objeto cuchillo
, y diéramos a éste
último el atributo concealed
,
Inform le diría al jugador al entrar en la localidad: "Puedes
ver un
esqueleto" (silenciando toda mención al cuchillo). Sin embargo,
el
jugador podría pese a todo escribir "COGE CUCHILLO" o "EXAMINA
CUCHILLO" y la librería le permitiría la acción.
La única forma de impedir que el jugador pueda referirse al cuchillo es ¡no poner un cuchillo en esa localidad! Si un objeto está en una localidad, el jugador siempre podrá referirse a él como blanco de sus acciones.
Por tanto la forma típica de programar esto consiste en tener programado el objeto cuchillo, pero no ponerlo dentro de ninguna de las localidades del juego. De este modo, cualquier intento de manipular el cuchillo por parte del jugador, causará el mensaje de error "No veo eso por aqui".
Programaremos después el esqueleto de forma que, tan pronto
como el
jugador lo examine, el cuchillo será movido a la Mazmorra
.
Así, a
partir de este punto el cuchillo se convertirá en un objeto
más del
juego, que podrá ser manipulado o no, según sus atributos
y sus
propias reglas programadas en su rutina before
.
Para crear un objeto que no está en ninguna localidad, basta
no poner
en su cabecera en qué localidad se halla. Recuerda que
éste era el
último parámetro que se daba en la cabecera (echale un
vistazo al
objeto antorcha
). Si el objeto cuchillo empieza
así:
Object cuchillo "cuchillo" |
al no poner en qué localidad se halla, resultará que
no está en
ninguna. De todas formas, es buena idea crear una localidad ficticia,
que yo suelo llamar "Limbo", y meter en ella todos los objetos que
no aparecen inicialmente en el juego. Cuando el objeto en
cuestión
deba aparecer, bastará moverlo desde el "Limbo" a la localidad
apropiada. Y a la inversa, para hacer desaparecer un objeto del mundo
del juego, bastará moverlo al "Limbo" (si no usaramos Limbo, la
forma de hacer desaparecer un objeto sería usar remove
objeto
, lo
que en realidad lo saca de todas las localidades).
Programemos de momento un cuchillo mínimo, que conste
simplemente de
su nombre y una descripción, y hagamos que inicialmente se halle
en la
localidad ficticia llamada "Limbo". Debemos crear también esta
localidad, claro. Esto puedes escribirlo en cualquier lugar del
código, después de incluir Verblib
. Un buen sitio puede
ser justo
antes de la rutina Initilaise
, para tener juntos todos
los objetos
de limbo y encontrarlos fácilmente, para cuando haya que cambiar
algo
en su código.
Object Limbo "Limbo" with description "Eh. ¿cómo has llegado aquí? Malditos betatesters...", has light; Object cuchillo "pequeño cuchillo" Limbo with name 'pequeno' 'cuchillo' 'punal' 'hoja' 'cuchilla', description "Un pequeño cuchillo, cubierto de herrumbre" |
Observa cómo he evitado el uso de la eñe en las palabras de
vocabulario (las que figuran en la propiedad name
).
Esto no impide
que el jugador pueda escribir "EXAMINA PEQUEÑO PUÑAL",
por
ejemplo. Si el jugador usa eñes, Inform las convertirá en
enes antes
de comenzar el parsing. Sin embargo, si hubieramos puesto
'puñal' en
la lista de nombres de este objeto, estaríamos discriminando a
los
jugadores que intentan jugar desde un ordenador sin eñes, ya que
nunca
podrían escribir esa palabra. Lo mismo vale para los acentos. No
uses
acentos en las palabras de vocabulario si quieres lograr la
máxima
compatibilidad y difusión de tu juego.
Ahora debemos programar el esqueleto, y necesitamos que al ser examinado haga aparecer el cuchillo. Sin embargo hay que estar al tanto de un importante detalle. El cuchillo debe aparecer sólo la primera vez que se examina el esqueleto, y no cada vez que se le examine. Por tanto necesitamos llevar de algún modo el registro de si el esqueleto ya ha sido examinado o no.
Para estos menesteres, todos los objetos del juego tienen un
atributo
llamado general
, que por defecto está desactivado
en todos ellos. La
librería nunca activa este atributo, ni lo consulta para nada.
Por
tanto, queda libre para que el programador lo use para sus propias
necesidades. El uso más frecuente de este atributo es como
indicador
de "puzzle resuelto", de modo que tan pronto como el jugador
resuelve algo (encuentra el cuchillo, por ejemplo), el objeto que
causaba el puzzle (el esqueleto) recibe el atributo general
,
como
indicador de "esto ya está". El código que hace aparecer
el
cuchillo, por tanto, sólo lo hará si el esqueleto no
tiene ya activado
el atributo general
.
Vamos pues con la programación del esqueleto. El siguiente
código
conviene que lo pongas después del de la Mazmorra
,
para tener los
objetos cerca de las localidades en que aparecen.
Object Esqueleto "esqueleto" Mazmorra with name 'esqueleto' 'humano' 'muerto' 'cadaver', description [; print "Los huesos amarillentos, las cuencas vacías"; if (Esqueleto hasnt general) { move cuchillo to Mazmorra; print ". Junto a él ves un pequeño cuchillo"; give Esqueleto general; } "."; ], has scenery; |
El código que hace aparecer el cuchillo lo hemos puesto
dentro de la
propia descripción del esqueleto, haciendo uso de la posibilidad
de
que las descripciones sean rutinas. Antes de hacer aparecer el
cuchillo, verificamos si el esqueleto tiene el atributo general
,
pues esto indicaría que el cuchillo ya ha sido encontrado y no
debe
aparecer de nuevo. La condición para comprobar que un objeto no
tenga un atributo, es hasnt
(opuesto de has
).
Si el esqueleto no tiene general
, movemos el
cuchillo a la
mazmorra. La instrucción Inform para esto es move obj1 to
obj2
. El
obj2
no tiene por qué ser necesariamente una
localidad. Podría ser
un recipiente, o incluso el propio jugador (en cuyo caso el obj1
pasaría directamente a su inventario). Seguidamente imprimimos
un
mensaje para informar al jugador de su descubrimiento y finalmente
activamos el atributo general
del esqueleto, para
señalar que el
puzzle ha sido resuelto y así evitar que el cuchillo aparezca al
examinarlo por segunda vez.
La línea que pone simplemente "."
, como ves,
está fuera del if
, y
por tanto se ejecutará siempre, independientemente de la
condición. Se
trata de uno de esos print
implícitos, que
imprimirá el punto final
de la frase, un retorno de carro, y finalizará la función
retornando
true
. Observa cómo hemos omitido el punto final en
los print
anteriores. También podríamos haber puesto el punto final
en ambos
print
, y omitir el que va fuera del if
, pero
en este caso
convendría sustituir este último punto por un rtrue
,
para asegurarse
de que nuestra función devuelve true
. Si bien en
este caso concreto
da lo mismo lo que la función retorne, es buena costumbre
retornar
true
cuando nuestra rutina ha mostrado algún
mensaje que debe
sustituir a los defectos de la librería.
En Inform siempre hay muchas formas diferentes de lograr un mismo
resultado. Por ejemplo, podríamos haber escrito el código
que hace
aparecer el cuchillo como parte de la rutina before
del
esqueleto
(ante la acción Examine
), en lugar de ponerlo en
su description
.
En este caso sí se haría imprescindible retornar true
,
pues si no lo
hicieramos, después de nuestros mensajes la librería
imprimiría los
suyos (que consistirían en la descripción del esqueleto,
si éste tiene
una propiedad description
, o en el mensaje "No observas
nada
especial en el esqueleto" si no la tiene).
Convendría ahora pararse a testear si nuestro puzzle funciona
correctamente, y de paso intentar unas cuantas acciones previsibles
sobre el esqueleto o el cuchillo (cogerlo, moverlo,...) para
asegurarnos de que las respuestas por defecto de la librería son
adecuadas en todos los casos, o para sustituir las que no lo sean
mediante una rutina before
en estos objetos. Aquí
tienes una versión del juego que incorpora el Limbo
,
el cuchillo
y
el esqueleto
, en código fuente.
Verifica (mediante el comando xlista
) que el cuchillo
inicialmente
está en el limbo, y comprueba (de nuevo con xlista
) cómo se mueve a
la mazmorra tras examinar el esqueleto.
![]() |
Otro verbo de
depuración que puede interesarte es el verbo CAMBIOS .
Si pones esto en el juego, a partir de ese instante Inform te
avisará cada vez que un objeto se mueva, un atributo se ponga o
quite mediante give , o una propiedad cambie de valor
mediante una asignación. Para ello es indispensable que trabajes con la
opcion S y D activadas. Buscalas en el Switch Manager de JIF.
Vamos a probarlo. Tal vez te sorprenda ver la gran cantidad
de atributos que cambian cada vez que efectúas algunas acciones.
Sigue mis pasos: carga el juego, escribe
Vemos que ha cambiado el valor de la propiedad Prosigamos, y caminemos hacia el oeste:
La
línea "[Moving ti mismo to Mazmorra]" indica que el objeto Finalmente, veamos como aparece el cuchillo:
Vemos también que,
tras la descripción del esqueleto, se nos informa de que el
cuchillo
ha sido movido a la mazmorra. Después aparece el mensaje para el
jugador "Junto a él ves...", tras el cual Inform nos avisa de
que se
ha activado el atributo |
Como ya hemos dicho, hay muchas formas de programar una misma idea
en Inform. Por ejemplo, para verificar si el cuchillo ya había
sido
encontrado, podríamos haber mirado si estaba o no estaba en el
limbo,
en lugar de usar el atributo general
del esqueleto. En
este enfoque
(más parecido al que seguiría un programador de PAWS)
bastaría algo
como:
description [; print "Los huesos amarillentos, las cuencas vacías"; if (cuchillo in Limbo) { move cuchillo to Mazmorra; print ". Junto a él ves un pequeño cuchillo"; } "."; ], |
Este mecanismo, de hecho, parece más simple. Pero imagina que decidimos permitirle al jugador que lance el cuchillo por la ventana, y como "premio" a su inteligencia, hacemos desaparecer el cuchillo del juego moviendolo de nuevo al Limbo. En este caso, con la programación anterior, ¡el jugador haría reaparecer el cuchillo examinando el esqueleto de nuevo! La cosa podría solucionarse teniendo dos Limbos diferentes: un Limbo1 para los objetos que aun no han aparecido en el juego y un Limbo2 para los que han sido eliminados.
La librería Inform tiene varias acciones relacionadas con
la idea
de examinar un objeto. Se trata de Examine
, Search
(que puede
entenderse como "registrar o mirar dentro de un objeto") y LookUnder
. Los verbos que el jugador puede usar para dar
lugar a
estas acciones son muy variados:
![]() |
Observa que "LEE OBJETO"
causa la acción Examine . No hay acción Read (Leer)
en Inform, aunque esto puede cambiarse si lo necesitas. |
![]() |
Observa que Inform no entiende por ejemplo "EXAMINA EN OBJETO". Esto es lógico, ya que esa frase es incorrecta en español, pero puede despistar a algunos jugadores que piensen erróneamente que "MIRA" equivale a "EXAMINA" en todos los contextos. Recuerda que en Inform el significado de un verbo depende de la frase en que aparece, y no solo de la palabra que lo representa. |
![]() |
De nuevo, "EXAMINA DEBAJO DE OBJETO" no sería una forma válida para Inform. |
Es de destacar que Inform no incorpora verbos ni acciones para la
idea de "mirar detrás de algo". Incluso la idea de "mirar a
través
de algo" (como una ventana) o "Mirar por algo" (como mirar por un
telescopio), aunque sí están contempladas, desembocan en
la acción Search
en lugar de tener sus propias acciones. Conviene
estar
sobreaviso de todos estos detalles.
En nuestro esqueleto, el cuchillo aparecerá si la
acción generada por
el jugador ha sido Examine
(ya que es esta acción
la que da lugar a
la ejecución de la rutina description
del
esqueleto). Sin embargo,
si el jugador genera Search
o LookUnder
,
al no haber
contemplado en el esqueleto estas acciones, la respuesta del juego
sería: "No encuentras nada interesante" y "No ves nada
interesante", respectivamente. Esto puede considerarse un bug del
juego, ya que si lo primero que pone el jugador, en lugar de "EXAMINA
ESQUELETO" es "REGISTRA ESQUELETO", el cuchillo no aparecerá.
Bien, es cierto que el cuchillo está "junto al esqueleto", y por tanto al registrarlo no debería aparecer, pero sin embargo considero "juego sucio" obligar al jugador a poner "EXAMINA ESQUELETO" para descubrir el cuchillo (tambien se conoce esta idea como 'sindrome de la palabra exacta'). Deberíamos ser tolerantes y admitir cualquier intento de búsqueda en el esqueleto.
Por tanto, debemos escribir una rutina before
para
nuestro esqueleto,
que se haga cargo de las acciones Search
y LookUnder
,
y que
cambie los mensajes por defecto para este caso. Por ejemplo:
before [; Search, LookUnder: if (Esqueleto hasnt general) { move cuchillo to Mazmorra; give Esqueleto general; "Al acercarte al esqueleto ves a su lado un pequeño cuchillo."; } else "Este desgraciado no llevaba nada más."; ], |
Fíjate cómo trato ambas acciones de la misma forma,
poniéndolas
separadas por comas antes de los dos puntos. Esta sintaxis indica que
si la acción es Search
o LookUnder
,
debe ejecutarse ese
código. El código en cuestión es similar al que
había escrito en la
descripción del esqueleto. Pero he movido la línea give
esqueleto
general
delante del mensaje (¿sabes por qué?).
Piensa cuáles serían las respuestas del juego si el jugador primero EXAMINA el esqueleto, y después lo REGISTRA. Piensa también cómo serían si hace estas acciones en orden inverso. Comprueba lo que ocurre realmente jugando el juego que tienes aquí
Una vez que hemos visto la idea clave de usar general
como indicador
de puzzle resuelto, el problema de encontrar un carbón dentro de
la
chimenea se resuelve de forma análoga al del cuchillo:
Object carbon "trozo de carbón" Limbo with name 'trozo' 'carbon', description "Un trozo de negro carbón que parece haber sobrevivido al fuego."; Object chimenea "chimenea" PuertaPrincipal with name 'chimenea' 'hogar', description [; print "Hace mucho tiempo que no arde fuego alguno en esta vieja chimenea"; if (chimenea hasnt general) { move carbon to PuertaPrincipal; print ". Un trozo de carbón es todo lo que queda del antiguo hogar"; give chimenea general; } "."; ], has female scenery; |
Observa un detalle en la propiedad name
del trozo de
carbón. No
hemos incluido la palabra "de" en esta propiedad ¿cómo es
posible
que el parser entienda entonces "coge trozo de carbón"?
![]() |
El parser de Inform (INFSP) está preparado para ignorar la preposición "de" si esta
aparece entre dos palabras que se refieren al mismo objeto. Así,
en "COGE TROZO DE CARBON" el "DE" es ignorado por el parser, puesto que
"TROZO" y "CARBON" se refieren a un mismo objeto. La acción
resultante será Take con noun==carbon .
En cambio, en una orden como "COGE CARBON DE CHIMENEA", la
preposición "DE" no es ignorada, pues "CARBON" y "CHIMENEA" no
son palabras que estén en el |
Te dejaré como ejercicio, amable lector, que codifiques tú mismo este último puzzle. Cuando el jugador examine la cama por primera vez, debe aparecer el mensaje "Una funda de tela cubre la cama", y en los sucesivos exámenes de la cama "Sólo restos de paja cubren la cama".
Naturalmente, la funda no debe ser accesible hasta que el jugador
haya
examinado la cama. En cuanto a la paja, la versión fácil
del ejercicio
no hace aparecer ninguna paja, de modo que si el jugador escribe
"EXAMINA PAJA" simplemente recibirá un "No veo eso por
aquí". ¿Te
atreves con una versión difícil en la que realmente
aparezca un objeto
paja
al examinar la cama por segunda vez?
Por el momento, la funda puede ser cogida por el jugador. En la próxima entrega haremos que no pueda cogerla hasta que haya cortado las ataduras que la sujetan a la cama.
Aquí tienes la versión del juego que incluye la chimenea con su carbón, y la cama con su funda y la paja que aparece al mirar una segunda vez.
![]() |
![]() |
![]() |
Rompecabezas clásicos |