Parte II: Empieza lo interesanteParte I: Lo más básicoCómo se ejecutan las accionesProgramando la antorcha (¡por fin!)

Programando la antorcha (¡por fin!)

Cambiar las respuestas

Necesitamos un modo de discernir qué acción es la que está intentando el jugador, para que la respuesta de la antorcha no sea siempre la misma. Esto es muy sencillo, cambiemos la antorcha por esto:

Object antorcha "antorcha" EscaleraCaracol2
with   name 'antorcha',
       before [;
         Take: print "La antorcha está incrustada en la pared.^";
                rtrue;
              ],
has female;     

Ahora, el print y el rtrue sólo se ejecutarán si la acción era Take (¡cuidado! Hablamos de acciones, no de verbos, recuerda). Cualquier otra acción es ignorada por la antorcha, y por tanto causará la acción por defecto.

Si queremos que la antorcha reaccione de la misma forma ante dos o más acciones diferentes, basta poner éstas separadas por comas, por ejemplo:

Object antorcha "antorcha" EscaleraCaracol2
with   name 'antorcha',
       before [;
         Take, Turn: print "La antorcha está incrustada en la pared.^";
                rtrue;
              ],
has female;     

Ahora el print y el rtrue se ejecutarán para las acciones Take y Turn, las restantes acciones seguirán siendo tratadas por la librería.

Si queremos diferentes mensajes de respuesta ante diferentes acciones, basta ir repitiendo una estructura similar a la anterior, por ejemplo ¿qué pasa si el jugador pone SOPLA LA ANTORCHA? Intentémoslo (con el listado de acciones activado para ver qué acción se genera):

>sopla antorcha
[ Action Blow with noun 31 (antorcha) ]
Tu soplido no produce ningún efecto.

¡Vaya! Inform tenía prevista una acción Blow (Soplar), y la respuesta por defecto no parece del todo mala, pero cambiémosla por otra más apropiada:

Object antorcha "antorcha" EscaleraCaracol2
with   name 'antorcha',
       before [;
         Take, Turn: print "La antorcha está incrustada en la pared.^";
                rtrue;
         Blow: print "Iluso, el fuego que arde en esta antorcha no
                 es de este mundo.^";
                rtrue;
              ],
has female;     

¡Acuerdate de poner el rtrue! (¿Qué pasaría si no lo pones?) La verdad es que repetir rtrue después de cada print es un poco pesado. Por eso Inform nos proporciona una abreviatura: el comando print_ret imprime una cadena e inmediatamente retorna con rtrue. Además, imprime una "linea nueva" al final del texto, por lo que nos evita tener que poner el ^ en cada mensaje. Usando esta abreviatura, la cosa quedaría así:

Object antorcha "antorcha" EscaleraCaracol2
with   name 'antorcha',
       before [;
         Take, Turn: print_ret "La antorcha está incrustada en la pared.";
         Blow: print_ret "Iluso, el fuego que arde en esta antorcha no
                 es de este mundo.";
              ],
has female;     

Aún es posible abreviar más. ¿No te suena esto de "imprimir, añadir salto de linea y retornar inmediatamente"? Todo ello podía abreviarse sin más que poner el mensaje entre comillas. Así:

Object antorcha "antorcha" EscaleraCaracol2
with   name 'antorcha',
       before [;
         Take, Turn: print_ret "La antorcha está incrustada en la pared.";
         Blow: "Iluso, el fuego que arde en esta antorcha no
                 es de este mundo.";
              ],
has female;       

Esta forma es muy habitual, porque resulta muy cómoda. Sin embargo es un poco "rara" para el principiante, ya que en realidad se trata de una orden print, pero el print no se escribe. Además retorna inmediatamente (con rtrue), y eso tampoco se ve claramente en el código. Con todo, si sabes esto, no deberías tener problemas con su uso.

Como ves, añadir mensajes específicos para la antorcha resulta muy sencillo. ¡Añade tú mismo alguno! Por ejemplo, ¿tendrá Inform previsto HUELE ANTORCHA? ¿COME ANTORCHA? ¿QUEMA ANTORCHA? ¿TOCA ANTORCHA? Pruebalo, verás que todos ellos están previstos, pero el mensaje de respuesta estándar puede no encajar muy bien. Averigua qué acciones genera cada una de las frases anteriores y modifica la antorcha para que imprima mensajes más apropiados.

Haciendo que aparezca la puerta

Pero ahora pasemos a algo más serio. Hasta aquí, nos hemos limitado a variar los textos de respuesta de la antorcha, pero no hemos programado nada "realmente", en el sentido de que todas estas acciones no tienen efecto sobre el juego. Queremos que, al empujar la antorcha, aparezca una salida en EscaleraCaracol2. Recuerda que las salidas de una localidad son propiedades de esa localidad. Ahora tenemos un pequeño problema:

Clave Una vez que el juego ha comenzado, no se pueden añadir o quitar propiedades a los objetos, pero sí que es posible cambiar el valor de cualquier propiedad de cualquier objeto.

Por tanto no podemos hacer que EscaleraCaracol2 empieze sin su propiedad w_to, y más adelante en el juego de pronto adquiera esta propiedad. Sin embargo, lo que si podemos hacer es que EscaleraCaracol2, inicialmente tenga la propiedad w_to, pero esta propiedad tome el valor cero (que es lo mismo que decir que esa salida no lleva a ningún lugar). Más adelante, cuando se empuje la antorcha, cambiamos el valor de esta propiedad para que sea igual a Mazmorra.

Es decir, edita el juego y añade de nuevo la salida w_to en la escalera, pero en lugar de hacer que lleve a Mazmorra, haz que "lleve a 0", así:

Object  EscaleraCaracol2 "Escalera de caracol"
 with   description "Los desgastados peldaños de piedra resbalan en 
            ocasiones. A mitad de la escalera una antorcha en la 
            pared impide que la oscuridad sea completa.",
        d_to EscaleraCaracol1,
        w_to 0,
        u_to AltoTorre,
 has    light;

Cuando una salida "lleva a 0 (cero)", el jugador no podrá pasar por ella. Es como si no existiera. Pero el declararla de este modo nos permitirá cambiar su valor más adelante en el juego, mediante una instrucción como esta:

 EscaleraCaracol2.w_to = Mazmorra;  

Que puedes leer así: "Cambia la propiedad w_to del objeto EscaleraCaracol2, y haz que sea igual a Mazmorra". Si quisieramos eliminar esa salida, basta hacerla igual a cero de nuevo:

 EscaleraCaracol2.w_to = 0;  

¿Cuándo debe ejecutarse este código? Evidentemente cuando el jugador empuje o tire de la antorcha. Cuando empuja la conexión se crea, cuando tira, la conexión desaparece. Es decir:

Object antorcha "antorcha" EscaleraCaracol2
with   name 'antorcha',
       before [;
         Take, Turn:  "La antorcha está incrustada en la pared.";
         Blow:  "Iluso, el fuego que arde en esta antorcha no
                 es de este mundo.";
         Push: EscaleraCaracol2.w_to = Mazmorra;
                  "Al empujar la antorcha una porción de pared se abre 
                   al oeste dando acceso a una estancia.";
         Pull: EscaleraCaracol2.w_to = 0;
                "Al tirar la antorcha, la puerta secreta se cierra 
                 de nuevo.";
              ],
has female;     

¿Por qué he puesto la asignación antes del mensaje? ¿Daría igual hacerlo al revés?

Intenta probar si la cosa funciona. Empuja la antorcha, se abre la conexión, tira de ella, se cierra. ¡Bien! Empuja de nuevo, se abre otra vez. Y ahora que está abierta ¡empuja otra vez! ¿qué ocurre? Hum, esto no queda muy bien, el mensaje diciendo que la pared se mueve aparece de nuevo.

Deberíamos comprobar si la conexión existe antes de mostrar ese mensaje. Un poco más de programación:

Object antorcha "antorcha" EscaleraCaracol2
with   name 'antorcha',
       before [;
         Take, Turn:  "La antorcha está incrustada en la pared.";
         Blow:  "Iluso, el fuego que arde en esta antorcha no
                 es de este mundo.";
         Push: if (EscaleraCaracol2.w_to==0) {
                     EscaleraCaracol2.w_to = Mazmorra;
                     "Al empujar la antorcha una porción de pared se
                     abre al oeste dando acceso a una estancia.";
                 }
                 else "La antorcha no cede más.";
         Pull: if (EscaleraCaracol2.w_to == Mazmorra) {
                    EscaleraCaracol2.w_to = 0;
                    "Al tirar la antorcha, la puerta secreta se cierra 
                    de nuevo.";
                 }
                 else "La antorcha no cede más.";
              ],
has female;     

Hemos usado aqui una estructura condicional, es decir, una forma de decirle a Inform que cierto código sólo debe ejecutarse si se cumple cierta condición. La estructura if...else tiene la siguiente sintaxis:

  if (condicion) {
    ! Secuencia de instrucciones que deben ejecutarse
    ! si la condición se cumple
  }
  else {
    ! Secuencia de instrucciones que deben ejecutarse
    ! si la condición no se cumple
  }

Observaciones:

Truco ¡Cuidado! Una pifia típica es olvidar las llaves cuando hay más de una instrucción. Considera el ejemplo siguiente:

  if (talisman in player)
     AumentarFuerza();
     print "Sientes como tu fuerza aumenta.";

Parece que el programador pretendía aquí que la fuerza del jugador aumente si lleva un talismán consigo (la condición talisman in player sería cierta en este caso), y además mostrar un mensaje indicandole ésto al jugador. Sin embargo, ha olvidado poner llaves alrededor de estas instrucciones. ¿Qué crees que pasará? ¿Dará un mensaje de error Inform?

La respuesta es que no habrá mensaje de error, pero en cambio el juego no funcionará de la forma prevista. Seguramente lo verás más claro si elimino un par de espacios delante de print (para Inform es lo mismo que haya dos espacios o que haya doce):

  if (talisman in player)
     AumentarFuerza();
  print "Sientes como tu fuerza aumenta.";

¿Vés ahora mejor lo que ocurre? Al no poner llaves, Inform entiende que la "secuencia de instrucciones a ejecutar en caso de que la condición sea cierta", se compone de una sola instrucción: la llamada a la rutina AumentarFuerza(). El print que le sigue se considera que ya está "fuera del if", y por tanto no depende de la condición. Es decir, el mensaje "Sientes como tu fuerza aumenta" se imprimiría incluso si el jugador no lleva talismán (¡sin embargo su fuerza no aumentaría!).

Una vez explicado if, podemos ver con mayor claridad qué ocurre cuando el jugador empuja la antorcha. El código que se ejecutará entonces será el siguiente (un extracto de la rutina before de la antorcha):

Push: if (EscaleraCaracol2.w_to==0) {
            EscaleraCaracol2.w_to = Mazmorra;
            "Al empujar la antorcha una porción de pared se
            abre al oeste dando acceso a una estancia.";
        }
        else "La antorcha no cede más."

La condición que comprobamos es EscaleraCaracol2.w_to==0. El doble signo == significa que se intenta comprobar la igualdad, y no debe ser confundido con un solo = que sería una asignación como ya hemos visto. Por tanto la condición evaluada es "¿el valor de la propiedad w_to del objeto EscaleraCaracol2 es igual a cero?" Si esta condición es cierta, se ejecutará lo que viene entre llaves a continuación. Si es falsa se ejecutará lo que va tras el else. Date cuenta de que si es cierta, esto implica que hacia el oeste la salida aún no existe, y por tanto lo que hacemos es crearla y emitir el mensaje. Y si es falsa, es que la salida hacia el oeste ya había sido creada, y por tanto emitimos un mensaje indicando que no ocurre nada más.

El código para el caso de la acción Pull (Tirar) es totalmente análogo.

Condiciones

Inform puede comprobar muchos tipos de condiciones diferentes. Ya hemos visto uno de ellos: la igualdad. Las comparaciones numéricas tienen una sintaxis bastante lógica: > significa "mayor que", < es "menor que", >= significa "mayor o igual" y <= sería "menor o igual".

Además de estos casos sencillos, hay otras comprobaciones interesantes que necesitarás usar para programar juegos:

~=
Desigualdad. El sigo ~ que aparece delante del igual se obtiene en la mayoría de los ordenadores pulsando AltGr y el 4. Si estás acostumbrado a programar en C, ten cuidado de no confundirte y usar != ya que en Inform el carácter ! es el comentario como ya hemos dicho.
in
Estar dentro. El resultado de esta condición es true (verdad) si el objeto que nombras a la izquierda del in está dentro del que nombras a su derecha. Por ejemplo antorcha in EscaleraCaracol2 es cierto, y sin embargo antorcha in player es falso (¡a menos que el jugador haya cogido la antorcha de alguna forma!)

Ten en cuenta que en Inform los objetos pueden estar dentro de otros y estos a su vez dentro de otros. Si el jugador llevara consigo una caja dentro de la cual hay una moneda, sería cierto caja in player y moneda in caja, pero no sería cierto moneda in player (la condición in se limita a comprobar la inclusión en un solo nivel).

has
Tener un atributo. El resultado de esta condición es cierto si el objeto que nombras a la izquierda de has tiene activado el atributo que nombras a su derecha. Por ejemplo antorcha has female sería cierto (puesto que en la declaración del objeto antorcha le hemos puesto el atributo female), o EscaleraCaracol2 has light también sería cierto (pues esta localidad tiene el atributo light). En cambio, antorcha has luz es falso pues no le hemos dado este atributo a la antorcha (pensandolo bien, parece que deberíamos haberselo dado, ¿no? De todas formas en este juego es intrascendente porque la antorcha nunca se mueve de su localidad)

Observa que has se limita a evaluar si el objeto dado tiene ese atributo, pero no tiene inteligencia alguna para hacer deducciones. Por ejemplo, si llevas una linterna que tiene el atributo light y con ella entras en una cueva que no tiene el atributo light, será cierto linterna has light pero falso que cueva has light. Es decir, has no es capaz de deducir que hay luz en la cueva (suministrada por la linterna). Para comprobar si hay luz en una localidad deberías comprobar si la localidad "has light" o si alguno de los objetos que tiene dentro "has light". Por suerte no tienes que molestarte en comprobar esto. La librería lo hace automáticamente en cada turno (de hecho hace algo mucho más complejo, pues tiene en cuenta si los objetos están dentro de otros. Así, si llevas la linterna dentro de una caja opaca, la librería deduciría que no hay luz visible, pero si la llevas dentro de una caja transparente sí la habría).

hasnt
Lo contrario de has. La condición objeto hasnt atributo es cierta si el objeto tiene ese atributo desactivado. Por tanto, Cueva hasnt light será cierto si la cueva no tiene luz.

Hay más condiciones, pero estas son las más importantes y las más usadas. Puedes combinar varias condiciones en una sola. Por ejemplo, si quieres que algo se ejecute sólo si el jugador lleva un talismán, y además el talismán tiene light, puedes combinar ambas condiciones usando el operador &&, en la forma siguiente:

  if ((talisman in player) && (talisman has light)) ...

Observa cómo hay que encerrar cada condición entre paréntesis, y colocar el operador && entre ambas.

Si lo que quieres es ejecutar algo cuando es cierta una condición cualquiera entre varias, por ejemplo, si el jugador lleva el talismán o si lleva el anillo, entonces el operador es || (con algunos teclados el símbolo | se obtiene pulsando AltGr y 1):

  if ((talisman in player) || (anillo in player)) ...

Puedes poner tantas condiciones como quieras combinadas con estos operadores, e incluso mezclar operadores. Piensa por ejemplo cómo harías para escribir una condición que sea "si el jugador (player) lleva el talismán con luz, o bien si lleva el anillo puesto" (para verificar si lleva el anillo puesto debes mirar si anillo has worn, además de comprobar que está en el jugador).

Inform tiene también una abreviatura muy cómoda para el caso de que estés comprobando varias igualdades alternativas. Por ejemplo, imagina que quieres emitir un mensaje si el jugador está en la localidad llamada Mazmorra, o en la localidad Pasadizo o en la localidad Sotano. En principio puede hacerse usando el operador || para diferentes comparaciones con la variable localizacion, así:

  if ((location==Mazmorra) || (location==Pasadizo)
      || (location==Sotano))  print "¡Qué frio!^"

Pero también es posible usar la siguiente forma abreviada:

  if (location== Mazmorra or Pasadizo or Sotano)
     print "¡Qué frio!^";

Esta abreviatura (or) sólo puede aplicarse para comparaciones de igualdad, en las que el elemento a la izquierda de la comparación es siempre el mismo, y queremos compararlo contra diferentes valores.


Zak McKraken - spinf_2000@yahoo.com.
Adaptado a JIF por Luis Fernandez nfseoi (en) yahoo.es bajo la licencia Creative Commons Attribution-ShareAlike 2.0
Adaptado a JIF3+INFSP por Sarganar - sarganar (en) gmail.com bajo la misma licencia.

Parte II: Empieza lo interesanteParte I: Lo más básicoCómo se ejecutan las accionesProgramando la antorcha (¡por fin!)