You are here

Relatório 3 - SOAR: Tutorial 2

Sumário

Exercício 1

Nessa atividade será usado o jogo Eaters. Nesse jogo, PACMANs comedores (eaters) competem entre si para consumir alimento em um mundo virtual simples, que consiste de uma grade retangular, com 15 quadrados de largura por 15 quadrados de altura. Há paredes que ligam os quatro cantos da grade e paredes no interior da grade. Essas últimas são geradas aleatoriamente a cada novo jogo.

Cada eater novo é colocado no jogo em uma posição aleatória. Além disso, no jogo, existem dois tipos de alimentos: alimentos normais (são círculos azuis e valem 5 pontos) e alimentos bônus (são quadrados vermelhos e valem 10 pontos). Os eaters consomem esses alimentos movendo-se pela grade.

Um eater pode perceber por meio de seus sensores o conteúdo das células (eater, comida normal, comida bônus e quadrado vazio) numa distância de até dois quadrados em todas as direções. Em cada jogada, um eater pode se mover para o norte, sul, leste ou oeste e pode saltar dois quadrados em qualquer uma dessas direções. Além disso, ele pode saltar uma parede ou outro comedor. Porém, cada salto custa 5 pontos.

Outro detalhe importante é que quando dois eaters tentam ocupar a mesma célula ao mesmo tempo, eles colidem. Como resultado dessa colisão, é feita a média de sua pontuação e os dois são teleportados para novas posições aleatórias na grade.

Agora que o jogo foi apresentado, será mostrado como criar um novo agente que usa uma regra simples. Para isso, basta clicar no botão New conforme mostrado na Figura 1.1.


Figura 1.1: Criando um novo agente.

Depois de clicar em New, você pode selecionar um programa em Soar para definir o comportamento do novo eater, conforme é mostrado na Figura 1.2.


Figura 1.2: Criando um novo agente.

Conforme foi solicitador na atividade, foi selecionado o programa move-to-food.soar que acompanha o Soar. Veja na Figura 1.3.


Figura 1.3: Escolhendo características do novo agente.

Após finalizar a criação do novo eater, o Soar Debugger é aberto, conforme mostra a Figura 1.4. Ele pode ser utilizado para enviar comandos para o jogo Eaters ou verificar detalhes da memória de trabalho durante a execução do jogo.


Figura 1.4: Eaters e Soar Debugger.

Na janela do jogo Eaters é possível visualizar os eaters que existem no mundo virtual. Para executar alguma ação com algum deles, tais como excluir o eater, ativá-lo, ver a sua pontuação, entre outras possibilidades, basta selecionar o seu nome e executar a ação desejada. Veja detalhes na Figura 1.5.


Figura 1.5: Detalhes do jogo Eaters.

Usando o programa move-to-food.soar para definir o comportamento do eater, após executá-lo, ele consome os alimentos que estãoem seu caminho. Enquanto houver alimento, ele se movimenta pelo mundo virtual. Porém, quando ele chega em uma quadrado que não tem alimento ele não consegue se movimentar mais. Veja na Figura 1.6, o mundo virtual após a ativação do eater.


Figura 1.6: Mundo virtual após a ação do eater.

A qualquer momento é possível reiniciar o jogo Eaters. Quando isso é feito, os alimentos são repostos em locais aleatórios. Além disso, as paredes do interior da grade também são recriadas em locais aleatórios e os agentes que já estavam no mundo virtual, são também aleatoriamente reposicionados. Veja na Figura 1.7.


Figura 1.7: Reiniciando o jogo.

Um eater pode ser clonado e, desta forma, o novo eater recebe o mesmo conjunto de regras que defini o comportamento do eater original. Veja na Figura 1.8 como clonar um eater e o resultado dessa ação.


Figura 1.8: Clonando um eater.

Para entender como o programa move-to-food.soar defini o comportamento de um eater, temos que entender sua lógica de funcionamento. Para isso, foi analisado o código desse programa, que é apresentado na Figura 1.9.


Figura 1.9: Programa move-to-food.soar.

Podemos ver na Figura 1.9, que o programa move-to-food.soar possui 3 regras: propose*move-to-food, apply*move-to-food e apply*move-to-food*remove-move. Essas três regras definem o comportamento do eater, formando um ciclo de decisão, que segue a estrutura apresentada na Figura 1.10.


Figura 1.10: Ciclo de operação do Soar.

A estrutura apresentada na Figura 1.10 mostra que o eater recebe dados dos seus sensores (input). Depois disso, a regra propose*move-to-food, que defini seu comportamento do eater, é proposta. A condição para que essa regra seja aplicada é que exista informação na memória de trabalho, originada no objeto input-link, conforme mostrado na Figura 1.9. Depois que a regra é proposta e suas condições são satisfeitas, a regra apply*move-to-food, que aplica o operador, é ativada. Então, o eater realiza uma ação no mundo, que nesse caso é a movimentação para o lado que tenha alguma comida. Essa ação é representada na memória de trabalho por meio do objeto output-link. A Figura 1.11 mostra o conteúdo desses objetos, monitorado no Soar Debugger, durante a ação do eater.


Figura 1.11: Input-link e output-link.

Como pode ser visto na imagem acima, no objeto input-link há informações sobre a posição atual do eater, além de informações de suas células vizinhas que estão no campo do seu sensor. Se há mais de uma célula com alimento, o eater irá se mover para uma delas de forma aleatória.

Conforme mencionado, a ação do eater forma um ciclo e, por isso, depois que ele executa um movimento, o objeto input-link é novamente realimento, o que ativa novamente as regras de proposição e seleção do operador move-to-food, que gera um novo movimento e consequentemente uma nova saída. Então, o acumulu de movimentos pode se tornar um problema, porque os elementos da memória de trabalho criados por esses movimentos são originados a partir da aplicação de um operador. No Soar, todos elementos da memória de trabalho que são criados por uma regra de aplicação do operador são persistentes, ou seja, continuam na memória de trabalho. Diante disso, o acumulo de movimentos, aumenta o número de elementos da memória de trabalho e isso pode sobrecarregá-la.

Para evitar que a memória seja sobrecarregada, foi criada a regra apply*move-to-food*remove-move, mostrada na Figura 1.9. Essa regra removew comandos de movimento antigos a partir do objeto output-link, quando o movimento do eater termina. O objeto output-link, cria um argumento depois que o movimento do eater é executado: ^status complete. Para remover a estrutura da memória de trabalho, a regra apply*move-to-food*remove-move rejeita o elemento da memória de trabalho, por meio do sinal "-", que aparece após o elemento ^move , conforme pode ser visto na Figura 1.9. Esse elemento, é criado na memória de trabalho, com a estrutura apresentada na Figura 1.11, que é a seguinte (I3 ^move m1). Quando esse elemento é removido, seus argumentos também são, logo os elementos apresentados na Figura 1.11, o m6 ^direction north e M6 ^status complete, também são removidos.

Podemos dizer que o jogo Eaters, usando o programa move-to-food.soar para definir o comportamento das criaturas, possui algumas limitações, pois as criaturas não conseguem se mover quando não há comida em uma de suas células vizinhas e a escolha da direção tomada por ele é aleatória quando mais de uma célula vizinha possui alimento. Para ser mais sofisticado, quando as células vizinhas do eater não possuem alimento, ele poderia continuar se movendo para células vizinhas vazias, até percorrer todo o mundo virtual ou parte dele, pois em algum momento poderia encontrar alimento. Além disso, a escolha da direção a ser tomada poderia ser inteligente. Por exemplo, imagine que exista comida na direção norte e na direção sul. Porém, após a célula vizinha na direção sul, há uma parede, enquanto após a célula vizinha na direção norte, há uma fila de alimentos. Então, em vez de escolher a direção aleatoriamente, seria mais inteligente a criatura se mover para a direção norte.

Exercício 2

Uso da interface de entrada e saída de um programa Soar

Como pode ser visto na Atividade 1, o jogo Eaters não inicializa o estado com um operador, mas sim com informações sobre sua situação no mundo virtual, por meio de uma estrutura de entrada, o input-link.

Para entender melhor a estrutura usada no Jogo Eaters, foi usada uma regra simples, que faz com qua a criatura se mova um passo para o norte.


Figura 2.1: Programa move-north.

Como pode ser visto na Figura 2.1, a primeira regra propôe o operador move-north, que é igual a regra de aplicação do programa Hello World, apresentado no relatório da aula 2.

A segunda regra aplica o operador move-north. Nessa regra, um comando de movimento é adicionado a um objeto output-link. Esse comando faz com que o eater se mova uma célula para o norte. Observe que na regra, ^output-link fornece um identificador para a ação, que é o . Então, a ação é adicionada para o ^output-link usando um atributo move que recebe o valor ^direction north, o que faz o eater se mover para o norte.

Ao executar o jogo Eaters, usando o programa apresentado na Figura 2.1 para definir o comportamento dos eaters, podemos ver detalhes do seu funcionamento, conforme é apresentado na Figura 2.1.


Figura 2.2: Detalhes da execução do programa move-north no Soar Debugger.

Pela Figura 2.1, é poissível ver que depois de executar a ação, o eater não tem mais o que fazer. Então um novo substado S13 foi criado.

Uso de shortcuts em programas Soar

A regra apply*move-north, mostrada na Figura 2.1, usa várias variáveis para fazer conexões entre os atributos. Por exemplo, é usado para corresponder ao nome do operador, é usado para corresponder ao objeto output-link e move conecta o output-link à direção final a ser tomada pelo eater.

Diante disso, para simplificar a regra, o Soar permite combinar as condições que são ligadas por variáveis. Então, podemos encadear os atributos, substituindo as variáveis intermediárias com um ".", para separar os atributos. A Figura 2.2, apresenta a regra apply*move-north simplificada.


Figura 2.2: Regra apply*move-north simplificada.

Uso do SoarJavaDebugger para acompanhar o processo de escolha e aplicação de operadores, por meio de traces

Para que os eaters consigam dar mais que um passo, é necessário que a regra de proposição do operador move-norte teste elementos da memória de trabalho que mudem a cada movimento do eater. A regra apresentada na Figura 2.1 testa apenas ^type state que fica para sempre na memória de trabalho. Logo, ela deve ser substituída por alguma regra que verifica informações na memória de trabalho recebidas pelos sensores da criatura, os input-links. O Soar é projetado de forma que as mudanças para o input-link são feitas seguindo o output-link e afetam as propostas do operador, o que forma o ciclo apresentado na Figura 1.10.

A Figura 2.2 apresenta a regra propose*move-north modificada.


Figura 2.2: Regra propose*move-north modificada.

Na nova regra propose*move-north, x e y guardam a posição do eater no mundo virtual e são alteradas a cada movimento.

Com a regra apresentada acima, quando os elementos da memória de trabalho para x e y são removidos, o operador move-north também é removido, pois a regra de instanciação que o criou não existe mais. Nas regras de proposição de operadores, os elementos associados ao operador só permanecem na memória de trabalho enquanto não sofrem nenhuma mudança. Depois disso, uma nova instância do operador move-north será criada pois ele será associado aos novos valores de x e y.

Para ver as mudanças na memória de trabalho, na janela do Soar Debugger, podemos digitar o comando "watch 4 --timetags". Com esse comando podemos analizar os traces que incluem a adição e remoção de todos os dados sensoriais que se alteram durante o movimento do eater. A adição de um elemento na memória de trabalho é precedida ppor "=>", enquanto a remoção é predida por "<=". Cada elemento da memória de trabalho é representada por um número, que é chamado de timetag.


Figura 2.2: Regra propose*move-north modificada.

Diferença entre ações o-supported e i-supported, e WMEs persistentes

Como foi mencionado no Exercício 1, a cada movimento do eater, novos elementos são criados na memória de trabalho e são acumulados, pois, eles são criados por uma regra de aplicação de um operador. Isso pode ocasionar um estouro de memória.

Elementos da memória de trabalho persistentes são chamados operator-supported ou o-supported, pois são criados por operadores. Por exemplo, na regra apply*move-north apresentada na Figura 2.2, a ação ^move.direction north é um o-supported.

Porém, é importante mencionar que nem todos os elementos da memória de trabalho são persistentes. Apenas os elementos criados por regras de aplicação persistem. Os elementos não persistentes, tais como os criados por regras de proposição de operadores, são chamados de instantiation-supported ou i-supported. Por exemplo, a regra propose*move-north, mostrado na Figura 2.2, não é uma regra de aplicação, pois não testa se um operador foi selecionado. Então, todas as suas ações são i-supported, tais como: (<s> ^operator <o> +) e (<o> ^name move-north).

Para remover os elementos persistentes depois que um movimento do eater foi finalizado, foi criado uma nova regra, que é apresentada na Figura 2.3.


Figura 2.3: Regra que remove elementos persistentes da memória de trabalho após a finalização de um movimento de um eater.

A regra apply*move-north*remove-move será disparada durante a próxima fase de aplicação do operador move-north, depois que o operador move-north for selecionado. Isso não interfere nas regras de aplicação de outros operadores, porque a regra apply*move-north*remove-move é executada em paralelo. Esse paralelismo é apenas simulado, mas é como se as regras fossem disparadas ao mesmo tempo.

O programa move-to-north, como está atualmente, faz com que o eater se mova para o norte até encontrar uma parede. Quando encontra uma parede, ele não consegue mais se mover e fica gerando estados infinitamente, pois não consegue mais aplicar e terminar o operador move-north. Além disso, os movimentos antigos feitos pelo eater serão removidos da memória de trabalho, por meio da regra apply*move-north*remove-move. Os detalhes da nova versão do jogo Eaters foram analisados na Soar Debugger, usando o comando watch 3 --timetags e são apresentados na Figura 2.4.


Figura 2.4: Detalhes da nova versão do jogo Eaters no Soar Debuuger.

Uso de preferências entre operadores

Usando o programa move-to-north cada eater se move apenas para a direção norte. Porém, esse programa foi melhorado para que o eater se mova para qualquer direção que tenha comida em sua vizinhança. Como pode haver mais de uma célula vizinha com alimento, então pode ser proposto mais de um operador. Porém, como o Soar não consegue selecionar aleatoriamente um operador, que tenha apenas preferência aceitável, ele gera um impasse. Para evitar isso, pode-se adicionar preferências adicionais para os operadores. Como não importa qual alimento seja consumido primeiro, temos que adicionar preferencias chamadas indiferentes. Esse tipo de preferência faz com que o Soar selecione aleatorimente um dos operadores. Então, devemos adicionar um sinal de "+" após o operador, para indicar que o operador é aceitável, juntamente com um sinal de "=", para indicar que é também uma preferência indiferente. Seguindo essas instruções, as regras de seleção e aplicação dos operados do novo pograma criado para definir o comportamento do eater, é apresentado na Figura 2.5.


Figura 2.5: Regras de seleção e aplicação dos operadores do novo programa move-to-normalfood-bonusfood

No programa apresentado na Figura 2.5, há uma regra que propõe o operador move-to-food quando há uma comida normal (normal food) e uma regra que propõe o operador quando a uma comida bônus (bonus food) na vizinhança do eater. Como o eater pode se alimentar tanto de comida normal, quanto de comida bônus, os dois operadores possuem preferência aceitável e indiferente, ou seja, o Soar pode selecionar um dos dois aleatoriamente. Os dois operadores, que usam o mesmo identificador, são aplicados por meio da mesma regra, a apply*move-to-food.

Uso de extensões em regras

As duas primeiras regras apresentadas na Figura 2.5 são semelhantes e se diferem apenas no tipo de alimento (content), que pode ser normalfood ou bonusfood. Então, é possível escrever uma única regra que teste os dois valores possíveis. Esse valores podem ser escritos na mesma posição com um único valor, mas cercados por colchetes duplos: << normalfood bonusfood >>. A Figura 2.6 apresenta a regra que pode substituir as duas primeiras regras da Figura 2.5.


Figura 2.6: Regra move-to-food

Também foi criada um regra para rastrear a direção do operador selecionado. Essa regra testa qual operador é selecionado e escreve a direção tomada pelo eater. Outro ponto importante é que ela tem a mesma característica da regra apply*move-to-food*remove-move, pois também é executada em paralelo com a regra apply*move-to-food. Veja a regra na Figura 2.7.


Figura 2.7: Regra para monitorar a direção de movimento do eater.

A Figura 2.8 mostra o efeito produzido pela adição da regra monitor*move-to-food.


Figura 2.8: Direção impressa pela regra monitor*move-to-food.

Conforme pode ser observado na Figura 2.8, a regra monitor*move-to-food imprimiu a direção tomada pelo eater em duas movimentações do eater exibidas no Soar Debugger.

Uso do VisualSoar para detectar erros em regras

Ao escrever programas no Soar, podem se cometidos dois tipos de erros: sintático e semântico. O primeiro tipo de erro ocorre quando é feita uma construção mal formada de uma regra ou de outras estruturas do Soar. Já o segundo ocorre quando a algum problema na lógica de formação de uma regra ou outras estruturas do programa.

Os principais erros sintático ocorrem nas seguintes situações:

  • falta o caracter "#" nos comentários;
  • falta {;
  • falta (;
  • sobra );
  • falta };
  • falta state;
  • falta ^.

Para evitar esses erros, é recomenda que se use o Visual Soar para escrever os programas e localizar possíveis erros sintáticos. No Visual Soar, para procurar por erros sintáticos, basta clicar no menu Datamap e selecionar a opção Check All Productions for Syntax Errors, conforme mostrado na Figura 2.9.


Figura 2.9: Procurar erro sintáticos usando o Visual Soar.

Caso haja erros, ele será apresentado na janela de Feedback do Visual Soar. Para ver a posição do erro no código do programa, basta dar um duplo clique no erro apontado na janela de Feedback. Veja na Figura 2.10.


Figura 2.10: Erro apontado na janela de Feedback do Visual Soar.

Uso de comandos do Debugger em tempo de execução

O Soar permite que se faça debugging em tempo de execução, por meio da adição de instruções de escrita na ação das regras. Por exemplo, para controlar todos os elementos associados à regra de proposição do operador move-to-food, incluindo sua direção e alimento esperado, a regra propose*move-to-food, apresentada na Figura 2.6, foi alterada conforme mostrado na Figura 2.11.


Figura 2.11: Regra propose*move-to-food alterada.

Na Figura 2.10, o comando write, insere um retorno de carro, seguido por uma quebra de linha, usando o comando (crlf). Depois é impresso o texto Propose move, seguido pela direção do alimento, armazenada na variável dir. Em seguida imprimi-se, na mesma linha, o texto , for, seguido pelo tipo de alimento, armazenado na variável type. O resultado da atualização da regra propose*move-to-food pode ser vista na análise da execução da ação do eater, por meio do programa Soar Debugger, conforme é apresentado na Figura 2.12.


Figura 2.12: Debugging usando o Soar Debugger.

Outra forma de fazer debugging em tempo de execução no Soar é através de comandos do Soar Debugger. Um de seus comandos mais usados é o print, que imprime a estrutura da memória de trabalho. Esse comando possui uma variedade de argumentos, sendo que um deles é a profundidade. Por exemplo, se você quer imprimir o estado atual com profundidade 2, basta inserir o comando print -–depth 2 s1. Outra forma de imprimir, é clicando com o botão direito do mouse no elemento desejado, e escolher o tipo de impressão. Veja na Figura 2.13.


Figura 2.13: Comando "print -–depth 2 s1".

Na Figura 2.13, a área com retângulo vermelho, acima da linha de comando do Soar Debugger, contém todos os argumentos do estado atual, impressos pelo comando print -–depth 2 s1.

Outro comando que pode ser usado é o wmes, que imprimi um elemento individual da memória de trabalho. Por exemplo, veja o resultado do comando wmes O8, destacado na cor azul, apresentado na Figura 2.14.


Figura 2.14: Comando "wmes O8".

O Soar Debugger também possui um comando para imprimir todas as preferências de um determinado operador. Esse comando é o preferences. Um exemplo de seu uso, destacado em azul, é apresentado na Figura 2.15.


Figura 2.14: Comando "preferences S1 operator".

Outros comandos do Soar Debugger podem ser consultados no manual do usuário do Soar, disponível no seguinte link: Soar User’s Manual

Exercício 3

Nessa atividade foi desenvolvido um programa Soar, para definir o comportamento das criaturas do jogo Eaters, que evita que elas fiquem presas em posições que não tenha comida, além de dar a elas a capacidade de pular e lembrar o último passo, para que não volte na célula que acabou de sair.

A tarefa foi dividida em três etapas. Na primeira, foi construído um programa que evita que o eater fique preso em posições em não haja comida. Com esse programa, o eater consegue encontrar todos os alimentos, e como há prefêrencia por alimento bônus (bonusfood), normalmente esse tipo de alimento acaba primeiro.

Com o programa desenvolvido na primeira etapa dessa atividade, o eater faz movimentos poucos inteligentes, pois várias vezes ele volta para uma célula que ele acabou de sair. Diante disso, na segunda etapa, foi criado um programa que evita que o eater volte para uma célula que ele acabou de sair.

Na terceira etapa, foi construído um programa que dá ao eater a capacidade de pular. Com isso, ele pode, por exemplo, pular uma parede para se alimentar de uma comida que está do outro lado. Porém, como um pulo custa 5 pontos, foram criadas preferências para que o eater escolha a opção que dará maior pontuação para ele. Por exemplo, se há uma comida normal próximo ao eater, é melhor ele se alimentar dela, do que pular para se alimentar de outra comida normal. Porém, se há uma célula vazia como opção de movimento, e há uma comida bônus do outro lado de uma parede, é melhor que ele pule e se alimente da comida bônus, que lhe renderá 5 pontos (10 pontos da comida bônus, subtraído de 5 pontos referentes ao pulo). O download do arquivo com o programa desenvolvido na terceira etapa, pode ser feito usando o link apresentado abaixo:

Observe, no vídeo a seguir, o comportamente do eater, usando o programa desenvolvido na última etapa dessa atividade.

 

Exercício 4

Nesse exercício foi desenvolvido um conjunto de regras Soar que faz com que o eater, sistematicamente, busque por comida, quando estiver cercado por células sem comida.

A lógica das regras desenvolvidas é a seguinte:

  • Quando o eater percebe que está cercado por células vazias, ele olha para o conteúdo das células que estão a uma distância 2. Caso haja comida nessas células, ele se direciona para uma delas. Porém, isso só acontece se não houver nenhuma parede separando o eater dessa comida.
  • Se o eater estiver cercado por células sem alimento, e não há alimento a uma distância 2 sem estar separado por alguma parede, ele continua seguindo a mesma direção. Então, se por exemplo, o último movimento do eater era para o norte, ele continua se movimentando para o norte.
  • Se o eater estiver seguindo na mesma direção, por estar cercado por células sem alimento, e encontra uma parede, ele muda de direção. Essa mudança ocorre da seguinte forma:
    • se o eater estava seguindo para o oeste e encontra uma parede, ele segue para o sul;
    • se o eater estava seguindo para o sul e encontra uma parede, ele anda uma posição para o leste;
    • se o eater estava seguindo para o leste e encontra uma parede, ele segue para o norte;
    • se o eater estava seguindo para o norte e encontra uma parede, ele segue para o oeste.

Com as simulações que foram realizadas, percebeu-se que as regras que foram propostas nesse exercício são muito eficientes para que o eater encontre os alimentos que estão nas extremidades do mundo virtual. Porém, elas dificultam o acesso da criatura aos alimentos que estão mo meio do mundo virtual, principalmente, devido à presença das paredes no meio do caminho. Logo, em geral, o tempo para o consumo dos alimentos usando essas regras é maior do que usando as regras propostas no exercício 3.

O download do projeto do programa criado nesse exercício pode ser feito pelo link a seguir:

Abaixo pode ser conferido o vídeo que mostra o eater explorando o mundo virtual, usando as regras propostas nesse exercício:

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer