You are here

Atividade 2 - Execução do Tutorial 3

Esta atividade corresponde à execução de todo o tutorial 3. As principais etapas estão descritas a seguir.

 

2.1. Criação da primeira versão do agente, capaz de se mover para posições desbloqueadas, virar, fazer meia-volta e ligar e desligar o radar.

 
Nesta primeira parte do tutorial 4 regras simples são propostas, permitindo que o tanque se mova pelo tabuleiro, ligando o radar toda vez que mudar de direção e desligando o radar quando não tiver detectado nada interessante à frente - algum recarregador, pacote de mísseis ou outro tanque.
 
  • A primeira regra é a para mover à frente, caso não haja obstáculo.
  • A segunda regra é para virar para esquerda ou direita (qual posição estiver livre) caso à frente haja um bloqueio. Esta regra ativa o radar com potência 13, que é a maior que faz sentido ligar: das 16 posições do tabuleiro, as extremidades estão sempre ocupadas por obstáculos e um pelo próprio tanque.
  • A terceira regra é para fazer meia-volta, caso existam obstáculos tanto à frente quanto à esquerda e direita.
  • A quarta regra é para desligar o radar caso ele não tenha detectado nenhum objeto "interessante": "energy", "health", "missiles" ou "tank".
 
Com essas regras básicas o tanque é capaz de explorar o tabuleiro, mas de uma forma pouco eficiente, fazendo movimentos cíclicos e só mudando de direção quando o movimento para frente está impedido. O uso do radar também é pouco eficiente, pois ele permanece ligado a cada movimento mesmo, por exemplo, quando o tanque está fazendo meia-volta.
 
 

2.2. Criação de submetas ou operadores de alto nível

 
É possível criar operadores de alto nível e agrupar, dentro deles, sub-operadores. Assim permite-se separar cada conjunto de sub-operadores, simplificando o tratamento entre eles, uma vez que deixa de existir a possibilidade de interferência mútua durante o processo de avaliação do Soar.
 
No caso deste jogo, operadores de alto nível podem ser os que determinam se o tanque deve:
 
  1. ANDAR (Wander) até avistar um tanque
  2. PERSEGUIR (Chase) um tanque avistado
  3. ATACAR (Attack) um tanque na linha de tiro
  4. RECUAR (Retreat) de um ataque ou de outro tanque
 
Para cada um desses operadores de alto nível, é possível então definir sub-operadores que, neste caso, corresponderiam a ações diretas dos tanques:
 
  1. Sub-operadores ANDAR: "Move", "Turn"
  2. Sub-operadores PERSEGUIR: "Move", "Turn"
  3. Sub-operadores ATACAR: "Move", "Turn", "Slide", "Fire"
  4. Sub-operadores RECUAR: "Move", "Wait"
 
 
 
Esquema ilustrativo dos operadores de alto nível propostos para TankSoar
 
 
A implementação de operadores de alto nível faz uso da capacidade do Soar de criação automática de sub-estado: toda vez que um impasse é encontrado, Soar gera um sub-estado onde um novo contexto é gerado e novos operadores podem ser selecionados e aplicados de forma a tentar solucionar o impasse.
 
Impasses no Soar podem acontecer quando:
 
  • O operador proposto não é alterado - "operator no-change".
  • Quando o estado atual não é alterado - "state no-change".
  • Quando múltiplos operadores são propostos e não existe como decidir qual aplicar - "tie impasse".
  • Quanto múltiplos operadores são propostos e as suas preferências são conflitantes - "conflict impasse".
 
 
É a exploração da primeira forma de impasse descrita acima - "operator no-change" - que corresponde à criação de sub-estados: inicialmente operadores de alto-nível podem ser propostos de acordo com condições específicas, mas sem haver regras diretas para a sua aplicação; assim, Soar irá criar um sub-estado para tentar resolver o impasse. Uma vez criado esse sub-estado, uma regra de elaboração - "default" para o uso de sub-estados - deve ser criada para associar ao nome desse sub-estado o nome do operador que havia sido proposto:
 
 
 
sp {elaborate*state*name
  (state <s> ^superstate.operator.name <operator-name>)
-->
  (<s> ^name <operator-name>)
}
 
 
 
O nome do sub-estado deve, então, ser usado para ser condição do conjunto de sub-operadores válidos para esse sub-estado, ou seja, para esse operador de alto-nível que foi selecionado; a seguir está a regra básica de mover à frente, no caso do operator de alto nível WANDER:
 
 
 
sp {wander*propose*move-forward
   (state <s> ^name wander
              ^io.input-link.blocked.forward no)
-->
   (<s> ^operator <o> +)
   (<o> ^name move
        ^actions.move.direction forward)
}
 
 
 
 
É importante ressaltar que o estado original - ou super-estado do estado atual - é acessível a partir do seu sub-estado. Uma outra elaboração - também "default" no uso de sub-estados - deve ser criada para claramente indicar, dentro do sub-estado, qual é o estado incial da execução:
 
 
 
sp {elaborate*state*top-state
  (state <s> ^superstate.top-state <ts>)
-->
  (<s> ^top-state <ts>)
}
 
 
 
Além de serem acessíveis, os super-estados - considerando uma possível sequência de sub-estados aninhados - continuam ativos, ou seja, continuam sendo analizados e atualizados - por exemplo, na sua memória de trabalho - a cada ciclo de execução do Soar. De fato, isso é a essência do mecanismo de criação de sub-estados para resolução de impasses: assim que estes são solucionados, os sub-estados são automaticamente destruídos, uma vez que no seu super-estado (onde o impasse originou-se) o impasse deixa de existir.
 
Um sub-estado não possui extensão "io", apenas existente no estado inicial. Entretanto, entrada e saída também podem ser feitas em sub-estados, utilizando-se a extensão "io" do estado inicial, a qual continua válida e ativa. Assim, uma terceira extensão - também "default" nesse cenário - deve ser criara para estabelecer - e propagar - uma ligação com o "io" do estado original:
 
 
 
sp {elaborate*state*io
   (state <s> ^superstate.io <io>)
-->
   (<s> ^io <io>)
}
 
 
2.2.1. Persistência de elementos da memória de trabalho X subestados
 
Um ponto importante do uso de subestados é a persistência dos elementos criados na memória de trabalho durante a sua execução.
 
Para ser efetivo na resolução de impasses, o uso de subestados deve permitir a modificação do superestado de forma a impedir/resolver o impasse. Entretanto, a própria resolução do impasse faz com que o subestado seja automaticamente destruído, ou seja, removido da memória de trabalho o que, em teoria, provocaria a remoção de todos os elementos criados a partir do objeto do subestado.
 
Soar possui um mecanismo para resolver esse impasse, permitindo que certas extensões, criadas a partir de um subestado, sobrevivam à sua destruição. De forma similar à avalição de "o-support" e "i-support", Soar tem o mecanismo de justification, o qual extende até os elementos do superestado a verificação do suporte que será dado a um elemento criado num subestado.
 
Numa análise retroativa, Soar busca até chegar ao superestado todos os elementos da memória de trabalho que foram sucessivamente consultados até levar à criação - no subestado - da extensão analisada. Nesse percurso, caso chegue a conclusão que as bases dessa cadeia de elementos foi a aplicação de um operador, Soar determina que essa extensão terá "o-support" e, consequentemente, permanecerá na memória mesmo o subestado sendo destruído. A figura abaixo apresenta essa análise para o caso da elaboração que remove um som ouvido pelo tank (sensor "^sound") que foi armazenado por um certo tempo, permitindo que o tanque lembrasse a direção que o som fora inicialmente ouvido:
 
 
 
 
Essa análise é feita na memória de trabalho a partir do disparo da seguinte regra, que propõe a remoção do som antigo:
 
 
#
# Propose to remove expired sound direction saved in top-state
#
 
sp {all*propose*remove-sound
   (state <s> ^name << wander chase retreat attack >>
              ^superstate.sound.expire-time <clock>
              ^io.input-link.clock > <clock>)
-->
   (<s> ^operator <o> + =, >)
   (<o> ^name remove-sound)
}
 
 
 

2.3. Observações importantes:

 
  1. Soar não permite elementos de memória com exatamente os mesmos valores duplicados; assim é possível ter 2 regras criando exatamente os mesmos elementos de memória, pois Soar irá criá-los apenas uma vez. É o que acontece com as regras de elaboração que permitem indicar o estado de baixo nível de energia ou de mísseis:
 
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)
}
 
Essas regras irão disparar toda vez que a extensão "^name" ou "^missiles"/"^energy" forem alteradas; no entanto, apenas uma extensão "^missiles-energy low" será criada.
 
 
  1. Durante o tutorial, logo após a implementação inicial dos operadores de alto nível, foi acrescentada a seguinte regra para desligar o radar, seguindo a abordagem que havia sido indicada na seção anterior, quando ainda não eram utilizados os operadores de alto nível:
 
#
# Radar-off elaboration
#
 
sp {elaborate*radar-off
   (state <s> ^operator.actions <a>
              ^io.input-link <il>)
   (<il> ^radar-status on
        -^radar.<< energy health missiles tank >>)
  -(<a> ^radar.switch on)
-->
   (<a> ^radar.switch off)
}
 
 
Assim como no caso do controle do escudo, esse comando é adicionado a um comando já existente, para mandar sempre todos os comandos juntos.
 
 
 
  1. Houve um problema de prioridade ("tie impasse") com as regras "remove-sound" e "fire" uma vez que ambas estavam definidas como "as mais prioritárias". A solução foi fazer com que as regras de ATTACK sejam priorizadas apenas entre elas, com demostrado na regra abaixo:

 

#
# Attack operators preference rules - to avoid conflict with external operator rules like "remove-sound".
        
sp {select*fire-against-others
   (state <s> ^name attack
              ^operator <op1> +
              ^operator <op2> +)
   (<op1> ^name fire)
   (<op2> ^name <> fire)
-->
   (<s> ^operator <op1> > <op2>)
}
 
 
De acordo com a regra acima, a operação "fire" tem prioridade sobre as operações "slide", "move-forward" e "turn-and-fire", todas do operador de alto nível ATTACK.
 
 

 

  1. Não existe regra para CHASE no caso do som vir de um lado que se está bloqueado. Outra situação encontrada é no caso do RETREAT onde não tem para onde fugir. Nesses casos o agente executa a operador de alto nível WAIT - criado como "fallback" - com uma certa frequência.

Para o caso do operador de alto nível CHASE, foi proposta a seguinte regra para executar o "slide", caso a direção do som esteja bloqueada:

 

#
# Propose to slide if sound direction is blocked
#
 
sp {chase*propose*slide
   (state <s> ^name chase
              ^sound-direction <direction>
              ^io.input-link <il>
              ^superstate <ss>)
   (<ss> ^side-direction.<direction> <side-direction>)
   (<il> ^blocked <direction>
        -^blocked <side-direction>)
-->
   (<s> ^operator <o> + =)
   (<o> ^name slide
        ^action.move.direction <side-direction>)
   (write (crlf) | CHASE: Proposing slide | <side-direction>)
}
 
 
 
  1. Uma das regras para atualização do mapa gerou um problema, detectado durante a execução da ATIVIDADE 3, considerando as demais regras: marcando direto como OPEN a posição em que o tanque está removia os carregadores, caso um deles estivesse nessa posição; por algum motivo a regra de recriação dos carregadores - a partir do status de recarga em curso - não disparava em algumas situações. A regra foi então modificada para checar se a posição não era um recarregador:

 

 

#
# Positively mark the current square as open if it is not health/energy recharger
#
 
sp {elaborate*map*record-open*current-square
   :o-support
   (state <s> ^name tanksoar
              ^square <sq>)
   (<sq> ^x <x>
         ^y <y>)
  -{(<sq> ^<< open energy health >> *yes*)}
-->
   (<sq> ^open *yes*)
   (write (crlf) | MAP: mark current square (| <x> |, | <y> |) as open|)
}
 
 
  1. Um outro problema das regras de atualização dos mapas - de acordo com o sugerido no tutorial - foi o fato delas criarem numa mesma posição do mapa uma indicação de RECARREGADOR (tanto de ENERGIA quanto de SAÚDE) e uma indicação de OPEN. Isso acontecia se o tanque chegasse ao recarregador totalmente sem energia, ou seja, sem conseguir utilizar o radar para mapear a posição anteriormente: nesse cenário tanto a regra que marca a posição atual do tanque como OPEN quanto a regra que marca o RECARREGADOR cujo recarregamento estiver em curso disparam, criando ambas indicações para aquela posição. Essa dupla marcação criava impasses durante o procedimento de busca de caminho, desenvolvido na ATIVIDADE 3. Foi criada uma regra para remover uma marcação de OPEN caso a posição fosse indicada como RECARREGADOR:

 

#
# Remove open from current square if energy or health recharger
#
 
sp {elaborate*map*remove-open*energy-health
   :o-support
   (state <s> ^name tanksoar
              ^square <sq>)
   (<sq> ^{<< health energy >> <what>} *yes*
          ^x <x>
          ^y <y>)
   (<sq> ^open *yes*
         ^x <x>
         ^y <y>)
-->
   (<sq> ^open *yes* -)
   (write (crlf) | MAP: remove open mark from square (| <x> |, | <y> |) which is | <what> | recharger|)
}
 

 

 

2.4. Pontos importantes desse tutorial

 
  1. Introdução à utilização de subestados.
  2. Detalhamento sobre criação de regras com o-support i-support, especialmente no caso de uso de subestados; utilização do operador ":o-support".
  3. Uso de estruturas na memória de trabalho para realização de cálculos - exemplo da criação dos mapas.
 
 

 

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer