Nombres en plural para objetos
   repetidosDescripciones y parsingListas de objetos y cómo agruparlosCómo se interpretan los nombres

Cómo se interpretan los nombres

Poner Nombre a los Gatos es un tema complejo,
No es un juego de niños, no es un pasatiempo;
Seguro que piensas soy un hombre demente
Si digo que el gato ha TRES NOMBRES DIFERENTES

T.S.Eliot (1888-1965), The Naming of Cats

Supón que tenemos un tomate, definido con el nombre:

  nombre 'tomate' 'verde' 'frito',

pero que más adelante madura, y el jugador debe poder referirse a él como "tomate rojo". La lista de nombres de un objeto no es más que un array, por lo que es perfectamente posible consultar lo que contiene e incluso alterarlo. Por ejemplo, para consultarlo:

[ Nombres obj i;
  for (i=0: i<2*obj.#nombre: i++)
    print (address) (obj.&nombre)-->i, "^";
];

y para alterarlo:

  (tomate.&nombre)-->1='rojo';

pero no es una solución muy flexible ni elegante. Es hora de empezar a trastear con el parser.

(!) Observa que lo que no podemos hacer es cambiar el tamaño de un array. Si necesitamos "añadir" nombres a la propiedad nombre, debemos de declararla de antemano con el sitio suficiente, es decir, podemos poner en ella 30 copias de una palabra inescribible, como 'nada.' (nota el punto en la palabra).

El parser de InformATE está diseñado para ser de acceso lo más abierto posible, porque no puede existir un parser lo bastante general como para servir para cualquier juego, a menos que sea altamente modificable. Lo primero que hace el parser es leer texto del teclado y separarlo en una secuencia de palabras, de modo que el texto "hombre pobre, come el pan gris" se convierte en la secuencia:

  hombre / pobre / , / come / el / pan / gris

y estas palabras se numeran comenzando por 1 (que sería la palabra hombre).

(Esp) La librería InformATE, además de separar las palabras basándose en los espacios y los signos de puntuación, en ocasiones rompe un verbo en dos palabras, para separar el pronombre que puede llevar adosado. Así, la orden "EXAMINATE", es convertida en la secuencia de palabras:
  examina / -te    
('-te' es un nombre que siempre es interpretado como "el jugador"). Pero no siempre separa el sufijo. La regla es:
  1. Busca el verbo en el diccionario del juego. Si aparece, entonces no separes el sufijo (por lo tanto, si el juego usa en algún momento la palabra 'examinate' entre comillas simples, esta palabra será añadida al diccionario y la separación no tendrá lugar.
  2. Si el verbo no aparece en el diccionario, entonces mirar sus últimas letras. Si éstas son '-te', '-me', '-lo', '-la', '-los' o '-las', separarlas del resto del verbo.
  3. Probar si el verbo resultante (una vez separado el sufijo) es un verbo válido de la gramática. Si lo es, se acepta la separación. Si no, vuelve a dejarse todo como estaba.

Esta regla funciona bien en la mayoría de los casos, pero puede dar resultados inesperados a veces, por eso conviene conocerla.

El parser mantiene en todo momento un indicador de qué palabra está examinando dentro de la secuencia anterior. Este indicador se denomina "número de palabra" y está almacenado en la variable global np. Si llamamos a la rutina SiguientePalabra(), obtenemos la palabra de diccionario que hace el lugar np dentro de la secuencia (recordemos que la primera hace el lugar 1), y además np es incrementado. Por ejemplo, supongamos que el parser está examinando la palabra 6 del ejemplo anterior (pan), intentando resolver si esa palabra y las siguientes pueden ser el nombre de un objeto del juego. Si en ese momento se efectúa una llamada a SiguientePalabra(), ésta retornará la palabra 'pan'. Llamándola de nuevo retornará 'gris'.

Conviene recordar que si el jugador ha tecleado equivocadamente 'pna gris', entonces SiguientePalabra() retornaría 0, que significa "palabra no reconocida" (asumiendo que el juego no usa la palabra 'pna' en ningún otro punto del código fuente. Basta que el código incluya una línea como if (w=='pna') para que la palabra 'pna' pase al diccionario automáticamente).

El diccionario del juego tiene una resolución de sólo 9 letras (menos aún, 6, si Inform está compilando para la versión 3 de la máquina Z, pero esta es una versión obsoleta que ya no se usa. De hecho, la librería InformATE sería demasiado grande para esa versión). Por tanto, las palabras 'polisaturar' y 'polisaturado' serían iguales para Inform. Además, no se hace distinción entre mayúsculas y minúsculas. Se permite que las palabras contengan números o símbolos, y también que contengan acentos, pero se desaconseja esto último.

(Esp) Si usas una palabra con acentos como 'camión', entonces el usuario debe escribir exactamente esa palabra para que el parser la reconozca. Si el usuario pone 'camion' (sin acento), el parser no la reconocerá. Esto puede ser una faena para los usuarios que no pueden usar acentos (recuerda que Inform es multiplataforma, y algunos intérpretes no permiten introducir acentos).

Sin embargo, si defines la palabra como 'camion' (sin acento), nadie sale perjudicado, porque el parser entenderá tanto 'camion' como 'camión'. Esto último se debe a que, si el parser no entiende la palabra a la primera, le quita los acentos y vuelve a buscarla en el diccionario. De modo que 'camión' no es encontrado (porque tu juego no define esa palabra), pero 'camion' (una vez quitado el acento por el preparser) sí será encontrado.

(!)(!) El intérprete IznoguZ es especial, ya que elimina los acentos de todo lo que escribe el jugador, sin comprobar antes si la palabra es comprendida o no. Esto es especialmente peligroso, porque si tu juego ha definido la palabra 'camión' (con acento), el jugador no podrá hacerse entender, es una palabra "inescribible" para él. Tanto si lo escribe "camión" con acento como si lo escribe sin él, el juego recibirá finalmente 'camion' (sin acento), que no será entendido por no estar en el diccionario del juego.

De modo que si quieres asegurarte compatibilidad con IzngouZ, no uses nunca acentos en las palabras de diccionario. Por lo mismo, tampoco puedes usar eñes, que IznoguZ convierte automáticamente en enes.

(!)(!) Una palabra de diccionario puede incluso contener espacios, puntos o comas en su interior. En ese caso es "inescribible". Por ejemplo, la palabra de diccionario 'in,out' es inescribible porque si el jugador la escribe, el parser la separará en tres palabras, antes siquiera de buscarla en el diccionario. La rutina SiguientePalabra() nunca podrá retornar el valor 'in,out'. En realidad esto puede ser útil (véase la sección sobre criaturas vivas y conversación).

(!) También puede ser útil comprobar si una palabra es en realidad un número. La rutina de librería IntentarNumero(num_pal) mira la palabra que está en la posición num_pal dentro de la secuencia que el jugador ha escrito, intentando comprenderla como si fuera un número. Es capaz de reconocer números escritos con dígitos, o escritos en español (desde "uno" hasta "veinte"). Retorna el valor del número reconocido, y si no es capaz de interpretar esa palabra como un número, retornará -1000. Números mayores de 10000 se recortan a 10000.

(!)(!) En ocasiones no queda más remedio que examinar lo que el jugador ha escrito letra a letra (es decir, examinar la cadena de texto originalmente escrita por el jugador, en lugar de la secuencia de palabras que el parser ha separado). Por ejemplo, si queremos leer un número de teléfono de 20 cifras. La rutina DireccionDePalabra(num_pal) devuelve un array de tipo byte, que contiene las letras que componen la palabra en posición num_pal. La rutina LongitudDePalabra(num_pal) devuelve cuántas letras tiene. Observa que, al no tratarse de palabras de diccionario sino de texto real, puede tener más de 9 letras de longitud.

Por tanto, en el ejemplo del hombre pobre de antes, el siguiente código:

  eltexto=DireccionDePalabra(4);
  print LongitudDePalabra(4), " ", (char) eltexto->0, (char) eltexto->2;

imprimiría:

4 cm

puesto que la palabra 4 es "come". La variable eltexto que recibe el resultado puede ser una variable local o global normal. No necesita ser un array (ya que en realidad, lo que recibe es un puntero que apunta dentro del verdadero array donde el parser guarda el texto).

Un objeto puede intervenir en cómo se está interpretando su nombre, proporcionando una propiedad llamada parse_nombre (que ha de ser una rutina). Lo que se espera de ella es que, comenzando en la posición indicada por np, intente avanzar el máximo de palabras posible dentro de la secuencia escrita por el jugador, leyendolas de una en una con SiguientePalabra(), mientras que todas estas palabras seguidas se entiendan como referidas al objeto (o lo que es lo mismo, hasta que encuentre una que no sea aplicable al objeto). Debe retornar uno de estos valores:

0
si no encontró ninguna palabra válida.
k
si encontró k palabras seguidas que parecen referirse al objeto.
-1
si prefiere no opinar, y dejar que el parser lo averigue por el método normal.

Una vez que parse_nombre ha retornado, el valor de np puede haber sido modificado, pero esto no importa a la librería, ya que mantiene una copia del valor que tenía antes.

El siguiente ejemplo:

Object -> cosa "cosa rara"
  with parse_nombre [ i;
         while (SiguientePalabra()=='cosa' or 'rara') i++;
         return i;
       ],
has femenino;

es prácticamente lo mismo que haber escrito:

Object -> cosa "cosa rara"
  with nombre 'cosa' 'rara',
has femenino;

por lo que no es demasiado útil. Pero volviendo al tomate verde que cambia de color, podemos programarlo ahora así (suponiendo que su atributo general es usado para indicar que ha madurado):

  parse_nombre [ i j;
    if (self has general) j='rojo'; else j='verde';
    while (SiguientePalabra()=='tomate' or 'frito' or j) i++;
    return i;
  ],    

por lo que si el jugador pone TOMATE ROJO, este tomate sólo se dará por aludido si su atributo general está activado. Y si pone TOMATE VERDE, sólo se dará por aludido si general está desactivado.

(?) EJERCICIO 56  El tomate anterior también se dará por aludido si el jugador pone ROJO FRITO TOMATE, o FRITO ROJO, por ejemplo (y general está activo). Escribelo de forma que sólo entienda el orden TOMATE <color> FRITO, siendo obligatorio TOMATE y las otras dos opcionales (debe entender TOMATE, TOMATE ROJO, TOMATE FRITO y TOMATE ROJO FRITO, o sus equivalentes con VERDE)
(Solución)

(?) EJERCICIO 57  Crea una cantante llamada Princess que, al ser besada se transforma en "/.%./ (la artista antes conocida como Princess)". NOTA: date cuenta que su nuevo nombre /.%./ es "inescribible". Debes acceder al buffer de letras para parsearlo.
(Solución)

(?) EJERCICIO 58  Construye una máquina expendedora de refrescos capaz de servir cola, café o té, usando sólo un objeto para los tres botones, y otro para la posible bebida.
(Solución)

(!) La propiedad parse_nombre también se ocupa de detectar plurales. Más información en la sección sobre nombres en plural.

Supón que un objeto no tiene la rutina parse_nombre, o que tiene una pero ésta ha retornado -1. Lo que hace el parser entonces es mirar la propiedad nombre. Admite cualquier secuencia de estas palabras, cuantas más mejor. Así que admitirá "tomate verde frito", y también "tomate verde" y "tomate frito". Por otro lado, también admitirá "frito verde" y "verde verde tomate verde frito verde". Este método es rápido y bueno para comprender una gran variedad de entradas razonables, pero no es muy buen para deshechar las entradas absurdas.

Sin embargo, puedes modificar esto si escribes una rutina InterpretarNombre. La rutina InterpretarNombre es llamada por la librería cuando llega a un punto en el que espera un nombre de un objeto, y está tratando de decidir cuántas palabras seguidas de las que vienen a continuación podrían ser el nombre de un objeto. La librería va probando con todos los objetos al alcance del jugador, y para cada uno (que no tenga la propiedad parse_nombre) llama a InterpretarNombre pasándole como parámetro ese objeto. InterpretarNombre debe hacer lo mismo que parse_nombre, es decir, retornar -1, 0 o el número de palabras encajadas.

(Esp) En realidad, la librería InformATE ya suministra una rutina InterpretarNombre para modificar el mecanismo estándar de parsing que se acaba de describir.

La rutina InterpretarNombre suministrada por InformATE no sólo utiliza la propiedad nombre, sino también nombre_m, nombre_f, nombre_mp, nombre_fp y adjetivos, a la hora de decidir si una secuencia de palabras es aplicable a un objeto o no. Esta rutina es bastante complicada porque además de decidir si la secuencia de palabras se adapta al objeto, debe modificar el género de éste, según las palabras hayan sido halladas en la lista nombre_m, o nombre_f.

Básicamente, el mecanismo de parsing que usa esta función consiste en repetir el siguiente bucle:

  1. Toma una palabra de las escritas por el jugador.
  2. Si es "de", "el", "la", "los" y "las", la ignora provisionalmente (pero lleva la cuenta de las que ha ignorado) hasta llegar a una palabra que esté en alguna de las listas nombre_m, nombre_f, nombre_mp, nombre_fp o adjetivos
  3. Si no encuentra la palabra en ninguna de esas propiedades, retorna el número de palabras comprendidas hasta ese punto (que será cero en la primera iteración de este bucle)
  4. Si la encuentra en una, anota el género y número que se ha usado (a menos que esté en adjetivos, que no tiene género ni número). Anota también (en el atributo nombreusado del objeto), si la palabra has sido encontrada en una de las listas nombre (no anotará esto si sólo se encuentra en la lista de adjetivos).

    Además, añade a la lista de palabras comprendidas las que había ignorado provisionalmente.

  5. Vuelve al paso 1.

Aunque pueda parecer confuso, el mecanismo sirve para ignorar la palabra "de", y los artículos, siempre que estos aparezcan antes de alguna palabra referida al objeto. Así, si el objeto tiene en nombre 'caja' y en adjetivos 'madera', el parser comprenderá "CAJA DE MADERA", puesto que ignora "DE" por aparecer delante de la palabra "MADERA" que es una palabra válida para este objeto. Sin embargo, en la expresión SACA LA CAJA DE MADERA DE LA CESTA, la rutina sólo admitirá como referida a la caja, la expresión CAJA DE MADERA. Las palabras "DE LA" que la siguen, no se cuentan como válidas, pues no preceden a una palabra válida (puesto que la siguiente palabra que aparece es "CESTA", que no está en la lista nombre ni adjetivos de la caja.

La preposición "DEL" es convertida en "DE" antes incluso de que entre el parser, por lo que también es ignorada por este mecanismo.

Por otro lado, el mecanismo antes descrito acepta como buena una frase que sólo contenga adjetivos, si bien toma nota de este hecho, por si acaso otros objetos cercanos también "se dan por aludidos". Por ejemplo, imagina que tienes un objeto llamado "madera" (un trozo de madera) y otro llamado "caja de madera". Si en la misma localidad sólo se halla la caja de madera, puede parecer razonable admitir "MIRA MADERA" como una forma de referirse a la caja. Sin embargo, si están juntos la caja y el madero, parece que "MIRA MADERA" debería referirse al madero y no a la caja.

La forma en que InformATE resuelve esta ambigüedad consiste en admitir "provisionalmente" denominaciones que sólo usen el adjetivo, pero en caso de conflicto, dar prioridad a los objetos que han sido llamados por su nombre, en lugar de su adjetivo. Así, si el madero tiene 'madera' en su propiedad nombre, pero la caja sólo lo tiene en su propiedad adjetivos, entonces el madero tendrá prioridad sobre la caja cuando estén juntos y el jugador ponga "COGE MADERA".

(!) En la última versión de la librería InformATE, el programador puede elegir entre dos rutinas InterpretarNombre diferentes. La primera se comporta como acabamos de explicar. La nueva no permite que un nombre sea llamado usando solo el adjetivo, de modo que "COGE MADERA" nunca será entendido como una referencia a la caja de madera.

Por defecto, la rutina usada por la librería es la primera, pero el programador puede cambiar esto si en la rutina Inicializar pone una línea:

  RutinaInterpretarNombre=NuevoInterpretarNombre;

De hecho, es posible que el programador escriba una rutina totalmente nueva, por ejemplo MiInterpretarNombre, que debe ser escrita antes de Inicializar y que sustituirá a la suministrada por la librería si en Inicializar ponemos:

  RutinaInterpretarNombre=MiInterpretarNombre;

De forma más simple, si la rutina de usuario tiene el nombre InterpretarNombre, sustituirá automáticamente a la de la librería sin necesidad de asignar nada en Inicializar (pero para que esto funcione debe escribirse la rutina de usuario antes de incluir Gramatica.h, puesto que es en este fichero donde se define la rutina InterpretarNombre por defecto si el usuario no ha dado otra).

Por ejemplo, la siguiente rutina haría lo mismo que el parser inglés:

[ InterpretarNombre objeto n;
  while (PalabraEnPropiedad(SiguientePalabra(), objeto, nombre)) n++;
  return n;
]; 

siendo PalabraEnPropiedad una rutina de la librería que sirve para comprobar si una palabra dada está entre las listadas en una propiedad dada. Esta rutina recibe como primer parámetro la palabra que hay que buscar, como segundo parámetro el objeto en el que hay que buscarla, y como tercer parámetro la propiedad dentro de ese objeto. En el ejemplo anterior, busca en la propiedad nombre.

(!) Por si tienes curiosidad, he aquí cómo está programada la rutina PalabraEnPropiedad():

[ PalabraEnPropiedad palabra obj prop k l m;
    k=obj.&prop; l=(obj.#prop)/2-1;
    for (m=0:m<=l:m++)
        if (palabra==k-->m) rtrue;
    rfalse;
];

(!)
(?) EJERCICIO 59  En inglés, el adjetivo debe ir siempre delante del nombre. La librería original inglesa no hacía distinción entre nombres y adjetivos, sino que los ponía todos juntos en la propiedad nombre. Al no hacer esta distinción, el jugador podía poner RED BALL o BALL RED indistintamente.

Programa una rutina InterpretarNombre que sólo acepte el patrón "cero o más adjetivos seguidos de 1 ó más nombres" (suponiendo que los adjetivos están almacenados en la propiedad adjetivos del objeto, y los nombres en la propiedad nombre)
(Solución)

(!)
(?) EJERCICIO 60  A veces, durante la depuración, puede ser útil referirse a los objetos por su número interno, de modo que el juego acepte la orden "PON OBJETO 31 SOBRE OBJETO 5". Programa un InterpretarNombre que acepte ésto.
(Solución)

(!)
(?) EJERCICIO 61  ¿Cómo podríamos hacer que la palabra '#' sea un comodín que signifique "cualquier objeto"?
(Solución)

(!)(!)
(?) EJERCICIO 62  Y ¿cómo podríamos hacer que, además, la palabra '*' sea un comodín que signifique "cualquier grupo de objetos idénticos"? (como un grupo de monedas, por ejemplo). NOTA: para resolver ésta hace falta saber cosas de la sección sobre nombres en plural)
(Solución)

(!)(!)
(?) EJERCICIO 63  No hay problema en crear un objeto llamado "agujero en la pared", (que tenga en su propiedad nombre la lista 'agujero', 'en' y 'pared'. El parser entenderá "PON MANZANA EN AGUJERO EN LA PARED" como "PON (MANZANA) EN (AGUJERO EN LA PARED)", ya que intenta encajar tantas palabras como sea posible para referirse a un nombre dado (saltándose los artículos), por lo que la secuencia "AGUJERO EN LA PARED" es entendida correctamente al estar todas esas palabras en la propiedad nombre (excepto el artículo 'la' que es ignorado).

Pero si creas además un objeto llamado "PESCADO EN SALSA", que tenga en su propiedad nombre la lista de palabras 'pescado', 'en', 'salsa', la frase "PON PESCADO EN SALSA EN AGUJERO EN LA PARED" será malinterpretada, debido a que la secuencia de palabras "PESCADO EN SALSA EN" son todas válidas como una referencia al objeto pescado. De modo que el parser intentará comprender esta frase como: "PON (PESCADO EN SALSA EN) (AGUJERO EN LA PARED)", es decir como "PON (obj1) (obj2)" y esta frase no existe en la gramática (la que existe es "PON (obj1) EN (obj2)")

¿Cómo solucionarías este problema para que la frase anterior fuera correctamente analizada como "PON (PESCADO EN SALSA) EN (AGUJERO EN LA PARED)"?
(Solución)


Zak McKraken - spinf@geocities.com

Nombres en plural para objetos
   repetidosDescripciones y parsingListas de objetos y cómo agruparlosCómo se interpretan los nombres