![]() | ![]() | ![]() | Acciones y reacciones |
Inform es un lenguaje obsesionado con las acciones. Una 'acción' es un intento de llevar a cabo una tarea simple. Por ejemplo:
Inv Coger espada Meter moneda_dorada bolsa_tela |
son ejemplos de acciones. Aquí, realmente las acciones son Inv, Coger y Meter. Una acción lleva asociados 0, 1 ó 2 objetos (en algunos casos especiales, en vez de objetos tiene asociada una información numérica). La mayoría de las acciones son generadas por el parser del juego; de hecho el trabajo del parser puede resumirse en traducir las palabras del jugador a acciones. A veces una acción causa otra. Una orden complicada del jugador (como por ejemplo "vacia el saco en la mesa") puede lanzar una larga secuencia de acciones.
Una acción es un intento, puede fracasar. En primer lugar una rutina
antes puede interferir, como ya hemos visto. En segundo lugar
puede que la acción en sí no tenga sentido. El parser generará
alegremente la acción Comer espada_hierro, si el
jugador lo pidió en correcto español. En este caso, incluso aunque
ninguna regla antes interfiera, las reglas normales de la
librería se asegurarán de que la espada no sea engullida.
Las acciones también pueden ser generadas por tu propio código, y esto simulará perfectamente el efecto de que ha sido escrito por el jugador. Por ejemplo, si generas la acción Mirar, el juego producirá la descripción de la habitación en que está el jugador, exactamente igual que si él hubiera tecleado "MIRA". Un ejemplo más sutil, supón que en "la habitación de la pimienta" el aire causa que el jugador estornude en cada turno, y se le caiga algo al azar. Esto podría programarse "a mano", mediante instrucciones move que movieran los objetos del jugador al suelo. Pero ahora supón que el juego contiene un caramelo pegajoso, que se queda pegado a la mano del jugador. Si no ponemos cuidado al programarlo, pudiera ser que un estornudo causase la pérdida del caramelo, con lo que habríamos dado al jugador una solución imprevista para su problema con él. Así que es mejor que, en lugar de mover los objetos directamente con move, el juego genere acciones Dejar. De este modo el juego podría verse así:
Estornudas convulsivamente y dejas de agarrar el caramelo pegajoso. El caramelo pegajoso sigue pegado en tu mano. |
lo cual es, al menos, consistente.
Otro ejemplo de causar acciones: añadiremos más adelante una
niebla_olorosa a nuestro juego "Ruinas". Tendrá como
descripción "La niebla transporta un rico aroma a caldo." El jugador
listo que lea esto inmediatamente intentará "HUELE NIEBLA", y
deberíamos suministrar una respuesta mejor que la estándar "No hueles
nada extraño." Una forma barata de hacer esto sería conseguir que la
acción Oler niebla_olorosa generase la acción
Examinar niebla_olorosa, y así el mensaje del "aroma
a flores" sería impreso también en este caso. Aquí hay una regla que
haría eso:
Oler: <Examinar self>; rtrue; |
El comando <Examinar self> causa inmediatamente la acción
Examinar niebla_olorosa, y cuando esta acción haya
terminado, se continuará por el punto donde estaba. En nuestro ejemplo
inmediatamente después retornamos true, con lo que damos por
terminada la acción Oler.
Causar una acción y a continuación retornar true tiene el efecto de sustituir una acción por otra. Esto es muy útil, y tiene una abreviatura: poner la acción entre ángulos dobles. Por ejemplo:
<Mirar>; <<Lanzar piedra arania>>; |
hará que el juego se comporte como si el jugador hubiera mirado alrededor y después lanzado una piedra a la araña, tras lo cual retornará true.
En un momento dado, sólo una acción está siendo efectuada (aunque puede haber otras acciones esperando a que termine esta). Esta acción actual se encuentra almacenada en tres variables:
accion uno otro
|
uno y otro contienen los objetos involucrados en la
acción, o bien el valor especial nothing si no hay objetos en
esta acción. La variable accion representa el tipo de
acción. Podemos referirnos a sus posibles valores usando una notación
que consiste en poner ## seguido del nombre de la acción (los
nombres de las acciones están predefinidos en la librería, aunque el
autor puede añadir sus propias acciones como ya veremos). Así por
ejemplo, para comprobar si la acción es Mirar escribiremos:
if (accion == ##Mirar) ... |
![]() |
¿Por qué hay que escribir ##Mirar en vez de, simplemente
Mirar? En parte porque de este modo el lector del programa
podrá comprobar de un vistazo que se trata de un tipo de acción,
pero también porque el nombre Mirar podría quererse para
otra cosa. Por ejemplo, hay una variable llamada puntuacion
que almacena los puntos logrados por el jugador, y también hay una
acción ##puntuacion que muestra esos puntos. Piensa también
en el ejemplo del loro. Le hemos dado una propiedad llamada
gritar, pero todavía tenemos disponible la acción
##gritar si quisiéramos añadir este verbo a nuestro juego
(por defecto la librería no tiene esta acción, aunque comprende el
verbo "Grita", generara para él la acción Responder)
|
![]() ![]() | Para algunas acciones (pocas) las variables uno u otro pueden contener un número en lugar de un objeto. (Por ejemplo "Marca 145 en el teléfono" podría acabar con el valor 145 en la variable uno y el objeto telefono en la variable otro. Pero debido a que los objetos también son números (en la codificación interna de Inform) no es fácil saber si el 145 que hay dentro de uno era un número originalmente escrito por el jugador o es el número interno de algún objeto del juego. Para ayudar a estos casos existen dos variables adicionales, llamadas dat1 y dat2, paralelas a uno y otro. Normalmente dat1 y dat2 tienen los mismos valores que uno y otro, respectivamente. No obstante si dat1 valiera 1, significaría que "el número almacenado en uno es un valor numérico, no un objeto" (y análogamente para dat2 y otro). |
La librería soporta más de 120 acciones y cualquier juego de un tamaño
decente añadirá más. Esta enorme lista puede atemorizarte, pero muchas
de las acciones son usadas raramente, y hay otras que se convierten en
una serie de acciones más simples. Por ejemplo <VaciarEn
mochila mesa> (que es la acción que se generaría si
el jugador pone "Vacía la mochila sobre la mesa"), se convierte en
una secuencia de acciones como <Sacar pez
mochila>, <PonerSobre pez mesa>. Es
muy útil saber que un objeto sólo puede pasar al inventario del
jugador a través de una acción Coger o Sacar. Si
bloqueamos estas acciones, el jugador no podrá hacerse con ningún
objeto, no importa lo que teclee.
La lista de acciones puede dividirse en tres grupos, que llamaremos Grupo 1, Grupo 2 y Grupo 3. El Grupo 1 contiene `meta'-acciones que sirven para controlar aspectos exteriores al propio juego, como Puntuacion o Salvar. Estas acciones son tratadas de forma ligeramente diferente a las otras, pero no merece la pena listarlas aquí. De las restantes, las acciones que normalmente hacen algo forman el Grupo 3, mientras que las que se limitan a escribir un educado mensaje de rechazo forman el Grupo 3. El grupo 2 contiene:
Inv [inventario], Coger, Dejar, Sacar, PonerSobre, Meter, Entrar, Salir, Ir, Mirar, Examinar, QuitarCerrojo, EcharCerrojo, Encender, Apagar, Abrir, Cerrar, Desvestir, Vestir, Comer, BuscarEn |
Debería ser evidente que las acciones anteriores hacen algo. En cambio una acción como Escuchar cae en el Grupo 3: la librería normalmente responderá "No escuchas nada fuera de lo común." Sólo si tu programa interfiere (a través de una regla antes) podrá ocurrir algo más. El grupo 3 contiene, más o menos en orden de utilidad:
Tirar, Empujar, EmpujarDir [Empujar un objeto en una dirección], Girar, Lanzar, Consultar, MirarDebajo, Escuchar, Probar, Beber, Tocar, Oler, Esperar, Cantar, Saltar [saltar en el sitio], SaltarSobre, Atacar, Columpiar [en algo], Soplar, Frotar, Fijar, PonerA [poner un control a un valor], Agitar, Quemar, Excavar, Cortar, Atar, Llenar, Nadar, Trepar, Retorcer, Rezar, Pensar, Dormir, Despertarse, Gesticular, DespertarOtro [despertar a alguien], Besar, Responder, Preguntar, Hablar, Si, No, LoSiento, Tacos [palabras malsonantes], Soso [insultos sin garra], |
![]() | Las acciones que tienen que ver con otros seres vivos, como Besar se manejan mejor mediante una regla llamada vida (de funcionamiento similar a antes) que veremos en la sección sobre criaturas. |
![]() | Se han omitido algunas acciones de la lista anterior (como Transferir, Vaciar o Salirse), porque al final son convertidas por la librería en otras más familiares. Por ejemplo, la acción InvAncho (que sirve para mostrar el inventario de lo que lleva el jugador en un formato "ancho") siempre termina por generar una acción Inv. |
![]() ![]() | La acción BuscarEn es generada cuando el jugador escribe algo como "Mira dentro del recipiente", o "Registra algo". Esta acción en realidad sólo imprime texto, pero está en el Grupo 2 (en vez del 3) porque lleva a cabo algo más sustancial: Determina primero si el objeto dado es un recipiente y si hay bastante luz para verlo, imprime sus contenidos. Así, la regla antes aplicada a la acción BuscarEn puede usarse para impedir la búsqueda en algunos elementos del escenario, mientras que la regla despues puede usarse para alterar la forma en que se muestra los contenidos de los recipientes. |
![]() ![]() |
La mayoría de las acciones del grupo 2, en particular las
siguientes:
pueden ocurrir de forma "silenciosa". Si la variable tate_callao se pone a 1, estas acciones no imprimirán nada, incluso si se ejecutan con éxito (por ejemplo, si se ha abierto la puerta como se había pedido). Pero imprimirán los mensajes de error habituales si algo va mal (por ejemplo, si la puerta estaba cerrada con llave). Esto es útil para programar acciones implícitas, por ejemplo, una puerta que se abre automáticamente si el jugador intenta ir por ella y estaba cerrada. |
A las acciones estándar anteriores puedes añadir las tuyas propias de forma muy sencilla. Para crear una nueva acción debes hacer dos cosas. En primer lugar escribir una rutina que efectúe la acción solicitada, por ejemplo:
[ XyzzySub; "Pronuncias la palabra mágica ~Xyzzy~, pero no ocurre nada."; ]; |
Cada acción tiene que tener una rutina como esta, cuyo nombre será siempre el nombre de la acción con el sufijo "Sub" pegado (en este ejemplo, la acción sería Xyzzy). En segundo lugar, debemos escribir una línea de gramática, de modo que el jugador pueda realmente causar esta acción escribiendo un verbo. Veremos mucho más sobre gramática en el capítulo sobre descripciones y parsing, de momento nos conformaremos con la gramática más sencilla posible, que sería la línea siguiente:
Verb 'Xyzzy' * -> Xyzzy; |
que habría que escribir después del "Include "Gramatica";". El número de espacios detrás del * no es importante. Ahora el jugador puede escribir "XYZZY" para causar esa acción, sin embargo este verbo no admite nombres, por lo que si el jugador intenta "XYZZY ROSA", el juego protestará.
![]() | En los juegos españoles los verbos que se esperan del jugador siempre son imperativos (es decir, se espera que el jugador escriba "MUEVE"), mientras que las acciones son siempre infinitivos (Mover). En la línea de gramática se especifica la forma imperativa, entre comillas simples (lo que se espera que escriba el jugador), y tras la flecha se escribe la acción generada para ese verbo. En el ejemplo anterior, tanto el verbo como la acción son Xyzzy, por lo que era necesaria esta aclaración. |
Con la definición anterior el juego tiene un nuevo verbo del Grupo 3 (ya que en realidad, como se ve en la rutina XyzzySub, realmente no ocurre nada en el juego). Esta acción puede capturarse en la rutina antes de los objetos (aunque en este caso no tendría sentido ya que ese verbo no puede aplicarse sobre objetos), o puede ser también generada dentro del juego mediante una orden como:
<Xyzzy>; |
![]() ![]() | Para crear acciones del Grupo 1 es necesario definir el verbo como meta. Ver la sección sobre cómo se interpretan los verbos. |
![]() ![]() |
Para crear acciones del Grupo 2, además de programar la rutina para
que realmente haga algo en términos del juego, hay que hacer que
esta rutina envíe mensajes despues a los objetos que
participen en la acción. Esto se lograría con una rutina como la
siguiente:
La rutina RutinasDespues() forma parte de la librería y se ocupa de enviar mensajes despues a los objetos adecuados. Si alguno de ellos retorna true, nuestra rutina ya no hace nada más. De lo contrario imprime el mensaje de éxito. En realidad las acciones del Grupo 2 de la librería están programadas según ese mismo esquema. |
![]() |
Algunas de las acciones de la librería no encajan de hecho en
ninguno de los tres grupos vistos, ya que no son verdaderas acciones
sino que se usan sólo para señalar a otros objetos lo que está
ocurriendo. Por ejemplo, si el jugador "LANZA ROCA AL ENANO", el
parser generará la acción Lanzar roca enano. Se enviará un
mensaje antes a la roca, preguntándole si pone alguna
objeción a ser lanzada sobre el enano. Si la roca responde
true, la librería no hará nada más. Pero si responde
false la librería debería causar la acción del
lanzamiento. Antes de ello, no obstante, sería útil conocer también
la opinión del enano, por lo que la librería enviará un mensaje
antes al enano también, sólo que en este caso la acción
cambia y pasa a ser RecibirLanzamiento. Por ejemplo, la
siguiente podría ser la respuesta de una diana ante el lanzamiento
de algo (sólo aceptará que le lancen dardos):
Este tipo de acciones imaginarias que son perfectamente lógicas desde el punto de vista del objeto otro, en lugar del objeto uno, se denominan "acciones falsas" (en inglés fake actions). Las más importantes son RecibirLanzamiento, Recibir y DejarSalir (las dos últimas se usan al meter y sacar cosas de recipientes, como veremos en la sección sobre recipientes y superficies). |
![]() ![]() | Si realmente lo consideras necesario, puedes inventar tus propias acciones falsas con la directiva Fake_action NombreDeAccion; |
![]() ![]() |
|
Las acciones se procesan de una forma muy sencilla, pero que requiere varios pequeños pasos. Estos son los estados por los que va pasando la acción:
![]() | Las acciones del Grupo 1 no tienen los estados 'antes' ni 'después', así que no puedes impedir que tengan lugar (al menos no de forma fácil). Estos comandos no ocurren en el mundo del juego, sino en el del jugador. |
![]() |
Durante el estado 'Antes' la librería llama a tu código varias veces
y puede resultar útil conocer en qué orden lo hace:
|
![]() | Para realizar la fase 'Durante', la librería llama a la rutina encargada de la acción. Por ejemplo, la acción Coger está programada en la rutina CogerSub (que, por cierto, ocupa una buena parte de la librería ya que tiene que manejar muchos detalles). |
![]() |
La fase 'Después' sólo se aplica a las acciones del Grupo 2, ya que
todas las del Grupo 3 ya han sido tratadas en la fase 'Durante' (si
no 'antes'). En la fase 'Después' la secuencia es como sigue:
|
![]() ![]() | Las dos cosas "falsas" de las acciones falsas son: no tienen rutina (no existe por ejemplo RecibirLanzamientoSub), y no tienen línea de gramática (el jugador no puede escribir un verbo que genere esa acción de forma directa, sólo se generará de forma indirecta como consecuencia de otra acción). Tampoco puede generarse una acción falsa con la sintaxis: <RecibirLanzamiento uno otro>. |
![]() |
Hay que tener cuidado al escribir rutinas antes, y tener
presente que cuando una de estas rutinas es ejecutada, el parser aún
no ha comprobado si la acción es posible. Me explico con un ejemplo:
supón que la rutina antes de la seta intercepta la acción
Meter para detectar si estás intentando meter la seta en
una maceta mágica, y en ese caso haría ocurrir algo especial (qué se
yo, por ejemplo la seta se hace enorme). El caso es que al haber
programado esto como parte de la rutina antes, estamos
permitiendo que pasen cosas raras en el juego. Imagina que la seta
está dentro de una botella cerrada. Si el jugador pone "PON SETA EN
MACETA", el parser de momento sólo comprueba que la seta y la
maceta sean visibles (si no, el jugador no podría referirse a
ellas), y ya que la seta está dentro de una botella transparente, es
visible. Así que el parser de momento admite la orden y pasa a la
fase 'Antes', causando que se ejecute la rutina antes de la
seta y por tanto que ocurra la magia, cuando en realidad no debía
haber ocurrido por estar la seta dentro de la botella.
Esto se debe a que la librería hace las comprobaciones de accesibilidad más tarde, en la fase 'Durante'. Es entonces cuando se daría cuenta de que la seta no puede meterse en la maceta, porque está dentro de la botella (e imprimiría el mensaje que indica al jugador que debe sacar la seta de la botella antes). Solución: el mágico crecimiento de la seta tiene que ser programado en la rutina despues, cuando ya hay garantías de que la seta realmente ha sido puesta en la maceta.
|
![]() ![]() | Hasta cierto punto puedes incluso entrometerte en la fase 'Durante', (y en los mensajes de error que se producen en esta fase), y por consiguiente incluso interferir con las acciones del Grupo 1, si eres lo suficientemente falto de escrúpulos. Esto puede lograrse con un hábil uso de MensajesLibreria. Ver la sección sobre cómo extender la librería. |
![]() | ![]() | ![]() | Acciones y reacciones |