Diccionarios de objetos

Una de las partes más importantes de un aventura conversacional pura (de las que hay que teclear las ordenes) es la gramática que acepta el juego. Por un lado están los verbos, preposiciones, determinantes, que son los que forman las estructuras de las frases y por otro lado están los nombres de los objetos (ya sean personajes animados, decorado u objetos manipulables por el jugador).
Ya desde los primeros parsers se utilizan sinónimos para los objetos, de manera que el jugador pueda referirse a un objeto concreto usando varios nombres.
Por ejemplo Inform/Informate utiliza varios nombres para un mismo objeto que pueden utilizarse a la vez. De manera que el jugador puede utilizar cualquiera de ellos e incluso varios a la vez para referirse a un objeto. Por ejemplo, un objeto definido en Inform como

nombre 'tomate' 'verde' 'frito'

Podrá ser referido como “tomate verde”, “verde frito” y “frito tomate”.
El parser que estoy desarrollando esta basado plenamente en Inform, de manera que cualquier programador que conozca este lenguaje tan solo tenga que aprenderse las correspondencias de un lenguaje al otro. Sin embargo, hay algunas cosas que he cambiado, como el tratamiento de nombres y sinónimos.
Quería que el jugador pudiera referirse a los objetos con muchos nombres y combinaciones, pero no quería que pudiese teclear cosas que pueden ser absurdas. Así que utilicé la siguiente nomenclatura.

  • Si una palabra aparece aislada, es obligatoria.
  • Si una palabra aparece con una interrogación al final “?”, es opcional.
  • Si varias palabras aparecen separadas por una barra, es obligatorio utilizar una de ellas.
  • Si varias palabras aparecen separadas por una barra y con una interrogación al final, es opcional utilizar solo una de ellas.

Así, un ejemplo básico sería:

palabra1 palabra2? palabra21|palabra21 palabra3|palabra4?

De manera que la palabras 1 es obligatoria; la palabra2 es opcional; con las palabra21 y 22 es obligatorio utilizar una de ellas; y las palabras 3 y 4 son opcionales, pero sólo se puede utilizar una a la vez.
Así, se pueden crear nombres compuestos de una manera fácil:

nombre tomate verde? frito?

Nos dará como resultado todos los siguientes nombres válidos: “tomate”, “tomate verde”, “tomate frito” y “tomate verde frito”, pero no aceptará “verde” o “frito tomate” como nombre válido.
Podemos ir más allá, y utilizar nombres más complejos:

nombre estatua|estatuilla|figura de? color? azul|azulado?
nombre zapato|zapatos? deportivo|deportivos para correr

Si este sistema te parece interesante, te diré que en Java es muy fácil de programar. Más que nada porque el código que te trata una definición de nombre de este estilo y te devuelve un conjunto (Set) con todos los posibles nombres que coinciden con el objeto de la definición te lo adjunto aquí.
Si alguien lo consigue mejorar u optimizar, que me lo envíe y lo publico.

private static Set multiply(String wordName) throws AdvParserException {
    Set oldSet = new LinkedHashSet();
    boolean first = true;
    boolean allOptional = true;
    for (StringTokenizer stringTokenizer = new StringTokenizer(wordName); stringTokenizer.hasMoreTokens();) {
        String v = stringTokenizer.nextToken();
        boolean optional = false;
        if (v.endsWith("?")) {
            optional = true;
            v = v.substring(0, v.length() - 1);
            if (first) {
                oldSet.add("");
            }
        } else {
            allOptional = false;
        }
        if (!first) {
            oldSet.remove("");
        }

        Set newSet = new LinkedHashSet();
        for (StringTokenizer stv = new StringTokenizer(v, "|"); stv.hasMoreTokens();) {
            String part = stv.nextToken();
            // Si tenemos conjunto anterior, le añadimos a cada una de las partes el token actual
            if (!oldSet.isEmpty()) {
                for (Iterator i = oldSet.iterator(); i.hasNext();) {
                    String oldPhrase = (String) i.next();
                    newSet.add((oldPhrase + " " + part).trim());
                }
            } else {
                newSet.add(part);
            }
        }
        if (optional) {
            newSet.addAll(oldSet);
        }
        oldSet = newSet;
        first = false;
    }
    if (allOptional) {
        throw new AdvParserException("Todas las palabras no pueden ser opcionales [" + wordName + "]");
    }
    return oldSet;
}

Espero que a alguien le sirva de utilidad para el desarrollo de una aventura o para cualquier otra cosa.

8 thoughts on “Diccionarios de objetos

  1. Realmente interesantes todos los posts dedicados a cómo hacer aventuras conversacionales, Alberto, ¡no dejes de escribirlos!

  2. Saludos! He comenzado el desarrollo de un remake de la Aventura Original para Nintendo DS y tu trabajo me parece de lo mas util e interesante que he visto. Pensaba hacer el analizador lexco con flex y bison.. ¿qué me recomiendas? :)

  3. Personalmente yo no utilizo ningun analizador. Al principio estuve buscando algo con lo que desarrollar el lenguaje del parser (es decir, el lenguaje con el que se programan las aventuras), pero al final opte por una implementación propia por una razones que ya explicaré en otro momento.
    Por otro lado, tenemos el parser o analizador de las ordenes que introduce el jugador durante la partida. Aqui utilizar un analizador era matar moscas a cañonazos, asi que de nuevo, utilice una implementación propia.
    Si no vas a desarrollar un lenguaje para crear aventuras, y solo necesitas analizar las ordenes/frases que introduce el jugador, entonces creo que no es necesario utilizar un analizador lexico. De todas formas, voy a mirarme bison y flex, porque lo mismo son una buena opción que no he tenido en cuenta…. mmm, ahora te cuento :-)
    Por cierto, mucha suerte en tu proyecto, eso si que es una aventura! un remake para Nintendo DS! pasarás a los anales de la historia aventurera! Te seguiremos de cerca…

  4. Acabo de mirar flex/bison y te adelanto que ese tipo de analizadores son brutales para definir nuevos lenguajes, pero demasiado complejos si solo queremos parsear ordenes o frases simples.
    Yo en su momento estuve mirando antlr, que tembien sirve para crear analizadores sintacticos a partir de gramaticas. A partir de una definicion con reglas y expresiones, generaba una serie de clases java que servian para interpretar un programa escrito en el lenguaje que tu habias definido de la misma manera que hace flex/bison. Detectan estructuras anidadas, letras con numeros y simbolos, y un sinfin de cosas mas o lo que a ti se te ocurra, pero creo que para nuestro proposito es excesivo. Nosotros necesitamos un parser que entienda (mas o menos..) el lenguaje natural y no una gramatico independiente del contexto de un lenguaje de programacion. Mas sencillo todavia, nosotros necesitas un analizador de ordenes que, basicamente, siguen una misma estrutura: verbo + objeto1 + objeto2. Por supuesto que el verbo puede tener varias palabras, los objetos pueden tener preposiciones o articulos delante, pero eso no son mas que particularidades que variaran en funcion de la accion.
    Creo que tardaras mucho menos y disfrutaras mas si lo implementas por ti misma. Si sigues este camino puedes contar con mi ayuda, pues asi lo he hecho yo. Suerte tomes la opcion que tomes.

  5. Tienes razón, creo que voy a aprender y a disfrutarlo mas si hago en analizador por mi misma. Ya dejare el flex/bison para cuando haga algo mas complicadillo. Yo tambien seguiré tu trabajo de cerca, que la verdad es que este post me esta ayudando muchisimo a comprender la mecánica dl analizador.

    Muchas gracias por todo!

  6. Estupendo blog!

    Por cierto, cuando implementaste esta caracteristica, estudiaste la posibilidad de utilizar expresiones regulares en java con el modulo java.util.regex? O tambien resultaria matar moscas a cañonazos?

  7. La verdad es que hace mucho que programé esto y no me acuerdo bien porque lo hice así en vez de usar expresiones regulares. Pero si, tiene bastante sentido usarlas :)

Comments are closed.