You are here

Aula 4 - SOAR Tutorial 3 - TankSoar

Aula 4 - SOAR Tutorial 3 - Tank Soar

Atividade 1 - Conhecendo o TankSoar

O Tank Soar é similar ao Eaters, no que diz respeito a um programa Soar que controla um agente dentro de um mundo virtual baseado em um tabuleiro formado por células. Porém, dessa vez nosso agente é um tanque de guerra com mais sensores e mais ações. E o fato de haver interações entre tanques tornam os problemas muito mais complexos.

Abaixo temos uma visão do campo no qual nosso tanque irá trafegar:

No mapa podemos verificar a existencia de um tabuleiro de 14x14 células, sendo as bordas cercadas por paredes de pedra. Os obstáculos interiores são árvores.

Cada agente Soar controlará um único tanque, que pode ocupar apenas uma célula do tabuleiro. Os tanques podem tomar ações, carregar recursos e ler informações de seus sensores que os permitem observar o ambiente.
Entre os recursos que os tanques podem carregar temos:

  • Mísseis - Todo tanque começa com 15 mísseis, e cada vez que passam por um pacote de mísseis, 7 são somados a quantidade sendo carregada.
  • Saúde - Cada tanque tem 1000 pontos de saúde e morre quando esse número alcança o zero. Cada vez que um tanque é atingido com os escudos abaixados, essa valor cai 400 pontos. A saúde pode ser recarregada ao passar por um carregador de saúde, que incrementa 150 pontos por vez.
  • Energia - Cada tanque tem 1000 pontos de energia. Os tanques gastam energia para usar seus radares e para levantarem seus escudos. Para recarregar a energia basta passar por um carregador de energia, que incrementa 250 pontos por vez. Cada vez que um tanque é atingido com os escudos levantados, 250 pontos de energia são decrementados. Se a energia chegar em zero, o tanque não será capaz de usar o escudo nem o radar.

Abaixo temos uma mostra do Tank Soar rodando com dois tanques:

Além dos itens que podemos encontrar no mapa, temos seis sensores primários que auxiliam nas decisões do tanque:

  • Blocked sensor - o sensor de bloqueio detecta se as células imediatamente adjacentes ao tanque estão bloqueadas ou livres, não fazendo distinção entre qual item está bloqueado o acesso às células adjacentes.
  • Incoming sensor - detecta se há mísseis aproximando-se a qualquer ditância, a menos que o míssel esteja atrás de um obstáculo ou outro tanque. Esse sensor ignora os mísseis do próprio tanque.
  • Radar sensor - Ativado apenas quando o tanque tem energia suficiente para usá-lo. A distância que o radar consegue perceber é dada pela configuração do radar ou pelo número de células entre o tanque e o obstáclo mais próximo, qual for menor. O radar consegue captar objetos em um campo de 3 células de largura à frente e nas células laterais adjacentes. A informação recebida por esse sensor é dada pelo tipo e lugar onde foram percebidos objetos.
  • Rwaves sensor - detecta ondas de radar provenientes de outros tanques rastreando nossa posição.
  • Smell sensor - detecta o tanque inimigo mais próximo com informações como quão perto está e qual sua cor. Se há dois tanques igualmente próximos, então um é aleatoriamente escolhido pelo radar. Esse radar penetra obstáculos, portanto o tanque mais próximo pode não ser o mais fácil de se aproximar.
  • Sound sensor - detecta o tanque mais próximo que se moveu durante a última decisão, desde que o tanque esteja a 7 ou menos células de distância. Se há um tanque nesse raio, porém que não tenha se movido na última decisão, nenhum sinal será captado por esse sensor.

Para verificar o status corrente dos sensores de um tanque, selecione o tanque no mapa e visualize as informações à direita do painel:

Atividade 2 - Atividades do tutorial

Um simples tanque vagante

O tutorial começa com o desenvolvimento de um simples tanque que vaga pelo mapa usando seus sensores para evitar colisões com obstáculos e para detectar outras objetos.
O principal recurso que um tanque dispõe para perceber objetos é seu radar, que funciona a partir da frente do tanque. O radar usa energia, então é melhor desliga-lo quando não houver nada interessante em volta.

Essa análise consiste de três operadores e o controle de busca descritos a seguir:

  • Move - move-se para frente se não estiver bloqueado.
  • Turn - se a frente estiver bloqueada, rotacione e ligue o radar com poder 13.
  • Radar-off - se o radar estiver ligado e não há objetos visíveis, desligue-o.
  • Se o operador 'radar-off' foi proposto, prefira-o sobre o 'move'.

Para iniciarmos a codificação das regras, usarei como base o agente padrão providenciado com o tutorial, que já inclui regras gerais de saída e um data map completo para os links de entrada e saída do agente.

Tentei rodar o tanque apenas com as regras apresentadas na seção 2, porém o tanque não se comportou como o esperado, eis a saída do debugger:

Foi quando eu percebi que não há regras de aplicação para os operadores apresentados. E então, como não fica muita claro no tutorial qual projeto devemos usar de base, resolvi usar o "simple-bot" que contém já as regras apresentadas e mais.
O comportamento esperado, então foi atingido, o tanque simplesmente vaga pelo mapa, mantendo o radar ligado quando consegue rastrear algum item à frente e desligando-o quando nada está em vista.
Percebe-se que depois de um tempo rodando, o tanque fica sem pontos de energia e não consegue mais usar o radar.

Abaixo, a saída do debugger com o projeto base em execução:

Os operadores apresentados nessa seção foram os seguintes:

Move

Turn

Radar-off

Esse não parecia estar em lugar algum nas regras do projeto base, então o incluí. Porém, antes da inclusão dessa regras, o tanque desliga o radar quando não consegue perceber nada. O tutorial sugere que essa regra esteja junto da regra de aplicação do operador 'move'. De qualquer forma, a regra no visual soar ficaria assim:


Sub-objetivos

As regras que escrevemos na seção anterior poderiam ser extendidas e regras para perseguir e atacar tanques inimigos, recarregar, e coletar misseis poderiam ser adicionadas para formar um tanque mais completo. Porém, teríamos que adicionar muito mais regras de controle para impedir que as regras de vagar sejam disparadas durante essas outras atividades. O soar permite então abstrair as regras de vagar e seus operadores em uma única unidade que pode ser usada quando o tanque deve vagar. Há regras de alto nível que um tanque pode realizar envolvendo combinações de atividades de baixo-nível. Um conjunto de atividades de alto-nível seria 'vagar', 'perseguir', 'atacar' e 'recuar'. Embora sejam atividades de alto- nível, um tanque deve ser capaz de usar seus conhecimentos para selecionar entre essas atividades baseado em sua situação atual, da mesma forma como faz entre diferentes operadores. A hierarquia de regras que iremos montar está representada na figura abaixo:

Essa seção introduz sub-objetivos através de proposição, seleção e aplicação de operadores de alto-nível para a atividade 'vagar'. Nas próximas seções, adicionaremos outras operadores para perseguição, ataque e recuo.

O operador 'wander' (vagar)

Se pensarmos na atividade de vagar como um operador, então consideramos propor e aplicar regras para que essa atividade seja realizada. Quando um tanque deve vagar? Quando não está atacando, nem perseguindo e nem sendo atacado por um tanque inimigo.
Um tanque tem alguns sentidos para identificar outros tanques:

  • Blocked - não indica se o bloqueio é um tanque, então não nos ajuda a decidir se devemos vagar ou não.
  • Incoming - indica que um míssil se aproxima
  • Radar - detecta exatamente onde outro tanque está, porém é limitado a ver apenas à frente.
  • Rwaves - indica que um outro tanque está nos detectando em alguma das direções.
  • Smell - detecta o tanque mais próximo, mas ele pode estar atrás da parede.
  • Sound - dá a direção de outro taque que está próximo (7 células ou menos); porém não funciona se o outro tanque parar de se movimentar.

Com esses sentidos em mente, vamos considerar a seguinte proposta de operador: Se não há nenhum tanque detectado no radar, nenhum som, e não há nenhum míssil se aproximando, então proponha o operador 'wander':

Impasses e criação de estados

Sempre que ocorre um impasse no processo de decisão de um agente, o Soar cria um novo estado que dá um contexto para que o impasse seja resolvido. Nesse novo estado então são criados novos resultados que possivelmente resolverão o impasse do estado anterior.

No Soar, temos três tipos de impasse:

  • state no-change - acontece quando nenhum operador é proposto para o estado corrente.
  • operator tie - acontece quando as preferencias criadas para os operadores propostos não são suficientes para decidir qual deles usar.
  • operator conflict - acontece quando múltiplos operadores são propostos, mas as preferências são conflitantes.

Conforme já mencionado, ao atingir um impasse, o Soar cria um novo estado, sem descartar o estado original, na verdade eles ficam interligados, como em uma hierarquia. Esse sub-estados podem alterar o estado original diretamente, ou causar mudanças que afetarão indiretamente o estado acima.

Ao rodar nosso programa apenas com as regras de 'wander', observamos a criação de novos estados a partir do momento que não há mais proposições de operadores para o estado corrente:

Sub operadores

Para usarmos a regra de 'move' como um sub operador de 'wander' temos que modificar nossa regra original:

Temos que incluir o teste do operador selecionado no 'superestado'. Se o operador 'wander' está selecionado no superestado, e o tanque não estiver bloqueado à frente, então proponha o operador 'move':

Porém, infelizmente, esse regra não vai rodar, pois não temos a extensão 'io' em nosso subestado. Para resolver esse problema precisamos de uma regra geral que copie o link pra o link de saída em cada novo subestado criado:

Após sugeridas essas mudanças, o tutorial ainda sugere que ao invés de testarmos o nome do operador selecionado no superestado com as extensões ^superstate.operator.name, podemos apenas testar o nome do estado. Da mesma forma que copiamos a referencia do link de saída para um subestado, há uma regra que copia o nome do operador selecionado para o nome do novo subestado, essa regra já é inserida pelo VisualSoar automaticamente na criação do projeto (no arquivo elaborations/_all):

Com essas considerações reescrevemos as regras 'move' e 'turn' como sub-operadores de 'wander':

Se rodarmos apenas as regras de 'wander' desenvolvidas da forma descrita até agora, teremos o seguinte resultado no debugger ao dar alguns passos com o agente:

Fica claro que após a inicialização foi proposto e aplicado o operador 'wander' que por sua vez causou a criação de um novo estado e então entram nossos sub-operadores.
Para visualizar o código fonte dessa execução clique aqui.

Perseguir, Atacar e Recuar

Voltando à hierarquia de operadores que criamos, nessa seção iremos escrever regras para perseguir, atacar e recuar. Mesmo que já nos sintamos confortáveis com a linguagem e a forma de trabalhar do Soar, o tutorial sugere que visitemos as próximas páginas, já que introduzem novas idéias de desenvolvimento.

O operador 'chase' (perseguir)

O operador de perseguição deve ser selecionado quando um tanque percebe que outro está por perto, mesmo sem saber por onde ele anda. Conforme já citado, o sensor de som é capaz de indicar que um tanque está próximo, mas não é o suficiente para decidirmos iniciar a perseguição. Se tivermos um tanque detectado em nosso radar, podemos atacar prontamente. Porém, se estamos sem mísseis ou com baixa energia, de forma que não possamos usar o radar ou os escudos, devemos evitar a perseguição. O operador 'chase' deve ser proposto apenas para o superestado.

Para começarmos a desenvolver essas regras, precisamos montar algumas definições, como por exemplo o que significa 'baixa energia' e 'baixa munição'. Podemos abstrair esses conceitos em estruturas na memória de trabalho e usá-las com o operador 'retreat', por exemplo, que será responsável por identificar uma situação onde devemos cessar intenções de ataque.
No Soar, essas regras ficam da seguinte forma:

Para restringir que essas regras disparem apenas no superestado, devemos testar se o nome do estado é 'tanksoar'.
Repare que ambas as regras têm a mesma ação. O Soar não permite elementos duplicados na memória de trabalho, então mesmo que as duas disparem em paralelo, apenas um elemento é criado na memória de trabalho.

Para concluir, a proposição do operador 'chase' fica assim:

Aplicação do operador 'chase'

A aplicação do operador 'chase' se dá com a aplicação dos operadores 'move' e 'turn', que testam a direção de onde foi detectado o som de outro tanque se movimentando. A detecção do som pode ser feita diretamente pelo link de entrada, ou podemos abstrair a forma como essa detecção é realizada copiando essa informação para o estado:

Essa é uma boa prática de desenvolvimento, uma vez que isolando essa regra, podemos tranquilamente mudar a forma como o som é detectado sem impactar as regras que dependem do resultado dessa detecção. Essa prática soa como "desacoplamento" e "separação de responsabilidades" no paradigma de orientação a objetos.

O objetivo da perseguição é fazer nosso radar entrar em contato com um tanque inimigo. As regras a seguir modelam os comportamentos esperados durante a perseguição:

A regra chase*elaborate*radar simplesmente sugere que o radar seja ligado quando ele estiver desligado.

A regra chase*propose*move testa se o nome da tarefa é 'chase' e propõe mover-se pra frente, caso essa seja a direção de onde um som foi detectado.

As regras a seguir testam de qual direção estamos detectando som e propõe mover-se naquela direção.

O operador 'attack' (atacar)

O propósito do operador 'attack' é atingir tanques inimigos com mísseis, porém, se o tanque estiver com baixa energia ou baixa saúde, talvez seja melhor recuar. Assim como o operador 'chase', devemos propor o 'attack' apenas para o superestado.

A regra que propõe o operador 'attack' é a seguinte:

Devemos incluir uma preferencia indiferente para o operador, sendo que podemos ter mais de um tanque em nosso radar, o que causará a criação de várias intâncias dessa regras. Uma abordagem alternativa para evitar multiplas instanciações é criar uma regra que elabore uma nova extensão no estado, como por exemplo, ^tank detected, e outras regras poderiam se basear no valor dessa extensão apenas, mesmo que essa regra de elaboração dispare mais de uma instância, a memória de trabalho não vai gerar duplicatas dessa informação.

Aplicação do operador 'attack'

A aplicação do operador 'attack' se dá com a seleção entre os operadores 'move', 'turn' e 'fire' seguindo as seguintes diretivas:

  • Se o inimigo está na posição central do radar, o tanque atirar um míssil.
  • Se o inimigo está à frente mas não na posição central, o tanque deve mover-se para esquerda ou direita para ter um tiro certeiro.
  • Se o tanque não consegue deslizar por estar bloqueado, deve-se mover para frente.
  • Se o inimigo está logo próximo ao tanque, o tanque deve virar.

Com as regras definidas, vamos escrever o código em Soar:

Atirar míssil:

O número de mísseis é testado para que o operador seja proposto somente enquanto houver mísseis disponíveis. A proposição do operador precisa de uma preferencia "o melhor" para que seja preferido a todos os outros.

Deslizar:

Caso o tanque inimigo esteja a frente, mas não na posição central, sugira deslizar o tanque na direção no tanque inimigo. Esse operador possui uma preferência indiferente, pois podemos ter mais de um tanque em nosso radar.

Mover-se à frente:

Caso o tanque inimigo não esteja na posição central do radar e seja impossível deslizar na direção dele, então o tanque deve se mover para a frente.
Nessa regra também testamos se o tanque inimigo está há uma distância 0, o que significa que ele está em uma célula adjacente, nessa caso é melhor virar e atirar, é o que propomos na regra a seguir:

O operador 'retract' (recuar)

O operador recuar tem a intenção de afastar o tanque de situações de perigo quando este está com baixa munição ou energia. Assim, 'retreat' deve ser escolhido quando o tanque está com baixa munição, baixa energia e detectou que há um tanque por perto. E seguindo os outros operadores, deve ser proposto apenas para o superestado.

A descrição da regra parece bastante simples:
Se o estado for 'tanksoar' (superestado) e o sensor de som detectando sinais, ou há um tanque em no radar, ou há um míssil em direção ao tanque, e saúde ou energia estão baixos, então proponha o operador 'retreat'.
Precisaremos de três regras em Soar para modelar esse comportamento, e uma vez que mais de uma delas pode ser verdade ao mesmo tempo, a preferencia entre elas deve ser indiferente:

Outra situação na qual devemos propor o operador 'retreat' é quando o tanque está sendo atacado, mas não consegue diretamente detectar o tanque inimigo. É o que cobre a regra abaixo:

Aplicação do operador 'retract'

// TODO
* Devemos elaborar extensões no estado para guardar direções que devemos evitar
* Aplicação se dá com operadores 'move'
* Controle do escudo
* Operador 'wait'

Melhorando a detecção de som

O problema mais óbvio do nosso tanque é que ele perde rastreabilidade dos outros tanques assim que eles param de se mover. Para eliminar esse problema, criaremos uma memória persistente que guarda a direção dos sons captados para que possamos guiar nossa perseguição ainda que o outro tanque pare. Essas mudanças são extensas e mudam completamente as regras que testam os sons captados diretamente dos links de entrada.

// TODO
* Nessa seção iremos criar estruturas persistentes que irão manter memória dos sons captados durante a atividade de 'vagar'.
* Todos os nossos operadores vão utilizar essa nova estrutura, então eles serão modificados.
* Uma regra para remover a estrutura gravada quando não for mais válida ficará disponível na pasta 'all' do nosso projeto no VisualSoar.
* A regra que propões a gravação do som deve executar assim que o tanque captar um som, então as estruturas são criadas no superestado junto com um contador de tempo para que o som tenha um prazo de expiração, caso nada mais seja detectado.
* Depois, devemos computar a direção absoluta do som, como norte, sul, lest ou oeste, ao invés de simplesmente persistirmos a informação que vem do sensor de som (forward, backward, right, left), pois se o tanque resolve virar de frente para a fonte do som, essa informação pode o fazer girar em círculos.
* Para computar essa direção absoluta criaremos uma estrutura de mapa que mapeia as relações entre uma direção absoluta e uma relativa. Por exemplo: ^direction-map.north.left = west.
* Esse é o momento onde refatoramos o código isolade que detecta sons para essa nova abordagem.
* Então desenvolvemos uma regra para remover a estrutura de sons gravada. O operador 'remove-sound' pode ser proposto a qualquer momento, por isso é prudente coloca-lo na pasta 'all'.
* O operador 'remove-sound' deve entrar em ação quando o tempo de existência de uma gravação expirar.
* Então segue uma explicação sobre 'justificativas' em Soar.

Criando um mapa

Conforme nosso tanque vaga pelo mapa, ele pode montar um mapa para encontrar mais facilmente os carregadores de energia e saúde, e também para fazer um uso mais eficiente de seu radar.
O código para esse tanque é o mesmo desenvolvido com as melhorias de percepção de som, porém, com regras a mais.

// TODO

Atividade 3

// TODO
//Desenvolva um programa Soar, utilizando o mapa do ambiente, para fazer com que o tanque,
//além de suas atividades normais, dirija-se aos carregadores de energia e saúde, quando
//estiverem com baixa energia ou baixa saúde.

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer