3. Experimentos e Resultados
3.1. Atividade 1 - Controle da Criatura Virtual no WorldServer3D usando o SOAR
3.1.1. Objetivos específicos
- Explorar o código-fonte do WorldServer3D e da biblioteca de apoio WS3DProxy;
- Estudar o controle da criatura implementado no SOAR e disponibilizado para os alunos;
- Realizar simulações com o WorldServer3D e o DemoSOAR.
3.1.2. Desenvolvimento
O controle da criatura virtual é feito através do programa DemoSOAR.jar, que contém classes para representação da simulação e da comunicação com o SOAR por meio da interface de programação SML. Este programa implementa o ciclo cognitivo da criatura capturando os objetos percebidos no ambiente repassando-os para o processamento por parte do sistema de produção e recebe instruções deste sistema para atuar no mundo. A Figura 2 mostra o DemoSOAR em execução implementando o sistema de controle da criatura virtual presente no WorldServer3D. A Figura 3 é o log de execução do programa no SoarDebugger.
Figura 2 - Criatura sendo controlada pelo programa DemoSOAR no WorldServer3D.
Figura 3 - Execução do programa DemoSOAR no SoarDebugger.O Ponto de Entrada do Programa (SimulationSOAR.java)
A implementação do sistema de controle da criatura leva em conta detalhes da plataforma de execução. Dependendo da plataforma, as diferentes bibliotecas de código nativo que fazem parte do programa deve ser carregadas de acordo com a sua versão para cada plataforma.
Uma vez que o sistema de controle é implementado em Java, é possível obter identificar o sistema operacional utilizado e a arquitetura por meio da classe System e das propriedades especificadas para este fim. A Listagem 1 mostra um techo do código-fonte extraído do programa DemoSOAR que trata a particularidade de sistema operacional e arquitetura envolvidas:
...
String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
String osArch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH);
try {
if (osName.contains("win")){
if (osArch.contains("64")) {
System.out.println("Windows 64 bits");
NativeUtils.loadFileFromJar("/win64/Soar.lib");
NativeUtils.loadFileFromJar("/win64/Soar.dll");
NativeUtils.loadFileFromJar("/win64/Java_sml_ClientInterface.dll");
} else {
System.out.println("Windows 32 bits");
NativeUtils.loadFileFromJar("/win32/Soar.lib");
NativeUtils.loadFileFromJar("/win32/Soar.dll");
NativeUtils.loadFileFromJar("/win32/Java_sml_ClientInterface.dll");
}
} else if(osName.contains("nix") || osName.contains("nux")){
if (osArch.contains("64")) {
System.out.println("Linux 64 bits");
NativeUtils.loadFileFromJar("/linux64/libSoar.so");
NativeUtils.loadFileFromJar("/linux64/libJava_sml_ClientInterface.so");
} else {
System.out.println("Linux 32 bits");
NativeUtils.loadFileFromJar("/linux32/libSoar.so");
NativeUtils.loadFileFromJar("/linux32/libJava_sml_ClientInterface.so");
}
} else if(osName.contains("mac")){
System.out.println("MacOSX");
NativeUtils.loadFileFromJar("/macos/libSoar.dylib");
NativeUtils.loadFileFromJar("/macos/libJava_sml_ClientInterface.jnilib");
} else {
//Unable to identify
throw new IllegalStateException("Unable to determine what the "
+ "operating system is, cannot "
+ "automatically load native libraries");
}
} catch (Exception e) {
System.out.println("Unable to load native libraries. "
+ "They must be set manually with '-Djava.library.path'"+e);
// We failed. We shouldn't kill the application however,
// linking *may* have succeeded because of user manually setting location
}
...
Listagem 1 - Identificação da plataforma de execução do sistema de controle da criatura virtual.
O código da Listagem 1 testa o nome do sistema operacional em execução e, dependendo do sistema, testa a arquitetura de endereçamento de dados (32 ou 64 bits). O código nativo do respectivo sistema operacional e arquitetura é carregado através do método estático loadFileFromJar da classe NativeUtils. Todas as bibliotecas de código nativo estão empacotados no arquivo Java (JAR) do programa.
Carregamento das Produções do SOAR
Da mesma forma que as bibliotecas de código nativo, as regras SOAR utilizadas no programa também estão empacotadas no arquivo Java DemoSOAR.jar e são carregadas pelo método estático loadFileFromJar da classe NativeUtils. A Listagem 2 mostra o trecho de código que carrega as regras.
NativeUtils.loadFileFromJar("/soar-rules.soar");
String soarRulesPath = "soar-rules.soar";
Listagem 2 - Carregamento das produções no programa DemoSOAR.
Este código faz com que o arquivo soar-rules.soar seja extraído do arquivo Java DemoSOAR.jar e seja colocado no diretório de trabalho (execução) do programa. Este arquivo será utilizado posteriormente pelo enclave SOAR para processamento dos ciclos de decisão envolvidos no comportamento da criatura virtual.
O Laço Principal da Simulação
O laço principal do programa é responsável pela execução contínua do ciclo percepção-decisão-atuação. Cada etapa da simulação é implementada na classe SimulationTask. A execução da simulação, em si, é realizada pelo método runSimulation(). A cada intervalo de 100 milisegundos, um novo ciclo é iniciado. Caso ocorra uma exceção durante a execução, uma mensagem de erro apropriada é exibida. A Listagem 3 mostra o fragmento de código responsável pela implementação do laço principal.
O aprendizado por reforço está desabilitado por padrão no Soar. Para habilitá-lo, devem ser executados os seguintes comandos:
// Start enviroment data
SimulationTask simulationTask = new SimulationTask();
simulationTask.initializeEnviroment(Boolean.FALSE);
simulationTask.initializeCreatureAndSOAR(soarRulesPath,
true,soarDebuggerPath,soarDebuggerPort);
// Run Simulation until some criteria was reached
Thread.sleep(3000);
while(true)
{
simulationTask.runSimulation();
Thread.sleep(100);
}
Listagem 3 - Laço principal do programa DemoSOAR.
O código da Listagem 3 mostra ainda a etapa inicial de preparação da simulação responsável pela inicialização do ambiente e da criatura a ser controlada. Além disso, é nesta etapa que as produções SOAR são carregadas. Mais detalhes sobre a classe SimulationTask são dados na seção a seguir.
A Simulação (SimulationTask.java)
A classe SimulationTask é responsável pela implementação das etapas de preparação e execução da simulação. A preparação envolve a inicialização do ambiente e da criatura, e o carregamento das regras SOAR a serem utilizadas no ciclo de decisão de ação da criatura, ao passo que a execução trata da atualização das informações do ambiente na mente da criatura virtual, processamento do ciclo de decisão e geração e comandos de atuação no mundo virtual.
Inicialização da Criatura Virtual
A classe SimulationTask.java possui o método initializeCreatureAndSOAR(), que é responsável pela criação e inicialização da criatura e do mundo virtual. A Listagem 4 mostra o código-fonte deste método.
...
World w = null;
Creature c = null;
WS3DProxy proxy = null;
SoarBridge soarBridge = null;
...
public void initializeCreatureAndSOAR(String rulesPath,
Boolean runDebugger,
String soarDebuggerPath,
Integer soarDebuggerPort)
throws SoarBridgeException, CommandExecException
{
// Create Creature
c = proxy.createCreature(100,100,0);
w = proxy.getWorld();
c.start();
w.grow(1);
// SOAR Enviroment
soarBridge = new SoarBridge("agentSimulationSOAR", new File(rulesPath),
runDebugger,soarDebuggerPath, soarDebuggerPort);
}
Listagem 4 - Inicialização da Criatura no programa DemoSOAR.
O método realiza a criação e inicialização da criatura no mundo virtual, obtém o objeto que representa este mundo no WorldServer3D e o configura para que joias e comida sejam colocadas no mundo aleatoriamente no intervalo de tempo determinado (um minuto). Em seguida, o enclave SOAR é criado a fim de instanciar as produções e preparar a mente artificial da criatura.
A Conexão com o Mundo Virtual (WS3DProxy)
A classe WS3DProxy é a classe responsável por conectar o cliente ao servidor do mundo virtual. Esta conexão atualmente é feita por meio de socket TCP/IP. Esta classe esta presente em um arquivo Java separado (WS3DProxy.jar) e é referenciado como biblioteca externa no programa DemoSOAR.
Em seu construtor, são passados o endereço IP e a porta de conexão. Ao ser criada a instância, uma tentativa de conexão com o WorldServer3D é realizada. Caso ocorra um erro durante a conexão, o usuário é notificado e a aplicação é encerrada.
Além da classe WS3DProxy, outras classes estão disponíveis neste arquivo Java. Dentre as principais estão as classes que representam o modelo do domínio, as classes utilitárias e as auxiliares. O modelo do domínio, especificamente, traz representações de conceitos existentes no mundo, tais como: Actuator, Creature, Environment, Leaflet, MySensors, Thing, World, WorldMap etc.
O Enclave SOAR (SoarBridge)
A classe SoarBridge é a classe responsável pelo gerenciamento da instância do SOAR utilizada para representar o sistema de controle da criatura. Ela realiza a atualização do estado do ambiente na mente da criatura, ou seja, traduz os objetos percebidos no mundo para estruturas de entrada utilizadas pelo SOAR e captura as informações contidas nas estruturas de saída do SOAR para instruções de atuação no mundo virtual.
O seu construtor recebe as informações da instância do SOAR a ser configurada, tais como o nome do agente, o caminho no sistema de arquivos do arquivo de produções, um indicativo sobre o uso do modo de depuração, o caminho no sistema de arquivos da instalação do SoarDebugger e, por fim, a porta de conexão do depurador.
Além disso, é no construtor que o programa usa a interface especificada pela SML (Soar Markup Language) para criar o agente, carregar as produções e obter a estrutura do link de entrada. A Listagem 5 mostra o fragmento de código responsável por esta tarefa.
...
// SML variables
Agent agent = null;
Kernel kernel = null;
Identifier inputLink = null;
...
public SoarBridge(String _agentName, File _productionPath, Boolean startSOARDebugger,
String soarDebuggerPath, Integer soarDebuggerPort)
throws SoarBridgeException
{
...
// Inicial variables
agentName = _agentName;
productionPath = _productionPath;
...
// create Soar kernel And Agent
kernel = Kernel.CreateKernelInNewThread();
checkForKernelOrAgentError();
agent = kernel.CreateAgent(agentName);
checkForKernelOrAgentError();
// Load some productions
agent.LoadProductions(productionPath.getAbsolutePath());
checkForKernelOrAgentError();
inputLink = agent.GetInputLink();
...
// Debugger line
if (startSOARDebugger) {
agent.SpawnDebugger(soarDebuggerPort);
}
...
Listagem 5 - Inicialização da Criatura no programa DemoSOAR.
Os objetos kernel, agent e inputLink da Listagem 5 são instâncias das classes fornecidas pela SML. Esta classes tornam-se disponíveis para o programa através da inclusão do arquivo Java sml.jar no projeto.
A Atualização do Estado do Ambiente e o Controle da Criatura (SimulationTask.java)
O ciclo cognitivo da criatura virtual envolve a percepção do mundo e, eventualmente, a seleção de ação e atuação no ambiente. A partir dos dados sensoriais, decisões de ação são tomadas e comandos são executados de modo a caracterizar a atuação da criatura no mundo de acordo com os seus propósitos.
A implementação da atualização do estado do ambiente é feita na classe SimulationTask, especificamente no método prepareAndSetupCreatureToSimulation(). A transformação da ação selecionada em ações no mundo virtual, por sua vez, é implementada no método processResponseCommands(). A Listagem 6 mostra o código-fonte deste método.
...
public void runSimulation()
throws SoarBridgeException, NullPointerException,
IllegalAccessException, CommandExecException
{
if (soarBridge != null)
{
creature.updateState();
List<Thing> thingsInVision = creature.getThingsInVision();
logger.info("Objetos no Ambiente: " + thingsInVision.size());
System.out.println("Objetos no Ambiente: " + v.size());
for (Thing t : thingsInVision) {
logger.info(t.toString());
}
if (creature != null)
{
// Prepare Creature To Simulation
prepareAndSetupCreatureToSimulation(creature, thingsInVision);
// Run simulation
soarBridge.runSimulation();
// Process Responde Commands
processResponseCommands();
} else {
throw new NullPointerException("There are no creatures in simulation");
}
} else {
throw new NullPointerException("soarBrige is null. "
+"Please, invoke initializeEnviroment");
}
}
...
Listagem 6 - Atualização do estado do ambiente no programa DemoSOAR.
No código da Listagem 6, há quatro aspectos interessantes a serem destacados. O primeiro deles é o captura dos objetos presentes de campo de visão da criatura através do método getThingsInVision() da classe Creature. Este método retorna uma lista de objetos do tipo Thing. Esta classe representa coisas no mundo que são observáveis e são de interesse da criatura, tais como joias, comida (perecível ou não) e paredes. Não à toa, as classes Jewel, Food, PerishableFood e Brick são subclasses de Thing. Estes objetos são passíveis de memorização por parte da criatura, que tem interesses e ações específicos para cada um destes deles.
O segundo aspecto é o fato da apreensão dos objetos percebidos na mente da criatura e da criação e preparação dos artefatos responsáveis pelo sensoriamento. O método prepareAndSetupCreatureToSimulation() é responsável por criar os sensores visual e de combustível e associá-los à criatura. É responsável ainda pela criação da estrutura que representam as informações do ambiente e da própria criatura no enclave SOAR. Estas informações são representados como elementos na memória de trabalho (WME) a serem manipulados durante os ciclos de decisão e seleção de ação.
O terceiro aspecto é a execução da simulação pelo SOAR e isso é realizado no método runSimulation() da classe SoarBrigde. Basicamente, como já estudado na aulas anteriores sobre o SOAR, depois de ter o estado o ambiente atualizado na esttrutura de entrada (input-link), ocorrem proposições e seleção de operadores a partir de regras especificadas de modo a determinar que ações devem ser tomadas pela criatura (estas regras serão detalhadas na seção seguinte). Assim que selecionadas, são colocadas informações sobre tais ações na estrutura de saída (output-link).
O quarto e último aspecto relevante é a tradução das informações colocadas pelo SOAR na estrutura de saída em comandos a serem executados pela criatura no mundo virtual. Isso é implementado no método processResponseCommands(). Atualmente, são suportados os comandos para movimentação no mundo (move), pegar coisas (get) e comer comida (eat). Estes comandos provocam alterações no ambiente.
A partir daí, um novo ciclo se inicia no qual as alterações realizadas são percebidas e então novas decisões são tomadas e ações são selecionadas.
As Regras SOAR (soar-rules.soar)
As regras SOAR utilizadas no programa DemoSOAR são responsáveis pela seleção de ação a partir dos objetos percebidos no mundo. Os objetos são armazenados na memória da criatura e permitem que ela tenha conhecimento do ambiente em que está inserida.
Atualmente, estas regras implementam alguns dos comportamentos possíveis de serem executados pela criatura no mundo virtual. São eles: vagar pelo ambiente, mover-se em direção a algo (joia e comida), pegar joias, comer comida e desviar de paredes.
As produções existentes no programa soar-rules.soar podem ser categorizadas a partir destes comportamentos. Além destas regras, outras são especificadas para implementarem a atualização dos objetos na memória da criatura e definição de preferências entre as ações possíveis a partir do estado do ambiente em um dado instante de tempo. As regras podem ser divididas em:
Passeio pelo ambiente:
- propose*wander
- apply*wander
- apply*wander*remove*move
Atualização dos objetos no campo de visão:
- propose*see*entity*with*memory*count
- apply*see*entity*with*memory*count
- propose*see*entity*without*memory*count
- apply*see*entity*without*memory*count
Movimentação em direção à comida:
- propose*move*food
- apply*move*food
- apply*moveFood*remove-move
- apply*moveFood*remove*food
Comer a comida:
- propose*eat*food
- apply*eat*food
- apply*eatFood*remove-eat
Movimentação em direção à joia:
- propose*move*jewel
- apply*move*jewel
- apply*moveJewel*remove-move
- apply*moveJewel*remove*jewel
Pegar a joia
- propose*get*jewel
- apply*get*jewel
- apply*getJewel*remove-get
Desvio de paredes
- propose*avoidBrick
- apply*avoidBrick
- apply*avoidBrick*remove*entity*memory
- apply*avoidBrick*remove-move
Preferências de operadores
- moveJewel*seeEntity*preferences
- avoidBrick*seeEntityWithMemory*preferences
- seeEntity*without*memory*preferences
- moveJewel*getJewel*preferences
- getJewel*avoidBrick*preferences
- moveJewel*moveJewel*less*distance
- getJewel*getJewel*preferences
- moveFood*eatFood*preferences
- eatFood*avoidBrick*preferences
- moveFood*moveFood*preferences
- eatFood*eatFood*preferences
- moveFood*moveJewel*preferences*moveFoodWins
- moveFood*moveJewel*preferences*moveJewelWins
- avoidBrick*avoidBrick*without*move*jewel*preferences
- avoidBrick*moveJewel*moveFood*preferences
- wander*preferences
Como sugere a convenção para escrita de regras SOAR, as regras iniciadas em propose* são responsáveis pela proposição de operadores. Da mesma forma, as regras iniciadas em apply* são responsáveis pela aplicação do operador, caso ele tenha sido selecionado durante o ciclo de decisão. As regras terminadas em *remove*move dão conta da retratação das regras de aplicação suportadas por operadores (o-supported) assim que a ação selecionada tem sua execução completada no mundo virtual. As regras de preferência dão conta da ordem de preferência e solução de impasses na seleção dos operadores propostos.
Lógica de Funcionamento das Regras
Na implementação atual, o objetivo da criatura é sempre pegar as joias presentes no ambiente. Ela procurará por comida caso o nível de energia esteja abaixo do limiar definido. São estas metas que conduzem a criatura ao longo das simulações.
Quando uma simulação é iniciada, a proposição e aplicação do operador de vagueio é realizada e a criatura passa a vagar pelo ambiente. Na implementação atual, a criatura gira sempre à direita a uma velocidade constante à procura de objetos no ambiente.
Ao serem detectados objetos (percebidos pelo sensor visual da criatura), novos operadores são propostos. Estes operadores são responsáveis pelo armazenamento dos objetos (entidades, que podem ser joias ou comida) na memória da criatura e pela atualização do contador de objetos na memória.
Após a memorização dos objetos presentes no ambiente, operadores são propostos para fazer com que a criatura mova-se em direção e eles. Como vários operadores são propostos, cada qual propondo a movimentação em direção a um objeto, impasses são lançados no processamento da decisão. Estes impasses, que são impasses de empate, são resolvidos pela definição de preferências entre operadores.
Ao chegar próximo a joia ou a comida selecionada, novos operadores são propostos para pegá-la ou comê-la, respectivamente. O simples fato de mover-se em direção à joia ou comida não faz com que a criatura pegue-a ou coma-a. É preciso que a criatura deliberadamente o faça. Após tomada a decisão de pegar a joia ou comer a comida, os respectivos operadores são removidos da memória da trabalho e uma nova seleção de ação pode iniciar-se.
As preferências atuais determinam que mover-se em direção à comida deve prevalecer caso o nível de combustível estiver abaixo do limiar aceitável. Caso contrário, a criatura irá mover-se em direção a uma joia, só que neste caso levando em conta a distância da criatura para cada joia. É preferida a joia mais próxima. Além disso, prevalece sobre as demais a preferência pelo desvio de paredes, uma vez que elas impedem a movimentação da criatura. Como pior caso, fica o operador que propõe o vagueio pelo ambiente, significando que devem ser procurados novos objetos no ambiente.
3.1.3. Resultados
Nesta atividade foram estudados o programa disponibilizado para a simulação do controle da criatura utilizando o SOAR: o DemoSOAR. Este programa utiliza uma biblioteca de apoio, a WS3DProxy, que é responsável pela conexão do cliente (o sistema de controle da criatura) ao servidor do mundo virtual WorldServer3D.
Várias simulações foram realizadas no intuito de compreender o funcionamento do sistema de controle e a implementação do ciclo cognitivo da criatura. Em várias delas, o modo de depuração foi utilizado para inspeção dos objetos e do fluxo de operação entre as classes envolvidas.
Os experimentos realizados nesta atividade permitiram:
- o entendimento sobre o problema da variedade de plataforma e arquitetura e a solução proposta;
- o entendimento do programa DemoSOAR para a implementação do sistema de controle da criatura;
- o entendimento de como o SOAR é utilizado como um enclave e a interface de programação SML;
- o entendimento da biblioteca WS3DProxy e a sua comunicação com o servidor do mundo virtual;
- o entendimento das regras SOAR utilizadas para implementar o comportamento da criatura.
Ficou constatado que não há, na implementação atual, o uso de estratégias de aprendizado e das memórias semânticas e episódica, sendo realizada a seleção de ação da criatura através de operadores que manipulam itens exclusivamente a partir da memória de trabalho. Isso se justifica pelo fato do programa disponibilizado ter o propósito de demonstração e, especialmente, por não haver necessidade destes recursos adicionais para os objetivos propostos.
3.2. Atividade 2 - Extensão do Controle da Criatura considerando Metas
3.2.1 Objetivos específicos
- Estender o comportamento da criatura para contemplar a meta na obtenção de jóias (leaflets);
- Realizar simulações com o controle desenvolvido pelos colegas de classe.
3.2.2. Desenvolvimento
Os leaflets (folhetos) são instruções para a coleta de joias por parte da criatura no ambiente. Eles definem um número de joias e as suas respectivas cores a serem coletadas e entregues em um delivery spot (local de entrega) e são atribuídos aleatoriamente para cada criatura no ambiente. Há também um pagamento em pontos atribuído a cada folheto como forma de estimular a priorização da coleta. Idealmente, a criatura deve seguir a meta conforme definido nos folhetos, enchendo a sacola com joias e entregando-as nos locais de entregas. A Figura 4 mostra a tela dos folhetos atribuídos a criatura 0 (a primeira criatura existente no mundo virtual) no WorldServer3D.
Figura 4 - Os Leaflets no WorldServer3D.Na medida em que a criatura coleta as joias conforme estabelecido pelos folhetos, a sua pontuação é atualizada (embora a versão atual do WorldServer3D não esteja atualmente computando/mostrando a pontuação).
Para que a criatura considere as metas que são estabelecidas, é necessário que ela seja informada sobre tal e leve-as em conta no processo de tomada de decisão. A ciência da existência dos leaflets é feita criando no enclave SOAR estruturas para representá-los a partir da estrutura de entrada (input-link). A implementação desta característica foi feita no método setupStackHolder da classe SoarBridge, conforme mostrado da Listagem 7.
...
public void setupStackHolder(StakeholderType entityType, SimulationCreature parameter)
throws SoarBridgeException
{
...
// Set Creature Leaflets -->
int order = 0;
Map<String,Integer[]> colorMap = new LinkedHashMap<String,Integer[]>();
creatureLeaflets = agent.CreateIdWME(creatureMemory, "LEAFLETS");
for (Leaflet leaflet: creatureParameter.getLeafletList()) {
Identifier creatureLeafletId =
agent.CreateIdWME(creatureLeaflets, leaflet.getID().toString());
agent.CreateStringWME(creatureLeafletId, "ID", leaflet.getID().toString());
agent.CreateIntWME(creatureLeafletId, "ORDER", ++order);
agent.CreateIntWME(creatureLeafletId, "PAYMENT", leaflet.getPayment());
Identifier creatureLeafletItems = agent.CreateIdWME(creatureLeafletId, "ITEMS");
Set<String> colorSet = new LinkedHashSet<String>();
for (String color: leaflet.getItems().keySet()) {
colorSet.add(color);
Integer[] numbers = leaflet.getItems().get(color);
Identifier creatureLeafletItemColor =
agent.CreateIdWME(creatureLeafletItems, "COLOR");
agent.CreateStringWME(creatureLeafletItemColor, "NAME", color);
agent.CreateIntWME(creatureLeafletItemColor, "TOTAL_NUMBER", numbers[0]);
agent.CreateIntWME(creatureLeafletItemColor, "COLLECTED", numbers[1]);
}
for (String color: colorSet) {
Integer[] totals = null;
if (colorMap.containsKey(color)) {
totals = colorMap.get(color);
} else {
totals = new Integer[3];
totals[0] = new Integer(0);
totals[1] = new Integer(0);
totals[2] = new Integer(0);
colorMap.put(color, totals);
}
totals[0] += leaflet.getTotalNumberOfType(color);
totals[1] += leaflet.getMissingNumberOfType(color);
totals[2] += leaflet.getCollectedNumberOfType(color);
}
}
Identifier creatureLeafletTotals = agent.CreateIdWME(creatureLeaflets, "TOTALS");
int total = 0;
for (String color: colorMap.keySet()) {
Integer[] totals = colorMap.get(color);
Identifier creatureLeafletItemColor =
agent.CreateIdWME(creatureLeafletTotals,"COLOR");
agent.CreateStringWME(creatureLeafletItemColor, "NAME", color);
agent.CreateIntWME(creatureLeafletItemColor,"TOTAL_NUMBER_OF_TYPE",totals[0]);
agent.CreateIntWME(creatureLeafletItemColor,"MISSING_NUMBER_OF_TYPE",totals[1]);
agent.CreateIntWME(creatureLeafletItemColor,"COLLECTED_NUMBER_OF_TYPE",totals[2]);
}
agent.CreateIntWME(creatureLeaflets, "KNAPSACK", 0);
agent.CreateIntWME(creatureLeaflets, "COMPLETE", total);
agent.CreateIntWME(creatureLeaflets, "CURRENT_LEAFLET_ID", 1);
// <-- End of Leaflets
...
Listagem 7 - Implementação da transferência dos leaflets para o enclave SOAR no WorldServer3D.
A seguir serão dados os requisitos que definem a estratégia escolhida para implementação do sistema de controle da criatura baseando-se nas instruções dos folhetos.
Estratégia para o Sistema de Controle da Criatura considerando os Leaflets
A estratégia de implementação dos leaflets considerou alguns aspectos da implementação disponibilizada como base propondo a extensão a partir de novos requisitos existentes:
- Requisito #1: A criatura deve memorizar as joias existentes no ambiente conforme o seu campo de visão;
- Requisito #2: Os leaflets devem ser informados à criatura assim que esta é inserida no mundo virtual. Esta deve memorizá-los para execução do seu trabalho. A criatura inicialmente não fará distinção entre joias de leaflets diferentes, baseando-se no total conforme suas cores e optando por coletar qualquer joia que estiver em um dos três leaflets informados.
- Requisito #3: A coleta das joias deve ser feita conforme estabelecido pelos leaflets, impondo a movimentação em direção àquelas joias que estão contidas na meta;
- Requisito #4: As joias coletadas devem ser contabilizadas de modo que a criatura tenha ciência, por si só, quantas joias já foram coletadas (estão na sua sacola) e quantas ainda restam.
- Requisito #5: As joias que não estiverem nos leaflets mas que, porventura, estiverem no caminho definido pela criatura deverão ser capturadas.
- Requisito #6: Com a sacola cheia, a criatura deverá procurar o local de entrega para depositar todas as joias, levando em conta os diferentes leaflets, um por vez.
- Requisito #7: Ao concluir a entrega, a criatura permanecerá no local de entrega dando por encerrado o seu trabalho.
Implementação
O Requisito #1 já está implementado na versão disponibilizada do DemoSOAR e não foi alterado. O mesmo número total de entidades mantidas na memória de trabalho simultaneamente foi mantido (7 entidades).
O Requisito #2 foi atendido por meio da implementação do operador memorizeLeaflets. Este operador se encarrega de salvar a estrutura dos leaflets, obtidas do mundo virtual, para o estado interno da criatura. Esta característica é útil para viabilizar a manipulação do número de joias coletadas e das joias restantes. Este operador tem preferência definida como melhor operador porque precisa, obrigatoriamente, prevalecer sobre os demais assim que a criatura é criada no mundo virtual. As regras de proposição, aplicação e preferências deste operador estão listados na Listagem 8.
# Propose*prepare*leaflets:
sp {propose*memorize*leaflets
(state <s> ^io.input-link <il>)
(<il> ^CREATURE <creature>)
(<creature> ^MEMORY <memory>)
(<memory> ^LEAFLETS <leaflets>)
-(<s> ^LEAFLETS <leaflets>)
-->
(write (crlf) | [memorizeLeaflets] |)
(<s> ^operator <o> +)
(<o> ^name memorizeLeaflets)
(<o> ^parameter <leaflets>)}
# Apply*memorize*leaflets:
sp {apply*memorize*leaflets
(state <s> ^operator <o>)
(<o> ^name memorizeLeaflets)
(<o> ^parameter <leaflets>)
-->
(<s> ^LEAFLETS <leaflets>)}
sp {memorize*leaflets*preferences
(state <s> ^operator <o> +)
(<o> ^name memorizeLeaflets)
-->
(<s> ^operator <o> >)}
Listagem 8 - Implementação do operador memorizeLeaflets.
O Requisito #3 foi atendido pela alteração das regras de proposição do operador moveJewel existente, agora referenciado como movelJewelLeaflets. Ele agora deve considerar apenas as joias cujas cores estão definidas nos leaflets. Desta forma, fica garantido que a criatura só persegue as joias que realmente são de seu interesse, para atingir a meta de coletar todas as joias atribuídas a ela. A regra de proposição do operador garante, ainda, que apenas joias que faltam sejam perseguidas, conforme a contagem atual de joias já coletadas e o total a ser coletado. As regras de proposição, aplicação e preferências deste operador estão listados na Listagem 9.
# Propose*move*jewel
sp {propose*move*jewel*leaflet
(state <s> ^io.input-link <il>)
-(<s> ^operator getJewelLeaflet)
(<il> ^CREATURE <creature>)
(<creature> ^MEMORY <memory>)
(<memory> ^ENTITY <entityInMemory>)
(<creature> ^POSITION <creaturePosition>)
(<creaturePosition> ^X <creaturePositionX>)
(<creaturePosition> ^Y <creaturePositionY>)
(<entityInMemory> ^TYPE JEWEL)
(<entityInMemory> ^X <entityInMemoryPositionX>)
(<entityInMemory> ^Y <entityInMemoryPositionY>)
(<entityInMemory> ^NAME <entityInMemoryName>)
(<entityInMemory> ^COLOR <entityInMemoryColor>)
(<s> ^LEAFLETS <leaflets>)
(<leaflets> ^TOTALS <leafletTotals>)
(<leafletTotals> ^COLOR <leafletColor>)
(<leafletColor> ^NAME <leafletColorName> <entityInMemoryColor>)
(<leafletColor> ^TOTAL_NUMBER_OF_TYPE <leafletColorTotalNumberOfType>)
(<leafletColor> ^COLLECTED_NUMBER_OF_TYPE
<leafletColorCollectedNumberOfType> < <leafletColorTotalNumberOfType>)
## variantes para consideração de um leaflets
## por vez ao invés do total de joias.
# (<leaflets> ^<idNode> <leaflet>)
# (<leaflet> ^ORDER <order>)
# (<leaflet> ^PAYMENT <leafletPayment>)
# (<leaflet> ^ITEMS <leafletItems>)
# (<leafletItems> ^COLOR <entityInMemoryColor>)
# (<leafletColorItem> ^NAME <leafletItemColorName>)
# (<leafletColorItem> ^TOTAL_NUMBER <leafletColorItemTotalNumber>)
# (<leafletColorItem> ^COLLECTED <leafletColorItemCollected>)
-->
(write (crlf) | [moveJewelLeaflet] JEWEL(Color:| <entityInMemoryColor> |
,Collected:| <leafletColorCollectedNumberOfType> |/|
<leafletColorTotalNumberOfType> |,Name:| <entityInMemoryName> |)|)
(<s> ^operator <o> +)
(<o> ^name moveJewel)
(<o> ^parameter <leaflet>)
(<leaflet> ^collected <leafletColorCollectedNumberOfType>)
(<o> ^parameter <jewel>)
(<jewel> ^distance (sqrt (+ (* (- <creaturePositionX> <entityInMemoryPositionX>)
(- <creaturePositionX> <entityInMemoryPositionX>))
(* (- <creaturePositionY> <entityInMemoryPositionY>)
(- <creaturePositionY> <entityInMemoryPositionY>)))))
(<jewel> ^X <entityInMemoryPositionX>)
(<jewel> ^Y <entityInMemoryPositionY>)
(<jewel> ^NAME <entityInMemoryName>)
(<jewel> ^COLOR <entityInMemoryColor>)}
# Apply*move*jewel:
# If the move operator is selected, then generate an output command to it
sp {apply*move*jewel
(state <s> ^operator <o>
^io <io>)
(<io> ^input-link <il>)
(<io> ^output-link <ol>)
(<o> ^name moveJewel)
(<o> ^parameter <jewel>)
(<jewel> ^X <x>)
(<jewel> ^Y <y>)
(<jewel> ^NAME <entityInMemoryName>)
(<il> ^CREATURE <creature>)
(<creature> ^MEMORY <memory>)
(<memory> ^ENTITY <entityInMemory>)
(<entityInMemory> ^NAME <entityInMemoryName>)
-->
(<ol> ^MOVE <command>)
(<command> ^Vel 3)
(<command> ^VelR 3)
(<command> ^VelL 3)
(<command> ^X <x>)
(<command> ^Y <y>)}
# Apply*moveJewel*remove-move:
sp {apply*moveJewel*remove-move
(state <s> ^operator.name moveJewel
^io.output-link <out>)
(<out> ^MOVE <move>)
(<move> ^status complete)
-->
(<out> ^MOVE <move> -)}
# Remove the jewel From memory because de jewel is not there
sp {apply*moveJewel*remove*jewel
(state <s> ^operator <o>
^io.input-link <il>)
(<o> ^name moveJewel)
(<o> ^parameter <jewel>)
(<jewel> ^X <x>)
(<jewel> ^Y <y>)
(<il> ^CREATURE <creature>)
(<creature> ^MEMORY <memory>)
(<memory> ^ENTITY <entityInMemory>)
(<memory> ^COUNT <quantity>)
(<entityInMemory> ^X <x>)
(<entityInMemory> ^Y <y>)
-(<creature> ^SENSOR.VISUAL.ENTITY.X <entityX> <x>)
-(<creature> ^SENSOR.VISUAL.ENTITY.Y <entityY> <y>)
-->
(<memory> ^ENTITY <entityInMemory> -)
(<memory> ^COUNT <quantity> -
^COUNT (- <quantity> 1))}
Listagem 9 - Implementação da adaptação do operador moveJewel.
O Requisito #4 foi atendido com a criação de um novo operador para captura da joia: getJewelLeaflet. Este operador é proposto para as joias sejam coletadas pela criatura uma vez que a joia diante dela é uma de seu interesse, conforme a meta. Ao ser aplicado, a contabilização da joia coletada é feita guardando-a na sacola. As regras de proposição, aplicação e preferências deste operador estão listados na Listagem 10.
########################## GET JEWEL LEAFLET ##################################
# This operator will make the agent get the jewel considering leaflets
# Propose*get*jewel*leaflet:
sp {propose*get*jewel*leaflet
(state <s> ^io.input-link <il>)
(<il> ^CREATURE <creature>)
(<creature> ^SENSOR.VISUAL.ENTITY <entity>)
(<entity> ^TYPE JEWEL)
(<entity> ^DISTANCE <jewelDistance> < 30)
(<entity> ^NAME <jewelName>)
(<entity> ^COLOR <jewelColor>)
(<creature> ^MEMORY.ENTITY.NAME <memoryItemName> <jewelName>)
(<s> ^LEAFLETS <leaflets>)
(<leaflets> ^TOTALS <leafletTotals>)
(<leafletTotals> ^COLOR <leafletColor>)
(<leafletColor> ^NAME <leafletColorName> <jewelColor>)
(<leafletColor> ^COLLECTED_NUMBER_OF_TYPE <leafletColorCollectedNumberOfType>)
-->
(write (crlf) | [ProposeGetJewelLeaflet] JEWEL(Color:| <jewelColor> |
,Collected:| <leafletColorCollectedNumberOfType> |)|)
(<s> ^operator <o> +)
(<o> ^name getJewelLeaflet)
(<o> ^parameter <jewel>)
(<jewel> ^NAME <jewelName>)
(<jewel> ^COLOR <jewelColor>)
(<jewel> ^distance <jewelDistance>)
(<o> ^parameter <leaflet>)
(<leaflet> ^collected <leafletColorCollectedNumberOfType>)
}
# Apply*get*jewel*leaflet:
# If the move operator is selected, then generate an output command to it
sp {apply*get*jewel*leaflet
(state <s> ^operator <o>
^io <io>)
(<io> ^input-link <il>)
(<io> ^output-link <ol>)
(<o> ^name getJewelLeaflet)
(<o> ^parameter <p>)
(<p> ^NAME <jewelName>)
(<p> ^COLOR <jewelColor>)
(<il> ^CREATURE <creature>)
(<creature> ^MEMORY <memory>)
(<memory> ^COUNT <quantity>)
(<memory> ^ENTITY <memoryEntity>)
(<memoryEntity> ^NAME <memoryEntityName> <jewelName>)
(<s> ^LEAFLETS <leaflets>)
(<leaflets> ^KNAPSACK <totalInKnapsack>)
(<leaflets> ^TOTALS <leafletTotals>)
(<leafletTotals> ^COLOR <leafletColor>)
(<leafletColor> ^NAME <entityInMemoryColor> <jewelColor>)
(<leafletColor> ^COLLECTED_NUMBER_OF_TYPE <leafletColorCollectedNumberOfType>)
-->
(write (crlf) | [GetJewelLeaflet] JEWEL(Color:| <jewelColor> |,Collected:|
<leafletColorCollectedNumberOfType> |)|)
(<ol> ^GET <command>)
(<command> ^Name <jewelName>)
(<memory> ^COUNT <quantity> -
^COUNT (- <quantity> 1))
(<memory> ^ENTITY <memoryEntity> -)
(<leafletColor> ^COLLECTED_NUMBER_OF_TYPE <leafletColorCollectedNumberOfType> -
^COLLECTED_NUMBER_OF_TYPE (+ <leafletColorCollectedNumberOfType> 1))
(<leaflets> ^KNAPSACK <totalInKnapsack> -
^KNAPSACK (+ <totalInKnapsack> 1))
}
# Apply*get*remove-move:
# If the getJewel operator is selected,
# and there is a completed move command on the output link,
# then remove that command.
sp {apply*getJewelLeaflet*remove-get
(state <s> ^operator <o>
^io.output-link <out>)
(<o> ^name getJewelLeaflet)
(<o> ^parameter.name <jewelName>)
(<out> ^GET <move>)
(<move> ^status complete)
-->
(<out> ^GET <move> -)}
Listagem 10 - Implementação da adaptação do operador moveJewel.
O Requisito #5 foi atendido com uma pequena modificação no operador já existente getJewel. A modificação feita foi a acomodação das preferências entre os operadores de captura. A criatura sempre persegue as joias que são de seu interesse, porém, por vezes pode encontrar outras joias no seu trajeto que não necessariamente estão definidas nos leaflets. Para evitar este "tropeço" da criatura nessas joias, o operador getJewel é proposto e tem preferência pela aplicação quando uma joia que não é de interesse esteja no caminho. As regras de proposição, aplicação e preferências deste operador estão listados na Listagem 11.
# Propose*get*jewel:
sp {propose*get*jewel
(state <s> ^io.input-link <il>)
(<il> ^CREATURE <creature>)
(<creature> ^SENSOR.VISUAL.ENTITY <entity>)
(<entity> ^TYPE JEWEL)
(<entity> ^DISTANCE <jewelDistance> < 30)
(<entity> ^NAME <jewelName>)
(<entity> ^COLOR <jewelColor>)
(<creature> ^MEMORY.ENTITY.NAME <memoryItemName> <jewelName>)
(<s> ^LEAFLETS <leaflets>)
(<leaflets> ^TOTALS <leafletTotals>)
(<leafletTotals> ^COLOR <leafletColor>)
-(<leafletColor> ^NAME <entityInMemoryColor> <jewelColor>)
-->
(<s> ^operator <o> +)
(<o> ^name getJewel)
(<o> ^parameter <jewel>)
(<jewel> ^NAME <jewelName>)
(<jewel> ^DISTANCE <jewelDistance>)}
...
# Get Jewel Leaflet vs Get Jewel
sp {getJewelLeaflet*getJewel*preferences
(state <s> ^operator <o> +
<o2> +)
(<o> ^name getJewel)
(<o2> ^name getJewelLeaflet)
-->
(<s> ^operator <o> < <o2>)}
Listagem 11 - Implementação da adaptação do operador getJewel.
O Requisito #6 foi atendido através da implementação do operador deliverLeaflets. Este operador propõe a entrega da sacola no instante em que a criatura conseguiu capturar todas as joias estipuladas nos leaflets. O WorldServer3D requer que a entrega seja feita passando o identificador do respectivo leaflet. Esta particularidade é resolvida propondo-se a entrega sucessiva dos leaflets individuais, como se criatura entregasse as joias conferindo o total especificado em cada um deles, um por vez. As regras de proposição, aplicação e preferências deste operador estão listados na Listagem 12.
NOTA: Foi necessária a implementação em Java do comando de entrega no projeto: SoarCommandDeliver.java. Juntamente com a implementação deste comando, foram necessárias adaptações nas classes SoarCommand e SoarBrigde para tratamento deste novo comando. Além disso, alguns ajustes adicionais foram necessários para alterar o modo como a entrega dos leaflets estão implementados no WS3DProxy. Atualmente a entrega considera o identificador da criatura. Ele foi alterado para considerar o identificador do leaflet. Esta alteração foi feita na classe SimulationTask do projeto atual e nas classes Creature e CommandUtility (do projeto WS3DProxy).
################################ DELIVER #####################################
# Propose*deliver*leaflets:
sp {propose*deliver*leaflets
(state <s> ^io <io>)
(<s> ^LEAFLETS <leaflets>)
(<leaflets> ^COMPLETE <complete>)
(<leaflets> ^KNAPSACK <knapsack> >= <complete>)
(<leaflets> ^CURRENT_LEAFLET_ID <leafletId> <= 3)
-->
(write (crlf) | [deliverLeaflets] |)
(<s> ^operator <o> +)
(<o> ^name deliverLeaflets)
(<o> ^parameter <leafletId>)}
# Apply*deliver*leaflets:
sp {apply*deliver*leaflets
(state <s> ^operator <o>
^io <io>)
(<io> ^output-link <ol>)
(<o> ^name deliverLeaflets)
(<o> ^parameter <leafletId>)
(<s> ^LEAFLETS <leaflets>)
-->
(<ol> ^DELIVER <command>)
(<command> ^Id <leafletId>)
(<leaflets> ^CURRENT_LEAFLET_ID <leafletId> -
^CURRENT_LEAFLET_ID (+ <leafletId> 1))}
# Apply*deliverLeaflets*remove-move:
sp {apply*deliverLeaflets*remove-deliver
(state <s> ^operator <o>
^io.output-link <ol>)
(<o> ^name deliverLeaflets)
(<ol> ^DELIVER <deliver>)
(<deliver> ^status complete)
-->
(<ol> ^DELIVER <deliver> -)}
# Deliver Leaflets Preferences
sp {deliver*leaflets*preferences
(state <s> ^operator <o> +)
(<o> ^name deliverLeaflets)
-->
(<s> ^operator <o> >)}
Listagem 12 - Implementação operador deliverLeaflet.
O Requisito #8 foi atendido automaticamente por consequência do comportamento da criatura conforme implementado pelas regras existentes. Ao serem entregues todas as joias, a criatura não zera os totais coletados, o que faz com que nenhum operador de movimentação seja proposto, uma vez que estes só são propostos caso ainda restem joias a serem coletadas. O opeador que é proposto e aplicado é o operador wander, que faz com que criatura permaneça onde está.
3.2.3. Resultados
- AgentControllerLeafletsSOAR (Java Web Start):