You are here

Aula 03 - Soar

Controle de comportamento de agentes artificiais usando o SOAR

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



Introdução

Nas atividades abaixo exploramos o conteúdo do Tutorial 2 do Soar. Mais especificamente, utilizamos o Soar para controlar o comportamento de criaturas artificias chamadas "Eaters" (em um jogo similar ao  Pac-Man). Exploramos mecanismos de estados e operadores do Soar para diagnosticar o estado de cada Eater, propomos diferentes tipos de ações a cada instante e mostrar como o Soar faz para interfacear com o jogo (uso da arquitetura como sistema de controle de comportamento do Eater).

 


Comportamento para encontrar comida

Nesta atividade inicial exibimos um passo-a-passo para o uso da interface do jogo Eaters (a) elaboramos  uma criatura com as regras move-to-food.soar (dica: eaters move agent e eaters move-to-food agent); (b) executamos o programa e relatamos pontos importantes de sua execução; (c) usamos o reset e executamos o programa usando o passo-a-passo; (d) clonamos o agente  e refazemos a simulação; (e) editamos o programa move-to-food.soar com o do Visual Soar (VS) e explicamos a estrutura lógica do comportamento gerado; (f) estudamos como o estado atual da criatura é considerado nas regras, como a decisão de ação escolhida é aplicada de fato ao jogo,  como esse conjunto de regras escolhe a direção para a qual a criatura deve se mover, para que serve a regra apply*move-to-food*remove-move, o que aconteceria se ela não existisse, quais as limitações desse programa, o que seria necessário fazer para que ele não ficasse paralizado, depois de um certo tempo.

Faça o download do pacote Eaters adequado ao seu sistema operacional [ aqui ], siga as instruções para instalar o Eaters. A janela inicial é similar à da figura abaixo:

Na figura vemos um único agente (um blue eater) numa arena contendo dois tipos de comida. Para carregar novos agentes basta seguir os passos da figura abaixo:

Na janela mais a esquerda, ative o botão "New" (veja os detalhes em vermelho) para obter uma janela "Create Agent" (canto superior direito com detalhes em laranja). Nessa janela há duas opções de agentes, "Soar" e "Human", escolha a opção "Soar" e ative o botão "Create Agent". Uma outra janela será exibida com diversos agentes Soar para serem escolhidos (detalhes em verde). No caso da figura acima, vemos a escolha pelo agente move.soar.

Na figura abaixo exibimos o Eaters com um agente do tipo advanced-move.soar (um agente azul), a janela mais à esquerda mostra o SJD carregado com o código do agente. 

Na figura abaixo ativamos o agente (basta ativá-lo usando o botão "Run"), observe na janela mais à esquerda a execução da simulação e na janela mais à direita o agente consumindo os alimentos. 

Na figura abaixo, o agente azul termina com todos os objetos "food" - confirmado pela janela de aviso.

 

 

Diferente do agente advanced-move.soar, o agente move-to-food.soar (eaters move-to-food agent) não elemina todos os objetos food. Na figura abaixo, a janela da direita mostra que o agente (verde) não elimina todos os objetos food e a janela da esquerda retrata o comportamento da estrutura interna do agente: ao passo 11 o agente se move para a comida, no entanto a partir do passo 12 seu estado (representação interna)  não muda; permanece inalterado até o passo 111 - a simulação termina por "estouro da pilha de execução". 

Observamos a seguinte mensagem na janela de simulação: 

                    
Goal stack depth exceeded on a no-change impasse. 
Soar appears to be in an infinite loop.
Continuing to subgoal may cause Soar to exceed the program stack of your system.

Interrupt received.

Na figura abaixo mostramos dois agentes move-to-food.soar (eaters move-to-food agent), criamos um e o clonamos (usando o botão de clonagem - vide detalhe em amarelo): 

Dado que os dois agentes iniciam o jogo em posições distintos, isto é, a vizinhança deles não é necessariamente a mesma. Desse modo, é natural que o comportamento deles seja distinto, apesar do sistema de controle ser o mesmo. Uma forma de avaliar o comportamento distinto dos agentes é pela pontuação de cada um deles: 70 pontos para o agente azul e 270 para o agente vermelho.  Vejamos o funcionamento dos agentes  move-to-food.soar (abaixo temos um pseudo-código)

                    
############################################################################
# From Chapter 6 of Soar 8 Tutorial
# 
# This program proposes the move-to-food operator in any direction that 
# contains normal or bonus food.  If there is no food nearby, no instances 
# of the operator will be proposed and the halt operator will be proposed.
# 
# -------------------------------------------------------------------------
# Propose*move-to-food:
# If there is normalfood in an adjacent cell, propose move-to-food in the 
# direction of that cell and indicate that this operator can be selected 
# randomly.

# -------------------------------------------------------------------------
# Apply*move-to-food: 
# If the move-to-food operator for a direction is selected, generate an 
# output command to move in that direction.

# -------------------------------------------------------------------------
# Apply*move-to-food*remove-move:
# If the move-to-food operator is selected, and there is a completed move 
# command on the output link, then remove that command.

A seguir analisamos cada uma das partes, começando com o move-to-food (para normalfood e bonusfood) Apresentamos uma primeira versão sem o uso do "." e de short-cuts.

                    
############################################################################
# Sem o uso do "." 
# -------------------------------------------------------------------------

sp {propose*move-to-normalfood
    (state <s> ^io <io>)
    (<io> ^input-link <input-link>)
    (<input-link> ^my-location <my-loc>)
    (<my-loc> ^<direction> <cell>)
    (<cell> ^content normalfood)
   -->
    (<s> ^operator <o> +)
    (<s> ^operator <o> =)
    (<o> ^name move-to-food ^direction <direction>)
   }

# -------------------------------------------------------------------------
# Versão concisa (com o uso do ".") 
#
# A notação "." (dot) permite juntar atributos numa string, substituindo 
# as variáveis intermediárias por "." para separar os atributos. 
# No Soar, esta aberviação é chamada de "dot" ou "path" notation. 
# OBS.: O uso da notação "." restringe-se aos casos em que há apenas uma 
# única augmentation.
#
# Em ^io.input-link.my-location.<dir>.content normalfood temos quatro 
# substituições: em  io.input-link "." substitui <io>; 
#                em  input-link.my-location  "." substitui <input-link>;
#                em  my-location.<dir>  "." substitui <my-loc> e 
#                em  <dir>.content  "." substitui <cell>.

sp {propose*move-to-normalfood
    (state <s> ^io.input-link.my-location.<dir>.content normalfood)
   -->
    (<s> ^operator <o> + =)      # aqui as preferências são "juntadas"
    (<o> ^name move-to-food ^direction <dir>)
   }

# -------------------------------------------------------------------------
# Versão análoga para bonusfood

sp {propose*move-to-normalfood
    (state <s> ^io.input-link.my-location.<dir>.content bonusfood)
   -->
    (<s> ^operator <o> + =)
    (<o> ^name move-to-food ^direction <dir>)
   }

# -------------------------------------------------------------------------
# Versão concisa. Dois em uma única regra, normalfood e bonusfood com o uso 
# de short-cut "<< ... >>" (deixar espaço entre "<<" e "..." e "..." e ">>")

sp {propose*move-to-food
    (state <s> ^io.input-link.my-location.<dir>.content 
                            << normalfood bonusfood >>)
   -->
    (<s> ^operator <o> + =)
    (<o> ^name move-to-food ^direction <dir>)
   }

Para entendermos a codificação acima apresentamos a especificação do Soar referente codificação da arena e à percepção do agente. A representação abaixo mostra de forma ampliada os dados captuados  pelo sensor do agente (janela 5x5 - figura mais à direita).

A vizinhança do agente, formada pelos nós adjacentes ao nó onde está o agente, é expressa em ^my-location (vide código abaixo), Em ^my-location temos informações sobre o conteúdo da posição corrente e sobre os quatro nós adjacentes (nas direções North, South, East, West). 

      
^io
   ^input-link
      ^eater
         ^direction east/north/south/west
         ^name red/blue/yellow/green/purple/black
         ^score 0-1000
         ^x 1-15
         ^y 1-15
      ^my-location      
         ^content bonusfood/normalfood/eater/empty/wall
         ^east
            ^content bonusfood/normalfood/eater/empty/wall
            ...
         ^north 
            ^content bonusfood/normalfood/eater/empty/wall
            ...
         ^south
            ^content bonusfood/normalfood/eater/empty/wall
            ...
         ^west
            ^content bonusfood/normalfood/eater/empty/wall
            ...
   ^output-link
      ^move
         ^direction east/north/south/west
         ^status complete   - created by Soar as feedback 
      ^jump
         ^direction east/north/south/west 
         ^status complete   - created by Soar as feedback       
^superstate nil
^type state

Lembrando que a codificação acima é parte da estrutura inicial da WM, vide representação abaixo:

 

De fato, a estrutura acima (chamada de top-state structure) está subjacente a todo agente eater. Top-state structures contém todos os atributos do objeto (com a devida indentação para indicar sub-objetos e valores), por isso servem de referência (base) para a construção de outros agentes da mesma "espécie".

No  ^output-link temos a direção para o qual o agente decidiu ir, seja por um movimento ( ^move  ) ou por um salto ( ^jump ). A ação para a escolha de uma direção a ser tomada tem início na regra referente ao operador move-to-food que é acionada se o conteúdo de alguma célula vizinha for normalfood (ou bonusfood). Em caso afirmativo, o agente irá se mover na direção do alimento (isto é, aplica-se o move-to-food):

                   
############################################################################
# Apply*move-to-food: 
# -------------------------------------------------------------------------
# If the move-to-food operator for a direction is selected, generate an 
# output command to move in that direction.

sp {apply*move-to-food
    (state <s> ^io.output-link <ol> ^operator <o>)
    (<o> ^name move-to-food ^direction <dir>)
   -->
    (<ol> ^move.direction <dir>)
   }

Pela estrutura do ^output-link, uma vez efetuado o movimento o sistema cria uma augmentation sobre o objeto  ^move : descreve o movimento como  ^status complete.  Desse modo, para não sobrecarregar a WM, é necesário adicionar uma regra que remova os movimentos já executados. Essa é a finalidade do  apply*move-to-food*remove-move.

                    
############################################################################
# Apply*move-to-food*remove-move:
# -------------------------------------------------------------------------
# If the move-to-food operator is selected, and there is a completed move 
# command on the output link, then remove that command.

sp {apply*move-to-food*remove-move
    (state <s> ^io.output-link <ol>  ^operator.name move-to-food)
    (<ol> ^move <move>)
    (<move> ^status complete)
   -->
    (<ol> ^move <move> -)   # Uso do reject "-" para remover um WME
   }

Como vimos anteriormente (vide Atividade), para a remoção de um WME usamos o "reject" ("-") na ação da regra. O reject é aplicado sobre um WME persistente, logo para removê-lo é necessário verificar se o WME em questão é parte de um operador de aplicação (neste caso, move-to-food). Este teste é feito pelas duas primeiras linhas da parte IF na regra acima.  

Conforme comentamos anterioremente (vide em Figura), esse conjunto de regras não é suficiente para que o agente termine de consumir todos os itens food (noramlfood e bonusfood). Com as regras dadas, o agente não executa nenhum movimento caso não haja alimento nas células adjacentes. Uma solução para evitar que o agente fique parado numa dada posição é colocar uma regra para um comportamento de vagar pelo ambiente (wander - é uma forma de steering behavior que faz com que um agente vagueie pelo ambiente). Vejamos como codificar isso no Soar:

                     
############################################################################
# Propose*move-to-empty:
# -------------------------------------------------------------------------
# If there is no food nearby, propose move-to in the direction of the empty 
# cell and indicate that this operator can be selected randomly.

sp {propose*move-to-empty
    (state <s> ^io.input-link.my-location.<dir>.content empty)
   -->
    (<s> ^operator <o> + =)
    (<o> ^name move-to-empty ^direction <dir>)
   }

############################################################################
# Apply*move-to-empty:
# -------------------------------------------------------------------------
# If the move-to-empty operator is selected, generate an output command to 
# move in that direction.

sp {apply*move-to
    (state <s> ^io.output-link <ol> ^operator <o>)
    (<o> ^name move-to-empty ^direction <dir>)
   -->
    (<ol> ^move.direction <dir>)
   } 

############################################################################
# Apply*move-to-empty*remove-move:
# -------------------------------------------------------------------------
# If the move-to-empty operator is selected, and there is a completed move 
# command on the output link, then remove that command.

sp {apply*move-to-empty*remove-move
    (state <s> ^io.output-link <ol>  ^operator.name move-to-empty)
    (<ol> ^move <move>)
    (<move> ^status complete)
   -->
    (<ol> ^move <move> -)
   }

O novo agente move-to-food (código-fonte aqui), após anexarmos a codificação acima código do move-to-food.soar. Em todas as simulações executadas o novo agente consumiu todos os itens food. Evidentemente, o desempenho não foi dos melhores (e nem era o objetivo deste experimento).

 


Usando recursos do Soar Java Debugger (SJD) e do Visual Soar (VS).

Nesta seção complementamos as atividades anterior, discorremos um pouco mais sobre o uso do Soar Java Debugger (SJD) para acompanhar o processo de escolha e aplicação de operadores (via traces) e usos de comandos em tempo de execução e do Visual Soar (VS) como ferramenta de edição e de detecção de eventuais erros sintáticos em regras.

A escrita de programas Soar, como toda atividade associada à programação, está sujeita à ocorrência de erros sintáticos e semânticos. O VS fornece alguns recursos para a detecção de erros sintáticos. Vejamos como fazer uso do VS para a edição de códigos. Para exemplificar o uso do VS carregamos o código abaixo num projeto no VS. 

                      
############################################################################

### This program proposes the move-to-food operator in any direction
### that contains normal or bonus food.  If there is no food nearby, no
### instances of the operator will be proposed and the halt operator

 will be proposed.

sp propose*move-to-food

   (state <s> ^io.input-link.my-location.<dir>.content

                 << normalfood bonusfood >>
-->
   (<s> ^operator <o> +, =)

   (<o> ^name move-to-food

        ^direction <dir>)}

sp {apply*move-to-food

   (state <s> ^io.output-link <ol>

              ^operator <o>)

   (<o> ^name move-to-food)

        ^direction <dir>)

-->

   (<ol> ^move.direction <dir>)}

sp {apply*move-to-food*remove-move

   (state <s> operator.name move-to-food

              ^io.output-link <ol>)

   (<ol> ^move <move>)

-->

   (<ol> ^move <move> -)}

Ative o VS, abra um novo projeto (chamamos o novo projeto de prj_teste_erro-sintatico) e carregue o código como parte do projeto. Por exemplo, basta copiar e colar no arquivo initialize-prj_teste_erro-sintatico), siga o esquema da figura abaixo:

Uma vez que o conteúdo do código esteja carregado, usamo os recursos do VS para encontrar alguns erros sintáticos comuns. A figura abaixo mostra o item Check All Productions for Syntax Errors da opção  Datamap.

Na parte inferior temos o feedback sobre o erro localizado. Basta um duplo-clique com o botão direito do  mouse sobre a mensagem do feedback para o VS apontar o local do erro (referente à mensagem). Com o uso do Check All Productions for Syntax Errors é possível encontrar todos os erros do código teste.

                      
############################################################################

### This program proposes the move-to-food operator in any direction
### that contains normal or bonus food.  If there is no food nearby, no
### instances of the operator will be proposed and the halt operator

 will be proposed.        # Trata-se de um comentário. 

sp propose*move-to-food   # Falta o abre chaves "{"

   (state <s> ^io.input-link.my-location.<dir>.content

                 << normalfood bonusfood >>   # Falta o fecha parênteses
-->
   (<s> ^operator <o> +, =)                   

   (<o> ^name move-to-food

        ^direction <dir>)}                    

sp {apply*move-to-food

   (state <s> ^io.output-link <ol>

              ^operator <o>)

   (<o> ^name move-to-food)             # Uso incorreto do fecha parênteses

        ^direction <dir>)

-->

   (<ol> ^move.direction <dir>)}

sp {apply*move-to-food*remove-move

   (state <s> operator.name move-to-food     # Falta o "^"

              ^io.output-link <ol>)

   (<ol> ^move <move>)

-->

   (<ol> ^move <move> -)}

Se não houver erros de sintaxe, vemos a seguinte mensagem (em feedback): There were no errors detected in this project

Uma outra categoria de erros é a que se refere à "semântica do programa" (diz respeito ao comportamento do agente). Antes de depurarmos um código com erro semântico, testamos alguns recursos do SJD executando move-to-food.soar (move-to-food agent). 

Uma das formas usuais de depuração de código é a de imprimir certas ocorrências do programa durante a sua execução. Por exemplo, podemos colocar o comando write (crlf) | Propose move | <dir> |, for | <type> para rastrear o operador que propõe o  move-to-food. O comando de impressão exibe a direção e o tipo do alimento. Note a modificação feita para capturar o tipo do alimento.

                 
############################################################################
# Propose*move-to-food:
# If there is normalfood in an adjacent cell, propose move-to-food in the 
# direction of that cell and indicate that this operator can be selected 
# randomly.

sp {propose*move-to-food
    (state <s> ^io.input-link.my-location.<dir>.content 
                            { <type> << normalfood bonusfood >> })
   -->
    (write (crlf) | Propose move | <dir> |, for | <type>)
    (<s> ^operator <o> + =)
    (<o> ^name move-to-food ^direction <dir>)
   }

A figura abaixo mostra a execução do rastreamento acima. O eater começa o jogo cercado por dois nomalfood (um ao norte e outro ao sul) e um bonusfood (na direção oeste). No quadro seguinte, o eater come o normalfood ao sul e temos uma nova listagem dos alimentos em sua vizinhança. O terceiro quadro exibe o rastreamento após vários passos. 

O SJD oferece outros recursos para a depuração, como o Watch (run-time tracing of Soar), vide botões (on/off) para o Watch 1, 3 e 5 na parte inferior do SJD. O Watch possui seis níveis (0 - 5):

                
0 none              -  turns off all printing about Soar's internals;

1 decisions status  -  controls whether state and operator decisions 
                       are printed as they are made;

2 phases status      - controls whether decisions cycle phase names are 
                       printed as Soar executes; 

3 productions status - controls whether the names of productions are 
                       printed as they fire and retract. See the optional
                       arguments below 
                       [-all|-chunks|-defaults|-justifications|-user] 
                       which specify which types of productions the 
                       status argument refers to;

4 wmes               - controls the printing of working memory elements 
                       that are added and deleted as productions are 
                       fired and retracted. 

5 preferences status - controls whether the preferences generated by the 
                       traced productions are printed when those 
                       productions fire or retract. When a production 
                       fires, all the preferences it generates are 
                       printed. When it retracts, only the ones being 
                       removed from preference memory are printed 
                       (i.e., the i-supported ones).
                 

Vamos utilizar o "watch 4" no arquivo eaters semantic-error example (vide código abaixo) para detectar o o motivo pelo qual o eater não se move.

                        
############################################################################
# This program proposes the move-to-food operator in any direction that
# contains normal or bonus food.  If there is no food nearby, no instances 
# of the operator will be proposed and the halt operator will be proposed.

sp {propose*move-to-food
    (state <s> ^io.input-link.my-location.<dir>.contant       # Erro!!!
                                 << normalfood bonusfood >>)
   -->
    (<s> ^operator <o> + =)
    (<o> ^name move-to-food ^direction <dir>)
   }

sp {apply*move-to-food
    (state <s> ^io.output-link <ol> ^operator <o>)
    (<o> ^name move-to-food ^direction <dir>)
   -->
    (<ol> ^moves.direction <dir>)
   }

sp {apply*move-to-food*remove-move
    (state <s> ^operator.name move-to-food ^io.output-link <ol>)
    (<ol> ^move <move>) 
    (<move> ^status complete)
   -->
    (<ol> ^move <move> -)
   }

Após carregar o agente  eaters semantic-error example na arena e pô-lo para percorrer a arena percebe-se que o agente não se move. A figura abaixo, quadro mais à esquerda, mostra que não há mudança de estado após um passo do jogo. Atividando o  "watch 4" e executando um novo passo, temos outras informações, por exemplo, que nenhuma regra (produção) está para ser disparada (ou seja, nenhum operador foi proposto, em particular, o move-to-food), isto é, há algo com o  propose*move-to-food.


Após corrigir o contant por content, verificamos com uma nova simulação, ativando  o  watch 4 (vide quadro do meio na figura acima) que houve mudanças na WM, em particular, em relação ao status do operador o move-to-food. Além do Watch (em seus vários níveis) o SJD oferece outros recursos para a depuração de programas, veremos esses recursos ao longo das atividades. A seguir fazemos algumas modificações no agente move-to-food.soar (eaters move-to-food agent) para evitar que ele fique parado na ausência de comida em sua vizinhança.

 


Move Jump Eaters   

Nesta atividade exploramos o uso de estruturas top-state (descrita anteriormente) para desenvolvermos eaters que procuram por comida (sem ficar vagando pelo ambiente). O comportamento deste tipo de eater difere da codificação já feita (vide seção anterior: código-fonte aqui). Nesta implementação o eater deve executar jumps (ver exemplos em: eaters jump agent e eaters jump and move agent) e não deve ficar "indo e vindo" (de uma célula para outra, especialmente se estiverem vazias). O código-fonte completo do Move-Jump_Eaters.soar está listado ao longo desta seção pode ser obtido aqui.

Para o uso dos jumps tomamos o cuidado para que o eater não "entre" na parede. E para evitar que o eater não fique indo-e-vindo (movimento entre células vizinhas) usamos a esquema do  last-operator (do water jug problem) combinado com o uso de estruturas top-state (anexamos à estrutura do eater os ^type state). Abaixo apresentamos a estrutura e, como toda estrutura associada ao papel de memória, o primeiro procedimento:  o de "inicializar a estrutura".


                  
############################################################################
# This is the first rule with multiple values (<n> <e> <s> <w>) for a single 
# attribute (^direction). 
# Each value can be written as individual actions; however, as a shortcut, 
# all of the values can be listed following their common attribute.
# One might be tempted to have the values of ^direction be the symbols north, 
# east, south, and west. However, these values cannot be further augmented 
# with their opposites; only identifiers can have augmentations. It is not 
# legal to have augmentations of the form: (north ^opposite south).


sp {initialize*state*directions
    (state <ss> ^type state)
   -->
    (<ss> ^directions <n> <e> <s> <w>)
    (<n> ^value north ^opposite south)
    (<e> ^value east ^opposite west)
    (<s> ^value south ^opposite north)
    (<w> ^value west ^opposite east)
   }

Em termos implementacionais, essas regras de inicialização (como o initialize*state*directions) facilitam a escrita de códigos. Por exemplo, a regra initialize*state*directions fixa uma estrutura na WM que simplifica o uso de operadores que usam a relação de oposição entre direções: A regra de inicialização é disparada durante o primeiro ciclo, quando (s1 ^type state) é adicionado na WM. Note-se que a condição da regra garante que a estrutura não será removida da WM, apesar de ser i-supported. A estrutura irá persisitir ao longo da vida do eater.

Em seguida, temos duas regras para propor as formas de movimentação, digamos, por passo (move) e por salto (jump). 

                   
# --------------------------------------------------------------------------
# Propose*move:
# If there is no wall surrounding, propose move in the direction of that cell
# and indicate that this operator can be selected randomly.

sp {propose*move
    (state <s> ^io.input-link.my-location.<dir>.content {<cont> <> wall})
   -->
    (<s> ^operator <o> +, = 0)    # O valor "0" para a preferência "=" 
                                  # evita muitos "travamentos" do jump 
                                  # nos corners.  
    (<o> ^name move ^direction <dir> ^content <cont>)
   }

# --------------------------------------------------------------------------
# Propose*jump:
# If the content of a cell two steps away in a direction is not a wall,
# propose jump in the direction of that cell, with the cell's content,
# and indicate that this operator can be selected randomly.

sp {propose*jump
    (state <s> ^io.input-link.my-location.<dir>.<dir>.content  { <cont> <> wall }
               ^io.input-link.my-location.<dir>.content empty)
   -->
    (<s> ^operator <o> +, =)
    (<o> ^name jump ^direction <dir> ^content <cont>)
   }
 

Dada as regras de proposição temos as regreas de aplicação das regras propostas: 

                 
# --------------------------------------------------------------------------
# Apply*move*jump:
# If the move or jump operator for a direction is selected, generate an
# output name to move in that direction.

sp {apply*move
    (state <s> ^io.output-link <ol> ^operator <o>)
    (<o> ^name { <name> << move jump >> } ^direction <dir>)
   -->
    (write  | | <name> | | <dir>)
    (<ol> ^<name>.direction <dir>)
   }  

# --------------------------------------------------------------------------
# Apply*move
# If the move operator for a direction is selected, generate an output
# command to move in that direction.
#
#sp {apply*move
#    (state <s> ^io.output-link <ol> ^operator <o>)
#    (<o> ^name move ^direction <dir>)
#   -->
#    (write | | <dir>)
#    (<ol> ^move.direction <dir>)
#   }

# --------------------------------------------------------------------------
# Apply*move*remove-move:
#
# If the move operator is selected, and there is a completed move command
# on the output link, then remove that command.

sp {apply*move*remove-move
    (state <s> ^io.output-link <ol> ^operator.name move)
    (<ol> ^move <direction>)
    (<direction> ^status complete)
    -->
    (<ol> ^move <direction> -)
   }

A última regra evita que WMEs referentes à movimentos já completados persistam na WM. 

Agora, se desejamos evitar o comportamento de ir-e-vir (entre células vizinhas), então o eater deve-se lembrar por onde passou (pelo menos da última célula por onde passou). O eater deve ter algum tipo se memória  (tecnicamente, alguns elementos devem persisitir na WM) que mantenha a informação sobre a direção que tomou para estar onde está (posição corrente). A persistência dessa informação é controlada por uma regra que cria a informação (uma WME, o-supported) e, depois da perda de sua validade, outra regra a descarta (remove a WME). 

A primeira regra cria uma augmentation (descrição) do estado em relação ao atributo  ^last-direction,  a descrição (augmentation) contém a direção utilizada pelo último operador aplicado. A segunda regra remove o atributo ^last-direction caso a informação contida não seja mais a atual. Estas regras são disparadas durante a fase de aplicação (dos operadores).

               
# --------------------------------------------------------------------------
# Apply*move*create*last-direction
#
# If the move operator for a direction is selected, create an augmentation
# called last-direction with that direction.

sp {apply*move*create*last-direction
    (state <s> ^operator <o>)
    (<o> ^name { <name> << move jump >> } ^direction <direction>)
   -->
    (<s> ^last-direction <direction>)
   }

# --------------------------------------------------------------------------
# Apply*move*remove*last-direction
#
# If the move operator for a direction is selected, and the last-direction
# is not equal to that direction, then remove the last-direction.

sp {apply*move*remove*last-direction
    (state <s> ^operator <o> ^last-direction <direction>)
    (<o> ^direction <> <direction> ^name move)
   -->
    (<s> ^last-direction <direction> -)
   }

Na implementação feita anteriormente (alteração do agente move-to-food: código-fonte aqui), a informação sobre a última movimentação era removida (após o movimentop ser executado). Nesta implementação usamos uma estrutura de dados para manter WMEs que contenham as informações necessária para evitar o comportamento de ir-e-vir. Para essa finalidade definimos os ^type state na estrutura do eater. Desse modo, cada eater  mantém na WM  as direções possíveis e um atributo  indicando a respectiva direção oposta.

A seguir apresentamos as regras de seleção de operadores, são elas que determinam (em grande parte) como os eaters irão se comportar. Por exemplo, o de rejeição à voltar para a célula que acabara de passar, ou a preferência por células que contenham comida a células vazias, ou andar é melhor que saltar (já que saltar consome pontos).

            
# --------------------------------------------------------------------------
# Select*move*reject*backward
# If there is a proposed operator to move in the direction opposite the last
# move, reject that operator.

sp {select*move*reject*backward
    (state <s> ^operator <o> + ^directions <d> ^last-direction <dir>)
    (<d> ^value <dir> ^opposite <o-dir>)
    (<o> ^name { <name> << move jump >> } ^direction <o-dir>)
   -->
    (write | Reject | <o-dir>)
    (<s> ^operator <o> -)
   }

# --------------------------------------------------------------------------
# Select*move*food-better-than-empty
# If there is a proposed operator to move to a cell with food (normalfood or
# bonusfood) and there is a second proposed operator to move to a cell that
# is empty  prefer the first operator.

sp {select*move*food-better-than-empty
    (state <s> ^operator <o1> +  ^operator <o2> +)
    (<o1> ^name move ^content {<cont> << normalfood  bonusfood >>})
    (<o2> ^name move ^content empty)
   -->
    (write <o1> | better than | <o2>)
    (<s> ^operator <o1> > <o2>)
   }

# --------------------------------------------------------------------------
# Select*move*better-than-jump
# If there is a proposed operator to move and there is a second proposed
# operator to jump prefer the first operator.
#
sp {select*move*better-than-jump
    (state <s> ^operator <o1> +  ^operator <o2> +)
    (<o1> ^name move ^content empty)
    (<o2> ^name jump ^content wall)
   -->
    (write <o1> | better than | <o2>)
    (<s> ^operator <o1> > <o2>)
   }


 


Smart Eaters

Nesta atividade utilizamos a estrutura top-state para implementar regras nos eaters que sistematizam a busca por comida quando eles se encontram em regiões sem comida (p.ex., células vizinhas sem comida). 

OBS.: complementos do eater (código [ aqui ]): este novo eater prefere bonusfood (caso tenha acesso à uma célula contendo normalfood e outra bonusfood. Para isso, basta anexar a regra abaixo ao código do eater da seção anterior:

           
# --------------------------------------------------------------------------
# Select*move*bonusfood-better-than-normalfood
# If there is a proposed operator to move to a cell with bonusfood and there
# is a second proposed operator to move to a cell with normalfood prefer the
# first operator.

sp {select*move*bonusfood-better-than-normalfood
    (state <s> ^operator <o1> +  ^operator <o2> +)
    (<o1> ^name move ^content bonusfood)
    (<o2> ^name move ^content normalfood)
   -->
    (write <o1> | better than | <o2>)
    (<s> ^operator <o1> > <o2>)
   }

Outra modificação está relacionada ao ato de saltar para um corner (código [ aqui ]): para isso incluímos um controle do tipo i-support (válido somente no ato anterior ao salto) na estrutura top-state. O trecho de codificação abaixo exemplifica o complemento (refere-se somente ao corner North-West):   

                      
# --------------------------------------------------------------------------
# Eater jumping to the corner
sp {elaborate*state*jumping-nw-corner1
   :i-support
    (state <s> ^io.input-link <io>)
    (<io> ^eater.x 1 ^eater.y 3)
   -->
    (<s> ^avoid-jump yes)            # Anexado na estrutura
   }

# --------------------------------------------------------------------------
# Eater jumping to the corner
sp {elaborate*state*jumping-nw-corner2
   :i-support
    (state <s> ^io.input-link <io>)
    (<io> ^eater.x 3 ^eater.y 1)
   -->
    (<s> ^avoid-jump yes)            # Anexado na estrutura
   }
 

A codificação acima é feita pra os quatro corners. Com esse controle, a regra que propõe o jump é ligeiramente mnodificado:  

 
# --------------------------------------------------------------------------
# Propose*jump:
# If the content of a cell two steps away in a direction is not a wall,
# propose jump in the direction of that cell, with the cell's content,
# and indicate that this operator can be selected randomly.
#
sp {propose*jump
    (state <s> ^io.input-link.my-location.<dir>.<dir>.content  { <cont> <> wall }
               ^io.input-link.my-location.<dir>.content empty
               -^avoid-jump yes)
   -->
    (<s> ^operator <o> +, =)
    (<o> ^name jump ^direction <dir> ^content <cont>)
   }

 

Nossa proposta de uso da estrutura top-state para os casos em que o eater entra numa região sem comida (nenhum alimento ao seu redor) é similar ao uso acima. O código abaixo mostra uma forma do eater saber se está numa área sem comida:

 
# ------------------------------------------------------------------------------------------
# No food around
sp {elaborate*state*no-food-around
   :i-support                                              # Só ativa na condição abaixo 
    (state <s> ^io.input-link.my-location <io>)
    (<io>      -^<dir>.content << normalfood bonusfood >>) # Não há comida ao redor do eater
   -->
    (<s> ^no-food-around yes)                              # Anexado na estrutura
  }

Na situação da regra acima, isto é, quando não há comida ao redor do eater, é proposto uma nova regra de movimentação. A direção indicada é a de células mais distantes (duas células de distância) que contenham comida, apesar das células adjacentes na mesma direção estarem vazias. 

 
# ------------------------------------------------------------------------------------------------
#sp {propose*move-no-food-around
    (state <s> ^no-food-around yes
             ^io.input-link.my-location.<dir>.<dir>.content { <cont> << bonusfood normalfood >> }
             ^io.input-link.my-location.<dir>.content empty)
   -->
    (<s> ^operator <o> +, =)
    (<o> ^name move-no-food-around   ^direction <dir>    ^content <cont>)
   }

A regra de aplicação não é muito diferente das anteriores:

 
# ------------------------------------------------------------------------------------------
# Apply*move*move-no-food-around:
# If the move or move-no-food-around operator for a direction is selected, generate an
# output name to move in that direction.

sp {apply*move
    (state <s> ^io.input-link.eater <e> ^io.output-link <ol> ^operator <o>)
    (<e> ^x <x> ^y <y>)
    (<o> ^name { <name> << move move-no-food-around >> } ^direction <dir>)
   -->
    (write  | | <name> | | <dir> |  :: from: x-coord: | <x> | and y-coord: | <y>)
    (<ol> ^move.direction <dir>)
   }  

O código completo dessa atividade está [ aqui ].

 


 

Nas próximas atividade lidaremos com problemas que podem exigir a decomposição de uma meta em metas intermediárias (subgoals) para serem resolvidos. A decomposição de problemas em sub-problemas exige, por exemplo, o tratamento de possíveis impasses que venham a ocorrer (veremos esse tópico no Tutorial 3: Aula 4).

 



 

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer