You are here

Aula 07 - Soar e WS3D

Agentes SOAR no WorldServer3D

Soar is a general cognitive architecture for developing systems that exhibit intelligent behavior.

 



Apresentação

Esta atividade complementa as anteriores (todas baseadas no pacote de tutoriais do Soar) e visa explorar o Soar como uma camada de controle de um agente num ambiente de simulação 3D. Para esta atividade, além do material anteriormente utilizado, recorremos às dicas sobre implementações de agentes Soar (em particular, os guias sobre o Soar Markup Language (SML): o QuickStartGuide e o BuildingClients). 

 


Estudo do DemoSoar e do WorldServer3D

Nesta seção estudamos uma simulação no WorldServer3D (WS3D). Um agente (bot) controlado pelo Soar vagando no WS3D. O ambiente de simulação é o mesmo utilizado na primeira aula, ligeiramente modificado. O uso do Soar para controlar o bot se dá por meio de uma interface Soar Markup Language (SML). Apresentamos nesta seção alguns pontos sobre os códigos que fazem parte desta simulação: o DemoSoar, o WorldServer3D e o código WS3DProxy.  Seguimos o seguinte roteiro:

  1. Funcionamento do DemoSoar
    • Estudo da classe SimulationSOAR.java (que contém o método main). Para este estudo utilizamos o código-fonte do DemoSoar.
      • Atenção para a classe NativeUtils, usada para resolver o problema do gerenciamento do código JNI para diferentes versões de Sistema Operacional (SO). Explicar como esse gerenciamento é feito.
      • Como ocorre o carregamento de arquivos com código Soar, para uso do controlador (implemenção no Demo)?
      • Explicar o funcionamento do laço principal de simulação do DemoSOAR (está no método main). Conexão com o WorldServer3D
  2. Conexão com o WorldServer3D
    • Nesta seção exploramos os códigos do WorldServer3D (código-fonte [ aqui ]) e do WS3DProxy (código-fonte [ aqui ]).  O WS3DProxy é uma biblioteca de apoio, faz a conexão com o WS3D: envia e recebe comandos em objetos de alto nível.
      • Estudo da classe SimulationTask.java (que SimulationTask.java uso classes em WS3DProxy).
        • Explique como o WS3DProxy estabelece conexão com o WS3D.
        • A classe SimulationTask utiliza a classe SoarBridge, para ter acesso ao Soar. Explique como é feita a leitura do estado corrente no WS3D e como esses dados sensoriais são enviado para o Soar. Analogamente, como os dados enviados pelo Soar são aproveitados para controlar a criatura no WS3D?
  3. Funcionamento do BotSoar no WS3D
  • Estudo das regras Soar em soar-rules.soar.  Explicar a estrutura de controle do bot.
 
Relatório de Estudo dos itens propostos

Para a elaboração do relatório seguimos o roteiro acima estabelecido. A Figura 1 mostra a arena do WorldServer3D carregado com um bot, alimentos e  jóias. 

Figura 1. Ambiente WorldServer3D com um botSoar
 
 
1. Funcionamento do DemoSoar

Estudo da classe SimulationSOAR.java (que contém o método main). Para este estudo utilizamos o código-fonte do DemoSoar. A classe SimulationSOAR.java é parte do pacote Simulation e utiliza as seguinte bibliotecas: 

                              
// ----------------------------------------------------------------------------------------------
import SoarBridge.NativeUtils;
import SoarBridge.SoarBridgeException;
import java.util.Locale;
import org.apache.log4j.Logger;

 

  • A biblioteca SoarBridge.NativeUtils, pertencente ao pacote SoarBridge, permite trabalhar com interfaces nativas do Java (JNI - Java Native Interface). Nesse caso, é a JNI que permite a utilização da interface Soar, implementada em SML, no WS3D, implementado em Java. As dicas sobre como carregar bibliotecas nativas JNI a partir do jar estão [ aqui ] e outras relativas ao NetBeans [ aqui ].
  • A biblioteca SoarBridge.SoarBridgeException, também pertencente ao pacote SoarBridge, ela estende a classe Exception (superclasse de todas as exceções) do pacote java.lang.
  • O pacote java.util.Locale é utilizado para selecionar valores de chave, o pacote contém valores finais tais como Locale.FRANCE, Locale.CHINESE, Locale.ENGLISH ( que foi o valor de chave utilizado), etc. 
  • O org.apache.log4j.Logger é a classe principal do framework Log4j, ela trata das operações de log, exceto partes referentes à configuração. O Log4j possui três componentes principais: loggers, appenders e layouts. Grosso modo, o Logger tem a função de receber as mensagens e incluí-las ao output de acordo com a configuração dada, o Appender é o output do log (o console, algum arquivo, um servidor remoto de sockets, etc), e a forma como o log é exibido é definida pelo Layout (pode incluir a data da mensagem, qual o método que contém o log, qual linha, classe, arquivo, etc). O uso na classe SimulationSOAR.java visa tratar os log da classe SimulationSOAR: (conforme mostra o trecho de código abaixo):
static Logger logger = Logger.getLogger(SimulationSOAR.class.getName( ));

O esquema abaixo exibe a estrutura geral da classe  SimuilationSoar.  

                              
// ----------------------------------------------------------------------------------------------
public class SimulationSOAR {

  static Logger logger = Logger.getLogger(SimulationSOAR.class.getName());

  public static void main(String[] args) {

    try {   // captura exceções referente à conexão com o Soar, trata-as no bloco catch                  

      NativeUtils.setLibraryPath(".");

      try {     // captura exceções referente ao S.O. corrente,  trata-as no respectivo catch 
          if( Win32 ou Win64 )
            . . . 

          else if( MacOSX ) 
               . . . 

               else if( Linux32 ou Linux64 ) 
                      . . .
 
                    else Exceção 
                                  
      }
      catch ( Trata exceção ) {  
              . . . referente ao S.O. 

      }
      . . . Carrega o ambiente de simulação: simulationtask    

      . . . Executa o ambiente de simulação enquanto satisfaz a condição imposta 
    
    } 
    catch ( Trata exceção ) { 
    . . . referente à conexão com o Soar

    }
  }
}

 

A seguir comentamos o main em três passos: o primeiro se refere ao bloco que trata do carregamento das bibliotecas nativas e identificação do sistema operacional encontrado; o segundo cuida do carregamento do ambiente de simulação e o terceiro trata do estabelecimento da conexão entre o Soar e o WS3D e execução da simulação.

O trecho de código abaixo se refere ao carregamento de bibliotecas nativas e identificação do sistema operacional. Todo o processo dessa etapa depende da classe NativeUtils.

                         
# -----------------------------------------------------------------------------------------------
NativeUtils.setLibraryPath("."); // Diz que as bibliotecas nativas estão no diretório corrente "."
                                 // O caminho é obtido por System.setProperty("java.library.path", path);

// Captura qual o S. O. (Win, Mac, Linux-Unix) e a arquitetura (32bit, 64bit) 
System.out.println("OS:" + System.getProperties().getProperty("os.name") + 
                   " Architecture: " + System.getProperties().getProperty("os.arch"));
            
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");
         // A classe NativeUtils é a base para trabalhar com o JNI - Veja dicas [ aqui ]  
         // O método loadFileFromJar segue o esquema apresentado [ aqui ], faz um cópia temporária do 
         // arquivo indicado e depois o carrega para uso do DemoSoar. Note que o argumento (para o 
         // nome do arquivo) é uma é uma String, a "referência" independe do S.O. 
         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("mac")) {
           System.out.println("MacOSX");
           NativeUtils.loadFileFromJar("/macos/libSoar.dylib");
           NativeUtils.loadFileFromJar("/macos/libJava_sml_ClientInterface.jnilib");
        } 
        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 {
           //Unable to identify operation system
           throw new IllegalStateException("Unable to determine guest O. S. Libraries cannot be load");
        }
} 
catch (Exception e) {
  // Falha no carregamento das bibliotecas. Tentar a carga indicando manualmente o local da biblioteca. 
  System.out.println("Unable to load native libraries. Adjust set path manually '-Djava.library.path'"+e);
}
   

 

O trecho de código abaixo trata do carregamento do ambiente de simulação. Comentamos os trechos de códigos que fazem o carregamento de arquivos com código Soar.

                        
# -------------------------------------------------------------------------------------------
try {  
    . . .  captura as informações do S.O. corrente 
               
   try {
      . . . captura exceções referente ao S.O. corrente,  trata-as no respectivo catch  

   } 
   catch ( Trata exceção referente ao S.O. ) {
      . . . 

   }
   // Após a identificação do S. O.
   // O método loadFileFromJar( ), já mencionado, carrega o arquivo: soar-rules.soar.
   // O nome do arquivo (a string entre aspas-duplas " ..." ) dentro do JAR deve indicar o caminho 
   // do arquivo (começa com '/'). 
   NativeUtils.loadFileFromJar("/soar-rules.soar");
   String soarRulesPath = "soar-rules.soar";
   String soarDebuggerPath = "";
   Integer soarDebuggerPort = 12121; 

   //Start enviroment data
   // A classe SimulationTask trata da conexão para a execução da simulação
   SimulationTask simulationTask = new SimulationTask();
   simulationTask.initializeEnviroment(Boolean.FALSE);
   // O método initializeCreatureAndSOAR( )
   // soarRulesPath é uma string com o caminho do arquivo que contém as regras: soar-rules.soar. 
   // soarDebuggerPath é uma string com o caminho do SJD (basta colocar o SJD em "/bin")
   simulationTask.initializeCreatureAndSOAR(soarRulesPath,true,soarDebuggerPath,soarDebuggerPort);

   . . .   run simulation until some criteria was reached

}
// Faz o tratamento da exceção
catch (SoarBridgeException ex) { 
    logger.error("Error in soar bridge",ex); 
}
// Faz o tratamento da exceção
catch (Exception ex) {
    System.out.println(ex.toString());
    ex.printStackTrace();
    logger.error("Unknown error",ex);
}


 

O trecho de código abaixo cuida da conexão e execução da simulação. Comentamos sobre a conexão Soar-WS3D mantida pelo laço principal de simulação do DemoSOAR.

                
# -------------------------------------------------------------------------------------------
try {  
    . . .  captura as informações do S.O. corrente 
               
   try {
      . . . captura exceções referente ao S.O. corrente,  trata-as no respectivo catch  
   
   } 
   catch ( Trata exceção referente ao S.O. ) {
      . . . 

   }
   . . . load soar-rules.soar

   . . . start enviroment data

   // Run Simulation until some criteria was reached
   Thread.sleep(3000);

   while(true) {
       //
       // O método runSimulation( ) mantém no laço três etapas: 
       // (1) a preparação da criatura para a simulação: prepareAndSetupCreatureToSimulation(c, v); 
       // (2) a execução da simulação:  soarBridge.runSimulation(); 
       // (3) a execução dos comandos de resposta: processResponseCommands().
       // 
       // (1) : A preparação é feita em quatro etapas:  
       //     (a): cria o sensor visual; 
       //     (b): cria o sensor de combustível (energia); 
       //     (c): coloca os elementos na interface de saída do Soar para iniciar a simulação; 
       //     (d): configura o StackHolder para executar a simulação: 
       //          soarBridge.setupStackHolder(StackholderType.CREATURE, simulationCreature), esse  
       //          método cria uma nova entidade no kernel do Soar (vide sml.jar) - repassa para o 
       //          Soar os dados fornecidos pela simulação (do mundo WS3D). 
       // 
       // (2) : No SoarBridge() temos quatro etapas:
       //     (a): estabelece o nome do agente (agentName) e a localização do arquivo de regras;
       //     (b): cria o kernel do Soar (para uso do SML): Kernel.CreateKernelInThread() e um 
       //          agente: kernelCreateAgent(agentName);
       //     (c): carrega as produções: agent.LoadProduction();
       //     (d): ativa uma instância do SoarJavaDebugger: agent.SapwnDebugger(soarDebuggerPort).  
       // 
       // (3) : São três os comandos que resultam de command.getCommandType():
       //     (a): processMoveCommand() para o MOVE; 
       //     (b): processMoveCommand() para o GET;
       //     (c): processMoveCommand() para o EAT.   
       //  
       simulationTask.runSimulation();
       Thread.sleep(100);
   }
}
// Faz o tratamento da exceção
catch ( error in soar bridge ) { 
    . . .  

}
// Faz o tratamento da exceção
catch ( unknown error ) {
    . . . 

}

 

A seguir complementamos os comentários sobre  o SimulationSOAR apontando alguns detalhes sobre as classes SimulationTask  e  SoarBridge, relacionadas com a conexão Soar-WS3D.

 

2. Conexão com o WorldServer3D

Nesta seção exploramos os códigos do WorldServer3D (código-fonte [ aqui ]) e do WS3DProxy (código-fonte [ aqui ]).  O WS3DProxy é uma biblioteca de apoio, faz a conexão com o WS3D: envia e recebe comandos em objetos de alto nível. A Figura 2 mostra uma simulação, isto é, a ação do botSoar (visualizada no SoarJavaDebugger) no ambiente do WS3D. 

Figura 2. Simulação do DemoSoar

 

Antes de comentarmos sobre os pacotes e suas respectivas classes que implementam a simulação Soar-WS3D, apresentamos um diagrama sobre a dependência dos pacotes, Figura 3.

Figura 3. Estrutura dos pacotes que compõem a simulação

 

Iniciamos explicando como o WS3DProxy estabelece conexão com o WS3D.  O trecho de código abaixo contém o laço principal do SimulationSOAR, que é o núcleo do DemoSOAR, comentado anteriomente.

                          
# ----------------------------------------------------------------------------------------------
# Trecho com o laço principal do SimulationSOAR

public class SimulationSOAR {
    
    . . .  

   // Run Simulation until some criteria was reached
   Thread.sleep(3000);

   while(true) {
       simulationTask.runSimulation();
       Thread.sleep(100);         // Óbvio: o sleep retarda o início de cada iteração.
   }                           // Essa margem de tempo é para garantir a troca de informações
                               // com o Soar. 
    . . .  

}

                           

O método simulationTask.runSimulation() é chamado a cada ciclo de execução. A classe simulationTask é a responsável pelo estabelecimento da conexão entre o WS3D e o Soar. A conexão é feita usando a classe WS3DProxy via SocketUtility.createSocket(host,port), pelo método connect() - veja Figura 3. O diagrama abaixo, Figura 4, complementa o diagrama do pacote ws3dproxy da Figura 3. a O pacote ws3dproxy A Figura 4 mostra o diagrama das classes do pacote WS3DProxy (a partir da classe WS3DProxy).

Figura 4. Elementos que compões o pacote ws3dproxy.

OBS.: o diagrama foi gerado usando o plantuml-dependency e o plantuml. O primeiro gera a codificação do diagrama em puml (a partir do código Java). O segundo gera um diagrama (neste caso em formato png) a partir do código puml. Veja [ aqui ] o código puml gerado pelo plantuml-dependency para o digrama acima.

 

Uma vez que  WS3DProxy estabelece a conexão ficam disponíveis os métodos, vide Figura 3, das classes Creature e World, do pacote  ws3dproxy.model, que fornecem os elementos de controle do botSoar (inclusive a imagem capturada pela visão do botSoar - frustum) e da dinâmica da arena. Os métodos básicos da classe WS3DProxy que cuidam da interação Soar-WS3D são: createCreature() - que cria um botSoar no WS3D (retorna um elemento da classe Creature - vide Figura 4);  getCreature() - que referencia um botSoar (se erle existir no WS3D); setWorld() - que define o objeto da classe World (vide Figura 4) a ser utilizado pelo proxy; getWorld() - que obtém um objeto da classe World para a interação com o WS3D - isto é, retorna o ambiente conhecido pelo botSoar (também retorna a localização do DeliverySpot). Ou seja, a manipulação das criaturas e objetos no mundo é feita pelas classes Creature e World, vide Figura 4.

A conexão com o Soar (com o sml.jar: via Kernel e Agent) é estabelecida pelo SoarBridge, veja esquema na Figura 3. O diagrama da Figura 5 exibe os elementos que compõem o pacote SoarBridge (para aumentar o tamanho da figura, coloque o cursor do mouse sobre a figura e acione o botão direito: escolha a opção "Exibir Imagem"):

Figura 5.  Classes do pacote SoarBridge.

 

O método setupStackHolder(), da classe SoarBridge  - vide Figura 3, repassa para o Soar (manipula os elementos da WM do botSoar usando os métodos do sml.jar) os dados obtidos do WS3D (via getReceivedCommand()). Por exemplo, o createIdWME(<node-wme>, <name-new-node-wme>) vide Figura 3, cria um novo WME.  De fato, é criada uma estrutura tal como descrita na codificação abaixo (estrutura do botSoar):

                
# -----------------------------------------------------------------------------------------------
# Estrutura do botSoar 
# 
# A estrutura é montada no pacote SoarBridge (do DemoSoar). Apenas o COUNT é implementado 
# no Soar (codificado no soar-rules.soar). 
^io                                           ^io
   ^input-link                                   ^output-link
     ^CREATURE                                      ^MOVE
         ^PARAMETERS                                        ^Vel
            ^MINFUEL                                    ^VelR  
           ^TIMESTAMP                                  ^VelL
        ^SENSOR                                     ^EAT 
           ^FUEL                                       ^Name  
              ^VALUE                                     ^GET
           ^VISUAL                                     ^Name
             ^ENTITY                                   
               ^NAME                                   
               ^COLOR  White/Red/Yellow/Green/Magenta/Blue         
               ^DISTANCE  0.0 .. 1000.0  
               ^TYPE FOOD/JEWELL/BRICK
               ^X    0.0 .. 800.0   # (x, y) = (0.0, 0.0) fica no canto superior esquerdo.  
               ^Y     0.0 .. 600.0   # (x, y) = (800.0, 600.0) fica no canto inferior direito.
       ^MEMORY     
            ^COUNT     0.0 .. 7.0   # Esse elemento da estrutura é implementado no Soar.
           ^ENTITY                # Até sete elementos
             ^NAME 
             ^COLOR             
             ^DISTANCE   
             ^TYPE FOOD/JEWELL/BRICK
             ^X
             ^Y                     
        ^POSITION   
           ^X
           ^Y
                                          

 

Como já dissemos, todo controle da execução da simulação, incluindo a manutenção da conexão WS3D e Soar, é feito pela classe SimutaltionTask(), veja o esquema da Figura 3.  A Figura 6 mostra as classes do pacote Simulation (a partir do SimulationSOARSimulationTask): 

Figura 6.  As classes do pacote Simulation.

OBS.: novamente, os diagramas foram gerados usando o plantuml-dependency e o plantuml. Veja [ aqui ] o código puml gerado pelo plantuml-dependency para o digrama do SimulationTask().

 

Uma vez que a classe SimulationTask() acessa o Soar, via SoarBridge, temos a execução, entre um  Thread.sleep() e outro, do método  simulationTask.runSimulation() - veja o trecho de código abaixo e diagrama na Figura 3

     
/** ----------------------------------------------------------------------------------------
  * package Simulation; 
  *  
  * public class SimulationTask 
  *
  *   Logger logger = Logger.getLogger(SimulationTask.class.getName());
  *   WS3DProxy proxy = null;
  *   SoarBridge soarBridge = null;
  *   Creature c = null;
  *   World w = null;
  * 
  *   ....
  *  
  */
                
/** ----------------------------------------------------------------------------------------
  * Start Simulation Method 
  * @throws SoarBridgeException
  * @throws NullPointerException
  */

public void runSimulation() throws SoarBridgeException, NullPointerException, 
                                   IllegalAccessException, CommandExecException {

   if (soarBridge != null) {

       c.updateState();         

       //Status currentStatus = c.getState();
       List<Thing> v = c.getThingsInVision();
       logger.info("Objetos no Ambiente: "+v.size());
       System.out.println("Objetos no Ambiente: "+v.size());
       for (Thing t : v) {

           logger.info(t.toString());
       }
            
       if (c != null) {

          // Prepare Creature To Simulation
          prepareAndSetupCreatureToSimulation(c, v);

          // 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");
   }
}

                

 

  • c.updateState(): atualiza o estado do botSoar;
  • c.getThingsInVision(): armazena os objetos capturados pelo sensor de visão;

A Figura 7 mostra o WS3D com um boSoar na arena. A identificação do bot está na janela intitulada "Knapsack and Score - creature 0", de fato, o ID do bot é Creature_1370917673872. No terminal, parte inferior da figura, vemos o output do comando getcreaturestate.

Figura 7. Capturando o estado do botSoar.

Abaixo copiamos e editamos a saída com comando getcreaturestate:

      
# ----------------------------------------------------------------------------------------------
# output do comando getcreaturestate Creature_1370917673872 
# 
Creature_1370917673872 0 219.0 273.0 20.0  0.0   2     0.0  0.0 1000     Red  0  0  4 || 
Brick_1370917676657    1 0     797.0 822.0 70.0  209.0 -1.0 1.0 0.0  1.0 Magenta      || 
Jewel_1370917694416    3 0     475.0 475.0 262.0 262.0 -1.0 1.0 0.0  1.0 Green        || 
Jewel_1370917696975    3 0     543.0 543.0 237.0 237.0 -1.0 1.0 0.0  1.0 Green        || 
PFood_1370917700437   21 0     321.0 321.0 283.0 283.0 -1.0 1.0 1.0  1.0 Red
           
Os dados referentes a Creature_1370917673872 são:

<name>      <index>    <x>    <y>   <size>    <pitch>    <motor>    <whell speed>   <fuel> 
  0          219.0    273.0  20.0    0.0         2         0.0          0.0          1000   

<color>   <active leaflet>   <leaflet pool>   <visual sys>     
  Red             0                0              4 
        

 

  • prepareAndSetupCreatureToSimulation(c, v): parâmetros: o botSoar e uma lista de objetos. Esse método prepara as informações obtidas pelos sensores (visualSensor e fuelSensor, passados para o SimulationRobot via simulationCreature) para envio ao Soar. Isto é, prepara os outputs para para execução da simulação, feita pela classe simulationCreature do pacote Simulation. Note que simulationCreature é uma variável passada como parâmetro do método setupStackHolder() para a execução da simulação: setupStackHolder(StakeholderType.CREATURE, simulationCreature) - esse método da classe soarBridge cria uma nova entidade no Kernel do Soar: cria o botSoar, configura os parâmetros do botSoar, o coloca na arena, configura os sensores do bot - veja essa etapas no trcho de código abaixo (detalhes em vermelho): 
     
/** ----------------------------------------------------------------------------------------
  * package SoarBridge; 
  *  
  * public class SoarBridge
  *
  *   // Log Variable
  *   Logger logger = Logger.getLogger(SoarBridge.class.getName());
  * 
  *   // SML variables
  *   Kernel kernel = null;
  *   Agent agent = null;
  *   Identifier inputLink = null;
  *
  *   // Entity Variables
  *   Identifier creature;
  *   Identifier creatureSensor;
  *   Identifier creatureParameters;
  *   Identifier creaturePosition;
  *   Identifier creatureMemory;
  * 
  *   // Ordinary Variables
  *   String agentName;
  *   File productionPath;
  * 
  *   . . . 
  *  
  */
                
/** ----------------------------------------------------------------------------------------
  * Create New Entity in SOAR Kernel
  * @param categoryType
  * @param parameters
  * @throws SoarBridgeException
  */
public void setupStackHolder(StakeholderType entityType, SimulationCreature parameter) 
                            throws SoarBridgeException {
  try {

    if (agent != null) {

      switch (entityType) {

        case CREATURE:
          SimulationRobot creatureParameter = (SimulationRobot)parameter;

          // Create creature
          if (creature == null) {

            // Initialize Creature Ententy
            creature = agent.CreateIdWME(inputLink, "CREATURE");

            // Initialize Creature Memory
            creatureMemory = agent.CreateIdWME(creature, "MEMORY");
          }
          // Set Creature Parameters
          Calendar lCDateTime = Calendar.getInstance();
          creatureParameters = agent.CreateIdWME(creature, "PARAMETERS");
          agent.CreateFloatWME(creatureParameters, "MINFUEL", 400);
          agent.CreateFloatWME(creatureParameters, "TIMESTAMP", lCDateTime.getTimeInMillis());

          // Setting creature Position
          creaturePosition = agent.CreateIdWME(creature, "POSITION");
          agent.CreateFloatWME(creaturePosition, "X", creatureParameter.getPosition().getX());
          agent.CreateFloatWME(creaturePosition, "Y", creatureParameter.getPosition().getY());

          // Set creature sensors
          if (creatureParameter.getCountSensorsAvailable() > 0) {

             // Create Sensor Node
             creatureSensor = agent.CreateIdWME(creature, "SENSOR");

             // Get Sensor Enabled
             List<SensorType> enabledSensorTypeList = creatureParameter.getSensorsEnabled();

             // Check if we have active sensors in creature
             if (enabledSensorTypeList != null && enabledSensorTypeList.size() > 0) {

                 for ( . . . sensorTypeCount < enabledSensorTypeList.size() . . . ) {

                     // Check wich sensors are actived
                     switch (enabledSensorTypeList.get(sensorTypeCount)) {
                         case FUEL: { 
                                    . . .                                     
                         } break;

                         case VISUAL: { 
                                    . . . 
                         } break;
                      }
                  }
              }
          }
        break;
      }
      agent.Commit();
      checkForKernelOrAgentError();
    }
  }
  catch ( exceção ) {
       . . .
  }
}


 

Os itens em azul (escritos em maiúsculas e entre aspas duplas) são os elementos que compõe a estrutura do botSoar. O trecho  de código abaixo refere-se à parte em que os dados do WS3D são enviados para o botSoar, exemplificamos apenas a parte que trata do sensor visual. O método setupStackHolder() cria um sensor visual e WMEs que armazenam os elementos capturados pela visão. A classe Agent usa o pacote sml (em jar) para manipular a WM do botSoar (conforme as regras contidas em soar-rules.soar)

                
/** ----------------------------------------------------------------------------------------
  * public void setupStackHolder(StakeholderType entityType, SimulationCreature parameter) 
  */                          throws SoarBridgeException 

/** ----------------------------------------------------------------------------------------
  * ... continuação: referente ao trecho: 
  * 
  * // Check wich sensors are actived
  * switch (enabledSensorTypeList.get(sensorTypeCount)) 
  * . . . 
  */ 

case VISUAL: { 
   Identifier visual = agent.CreateIdWME(creatureSensor, "VISUAL");
   List<Thing> thingsList = 
               (List<Thing>)(creatureParameter.getSensorHandler(SensorType.VISUAL).getSensorReadings());

   if (thingsList != null) {
      for ( ... thingsInSensorCount < thingsList.size() ...) { 
          Identifier entity = agent.CreateIdWME(visual, "ENTITY");
          Thing t = thingsList.get(thingsInSensorCount);
          agent.CreateFloatWME(entity, "DISTANCE", GetGeometricDistanceToCreature(t.getX1(),
                               t.getY1(), t.getX2(), t.getY2(), creatureParameter.getPosition().getX(), 
                               creatureParameter.getPosition().getY()));           
          agent.CreateFloatWME(entity, "X", t.getX1());
          agent.CreateFloatWME(entity, "Y", t.getY1());
          agent.CreateFloatWME(entity, "X2", t.getX2());
          agent.CreateFloatWME(entity, "Y2", t.getY2());                            
          agent.CreateStringWME(entity, "TYPE", 
                                SimulationItem.getItemTypeFromThingCategory(t.category).toString());
          agent.CreateStringWME(entity, "NAME", t.getName());
          agent.CreateStringWME(entity, "COLOR", Constants.getColorName(t.getMaterial().getColor()));    
      }
   }
}
break;

. . . 
                 
  • soarBridge.runSimulation() dispara a simulação. Chama o updateState(), do pacote ws3dproxy.model, para a atualização no WS3D. Invoca o agent.RunSelfTilOutput(), do pacote sml (arquivo jar)  para executar a simulação (o botSoar).
  • processResponseCommands(): processa as respostas do botSoar (criadas no output-link - veja na estrutura do botSoar) e envia-os para o WS3D.

O processResponseCommands() chama o método getReceivedCommands(), da classe SoarBridge, e retorna uma lista de comandos do Soar: MOVE, GET e EAT (veja o output-link na estrutura do botSoar). Cada um dos comandos faz o botSoar agir conforme os argumentos recebidos: Os argumentos do MOVErightVelocity, leftVelocity, linearVelocity, xPosition e yPosition determinam como o bot irá se mover, o do GET: thingNameToGet - o que o botSoar irá capturar e o do EATthingNameToEat o que o bot irá comer. Como exemplos de atribuição de valores para os argumentos mostramos duas regras: o apply*wander e o apply*move*food, contidas no soar-rules.soar,  que aplicam o MOVE:  

                    
# -----------------------------------------------------------------------------------------
# Apply*wander: If the wander operator is selected, then generate an output command to it

sp {apply*wander
    (state <s> ^operator <o>   ^io <io>)
    (<io> ^output-link <ol>)
    (<o> ^name wander)
   -->
    (<ol> ^MOVE <command>)    # Mudança de direção: Vel = 0 e VelR =/= VelL 
    (<command> ^Vel 0)        # Mantém o botSoar na posição em que está  
    (<command> ^VelR 2)       # Aciona o motor da roda direita
    (<command> ^VelL 0)       # Mantém a roda esquerda travada
   }

# -----------------------------------------------------------------------------------------
# Apply*move*food: If the move operator is selected, then generate an output command to it

sp {apply*move*food
    (state <s> ^operator <o>   ^io <io>)
    (<io> ^input-link <il>)             
    (<io> ^output-link <ol>)
    (<o> ^name moveFood)
    (<o> ^parameter <food>)
    (<food> ^X <x>)       # Captura as coordenadas de <food>
    (<food> ^Y <y>)
    (<food> ^NAME <entityInMemoryName>)
    (<il> ^CREATURE <creature>)
    (<creature> ^MEMORY <memory>)
    (<memory> ^ENTITY <entityInMemory>)
    (<entityInMemory> ^NAME <entityInMemoryName>)
   -->
    (<ol> ^MOVE <command>) # Dada a direção estabelecida por (<x>, <y>)
    (<command> ^Vel 1)     # O bot se move, Vel =/= 0, na direção dada VelR == VelL
    (<command> ^VelR 1)    # As duas rodas mantém a mesma velocidade, evita o giro. 
    (<command> ^VelL 1)
    (<command> ^X <x>)
    (<command> ^Y <y>)
   }
   
    

 

Cada comando é executado por um método associado: o MOVE é feito pelo processMoveCommand() que chama um método da classe CommandUtility (do pacote WS3DProxy) para realizar o solicitado; o GET é executado pelo método processGetCommand() que chama o putInSack(), da classe Creature (também do WS3DProxy); e , finalmente, o EAT  é realizado pelo método processEatCommand() que chama o método eatIt(), da classe Creature. Isto é, o pacote WS3DProxy, como comentamos inicialmente, recebe e executa os comandos do Soar.     

A seguir, explicamos o funcionamento do botSoar, isto é, tratamos das regras que compõem o soar-rules.soar.

 

3. Funcionamento do BotSoar no WS3D

Estudo das regras Soar em soar-rules.soar.  O botSoar atualiza as informações capturadas pelo sensor visual a cada ciclo. O output abaixo é referente a uma simulação do DemoSoar no WS3D

                          
# ----------------------------------------------------------------------------------------------
# A execução do SoarJavaDebugger carrega 41 produções: 

Total: 41 productions sourced.

# A variável associada ao botSoar é C3, obtida pelo comando "print I2":  

(I2 ^CREATURE C3)

# O comando "print C3" gera o conteúdo do botSoar. 

(C3 ^MEMORY M1 ^PARAMETERS P4247 ^POSITION P4248 ^SENSOR S6344) 

# O comando "print M1" gera o conteúdo da memória do botSoar. 

(M1 ^COUNT 7 ^ENTITY M33 ^ENTITY M32 ^ENTITY M31 ^ENTITY M28 ^ENTITY M27 
             ^ENTITY M26 ^ENTITY M18)

# Nesse momento a memória contém sete elementos, por exemplo: 

(M33 ^COLOR Red  ^NAME PFood_1370477437151 ^TYPE FOOD  ^X 746.4233501049703 ^Y 45.49990077066391)

(M32 ^COLOR Blue ^NAME Jewel_1370477505870 ^TYPE JEWEL ^X 164.7644180947649 ^Y 473.6712503282833)

(M31 ^COLOR Red  ^NAME Jewel_1370477118200 ^TYPE JEWEL ^X 462.0983503786395 ^Y 124.0664657271997)

. . . 

(M18 ^COLOR Red  ^NAME PFood_1370477373540 ^TYPE FOOD ^X 289.9778765747484  ^Y 73.52236479863248)

# Observe o formato dos conteúdos. Por exemplo o formato dos nomes das jóias (ou alimentos) e dos 
# valores das coordenadas.   
# 
# Também podemos ver os conteúdos de ^PARAMETERS, ^POSITION e ^SENSOR.

#O conteúdo de ^PARAMETERS: 

(P4247 ^MINFUEL 400. ^TIMESTAMP 1370477670732.)

#O conteúdo de ^POSITION: 

(P4248 ^X 719.075155775217 ^Y 491.2564876257079)

#O conteúdo de ^SENSOR: 

(S6344 ^FUEL F8887 ^VISUAL V2124)

(F8887 ^VALUE 0.)

# Há dezoito elementos capturados pela percepção visual:   

(V2124 ^ENTITY E18693 ^ENTITY E18692 ^ENTITY E18691 ^ENTITY E18690 ^ENTITY E18689 
       ^ENTITY E18688 ^ENTITY E18687 ^ENTITY E18686 ^ENTITY E18685 ^ENTITY E18684 
       ^ENTITY E18683 ^ENTITY E18682 ^ENTITY E18681 ^ENTITY E18680 ^ENTITY E18679 
       ^ENTITY E18678 ^ENTITY E18677 ^ENTITY E18676)

# Eis alguns objetos capturados pela percepção visual do botSoar:  

(E18693 ^COLOR White ^DISTANCE 274.7962313242305 ^NAME Jewel_1370477570245 ^TYPE JEWEL 
        ^X 703.2430920763505 ^X2 703.2430920763505  
        ^Y 216.9167053452999 ^Y2 216.9167053452999)

(E18692 ^COLOR Red ^DISTANCE 273.2291563413393 ^NAME Jewel_1370477569953   ^TYPE JEWEL 
        ^X 792.0508508525725 ^X2 792.0508508525725  
        ^Y 227.9530059982283 ^Y2 227.9530059982283)
.....

(E18686 ^COLOR Green ^DISTANCE 353.4761307358673 ^NAME NPFood_1370477505578 ^TYPE FOOD 
        ^X 563.2464439537285 ^X2 563.2464439537285
        ^Y 173.9824972822561 ^Y2 173.9824972822561)
...

(E18680 ^COLOR Red ^DISTANCE 236.2901586238835 ^NAME PFood_1370477308746    ^TYPE FOOD 
        ^X 675.5013745990468 ^X2 675.5013745990468
        ^Y 259.0187509107389 ^Y2 259.0187509107389)
...

(E18676 ^COLOR Red ^DISTANCE 292.1797392017455 ^NAME PFood_1370477116116    ^TYPE FOOD 
        ^X 631.5003091307881 ^X2 631.5003091307881
        ^Y 212.5099237209347 ^Y2 212.5099237209347)

     

 

A seguir explicamos o programa Soar que controla o bot. Estão desativados os mecanismos referentes ao aprendizado e a memória episódica (sobre esses mecanismo veja a Aula 6).

                              
# -----------------------------------------------------------------------------------------
# Ativações 
 
watch 5      
learn --off
epmem --set learning off

    

 

Os operadores propostos se dividem em "de movimentação" (quatro operadores), "de captura de objetos" (dois operadores) e "de armazenamento na memória" (dois operadores).  

                
# -----------------------------------------------------------------------------------------
# De movimentação: 
# Podemos resumir esses operadores em: se nada há para fazer, então wander. 
# Caso contrário, propõe um movimento de acordo com o que é capturado pelo sensor visual.
          
# Propose*wander: This operator will make the agent to walk ahead at the enviroment

# Propose*move*food: This operator will make the agent go straight to the food

# Propose*move*jewel: This operator will make the agent go straight to the jewel

# Propose*avoid*brick: This operator will make the agent avoid the brick

# -----------------------------------------------------------------------------------------
# De captura: 
# São dois tipos de objetos: alimentos (recarregadores de energia) e jóias (pontuação) 

# Propose*eat*food: This operator will make the agent eat the food

# Propose*get*jewel: This operator will make the agent get the jewel

# -----------------------------------------------------------------------------------------
# De armazenamento na memória: 
# São duas as situações para o armazenamento na memória: com a memória "vazia" ou "não-vazia". 

# Propose*see*entity*with*memory*count: This operator will make the agent hold some entities 
# in memory if the agent already has  some entities in memory

# Propose*see*entity*without*memory*count: This operator will make the agent hold some 
# entities in memory if the agent has not some  entities in memory

 

As regras referentes à movimentação estão codificados abaixo (seguem o estilo do TankSoar - Aula 4). Observe que a distância é a euclidiana, diferente da distância definida no TankSoar (que adotava a distância de Manhattan).   

                              
# -----------------------------------------------------------------------------------------
# Propose*wander:
# This operator will make the agent to walk ahead at the enviroment
sp {propose*wander
    (state <s> ^attribute state   ^impasse no-change  ^superstate <ss>)
    (<ss> ^io.input-link <il>)
    (<ss> ^superstate nil)
    (<il> ^CREATURE <creature>)
    (<creature> ^SENSOR.VISUAL <visual>)
   -->
    (<ss> ^operator <o> +)
    (<o> ^name wander)
   }

# -----------------------------------------------------------------------------------------
# Propose*move*food:
# This operator will make the agent go straight to the food
sp {propose*move*food
    (state <s> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)
    (<creature> ^MEMORY <memory>)
    (<memory> ^ENTITY <entityInMemory>)
    (<creature> ^POSITION <creaturePosition>)
    (<creaturePosition> ^X <creaturePositionX>)
    (<creaturePosition> ^Y <creaturePositionY>)
    (<entityInMemory> ^TYPE FOOD)
    (<entityInMemory> ^X <entityInMemoryPositionX>)
    (<entityInMemory> ^Y <entityInMemoryPositionY>)
    (<entityInMemory> ^NAME <entityInMemoryName>)
    (<creature> ^PARAMETERS.MINFUEL <minFuel>)
   -->
    (<s> ^operator <o> +)
    (<o> ^name moveFood)
    (<o> ^parameter <food>)
    (<food> ^distance (sqrt (+ (* (- <creaturePositionX> <entityInMemoryPositionX>) 
                                  (- <creaturePositionX> <entityInMemoryPositionX>)) 
                               (* (- <creaturePositionY> <entityInMemoryPositionY>) 
                                  (- <creaturePositionY> <entityInMemoryPositionY>)))))
    (<food> ^X <entityInMemoryPositionX>)
    (<food> ^Y <entityInMemoryPositionY>)
    (<food> ^NAME <entityInMemoryName>)
    (<o> ^parameterFuel <minFuel>)
   }

# -----------------------------------------------------------------------------------------
# Propose*move*jewel:
# This operator will make the agent go straight to the jewel
sp {propose*move*jewel
    (state <s> ^io.input-link <il>)
    (<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> ^operator <o> +)
    (<o> ^name moveJewel)
    (<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>)
   }

# -----------------------------------------------------------------------------------------
# Propose*avoid*brick:
# This operator will make the agent avoid the brick
sp {propose*avoidBrick
    (state <s> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)
    (<creature> ^SENSOR.VISUAL.ENTITY <entity>)
    (<entity> ^TYPE BRICK)
    (<entity> ^DISTANCE <distance> <= 61)      
   -->
    (<s> ^operator <o> +)     
    (<o> ^name avoidBrick)
    (<o> ^parameter <distance>)
   }
                    

 

As regras relativas à captura (comer ou pegar) de objetos são dadas a seguir. Note que a proposta é feita se o objeto percebido está a uma distância inferior a trinta unidades de medida. 

                              
# -----------------------------------------------------------------------------------------
# Propose*get*jewel:
# This operator will make the agent get the 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>)
    (<creature> ^MEMORY.ENTITY.NAME <memoryItemName> <jewelName>)     
   -->
    (<s> ^operator <o> +)
    (<o> ^name getJewel)
    (<o> ^parameter <jewel>)
    (<jewel> ^NAME <jewelName>)
    (<jewel> ^DISTANCE <jewelDistance>)
   }

# -----------------------------------------------------------------------------------------
# Propose*eat*food:
# This operator will make the agent eat the food
sp {propose*eat*food
    (state <s> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)
    (<creature> ^SENSOR.VISUAL.ENTITY <entity>)
    (<entity> ^TYPE FOOD)
    (<entity> ^DISTANCE <foodDistance> < 30)
    (<entity> ^NAME <foodName>)
    (<creature> ^MEMORY.ENTITY.NAME <memoryItemName> <foodName>)     
   -->
    (<s> ^operator <o> +)
    (<o> ^name eatFood)
    (<o> ^parameter <food>)
    (<food> ^NAME <foodName>)
    (<food> ^DISTANCE <foodDistance>)
   }


 

São duas as regras que tratam do armazenamento na memória: a memória pode estar carregada com algum elemento ou pode estar "vazia".

             
# -----------------------------------------------------------------------------------------
# Propose*see*entity*with*memory*count:
# This operator will make the agent hold some entities in memory if the agent already has 
# some entities in memory
sp {propose*see*entity*with*memory*count
    (state <s> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)
    (<creature> ^SENSOR.VISUAL.ENTITY <entity>)     
    (<entity> ^TYPE <type> << JEWEL FOOD >>)   
    (<entity> ^COLOR <color>)
    (<entity> ^X <x>)
    (<entity> ^Y <y>)
    (<entity> ^NAME <name>)
    (<creature> ^MEMORY <memory>)   
    -(<memory> ^ENTITY.NAME <name>)
    (<memory> ^COUNT <quantity> < 7)
   -->
    (<s> ^operator <o> +)
    (<o> ^name seeEntityWithMemoryCount)
    (<o> ^parameterEntity <newEntity>)
    (<newEntity> ^Name <name>)
    (<newEntity> ^Type <type>)
    (<newEntity> ^X <x>)
    (<newEntity> ^Y <y>)
    (<newEntity> ^Color <color>)
   }
   
# -----------------------------------------------------------------------------------------
# Propose*see*entity*without*memory*count:
# This operator will make the agent hold some entities in memory if the agent has not some 
# entities in memory
sp {propose*see*entity*without*memory*count
    (state <s> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)
    (<creature> ^SENSOR.VISUAL.ENTITY <entity>)
    (<entity> ^TYPE <type> << JEWEL FOOD >>)
    (<entity> ^X <x>)
    (<entity> ^Y <y>)
    (<entity> ^NAME <name>)
    (<entity> ^COLOR <color>)
    (<creature> ^MEMORY <memory>)   
    -(<memory> ^ENTITY.NAME <name>)
    -(<memory> ^COUNT <quantity>)
   -->
    (<s> ^operator <o> +)
    (<o> ^name seeEntityWithoutMemoryCount)
    (<o> ^parameterEntity <newEntity>)
    (<newEntity> ^Name <name>)
    (<newEntity> ^Type <type>)
    (<newEntity> ^X <x>)
    (<newEntity> ^Y <y>)
    (<newEntity> ^Color <color>)
   }
   

 

As situações que envolvem tratamentos de conflitos e atribuições de preferências são as seguintes: 

                     
# -----------------------------------------------------------------------------------------
# Eat Food vs Move Food: prefere comer a mover-se (em direção à comida ou jóia) 

# Eat Food vs Avoid Brick: prefere comer a evitar a parede 

# Move Jewel vs Get Jewel: prefere pegar uma jóia a mover-se (em direção à comida ou jóia) 

# Get Jewel vs Avoid Brick: prefere pegar uma jóia a evitar a parede 

# Move Jewel or Move Food vs See Entity: prefere memorizar um objeto visto a mover-se 

# See Entity With Memory vs Avoid Brick: prefere evitar colisão a memorizar algo

# -----------------------------------------------------------------------------------------
# See Ententy Without Memory Preferences: preferência indiferente

# Move Jewel vs Move Jewel Preferences: prefere mover-se para a jóia mais próxima

# Get Jewel vs Get Jewel Preferences: prefere capturar a jóia mais próxima

# Move Food vs Move Food Preferences:  prefere mover-se para o alimento mais próximo

# Eat Food vs Eat Food Preferences: prefere comer o alimento mais próximo

# Avoid Brick vs Avoid Brick Preferences: prefere evitar a colisão com a parede mais próxima 

# Move Food vs Move Jewel Preferences: prefere o alimento à jóia, se estiver com baixa energia 
                   
# Move Food vs Move Jewel Preferences: prefere a jóia ao alimento, se a energia não está baixa 

# Avoid Brick vs Move Jewel vs Move Food Preferences with element in memory: preferere evitar 
# colisão com a parede a mover-se em direção a um objeto armazenado na memória 
      
# Wander Preferences: prefere evitar vagar pela arena 
      

 

Algumas regras são muito similares, por exemplo moveJewel*moveJewel*less*distance  e moveFood*moveFood*preferences ou getJewel*getJewel*preferences e eatFood*eatFood*preferences. Desse modo, para não sobrecarregar o texto, evitamos colocar os códigos de todas as regras (apenas uma ou outra para exemplificar). Para ver todas as regras consulte o arquivo soar-rules.soar.  

             
# -----------------------------------------------------------------------------------------
# Eat Food vs Move Food
sp {moveFood*eatFood*preferences
    (state <s> ^operator <o> +
                         <o2> +)
    (<o> ^name eatFood)
    (<o2> ^name << moveFood moveJewel >>)
   -->
    (<s> ^operator <o> > <o2>)
   }

# -----------------------------------------------------------------------------------------
# Eat Food vs Avoid Brick
sp {eatFood*avoidBrick*preferences
    (state <s> ^operator <o> +
                         <o2> +)
    (<o> ^name eatFood)
    (<o2> ^name avoidBrick)
   -->
    (<s> ^operator <o> > <o2>)
   }

# -----------------------------------------------------------------------------------------
# Move Jewel or Move Food vs See Entity
sp {moveJewel*seeEntity*preferences
    (state <s> ^operator <o> +
                         <o2> +)
    (<o> ^name << seeEntityWithMemoryCount seeEntityWithoutMemoryCount >>)
    (<o2> ^name << moveJewel moveFood >>)
   -->
    (<s> ^operator <o> > <o2>)
   }

# -----------------------------------------------------------------------------------------
# See Ententy Without Memory Preferences
sp {seeEntity*without*memory*preferences
    (state <s> ^operator <o> +)
    (<o> ^name << seeEntityWithMemoryCount seeEntityWithoutMemoryCount >>)
   -->
    (<s> ^operator <o> =)
   }

# -----------------------------------------------------------------------------------------
# Move Jewel vs Move Jewel Preferences
sp {moveJewel*moveJewel*less*distance
    (state <s> ^attribute operator
               ^impasse tie
               ^item <o> {<> <o> <o2>}
               ^superstate <ss>)
    (<ss> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)                 
    (<o> ^name moveJewel)
    (<o2> ^name moveJewel)
    (<o2> ^parameter.distance <distance2>)
    (<o>  ^parameter.distance <distance> <= <distance2>)
   -->
    (<ss> ^operator <o> > <o2>)
   }

# -----------------------------------------------------------------------------------------
# Move Food vs Move Jewel Preferences - Move Food Wins
sp {moveFood*moveJewel*preferences*moveFoodWins
    (state <s> ^attribute operator
               ^impasse tie
               ^item <o> {<> <o> <o2>}
               ^superstate <ss>)
    (<ss> ^io.input-link <il>)
    (<o> ^name moveFood)
    (<o> ^parameterFuel <threshold>)
    (<o2> ^name moveJewel)
    (<il> ^CREATURE.SENSOR.FUEL <fuel>)
    (<fuel> ^VALUE <value> <= <threshold>)        
   -->
    (<ss> ^operator <o> > <o2>)
   }
                   
# -----------------------------------------------------------------------------------------
# Avoid Brick vs Move Jewel vs Move Food Preferences with element in memory
sp {avoidBrick*moveJewel*moveFood*preferences
    (state <s> ^attribute operator
               ^impasse tie
               ^item <o> {<> <o> <o2>}
               ^item-count <itemCount> 2
               ^superstate <ss>)        
    (<o> ^name avoidBrick)
    (<o2> ^name << moveJewel moveFood >>)
    (<o2> ^parameter <entity>)
    (<entity> ^NAME <entityName>)
    (<ss> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)
    (<creature> ^MEMORY <memory>)
    (<memory> ^ENTITY <entityInMemory>)
    (<entityInMemory> ^NAME <entityName>)
   -->
    (<o> ^entityName <entityName>)
    (<ss> ^operator <o> > <o2>)
   }

# -----------------------------------------------------------------------------------------
# Wander Preferences
sp {wander*preferences
    (state <s> ^operator <o> +)
    (<o> ^name wander)
   -->
    (<s> ^operator <o> <)
   }      
                

 

As regras para a aplicação dos operadores estão associadas a três possibilidades de ação no ambiente WS3D: MOVE (para movimentação ou mudança de direção) GET (para capturar jóias) e EAT (para recarga de energia) ou para alteração do estado da memória: adição ou remoção de um elemento (jóia ou alimento) na memória.  Para exemplificar, colocamos algumas regras que aplicam o   MOVEGET  e EAT.  De fato, as regras seguem os esquemas já comentados nas aulas anteriores (2, 3 e 4):

                              
# -----------------------------------------------------------------------------------------
# Apply*wander:
# If the wander operator is selected, then generate an output command to it
sp {apply*wander
    (state <s> ^operator <o>   ^io <io>)
    (<io> ^output-link <ol>)
    (<o> ^name wander)
   -->
    (<ol> ^MOVE <command>)    # Mudança de direção: Vel = 0 e VelR =/= VelL 
    (<command> ^Vel 0)         
    (<command> ^VelR 2)
    (<command> ^VelL 0)
   }
    
# -----------------------------------------------------------------------------------------
# If the wander operator is selected, and there is a completed move command on the output 
# link, then remove that command.   
sp {apply*wander*remove*move
    (state <s> ^operator.name wander  ^io.output-link <out>)
    (<out> ^MOVE <move>)
    (<move> ^status complete)
   -->
    (<out> ^MOVE <move> -)  # Remove o operador executado
   }
                            
# -----------------------------------------------------------------------------------------
# Apply*see*entity*with*memory*count: 
# If the see operator is selected, then generate an output command to it
sp {apply*see*entity*with*memory*count
    (state <s> ^operator <o>  ^io <io>)
    (<io> ^input-link <il>)
    (<io> ^output-link <ol>)
    (<il> ^CREATURE <creature>)
    (<creature> ^MEMORY <memory>)
    (<memory> ^COUNT <quantity>)   
    (<o> ^name seeEntityWithMemoryCount)
    (<o> ^parameterEntity <newEntity>)
    (<newEntity> ^X <x>)            # Coordenadas do objeto capturado: <newEntity>
    (<newEntity> ^Y <y>)
    (<newEntity> ^Name <name>)
    (<newEntity> ^Type <type>)
    (<newEntity> ^Color <color>)
   -->
    (<memory> ^ENTITY <memoryEntity>) # Armazena um elemento na WM
    (<memoryEntity> ^X <x>)           # Coordenadas do objeto
    (<memoryEntity> ^Y <y>)
    (<memoryEntity> ^NAME <name>)      # Nome do objeto
    (<memoryEntity> ^TYPE <type>)      # Tipo do objeto
    (<memoryEntity> ^COLOR <color>)    # Cor do objeto
    (<memory> ^COUNT <quantity> -)     # Remove o ^COUNT para acrescentar novo valor
    (<memory> ^COUNT (+ 1 <quantity>)) # Acrescenta uma unidade à quantdd de objetos armazenados
   }
                                                 
# -----------------------------------------------------------------------------------------
# Apply*see*entity*without*memory*count:
# If the see operator is selected, then generate an output command to it
sp {apply*see*entity*without*memory*count
    (state <s> ^operator <o>   ^io <io>)
    (<io> ^input-link <il>)
    (<io> ^output-link <ol>)
    (<il> ^CREATURE <creature>)
    (<creature> ^MEMORY <memory>)  
    (<o> ^name seeEntityWithoutMemoryCount)
    (<o> ^parameterEntity <newEntity>)
    (<newEntity> ^X <x>)         # Captura as coordenadas de <newEntity>
    (<newEntity> ^Y <y>)
    (<newEntity> ^Name <name>)
    (<newEntity> ^Type <type>)
    (<newEntity> ^Color <color>)
   -->
    (<memory> ^ENTITY <memoryEntity>)
    (<memoryEntity> ^X <x>)         # Armazena as coordenadas do objeto capturado 
    (<memoryEntity> ^Y <y>)
    (<memoryEntity> ^NAME <name>)   # Nome do objeto
    (<memoryEntity> ^TYPE <type>)   # Tipo do objeto
    (<memoryEntity> ^COLOR <color>) # Cor do objeto
    (<memory> ^COUNT 1)             # Acrescenta uma unidade à quantdd de objetos armazenados
   }
                
                      
# -----------------------------------------------------------------------------------------
# Apply*move*food:  (OBS: a regra apply*move*jewell foi omitida por ser similar)
# If the move operator is selected, then generate an output command to it
sp {apply*move*food
    (state <s> ^operator <o>   ^io <io>)
    (<io> ^input-link <il>)             
    (<io> ^output-link <ol>)
    (<o> ^name moveFood)
    (<o> ^parameter <food>)
    (<food> ^X <x>)      # Captura as coordenadas de <food>
    (<food> ^Y <y>)
    (<food> ^NAME <entityInMemoryName>)
    (<il> ^CREATURE <creature>)
    (<creature> ^MEMORY <memory>)
    (<memory> ^ENTITY <entityInMemory>)
    (<entityInMemory> ^NAME <entityInMemoryName>)
   -->
    (<ol> ^MOVE <command>)
    (<command> ^Vel 1)     # Velocidade de locomoção
    (<command> ^VelR 1)    
    (<command> ^VelL 1)
    (<command> ^X <x>)     # Determina a diração do MOVE
    (<command> ^Y <y>)
   }
   
# -----------------------------------------------------------------------------------------
# Apply*moveFood*remove-move: (OBS: idem. A OBS vale para TODAS as regras get: jewell)
# If the moveFood operator is selected, and there is a completed move command on the output 
# link, then remove that command.   
sp {apply*moveFood*remove-move
    (state <s> ^operator.name moveFood   ^io.output-link <out>)
    (<out> ^MOVE <move>)
    (<move> ^status complete)
   -->
    (<out> ^MOVE <move> -)
   }   

# -----------------------------------------------------------------------------------------
# Remove the food from memory because it is not there anymore  
sp {apply*moveFood*remove*food  
    (state <s> ^operator <o>   ^io.input-link <il>)
    (<o> ^name moveFood)
    (<o> ^parameter <food>)
    (<food> ^X <x>)        # Captura as coordenadas de <food>
    (<food> ^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> -) # Remove elemento da memória
    (<memory> ^COUNT <quantity> -
              ^COUNT (- <quantity> 1))    # Subtrai uma unidade da quantdd de objetos armazenados
   }   
                          
# -----------------------------------------------------------------------------------------
# Apply*eat*food:  
# If the move operator is selected, then generate an output command to it
sp {apply*eat*food
    (state <s> ^operator <o>   ^io <io>)
    (<io> ^input-link <il>)      
    (<io> ^output-link <ol>)
    (<o> ^name eatFood)
    (<o> ^parameter.NAME <foodName>)
    (<il> ^CREATURE <creature>)
    (<creature> ^MEMORY <memory>)
    (<memory> ^COUNT <quantity>)  
    (<memory> ^ENTITY <memoryEntity>)
    (<memoryEntity> ^NAME <memoryEntityName> <foodName>)
   -->
    (<ol> ^EAT <command>)
    (<command> ^Name <foodName>)
    (<memory> ^COUNT <quantity> -
              ^COUNT (- <quantity> 1))  # Subtrai uma unidade da quantdd de objetos armazenados
    (<memory> ^ENTITY <memoryEntity> -) # Remove elemento da memória
   }
 
# -----------------------------------------------------------------------------------------
# Apply*eat*remove-move: # Correção: Apply*eat*remove-eat, analogamente, Apply*get*remove-get.
# If the eatFood operator is selected, and there is a completed eat command on the output 
# link, then remove that command.   
sp {apply*eatFood*remove-eat
    (state <s> ^operator <o>  ^io.output-link <out>)
    (<o> ^name eatFood)
    (<o> ^parameter.name <foodName>)
    (<out> ^EAT <eat>)
    (<eat> ^status complete)
   -->
    (<out> ^EAT <eat> -)
   }
                           
# -----------------------------------------------------------------------------------------
# Apply*avoidBrick:
# If the move operator is selected, then generate an output command to it
sp {apply*avoidBrick
    (state <s> ^operator <o>   ^io <io>)
    (<o> ^name avoidBrick)
    (<o> ^parameter <distance>)              
    (<io> ^output-link <ol>)
   -->
    (<ol> ^MOVE <command>)
    (<command> ^Vel  0)
    (<command> ^VelR (* 55 (/ <distance>)))
    (<command> ^VelL 0)
   }

# -----------------------------------------------------------------------------------------
#Remove the entity from memory
sp {apply*avoidBrick*remove*entity*memory
    (state <s> ^operator <o>
               ^io.input-link <il>)
    (<o> ^name avoidBrick)
    (<o> ^entityName <entityName>)
    (<il> ^CREATURE <creature>)
    (<creature> ^MEMORY <memory>)
    (<memory> ^COUNT <quantity>)
    (<memory> ^ENTITY <entityInMemory>)           
    (<entityInMemory> ^NAME <name> <entityName>)
   -->
    (<memory> ^ENTITY <entityInMemory> -)
    (<memory> ^COUNT <quantity> -
              ^COUNT (- <quantity> 1))
   }             
                      
# -----------------------------------------------------------------------------------------
# Apply*avoidBrick*remove-move:
# If the avoidBrick operator is selected, and there is a completed move command on the 
# output link, then remove that command.   
sp {apply*avoidBrick*remove-move
    (state <s> ^operator.name avoidBrick  ^io.output-link <out>)
    (<out> ^MOVE <move>)
    (<move> ^status complete)
   -->
    (<out> ^MOVE <move> -)
   }  
            

 

O estudo desta atividade serve como base para a implementação de um botSoar que deve cumprir uma meta estabelecida pelo leaflet.

 


Controle do BotSoar no WS3D

Todo bot ativado no WS3D possui um leaflet: uma meta na obtenção de jóias. O objetivo desta atividade é modificar os códigos do DemoSOAR e soar-rules.soar para que o comportamento do bot seja baseado no conteúdo do leaflet. Para desenvolver esta atividade seguimos o esquema do roteiro anterior. O experimento final será disponibilizado no formato JavaWebStart. Complementamos esta atividade colocando duas criaturas num mesmo ambiente, competindo para ver qual delas consegue completar seu leaflet.

 

Primeiros passos

Após algumas execuções da simulação (DemoSoar - WS3D) para análise do funcionamento das partes que compõem o experimento (algumas já explicadas no início destas notas), notamos alguns cuidados necessários para a nossa implementação de um botSoar cujo comportamento é orientado por leaflets. Na Figura 8, destacamos alguns eventos: um grande número de ocorrências do operador tie (detalhe em vermelho) indicando cuidado no caso de inclusão de novas regras; a não remoção de elementos do output-link (detalhe em laranja) indicando a necessidade de rever as regras de remoção; a aparente não funcionalidade do leaflet (indicamos pelo uso de comandos como o leaflet e deliver  - destaque em amarelo).

Figura 8. Execução do DemoSoar

 

Alguns desses ajustes são relativamente simples. Por exemplo, em relação às regras de remoção de elementos da estrutura output-link do botSoar associadas ao GET e ao EAT basta reescrever as regras da seguinte forma:

             
# -----------------------------------------------------------------------------------------
# Apply*eat*remove-eat:
# If the eatFood operator is selected, and there is a completed eat command on the output 
# link, then remove that command.   
sp {apply*eatFood*remove-eat
    (state <s> ^operator.name eatFood  ^io.output-link <out>)
    (<out> ^EAT <eat>)
    (<eat> ^status complete)
   -->
    (<out> ^EAT <eat> -)
   }

# -----------------------------------------------------------------------------------------
# Apply*get*remove-get:
# If the getJewel operator is selected, and there is a completed get command on the 
# output link, then remove that command.   
sp {apply*getJewel*remove-get
    (state <s> ^operator.name getJewel  ^io.output-link <out>)
    (<out> ^GET <get>)
    (<get> ^status complete)
   -->
    (<out> ^GET <get> -)
   }


 

Criamos uma versão "enxuta" do soar-rules.soar como ponto de partida, disponível [ aqui ]. Outros ajustes exigem mais. Por exemplo para tratar o número excessivo de ocorrências do operador tie (isto ocorre quando dois operadores são propostos como "acceptable", porém nenhum é melhor que outro: o problema é definir uma escolha entre esses operadores) não basta adicionamos regras de preferências mencionadas anteriormente. Um exemplo: a preferência estabelecida pela regra abaixo não consta no conjunto de regras do  soar-rules.soar.

               
# -----------------------------------------------------------------------------------------
# Get Jewel or Eat Food vs See Entity
sp {getJewel*eatFood*seeEntity*preferences
    (state <s> ^operator <o> +
                         <o2> +)
    (<o> ^name << seeEntityWithMemoryCount seeEntityWithoutMemoryCount >>)
    (<o2> ^name << getJewel eatFood >>)
   -->
    (<s> ^operator <o2> > <o>)
   }

 

A simples inclusão desta regra não soluciona as inúmeras ocorrências do operador tie, apenas preenche uma lacuna na relação de preferências entre os operadores em questão. Esses e outros problemas devem ser tratados para o correto funcionamento da implementação solicitada. Para tanto, estabelecemos as seguintes etapas: 

  • O leaflet (ou o conjunto de leaflets) é o elemento que orienta o comportamento do bot, logo o seu tratamento dever estar internalizado na estrutura do botSoar. Desse modo, deve haver um conjunto de regras que tratam dos leaflets, isto é, que definem o comportamento do botSoar.

Figura 9. Leaflets

 

 

 

 

 

 

 

 

 

 

  • Os leaflets são criados pelo WS3D, de forma randômica, atribuindo aos bots a quantidade de jóias (separadas por suas cores) a ser coletada e entregue num delivery spot (ponto de entrega). No DemoSoar os leaflets são atribuídos aleatoriamente para cada bot no ambiente (de fato, há um único conjunto de leaflets para todos os bots). Para esta implementação, o bot deve se orientar para atingir a meta definida pelos leaflets. A Figura 9 exibe a janela "Knapsack and Score" que mostra os leaflets (cada coluna representa um leaflet) e o score atribuídos para o bot 0. No caso da figura, o bot 0 tem três leaflets que determinam as seguintes entregas (1) duas jóias vermelhas e uma jóia azul; (2) uma vermelha, uma verde e uma amarela; (3) uma jóia vermelha, uma azul e uma magenta. A coluna Knapsack (detalhe em amarelo) mostra a quantidade de jóias, separadas por cor, capturadas pelo botSoar. Essa parte do processo de comunicação entre o Soar e o WS3D é mantido pelo pacote SoarBridge, mais especificamente, pelo método setupStackHolder(). Para cumprir essa atividade será preciso uma adequação do método setupStackHolder(). Evidentemente, devemos adequar os códigos das classes diretamente associadas a essa modificação.

Essas duas etapas, apesar do tratamento em seções separadas (as suas seções seguintes) são interdependentes.

 

Definindo o Comportamento do botSoar (Codificando o DemoSoar)

Para a internalização do leaflet na estrutura do botSoar e a definição do conjunto de regras que definem o comportamento do bot estabelecemos o seguinte roteiro:

  • Definir os novos elementos na estrutura do botSoar: no input-link e output-link
  • Declaração das regras de:
    • memorização (decorrente da percepção) das jóias de acordo com os leaflets.
    • movimentação em relação à localização das jóias associadas aos leaflets.
    • controle sobre a quantidade de jóias capturadas e ainda por capturar (conforme os leaflets).
    • movimentação para entrega no delivery spot.
    • desativação do botSoar.
  • Revisão das preferências das regras propostas

 

A Estrutura do BotSoar

Abaixo exibimos a estrutura do botSoar, é um extensão da estrutura apresentada anteriormente..

                
# -----------------------------------------------------------------------------------------------------
# Estrutura do botSoar com leaflets

^io                                                  ^io
   ^input-link                                          ^output-link
     ^CREATURE                                             ^MOVE
         ^PARAMETERS                                               ^Vel
            ^MINFUEL                                           ^VelR  
           ^TIMESTAMP                                        ^VelL
        ^SENSOR                                            ^EAT 
           ^FUEL                                             ^Name  
              ^VALUE                                            ^GET
           ^VISUAL                                           ^Name
             ^ENTITY                                       ^STOP   # Ainda NÃO implementado 
               ^NAME                                               # Simulado por MOVE: 0, 0, 0
               ^COLOR  White/Red/Yellow/Green/Magenta/Blue         
               ^DISTANCE  0.0 .. 1000.0  
               ^TYPE FOOD/JEWELL/BRICK/    # OBS.: o delivery spot não é percebido pelo bot 
               ^X    0.0 .. 800.0   
               ^Y     0.0 .. 600.0   
       ^MEMORY     
            ^COUNT      0.0 .. 7.0      # Codificado no Soar (no soar-rules.soar).
           ^ENTITY                   # Até sete elementos
             ^NAME 
             ^COLOR             
             ^DISTANCE   
             ^TYPE FOOD/JEWELL/BRICK
             ^X
             ^Y                     
       ^POSITION   
             ^X
             ^Y
       ^LEAFLETS
             ^LEAFLETSTODO  0 .. 3 # Qtdd de leaflets a cumprir codificado no soar-rules.soar.
             ^LEAFLET                                     # Um leaflet (são três leaflets).
               ^LEAFLETID                                 # Leaflet identification 
               ^LEAFLETSTATUS 0 = Incompleto / 1 = Completo # Status do leaflet, codificado no Soar 
               ^LEAFLETSCORE  0.0 ..                      # Score do leaflet. 
               ^LEAFLETJEWEL                              # Um conteúdo do leaflet 
                 ^COLOR     White/Red/Yellow/Green/Magenta/Blue  # Cor da jóia do leaflet.
                 ^QTTGOAL   0 .. 3  # Qtdd de jóias da cor COLOR a capturar
                 ^QTTINSACK 0 .. 3  # Qtdd de jóias da cor COLOR capturadas, codificado no Soar
       ^DELIVERYSPOT          # Ponto de entrega: com coordenadas: ^X e ^Y.
             ^X  0.0          # O delivery spot não aparece como objeto perceptivo ao bot,
             ^Y  0.0          # apesar da sua "representação sólida" na arena. 
                
          
                                          

 

Conjunto de Regras Sobre Leaflets

Para a escrita das regras estamos considerando que os métodos referentes a comunicação entre WS3D e Soar atualizam os campos da estrutura acima. Logo, só precisamos nos preocupar em ler os campos do input-link e escrever nos campos do output-link

Antes de iniciarmos a escrita das regras colocamos o esquema do ciclo de atividade do botSoar, de acordo com a estrutura acima. Suponha que o WS3D atribua ao BotSoar o seguinte conjunto (esquema figurativo abaixo) de leaflets:  

             
# --------------------------------------------------------------------------------------------------------------
# Exemplo retirado do help do WS3D
^LEAFLETS
  ^LEAFLETSTODO  3   # Incialmente são três leaflets a cumprir 
  ^LEAFLET                          ^LEAFLET                             ^LEAFLET 
      ^LEAFLETID  1317471651            ^LEAFLETID  1317471652                ^LEAFLETID  1317471653 
      ^LEAFLETSTATUS 0                  ^LEAFLETSTATUS 0                      ^LEAFLETSTATUS 0 
      ^LEAFLETSCORE 6                   ^LEAFLETSCORE 5                       ^LEAFLETSCORE 24 
      ^LEAFLETJEWEL                     ^LEAFLETJEWEL                         ^LEAFLETJEWEL 
        ^COLOR  White    ^COLOR Yellow     ^COLOR  White    ^COLOR Magenta      ^COLOR  Blue       ^COLOR Red      ^COLOR Green
        ^QTTGOAL   2     ^QTTGOAL   1      ^QTTGOAL   1     ^QTTGOAL   2        ^QTTGOAL   1       ^QTTGOAL   1    ^QTTGOAL   1
        ^QTTINSACK 0     ^QTTINSACK 0      ^QTTINSACK 0     ^QTTINSACK 0        ^QTTINSACK 0       ^QTTINSACK 0    ^QTTINSACK 0
^DELIVERYSPOT          
  ^X  0.0          
  ^Y  0.0           
                
          
   

 

A atividade de coleta do botSoar começa com a inicialização dos leaflets e é mantido enquanto houver leaflets incompletos, isto é,  ^LEAFLETSTODO diferente de ZERO. Cada objeto (em particular, uma jóia) capturado pelo sensor visual é armazenado na memória (até sete objetos no máximo). Após o armazenamento, podemos verificar se uma dada jóia armazenada na memória pertence ou não a um leaflet (a um dos tês leaflets). Para verificar a pertinência testamos a igualdade entre ^MEMORY.ENTITY.COLOR <color> ^LEAFLETS.LEAFLET.LEAFLETJEWEL.COLOR <lcolor>, desde que ^MEMORY.ENTITY.TYPE JEWEL. Se pertencer a um leaflet, o bot mantém a localização da jóia para poder capturá-la (em algum momento), caso contrário ele a remove da memória. Se houver uma jóia de um leaflet que não consta na memória, então o botSoar deve vagar (wander) para encontrar tal jóia. A entrega de um leaflet ao delivery spot, só é possível se ela estiver completa, isto é, se num leaflet: ^LEAFLET <lflt>, para todas as cores (jóias) <lfllt> ^LAFLETJEWEL.COLOR <color>, tivermos <color> ^QTTGOAL 0. A entrega de um leaflet resulta na diminuição de uma unidade no ^LEAFLETSTODO. Se ^LEAFLETSTODO igual a ZERO, então o botSoar pode ser desativado (condição para desativar o bot^LEAFLETSTODO 0). 

Com o esquema acima podemos elaborar um conjunto de regras que trata da (1) memorização das jóias de acordo com os leaflets; (2) movimentação em relação à localização das jóias associadas aos leaflets; (3) controle sobre a quantidade de jóias capturadas e ainda por capturar (conforme os leaflets); (4) movimentação para entrega no delivery spot; (5) desativação do botSoar.

Iniciamos com as regras que tratam da percepção e memorização das jóias de acordo com os leaflets. A idéia não é inventar (ou re-inventar) regras, mas utilizar as que ja existem no soar-rules.soar (na versão "enxuta") do DemoSoar.  As regras codificadas abaixo cuidam para que as jóias armazenadas na memória (que foram captadas pela percepção visual) sejam da mesma cor das jóias leaflets. De fato, uma vez estabelecida a estrutura do botSoar  (vide exemplo acima),  não há muito o que fazer: modificamos as regras propose*see*entity*with*memory*count e propose*see*entity*without*memory*count :

                         
# -----------------------------------------------------------------------------------------
# Propose*see*entity*with*memory*count:
# This operator will make the agent hold some entities in memory if the agent already has 
# some entities in memory
sp {propose*see*entity*with*memory*count
    (state <s> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)
    (<creature> ^SENSOR.VISUAL.ENTITY <entity>)     
    (<entity> ^TYPE <type> << JEWEL FOOD >>)   
    (<entity> ^COLOR <color>)
    (<entity> ^X <x>)
    (<entity> ^Y <y>)
    (<entity> ^NAME <name>)
    (<creature> ^MEMORY <memory>)   
    -(<memory> ^ENTITY.NAME <name>)
    (<memory> ^COUNT <quantity> < 7)
    (<creature> ^LEAFLETS.LEAFLET.LEAFLETJEWEL.COLOR <color>) # Matching: cor da jóia num dos leaflets
   -->
    (<s> ^operator <o> +)
    (<o> ^name seeEntityWithMemoryCount)
    (<o> ^parameterEntity <newEntity>)
    (<newEntity> ^Name <name>)
    (<newEntity> ^Type <type>)
    (<newEntity> ^X <x>)
    (<newEntity> ^Y <y>)
    (<newEntity> ^Color <color>)
   }
   


OBS.: A modificação na regra propose*see*entity*without*memory*count é a mesma.

                                                   
                    

 

Com as regras acima, somente as jóias pertencentes aos leaflets e que ainda não foram capturadas ficam na memória do botSoar. Eventualmente, se o botSoar necessitar de energia a memória armazena também alimentos (FOOD) para a captura.

A versão original soar-rules.soar, disponibilizada no DemoSoar, possui regras adequadas para gerenciar a movimentação, no entanto as regras de captura das jóias precisam ser adequadas aos leaflets. Isto é, precisamos cuidar do controle sobre a quantidade de jóias capturadas e ainda por capturar (conforme os leaflets). Utilizamos o campo ^LEAFLET.COLOR.QTTINSACK, para atualizar um leaftlet, cada jóia da cor COLOR capturada acrescentamos uma unidade em ^LEAFLET.COLOR.QTTINSACK. A captura de jóias de uma dada cor cessa para um leaflet, se ^LEAFLET.COLOR.QTTINSACK é igual a ^LEAFLET.COLOR.QTTGOAL. Se todas as jóias de um leaflet foram capturadas, então ^LEAFLETSTATUS 1 e subtrai este valor de ^LEAFLETSTODO. A forma de escolha sobre qual leaflet atualizar é randômica (uma opção seria: escolher o leaflet de maior pontuação - NÃO implementamos essa variante). Também é necessário verificar se devemos atualizar ^LEAFLETSTODO ou não.

                  
# -----------------------------------------------------------------------------------------
# Modificação do getJewel 
# Propose*get*jewel: this operator will make the agent get the jewel
sp {propose*get*jewel*no*qttinsack
    (state <s> ^io.input-link.CREATURE <creature>)
    (<creature> ^SENSOR.VISUAL.ENTITY <entity>)
    (<entity> ^TYPE JEWEL)
    (<entity> ^COLOR <jewelColor>)
    (<entity> ^DISTANCE <jewelDistance> < 30)
    (<entity> ^NAME <jewelName>)
    (<creature> ^MEMORY.ENTITY.NAME <entityName>  <jewelName>)
    (<creature> ^LEAFLETS.LEAFLET <leaflet>)
    (<leaflet> ^LEAFLETID <leafletId>)         # Identifica o leaflet com a cor da jóia
    (<leaflet> ^LEAFLETJEWEL <leafletJewel>)
    (<leafletJewel> ^COLOR <jewelColor>)       # O matching com a cor da jóia.     
    -(<leafletJewel> ^QTTINSACK <qttInSack>)   # Contador das jóias capturadas
   -->
    (<s> ^operator <o> + =)                    
    (<o> ^name getJewelNoQttinsack)
    (<o> ^parameter <jewel>)
    (<jewel> ^NAME <jewelName>)
    (<jewel> ^COLOR <jewelColor>)
    (<jewel> ^LEAFLETID <leafletId>)          # Identifica o leaflet para a aplicação da regra
    (<jewel> ^DISTANCE <jewelDistance>)
   }
 
# -----------------------------------------------------------------------------------------
# Apply*get*jewel: if the move operator is selected, then generate an output 
# command to it
sp {apply*get*jewel*no*qttinsack
    (state <s> ^io.input-link.CREATURE <creature>
               ^io.output-link <out>
               ^operator <o>)
    (<o> ^name getJewelNoQttinsack)
    (<o> ^parameter.NAME <jewelName>)
    (<o> ^parameter.COLOR <jewelColor>)
    (<o> ^parameter.LEAFLETID <leafletId>)  # Identifica o leaflet para a aplicação da regra 
    (<creature> ^MEMORY <memory>)
    (<memory> ^COUNT <quantity>)  
    (<memory> ^ENTITY <memoryEntity>)
    (<memoryEntity> ^NAME <entityName>  <jewelName>)
    (<creature> ^LEAFLETS.LEAFLET <leaflet>)
    (<leaflet> ^LEAFLETID <leafletId>)
    (<leaflet> ^LEAFLETJEWEL <leafletJewel>)
    (<leafletJewel> ^COLOR <jewelColor>)
  -->
    (<out> ^GET <command>)
    (<command> ^Name <jewelName>)
    (<memory> ^COUNT <quantity> -
              ^COUNT (- <quantity> 1))
    (<memory> ^ENTITY <memoryEntity> -)
    (<leafletJewel> ^QTTINSACK 1)          # Inicializa o contador de jóias capturadas
   }

# -----------------------------------------------------------------------------------------
# Propose*get*jewel: this operator will make the agent get the jewel
sp {propose*get*jewel*with*qttinsack
    (state <s> ^io.input-link.CREATURE <creature>)
    (<creature> ^SENSOR.VISUAL.ENTITY <entity>)
    (<entity> ^TYPE JEWEL)
    (<entity> ^COLOR <jewelColor>)
    (<entity> ^DISTANCE <jewelDistance> < 30)
    (<entity> ^NAME <jewelName>)
    (<creature> ^MEMORY.ENTITY.NAME <entityName>  <jewelName>)
    (<creature> ^LEAFLETS.LEAFLET <leaflet>)
    (<leaflet> ^LEAFLETID <leafletId>)
    (<leaflet> ^LEAFLETJEWEL <leafletJewel>)
    (<leafletJewel> ^COLOR <jewelColor>)
    (<leafletJewel> ^QTTGOAL <qttGoal>)                 # A quantidade de jóias a ser capturada   
    (<leafletJewel> ^QTTINSACK <qttInSack> < <qttGoal>) # Captura se não ultrapassou a meta 
   -->
    (<s> ^operator <o> +)
    (<o> ^name getJewelWithQttinsack)
    (<o> ^parameter <jewel>)
    (<jewel> ^NAME <jewelName>)
    (<jewel> ^COLOR <jewelColor>)
    (<jewel> ^LEAFLETID <leafletId>)
    (<jewel> ^DISTANCE <jewelDistance>)
   }

# -----------------------------------------------------------------------------------------
# Apply*get*jewel: if the move operator is selected, then generate an output
# command to it
sp {apply*get*jewel*with*qttinsack
    (state <s> ^io.input-link.CREATURE <creature>
               ^io.output-link <out>
               ^operator <o> +)
    (<o> ^name getJewelWithQttinsack)
    (<o> ^parameter.NAME <jewelName>)
    (<o> ^parameter.COLOR <jewelColor>)
    (<o> ^parameter.LEAFLETID <leafletId>)
    (<creature> ^MEMORY <memory>)
    (<memory> ^COUNT <quantity>)  
    (<memory> ^ENTITY <memoryEntity>)
    (<memoryEntity> ^NAME <entityName>  <jewelName>)
    (<creature> ^LEAFLETS.LEAFLET <leaflet>)
    (<leaflet> ^LEAFLETID <leafletId>)
    (<leaflet> ^LEAFLETJEWEL <leafletJewel>)
    (<leafletJewel> ^COLOR <jewelColor>)
    (<leafletJewel> ^QTTINSACK <qttInSack>)
   -->
    (<out> ^GET <command>)
    (<command> ^Name <jewelName>)
    (<memory> ^COUNT <quantity> -
              ^COUNT (- <quantity> 1))
    (<memory> ^ENTITY <memoryEntity> -)
    (<leafletJewel> ^QTTINSACK <qttInSack> -           # Atualização do contador
                               (+ <qttInSack> 1))
   }

 }
                            
                                       

 

OBS.: Inicialmente estabelecemos um critério de não capturar as jóias excedentes (se um dado leaflet já estiver completo). Esse tipo de comportamento do bot foi pensado na simulação com dois bots (para não prejudicar (quando for o caso) a ação do outro bot). No entanto, o tempo de resposta do botSoar não favoreceu a implementação de ações para o "desvio de objetos" de forma adequada. Desse modo, o bot captura jóias excedentes, porém sem computar no leaflet.  A Figura 10 mostra o botSoar capturando as jóias, à esquerda o painel do SJD exibe a estrutura dos leaflets.

Figura 10. Simulação do botSoar coletando as jóias de acordo com os leaflets

 

 

 

 

 

 

 

 

 

 

 

 

 

As outras modificações seguem a mesma estrutura. A finalização, isto é, o controle de movimentação para entrega dos leaflets deveria ser relativamente simples, no entanto a estrutura sólida do delivery spot é aparente, não há tratamento de detecção de colisão. O bot "entra" no sólido (aliás, como já comentamos, o delivery spot não é capturado pelo sistema de visão do bot). Podemos supor que há "algo" (um delivery spot) na posição de coordenadas  (0, 0), no entanto optamos simplesmente por desativar o bot assim que  ^LEAFLETSTODO 0. Desse modo, temos as seguintes regras: 

  • atualização do ^LEAFLETSTODO
    • se um leaflet está completo, então
      • subtraímos uma unidade do ^LEAFLETSTODO e modificamos o status do leaflet.   
  • desativação do botSoar
    • se ^LEAFLETSTODO 0,  então desativa o botSoar (envia um Stop)
                            
# -----------------------------------------------------------------------------------------
# If there is a complete leaflet and status incomplete, then subtract one from LEAFLETSTODO 
sp {elaboratate*update*leafletstodo
    (state <s> ^io.input-link.CREATURE.LEAFLETS <leaflets>)
    (<leaflets> ^LEAFLET.LEAFLETSTATUS 1)
    -(<leaflets> ^LEAFLET.QTTGOAL <qttgoal> {<> <qttgoal> 0})
   -->
    (<leaflets> ^LEAFLET.LEAFLETSTATUS 0)
    (<leaflets> ^LEAFLETSTODO <todo> -
                ^LEAFLETSTODO (- <todo> 1))
   }

# -----------------------------------------------------------------------------------------
# If there is a complete leaflet and status incomplete, then subtract one from LEAFLETSTODO 
sp {elaboratate*update*leafletstodo
    (state <s> ^io.input-link.CREATURE.LEAFLETS.LEAFLETSTODO 0
               ^io.output-link <out>)
   -->
    (<out> ^MOVE <command>)
    (<command> ^Vel  0)
    (<command> ^VelR 0)
    (<command> ^VelL 0)
   }

                
 
Preferência da Nova Regra

A regra abaixo trata da preferência da nova regra inclusa.  

                    
# -----------------------------------------------------------------------------------------
# Remove Non-Leflet Jewel From Memory vs Move Jewel 
sp {check*leaflet*jewel*in*memory*moveJewel
    (state <s> ^attribute operator
               ^impasse tie
               ^item <o> {<> <o> <o2>}
               ^superstate <ss>)
    (<ss> ^io.input-link <il>)
    (<il> ^CREATURE <creature>)                 
    (<o> ^name removeNonLeafletJewelFromMemory)
    (<o2> ^name moveJewel)
   -->
    (<ss> ^operator <o> > <o2>)
   }             
         
      

 

Com isso finalizamos o novo conjunto de regras que regulam o comportamento do botSoar (disponível [ aqui ]). 

 

Adequações no Código do WorldServer3D

Nesta parte tratamos das adequações necessárias para executar a simulação conforme especificado no início desta seção. Como mencionamos, a parte do processo de comunicação entre o Soar e o WS3D é mantido pelo pacote SoarBridge, mais especificamente, pelo método setupStackHolder().

Anter de efetuar as modificações do método setupStackHolder() e da classe SoarBridge (que já foi explorada anteriormente, veja [ aqui ]), devemos averiguar o conteúdo do DemoSoar em relação aos leaflets (isso inclui os pacotes WS3D e WS3DProxy). Começamos com a classe Leaflet.java do pacote ws3dproxy.model (que possui o cabeçalho abaixo):  

                
/** ----------------------------------------------------------------------------------------
 *
 * package ws3dproxy.model;
 * 
 * import java.util.HashMap;   // Estrutura básica dos leaflets
 * import java.util.Iterator;
 *
 */ 
 

 

Abaixo exibimos parte do código da classe Leaflet, apenas para mostrar as ferramentas básicas que foram utilizadas para a construção dos leaflets. Evidentemente, devemos utilizar a classe Leaflet.java para elaborar as modificações no setupStackHolder().

                       
/** ----------------------------------------------------------------------------------------
public class Leaflet {

    private Long ID;

    //number of points is gained by a creature when it is delivered
    private int payment = 0; 

    //Type (i.e. color name), (Total number, Collected number)
    private HashMap<String, Integer[]> itemsMap = new HashMap<String, Integer[]>();

    public Leaflet(Long ID, HashMap items, int payment) {
        this.ID = ID;
        setItems(items);
        this.payment = payment;
   }

    public HashMap<String, Integer[]> getItems() {

        return itemsMap;
    }

    public void setItems(HashMap<String, Integer[]> items) {

        for (Iterator<String> iter = items.keySet().iterator(); iter.hasNext();) {
            String str = iter.next();                     //jewel color
            Integer[]values = (Integer[]) items.get(str); //(total number) (collected)

            itemsMap.put(str, values);
        }
    }

    public int getPayment() {

        return payment;
    }

    . . . 

    public HashMap getWhatToCollect() {

        HashMap<String, Integer> itemsToSearch = new HashMap<String, Integer>();
        for (Iterator iter = itemsMap.keySet().iterator(); iter.hasNext();) {

            String type = (String)iter.next();
            itemsToSearch.put(type, getMissingNumberOfType(type));
        }
        return itemsToSearch;
    }
    . . . 

}
          
                

 

Seguindo o diagrama de dependência dos pacotes, Figura 3, devemos avaliar o pacote Simulation. De fato, podemos nos restringir a duas classes: SimulationRobotSimulationTask. A primeira estabelece o leaflet com uma lista e a segunda contém o método prepareAndSetupCreatureToSimulation() que prepara o botSoar para a simulação; comentamos sobre esse método anteriormente, por exemplo, [ aqui ]. Abaixo exibimos um trecho da classe SimulationRobot, colocamos apenas a parte que faz o tratamento dos leaflets

                
/** ----------------------------------------------------------------------------------------
 * package Simulation;
 * 
 * import java.util.Vector;
 * import ws3dproxy.model.Creature;
 * import ws3dproxy.model.Leaflet;
 * import ws3dproxy.model.WorldPoint;
 */

public class SimulationRobot extends SimulationCreature {

    // Leaflet List                        // Já definido no código original e lembrando que 
    private Vector<Leaflet> leafletList;   // um leaflet está amarrado ao java.util.HashMap.            
    
    // Default Constructor
    public SimulationRobot() {
        super(StakeholderType.CREATURE, new WorldPoint());
    }
    
    /**
     * Constructor
     * @param _position World Point Position
     */
    public SimulationRobot(WorldPoint _position) {
        super(StakeholderType.CREATURE, _position);
    }

    public SimulationRobot (Creature creature) {
        super(StakeholderType.CREATURE, creature.getPosition());
    }
        
    // ----------------------------------------------------------
    // Tratamento dos leaflets 
    // ----------------------------------------------------------
    public void setLeafletList(Vector<Leaflet> newLeafletList) {   
        leafletList = newLeafletList;
    }
        
    public Vector<Leaflet> getLeafletList() {
        return leafletList;
    }
}
              
 

Também já comentamos sobre a classe SimulationTask (por exemplo, [ aqui ]). O treco de código abaixo contém o prepareAndCreatureToSimulation() com a inclusão da list de leaflets:

             
/** ----------------------------------------------------------------------------------------
 * package Simulation;
 * 
 */

public class SimulationTask {
 . . .

   private void prepareAndSetupCreatureToSimulation(Creature creature, List<Thing> things) 
                                                   throws SoarBridgeException, IllegalAccessException {
      if (creature != null) {
          // Create Visual Sensor
          VisualSensor visualSensor = new VisualSensor();
          visualSensor.setSensorReadings(things);

          // Create Fuel Sensor
          FuelSensor fuelSensor = new FuelSensor();
          FuelSensorReadings fuelSensorReadings = new FuelSensorReadings();
          fuelSensorReadings.setCurrentFuel(creature.getFuel());
          fuelSensor.setSensorReadings(fuelSensorReadings);

          // Put things in SOAR output interface to start simulation
          SimulationRobot simulationCreature = new SimulationRobot(creature);
          simulationCreature.addSensorsAvailable(visualSensor);
          simulationCreature.addSensorsAvailable(fuelSensor);

          // -------------------------------------------------------
          // Inclui a lista de leaflets            
          // ------------------------------------------------------- 
          // A classe SimulationCreature é estendida pela SimulationRobot
          simulationCreature.setLeafletList(creature.getLeaflets());             

          // Setup StackHolder for run simulation
         soarBridge.setupStackHolder(StakeholderType.CREATURE,simulationCreature);
      } 
      else {
         throw new IllegalArgumentException("Arguments ares null");
      } 
   }
 . . .

}               
 

 

E, conforme o diagrama de dependência dos pacotes, Figura 3, basta inclouir no pacote SoarBridge as bibliotecas e uma variável para tratar dos leaflets (isso inclui a manipulação de estruturas HashMap). 
 

                  
/** ----------------------------------------------------------------------------------------
  * package SoarBridge; 
  *  
  *     . . .  
  *
  * import java.util.HashMap; 
  * import java.util.Vector; 
  * import ws3dproxy.model.Leaflet; 
  * 
  *   
  * public class SoarBridge { 
  *
  *   // Log Variable
  *     . . .  
  * 
  *   // SML variables
  *     . . . 
  *  
  *   // Entity Variables
  *   Identifier creature;
  *   Identifier creatureSensor;
  *   Identifier creatureParameters;
  *   Identifier creaturePosition;
  *   Identifier creatureMemory;
  *   Identifier creatureLeaflets;
  * 
  *   // Ordinary Variables
  *     . . . 
  * 
  * } 
  */
                
   
 
A codificação abaixo mostra as alteraçãos efetuadas no método SoarBridge.setupStackHolder() para criar na estrutura do botSoar, veja [ aqui ],  a subestrura ^LEAFLETS
 
               
/** ----------------------------------------------------------------------------------------
public void setupStackHolder(StakeholderType entityType, SimulationCreature parameter) 
                            throws SoarBridgeException {
  try {

    if (agent != null) {

      switch (entityType) {

        case CREATURE:
          SimulationRobot creatureParameter = (SimulationRobot)parameter;

          // Create creature
          . . . 

          // Set Creature Parameters
          . . . 

          // Setting creature Position
          . . . 

          // Set creature sensors
          . . . 

          // Set leaflets sctructure: cria a subestrutura LEAFLETS na estrutura do botSoar (veja [ aqui ])
          Vector<Leaflet> leaflets;
          leaflets = creatureParameter.getLeafletList();                       
          if (creatureLeaflets == null)                          
            creatureLeaflets = agent.CreateIdWME(creature, "LEAFLETS");                            
            //agent.CreateFloatWME(creatureLeaflets, "LEAFLETSTODO", leaflets.size()); -- para testes. 

            // Set leaflet structure: preenche os campos de cada leaflet  
            Identifier leaflet = null;
            HashMap<String, Integer[]> leafletItems = null;
            Leaflet oneLeaflet = null;
            Identifier leafletJewel = null;
            Object[] jewelsInLeaflet = null;
            Integer[] jewelQtt = null;

            for (int i = 0; i < leaflets.size(); i++) {
               oneLeaflet = leaflets.elementAt(i);
               leaflet = agent.CreateIdWME(creatureLeaflets, "LEAFLET");                                
               agent.CreateIntWME(leaflet, "LEAFLETID", oneLeaflet.getID());
               agent.CreateIntWME(leaflet, "LEAFLETSCORE", oneLeaflet.getPayment()); 
               leafletItems = oneLeaflet.getItems(); 
               jewelsInLeaflet = leafletItems.keySet().toArray();

               // Leaflets contents by jewel color 
               for (int j = 0; j < jewelsInLeaflet.length; j++) {
                 String leafletJewelColor = (String) jewelsInLeaflet[j];
                 leafletJewel = agent.CreateIdWME(leaflet, "JEWELCOLOR");
                 agent.CreateStringWME(leafletJewel, "COLOR", leafletJewelColor);
                 jewelQtt = leafletItems.get(jewelsInLeaflet[j]); 
                 agent.CreateIntWME(leafletJewel, "QTTGOAL", jewelQtt[0]);
                 agent.CreateIntWME(leafletJewel, "QTTINSACK", jewelQtt[1]); --  para testes. 
               }
            }
          }                       
        break;
      }
      agent.Commit();
      checkForKernelOrAgentError();
    }
  }
  catch ( exceção ) {
       . . .
  }
}

 

Note-se que a estrutura LEAFLETS dura uma simulação inteira, isto é, tem o mesmo status da estrutura MEMORY, logo não é o caso de atualizar os leaflets a cada ciclo. Com isso finalizamos as alterações necessárias, acredito que todas foram descritas. O pacote completo do DemoSoarModificado está num arquivo compactado (disponível [ aqui ]). Para testar via javawebstart utiliza os links abaixo: 

 

Experimento confrontando outros botsSoar

 

  • OBS.: ainda resolvendo o problemas de atribuição dos leaflets para os bots.

 

 



 

 

 

 

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer