You are here

Atividade 1

Jogo Eaters

 

    O Jogo Eaters se assemelha ao jogo, bem conhecido, PacMan. Neste jogo, os eaters (criaturas / agentes) competem pela comida disponível no seu ambiente bi-dimensional. A figura abaixo mostra uma tela do jogo:

    O ambiente do jogo é composto pelos próprios eaters, por paredes (Walls), células sem comida (Empty Square), e células com comida (blue, red). O mundo em que vivem esses agentes/criaturas (eaters) é composto por um retângulo de 15 x 15 células, onde as divisórias internas (paredes) são geradas aleatóriamente cada vez que um novo jogo é criado. Cada criatura é criada, aleatóriamente, numa posição qualquer. Todas as demais células do jogo estão preenchidas com comida (conforme as criaturas se movem pelas células, começam a surgir células vazias, sem comida). Há dois tipos de comida: comida normal (blue, que vale 5 pontos) e comida bônus (red, valendo 10 pontos). As criaturas podem visualizar o conteúdo das células (eater, red, blue, wall, empty) que estejam até 2 células à frente em qualquer direção. Cada criatura pode se mover, apenas, uma célula por vez nas direções norte, sul, leste ou oeste. Cada criatura pode "saltar" sobre uma parede ou sobre outra criatura. Quando a criatura salta, ela não consome a comida disponível naquela célula, e este movimento lhe custa 5 pontos. Quando duas criaturas colidem, suas pontuações são recalculadas (sendo uma média das pontuações de ambas criaturas) e as criaturas são "teleportadas" para novas posições aleatórias.

Criando um Eater

    Para criar uma nova criatura (eater), primeiro é necessário rodar o aplicativo Eaters. Será exibida a tela a seguir:

    Ao pressionar o botão New, será exibida uma janela que permite instanciar uma nova criatura no jogo. Nessa janela, pode-se escolher a cor da criatura e lhe atribuir um nome. Antes de instanciar a nova criatura, é necessário escolher as produções que vão gerar o comportamento da criatura. Para isso, pressiona-se o botão Soar e seleciona-se o arquivo desejado, neste caso, o arquivo move-to-food.soar (na pasta /Agents/Eaters/tutorial/) e pressione o botão Create Agent. O agente será criado e o aplicativo SOAR Debugger também será aberto. A figura, a seguir, exibe a nova tela do jogo:

    Pode-se visualizar a nova criatura (em vermelho) que foi criada, também há uma referência para o seu nome e sua pontuação atual (Name:red, Score:0). Ao pressionar o botão Run (nesta tela ou no SOAR Debugger) a criatura começa a navegar pelo seu ambiente, em busca de comida e consumindo-a. Ao pressionar-se o botão Stop, a criatura pára de se mover. A figura, a seguir, ilustra essa situação:

    Percebe-se que a criatura executou alguns movimentos, consumindo comida e aumentando sua pontuação (Score:190). Ao clicar sobre a criatura, é exibida (canto inferior direito da tela) a informação perceptual da criatura sobre o seu ambiente (até 2 células em todas as direções). No entanto, as regras da produção move-to-food consideram apenas as células adjacentes à criatura: norte, sul, leste e oeste. A tela do SOAR Debugger exibe as decisões tomadas pela criatura, conforme a figura a seguir:

    Na primeira linha da Janela de Interação, pode-se visualizar que o primeiro operador a ser selecionado foi o operador move-to-food. Sucessivas seleções desse operador foram realizadas, movendo a criatura para novas células, em várias direções, à procura de comida. Pode ocorrer de a criatura parar de se mover. Isso acontece quando a criatura está cercada por células sem comida. (Basta deixar a criatura continuar se movendo, que essa situação eventualmente ocorrerá). Isso acontece devido à simplicidade das regras da produção usada, que apenas move a criatura se esta detecta uma célula com comida ao seu redor. Nesse ponto, quando a criatura não sabe o que fazer, ao invés de selecionar um operador (move-to-food), o agente começa a gerar novos estados (figura abaixo). Este ponto será abordado posteriormente, ou seja, serão escritas novas regras que tiram proveito dos novos estados gerados.

 

Criando um Eater: passo a passo

   Usando o mesmo agente criado anteriormente, agora vamos analisar o que acontece (passo a passo) durante a movimentação da criatura pelo seu ambiente. Basta pressionar o botão Reset, e um novo jogo será criado, mantendo o mesmo agente criado anteriormente. Para visualizar as decisões tomadas pelo agente, será usado o SOAR Debugger. Basta clicar no botão Step, e a criatura fará apenas um movimento por vez. Clicando-se nos botões Watch 3, Watch 5 , é possível obter uma melhor descrição do processo de tomada de decisões pela criatura. Esta é a tela obtida, para um movimento da criatura, usando o nível de visualização 5:

    As informações da Janela de Interação não foram expandidas, para maior clareza. Basta clicar no símbolo "+" de cada linha da janela para obter as informações detalhadas sobre a seleção de operadores e os elementos da memória de trabalho. A figura abaixo mostra a situação atual da criatura no seu ambiente:

    Primeiramente, é interessante notar a estrutura da memória de trabalho referente às informações perceptuais da criatura. No SOAR Debugger, a aba input exibe as seguintes informações:

(I2 ^eater E2 ^my-location M1 ^random 0,)
 (E2 ^name red ^score 15 ^x 10 ^y 7)
 (M1 ^content eater ^content-name red ^east E3 ^north N1 ^south S3 ^west W1)

    O WME E2 exibe o nome da criatura e a sua pontuação, além da sua posição. O elemento M1 mostra as informações captadas pela criatura. A produção move-to-Food permite visualizar apenas as células adjacentes à criatura. Esse elemento, M1, é composto por outros WME's referentes às células adjacentes à criatura. Expandindo a última linha da Janela de Interação, pode-se visualizar que os elementos E3, N1, S3 e W1 se referem, respectivamente, a uma parede, comida (blue), e células vazias. Conforme a figura acima, pode-se verificar que essas mesmas informações estão representadas na janela referente aos sensores da criatura.

    A aba output (linha de comando: print --depth 2 i3) exibe o último operador selecionado (M4 ^direction north ^status complete), que se refere ao operador O6 na penúltima linha da janela de interação. Expandindo-se essa linha, é possível visualizar que esse operador, O6, se refere ao movimento direction north. A aba op_pref exibe as preferências para a seleção dos operadores. A aba operator (linha de comando: print <o>) exibe o operador que foi selecionado, para o próximo passo da criatura. Neste caso, o operador O8 cuja ação é direction north. Pela lista de operadores selecionados, pode-se rastrear os movimentos realizados pela criatura: O1 (direction south), O5 (direction east), O6 (direction north), O8 (direction north). É interessante visualizar as fases de proposição e decisão dos operadores, conforme expansão da última linha:

--- propose phase ---
Firing propose*move-to-food
-->
(O8 ^direction north +)
(O8 ^name move-to-food +)
(S1 ^operator O8 =)
(S1 ^operator O8 +)
Retracting propose*move-to-food
-->
(O6 ^direction north +)
(O6 ^name move-to-food +)
(S1 ^operator O6 =)
(S1 ^operator O6 +)
Retracting propose*move-to-food
-->
(O7 ^direction south +)
(O7 ^name move-to-food +)
(S1 ^operator O7 =)
(S1 ^operator O7 +)
=>WM: (252: S1 ^operator O8 +)
=>WM: (251: O8 ^direction north)
=>WM: (250: O8 ^name move-to-food)
<=WM: (215: S1 ^operator O6 +)
<=WM: (217: S1 ^operator O6)
<=WM: (216: S1 ^operator O7 +)
<=WM: (212: O6 ^direction north)
<=WM: (211: O6 ^name move-to-food)
<=WM: (214: O7 ^direction south)
<=WM: (213: O7 ^name move-to-food)
--- decision phase ---
=>WM: (253: S1 ^operator O8)
     4: O: O8 (move-to-food)

    Foram propostos três operadores (O8, O6, O7) e o operador O8 foi, de fato, selecionado. Também é interessante observar (logo no início da linha expandida) que foram aplicadas duas regras: apply*move-to-food e apply*move-to-food*remove-move. A segunda regra será discutida posteriormente.

Criando um Eater: clone do agente

    Na tela do aplicativo Eaters, pressiona-se o botão Clone. Um novo agente será criado. Esta nova criatura possui a mesma produção (move-to-food) que controlará o comportamento da criatura (eater). Agora o jogo possui duas criaturas, conforme ilustra a figura abaixo:

    Uma criatura, red, é o mesmo agente criado previamente para os experiementos anteriores. A outra criatura, orange, é o novo clone do agente prévio. Além das duas criaturas, também foram abertas duas janelas do aplicativo SOAR Debugger, uma janela para cada criatura do jogo. Clicando-se sobre cada eater, pode-se visualizar suas informações sensoriais (parte inferior direita da figura acima). Neste caso, pode-se visualizar as informações da criatura orange.

    Após rodar o jogo, as figuras abaixo exibem os operadores selecionados por cada criatura, além das suas informações sensoriais, conforme mostram as telas do SOAR Debugger de cada agente:

Criatura red:

Criatura orange:

    A figura abaixo exibe a tela do jogo após os movimentos realizados pelas criaturas, de acordo com os operadores selecionados (figuras acima) para cada criatura:

    As telas do SOAR Debugger exibem o nome de cada criatura (parte superior direita) e suas respectivas pontuações. Ao clicar sobre o botão Step, ambas as criaturas executam um passo em seus movimentos pelo ambiente do jogo. É possível visualizar os detalhes de cada movimento das duas criaturas. Basta expandir (símbolo "+") as linhas referentes aos operadores selecionados, em cada tela do SOAR Debugger de cada criatura.

Visualizando a produção move-to-food

    Através do uso do aplicativo VisualSoar, pode-se visualizar as regras que compõem a produção move-to-food e entender como essas regras funcionam. A figura abaixo ilustra a estrutura das regras propose*move-to-food e apply*move-to-food (existe mais uma regra, apply*move-to-food*remove-move, não mostrada nesta figura):

    Esta produção propõe a aplicação do operador move-to-food em qualquer direção que possua comida do tipo normal (blue) ou do tipo bônus (red). Caso não haja comida por perto, nenhuma instância desse operador será proposta, e o operador halt será proposto. Primeiramente, é necessário entender a estrutura de entrada (I2, io.input-link) deste agente, conforme o exemplo abaixo:

(I2 ^eater E2 ^my-location M1 ^random 0.9530344009399414)
  (E2 ^name red ^score 95 ^x 10 ^y 2)
  (M1 ^content eater ^content-name red ^east E3 ^north N1 ^south S3 ^west W1

    O WME I2 contém um WME M1 (^my-location) responsável pela percepção das células adjacentes à criatura. Este elemento, M1, exibe o conteúdo das células nas direções consideradas (east, north, south, west). A regra propose*move-to-food analisa o conteúdo de M1 (^io.input-link.my-location), verificando o conteúdo de cada célula nas direções consideradas (^io.input-link.my-location.<dir>.content). Caso o conteúdo da célula, numa dessas direções consideradas, seja comida normal ou tipo bônus (<< normalfood bonusfood >>) a regra propõe a aplicação do operador move-to-food na direção considerada (<o> ^name move-to-food ^direction <dir>). A regra também indica que o operador pode ser selecionado randomicamente (<s> ^operator <o> + =).

    A regra apply*move-to-food aplica, de fato, o operador selecionado aleatóriamente. Esta regra verifica se um operador do tipo move-to-food, numa direção qualquer, foi selecionado (<o> ^name move-to-food ^direction <dir>) e gera um comando na estrutura de saída do agente (I3, io.output-link) para mover a criatura naquela direção (<ol> ^move.direction <dir>). A produção move-to-food contém mais uma regra, conforme figura abaixo:

    A regra apply*move-to-food*remove-move analisa a estrutura de saída (I3, io.output-link). Um exemplo dessa estrutura está ilustrado a seguir. Pode-se ver que o WME I3 possui um WME, M16 (^move), que representa um comando do tipo move, que contém a direção do movimento (^direction) e se o mesmo já terminou sua execução (^status complete):

(I3 ^move M16)
  (M16 ^direction east ^status complete)

    Essa regra, apply*move-to-food*remove-move, verifica se um operador do tipo move-to-food foi selecionado (state <s> ^io.output-link <ol> ^operator.name move-to-food) e se há, em I3, um comando move (<ol> ^move <move>) que já terminou sua execução (<move> ^status complete). Caso essas condições sejam satisfeitas, a regra remove esse comando (elemento) da memória de trabalho (<ol> ^move <move> -). Caso essa regra não fosse aplicada, por exemplo, comentando-se esta regra no arquivo da produção move-to-food, a estrutura de saída (I3, io.output-link) fica alterada:

(I3 ^move M5 ^move M4 ^move M3 ^move M2 ^move M6)
  (M5 ^direction east ^status complete)
  (M4 ^direction east ^status complete)
  (M3 ^direction south ^status complete)

    Todos os movimentos realizados pela criatura continuam na memória de trabalho, como WME's, podendo causar uma "explosão" da memória de trabalho. Por isso, a importância de se aplicar esta regra sempre após a criatura ter executado um movimento no seu ambiente.

    Essa produção, move-to-food, é muito simples, portanto, também muito limitada. A criatura não tem a opção de escolher uma comida de maior pontuação (bonus food), em detrimento a uma de pontuação menor (normal food). Também não existe a opção da criatura poder escolher saltar sobre uma parede, e nem é tratado o caso de uma colisão entre duas (ou mais) criaturas. Além disso, essa produção não funciona adequadamente de acordo com as condições possíveis de serem encontradas no ambiente virtual, mundo, dos eaters. Como a criatura só tem acesso a uma única célula, em todas as direções, pode ocorrer da criatura ficar "presa" numa célula sem comida ao seu redor. Para este caso, as regras da produção move-to-food apenas disparam estados (S13, S15,...) e não propõem operadores. Para sanar esse problema, uma possível solução seria analisar o conteúdo das células vizinhas até duas células ao redor da criatura (que é o limite máximo de visualização permitido pelos seus sensores), e executar um movimento do tipo jump (saltar), saltando para outra célula. Essa solução será abordada mais adiante em outros exemplos deste tutorial.

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer