|  |  | Rompecabezas clásicos

## 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).

### Esqueleto y cuchillo

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 `IRDONDE ANTORCHA` (para teleportarnos a la escalera de caracol) y seguidamente `CAMBIOS`. Agarrate fuerte a la silla y pon:
    >empuja antorcha
    [Setting Escalera de caracol.w_to to 29]
    Al empujar la antorcha una porción de pared se abre al oeste
    dando acceso a una estancia.
Vemos que ha cambiado el valor de la propiedad `w_to` de la escalera de caracol, a la que ha dado el valor 29. Este 29, evidentemente, debe ser el número interno de la localidad `Mazmorra` (puedes verificarlo con `xlista`). Prosigamos, y caminemos hacia el oeste:
    >o
    [Moving ti mismo to Mazmorra]

    Mazmorra
    Una silenciosa estancia débilmente alumbrada por los rayos
    de luna que se filtran a través de un pequeño ventanuco. El suelo
    está lleno de paja, colgando de unos grilletes en la pared
    observas un esqueleto humano.
    [Giving Mazmorra visited]
La línea "[Moving ti mismo to Mazmorra]" indica que el objeto `player` ha sido movido al objeto `Mazmorra`. La línea "[Giving Mazmorra visited]" indica que se ha activado el atributo `visited (o sea visitado)` del objeto `Mazmorra`. Su función, como es evidente, es indicar si esa localidad ya ha sido visitada con anterioridad o no. La librería puede cambiar algunas cosas de la descripción en caso de que el jugador ya haya estado allí (por ejemplo, puede no escribir descripción en absoluto y limitarse a poner el título de la localidad. Como ya vimos, esto depende también del valor de la variable `lookmode`). Finalmente, veamos como aparece el cuchillo:
    >x esqueleto
    Los huesos amarillentos, las cuencas vacías[Moving pequeño cuchillo to Mazmorra]
    . Junto a él ves un pequeño cuchillo[Giving esqueleto general]
    .
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 `general` del esqueleto. Finalmente, el punto final (aparece aqui en línea aparte porque los avisos entre corchetes añaden siempre un salto de línea).

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.

### Examinar en Inform

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:

Examine
  Esta acción es causada por cualquiera de los verbos siguientes: "EXAMINA OBJETO", "X OBJETO", "EX OBJETO", "MIRA OBJETO", "MIRA HACIA OBJETO", "DESCRIBE OBJETO", "INSPECCIONA OBJETO", "OBSERVA OBJETO" y "LEE OBJETO".

  | Observa que "LEE OBJETO" causa la acción `Examine`. No hay acción `Read (Leer)` en Inform, aunque esto puede cambiarse si lo necesitas.

Search
  Esta acción es causada por cualquiera de las formas siguientes: "MIRA EN OBJETO", "MIRA DENTRO DE OBJETO", "MIRA SOBRE OBJETO", "MIRA A TRAVES DE OBJETO", "MIRA POR OBJETO", "BUSCA EN OBJETO", "REGISTRA OBJETO" y "REGISTRA EN OBJETO".

  | 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.

LookUnder
  Esta acción es causada por las formas: "MIRA BAJO OBJETO" y "MIRA DEBAJO DE OBJETO".

  | 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í

### La chimenea y el carbón

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 `name` de un mismo objeto. En este caso "DE" se entiende como una preposición que separa dos objetos diferentes, y por tanto para que el parser comprenda esta orden debe existir una gramática que le diga que la sintaxis "COGE `<cosa>` DE `<otra cosa>`" es correcta. (De hecho, la gramática contempla esta forma y generaría la acción `Remove (Sacar)` con `noun==carbon` y `second==chimenea`)

### La cama y la funda

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