Atividade 1
A primeira atividade consiste em se familiarizar com a aplicação Eaters e os agentes utilizados para controlar as criaturas do jogo. Conforme as instruções, executamos o programa Eaters e criamos um novo agente, utilizando a produção "move-to-food.soar" para este.
Após selecionar o agente e rodar o programa pela primeira vez, tivemos o seguinte resultado:
O programa possui alguns os indicadores "Food remaining", "Points remaining", "World count", além dá pontuação obtida por cada agente, o que permite avaliar a performance dos agentes utilizados.
Clonando a criatura, é criado um novo agente que utiliza as mesmas regras definidas na produção "move-to-food.soar" que analisaremos a seguir. Neste primeiro momento é possivel notar que a criatura quando envolta apenas por casas vazias interrompe suas ações.
Abrindo a produção no VisualSoar, encontramos o seguinte código na parte de proposição dos operadores:
p {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 entender melhor esse operador, vamos executar o programa passo-a-passo, observando os estados iniciais na memória de trabalho. Desejamos observar <s> ^io.input-link.my-location.<dir>.content:
p S1
(S1 ^epmem E1 ^io I1 ^operator O1 ^operator O3 + ^operator O1 + ^operator O2 +
^reward-link R1 ^smem S2 ^superstate nil ^type state)
p I1
(I1 ^input-link I2 ^output-link I3)
I1 é o objeto da memória de trabalho que vai manipular a entrada e saída de dados. Nos interessa analisar o input-link:
p I2
(I2 ^eater E2 ^my-location M1 ^random 0.6215689182281494)
Esse objeto possui um atributo de valor M1, que é um o objeto que descreve a posição inicial do agente:
p M1
(M1 ^content eater ^content-name red ^east E3 ^north N1 ^south S3 ^west W1)
Os elementos ^east E3 ^north N1 ^south S3 ^west W1 são os valores que serão substituidos pela variável <dir>. Eles possuem os seguintes atributos ^content:
p E3(E3 ^content normalfood ^east S6 ^north E4 ^south S8 ^west M1)
p N1
(N1 ^content normalfood ^east E4 ^north N6 ^south M1 ^west N2)
p S3
(S3 ^content normalfood ^east S8 ^north M1 ^south S10 ^west S4)
p W1
(W1 ^content wall ^east M1 ^north N2 ^south S4 ^west W2)
Para os 3 primeiros objetos, temos ^content normalfood. Estes satisfazem a comparação a seguir: << normalfood bonusfood >>
Isso nos mostra que esse operador seleciona aleatoriamente uma posiçao adjacente a posição da criatura e verifica se o conteudo dessa é "normalfood"ou "bonusfood". Caso sim, o operador (<o> ^name move-to-food ^direction <dir>) é proposto. A variável <dir> vai conter a direção testada que possui comida.
Por ser o único operador proposto, este é o escolhido na fase de decisão:
--- decision phase ---
=>WM: (139: S1 ^operator O1)
1: O: O1 (move-to-food)
p O1
(O1 ^direction south ^name move-to-food)
A direção escolhida foi "south". Após isso o operador O1 selecionado passa a fase de aplicação, está esta implementada da seguinte maneira:
sp {apply*move-to-food
(state <s> ^io.output-link <ol>
^operator <o>)
(<o> ^name move-to-food
^direction <dir>)
-->
(<ol> ^move.direction <dir>)}
O primeiro teste verifica a existencia do output-link e da existencia do operador criado na fase de proposição. O segundo teste verifica se o operador existente foi criado com os elementos de acordo com estabelecido na fase de proposição, ou seja, ^name move-to-food e ^direction <dir>, onde o valor <dir> ser a direção escolhida.
Satisfeitas, um novo elemento na memória de trabalho é criado em output-link. É a partir desse elemento que a decisão de ação escolhida é de fato aplicada ao jogo.
O disparo da fase de aplicação gera o seguinte resultado:
Firing apply*move-to-food
-->
(M2 ^direction south + :O)
(I3 ^move M2 + :O)
p I3
(I3 ^move M2)
p M2
(M2 ^direction south ^status complete)
Percebemos que aqui o objeto do output-link (I3) possui agora um elemento ^move M2 que por sua vez possui outro elemento com a direção escolhida ^direction south.
A partir dai o jogo utiliza esse objeto criado na memória de trabalho e executa a movimentação da criatura para baixo. Após essa execução o elemento ^status complete é adicionado a memória de trabalho indicando que essa ação ja foi realizada com sucesso.
Essa produção possui ainda outra parte de aplicação de regras, chamada apply*move-to-food*remove-move:
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> -)}
Ela executa as mesmas comparações de apply*move-to-food, exceto pelo elemento <move> ^status complete adicionado após a execução do movimento pelo jogo. O resultado é a remoção do elemento ^move <move> do output-link.
Isso é necessário pois esse elemento não é automaticamente removido por ter sido criado pela regra de aplicação de operador acima. Estes elementos de memória não são automaticamente eliminados quando a regra não é mais verdadeira.
Executando o programa sem a regra apply*move-to-food*remove-move notamos que a criatura se movimenta apenas uma vez. Isso acontece pois apos a primeira movimentação a memória de trabalho esta "suja" com um elemento <move> ^status <complete>.
Conforme observamos durante a execução, a criatura para de se movimentar quando se encontra envolta por casas vazias, mesmo havendo em outras partes do mapa alimento. Isso acontece pois a regra de proposição desse operador só é disparada quando existe alimento imediatamente adjecente (<< normalfood bonusfood>>).
Uma solução simples para esse problema seria adicionar a condição "empty" dentro da comparação, tendo agora << normalfood bonusfood empty>>.
Atividade 2
Uso da interface de entrada e saída de um programa Soar
Conforme haviamos visto na aula anterior, na criação de um novo agente, a seguinte estrutura é criada na memória de trabalho:
É através das sub-estruturas I2 e I3 que teremos as interfaces de entrada e saída no programa soar. No caso do programa eaters temos como entrada:
p I2
(I2 ^eater E2 ^my-location M1 ^random 0.4009833931922913)
p E2
(E2 ^name blue ^score 5 ^x 9 ^y 15)
p M1
(M1 ^content eater ^content-name blue ^east E3 ^north N1 ^south S3 ^west W1)
A representação dessa do objeto input-link é mostrada na figura abaixo.
Os objetos E3, N1, S3 e W1 tem subestruturas que definem a visibilidade da criatura, no caso uma matriz de casas 5x5 com a criatura na casa central, proporcionando uma visibilidade de duas casas de distância com os dados de input do programa.
Já a interface de saída é mais simples, tendo como valores apenas a próxima direção de movimento, e se essa ação foi ou não executada:
p I3
(I3 ^move M140)
p M140
(M140 ^direction north ^status complete)
Uso de shortcuts em programas Soar
Na regra com que trabalhamos no exemplo anterior, notamos a utilização da notação de ".". Ela facilita a escrita e o entendimento do programa quando queremos tratar de um elemento da memória de trabalho com augmentações subsequentes. Sem isso a expressão:
(state <s> ^io.output-link <ol>
Deveria ser reescrita como:
(state <s> ^io <i>)
(<i> output-link <ol>)
Uso do SoarJavaDebugger para acompanhar o processo de escolha e aplicação de operadores, por meio de traces.
Na atividade 1 demonstramos como obter as informações da fase de proposição de operadores, tomada de decisão e aplicação de operadores. Também foi possivel através do comando print, analisar a memória de trabalho e observar os objetos de entrada que são utilizados pela tomada de decisão, e os objetos de saída, utilizados para execução do movimento pelo programa.
Diferença entre ações o-supported e i-supported, e WMEs persistentes
As ações o-supported são geradas por regra de aplicação de operador, que criam elementos na memoria de trabalho persistentes. Um exemplo foi o elemento I3 ^move M2 do exemplo da atividade 1, que precisava ser retirado explicitamente pela aplicaçao de uma regra especifica para isso.
Já ações i-supported não criam elementos persistentes na memória de trabalho, sendo estes eliminados assim que a condição da regra passa a não ser verificada. Como exemplo podemos citar as regras de proposição de operadores.
Uso de preferências entre operadores
A utilização de preferência entre operadores