Ingeniería de los Sistemas Basados en el Conocimiento Simulación y Agentes con Reglas LAURA BEGUÉ GRANGED GERMÁN LATORRE ANTÍN 5º Ingeniería informática Curso 2001-2002 Centro Politécnico Superior Universidad de Zaragoza Índice Índice INTRODUCCIÓN 5 DESCRIPCIÓN DE LA APLICACIÓN ................................................................. 5 NUESTRA TAREA ........................................................................................... 5 JESS 6 INTRODUCCIÓN ............................................................................................. 6 OBTENCIÓN DE JESS ..................................................................................... 6 ARCHIVOS FUENTE ............................................................................................................ 7 INSTALACIÓN Y UTILIZACIÓN ............................................................................................ 7 DOCUMENTACIÓN .............................................................................................................. 7 PROGRAMACIÓN BÁSICA EN JESS ................................................................. 8 DESDE JAVA ...................................................................................................................... 8 PECULIARIDADES ............................................................................................................... 8 PROBLEMAS ....................................................................................................................... 9 MANEJO DE OBJETOS JAVA ........................................................................ 10 JAVABEANS ..................................................................................................................... 10 DECLARACIÓN DE CLASES EN JESS .................................................................................. 11 INSTANCIACIÓN DE LOS OBJETOS DESDE JAVA ................................................................. 11 MANEJO DE LOS OBJETOS DESDE JESS .............................................................................. 12 INVOCACIÓN A MÉTODOS DE LOS OBJETOS DESDE JESS .................................................... 12 APLICACIÓN INICIAL 14 CLASES RELEVANTES .................................................................................. 14 SIMULADOR ................................................................................................. 14 MUNDO ........................................................................................................ 14 AGENTE ....................................................................................................... 15 COCHE, PERSONA, SEMÁFORO, TESORO ................................................... 15 ACCIÓN........................................................................................................ 16 POSICIÓN ..................................................................................................... 16 CARRETERAAUTOCARGA ........................................................................... 16 RESUELVERUTASJESS ............................................................................... 17 IMPLEMENTACIÓN NO ALEATORIA 18 OBJETIVO .................................................................................................... 18 Índice CLASES RELEVANTES .................................................................................. 18 MOTOR ........................................................................................................ 19 ATRIBUTOS ...................................................................................................................... 19 MÉTODOS ........................................................................................................................ 19 SIMULADOR ................................................................................................. 20 ATRIBUTOS ...................................................................................................................... 20 MÉTODOS ........................................................................................................................ 20 MUNDO ........................................................................................................ 21 ATRIBUTOS ...................................................................................................................... 22 MÉTODOS ........................................................................................................................ 22 AGENTE ....................................................................................................... 23 ATRIBUTOS ...................................................................................................................... 24 MÉTODOS ........................................................................................................................ 25 JAVABEANS ..................................................................................................................... 27 COCHE ......................................................................................................... 28 MÉTODOS ........................................................................................................................ 28 PERSONA ..................................................................................................... 29 MÉTODOS ........................................................................................................................ 29 SEMÁFORO .................................................................................................. 29 MÉTODOS ........................................................................................................................ 29 POSICIÓN ..................................................................................................... 30 ATRIBUTOS ...................................................................................................................... 31 MÉTODOS ........................................................................................................................ 31 CARRETERAAUTOCARGA .......................................................................... 32 ATRIBUTOS ...................................................................................................................... 33 MÉTODOS ........................................................................................................................ 34 FICHERO DE REGLAS ................................................................................... 35 COLOR DE LOS AGENTES ............................................................................. 37 APPLET ........................................................................................................ 38 APLICACIÓN ALEATORIA (1 MOTOR GLOBAL) 40 OBJETIVO .................................................................................................... 40 CLASES MODIFICADAS ................................................................................ 40 CARRETERAAUTOCARGA .......................................................................... 40 FICHERO DE REGLAS .................................................................................. 41 DESCRIPCIÓN DEL MECANISMO ........................................................................................ 41 BÚSQUEDA DE LAS POSICIONES ....................................................................................... 42 ELECCIÓN DE LA POSICIÓN............................................................................................... 42 MOVIMIENTO ................................................................................................................... 43 MÓDULO RESTRICCIONES .......................................................................................... 44 APLICACIÓN ALEATORIA (1 MOTOR/AGENTE) 46 OBJETIVO .................................................................................................... 46 Índice CLASES MODIFICADAS ................................................................................ 46 AGENTE ....................................................................................................... 46 CARRETERAAUTOCARGA .......................................................................... 46 CREACIÓN DE AGENTES ................................................................................................... 46 INSTANCIACIÓN DE AGENTES Y POSICIONES ..................................................................... 47 CREACIÓN DE LOS MOTORES ............................................................................................ 48 MOTOR ........................................................................................................ 48 FICHERO DE REGLAS ................................................................................... 49 INDIVIDUALIZACIÓN DEL MOTOR ..................................................................................... 49 MÓDULOS ........................................................................................................................ 49 VALORACIÓN DEL TRABAJO 51 PROBLEMAS ENCONTRADOS ....................................................................... 51 COSAS INACABADAS .................................................................................... 51 POSIBLES AMPLIACIONES ........................................................................... 52 VALORACIÓN PERSONAL ............................................................................ 52 ANEXOS 54 Introducción 5 Introducción Descripción de la aplicación La aplicación sobre la que hemos trabajado se construyó basándose en AgentSheets, una herramienta que construye simulaciones interactivas en Java mediante programación tactil. AgentSheets permite definir tanto escenarios como agentes que interactúan sobre dichos escenarios, junto con su comportamiento. Basándose en las especificaciones y definiciones realizadas AgentSheets genera Applets de Java que llevan a cabo la simulación. El trabajo se ha realizado sobre una apliación construida con esta filosofía, que proporciona una herramienta de creación de escenarios y un simulador de los escenarios creados, donde puede observarse el comportamiento de los agentes. Nuestra tarea Nosotros nos hemos centrado en la parte de simulación de escenarios y agentes. Concretamente este trabajo ha consistido en la reprogramación de las estructuras de control que rigen el comportamiento de los agentes. En la version previa de la aplicación de la que partíamos este control estaba programado de forma imperativa en Java y, mediante la utilización del motor de inferencia Jess (clon de CLIPS en Java), hemos modelado el comportamiento de los agentes mediante reglas que interactuan directamente con los objetos de la aplicación de partida. De este modo el estado de cada objeto agente en Java va evolucionando de acuerdo a las reglas programadas en Jess, lo que nos permite simplificar el control anteriormente programado imperativamente y además posibilita la modificación del comportamiento de los agentes con sólo modificar las reglas, sin necesidad de modificar el código Java. Además se ha añadido la posibilidad de especificar el color del agente, cosa que no podía hacerse antes y de gran utilidad a la hora de identificar gráficamente a cada agente y su comportamiento. Se ha intentado hacer que la aplicación sea un Applet para de ese modo poder colocarse en la web y ser ejecutada desde un navegador. Esto no debería de ser difícil, pero se han encontrado ciertas dificultades para llevarlo acabo y dada la escasez de tiempo no se ha podido implementar. Jess 6 Jess Introducción Como hemos mencionado, la meta principal de nuestro trabajo ha sido la programación del comportamiento de los agentes mediante reglas. Para ello utilizamos el motor de inferencia Jess. Jess está programado en Java. Aunque está programado basándose en CLIPS (su sintáxis, su funcionamiento, ...) y coinciden prácticamente en todo, igualmente existen algunas diferencias que como programadores hemos de contemplar y cuidar a la hora de utilizar Jess o escribir sus scripts. Las semejanzas y alguna diferencia (ya que sólo hemos utilizado lo necesario) serán mencionadas y descritas de aquí en adelante. Jess presenta varias peculiaridades que lo hacen especialmente interesante para determinados usos. Su principal ventaja es su preparación para trabajar y cooperar con programas en Java. Por ejemplo, Jess permite la ejecución de scripts en Java, es decir, puedes crear objetos y estructuras de control Java dentro de un script de Jess y ejecutarlos sin necesidad de compilación previa. Sin embargo la característica más interesante de cara a la implementación del comportamiento de los agentes mediante reglas es la capacidad de interactuar directamente con objetos de Java. Así como CLIPS maneja hechos Jess puede manejar igualmente hechos e instancias de objetos de Java. Esto nos permite escribir las reglas que definen el comportamiento de los agentes sin necesidad de realizar una traducción de estructuras de datos de Java a estructuras de datos de Jess, sino haciendo referencia a los objetos tal cual existen en Java, aunque ello conlleve una mayor complejidad en la programación de los objetos, como se verá más adelante. Obtención de Jess El desarrollo de Jess se lleva a cabo en los Sandia National Laboratories en Livermore, California. Pese a ser algo relativamente interesante, en Internet la información de Jess es escasa, y hay que recurrir en la mayoría de las ocasiones a la web de los laboratorios de Sandia. Dicha dirección es: http://herzberg.ca.sandia.gov/jess/ En esta dirección puede encontrarse la documentación de Jess, una lista de correo a la que suscribirse (muy recomendable, el mismo autor de Jess te responde a las dudas), y por supuesto las clases o los fuentes (estos últimos bajo licencia) para descargalos, entre otras cosas. Jess 7 Archivos fuente Los archivos fuente o las clases de Java pueden obtenerse en la misma dirección, accediendo al link "Download Now!". Allí se pide el nombre, el de la empresa y la dirección de correo (no tienen por qué darse los verdaderos, pero no hacen nada malo con ellos). Tras ello se accede a la página donde están las diferentes versiones de Jess desarrolladas y allí se pueden descargar o bien los fuentes o las clases de la versión deseada. El inconveniente es que las clases son una versión de prueba de 30 días, y para obtener los fuentes hace falta una licencia. Puede obtenerse una licencia de estudiante, pero hay que rellenar unos formularios, que alguien de la universidad los firme y hay que mandarlos por fax o correo a Sandia. Instalación y utilización Una vez obtenido Jess (en formato .zip o .tar.gz) se descomprime y se coloca en el ordenador, bien en el directorio en el que se esté trabajando o donde parezca un buen sitio para tenerlo. Si se coloca en el directorio de trabajo no habrá mayores problemas, pero si se coloca en otro lugar habrá que asegurarse de que se incluye en la variable de entorno CLASSPATH, bien en el autoexec.bat o a la hora de compilar/ejecutar con la opción classpath. También se puede elegir entre tenerlo descomprimido (con lo que se obtiene una estructura de directorios), o comprimido. Si lo está comprimido en .zip su utilización es similar a la de un archivo .jar. Para utilizar Jess desde cualquier aplicación hay que añadir en el archivo fuente de Java: import jess.*; Con esto se puede trabajar normalmente con sus clases y su API (también disponible en la web). Documentación En la web de Jess de Sandía también están disponibles los manuales de las distintas versiones desarrolladas. Este manual no es una gran referencia, pero es la única y aún se le puede sacar bastante jugo con algo de paciencia. Dentro de cada manual también se puede encontrar la información de las clases y la API de Jess generada mediante Javadoc, para que resulte familiar. Jess 8 Programación básica en Jess Como se ha mencionado antes, los archivos de reglas de Jess son prácticamente idénticos a los de CLIPS, con alguna diferencia. Jess puede ejecutarse como una aplicación igual que CLIPS, con su linea de comandos y su interfaz sencillo. A parte de eso, Jess proporciona un API para trabajar desde Java con ella, para crear motores de inferencia, controlar su funcionamiento, instanciar hechos, etc. y todo desde Java. En nuestro caso vamos a utilizar Jess desde Java, desde la aplicación que existía anteriormente. Desde Java En primer lugar habrá que crear un motor Jess. Esto se hace mediante la instanciación de un objeto de la clase Rete. de la siguiente manera: ... import jess.*; ... Rete rete = new Rete(); ... Tras ello puede ejecutarse cualquier comando de Jess mediante el método de Rete executeCommand. Para cargar un archivo de reglas se hace lo siguiente: try { rete.executeCommand("(batch \"" + rutaFichero + "\")"); rete.executeCommand("(reset)"); } catch (JessException je) { ... } Finalmente para poner en marcha el motor Jess habrá que invocar al método Sin parámetros el motor se pone en marcha hasta que no hay reglas sensibilizadas, pero también puede pasarse como parámetro el número de pasos que se quieren ejecutar, del mismo modo que la función (run) de CLIPS. De hecho puede invocarse también con executeCommand: run. try { rete.run(1); /* como rete.executeCommand("(run 1)"); */ rete.run(); /* como rete.executeCommand("(run)"); */ } catch (JessException je) { ... } Peculiaridades Jess 9 Hasta aquí se ha visto cómo trabajar con Jess desde Java, pero a parte de eso la sintáxis viene a ser la misma que la de CLIPS. Existen diferencias des sintáxis en aspectos importantes, fundamentalmente en lo que se refiere al manejo de objetos de Java, que se verá acontinuación. Un punto importante en Jess por su peculiaridad son los módulos. En la programación CLIPS utilizar módulos es algo habitual e importante en el proceso de resolución de problemas mediante reglas. Sin embargo, hasta la versión 6.0 Jess no admitía módulos. En la versión 6.0 los admite, pero su comportamiento es algo diferente y hay que tener bastante cuidado. La definición del módulo se hace de la misma manera: (defmodule MODULO) Todas las reglas definidas a partir de esa definición pertenecerán a ese módulo. No obstante puede indicarse explícitamente el módulo al que ha de pertenecer una regla: (defrule MODULO::regla ... => ... ) La principal diferencia es que en CLIPS los deftemplate y los hechos que utilizan un deftemplate pertenecen al módulo en el que están definidos y en Jess son globales. Así como en CLIPS había que indicar qué cosas exportar e importar en cada módulo, en Jess todo es de todos. Tanto en CLIPS como en Jess la ejecución de los módulos se maneja de la misma manera. focus cambia el "current focus" y apila el anterior en una pila (focus stack). Cuando la agenda del current focus se vacia se obtiene el siguiente de la pila. (focus MODULO1) (focus MODULO2) Además pueden definirse reglas con auto-focus. Estas reglas en cuanto se sensibilizan hacen que se active su módulo (obtenga el "current focus") y se disparen las reglas sensibilizadas en dicho módulo. Cuando la agenda del módulo se vacía vuelve a perder el focus. (defrule MODULO::regla (declare (auto-focus TRUE)) ... => ... ) Problemas Jess 10 Tuvimos un problema con el orden de ejecución de las operaciones en la postcondición de las reglas. Esta ejecución en teoría debería hacerse de manera atómica, pero al parecer en Jess no, ya que nuestro problema se solucionó alterando el orden de las operaciones realizadas. Manejo de objetos Java Las modificaciones realizadas en la aplicación se basan en el uso de Jess y la facilidad de éste para manejar objetos Java. A continuación se describe el procedimiento para llevar a cabo esta comunicación. JavaBeans Para que un objeto Java sea accesible desde Jess, éste objeto ha de estar programado siguiendo la filosofía JavaBean. Un JavaBean es un objeto que tiene, para cada atributo que quiera ser accedido desde el exterior, un método para leerlo y otro para modificarlo. Estos métodos tendrán como nombre "set" seguido del nombre del atributo con la primera letra mayúscula y "get" con el mismo nombre detrás para el método de lectura y escritura respectivamente. ... Atributo atributo ... public Atributo getAtributo() { return atributo; } ... public void setAtributo(Atributo nuevo_atributo) { atributo = nuevo_atributo; } ... La tecnología JavaBean permite la comunicación entre diferentes aplicaciones mediante la invocación a estos métodos. Jess se basa en esto para poder acceder y modificar los objetos Java de nuestra aplicación. Además de los métodos "set" y "get", cuando se produce una modificación en el valor del atributo, ha de notificarse. Para ello hay que añadir un PropertyChangeListener, en el que se invocará al método firePropertyChange() cada vez que se modifique un atributo. La declaración del "listener" se hace de la siguiente manera: public PropertyChangeSupport pcs = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener pcl) { pcs.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { Jess 11 pcs.removePropertyChangeListener(pcl); } Para invocar al método firePropertyChange() hay que indicarle el nombre del atributo modificado, el valor antiguo de ese atributo y el valor nuevo. ... Atributo valor_antiguo; ... valor_antiguo = atributo; atributo = valor_nuevo; pcs.firePropertyChange("atributo", valor_antiguo, valor_nuevo); ... Declaración de clases en Jess Una vez preparados los objetos como JavaBeans, se procede a programar la aplicación, parte en Jess, parte en Java. En primer lugar hay que declarar las clases de Java que se van a utilizar. Para ello se utilizará la función defclass. El resultado de esta declaración es la definición de un template que tiene como slots cada uno de los atributos de la clase que tienen programado un método "get" (simplemente porque Jess sólo puede leer los que tienen dicho método). Por ejemplo, la definición de (defclass clase Clase) ;;;minúscula en Jess, mayúscula en Java si la clase Clase tiene como atributos con método "get" atributo1 y da como resultado (internamente) una definción como esta: atributo2, (deftemplate clase (slot atributo1) (slot atributo2)) Instanciación de los objetos desde Java Una vez definidas las clases, cuando los objetos son creados en el programa Java, han de ser instanciados (algo parecido a si fueran hechos) en Jess. Para la instanciación de los objetos recurrimos al API de Jess para Java. Hay que crear un objeto de la clase Funcall (llamada a función), de tipo definstance. En ese objeto indicaremos el nombre de la clase, el tipo del objeto, y una referencia al objeto en sí mediante creación de objetos de la clase Value. La llamada se hace de la siguiente manera: Rete rete; ... Clase objeto = new Clase(); ... //motor Jess Jess 12 f = new Funcall("definstance", rete); f.add(new Value("clase", RU.ATOM)); f.add(new Value(objeto)); f.execute(rete.getGlobalContext()); //tipo y motor //clase y tipo objeto //el objeto en sí //ejecución Manejo de los objetos desde Jess Tras esto, ya sólo queda utilizar los objetos en la programación de las reglas y funciones en Jess. Su utilización es similar a la de los hechos, pero con diferencias. A la hora de escribir las reglas, los objetos se manejan como hechos generados a partir de deftemplates. Sin embargo, cuando aparece un objeto en la "pre" de una regla y desea almacenarse una referencia al mismo en una variable para utilizarla en el resto de la regla, no puede hacerse como en CLIPS o en Jess con los hechos, sino de otra forma. Para guardar una referencia a un hecho se haría de la siguiente manera: (defrule regla ?referencia <- (hecho (slot1 ?referencia1) (slot2 ?referencia2)) => ... ) Para hacer lo mismo con un objeto se utilizará el slot OBJECT que tiene todo objeto generado a partir de un defclass. Un caso equivalente al anterior se haría así: (defrule regla (objeto (atributo1 ?referencia1) (atributo2 ?referencia2) (OBJECT ?referencia) ) => ... ) Invocación a métodos de los objetos desde Jess Tanto en las "pre" como en las "post" de las reglas suele ser necesario invocar métodos de los objetos referenciados con el fin de observar su estado (en las "pre") o de modificarlo (en las "post"). La invocación a métodos de objetos desde Jess se hace de la siguiente manera. Si tenemos que invocar a un método "get" o "set" lo podemos hacer directamente con la siguiente sintáxis: (get ?referencia atributo) (set ?referencia atributo valor) Jess 13 Siendo ?referencia una referencia (valga la redundancia) a un objeto de una clase, y atributo un atributo de dicha clase que siguiendo la filosofía JavaBean tiene los métodos "get" y "set" implementados. En general, cualquier método se puede invocar mediante la sentencia call de la siguiente manera: (call ?referencia método parámetro1 ... parámetroN) Los métodos "get" y "set" también pueden ser invocados de esta última forma: (call ?referencia getAtributo) (call ?referencia setAtributo valor) Aplicación inicial 14 Aplicación inicial Inicialmente la aplicación estaba programada enteramente en Java. Constaba de un interfaz gráfico, una clase simulador que se encarga de gestionar los agentes y el escenario y las clases de los agentes con su comportamiento definido. Clases relevantes Simulador Mundo Agente Coche Persona Semáforo Tesoro Acción Posición CarreteraAutocarga ResuelveRutasJESS Simulador Hereda de la clase Gamelet que contiene un thread que se encarga de generar "ticks". Un "tick" es la unidad de tiempo de la simulación. Gestiona el ritmo de ejecución de la simulación. Para ello implementa el método "tick()" que invoca al método del mismo nombre de la clase ActorManager que, en última instancia, invoca al método "tick()" de cada posición. Realiza otra serie de tareas que no están relacionadas con el desarrollo de la nueva aplicación y que, de hecho, serán eliminadas. Mundo Proporciona un método size (int, int) que, dado el número de filas y columnas del tablero, genera los objetos posición del escenario. Tiene un método que dadas unas coordenadas (x,y) devuelve el objeto posición correspondiente. También implementa una serie de métodos que, dada una posición, devuelven las posiciones vecinas. Aplicación inicial 15 Agente Es una clase abstracta. Contiene los atributos que caracterizan al agente (nombre, estado, orientación, posición en la que se encuentra, frecuencia con la que se mueve, tiempo de espera desde el último movimento...) y los métodos de acceso a dichos atributos. También la redefinición del método draw(Graphics) para el redibujado del agente. Atributos y métodos relacionados con la definición del comportamiento usando Java. Éstos serán eliminados en la nueva versión de la aplicación ya que el comportamiento de los agentes se define y se gestiona mediante Jess. Método "tick()". Este método es invocado desde la clase posición para cada agente contenido en ella. "tick()" se invoca cada cierto número de milisegundos y un agente realiza un movimiento cada cierto número de "ticks" especificado mediante su atributo "frecuencia." El algoritmo es similar al siguiente: if (--turnoActual != 0) return; // Espera a que le toque el turno else turnoActual = frecuencia; //--Para cada posicion vecina a la que nos encontramos ahora... //--Para cada agente que se encuentra en una posicion vecina... Accion accion = getAccion (agenteVecino.getTipo(), agenteVecino.getEstado()); reorientarse(); if ((accion.isDireccional() && estaEnfrente(posicionVecina)) || !accion.isDireccional())) accionesARealizar.addElement(accion); if (accionesARealizar.size() != 0) this.apilaObjetivo((Accion)accionesARealizar.elementAt(0)); ejecutaSiguienteAccion(); actualizarPosicion(); Coche, Persona, Semáforo, Tesoro Métodos relacionados con la realización de acciones y comportamientos propios de cada tipo de agente: acciónANDAR(),acciónIR_A, acciónPARAR, ejecutarSiguienteAcción. Estos métodos desaparecerán al utilizar Jess y definir el comportamiento de los agentes mediante reglas. Los atributos de la clase agente toman valores específicos de cada tipo de agente, cada tipo de agente puede adoptar unos estados determinados y puede visitar distintos tipos de casillas (pej. para un coche las casillas permitidas son la carretera y los pasos de cebra mientras que un peatón puede ir sólo por las aceras y los pasos de cebra). Aplicación inicial 16 La clase Tesoro respondía a una funcionalidad de la aplicación que en la nueva versión no tiene sentido y se elimina. En la versión antigua existía la posibilidad de colocar un agente Tesoro (sin ningún tipo de comportamiento) hacia el cual se movían los agentes. Para ello los agentes con capacidad de movimiento (Coche y Persona) cuando se les indicaba que debían buscar el tesoro trazaban una ruta entre su posición y la del objetivo y, una vez que la tenían se limitaban a moverse por ese camino. Era aquí donde se hacía uso de Jess, en la obtención de la ruta. Sin embargo este no es un verdadero comportamiento de agentes ya que, en teoría un agente debe "decidir" su movimiento en función de su entorno y no moverse siguiendo un itinerario precalculado. Acción Esta clase determina un tipo de comportamiento para un agente. Posee prioridad y puede ser parametrizada. Tiene además métodos que comprueban si se puede aplicar y métodos de acceso a sus atributos. La clase acción se elimina en la nueva versión ya que, como se ha comentado antes, se sustituye el control del comportamiento de los agentes realizado desde Java por un comportamiento definido mediante reglas utilizando Jess. Posición Clase que representa cada una de las casillas en las que está dividido el tablero y en las que se puede encontrar un agente. Tiene unas coordenadas (x,y) y una lista de los agentes que se encuentran situados en ella en cada momento. También tiene un vector con las posiciones adyacentes o vecinas así como diferentes métodos de acceso a estos atributos. Método "tick()" que es invocado desde ActorManager y que invoca al método "tick()" de todos los actores que se encuentren en la posición en ese momento. Métodos de redibujado que se encargan de dibujar no sólo la superficie de la posición (carretera, hierba, árbol, etc) sino todos los agentes que se encuentran en ella. CarreteraAutocarga Hereda de la clase Simulador y es la clase que se encarga de lanzar la simulación. Carga el fichero de definición de un escenario y luego crea a partir de él un objeto mundo y los objetos agentes con lo que la simulación queda ya en marcha . Aplicación inicial 17 Posee diversos métodos y atributos relacionados con la representación gráfica que no influyen en la realización de la nueva versión ya que se mantiene intacta la gestión del interfaz gráfico. ResuelveRutasJESS En esta clase es en la única en la que se utiliza Jess en la versión inicial de la aplicación. Posee como atributos un motor de inferencia de Jess (rete) y la ruta de un archivo con las reglas en Jess. Existe un método que inicializa el motor y otro que calcula la ruta dado un origen y un destino. Esto era así porque en la versión anterior de la aplicación los agentes se movían o bien buscando un tesoro o bien porque el usuario les indicaba una posición destino. En la nueva versión el comportamiento que se queire obtener de los agentes es distinto por lo que esta clase resulta eliminada. Implementación no aleatoria 18 Implementación no aleatoria Objetivo El objetivo de esta versión de la aplicación es modelar el comportamiento de los agentes en Jess mediante el uso de reglas y eliminar el código Java que hacía esta función en la versión anterior. El comportamiento de los agentes cambia. Ahora no existe la posibilidad de fijar una posición destino para el agente, ya que de lo que se trata es de modelar un comportamiento reactivo al entorno (escenario y otros agentes) y a los cambios que se produzcan en él. Con la desaparición del comportamiento descrito desparece también la clase Tesoro, cuya única función era ser el destino de un movimiento de una persona. Al no haber destinos, no hay tesoros. Así mismo desaparecerá la clase Acción, ya que el comportamiento será enteramente descrito mediante reglas, sin necesidad del uso de esta clase. Clases relevantes Motor Simulador Mundo Agente Coche Persona Semáforo Posición CarreteraAutocarga A continuación se muestra el diagrama de clases (sin métodos ni atributos, ya se mostrarán más adelante): Implementación no aleatoria 19 Posicion Mundo -mundo +posicion +mundo Simulador -owner CarreteraAutoCarga Agente +$motorRete Motor Coche Persona Semaforo Motor La clase Motor es una nueva clase que surge con la necesidad de crear un motor Jess que se ejecute independientemente del hilo de ejecución de la aplicación principal. De este modo la aplicación sigue su curso mientras que el motor Jess se ejecuta en un thread independiente, disparando y ejecutando reglas conforme sea necesario. Atributos Rete rete; Métodos public Motor (Rete p_rete); Es el constructor. Construye el thread a partir del objeto de clase Rete que es el motor de inferencia de Jess y se pasa por parámetro. public void run (); Es la función de ejecución del thread. Lo único que hace en este caso es invocar a rete.run(), para poner en marcha el motor. Una vez puesto en marcha no acaba nunca. Implementación no aleatoria 20 Simulador Como se ha mencionado, en esta aplicación se elimina el comportamiento de asignar destinos a agentes. Esto se hacía seleccionado un agente con el ratón e indicándole dónde tenía que ir. Ahora eso ya no ocurre, con lo que los atributos y métodos relacionados son eliminados. Borramos el atributo que indicaba quién está seleccionado y el método getAgenteSeleccionado. También se eliminan los métodos clickRatón(), seleccionarAgente() y deSeleccionarAgente(). Atributos public Mundo mundo; private String modoEjecucion; private Vector listaDeModos; private Vector agentes; private boolean keypressed; Métodos public abstract int mundoX (); public abstract int mundoY (); Métodos a implementar que devuelven la anchura y la altura del tablero. public abstract String mundoDescripcionEstandar (); Implementación no aleatoria 21 Método a implementar que proporciona una descripción del mundo. public void init () { ... } Método que inicializa el simulador y el mundo. No se han hecho grandes cambios aquí, simplemente algo de limpieza de código y comentarios. public void tick () { ... } Método "tick", que inicia la secuencia de invocaciones a "tick's" hasta llegar a los agentes. No se ha modificado, sólo limpiado. public void run () { ... } Indica cuándo invocar al método propio "tick()" en función de si el modo de ejecución es contínuo o dirigido por teclado. public public public public void elegirModo (String modo) { ... } String modoActual () { ... } void addAgente (Agente agente) { ... } void removeAgente (Agente agente) { ... } Métodos que manejan los agentes del simulador y los modos de ejecución. public boolean handleRequestedEvent (Event theEvent) { ... } Método que gestiona los eventos de teclado. Tan sólo se ha limpiado el código. public abstract Image getImagenSeleccion(); public abstract Point getTamTablero(); Otros métodos abstractos. Mundo En la clase Mundo se crean los objetos Posición para el escenario y se almacenan las orientaciones y métodos de gestión de posiciones, posiciones vecinas, etc. No se ha tocado nada con respecto a la aplicación inicial. Puede que haya alguna cosa innecesaria pero se ha dejado por si se le encontrara alguna utilidad en el futuro. Implementación no aleatoria 22 Atributos public public public public static static static static final final final final int int int int ARRIBA ABAJO IZQUIERDA DERECHA = = = = 3; 1; 2; 0; Constantes que indican la orientación de un agente. private Simulador owner; private Container ownerTab; El simulador y el tablero. private int ancho = 0, alto = 0; Ancho y alto del tablero. private Vector posiciones; Objetos de Posicion contenidos en el tablero. Métodos Mundo (Simulador simul) { ... } Constructor. Asigna los valores de owner y de ownerTab. Implementación no aleatoria 23 public Vector orientaciones () { ... } Devuelve un vector con los nombres (strings) de las orientaciones posibles. public void size (int x, int y) { ... } A partir de las dimensiones del tablero construye los objetos Posición. public Posicion posicion (int x, int y) { ... } Devuelve la posición correspondiente a las coordenadas x e y. private void borrarMundo () { ... } Elimina los objetos Posición del ActorManager. public Vector posicionesVecinasDe (int x, int y) { ... } public Vector posicionesVecinasDe (int x, int y, int orientacionInicial) {...} public Vector posicionesVecinasDe (Posicion pos) { ... } public Vector posicionesVecinasDe (Posicion pos, int orientacionInicial) {...} Devuelven un Vector con las posiciones vecinas de la Posición dada. public Posicion posicionDe(int xAbs, int yAbs, int anchoTotal, int altoTotal) {...} Devuelve la posición correspondiente a las coordenadas y dimensiones introducidas. public int[][] croquis(Vector posicionesPermitidas) { ... } A partir del escenario y de un vector de posiciones permitidas genera un esquema con 0's y 1's del tablero. Agente La clase Agente sirve como soporte para la implementación de agentes concretos (Persona, Coche, ...). Proporciona atributos con información del estado del agente, métodos para obtener dicha información y el método "tick()", que antes era el que controlaba el comportamiento del agente y que ahora se ha reducido significativamente, ya que el control pasa al motor Jess. Inicialmente la clase Agente contenía bastantes atributos y métodos con relación a la recepción de mensajes (órdenes recibidas por el agente) y al control de la ejecución del movimiento de cada agente (acciones permitidas, objetivos, ...). Toda esta información es eliminada en esta nueva versión, ya que los agentes no reciben órdenes, no hay objetivos ni planificación y el comportamiento se describe mediante reglas. Implementación no aleatoria 24 Atributos public public public public static static static static final final final final int int int int ARRIBA ABAJO IZQUIERDA DERECHA = = = = Mundo.ARRIBA; Mundo.ABAJO; Mundo.IZQUIERDA; Mundo.DERECHA; Implementación no aleatoria 25 Direcciones del Mundo. public int orientacionAgente; Hacia dónde esta mirando el agente. public Posicion posicion = null; Atributo nuevo. Es la posición sobre la que se encuentra el agente en un momento dado. public int frecuencia; public int espera; Cada cuantos "tick's" mueve el agente y número de "tick's" que le quedan para mover respectivamente. public String tipo; public String nombre; Tipo del agente y nombre del agente en particular. El atributo nombre no lo utilizamos pero puede resultar de utilidad en el futuro. public Vector posicionesPermitidas; Posiciones por las que el agente puede moverse. public String estado; Estado del agente (Ej. "andando", "parado", ...). Métodos public abstract int framesTotal (); public abstract int framesHorizontal (); Frames totales y por fila del dibujo .gif que representa al agente. public void setCurrentframe (int frame) { ... } public int getCurrentframe () { ... } Métodos "set" y "get" de currentFrame, atributo que indica el frame de la imagen gif que representa al actor que debe dibujarse. public void setPosicionesPermitidas (Vector posicionesPermitidas) { ... } public Vector getPosicionesPermitidas() { ... } public boolean esPermitida (Posicion pos) { ... } Implementación no aleatoria 26 Métodos de manejo del vector de posiciones permitidas. El método esPermitida() será invocado con frecuencia desde Jess para determinar el movimiento del agente. abstract String getTipo(); Obtiene el tipo del agente. public int getEspera () { ... } public void setEspera (int nuevaEspera) { ... } public int getFrecuencia () { ... } Métodos "set" y "get" de espera y frecuencia para ser invocados desde Jess. Frecuencia tiene tan solo un método "get" porque es un atributo que no se modificará desde Jess, con lo cual no necesita de método "set". public String getNombre() { ... } public void setNombre(String nombre) { ... } Manejo del nombre. public String getEstado() { ... } public void setEstado(String nuevoEstado) { ... } Manejo del estado. Métodos "set" y "get" invocados frecuentemente desde Jess. public int getOrientacion() { ... } public void setOrientacion(int orientacion) { ... } Métodos "set" y "get" de orientación, de gran utilidad y uso en Jess. public public public public boolean boolean boolean boolean estaEnfrente(Posicion pos) { ... } estaAlLado (Posicion pos) { ... } esAdyacente (Posicion pos) { ... } estaDetras (Posicion pos) { ... } Métodos de relación del agente con respecto a posiciones. Serán igualmente invocados con frecuencia desde Jess para determinar qué regla disparar en función de la situación del agente. public int calculaOrientacion(Posicion pIni, Posicion pFin) { ... } Dadas dos posiciones (posición inicial del agente y posición destino generalmente) calcula la orientación desde la primera a la segunda. Este método se utiliza en Jess para calcular la orientación en la que quedará el agente antes de que mueva. Agente (Simulador simul, String color) { ... } Constructor. Es prácticamente tal cual estaba, quitando lo innecesario. public void setPosicion(Posicion nP) { ... } public Posicion getPosicion () { ... } Implementación no aleatoria 27 Métodos "set" y "get" de la posición del agente. Esencial para Jess. public void destruir () { ... } Elimina el agente. No se utiliza explícitamente, pero se deja por si tuviera utilidad. public void draw (Graphics g) { ... } Método de redibujado del agente. public void tick () { ... } Método esencial en el flujo de ejecución. En la aplicación anterior aquí se centraba todo el control, objetivos, decisiones, etc. del movimiento del agente. Ahora todo ese control pasa al motor Jess y a las reglas programadas. En esta aplicación sólo se encarga de marcar el ritmo de movimiento de los agentes, pero las decisiones y el movimiento corren a cargo de Jess. El ritmo lo marca reduciendo espera en 1 cada vez que "tick()" es invocado hasta llegar a 0. Cuando "tick" llegue a 0 Jess detectará que el agente tiene que mover e iniciará su "turno" de movimiento. El código es el siguiente: if (this.getEspera() != 0) this.setEspera (this.getEspera() - 1); La simplificación del método es impresionante. JavaBeans Como se menciona en el apartado de Jess, los atributos de una clase que quieran ser utilizados desde Jess han de estar programados siguiendo la filosofía de JavaBeans. Esto hace que cada uno de ellos tenga un método "set" y otro "get". Además, los métodos "set" que sean invocados desde Jess u otro método que modifique un atributo que es leído desde Jess, tras hacer la modificación, ha de notificarlo mediante una invocación a firePropertyChange(). Para ello en primer lugar hay que realizar las siguiente instancia, tal y como está en la clase Agente: public PropertyChangeSupport pcs = new PropertyChangeSupport(this); public void addPropertyChangeListener (PropertyChangeListener pcl) { pcs.addPropertyChangeListener(pcl); } public void removePropertyChangeListener (PropertyChangeListener pcl) { Implementación no aleatoria 28 pcs.removePropertyChangeListener(pcl); } Luego, tras cada modificación de los atributos correspondientes se hará la invocación de la siguiente manera: pcs.firePropertyChange("nombre_atributo", valor_viejo, valor_nuevo); como se indicó en el apartado de programación con Jess. Coche La clase coche extiende Agente, implementando los métodos abstractos de Agente para un agente de tipo coche y creando un constructor que inicializa los atributos con los valores adecuados. En la versión anterior se instanciaban las acciones de cada una de las subclases de Agente, se establecían objetivos y se utilizaba Jess. Además se implementaban métodos para cada una de las acciones que tenía que hacer el agente. Ahora en cambio no hay que hacer nada de esto ni en Coche, ni en Persona, ni en Semáforo. Métodos public String getTipo() { ... } Devuelve "COCHE"; Coche (Simulador s, String nombre, String color) { ... } Constructor de Coche. Instancia el nombre, la frecuencia y espera del agente, las posiciones posibles, el estado inicial y la orientación. También admite el color como parámetro. Esto es algo añadido a la aplicación inicial. En el constructor de la clase Agente es donde se abre el fichero de la imagen del agente. Allí, de acuerdo con el color se abrirá un fichero u otro para el agente. Resulta extremadamente útil para distinguir visualmente los agentes y observar que su comportamiento es el correcto. public int framesHorizontal () { ... } public int framesTotal () { ... } Implementación no aleatoria 29 Devuelven 4 y 4 (las 4 orientaciones posibles del coche). Persona Como Coche pero para Persona. Métodos public String getTipo() { ... } Devuelve "PERSONA"; Persona (Simulador s, String nombre, String color) { ... } Constructor de Persona. Instancia el nombre, la frecuencia y espera del agente, las posiciones posibles, el estado inicial y la orientación. En este caso, aunque exista color como parámetro, no hace caso de él. Sin embargo el procedimiento para añadir personas de colores sería idéntico al de los coches. public int framesHorizontal () { ... } public int framesTotal () { ... } Devuelven 4 y 4 (las 4 orientaciones posibles de la persona). Semáforo Como las anteriores pero para Semáforo. Métodos public String getTipo() { ... } Implementación no aleatoria 30 Devuelve "SEMÁFORO"; Coche (Simulador s, String nombre, String color) { ... } Constructor de Semáforo. Instancia el nombre, la frecuencia y espera del agente, las posiciones posibles, el estado inicial y la orientación. En este caso, aunque exista color como parámetro, no hace caso de él. Sin embargo el procedimiento para añadir semáforos de colores, aunque puede no tener sentido, sería idéntico al de los coches y las personas. public int framesHorizontal () { ... } public int framesTotal () { ... } Devuelven 3 y 3 (los 3 estados posibles del semáforo). public void redibujar () { ... } Establece el "currentFrame" del semáforo correspondiente a su estado. Posición La clase Posición prácticamente no varía con respecto a la versión inicial. Sólo se hace limpieza de código y eliminación de los atributos y métodos innecesarios. Implementación no aleatoria Atributos public Vector agentes; Vector que contiene los agentes que están en la posición. private int posX, posY, ancho, alto; Situación de la posición. private String descripcion; Tipo de posición. private Mundo mundo; Tablero en el que se encuentra la posición. Métodos Posicion (Simulador simul, Mundo mundo, int nuevaPosX, int nuevaPosY, int anchoEspacio, int altoEspacio) { ... } 31 Implementación no aleatoria 32 Constructor de la clase. Inicializa el valor de los atributos. public int posX() { ... } public int posY() { ... } Obtienen los valores de posX y posY respectivamente. public void tick() { ... } Invoca al método "tick()" de cada uno de los agentes que hay en esa posición. public void draw(Graphics g) { ... } Dibuja todos los agentes que están en la posición. Invoca a dibujar(). public void dibujar (Image frame, int xFrame, int yFrame, int anchoFrame, int altoFrame, Graphics g) { ... } Dibuja una imagen. public String descripcion() { ... } Devuelve el tipo de posición. public void nuevaDescripcion (String d, Image i) { ... } public void nuevaDescripcion (String d) { ... } Crea una nueva descripción con su imagen. Si se la pasan como parámetro tal cual, y si no la obtiene con el nombre. public Vector tieneAgente (String nombre) { ... } public Vector getAgentes() { ... } Devuelven un Vector con el agente con el nombre pasado como parámetro y un Vector con todos los agentes de la posición respectivamente. public public public public Vector String String String orientaciones() { ... } siguienteOrientacion (String orientacion) { ... } anteriorOrientacion (String orientacion) { ... } cambiarOrientacion (String orientacion, String modo) { ... } Manejo de las orientaciones. public int[][] croquis(Vector posicionesPermitidas) Invoca a Mundo.croquis(). CarreteraAutoCarga { ... } Implementación no aleatoria 33 Esta es la clase ejecutable. Hace todas las labores para preparar el comienzo de la ejecución de la simulación. Atributos public static Rete rete; public static Motor motorRete; Objeto Rete y Motor que lo contiene. String nombre; String nombreFicheroEscenario; Nombres del escenario y del fichero donde está almacenado el escenario. Vector nombresPosicion; Vector imagenesPosicion; Vectores de nombres que puede tener una posición e imágenes que puede tener una posición respectivamente. int[][] tablero; Matriz de enteros que indica el tipo de suelo que hay en cada casilla del tablero. Implementación no aleatoria int int int int int int 34 casillasAncho; casillasAlto; anchoTotal; altoTotal; numAgentes; numTiposAgente; Datos varios del escenario. Vector lanzaAgentes; Vector utilizado para la creación de los agentes a partir del fichero que contiene la información del escenario. Vector nombresTiposAgente; Vector con los tipos de agente que hay en el escenario. Métodos boolean cargaEscenario() { ... } Pide al usuario el nombre del fichero del escenario mediante una ventana de navegación de ficheros y directorios y con ese fichero genera el nuevo escenario. Se ha modificado con respecto a la versión inicial, ya que en esta hay que obtener el color del agente del fichero. Se hace en la siguiente línea de código: lanzaAgentes.addElement(new String(st.nextToken())); //se lee el color! public Point getTamTablero(){ ... } Devuelve un objeto Point con las dimensiones del tablero. public int mundoX () public int mundoY () Obtienen las dimensiones del tablero (en Posiciones). public String mundoDescripcionEstandar () Devuelve una descripción del mundo. No parece demasiado útil, pero se deja por si acaso. public void init () Método que inicializa el escenario y el motor Rete. Para el escenario invoca al método cargaEscenario(), pone el fondo del escenario, y crea cada una de las posiciones y agentes en el escenario. Para el motor Jess carga los paquetes, carga el fichero de reglas, y define las instancias de cada una de las posiciones y agentes en Jess, además de crear el Motor que Implementación no aleatoria 35 contiene el objeto Rete. A continuación se muestran los fragmentos de código más significativos. rete.executeCommand("(batch \"" + this.currentPath() + "archivoReglas.jess\")"); rete.executeCommand("(reset)"); ... for (int y = 0; y < casillasAlto; y++) { for (int x = 0; x < casillasAncho; x++) { ... f = new Funcall("definstance", rete); f.add(new Value("posicion", RU.ATOM)); f.add(new Value(mundo.posicion (x,y))); f.execute(rete.getGlobalContext()); } } ... for (int i=0; i<numAgentes ;i++ ) { ... //obtención del color String colorAgente = (String)lanzaAgentes.elementAt(4*i+3); agente = creaAgentePorNombre(this, tipoDelAgente, colorAgente, mundo.posicion(pX, pY)); ... } this.motorRete = new Motor (this.rete); ... public static void main(String[] args) { ... } Crea un objeto CarreteraAutoCarga e invoca a sus métodos init() y start(). Agente creaAgentePorNombre(Simulador padre, String nombre, String color, Posicion posicion) { ... } Crea un agente del nombre, tipo, color y posición indicados, instanciándolo además en el motor Jess. El código para instanciar en Jess es el siguiente (Como ejemplo el caso de Persona: Persona per = new Persona(padre, nombre); ... f = new Funcall("definstance", rete); f.add(new Value("persona", RU.ATOM)); f.add(new Value(per)); f.execute(rete.getGlobalContext()); Fichero de reglas En esta versión de la aplicación existe un único fichero de reglas. Es una primera aproximación en la que se ha implementado más o menos lo evidente, es decir, el fichero está compuesto por reglas del tipo "si el agente está en esta situación, entonces mueve (o gira o para, según proceda)". Parece algo evidente, pero la siguiente Implementación no aleatoria 36 versión hace que para que un agente actúe tenga que activarse y dispararse una secuencia más compleja de reglas. Es una versión "no aleatoria" debido a que el motor Jess no es aleatorio. La programación de las reglas es independiente, no indica que se tengan que disparar en orden secuencial, pero Jess utiliza siempre el mismo algoritmo para ordenar y disparar las reglas, con lo que los agentes acaban haciendo movimientos cíclicos. Por ejemplo, si existiesen dos reglas, una "girar a derecha" y otra "girar a izquierda" y en un cruce de caminos un agente pudiera optar por ambas, el algoritmo de planificación de Jess siempre las ordenaría de la misma manera y dispararía la primera, siempre la misma. En este caso podría optar por coger siempre "girar a derecha", con lo que el agente, avanzaría por el escenario en el sentido de las agujas del reloj SIEMPRE. Para la activación de las reglas programadas se cuenta con el valor del atributo "espera" de los agentes. Este atributo es decrementado por la función "tick()", invocada periódicamente por la aplicación principal, hasta llegar a 0. Lo que las reglas hacen es poner como precondición (además de las condiciones del estado del agente y el escenario) que la espera sea 0. A continuación un ejemplo de regla sencilla: (defrule semaforo-a-rojo (semaforo (espera ?t&:(eq ?t 0)) (estado ?es&:(eq ?es "AMBAR")) (OBJECT ?s)) => (set ?s espera (get ?s frecuencia)) (set ?s estado "ROJO") (call ?s redibujar) ) Esta regla pone un semáforo a rojo cuando le toca. Como se ha dicho, lo primero que hace es mirar que la espera sea 0. Además mira que el estado anterior sea ambar, ya que no se puede pasar de verde a rojo ni de rojo a rojo, y guarda en la variable "?s" la referencia al objeto (como se ha descrito en el apartado de manejo de objetos en Jess). Lo que hace esta regla es hacer un "reset" de la espera, cambiar el estado a rojo e invocar al método "redibujar", que cambia el frame de la imagen del semáforo. Como se ha mencionado anteriormente, aunque parezca absurdo, es importante el orden de las operaciones realizadas en la "post" de una regla. Es por ello que lo primero que se hace es restaurar la espera a su valor inicial. En caso de no hacerlo así, lo que ocurre es que se activan más reglas, ya que en el intervalo de tiempo entre que la regla se dispara y la espera es modificada se disparan más reglas para el mismo objeto porque su espera no es 0. Por similitud a esto, y más por precaución que por otra cosa, lo primero que se hace en la precondición de la regla es verificar que la espera es 0. Así, si la espera es lo primero que se modifica en la postcondición y lo primero que se verifica en la Implementación no aleatoria 37 precondición, se garantiza que no ocurran "cosas raras" como la mencionada en el anterior párrafo. Una regla más complicada sería: (defrule coche-default-retrocede (coche (espera ?t&:(eq ?t 0)) (posicion ?pos) (OBJECT ?c)) (posicion (OBJECT ?pos-enfrente&:(and (call ?c estaEnfrente ?pos-enfrente) (not (call ?c esPermitida ?pos-enfrente))))) (posicion (OBJECT ?pos-libre&:(and (call ?c estaDetras ?pos-libre) (call ?c esPermitida ?pos-libre)))) (not (posicion (OBJECT ?pos-lateral&:(and (call ?c estaAlLado ?pos-lateral) (call ?c esPermitida ?pos-lateral))))) => (set ?c espera (get ?c frecuencia)) (set ?c posicion ?pos) (set ?c estado "ANDANDO") (set ?c orientacion (call ?c calculaOrientacion ?pos ?pos-libre)) (set ?c currentframe (call ?c getOrientacion)) ) En la precondición busca un coche que tenga espera 0, cuya posición de enfrente no sea permitida, que tenga una posición detrás permitida (lógico si ha llegado desde ella) y que no tenga posiciones a izquierda o a derecha permitidas para él. Evidentemente la postcondición lo que hace es restaurar la espera inicial y reorientar el coche hacia esa posición de detrás, que es la única hacia la que puede moverse. Además mantiene la misma posición (ya que no anda, gira), el estado pasa a ser "ANDANDO" (ya que el agente está en movimiento), y se cambia el frame de la imagen del agente en función de la nueva orientación. Al final del documento se anexan todos los ficheros de reglas con resaltado de sintáxis para mayor facilidad de lectura. Color de los agentes El color de los agentes viene especificado en el fichero de texto que define el escenario de simulación. En dicho fichero viene definido, para cada agente su tipo y su situación. Lo que hacemos es añadir a la línea en la que viene esa información, un campo más con el color del agente. Fragmento del fichero antes (cada fila tiene [x, y, tipo]): NUMERO_AGENTES 3 3 2 0 3 7 1 7 9 0 Implementación no aleatoria 38 Fragmento del fichero ahora (cada fila tiene [x, y, tipo, color]): NUMERO_AGENTES 3 3 2 0 amarillo 3 7 1 verde 7 9 0 rojo A la hora de generar el escenario y crear los agentes (como se menciona en CarreteraAutoCarga) se tiene en cuenta este dato para parsear el fichero de definición del escenario e invocar a los métodos de creación. Además, como también se menciona en las respectivas clases, los constructores de Persona, Coche y Semáforo manejan de manera adecuada el parámetro "color". Esta modificación en principio se hizo sólo para observar mejor el comportamiento de los agentes, ya que con agentes del mismo color había dificultad para saber si se cruzaban o se daban la vuelta, pero quedó de lo más vistoso. En cualquier caso sólo se ha implementado para la clase Coche y la aplicación de construcción de escenarios no está preparada para generar los ficheros de escenario con el campo "color", por lo que habría que pulir la aplicación en ese aspecto para que su funcionamiento fuera completo y correcto. En caso de encontrar esta modificación inútil, molesta o costosa de acabar no habría ningún inconveniente en deshacerla o rehacerla de nuevo. Aun así, como se ha dicho antes, es de gran ayuda para la identificación de los agentes del mismo tipo durante la simulación. Applet Se ha intentado hacer un Applet con la aplicación. En teoría la clase "Gamelet" de la que descienden "Simulador" y "CarreteraAutoCarga" estaba preparada para ser un Applet. Lo que se intentó fue hacer que en vez de que Gamelet descienda de JPanel, descienda de JApplet. Con esto surgen problemas de seguridad, ya que la aplicación maneja ficheros y accede a propiedades del sistema que a un Applet le están prohibidas. Esto se soluciona en parte haciendo que en vez de Gamelet al sistema para encontrar el directorio actual, el atributo que lo almacena dentro de Gamelet sea la cadena "./" (ver en el fuente de Gamelet.java la variable o atributo "currentPath"). Si se hacen estas dos modificaciones el applet funciona con el visor "appletviewer". El problema viene cuando se cuelga de un servidor, ya que entonces los archivos han de accederse vía protocolo HTTP, con lo que las rutas de los ficheros son URL's, y las clases de manejo de ficheros utilizadas en la aplicación no admiten URL's como parámetro. Implementación no aleatoria 39 No obstante es posible utilizar URL's, ya que utiliza la clase BufferedReader para realizar la lectura y si se construye previamente un objeto de la clase URL, puede crearse un BufferedReader a partir de la URL. El código podría ser algo así: URL urlFichero = new URL(nombreFicheroEscenario); BufferedReader ficheroEntrada = new BufferedReader(new InputStreamReader(urlFichero.openStream())); Lo que ocurre es que la identificación de todos los casos en los que se hace una lectura del fichero o de una imagen puede no ser trivial y llevar más tiempo del en principio necesario. Además existe el problema de que lo que hace la aplicación es crear un JFrame, cosa que desde un Applet ejecutado en un navegador puede dar problemas, y se intentó cambiar pero no funcionaba correctamente. Aplicación aleatoria (1 motor global) 40 Aplicación aleatoria (1 motor global) Objetivo Dado que la planificación de Jess es determinista y que lo que se desea obtener son agentes que "deambulen" por el escenario sin rumbo fijo, hay que inventar algún mecanismo para que su movimiento sea realmente aleatorio. Aunque la forma de escribir las reglas en la aplicación predecesora sea la más intuitiva a priori, habrá que reescribirlas para que, aun de manera menos sencilla y lógica, se implemente un movimiento aleatorio. Los cambios realizados en la aplicación afectan fundamentalmente a la escritura de las reglas. También hay modificaciones en el código Java, pero siempre relacionadas con el manejo del API de Jess, para definición de hechos o instancias. Clases modificadas CarreteraAutocarga CarreteraAutoCarga Al ser la clase principal en la que se instancia el motor Jess y se definen los hechos e instancias, es lógico que en ella se hagan las principales modificaciones con respecto a la versión anterior. De hecho, es la única clase modificada. El mecanismo implementado para hacer que el movimiento de los agentes sea aleatorio se basa en la aserción de hechos "posiciones", que contienen las posiciones accesibles por un agente en un momento dado, de las cuales se elegirá una al azar. Cada agente tendrá un hecho posiciones asociado, por lo que hay que hacerlo en tiempo de creación de los agentes, y esa tarea se hace en esta clase. Concretamente se modifica el método "creaAgentePorNombre()", con el que se crea cada agente. Una vez creado el agente e instanciado en el motor Jess, se instanciará un hecho "posiciones" para ese agente. Hay que decir que esto podría haberse intentado hacer en Jess, escribiendo una regla inicial que para todo objeto de la clase "agente" se instanciara un hecho "posiciones" de la misma manera, pero se optó por la solución de resultados más inmediatos, que es la modificación del código Java. La modificación concreta es la siguiente: ... //-CÓDIGO YA EXISTENTE- Aplicación aleatoria (1 motor global) if (nombre.equals("coche")) 41 { Coche coc = new Coche(padre, nombre, color); coc.setPosicion(posicion); f = new Funcall("definstance", rete); f.add(new Value("coche", RU.ATOM)); f.add(new Value(coc)); f.execute(rete.getGlobalContext()); //-CÓDIGO NUEVOFact hecho = new Fact("posiciones",rete); hecho.setSlotValue("agente", new Value(coc)); rete.assert(hecho); //-FIN CÓDIGO NUEVO//-CÓDIGO YA EXISTENTEreturn (coc); } if (nombre.equals("persona")){ Persona per = new Persona(padre, nombre); per.setPosicion(posicion); f = new Funcall("definstance", rete); f.add(new Value("persona", RU.ATOM)); f.add(new Value(per)); f.execute(rete.getGlobalContext()); //-CÓDIGO NUEVOFact hecho = new Fact("posiciones",rete); hecho.setSlotValue("agente", new Value(per)); rete.assert(hecho); //-FIN CÓDIGO NUEVO//-CÓDIGO YA EXISTENTEreturn (per); } ... Fichero de Reglas Las principales modificaciones de esta versión recaen sobre el fichero de reglas. Se cambia casi completamente la estructura y el funcionamiento de este archivo. Descripción del mecanismo Lo más significativo es la elección aleatoria de la posición hacia la cual mover. Para esto, como se ha indicado antes, lo que se hace es buscar todas las posiciones adyacentes accesibles y tras ello elegir una de ellas al azar e iniciar el movimiento hacia ella. Aplicación aleatoria (1 motor global) 42 Para esto se define un hecho "posiciones" por cada agente que existe. En este hecho cada posición encontrada a la que el agente pueda mover es añadida. Se evitará que el agente retroceda siempre que no sea necesario, para conseguir un movimiento medianamente lógico en el que el agente mueva de manera aleatoria pero siempre avanzando. Búsqueda de las posiciones Las posiciones a las que un agente puede mover se encontrarán mediante reglas y serán añadidas al hecho "posiciones" correspondiente. Veamos las reglas. (defrule persona-default-gira (persona (espera ?t&:(eq ?t 0)) (posicion ?pos) (OBJECT ?per)) (posicion (OBJECT ?pos-libre&:(and (call ?per esAdyacente ?pos-libre) (not (call ?per estaDetras ?pos-libre)) (call ?per esPermitida ?pos-libre)))) (not (persona (posicion ?pos-persona&:(eq ?pos-persona ?pos-libre)))) ?pos-posibles <- (posiciones (agente ?a&:(call ?a equals ?per)) (posibles $?p&:(not (member$ ?pos-libre $?p)))) (idle) => (printout t "regla persona-default-gira ejecutada" crlf) (modify ?pos-posibles (posibles $?p ?pos-libre)) ) Esta regla modela la búsqueda de posiciones en las que un agente persona pueda mover. Lo que hace es buscar las posiciones adyacentes (excepto la de detrás) en las que pueda mover y en las que además no haya ninguna persona (ya que no puede haber dos personas en el mismo sitio). Comprueba que la posición no ha sido añadida con anterioridad (para no repetir posiciones) y, si todo lo anterior se cumple, añade al hecho "posibles" perteneciente al agente en cuestión y referenciado por "?pos-posibles" la nueva posición encontrada. Hay otra regla que se encarga de añadir la posición de detrás cuando no se cumple esta regla para ninguna posición, es decir, cuando al agente no le queda más remedio que retroceder. Elección de la posición Una vez encontradas todas las posiciones hay que elegir una hacia la que dirigirse. El movimiento se hace en dos pasos. El agente en primer lugar gira hacia la posición elegida y después, si aún sigue libre, se desplaza dentro de ella. Para esto hay dos reglas fundamentales, una para girar y otra para mover. Aplicación aleatoria (1 motor global) 43 Regla para girar. (defrule persona-gira (persona (espera ?t&:(eq ?t 0)) (estado ?e&:(not (eq ?e "GIRANDO"))) (posicion ?pos) (OBJECT ?per)) ?pos-posibles <- (posiciones (agente ?a&:(call ?a equals ?per)) (posibles $?p&:(not (eq 0 (length$ $?p))))) => (set ?per espera (get ?per frecuencia)) (set ?per posicion ?pos) (set ?per estado "GIRANDO") (set ?per orientacion (call ?per calculaOrientacion ?pos (eligePos (length$ $?p) $?p))) (set ?per currentframe (get ?per orientacion)) (retract ?pos-posibles) (assert (posiciones (agente ?a))) (printout t "regla persona-gira ejecutada" crlf) ) Esta regla hace que un agente persona gire. Obtiene un agente que no esté girando con su hecho "posiciones" asociado y efectúa la elección y el giro. Lo que hace es cambiar la orientación del agente dirigiéndolo hacia la posición elegida al azar, cambiar el estado a "GIRANDO" (para garantizar que no haga más de un giro), eliminar el hecho "posiciones" del que se ha elegido y crear uno nuevo vacío. Para la elección de la posición aleatoriamente utiliza la siguiente función: (deffunction eligePos (?maximo $?lista ) (bind ?ran (new Random)) (bind ?nrandom (+ (call ?ran nextInt ?maximo) 1)) (return (nth$ ?nrandom $?lista)) ) Movimiento Regla para mover: (defrule persona-mueve (persona (espera ?t&:(eq ?t 0)) (estado ?e&:(eq ?e "GIRANDO")) (posicion ?pos) (OBJECT ?per)) ?pos-posibles <- (posiciones (agente ?a&:(call ?a equals ?per)) (posibles $?p&:(not (eq 0 (length$ $?p))))) (posicion (OBJECT ?pos-enfrente&:(and (call ?per estaEnfrente ?pos-enfrente) (member$ ?pos-enfrente $?p)))) (not (persona (posicion ?pos-persona&:(eq ?pos-persona ?pos-enfrente)))) => (set ?per espera (get ?per frecuencia)) (set ?per posicion ?pos-enfrente) Aplicación aleatoria (1 motor global) 44 (set ?per estado "ANDANDO") (set ?per currentframe (get ?per orientacion)) (retract ?pos-posibles) (assert (posiciones (agente ?a))) (printout t "regla persona-mueve ejecutada" crlf) ) Esta regla describe el comportamiento de la persona cuando gira. Lo que hace es buscar un agente persona que está en estado "GIRANDO", su hecho "posiciones" asociado (se han recalculado las posiciones para ver si todavía puede mover donde quería), y busca la posición que está enfrente, mirando si hay alguna persona que haya ocupado esa posición. Si todo va bien mueve a esa posición, cambia el estado a "ANDANDO" para que el agente vuelva a girar en el próximo turno y borra y crea de nuevo su hecho "posiciones". Existen otras dos reglas (reglas "no-puede") que lo que hacen es que cuando el agente bien girando o andando no pueda seguir normalmente con su movimiento, vuelvan a recalcular todo de nuevo para iniciar una nueva elección. Módulo RESTRICCIONES Para el correcto funcionamiento de esta versión es necesaria la utilización de, al menos, un módulo. Este módulo es el módulo RESTRICCIONES, que contendrá reglas para las situaciones en las que un agente esté obligado a parar por interacción con otro agente (semáforo, peatón-coche, ...), para proseguir su movimiento normalmente tras detenerse. Para utilizar módulos hay que usar la versión 6.0 de Jess, que es la única que los admite. Su uso se explica en el apartado de Jess. Programaremos las reglas con "auto-focus" para que en cuanto se produzca una situación descrita en este módulo se active inmediatamente con prioridad sobre las otras e impida avanzar al agente hasta que la regla se desactive. Ejemplo: (defmodule RESTRICCIONES) ... (defrule RESTRICCIONES::persona-coche (declare (auto-focus TRUE)) (persona (espera ?t&:(eq ?t 0)) (posicion ?pos) (OBJECT ?per)) (coche (posicion ?pos-coche&:(call ?per estaEnfrente ?pos-coche))) (posicion (OBJECT ?pos-enfrente&:(and (call ?per estaEnfrente ?pos-enfrente) (call ?per esPermitida ?pos-enfrente)))) => (set ?per espera (get ?per frecuencia)) Aplicación aleatoria (1 motor global) (set ?per (set ?per (set ?per (printout 45 posicion ?pos) estado "GIRANDO") currentframe (call ?per getOrientacion)) t "regla persona-coche ejecutada" crlf) ) Esta regla modela el comportamiento de cuando una persona se encuentra con un coche en la posición a la que quiere ir. Lo que hace la persona es esperar a que ese coche pase para continuar con su movimiento. En un principio se pasaba a estado "PARADO", pero esto ocasionaba que cuando el coche pasaba (o lo que fuera) el agente reiniciaba su movimiento y podía cambiar de orientación en vez de seguir con lo que estaba haciendo. Esto carecía de sentido, por lo que finalmente pone "GIRANDO" para que cuando el agente reanude su movimiento avance a la posición hacia la que esta orientado. Aplicación aleatoria (1 motor/agente) 46 Aplicación aleatoria (1 motor/agente) Objetivo Conseguido el funcionamiento aleatorio la meta de esta nueva versión de la aplicación es conseguir que cada agente tenga su propio motor Jess y hacer que todos funcionen concurrentemente colaborando e interactuando entre sí. Esta versión es muy inestable. Lo que se ha hecho es programar de manera similar a la versión anterior, pero haciendo un archivo de reglas para cada tipo de agente e instanciando un thread Motor para cada objeto agente, sea del tipo que sea. Sin embargo, partiendo de este planteamiento que parece evidente, no se ha conseguido que la versión funcione de manera coherente, ya que el comportamiento de los agentes es imprevisible y desobedecen sus reglas a menudo debido a problemas que no se han conseguido identificar. Clases modificadas Agente CarreteraAutoCarga Motor Agente A la clase Agente se le añade un atributo de clase Rete que será el motor de inferencia propio del agente. El código añadido es el siguiente: public Rete rete = new Rete(); CarreteraAutoCarga Al haber ahora un motor por cada objeto, la creación e instanciación de los objetos y hechos en cada uno de los motores cambia. Creación de agentes Aplicación aleatoria (1 motor/agente) 47 En primer lugar la creación de los agentes cambia. Ahora tenemos un archivo de reglas para cada tipo de agente. Veamos un trozo de código del método "creaAgentePorNombre()": ... //creamos el semáforo. Semaforo sem = new Semaforo(padre, nombre); //le damos su posición. sem.setPosicion(posicion); //cargamos el fichero de jess con las reglas del semáforo. sem.rete.executeCommand("(batch \"" + this.currentPath() + "reglas_" + sem.getTipo().toLowerCase() + ".jess\")"); //asignamos a la variable ?*this* en JESS el puntero al objeto. Defglobal global = new Defglobal("*this*", new Value(sem)); sem.rete.addDefglobal(global); return (sem); ... Ahora es diferente. Creamos el objeto semáforo, asignamos su posición, pero ahora cargamos el archivo de reglas propio para los semáforos y definimos una variable global en Jess llamada "*this*". Esta variable global contiene una referencia al objeto. El motor de cada objeto tendrá una instancia de esta variable, que será utilizada para identificar al objeto "dueño" del motor. El comportamiento atípico de esta versión puede estar relacionado con el uso de esta variable, ya que a veces da la impresión de que un agente responde a órdenes de un motor que no es el suyo. En todo caso la gestión y compartición de memoria en Jess seguramente no será tan trivial como se ha pensado a la hora de programar esta versión multi-thread. Instanciación de agentes y posiciones Ahora cada agente tiene su propio motor, pero aun así tendrá que tener constancia de que existen todas las posiciones del escenario y todos los agentes del escenario. Esto implica la instanciación de agentes y posiciones para todos los motores existentes (uno por agente) excepto los de los semáforos, que no necesitan nada de esto. Aquí puede haber otra fuente de fallos, ya que, como se ha dicho antes, la compartición de memoria no debe de ser trivial en estos casos y se ha tratado como tal. Al tener todos los motores constancia de todos los objetos, como tienen que hacer llamadas todos entre sí, algún problema tiene que haber, y puede que esto sea la principal fuente de problemas de esta versión. Fragmento del código de instanciación: ... //Instanciación de todos los agentes for (int j = 0; j < this.agentes.size(); j++) Aplicación aleatoria (1 motor/agente) 48 { try { Agente agente_j = (Agente) agentes.elementAt(j); Funcall fun = new Funcall("definstance", agente_i.rete); fun.add(new Value(agente_j.getTipo().toLowerCase(), RU.ATOM)); fun.add(new Value(agente_j)); fun.execute(agente_i.rete.getGlobalContext()); } catch (JessException jeex) { ... } } //Instanciación de todas las posiciones try { for (int y = 0; y < casillasAlto; y++) { for (int x = 0; x < casillasAncho; x++) { Funcall fun = new Funcall("definstance", agente_i.rete); fun.add(new Value("posicion", RU.ATOM)); fun.add(new Value(mundo.posicion (x,y))); fun.execute(agente_i.rete.getGlobalContext()); } } } catch (JessException jeji){ ... } ... Creación de los motores Una vez instanciado todo y creado todo, con cada objeto Rete de cada agente se crea un nuevo objeto Motor. Aquí está el código: ... //Dentro del bucle de 1 a agentes.size() ... motores.addElement (new Motor (agente_i.rete)); ... Motor El comportamiento de la aplicación no es el esperado, y entre otras cosas hace que los motores de inferencia acaben su ejecución (lo cual no tiene mucho sentido dada la existencia del "idle-fact"). Para apañar esto se añade un bucle infinito en la ejecución del thread de Motor: rete.reset(); while (true) { Aplicación aleatoria (1 motor/agente) 49 rete.run(); } Fichero de reglas Como se ha mencionado anteriormente, ahora hay un fichero de reglas por cada tipo de agente. Además cada motor consta de varios módulos para asegurar que el disparo de las reglas de movimiento se hace en el orden adecuado. Individualización del motor Cada motor ha de saber a quién pertenece (a qué agente). Para esto se utiliza una variable global "*this*", como se ha mencionado anteriormente. Esta variable apunta a una instancia del agente que es el "propietario" del motor Jess. A la hora de ejecutar las reglas, cuando tenga que estar seguro de que el objeto es el agente propietario, verificará si es el contenido en la variable "*this*". Ejemplo: (defrule GIRA::coche-default-gira (coche (espera ?t&:(eq ?t 0)) (posicion ?pos) (OBJECT ?c&:(eq ?c ?*this*))) (posicion (OBJECT ?pos-libre&:(and (call ?c esAdyacente ?pos-libre) (not (call ?c estaDetras ?pos-libre)) (call ?c esPermitida ?pos-libre)))) (not (coche (posicion ?pos-coche&:(eq ?pos-coche ?pos-libre)))) ?pos-posibles <- (posiciones (posibles $?p&:(not (member$ ?pos-libre $?p)))) => (modify ?pos-posibles (posibles $?p ?pos-libre)) (printout t "regla coche-default-gira ejecutada " (get ?c color) crlf) ) Esta regla es equivalente a la coche-default-gira de la versión anterior. En esta comprueba que el objeto del coche referenciado por "?c" es igual al referenciado por "*this*" lo primero de todo (o casi). Módulos En esta versión los archivos de reglas constan de varios módulos. Aprovechando que la versión 6.0 de Jess los admite, y que ahora el motor de cada agente sigue su curso de ejecución sin tener en cuenta a los demás motores, se ha hecho un planteamiento algo más estructurado que el anterior. Aplicación aleatoria (1 motor/agente) 50 De este modo quedan 4 módulos: GIRA, RETROCEDE, MUEVE y RESTRICCIONES (además de MAIN). Los 3 primeros módulos se apilan para que se ejecuten en ese orden (de hecho MAIN no hace nada en el movimiento más que apilar los módulos). Así se garantiza que el agente primero intenta girar, si no ha podido girar retrocederá y cuando haya hecho una de las dos cosas anteriores moverá donde corresponda. Mientras tanto, el módulo RESTRICCIONES, programado con autofocus, puede activarse en cualquier momento, como en la versión anterior. Veamos un ejemplo de cómo se apilan los módulos: (defrule MAIN::inicia-movimiento (coche (espera ?t&:(eq ?t 0)) (OBJECT ?c&:(eq ?c ?*this*))) => (focus MUEVE) (focus RETROCEDE) (focus GIRA) (printout t "regla inicia-movimiento " (get ?c color) crlf) ) Esta regla apila los módulos para un agente coche. Lo que hace es que cuando encuentre a su propietario, apile los motores. La precondición es algo absurda, pero es para garantizar dos cosas: que siempre se ejecute y que se ejecute sólo una vez. Sin embargo puede que no fuera necesaria, ya que cuando se apilan los módulos, hasta que no se han ejecutado los 3 módulos apilados no puede volver a ejecutarse la regla de apilado. El resto de reglas que definen el movimiento y las restricciones vienen a ser como las de la versión anterior, pero distribuídas en módulos. Al final del documento pueden verse los archivos de reglas. Aplicación aleatoria (1 motor/agente) 51 Valoración del trabajo Problemas encontrados El principal problema de este trabajo, como en muchas otros, es el tener como punto de partida un trabajo anterior y ya no tener que ampliarlo, sino modificar su programación. Esto implica tener que entender la forma de programar del autor de la práctica de partida, así como su estilo de escritura de código (espacios en blanco, saltos de línea, nombres de variables y clases, etc.). No obstante con estas cosas siempre se aprende, y en nuestra vida de informáticos tendremos que enfrentarnos a muchas situaciones peores que esta, en la que se parta de una aplicación que puede tener una codificación y metodología de programación horrible y además no estar documentada y tengamos que hacer algo estructurado y elegante. En nuestro caso hemos de decir que al ser una aplicación "de tercera mano", el código estaba ya bastante mareado y "parcheado", y hay cosas, como por ejemplo hacer que la aplicación sea un Applet, que si desde un principio se hubieran planteado así no habrían supuesto ningún problema, pero en la situación actual resultan extremadamente costosas. Otro problema bastante famoso en los trabajos de los compañeros es la utilización de Jess. Se ha visto que Jess es mucho menos rápido que CLIPS, que CLIPS es mucho más eficiente. Nosotros además añadimos que Jess es "raro". Es raro porque tiene un comportamiento a veces atípico en cuándo y en qué orden se activan o disparan las reglas, en la ejecución "no atómica" de la postcondición de las reglas, ... La gran ventaja de Jess para este trabajo es el manejo de objetos de Java como si fueran hechos. También es cierto que la mayor parte de los problemas que hemos encontrado en Jess parecen tener relación con el hecho de manejar dichos objetos Java. En cualquier caso, la utilidad de Jess para estas tareas es magnífica y quizás una cada vez mejor comprensión de los mecanismos de reglas, hechos y objetos de Jess subsane gran parte de los problemas encontrados. Cosas inacabadas Como se ha mencionado ya, hay cosas inacabadas. La aplicación en este momento puede ejecutarse como Applet y como aplicación normal. Lo que ocurre es que como Applet sólo puede ejecutarse localmente (con appletviewer) ya que el acceso que se hace a los ficheros desde Java lo hace al Aplicación aleatoria (1 motor/agente) 52 directorio actual, sin tener en cuenta la URL en la que pueda encontrarse el Applet en un servidor web. Esto puede modificarse (todo es posible en Java, por supuesto) y no es demasiado complicado, pero hay tantos accesos a ficheros en la aplicación que identificar cada caso resultaba demasiado costoso y no ha habido tiempo de ello. En el apartado de la "Aplicación no aleatoria", del "Applet" habla más de ello. Además queda a mitad de hacer lo de los colores de los agentes. Actualmente está hecho sólo para la clase coche, y el constructor de escenarios no lo tiene en cuenta. Igualmente en el apartado de "Aplicación no aleatoria", de "Color de los agentes" habla más del tema. Por supuesto queda pendiente "arreglar" la versión con threads. La versión hace que los agentes se muevan, pero hay agentes que se quedan bloqueados pasado un cierto tiempo, y además no respetan su comportamiento tanto como debieran. Habría que mirar estas cosas y buscar posibles causas. Como se ha dicho antes, conforme se vaya conociendo más acerca del funcionamiento de Jess, y conforme Jess se vaya mejorando y consolidando, teniendo un comportamiento más estable, se sacará un mejor partido a lo que Jess porporciona y tal vez se arreglen estos problemas, incluso replanteando la programación inicial. Posibles ampliaciones En la presentación hecha en clase se mencionaban mejoras: ampliación del campo de visión de los agentes, mejora de iconos de agentes, nuevos agentes y comportamientos y comportamientos complejos. Todo esto, además de acabar las "cosas inacabadas" puede hacerse. Hay que decir aquí que la aplicación en cada iteración y versión es más enrevesada y complicada, ya que el código se va parcheando y ensuciando. Lo que haría falta es tal vez replantear el problema de nuevo y hacerlo bien estructurado, teniendo en cuenta todo lo que se ha ido teniendo en cuenta poco a poco para enfocarlo a ello desde el principio, pero tal vez fuera un trabajo demasiado costoso. Valoración personal Como se ha mencionado antes se ha encontrado el problema de la reutilización de código, que impide hacer las cosas todo lo bien que se quisiera. No obstante es cierto que también es una experiencia y se aprenden cosas en las que uno no habría pensado si no las hubiera visto, tanto en el contenido del programa como en la forma. La utilización de Jess en el trabajo ha resultado de lo más interesante. Si bien Jess plantea una serie de problemas anteriormente mencionados, aporta comodidades a la hora de programar con reglas como el manejo del motor Jess desde Java con el API que proporciona y, sobretodo, el manejo de objetos Java. Esto seguramente podrá hacerse con CLIPS y CORBA o algún mecanismo para ello, pero Jess está programado Aplicación aleatoria (1 motor/agente) 53 para utilizar objetos Java y comunicarse cómodamente. Esta es una capacidad que de explotarse bien tendría una gran utilidad y de hecho la ha tenido para este trabajo. Es por esto que la utilización de Jess resulta interesante y ventajosa, y hace del aprendizaje de Jess sea algo en cierto modo fascinante, lo que hace que el trabajo en sí sea fascinante. El único inconveniente de Jess es su falta de estabilidad y su extraño comportamiento a veces, como ya se ha mencionado, que compensa la fascinación por el trabajo (convirtiéndola a veces en desesperación). Sin embargo no por ello deja de ser un aprendizaje estimulante e interesante. En general la realización de este trabajo, quitando los problemas mencionados, nos ha gustado bastante y nos ha mantenido interesados y en cierto modo apasionados con los pequeños descubrimientos en Jess que estábamos haciendo, y con lo que se simplificaba la aplicación de partida. Consideramos que ha sido una experiencia positiva y recomendable para otros años. Anexos Anexos Reglas no aleatorias Reglas aleatorias para un solo motor Reglas aleatorias persona Reglas aleatorias coche Reglas aleatorias semáforo 54