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