Soar is a general cognitive architecture for developing systems that exhibit intelligent behavior.
Nesta seção exploramos o Tutorial 3 do Soar. Tratamos de problemas que exigem a decomposição de uma meta em metas intermediárias (subgoals) para serem resolvidos. A decomposição de problemas em sub-problemas exige, por exemplo, o tratamento de possíveis impasses que venham a ocorrer.
Nesta seção fazemos um resumo sobre as características dos agentes TankSoar e as sugestões de possíveis melhorias. Porém, antes dessa apresentação colocamos algumas dicas:
A seguir apresentamos as características básicas de cinco agentes TankSoar. Posteriormente listamos as sugestões de possíveis melhorias (que serão trabalhadas ao longo do texto).
Este é o mais simples dos bots TankSoar. O bot se movimenta pela arena usando seus sensores para evitar obstáculos e detectar outros objetos. Possui um radar frontal que ao detectar objetos faz com que o bot vire para um dos lados (direita, esquerda) somente para o caso de obstáculos. Como o uso do radar consome energia, a ativação do radar ocorre logo após o bot fazer uma manobra (muda de direção) e é desativado se o radar não captura nenhuma informação (como tanques, mísseis, recarregadores) à sua frente (direção apontada pelo radar).
O wander usa quatro operadores e uma regra de controle de busca:
Características do Soar utilizadas na implementação de funções PSCM: operadores de proposição, de avaliação e de aplicação interna.
Este bot é uma variante do wander bot que é capaz de procurar por objetos. Também possui características de combate (contra outros bots), de busca ("caça"), ataque e recuo ("fuga"). Um recurso Soar utilizado neste bot é o de decomposição hierárquica (como veremos mais adiante). Este agente usa operadores abstratos (vide nota abaixo) que são decompostos em ações básicas de baixo-nível. O bot usa seu conhecimento para selecionar quais ações aplicar (para resolver os problemas intermediários), baseado na situação corrente, para resolver o problema maior.
Nota: o Soar usa as produções (regras) como unidade básica de conhecimento, as regras são uadas para propor, selecionar e aplicar "operadores" (que correspondem a ações e metas (goals) durante alguma atividade (a resolução de problemas). Considere o ambiente do TankSoar, um operador abstrato, nesse contexto, poderia ser "interceptar outro bot" (note-se que essa ação-meta não é elementar). Operadores abstratos, atuam como metas, podem ser dinamicamente decompostos por regras que propõem operadores mais primitivos (básicos) para alcançar as ações abstratas - no caso da interceptação de bots, ações mais básicas poderiam ser: localizar o bot dentro do alcance dos mísseis, disparar os mísseis no alvo travado. O Soar cria uma nova estrutura, no contexto da nova meta, e propõe operadores adicionais. O Soar possui recursos para efetuar esse tipo de decomposição e execução hieráquica de "ações complexas" (operadores complexos).
Este bot é uma varinate do simple bot, possui a capacidade de tratar dados sonoros. Tem capacidade de memória e usa sua audição para localizar outros bots em lugares já percorridos. Neste bot estão presentes recursos como a decomposição hieráquica e a criação de Working Memory Structures (WMS) para lembrar de estados passados.
Este bot é uma extensão dos bots anteriores, possui a capacidade de criar uma representação interna do ambiente externo. O bot usa esse map interno para controle do radar e para encontrar os recarregadores. São utilizados todos os recursos do Soar utilizados nos botas anteriores.
Como o nome já diz, o código-fonte deste bot não é revelado. O bot serve de adverśario teste para a implementção de bots diversos.
A seguir, listamos as sugestões das possíveis melhorias (nem todos foram implementados):
Note-se que o excesso de operadores (um bot que faz mapeamento, "conhece" inúmeras táticas) no design de um bot pode acabar gerando um comportamento não desejável. Por exemplo, em situações de combate (com outros TankSoar) o excesso de regras pode ocupar ciclos de decisões preciosos, tornando-o (por um instante de tempo) um alvo fácil.
Nesta subseção exploramos TankSoar. Apresentamos o funcionamento do TankSoar, os sensores disponíveis e as ações possíveis. Criamos e executamos dois agentes soar baseados nas regras do programa simple-bot.soar (vide esquema em TankSoar simple agent).
Apresentamos os elementos que compõem o ambiente do TankSoar. Como mostra a figura abaixo, a arena é uma grade 14 por 14 (de fato, é uma grade 16x16 se contarmos as paredes), delimitada por quatro paredes (representadas pelas rochas) - no quadro que representa o sensor do bot as rochas são representadas por obstáculos (árvores), contendo diversos elementos (recarregadores, armamento, os tanques) listados na figura abaixo:
A figura abaixo mostra o painel de informações sobre o ambiente. Os agentes (vide rótulo Agents na figura abaixo) ativados com as respectivas pontuações, score, a quantidade de mísseis, missiles, a condição de saúde, health, e o nível de energia, energy. Outras informações que constam no painel: em Other Sensor (vide rótulo na figura abaixo) temos: a localização (no caso da figura, o tanque está na posição (11,14) em termos absolutos da grade 14x14; a face para o qual o tanque está orientado (no caso da figura, o tanque está orientado para o norte). Também há quatro painéis para diferentes sensores (Blocked, RWaves, Sound e Incoming), um painel que informa o que é capturado pelo radar frontal (quando ativado - a coluna ao lado indica o alcance do radar) e um indicardo de proximadade de um tanque inimigo (Smell).
Na figura abaixo temos uma seqüência de cenas com dois tanques (um laranja e outro vermelho) em combate. Inciamos descrevendo os três quadros superiores, da esquerda para a direita:
Dando continuidade à seqüência de cenas, os três quadros inferiores, da direita para a esquerda, mostram:
A figura abaixo mostra o ciclo do simulador com três tanques. O esquema da figura (módulo mais à direita) mostra que o ciclo do simulador ocorre somente quando todos os tanques geram ações externas. Esse froam de escalonamento permite que os tanques possam "pensar" (loop interno, em cada um dos tanques - um agente TankSoar pode executar no máximo quinze ciclos de decisão, esse parâmetro é definido pelo ambiente TankSoar).
No TankSoar nem toda decisão resulta em ações externas (veja alguns dos elementos em ^output-link: ^move, ^rotate, ^fire, vide esquema abaixo, que se executadas refletem em ações externas), pode ocorrer que, dentre os operadores selecionados, alguns operadores realizem ações internas (altera o estado interno do tanque). O esquema abaixo mostra a estrutura do Input-Link e Output-Link:
################################################################################### # Estrutura do TankSoar ^io ^io ^input-link ^input-link ^blocked ^rwaves ^backward yes/no ^backward yes/no ^forward yes/no ^forward yes/no ^left yes/no ^left yes/no ^right yes/no ^right yes/no ^incoming ^smell ^backward yes/no ^color none/red/blue/purple/... ^forward yes/no ^distance none/0-28 ^left yes/no ^sound silent/left/right/forward/backward ^right yes/no ^clock 1-N ^radar ^direction north/east/south/west ^energy ^energy 0-1000 ^distance 0-13 ^energyrecharger no/yes ^position left/center/right ^health 0-1000 ^health ^healthrecharger no/yes ^distance 0-13 ^missiles 0-N ^position left/center/right ^my-color blue/red/purple/... ^missiles ^radar-distance 1-14 ^distance 0-13 ^radar-setting 1-14 ^position left/center/right ^radar-status on/off ^obstacle ^random 0.0-1.0 ^distance 0-13 ^resurrect no/yes ^position left/center/right ^shield-status on/off ^open ^x 1-14 ^distance 0-13 ^y 1-14 ^position left/center/right ^tank ^distance 0-13 ^position left/center/right ^io ^output-link ^move.direction left/right/forward/backward/none ^rotate.direction left/right ^fire.weapon missile ^radar.switch on/off ^radar-power.setting 1-14 ^shields.switch on/off
Como já mencionamos, o wander bot usa quatro operadores e uma regra de controle de busca:
# ------------------------------------------------------------------- # If the task is tanksoar and the tank is not blocked in the forward # direction, propose the move operator sp {propose*move (state <s> ^io.input-link.blocked.forward no) # Verifica se a frente --> # do bot está livre (<s> ^operator <o> + =) (<o> ^name move ^actions.move.direction forward) }
# ------------------------------------------------------------------- # If the tank is blocked in the forward direction, and not blocked in # the right or left directions, then propose the turn operator for # the unblocked direction. Also create an indifferent preference for # the operator. # This operator will also turn on the radar and set the radar-power # to 13. If the tank is blocked in the forward direction, and in both # the right or left directions, then propose the turn operator left. sp {propose*turn (state <s> ^io.input-link.blocked <b>) (<b> ^forward yes ^ { << left right >> <dir> } no) --> (<s> ^operator <o> + =) (<o> ^name turn ^actions <a>) (<a> ^rotate.direction <dir> # Todas as ações (rotate, radar, ^radar.switch on # radar-power) são copiadas para ^radar-power.setting 13) # o output-link. }
# ----------------------------------------------------------------- sp {propose*turn*backward (state <s> ^io.input-link.blocked <b>) (<b> ^forward yes ^left yes ^right yes) --> (<s> ^operator <o> +) (<o> ^name turn ^actions.rotate.direction left) }
# ----------------------------------------------------------------- # If the radar is on but no energy, health, missiles and tanks visible, # then propose the radar-off operator sp {propose*radar-off (state <s> ^io.input-link <il>) (<il> ^radar-status on # Verifica se o radar está ativado. ^radar-setting <> 0 ^radar <r>) -(<r> ^ << energy health missiles tank >> <x>) # Tipos que NÃO devem estar --> # no escopo do radar. (<s> ^operator <o> + >) (<o> ^name radar-off ^actions.radar.switch off) }
Observe que para qualquer objeto no radar há uma augmentation do radar (vide estrutura do radar no Input-Output links), o valor do atributo é o tipo do objeto (capturado pelo radar):
^radar ^energy ^distance 0-13 ^position left/center/right ^health ^distance 0-13 ^position left/center/right ^missiles ^distance 0-13 ^position left/center/right ^obstacle ^distance 0-13 ^position left/center/right ^open ^distance 0-13 ^position left/center/right ^tank ^distance 0-13 ^position left/center/right
Por exemplo, se há um tanque no radar, então podemos ter uma condição como: (<s> ^io.input-link.radar.tank). É necessário escrever uma condição sobre o que está no radar: se o radar está ou não detectando um tanque (analogamente, um míssel, energia ou saúde). Evidentemente, antes é necessário verificar se o radar está ativado.
# ----------------------------------------------------------------- # If radar-off is proposed, then prefer it to move and turn sp {select*radar-off*move (state <s> ^operator <o1> + ^operator <o2> +) (<o1> ^name radar-off) (<o2> ^name move) --> (<s> ^operator <o1> > <o2>) # Preferência pelo radar-off ao move }
Regra de aplicação (e a respectiva remoção):
# ----------------------------------------------------------------- sp {apply*operator*create-action-command (state <s> ^operator <o> ^io.output-link <ol>) (<o> ^actions <act>) (<act> ^<att> <value>) --> (<ol> ^<att> <value>) # Aplicação da ação } # ----------------------------------------------------------------- sp {apply*operator*remove-command (state <s> ^operator.actions ^io.output-link <ol>) (<ol> ^<att> <value>) (<value> ^status complete) --> (<ol> ^<att> <value> -) # Remove a ação executada }
Os agentes TankSoar diferem dos agentes Eaters. (vide resumo da Aula 3) No eaters, todo operador atua numa ação externa: move ou jump. No TankSoar a atuação de alguns operadores (p.ex., radar-off) afeta somente o estado interno do agente. Outra distinção entre eaters e agentes TankSoar está interação entre agentes, por exemplo, um tanque pode atacar outro. Essas características criam um jogo mais elaborado, por exemplo, com estratégias de busca, ataque, fuga, de auto-preservação, etc. Podemos pensar em agentes num nível mais abstrato, com operadores associados a combinações de ações, por exemplo, uma operação de ataque envolve uma combinação de várias ações (movimentação em relação ao alvo, direção do alvo, aproximação em relação ao alvo e disparo). Na próxima seção apresentamos implementações de agentes TankSoar com tais estruturas.
Este é um bot que possui diferentes características, não só vagueia pela arena à procura de objetos, mas também caça e ataca outros agentes. O agente usa operadores abstratos que se decompõe em combinações de ações de baixo nível. A figura abaixo mostra atividades de alto-nível e suas respectivas formulações (composições em ações de baixo-nível).
A figura abaixo mostra um possível esquema de uso dessas atividades:
Na árvore acima estão especificadas as condições para a propostas de cada um dos operadores: o wander ocorre na condição (1): sound silent, no tanks on radar e no incoming missile (note que a condição missiles-energy low não interfere); para o attack basta (2): not missiles-energy low, tank on radar; para chase devemos ter (3) not missiles-energy low, not sound silent, no tank on radar; o retreat pode ocorrer em condições variadas (4): missiles-energy low, not sound silent; (5) missiles-energy low, tank on radar; (6) missiles-energy low, incoming missile ou (7): sound silent, no tanks on radar, incoming missile. Evidentemente, a árvore da figura acima pode ter outras configurações, envolver outros sensores e situações, depende do comportramento que se deseja dar ao agente TankSoar. A seguir apresentamos a codificação do simple bot baseado nos operadores da árvore acima, iniciamos com o wander operator:
############################################################################ # Simple Bot # ----------------------------------------------------------------- # Propose wander operator # # If there is no tank detected on radar, and the sound is silent, # and there is no incoming, then propose the wander operator. sp {propose*wander (state <s> ^name tanksoar ^io.input-link <io>) (<io> ^sound silent -^radar.tank -^incoming.<dir> yes) --> (<s> ^operator.name wander) }
A proposta do chase operator segue o esquema apresentado na árvore da figura acima. Note-se que os elementos da condição estão numa estrutura (são top state), logo estão disponíveis para qualquer subproblema gerado.
# ----------------------------------------------------------------- # Propose Chase Operator # # If the task is tanksoar, and sound sensor is not silent, and # there is no tank on radar, and energy or missiles is not low, # then propose the chase operator. sp {propose*chase (state <s> ^name tanksoar # Determina a ocorrência top state ^io.input-link <io> -^missiles-energy low) (<io> ^sound <> silent -^radar.tank) --> (<s> ^operator <o> +) (<o> ^name chase) }
O chase operator pode ser selecionado quando um tanque percebe a movimentação de outro em suas proximidades, apesar de não saber onde exatamente está (isto é, não está no radar) o tanque inimigo (^sound <> silent -^radar.tank). Evidentemente, essa é uma situação que não é favorável para um ataque (já que a posição exata do inimigo é desconhecida).
Nas regras de alaboração abaixo, a adição do nome ^name tanksoar serve para assegurar a verificação da condição na estrutura top state. Caso contrário, os atributos associados a ^io serão copiados para todos os estados causando o disparo dessas regras sempre que um novo estado for criado.
# ----------------------------------------------------------------- sp {elaborate*state*missiles*low (state <s> ^name tanksoar ^io.input-link.missiles 0) --> (<s> ^missiles-energy low) } # ----------------------------------------------------------------- sp {elaborate*state*energy*low (state <s> ^name tanksoar ^io.input-link.energy <= 200) --> (<s> ^missiles-energy low) }
Apesar da ação nas duas regras acima ser a mesma (mesmo identificador, atributo e valor), não são criados dois WMEs. A arquitetura do Soar não permite a criação de dois WMEs idênticos, isto é, apesar das duas regras serem disparadas apenas um WME será criado.
O operador chase é aplicado usando (escolhendo) os operadores move e turn.
# ----------------------------------------------------------------- # Propose Move Operator # # If the state is named chase and the sound is coming from the # forward position, propose move forward. sp {chase*propose*move (state <s> ^name chase ^sound-direction forward ^io.input-link.blocked.forward no) --> (<s> ^operator <o> +) (<o> ^name move ^actions.move.direction forward) } # ----------------------------------------------------------------- # Propose Turn Operator # # If the state is named chase and the sound is coming from left or # right, turn that direction. sp {chase*propose*turn (state <s> ^name chase ^sound-direction {<< left right >> <direction>}) --> (<s> ^operator <o> + =) (<o> ^name turn ^actions.rotate.direction <direction>) } # ----------------------------------------------------------------- # Propose Turn Operator Backward # # If the state is named chase and the sound is coming from backward, # turn left. sp {chase*propose*backward (state <s> ^name chase ^sound-direction backward) --> (<s> ^operator <o> +) (<o> ^name turn ^actions.rotate.direction left) }
Nos dois casos os operadores testam a direção da proximidade do tanque. A direção pode ser acessado diretamente a partir do Input-Link. No entanto, podemos simplificar as regras, basta adicionar uma regra de elaboração que copia o ^io.input-link.sound (o som do tanque inimigo em movimento) para o estado local. Por exemplo:
# ----------------------------------------------------------------- sp {chase*elaborate*state*sound-direction (state <s> ^name chase ^io.input-link.sound <sound>) --> (<s> ^sound-direction <sound>) }
Esse tipo de recurso torna o código modular, isto é, se, por exemplo, for alterado o modo como a direção é calculado, então basta mofificar a regra acima e não o teste da percepção do som (do tanque inimigo em movimento). Como o objetivo do operador chase é capturar o sinal do tanque inimigo no radar, então este deve ser ligado se estiver desligado. Isso pode ser feito em paralelo com os outros operadores.
# -----------------------------------------------------------------
sp {chase*elaborate*radar
(state <s> ^name chase
^operator.actions <a>
^io.input-link.radar-status off)
-->
(<a> ^radar.switch on ^radar-power.setting 13) # Maior alcance do radar
}
Um problema óbvio do uso do sound ocorre com a parada do tanque inimigo. Para não perder a última direção capturada pelo sound devemos criar uma WME persistente. Para isso é preciso criar novos operadores e alterar as regra que testam o som diretamente do Input-Link. Pelo menos duas regras serão necessárias: uma para criar e outra para remover WMEs associados ao som: record-sound (create the sound data structure when a new sound is heard) e remove-sound (remove the sound data structure if the direction of sound changes or if the recorded sound has expired). Não trataremos dessas implementações aqui, deixamos para apresentá-los mais adiante.
Como toda batalha, um ataque deve ser executado somente se as condições forem favoráveis. No caso dos tanques, em que a principal arma são mísseis, o attack operator pode ser selecionado quando um tanque tem o tanque inimigo em seu radar. Evidentemente, isso só é possível se o nível de energia para o disparo dos mísseis não for baixo.
# ----------------------------------------------------------------- # Propose Attack Operator # # If the state is tanksoar, and there is a tank on radar, and health # and energy are not low, then propose the attack operator. sp {propose*attack (state <s> ^name tanksoar ^io.input-link.radar.tank -^missiles-energy low) --> (<s> ^operator <o> + =) (<o> ^name attack) }
A aplicação do attack operator ocorre pela seleção de operadores move, turn e fire. Por exemplo, (1) se a imagem do tanque inimigo aparece no centro do radar, então basta disparar o míssel; (2) se a imagem do tanque inimigo não está no centro do radar, então é preciso mover para a direita ou esquerda antes de efetuar o disparo do míssel; (3) se não é possível uma aproximação devido a um bloqueio, então o tanque deverá prosseguir em sua perseguição; (4) se o tanque inimigo está à direita, então basta virar para o alvo.
# ----------------------------------------------------------------- # Propose Fire-missile Operator # # If the state is attack and there is a tank on radar in the center, # then propose the fire missile operator. sp {attack*propose*fire-missile (state <s> ^name attack ^io.input-link <il>) (<il> ^radar.tank.position center ^missiles > 0) --> (<s> ^operator <o> + >) (<o> ^name fire-missile ^actions.fire.weapon missile) } # ----------------------------------------------------------------- # Propose Slide Operator # # If the state is attack and there is a tank on radar that is not # in the center, and there is not a tank in the center, and there # is an open spot in the direction of the tank, then propose the # slide operator in the direction of the tank. sp {attack*propose*slide (state <s> ^name attack ^io.input-link <input>) (<input> ^blocked.<dir> no ^radar <r>) (<r> ^tank.position { << left right >> <dir> } -^tank.position center) --> (<s> ^operator <o> + =) (<o> ^name slide ^actions.move.direction <dir>) } # ----------------------------------------------------------------- # Propose Move-Forward Operator # # If the state is attack and there is a tank on radar that is not # in the center, and there is not a tank in the center, and the # tank is blocked in that direction then propose move-forward. sp {attack*propose*move-forward (state <s> ^name attack ^io.input-link <input>) (<input> ^blocked.<dir> yes ^radar <r>) (<r> ^tank <t> -^tank.position center) (<t> ^position { << left right >> <dir> } ^distance <> 0) --> (<s> ^operator <o> + =) (<o> ^name move-forward ^actions.move.direction forward) } # ----------------------------------------------------------------- # Propose Turn Operator # # If the state is attack and there is a tank on radar that right # next to the tank, then propose turning in that direction and # firing. sp {attack*propose*turn (state <s> ^name attack ^io.input-link.radar.tank <tank>) (<tank> ^distance 0 ^position { << left right >> <dir> }) --> (<s> ^operator <o> + =) (<o> ^name turn ^actions <a>) (<a> ^rotate.direction <dir> ^fire.weapon missile) }
O retreat operator é selecionado quando o tanque está com pouco (ou nenhum) armamento, com baixo nível de energia, na presença de um tanque inimigo. O retreat operator também pode ser acionado quando um tanque está sob ataque inimigo, sem saber onde exatamente está o adeversário. Análogo ao chase operator, o retreat operator lida com WMEs top state.
# ----------------------------------------------------------------- # Propose Attack Operator # # If the state is tanksoar, and there is a tank on radar, and health # and energy are not low, then propose the attack operator. sp {propose*retreat*sound (state <s> ^name tanksoar ^missiles-energy low ^io.input-link.sound {<direction> <> silent}) --> (<s> ^operator <o> + =) (<o> ^name retreat) } # ----------------------------------------------------------------- sp {propose*retreat*radar (state <s> ^name tanksoar ^missiles-energy low ^io.input-link.radar.tank) --> (<s> ^operator <o> + =) (<o> ^name retreat) } # ----------------------------------------------------------------- sp {propose*retreat*incoming (state <s> ^name tanksoar ^missiles-energy low ^io.input-link.incoming.<dir> yes) --> (<s> ^operator <o> + =) (<o> ^name retreat) } # ----------------------------------------------------------------- # Propose Retreat Operator # If the state is tanksoar and the tank is under attack but cannot # not directly sense the other tank, then propose the retreat # operator. sp {propose*retreat*incoming*not-sensed (state <s> ^name tanksoar ^io.input-link <io>) (<io> ^incoming.<dir> yes -^radar.tank ^sound silent) --> (<s> ^operator <o> + =) (<o> ^name retreat)} }
Algumas informações são importantes para a aplicação do retreat, por exemplo, saber a direção da movimentação do inimigo (usando o sound). A estratégia básica é evitar as possíveis direções que levem diretamente ao inimigo.
# ----------------------------------------------------------------- # Retreat Operator Elaboration # # If there is a retreat state and there is a sound coming in a # given direction, record that direction. sp {elaborate*retreat*sound*direction (state <s> ^name retreat ^io.input-link.sound { <> silent <direction> }) --> (<s> ^direction <direction>) } # ----------------------------------------------------------------- # # If there is a retreat state and there is radar contact with a # tank, record forward direction. sp {elaborate*retreat*radar*front (state <s> ^name retreat ^io.input-link.radar.tank) --> (<s> ^direction forward) } # ----------------------------------------------------------------- # # If there is a retreat state and there is an incoming, record # the direction. sp {elaborate*retreat*incoming*direction (state <s> ^name retreat ^io.input-link.incoming.<dir> yes) --> (<s> ^direction <dir>) } # ----------------------------------------------------------------- # # If there is a retreat state and there is radar contact with a # tank that is not in the center, record that direction as a # direction to avoid moving. sp {elaborate*retreat*radar*direction (state <s> ^name retreat ^io.input-link.radar.tank.position { <dir> <> center }) --> (<s> ^avoid-direction <dir>) }
Assim como os operadores anteriormente apresentados, a aplicação do retreat é basedo no move - a idéia é mover-se em uma direção segura (que afaste o tanque da localização do inimigo). Porém, pode ocorrer, dependendo da situação corrente e da configuração das barreiras, de não haver escolha para uma direção segura. Nesse caso, outros dois recursos podem ser úteis: o uso de escudo (shield) e o de tempo de espera (wait). Um certo cuidado deve ser considerado com a ativação do escud: tem elevado consumo de energia (portanto, seria um dos últimos recursos).
# ----------------------------------------------------------------- sp {elaborate*shields-on (state <s> ^operator.actions <a> ^io.input-link <il>) (<il> ^incoming.<dir> yes ^shield-status off) --> (<a> ^shields.switch on) } # ----------------------------------------------------------------- sp {elaborate*shields-off (state <s> ^operator.actions <a> ^io.input-link <il>) (<il> -^incoming.<dir> yes ^shield-status on) --> (<a> ^shields.switch off) }
O wait pode ser útil tanto como recurso de fuga quanto como para resolução de impasses (isto é, quando não ocorre mudança de estado, decorrente da falta de proposição de operadores). Nesse caso, o wait pode ser proposto sempre que ocorre: state no-change. Os atributos sobre o estado são criados quando nenhum operador for selecionado para um dado estado: ocorre um impasse (state no-change). Note-se que uma condição para o wait é que ele próprio não deve ser selecionado.
# ----------------------------------------------------------------- # Propose wait for a state-no-change sp {top-state*propose*wait (state <s> ^attribute state # This proposal tests that the state has ^choices none # ^attribute state and ^choices none -^operator.name wait) --> (<s> ^operator <o> +) (<o> ^name wait) }
Finalmente, um último recurso oferecido pelo TankSoar é o de mapeamento: a criação de uma representação interna do ambiente na memória do tanque (um WME persistente). Esse recurso é o objeto de estudo da próxima seção.
O armazenamento de uma representação do ambiente na memória do tanque deve ser preservado durante todo o tempo de jogo, isto é, trata-se de um WME persistente. No caso do TankSoar, usamos uma estrutura 16x16 (14x14 mais as paredes laterais).
########################################################################## # Mapping Bot # sp {apply*init-map (state <s> ^operator.name init-map) --> (<s> ^map <m>) (<m> ^square <s0-0> <s0-1> <s0-2> <s0-3> <s0-4> <s0-5> ... ) (<s0-0> ^x 0 ^y 0) (<s0-1> ^x 0 ^y 1) (<s0-2> ^x 0 ^y 2) (<s0-3> ^x 0 ^y 0) (<s0-4> ^x 0 ^y 1) (<s0-5> ^x 0 ^y 2) (<s0-6> ^x 0 ^y 0) (<s0-7> ^x 0 ^y 1) (<s0-8> ^x 0 ^y 2) (<s0-9> ^x 0 ^y 0) (<s0-10> ^x 0 ^y 1) (<s0-11> ^x 0 ^y 2) (<s0-12> ^x 0 ^y 0) (<s0-13> ^x 0 ^y 1) (<s0-14> ^x 0 ^y 2) (<s0-15> ^x 0 ^y 0) (<s1-0> ^x 0 ^y 1) (<s1-1> ^x 0 ^y 2) ... }
O primeiro passo é a inicialização da estrutura, cada célula é inicializada com sua identificação, neste caso a coordenada na grade 16x16. Outras informações podem ser adicionadas, por exemplo, se a célula contém algum objeto (um recarregador, tanque, míssel, obstáculo). Também podemos colocar as relações de adjacência entre as células (nas quatro direções cardeais), criam pares de adjacências east-west e north-south entre estados. Essas informações facilitam o manuseio da estrutura, porém causam um aumento na quantidade de elementos na WM.
# ----------------------------------------------------------------- sp {top-ps*apply*init-map*add-adjacencies*east (state <s> ^operator.name init-map ^map.square <sq>) (<sq> ^x { < 15 <x> }) --> (<sq> ^east-x (+ <x> 1)) } # ----------------------------------------------------------------- sp {top-ps*apply*init-map*add-adjacent*link*east (state <s> ^operator.name init-map ^map.square <sq1> <sq2>) (<sq1> ^y <y> ^east-x <x>) (<sq2> ^y <y> ^x <x>) --> (<sq1> ^east <sq2>) (<sq2> ^west <sq1>) } # ----------------------------------------------------------------- sp {top-ps*apply*init-map*add-adjacencies*north (state <s> ^operator.name init-map ^map.square <sq>) (<sq> ^y { < 15 <y> }) --> (<sq> ^north-y (+ <y> 1)) } # ----------------------------------------------------------------- sp {top-ps*apply*init-map*add-adjacent*link*north (state <s> ^operator.name init-map ^map.square <sq1> <sq2>) (<sq1> ^x <x> ^north-y <y>) (<sq2> ^x <x> ^y <y>) --> (<sq1> ^north <sq2>) (<sq2> ^south <sq1>) }
Além das relações de adjacências, a estrutura pode ser inicializada com informações sobre as células que contém objetos fixos: bordas, obstáculos e carregadores. Basta colocar, por exemplo, uma descrição (augmentation) dizendo sim ou não sobre o conteúdo. Abaixo um exemplo de inicialização de uma das bordas (x = 15):
# ----------------------------------------------------------------- sp {map*mark-obstacle*x15 (state <s> ^operator.name init-map ^map.square <sq>) (<sq> ^x 15) --> (<sq> ^obstacle *yes*) }
Para o uso adequado do mapa é necessário que o tanque saiba onde ele próprio está neste mapeamento, para cada movimentação feita.
# ----------------------------------------------------------------- sp {all*map*current-square (state <s> ^name tanksoar ^io.input-link <il> ^map.square <cs>) (<il> ^x <x> ^y <y>) (<cs> ^x <x> ^y <y>) --> (<s> ^square <cs>) }
Outro passo para a atualização da representação é o de registrar no mapeamento os elementos capturados pelos sensores (p.ex., o radar não fornece posições de objetos em termos absolutos, os objetos capturados pelo radar são relativos ao agente). Abaixo temos uma estrutura que atualiza o mapa com os dados capturados pelo radar:
# ----------------------------------------------------------------- # These are used in retreat/move.soar sp {elaborate*sidestep-directions (state <s> ^name tanksoar) --> (<s> ^side-direction <sd>) (<sd> ^forward right left ^backward right left ^right forward backward ^left forward backward) } # ----------------------------------------------------------------- sp {elaborate*directions (state <s> ^name tanksoar) --> (<s> ^direction-map <tns> ^radar-map <dirp> ^opposite-direction <opp> ^maze-size 14) (<tns> ^north <north> ^south <south> ^west <west> ^east <east>) (<north> ^right east ^left west ^backward south ^forward north) (<south> ^right west ^left east ^backward north ^forward south) (<west> ^right north ^left south ^backward east ^forward west) (<east> ^right south ^left north ^backward west ^forward east) (<dirp> ^north <northp> ^south <southp> ^west <westp> ^east <eastp>) (<northp> ^center <cr> ^right <nr> ^left <nl> ^sx 0 ^sy -1) (<southp> ^center <cr> ^right <sr> ^left <sl> ^sx 0 ^sy 1) (<westp> ^center <cr> ^right <wr> ^left <wl> ^sx -1 ^sy 0) (<eastp> ^center <cr> ^right <er> ^left <el> ^sx 1 ^sy 0) (<cr> ^x 0 ^y 0) (<nr> ^x 1 ^y 0) (<nl> ^x -1 ^y 0) (<sr> ^x -1 ^y 0) (<sl> ^x 1 ^y 0) (<wr> ^x 0 ^y -1) (<wl> ^x 0 ^y 1) (<er> ^x 0 ^y 1) (<el> ^x 0 ^y -1) (<opp> ^forward backward ^backward forward ^left right ^right left) }
Note que para cada direção há uma subestrutura com objetos ao centro, à esquerda e à direita. A estrutura é usada em regras para computar as coordenadas (x, y) de objetos capturados pelo radar, cria uma estrutura temporária (i-supported) sobre o mapa para cada objeto capturado.
# ----------------------------------------------------------------------------- sp {map*mark-object (state <s> ^name tanksoar ^map <m> ^io.input-link <io> ^square <cs> ^radar-map.<dir> <dirr>) (<dirr> ^<pos> <pss> ^sx <sx> ^sy <sy>) (<pss> ^x <dx> ^y <dy>) (<io> ^radar.{<type> << health energy obstacle open missiles tank >>} <ob> ^direction <dir>) (<ob> ^distance <d> ^position <pos>) (<cs> ^x <x> ^y <y>) --> (<m> ^<type> <obs>) (<obs> ^x (+ (+ <x> (* <sx> <d>)) <dx>) ^y (+ (+ <y> (* <sy> <d>)) <dy>)) }
As regras que utilizam essa estrutura devem proceder de forma que as alterações efetuadas sejam igualmente persistentes, logo devem fazer parte de um operador ou via notação :o-support, como ilustrado abaixo:
# ----------------------------------------------------------------------------- sp {map*record-object :o-support (state <s> ^name tanksoar ^map <m>) (<m> ^{<type> << obstacle health energy open tank missiles >>} <obs> ^square <sq>) (<sq> ^x <x> ^y <y>) (<obs> ^x <x> ^y <y>) -(<sq> ^<type> *yes*) --> (<sq> ^<type> *yes*) }
Um dos elementos da condição é um teste para saber sobre o operador selecionado: (^operator.name <name>). Na regra acima, por exemplo, as células que contém o recarregador de energia carregam a descrição (augmentation): ^health *yes*. Note que recarregadores e obstáculos são fixos em suas coordenadas, mísseis e tanques não. Logo, deve haver uma regra que trate da remoção de atributos, por exemplo, quando uma célula vazia (open) for detectada pelo radar. OBS.: o teste para open é apenas para assegurar que o radar está ativado (logo, pode capturar algo na referida célula).
# ------------------------------------------------------------------- sp {map*clean*missiles :o-support (state <s> ^name tanksoar ^map <m>) (<m> ^square <sq> ^open <obs>) (<sq> ^x <x> ^y <y> ^missiles *yes*) -{(<m> ^missiles <mi>) (<mi> ^x <x> ^y <y>)} (<obs> ^x <x> ^y <y>) --> (<sq> ^missiles *yes* -) } # ------------------------------------------------------------------- sp {map*clean*tank :o-support (state <s> ^name tanksoar ^map <m>) (<m> ^square <sq> ^open <obs>) (<sq> ^x <x> ^y <y> ^tank *yes*) -{(<m> ^tank <mi>) (<mi> ^x <x> ^y <y>)} (<obs> ^x <x> ^y <y>) --> (<sq> ^tank *yes* -) } # ------------------------------------------------------------------- # # Furthermore, even if the tank doesn’t have its radar on, it can # remove tanks and missiles from the square that the tank occupies: sp {map*clean*missile*occupied :o-support (state <s> ^name tanksoar ^square <cs>) (<cs> ^missiles *yes*) --> (<cs> ^missiles *yes* -) } # ------------------------------------------------------------------- sp {map*clean*tank*occupied :o-support (state <s> ^name tanksoar ^square <cs>) (<cs> ^tank *yes*) --> (<cs> ^tank *yes* -) }
Também é possível mneter registro das atuais células vazias e daquelas que contém recarregdores (quando detectados).
# ------------------------------------------------------------------- sp {map*record-open*there :o-support (state <s> ^name tanksoar ^square <sq>) (<sq> -^open *yes*) --> (<sq> ^open *yes*) } # ------------------------------------------------------------------- sp {map*record-energy*there :o-support (state <s> ^name tanksoar ^io.input-link.energyrecharger yes ^square <sq>) (<sq> -^energy *yes*) --> (<sq> ^energy *yes*) } # ------------------------------------------------------------------- sp {map*record-health*there :o-support (state <s> ^name tanksoar ^io.input-link.healthrecharger yes ^square <sq>) (<sq> -^health *yes*) --> (<sq> ^health *yes*) }
Essa estrutura de mapeamento pode ser usada de diferentes modos: para economizar energia com o uso do radar (escolher a potência do radar a partir da posição atual), criar caminhos para os recarregadores (a partir da posição atual do tanque) em casos de necessidade, etc. A seguir implementamos alguns desses usos.
Nesta subseção executamos algumas das sugestões de possíveis melhorias sobre os agentes apresentados, seguindo o roteiro solicitado no enunciado referente à Aula 4:
Observação: sobre o desenho das soluções para as práticas propostas.
Fixado um problema, a implementação de uma solução computacional (ou uma aplicação computacional) segue um padrão (um modelo de programação) suportado por determinados grupos de linguagens de programação (usualmente, as linguagens são classificadas em paradigmas de programação). A escolha de uma (ou mais) linguagem(ns) impacta diretamente na forma pela qual a solução (ou aplicação) é modelada (do ponto de vista computacional). Desse modo, a adoção de uma linguagem como Smalltalk leva a crer que o design do modelo de solução computacional segue o paradigma da programação orientada a objetos. Analogamente, a adoção do Soar estabelece um modelo de trabalho: o design das soluções referentes às práticas deve, pelo menos em parte, estar de acordo com as idéias subjacentes à proposta do Soar (como arquitetura cognitiva).
No nosso caso, as soluções para as práticas são moldadas pelo "estado de atenção a que se condiciona o agente". Os sensores determinam como o agente deve-se comportar, nenhuma estratégia (pré-fixada como busca em largura, profundidade, backtraking) é codificada nas produções. Especificamente para a Prática 2, nós nos perguntamos: se você fosse um agente TankSoar com um mapa do ambiente como você o usaria?
Como mencionamos, as implementações dos bots se baseiam no "estado de atenção a que se condiciona o agente".
No caso da Prática 1, as ações do Bot 1 são determinados de acordo com o estado a que se coloca conforme as informações capturadas pelos sensores: estado de alerta (percebe que há um inimigo por perto, mas não tão próximo suficiente para travar combate) ou estado de combate (como um alerta vermelho: a proximidade do inimigo indica combate iminente). Quando o agente não está em alerta ou sob estado de combate ele está vagando pela arena ou encontra-se efetivamente em luta. O Bot 2 segue o mesmo desenho, exceto pelo fato dele alterar seu comportamento de ataque se estiver com pouca munição (caso em que o agente procura evitar (manter-se distante) os inimigos).
No caso da Prática 2, considera-se que os bots têm uma representação interna da estrutura da arena (inicialmente, o tanque desconhece as posições dos recarregadores de saúde e energia). Nessas condições, o design de agentes que, em caso de baixa energia ou saúde, procuram por carregadores (de energia e saúde) deve levar em consideração a forma como os seres humanos (de um modo geral) fazem uso do conhecimento que possuem de um dado lugar para realizar uma tarefa de buscar algo. Evidentemente, há dois casos de busca: uma em que se desconhece a localização de um dado objeto (mesmo com uma representação interna do ambiente) e outra em que a localização do objeto é conhecida (auxiliado pela representação interna do ambiente). Também devemos considerar o fato de haver tanques inimigos na arena, isto é, não é o caso do bot simplesmente elaborar e seguir um caminho para o recarregador (se ele já souber a localização do recarregador). Pode ocorrer (dependendo da simulação) do único caminho possível estar "bloqueado" por tanques inimigos (note-se que não é uma questão de impasse).
Novamente, o design do bot segue o "estado de atenção a que se condiciona o agente". Como já mencionamos, temos dois casos em relação ao comportamento do bot para a busca por recarregadores (tendo em consideração o comportamento humano na mesma situação): um em que o bot desconhece a localização do recarregador desejado e outro que conhece exatamente sua localização (em termos de coordenadas).
Caso em que o bot sabe as coordenadas (x0, y0) do recarregador que procura. Como o bot possui uma representação interna da arena e conhece sua exata posição (x, y), basta ele se orientar (direção e sentido) pela localização do recarregador. Caso um bloqueio ou um inimigo o faça tomar outros rumos, basta que o bot se reoriente a cada trecho de deslocamento (tecnicamente, o resultado das contas |x0 - x | e |y0 - y | determinam o sentido que o bot deve seguir para cada uma das direções: norte-sul e leste-oeste - o referencial é egocêntrico). Devemos lembrar há tanques inimigos na arena, logo (talvez) o bot tenha que passar mais de uma vez por um mesmo lugar já que a situação é de evitar enfrentamentos.
O caso em que o bot não sabe da localização do recarregador. Como não há pista alguma sobre a localização do recarregador (seja de energia ou de saúde), o bot pode fazer a busca a partir de escolhas de áreas. Por exemplo, por quadrantes: noroeste (NW), nordeste (NE), sudoeste (SW) ou sudeste (SE). Essa subdivisão (que poderia ser outra qualquer) não é relativa a posição do bot, o referencial é alocêntrico (por exemplo, em relação ao centro da arena). Levando-se em conta que o bot não está em condições ideais de combate (com pouca energia e/ou saúde) e que é capaz de perceber o inimigo em movimentação ele fará a busca tentando evitar (se possível) qualquer tipo de confronto. Caso o bot não encontre o recarregador desejado num quadrante ele deve prosseguir para outro (pode até retornar a um quadrante vistoriado, no entanto deve ser apenas para alcançar áreas de quadrantes não visitados). Não há nenhum tipo de marcador de quadrantes visitados, o bot apenas segue a orientação Norte, Sul, Leste e Oeste. Se o bot já procurou em um dos quadrantes, por exemplo, no quadrante SE e não encontrou o recarregador procurado, então sabe que só pode estar no quadrante SW ou nos quadrantes ao norte. A busca num dado quadrante deve considerar todos as células livres e, novamente, não há estratégias de marcar as células visitadas. O bot pode visitar uma mesma célula várias vezes, seja por necessidade em evitar tanques inimigos ou por ser a única passagem disponível (dependendo da configuração da arena).
As implementações dos bots tomam como base uma modificação do wander.soar (aqui o código modificado): código-fonte: Bot 1 e Bot 2.
Recursos Utilizados:
Consideramos como recursos os sensores que cada tanque possui.
Busca: O comportamento de busca considera os sensores:
Sound: detecta a direção da movimentação do inimigo, captura a direção do som: left/right/forward/backward, caso a movimentação ocorra a menos de sete células de distância numa dada direção. Se não capturar movientação alguma retorna silent.
Smell: informa a distância da proximidade do inimigo (se houver mais de um tanque à mesma distância o sistema escolhe apenas um deles). A distância é calculada pelo números de células em x e y entre o tanque e o inimigo (Distância de Manhattan com valores entre 0 a 28: os tanques estão numa grade 14x14 e a distância é calculada do seguinte modo: dados dois pontos A= (x1,y1) e B = (x2,y2) a distância de Manhattan é dada por |x1 - x2| + |y1 - y2|. Como o Smell atravessa barreiras (árvores), a distância capturada pode não ser a do tanque inimigo mais acessível.
Incoming: este sensor detecta a aproximação de mísseis inimigos lançados contra o tanque a qualquer distância, informa (com yes/no) a direção de origem do míssel: left/right/forward/backward.
Rwaves: este sensor detecta o sinal de radar do tanque inimigo do qual é alvo, informa (com yes/no) a direção de origem do sinal: left/right/forward/backward.
Radar: O radar é um sensor que consome energia ao ser ativado (evidentemente, é preciso ter energia para ativá-lo), quando ativado pode detectar qualquer objeto (bloqueios, tanques e recarregadores). Diferente do Smell, o sinal do radar é barrado pelos bloqueios (árvores), o sinal é direcionado para a parte frontal do tanque com amplitude de três células e alcance máximo de 14 células. Por exemplo, se o radar está na célula (x,y), direcionado para o norte e com o radar ativado, então o radar varre as células (x-1,z), (x,z) e (x+1,z), com y <= z < 15. A varredura captura qualquer objeto nessas células desde que o sinal do radar não seja bloqueado, por exemplo, se há uma árvore na posição (x+1,y+2), com y+2 <= 14, então o radar não retorna nenhuma informação sobre os conteúdos das células (x+1,z) com y+2 < z < 15.
Perseguição: O ato de perseguir é centrado no uso do Radar.
Ataque: O comportamento de ataque também é baseado no uso do Radar.
Condições:
Cada um dos comportamentos mencionados é disparado conforme a situação corrente capturada pelos sensores do tanque.
Busca: O comportamento de busca ocorre nos casos em que há inimigos, isto é, o valor numérico informado pelo Smell é diferente de zero. Podemos limitar o valor numérico do Smell a um raio r (digamos, r = 13) de proximidade (circunferência de Manhattan: se o tanque está na posição (x1,y1), então o comportamento de busca será ativado se um inimigo estiver numa célula (x2,y2) tal que |x1 - x2| + |y1 - y2| <= 13). Caso contrário o tanque simplesmente vagueia (wander) pela arena. Se o Smell indicar que há inimigos nas redondezas, então o Sound, Incoming e/ou Rwaves podem indicar (dentro das respectivas limitações) a direção do inimigo. O uso do Radar pode ser associado à informação fornecida pelo Sound (desde que a direção indicada não esteja bloqueada: Blocked sensor).
Perseguição: O modo de perseguição é ativado se um tanque inimigo é capturado pelo radar. Nesse caso, o tanque se posiciona e faz as movimentações necessárias para ir de encontro ao tanque inimigo.
Ataque: O comportamento de ataque envolve o uso do radar combinado com o uso de armamento, disparos são executados na direção do inimigo: dois disparos no centro (se o tanque inimigo for detectado no centro), movimentos de Slide (quando possível, para o lado em que o radar indicar) e, novamente, dois disparos no centro (se o tanque inimigo estiver no centro do radar).
Como já mencionamos anteriormente, o esquema do Bot 2 consiste em comportamentos de busca, perseguição, ataque e busca por armamento. Os comportamentos do Bot 2 são iguais aos do bot anterior, exceto por um item na condição: a quantidade de mísseis que possui. Se o bot possui quantidade superior ou igual a quatro tudo ocorre como no Bot 1, caso contrário o Bot 2 assume postura evasiva em busca de armamento.
Evita o Inimigo e Busca por Armamento: se a quantidade de mísseis que possui for menor que quatro, então o tanque adota sentido contrário às direções capturadas pelos sensores (Sonar, Incoming, Rwaves e Radar). Para cada Turn executado o tanque ativa o Radar para tentar localizar mais armamento. Caso o tanque fique numa situação onde o confronto é inevitável (três direções bloqueadas e um tanque inimigo na quarta direção), então ele dispara na direção do tanque inimigo.
Implementação:
Um cuidado sobre a quantidade de condições na escrita de uma regra.
No esquema do algoritmo rete, que implementa o matching para o sistema de produção do Soar, regras com muitas condições frequentemente produzem um número elevado de casamentos (match) parciais na rede rete; comprometendo a performance do algoritmo (veja aqui relatório sobre o uso do rete no Soar e aqui uma análise). No Soar, o custo do algoritmo no pior caso para o matching de uma única produção é dado por O(W(2C-1) + P), com W (tamanho da WM), C (número de cláusulas na produção) e P (número de produções na WM). Para obter dados sobre o consumo de memória de um dado programa no Soar consulte a Seção 8.2 (pp.: 120-134) do Manual do Soar.
Além de evitar o comprometimento da performance, regras "pequenas" (com poucos itens nas condições) costumam ser mais fáceis na leitura, permitem melhor forma de reuso e são mais adequadas (no sentido de que menos condições impõem menos restrições) às formas de chunking (o chunk, ou aprendizado via chunking, ocorre com o matching de uma condição).
Analogamente, produções com muitas ações podem gerar efeitos indesejados.
No trecho de código abaixo temos a regra que propõe o operador recarga da saúde quando a condição de saúde está baixa e o tanque está em perigo (vide Soar Dogma):
# ------------------------------------------------------------------ # A noção de estar em perigo é retrada pelas três últimas condições. sp {propose*recharge*health (state <s> ^name tanksoar ^io.input-link <il>) (<il> ^health < 300 ^smell.distance < 4 # indicativo de perigo 1 -^sound silent # indicativo de perigo 2 ^incoming.<dir> yes) # indicativo de perigo 3 --> (<s> ^operator <o> +) (<o> ^name recharge-health) }
Apesar da regra acima não apresentar um número excessivo de condições, é desejável separar a noção de "estar em perigo" já que ela pode ocorrer em diversas situações e possivelmente utilizada como parte de uma condição em outras ocasiões. Nesse caso temos algo como:
# -------------------------------------------------------------- sp {elaborate*in-danger (state <s> ^name tanksoar ^io.input-link <il>) (<il> ^smell.distance < 4 # indicativo de perigo 1 -^sound silent # indicativo de perigo 2 ^incoming.<dir> yes) # indicativo de perigo 3 --> (<s> ^in-danger yes) } # -------------------------------------------------------------- sp {propose*recharge*health (state <s> ^name tanksoar ^in-danger yes ^io.input-link <il>) (<il> ^health < 300) --> (<s> ^operator <o> +) (<o> ^name recharge-health) }
Regras que propõem um operador servem para testar a existência das condições para a criação (ou não) de uma representação (uma WME) para o operador proposto com uma preferência de aceitação (a preferência é uma forma de estabelecer a candidatura do operador ao processo de seleção).
Observação final: no código acima o estado indicativo in-danger é um atributo anexado ao top-state. Nesse caso, os medidores dos sensores estão diretamente ligado ao estado, no momento em que os medidores deixarem de indicar o estado indicativo de perigo o atributo é retirado do top-state. No caso do Eaters ações desse porte devem ser gerenciados pelo próprio programador (escrever regras de remoção). Para fins de teste, faça o download do simple-bot e acrescente via VisualSoar o código abaixo no arquivo top-state (na pasta elaborations).
# ------------------------------------------------------------ # Estados de atenção (ou alerta) # # Alerta: inimigo nas redondezas sp {elaborate*state*alert (state <s> ^name tanksoar ^io.input-link.smell.distance < 15) --> (<s> ^alert yes) (write (crlf) |Alert: Smell | <s>)} # ------------------------------------------------------------ # Alerta: Capturado pelo radar sp {elaborate*state*rwaves*red-alert (state <s> ^name tanksoar ^io.input-link.rwaves.<dir> yes) --> (<s> ^alert-radar yes) (write (crlf) |Red Alert: Rwaves | <s>)} # ------------------------------------------------------------ # Alerta: Movimentação do inimigo sp {elaborate*state*sound*red-alert (state <s> ^name tanksoar ^io.input-link.sound {<direction> <> silent}) --> (<s> ^alert-sound yes) (write (crlf) |Red Alert: Sound | <s>)} # ------------------------------------------------------------ # Alerta: Sob ataque sp {elaborate*state*under-attack (state <s> ^name tanksoar ^io.input-link.incoming.<dir> yes) --> (<s> ^alert-under-attack yes) (write (crlf) |Under Attack| <s>) }
Basta carregar dois desses agentes TankSoar modificados e, conforme o andamento da simulação passo-a-passo imprima (via print S1) para verificar se os atributos referentes aos estados de alerta estão presentes ou não na estrutura top-state. Basicamente, a idéia para os agentes da Prática 2 é a mesma: se um agente procura por um recarregador que ele sabe estar a noroeste da sua posição, isto é, digamos que o agente está num estado de orientação NW, então enquanto este estado se mantiver o agente irá se locomover para o norte (se possível) e para o oeste (se possível). Caso a posição relativa se altere, o agente sai do estado NW e entra em outro estado. Há somente quatro estados possível: NW, NE, SW e SE. Veja maiores detalhes na seção Prática 2.
A partir dos códigos-fonte do Bot 1 e Bot 2, juntamente com o conteúdo das seções acima, o leitor não terá dificuldades em avaliar as implementações feitas. Ainda, parte das explicações dos bots referentes à Pratica 2 complementam esclarecimentos acerca dos bots (Bot 1 e Bot 2) da Pratica 1.
# ------------------------------
------------------------------ - # O agente adquire conhecimento sobre a posição de um # recarregador quando passa por ele. # # OBS. 1: Como a localização de um recarregador (de saúde ou # energia) não é alterada, o conhecimento do agente é mantido # durante toda a simulação (é o-support). # # OBS. 2.: a condição # -^position-health-recharger.
confirm <pos> # serve para verificar se a posição já foi "memorizada", # note-se que há somente um recarregador (de cada). # # Position: Health Recharger sp {elaborate*state*know-health- recharger-position :o-support (state <s> ^name tanksoar -^position-health-recharger. confirm <pos> ^io.input-link <input>) (<input> ^healthrecharger yes ^x <varx> ^y <vary>) --> (<s> ^position-health-recharger <info>) (<info> ^confirm yes ^xpos <varx> ^ypos <vary>) (write (crlf) |Health Reacharger Position| <s>) } # ------------------------------ ------------------------------ - # Position: Energy Recharger sp {elaborate*state*know-energy- recharger-position :o-support (state <s> ^name tanksoar -^position-energy-recharger. confirm <pos> ^io.input-link <input>) (<input> ^energyrecharger yes ^x <varx> ^y <vary>) --> (<s> ^position-energy-recharger <info>) (<info> ^confirm yes ^xpos <varx> ^ypos <vary>) (write (crlf) |Energy Reacharger Position| <s>) }
A codificação abaixo trata dos estados de orientação do bot em relação à localização dos recarregadores. O uso da representação interna é similar, pelo menos em parte, à forma como mos humanos manipulam os mapas (ou o conhecimento espacial). Por exemplo, um mapa, serve basicamente para localização ou auto-localização. Para efetuar uma locomoção pergunta-se: onde estou? Para saber: qual rumo tomar.
# ------------------------------------------------------------- # Os estados de orientação são ativados quando o bot passa por # um recarregador. # Supondo-se que o bot saiba a localização do recarregador, # então se ele souber sua própria posição saberá qual a # orientação espacial em relação ao carregador # # OBS. 1: Um dadoestado de orientação, por exemplo, # "estou a noroeste do recarregador de saúde" fica ativado quando o # bot estiver à noroeste do recarregador de saúde. # Caso contrário é desativado (é i-supported). # # OBS, 2: Note-se que no simple bot não há representação interna.
# O código abaixo difere no mapping bot apenas em aspectos # técnicos (no manuseio das coordenadas), a idéia é a mesma. # # Orientation: NW of Health Recharge # -------------------------------------------------------------sp {elaborate*state*orientation-
nw-of-health-recharger (state <s> ^name tanksoar ^position-health-recharger <hpos> ^io.input-link <input>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<input> ^x { <varx> < <xhpos> } ^y { <vary> < <yhpos> }) --> (<s> ^orientation-nw-of-health- recharger yes) (write (crlf) |NW on Health Reacharger Position | <s>) }
O código acima pode ser replicado para as outras orientações, conforme as referências cardeais e tipos de recarregadores. Evidentemente, se o bot está a procura de um recarregador e ele possui a orientação espacial necessária, então a ação (p.ex., move e turn) pode ser orientada (condicionada). Por exemplo, se o bot sabe que está à noroeste do recarregador que procura, então ele deve dirigir-se para o sul e leste.
Abaixo exibimos o trecho de código do mapping bot (conteúdo do arquivo top-state.soar na pasta elaborations) referente ao trecho de código acima. Internaliza no mapping bot o mesmo conhecimento do simple bot.
# -------------------------------------------------------------------------- # Ativa o estado de conhecimento sobre a localizacao de um recarregador # O bot adquire conhecimento sobre a posicao de um recarregador quando passa # por ele. # # OBS. 1: Como a localizacao de um recarregador (de saude ou energia) nao eh # alterada, o conhecimento do agente sobre a localizacao eh mantido durante # toda a simulacao (eh o-support). # # OBS. 2.: a condicao # -^know-healthrecharger-position.confirm <pos> # serve para verificar se o conhecimento sobre a localizacao ja foi adquirida, # note que ha somente um recarregador (de cada: saude e energia). # # OBS. 3.: o conhecimento sobre a localizacao de um recarregador nao eh # adquirido pelo estudo do mapa fornecido, eh necessario percorrer a arena. # Uma vez conhecida a localizacao de um recarregador, o bot marca a # localizacao no mapa; o conhecimento sobre essa localizacao independe do # mapa. # # Knowledge of health recharger position sp {elaborate*state*know-healthrecharger-position :o-support (state <s> ^name tanksoar -^know-healthrecharger-position.confirm <pos> ^square.health *yes* ^square.x <posx> ^square.y <posy>) --> (<s> ^know-healthrecharger-position <info>) (<info> ^confirm yes ^xpos <posx> ^ypos <posy>) (write (crlf) |Bot knows health reacharger position|) } # -------------------------------------------------------------------------- # Knowledge of energy recharger position sp {elaborate*state*know-energyrecharger-position :o-support (state <s> ^name tanksoar -^know-energyrecharger-position.confirm <pos> ^square.energy *yes* ^square.x <posx> ^square.y <posy>) --> (<s> ^know-energyrecharger-position <info>) (<info> ^confirm yes ^xpos <posx> ^ypos <posy>) (write (crlf) |Bot knows energy reacharger position|) } # -------------------------------------------------------------------------- # Ativa estado de orientacao se estiver com a saude (respec., energia) baixa # e se souber a localizacao do recarregador de saude (respec., energia) # # O estado de orientacao abaixo refere-se a situacao em que o bot esta a # noroeste (NW: NorthWest) do recarregador de saude. sp {elaborate*state*orientation-nw-healthrecharger (state <s> ^name tanksoar ^know-healthrecharger-position <hpos> ^square <sq>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<sq> ^x { <varx> <= <xhpos> } ^y { <vary> <= <yhpos> }) --> (<s> ^orientation-nw-healthrecharger yes) (write (crlf) |Orientation: NW of health recharger |)} # -------------------------------------------------------------------------- # Ativa estado de orientacao referente aa situacao em que o bot esta a # nordeste (NE: NorthEast) do recarregador de saude. sp {elaborate*state*orientation-ne-healthrecharger (state <s> ^name tanksoar ^know-healthrecharger-position <hpos> ^square <sq>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<sq> ^x { <varx> > <xhpos> } ^y { <vary> <= <yhpos> }) --> (<s> ^orientation-ne-healthrecharger yes) (write (crlf) |Orientation: NE of health recharger |)} # -------------------------------------------------------------------------- # Ativa estado de orientacao referente aa situacao em que o bot esta a # sudoeste (SW: SouthWest) do recarregador de saude. sp {elaborate*state*orientation-sw-healthrecharger (state <s> ^name tanksoar ^know-healthrecharger-position <hpos> ^square <sq>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<sq> ^x { <varx> <= <xhpos> } ^y { <vary> > <yhpos> }) --> (<s> ^orientation-sw-healthrecharger yes) (write (crlf) |Orientation: SW of health recharger |)} # -------------------------------------------------------------------------- # Ativa estado de orientacao referente aa situacao em que o bot esta a # sudeste (SE: SouthEast) do recarregador de saude. sp {elaborate*state*orientation-se-healthrecharger (state <s> ^name tanksoar ^know-healthrecharger-position <hpos> ^square <sq>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<sq> ^x { <varx> > <xhpos> } ^y { <vary> > <yhpos> }) --> (<s> ^orientation-se-healthrecharger yes) (write (crlf) |Orientation: SE of health recharger |)} # -------------------------------------------------------------------------- # Ativa estado de orientacao referente aa situacao em que o bot esta a # noroeste (NW: NorthWest) do recarregador de energia. sp {elaborate*state*orientation-nw-energyrecharger (state <s> ^name tanksoar ^know-energyrecharger-position <hpos> ^square <sq>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<sq> ^x { <varx> <= <xhpos> } ^y { <vary> <= <yhpos> }) --> (<s> ^orientation-nw-energyrecharger yes) (write (crlf) |Orientation: NW of energy recharger |)} # -------------------------------------------------------------------------- # Ativa estado de orientacao referente aa situacao em que o bot esta a # nordeste (NE: NorthEast) do recarregador de energia. sp {elaborate*state*orientation-ne-energyrecharger (state <s> ^name tanksoar ^know-energyrecharger-position <hpos> ^square <sq>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<sq> ^x { <varx> > <xhpos> } ^y { <vary> <= <yhpos> }) --> (<s> ^orientation-ne-energyrecharger yes) (write (crlf) |Orientation: NE of energy recharger |)} # -------------------------------------------------------------------------- # Ativa estado de orientacao referente aa situacao em que o bot esta a # sudoeste (SW: SouthWest) do recarregador de energia. sp {elaborate*state*orientation-sw-energyrecharger (state <s> ^name tanksoar ^know-energyrecharger-position <hpos> ^square <sq>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<sq> ^x { <varx> <= <xhpos> } ^y { <vary> > <yhpos> }) --> (<s> ^orientation-sw-energyrecharger yes) (write (crlf) |Orientation: SW of energy recharger |)} # -------------------------------------------------------------------------- # Ativa estado de orientacao referente aa situacao em que o bot esta a # sudeste (SE: SouthEast) do recarregador de energia. sp {elaborate*state*orientation-se-energyrecharger (state <s> ^name tanksoar ^know-energyrecharger-position <hpos> ^square <sq>) (<hpos> ^confirm yes ^xpos <xhpos> ^ypos <yhpos>) (<sq> ^x { <varx> > <xhpos> } ^y { <vary> > <yhpos> }) --> (<s> ^orientation-se-energyrecharger yes) (write (crlf) |Orientation: SE of energy recharger |)}
Caso o bot necessite recarregar a energia (ou a saúde), com esse conhecimento (da localização do recarregador e de sua orientação em relação a ele), as ações referentes à movimentação poderão ser melhor direcionadas. De fato, as ações associadas á locomoção podem ser classificadas como ações de retreat. A codificação abaixo (vide arquivos da pasta retreat) é um trecho do comportamento do bot nas situações acima expostas: bots com baixo nível de energia (respec., saúde) e que sabem da localização do recarregador de energia (respec., saúde).
# --------------------------------------------------------------------- # Modo retreat para recarga de energia # --------------------------------------------------------------------- sp {elaborate*retreat*energy-nw-to-south (state <s> ^name retreat ^superstate <ss>) (<ss> ^missiles-energy low ^orientation-nw-energyrecharger yes) --> (<s> ^direction south) (write (crlf) |Energy direction: go to south |)} # --------------------------------------------------------------------- sp {elaborate*retreat*energy-nw-to-east (state <s> ^name retreat ^superstate <ss>) (<ss> ^missiles-energy low ^orientation-nw-energyrecharger yes) --> (<s> ^direction east) (write (crlf) |Energy direction: go to east |)} # --------------------------------------------------------------------- sp {elaborate*retreat*energy-ne-to-south (state <s> ^name retreat ^superstate <ss>) (<ss> ^missiles-energy low ^orientation-ne-energyrecharger yes) --> (<s> ^direction south) (write (crlf) |Energy direction: go to south |)} # --------------------------------------------------------------------- sp {elaborate*retreat*energy-ne-to-west (state <s> ^name retreat ^superstate <ss>) (<ss> ^missiles-energy low ^orientation-ne-energyrecharger yes) --> (<s> ^direction west) (write (crlf) |Energy direction: go to west |)} # --------------------------------------------------------------------- sp {elaborate*retreat*energy-sw-to-north (state <s> ^name retreat ^superstate <ss>) (<ss> ^missiles-energy low ^orientation-sw-energyrecharger yes) --> (<s> ^direction north) (write (crlf) |Energy direction: go to north |)} # --------------------------------------------------------------------- sp {elaborate*retreat*energy-sw-to-east (state <s> ^name retreat ^superstate <ss>) (<ss> ^missiles-energy low ^orientation-sw-energyrecharger yes) --> (<s> ^direction east) (write (crlf) |Energy direction: go to east |)} # --------------------------------------------------------------------- sp {elaborate*retreat*energy-se-to-north (state <s> ^name retreat ^superstate <ss>) (<ss> ^missiles-energy low ^orientation-sw-energyrecharger yes) --> (<s> ^direction north) (write (crlf) |Energy direction: go to north |)} # --------------------------------------------------------------------- sp {elaborate*retreat*energy-se-to-west (state <s> ^name retreat ^superstate <ss>) (<ss> ^missiles-energy low ^orientation-se-energyrecharger yes) --> (<s> ^direction west) (write (crlf) |Energy direction: go to west |)}
A indicação da direção a ser tomada é avaliada com os outros modos do retreat (motivo pelo qual o bot entrou em retreat, por exemplo, estar sob ataque: incoming). Nesse caso, o bot deve avaliar qual a direção adequada (no nosso caso, o bot não toma a direção orientada para o recarregador, opta por fugir do ataque inimigo.)
Outros recursos do Soar podem ser adicionados à codificação dos bots TankSoar. Por exemplo, o uso explícito de estratégias de planejamento, aprendizado e propriedades da memória semântica e episódica. Veremos esses tópicos nas aulas seguintes.
Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer