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:
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:
Para verificar o status corrente dos sensores de um tanque, selecione o tanque no mapa e visualize as informações à direita do painel:
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:
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:
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.
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:
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':
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:
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:
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.
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:
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 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.
A aplicação do operador 'attack' se dá com a seleção entre os operadores 'move', 'turn' e 'fire' seguindo as seguintes diretivas:
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 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:
// 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'
// 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.
// TODO
// 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