![]() | ![]() | ![]() | ¡Empecemos! |
Nada tan difícil como un comienzo
En poesía, salvo quizás el final.
Lord Byron (1788-1824), Don Juan, IV iv
Escribe lo siguiente en un editor de texto (puedes usar por ejemplo
el bloc de notas de Windows) y guardalo en disco con el nombre
Ruinas.inf:
Constant Historia "RUINAS";
Constant Titular "^Un ejemplo a seguir^
Copyright (c) 1999 by Zak McKraken.^";
Include "EParser";
Include "Acciones";
Object Bosque "Bosque Sombrío"
with descripcion
"En este diminuto claro, la alfombra de agujas de pino
se ve rota por unos escalones tallados en piedra que
descienden hacia la oscuridad. Los oscuros árboles
crecen por todos los lados, el aire está húmedo por la
lluvia reciente, las lianas cuelgan en el aire.",
has luz;
[ Inicializar;
localizacion = Bosque;
"^^^^^Tras días de búsqueda, de sed, de penoso avance a través
de la maleza del bosque, al fin tu paciencia se ve recompensada.
¡Has descubierto algo!^";
];
Include "Gramatica";
|
Ahora compílalo. Suponiendo que trabajas en una sesión MS-DOS (y que
has copiado el sistema de desarrollo de Inform al directorio
c:\INFORM), deberías observar algo como lo siguiente:
C:\INFORM> inform Ruinas.inf DJGPP Inform 6.15 (22nd March 1998) InformatE! (Inform ahora totalmente en Espa#ol!) Lib 6/7E Rev. 990316 C:\INFORM> |
...a menos que hayas copiado algo mal del ejemplo anterior, en cuyo
caso pueden aparecer mensajes de error (deberás editar de nuevo
Ruinas.inf para corregirlos). Si todo ha ido bien, tendrás ahora el
fichero Ruinas.z5, que es la versión del juego que puedes
distribuir a tus amigos. Para poder jugarla necesitarán el programa
Frotz (el cual está disponible para muchos ordenadores y sistemas
operativos diferentes).
El ejemplo anterior aparentemente es muy corto, pero en realidad
contiene gran cantidad de código en otros ficheros que se
añaden a lo que tu has escrito para crear la versión final del
juego. Estos ficheros son añadidos al tuyo mediante la orden
include que puedes ver en varios puntos del programa. Los
ficheros añadidos son:
Acciones.
Además de incluir estos ficheros, nuestro programa Ruinas
contiene lo siguiente:
[, hasta que aparece el
correspondiente corchete cerrado ], es una rutina. La rutina
Inicializar se ejecutará cuando el juego empiece. Su único
cometido es decir en qué lugar empieza el juego (asignando el nombre
de este lugar a la variable localizacion). También suele
aprovecharse para imprimir un mensaje de bienvenida.
Tal como está, Ruinas es un juego bastante soso (por no decir ruinoso). Intentemos jugar, recuerda que debes usar el programa Frotz en la forma siguiente:
C:\INFORM> frotz Ruinas.z5 |
Veamos cómo reacciona el juego a algunas órdenes corrientes:
Tras días de búsqueda, de sed, de penoso avance a través de la maleza del bosque, al fin tu paciencia se ve recompensada. ¡Has descubierto algo! RUINAS Un ejemplo a seguir. Copyright (c) 1999 by Zak McKraken. Revisión 1 / Número de serie 990413 / Inform v6.15 Librería 6/7E Bosque Sombrío En este diminuto claro, la alfombra de agujas de pino se ve rota por unos escalones tallados en piedra que descienden hacia la oscuridad. Los oscuros árboles crecen por todos los lados, el aire está húmedo por la lluvia reciente, las lianas cuelgan en el aire. >i No llevas nada. >norte No puedes ir por ahí. >espera Pasa el tiempo... >fin ¿Seguro que quieres abandonar el juego? S |
(El "número de revisión" es 1, a menos que especifiques otro valor
mediante la directiva Release 2, por ejemplo, en el código
fuente. El número de versión es creado por Inform en función de la
fecha de compilación.)
Añadamos un segundo objeto: una seta. Debes escribir lo siguiente debajo del Object Bosque y antes de la rutina Inicializar.
Object -> Seta "seta moteada" with nombre 'seta' 'hongo' 'moteada' 'moteado' 'venenosa' 'venenoso', has femenino; |
La flecha -> significa que el objeto "seta" está dentro del objeto anterior (el bosque) [para ser preciso, significa que está dentro del último objeto que no llevaba ->, que en nuestro caso es el bosque]. Tras la palabra nombre va la lista de sinónimos que el jugador puede usar para referirse a este objeto. El parser entenderá cualquier combinación de palabras de esta lista como un intento de referirse a nuestra seta. Así, por ejemplo, entenderá 'SETA', 'SETA MOTEADA', 'HONGO VENENOSO', 'SETA VENENOSA MOTEADA', etc. (sí, es cierto, también entendería 'SETA HONGO' y otras cosas raras pero ¿qué más da?).
Si la librería necesita escribir algún mensaje refiriéndose a la seta (por ejemplo, en la descripción de la habitación la librería menciona siempre los objetos que hay dentro de ella), usará para ello la cadena entre comillas que aparece en la cabecera del objeto, es decir "seta moteada" en nuestro ejemplo. Así que el mensaje que generará la librería será: "Puedes ver una seta moteada." Para que la librería sepa qué artículo debe anteponer ("una" en este caso), hay que decirle que este objeto es femenino. Observa que el objeto puede tener sinónimos de diferente género (esta "seta" es también un "hongo"), pero especificamos que el objeto es femenino porque la librería siempre escribirá su nombre como "seta moteada" (aunque comprenda "hongo" como sinónimo, nunca usará esta palabra para referirse a la seta).
La librería usa también el género para crear algunos mensajes de respuesta. Por ejemplo, ante la orden "COGE LA SETA", responderá "Cogida." (en femenino). Además, posteriormente comprenderá el pronombre "-la" como referido a la seta, por lo que el jugador podría decir a continuación "MIRALA", o "COMETELA". Observar que esto puede ser erróneo en ciertos casos. Si el jugador escribe "COGE HONGO", la respuesta de la librería sería de todas formas "Cogida." porque ese es el género del objeto seta.
![]() |
Este comportamiento anómalo puede corregirse con un poco
más de esfuerzo de programación. Simplemente se trata de separar la
lista de sinónimos en dos grupos (masculinos y femeninos), y utilizar
una propiedad llamada genero para especificar cuál es el
género del "título" de este objeto (en nuestro ejemplo este título
es "seta venenosa" que sigue siendo femenino. Si quieres
puedes usar esta versión más compleja, para garantizar que la librería
siempre usará un mensaje que concuerda en género con lo escrito por el
jugador (es decir que si el jugador pone "COGE SETA", el juego
responde "Cogida.", pero si pone "COGE HONGO", el juego responde
"Cogido."). Esta sería la nueva versión (observa que en esta versión
ya no hay que poner has femenino al final del objeto, aunque
si lo pones tampoco pasa nada):
|
Dejando aparte estos sutiles asuntos acerca del género, volvamos a nuestro juego. Si jugamos la versión con la seta añadida, encontramos que la descripción de la habitación ha cambiado ligeramente, y tiene una línea extra, mencionando la seta:
Bosque Sombrío En este diminuto claro, la alfombra de agujas de pino se ve rota por unos escalones tallados en piedra que descienden hacia la oscuridad. Los oscuros árboles crecen por todos los lados, el aire está húmedo por la lluvia reciente, las lianas cuelgan en el aire. Puedes ver una seta moteada. > |
Esta línea no la hemos escrito nosotros, la escribe automáticamente la librería para mencionar los objetos presentes en esta habitación (veremos después que podemos marcar algunos objetos para que no los mencione). No obstante la frase estándar: "Puedes ver tal cosa" es un poco sosa. Si quisiéramos cambiarla por otra más literaria habrá que añadirle a la seta una nueva característica llamada inicial (no confundir con la rutina Inicializar vista anteriormente). Si el objeto tiene la propiedad inicial, entonces la librería usará esta propiedad para mencionar al objeto, en lugar de usar su mensaje estándar "Puedes ver...". Así por ejemplo:
Object -> Seta "seta moteada"
with nombre 'seta' 'moteada' 'venenosa' 'hongo' 'moteado' 'venenoso',
inicial "Una seta moteada, con un largo tallo, emerge del
suelo empapado.",
has femenino;
|
El mensaje inicial se usa para mencionar la seta al jugador como parte de la descripción del bosque. Si el jugador coge la seta, este mensaje no se mostrará nunca más, de ahí su nombre "inicial". Si el jugador intenta examinar la seta, sin embargo, seguirá obteniendo el soso mensaje "No observas nada especial en la seta moteada". Para lograr una inspección detallada más interesante, tendremos que darle a la seta su propia descripcion:
Object -> Seta "seta moteada"
with nombre 'seta' 'moteada' 'venenosa' 'hongo' 'moteado' 'venenoso',
inicial "Una seta moteada, con un largo tallo, emerge del
suelo empapado.",
descripcion "La seta está totalmente cubierta de manchas y no
estás muy seguro de que no sea venenosa.",
has femenino comestible;
|
Ahora, si el jugador observa la seta de cerca obtendrá una advertencia; pese a la advertencia podrá comérsela, ya que le hemos dado el atributo comestible.
El ejemplo anterior nos muestra los dos tipos de características que
puede tener un objeto. De un lado están lo que llamamos
propiedades (como nombre_f, nombre_m,
inicial, descripcion, genero) a las que se
puede asignar un valor o una lista de ellos (por ejemplo,
nombre tiene como valor una lista de palabras, mientras que
descripcion tiene como valor una cadena de texto. Por otro
lado están los atributos, que son características que el objeto
puede tener o no tener. En este caso, la seta tiene el atributo
comestible y femenino. Los atributos, o bien están
presentes en el objeto o bien no lo están, pero no se les puede dar un
valor particular como ocurre con las propiedades.
Las propiedades se escriben tras la palabra with y al final de cada propiedad debe ir una coma. Los atributos se escriben detrás de la palabra has y van separados unos de otros por espacios. Al final de la lista de atributos debe ir un punto y coma.
![]() |
|
Las propiedades pueden cambiar de valor a lo largo del juego, y los atributos se pueden poner y quitar. Los valores iniciales de las propiedades aparecen siempre tras la palabra reservada with (que suele estar justo debajo de la palabra Object), y cada propiedad se separa de la siguiente con una coma. Los atributos que inicialmente tendrá el objeto se especifican tras la palabra reservada has. Los atributos que no se listen en ese punto, no los tendrá el objeto inicialmente (aunque se le pueden dar más tarde).
Para cambiar el valor de una propiedad se usa la instrucción de
asignación. Para dar a un objeto un atributo que no tenía inicialmente
se usa el comando give, y para quitárselo se usa también
give, pero poniendo delante del nombre de atributo el símbolo
~. Puede consultarse si un objeto tiene o no un atributo
concreto mediante la palabra reservada has (para ver si lo
tiene) o bien hasnt para ver si no lo tiene, y actuar en
consecuencia. Por ejemplo:
seta.descripcion="Ahora estás seguro de que es una seta venenosa."; give seta general; give seta ~comestible; if (seta has comestible) print "Parece que puede comerse.^"; |
La primera línea cambia la descripción de la seta. La segunda le da un atributo llamado general (este atributo forma parte de la librería, el programador no puede inventar atributos nuevos en medio del juego, sino que debe conocer los que ya existen. En particular, el atributo general no significa nada en concreto para la librería, por lo que el programador puede usarlo para lo que quiera). La tercera línea le quita a la seta el atributo comestible (con lo cual la librería rechazará la orden 'COME SETA'). Finalmente la cuarta línea imprime el mensaje entre comillas, sólo si la seta tiene el atributo comestible.
Podríamos seguir definiendo objetos mediante un proceso similar, que como vemos no deja de ser como si rellenáramos un formulario. Pero mejor pasemos a hacer algo que involucre programación real, para lo cual añadiremos la siguiente propiedad a la seta (al ser una propiedad debes ponerla en cualquier lugar entre el with y el has. Por ejemplo, a continuación del genero. El orden en que se escriban las propiedades no es relevante):
despues [;
Coger: "Coges la seta, cortando con cuidado su largo tallo.";
Dejar: "La seta cae al suelo, ligeramente machacada.";
],
|
Esta propiedad es diferente a las que habíamos visto hasta ahora. No contiene una cadena entre comillas, ni tampoco una lista de valores o una constante. En vez de ello contiene una rutina (reconocible porque va encerrada entre corchetes). Es decir, esta propiedad contiene un pequeño programa que la librería ejecutará bajo ciertas condiciones. Cuando esta rutina sea llamada, una variable oculta contiene la acción que el jugador ha efectuado sobre este objeto. Esta acción es comparada con las acciones que hemos escrito dentro de la propiedad (en nuestro ejemplo, con 'Coger' y con 'Dejar'. Si coincide con una de ellas, se ejecutará el código que hay tras los dos puntos. Así, si la acción que el jugador ha efectuado ha sido 'COGER SETA', se ejecutará el mensaje "Coges la seta, cortando con cuidado..." (Ejecutar un mensaje entre comillas, para Inform significa mostrarlo en pantalla y terminar la rutina). Si, en cambio, la acción del jugador fue 'DEJAR SETA' se ejecutará el mensaje "La seta cae al suelo...". Por tanto, ante las acciones Coger y Dejar hemos cambiado el mensaje por defecto (el juego ya no escribirá "Cogida." y "Dejada.") Si la acción del jugador no era ninguna de estas, la rutina despues no hace nada especial, por lo que será la librería la que se ocupe de mostrar el mensaje apropiado.
El juego empieza a ser un poco más interesante:
Bosque Sombrío En este diminuto claro, la alfombra de agujas de pino se ve rota por unos escalones tallados en piedra que descienden hacia la oscuridad. Los oscuros árboles crecen por todos los lados, el aire está húmedo por la lluvia reciente, las lianas cuelgan en el aire. Una seta moteada, con un largo tallo, emerge del suelo empapado. >coge seta Coges la seta, cortando con cuidado su largo tallo. >examina seta La seta está totalmente cubierta de manchas y no estás muy seguro de que no sea venenosa. >dejala La seta cae al suelo, ligeramente machacada. > |
Ahora la seta es un poco más convincente, pero aún demasiado pasiva. Podemos darle una nueva regla de comportamiento (en este caso con un poco de mala idea), añadiéndole la siguiente propiedad:
antes [;
Comer:
if (random(100) <= 30)
{
banderafin=1;
"Basta con un diminuto bocado. Era una seta venenosa
¡Y ya ha envenenado a alguien!";
}
"Mordisqueas una esquina, pero tiene un sabor curioso que
te repele.";
],
|
La rutina antes es ejecutada por la librería antes de que tenga lugar la acción solicitada por el jugador. El resto de su estructura es idéntica a la de la rutina despues, es decir, si la acción que el jugador está intentando es Comer, se ejecutará el código que hemos escrito, que comienza generando un número aleatorio entre 0 y 100, y si este número es menor de 30 el jugador resultará envenenado (lo cual se indica poniendo un 1 en la variable banderafin y escribiendo el texto apropiado en pantalla). Si el número es mayor de 30 no pasa nada (aunque escribimos un mensaje para que el jugador sepa que su acción ha sido reconocida). Si la acción no era Comer, la rutina antes no hará nada especial, por lo que será la librería la que lleve a cabo la acción según sus reglas estándar.
![]() | La variable banderafin normalmente vale 0 durante todo el juego. Si en algún momento la cambiamos por 1, esto hará terminar el juego con la muerte del jugador. Si se cambia por 2, el juego terminará también, pero en este caso con éxito. Se trata de una variable global que no pertenece a ningún objeto. |
Observar que si se entra en el if, se asignará un 1 a banderafin y después se escribirá un texto, lo cual hará terminar a la rutina. El otro texto (que hay fuera del if) no llega a ser ejecutado. Así que sólo uno de los mensajes será impreso (como tiene que ser). Veamos cómo se consigue. Cuando la librería ejecuta la rutina antes debemos imaginar que le está preguntando al objeto seta "¿Vas a cambiar las reglas habituales?". La rutina antes debe responder "sí" (y entonces la librería ya no hará nada más), o bien "no" (y entonces la librería seguirá como si nada, aplicando sus reglas habituales). La forma de responder "sí" desde dentro de la rutina es retornar el valor true, y la forma de responder "no" sería retornar el valor false.
Debido a que en un juego Inform deberemos responder a esta pregunta en gran número de situaciones, existen varias formas (y abreviaturas) para responderla. Por ejemplo:
return true; rtrue; |
Ambas hacen lo mismo. Es más, la orden
print_ret "Basta con un diminuto bocado..."; |
Lleva a cabo tres importantes acciones: imprime el mensaje entre comillas, después añade un retorno de carro (para que el texto a imprimir más tarde salga ya en otra línea) y finalmente retorna el valor true. Estas tres acciones en secuencia se dan tan a menudo que Inform tiene otra abreviatura aún más útil:
"Basta con un diminuto bocado..."; |
La mera cadena entre comillas (sin print_ret delante) efectúa
las mismas acciones que en el caso anterior (lo cual implica que la
función termine inmediatamente retornando true). Si queremos
simplemente imprimir un mensaje sin que la función termine (o bien
queremos retornar false) habrá que usar el comando
print, como por ejemplo:
antes [;
Probar: print "Extiendes nervioso la lengua.^";
rfalse;
],
|
En este caso cuando la librería llame a la rutina antes, se
mostrará el mensaje "Extiendes nervioso la lengua." (el ^ a
continuación indica un retorno de carro) y seguidamente se termina la
rutina devolviendo el valor false. Recordemos que desde el
punto de vista de la librería la ejecución de esta rutina es como si
preguntara a la seta "¿Vas a cambiar las reglas habituales?",
y en este caso la seta respondería "no", por lo que la librería
seguiría aplicando su regla estándar para la acción Probar, y
por tanto mostraría a continuación el mensaje "No saboreas nada
inesperado." (De hecho, la instrucción rfalse podría
quitarse y el resultado sería el mismo, ya que si una
propiedad-rutina, como es el caso de antes, llega a su final
sin haber retornado, automáticamente retornará false).
EJERCICIO 1 |
La seta que hemos programado tiene un pequeño "bug". El mensaje que
dice que "cortas su tallo" aparece cada vez que se coge la seta,
cuando en realidad sólo debería aparecer la primera vez que se coge la
seta. Intenta corregir este problema (Pista: puedes ponerle a la seta
el atributo general como indicador de que ya ha sido cortado
su tallo). (Solución) |
![]() |
Puede ocurrir en algún caso que la rutina antes o
despues deba ejecutarse una sola vez para un objeto dado, y
no volver a ejecutarse nunca más. La forma de lograr esto es hacer
que la rutina se "autodestruya", por ejemplo:
En este caso se elimina por completo la regla `antes' del objeto tapiz. NULL es un valor especial que significa, para el caso de las propiedades antes, despues, vida y describir que no hay rutina. |
Veamos otro ejemplo. Añadimos unos escalones al juego. (NOTA:Si añades el objeto siguiente al juego, no podrás compilarlo, ya que estos escalones son en realidad una puerta que llevan a un lugar llamado Camara, y éste no está programado. Veremos muchas más cosas sobre las puertas y cómo enlazar unas habitaciones con otras más adelante. El siguiente objeto se da, más que nada, como un ejemplo de sintaxis pero no se explica cómo funcionaría).
Object "escalones tallados en piedra" Bosque
with nombre 'escalon' 'escalones' 'tallado' 'tallados' 'en'
'piedra',
descripcion
"Los agrietados y gastados escalones descienden a una cámara
oscura. Tus pies serían los primeros en pisarlos en los
últimos quinientos años.",
puerta_a Camara,
direcc_puerta abajo,
has escenario puerta abierta masculino nombreplural;
|
Vemos de nuevo la forma típica de escribir un objeto: primero la cabecera (encabezada por la palabra Object), seguidamente la lista de las propiedades (encabezada por la palabra with). Al final de cada propiedad, una coma. Finalmente la lista de atributos (encabezada por la palabra has).
No obstante en este objeto la cabecera es diferente a la seta. Comparalas con cuidado y verás las siguientes diferencias:
Puedes usar la "versión flecha" o la versión con nombre al final. Personalmente prefiero la versión con nombre al final, que es mucho más clara y además te permite escribir los objetos del juego en el orden que prefieras (usando la "versión flecha" necesitarás escribir todos los objetos que hay en una habitación justo a continuación de la propia habitación.
Ten cuidado de no intentar usar ambas modalidades a la vez.
Los escalones no tienen este "nombre interno". Esto significa que el programador no podrá manipular este objeto desde el programa (no podrá cambiar su descripción ni sus atributos). En este caso particular esto no es problema ya que este objeto nunca se va a mover de donde está ni necesitamos que cambie nada de él. Además de este modo evitamos tener que inventar un nombre para el objeto.
![]() | Observar que en la lista de sinónimos que aparece en la propiedad nombre,no se pueden poner acentos. Es correcto dar el sinónimo 'escalon', pero es incorrecto 'escalón'. Esto es algo que sólo afecta al programador, no al jugador. No te preocupes que si el jugador escribe "EXAMINA ESCALÓN" (con acento), el programa lo comprenderá perfectamente (ya que elimina los acentos de lo que el jugador escribe). |
![]() |
|
![]() | ![]() | ![]() | ¡Empecemos! |