You are here

Atividade 3

Operador Move Generalizado

 

    O operador move-to-food que foi desenvolvido anteriormente não gerava um comportamento adequado para as criaturas eaters. A criatura poderia ficar presa numa célula sem comida ao seu redor, e nem tinha preferência por comida bônus ao invés de comida normal. Nesta seção, será desenvolvido um operador move generalizado que permite, à criatura, mover-se para uma célula com qualquer tipo de conteúdo. Também serão adicionadas preferências pelo tipo de comida consumido pela criatura.

    A regra de proposição deste operador deve testar se há uma célula, ao redor da criatura, para onde a criatura possa se mover. Ou seja, a criatura deve evitar células que contenham paredes (wall). As células para onde a criatura pode se mover são aquelas que contenham: comida normal (normalfood), comida bônus (bonusfood), célula vazia (empty), ou mesmo outra criatura (eater).

    Há duas formas de se fazer isso. Pode-se testar se uma célula contém os valores aceitáveis para o movimento. Ou, pode-se testar se a célula contém o valor inadequado ao movimento da criatura. O código abaixo ilustra a primeira situação:

sp {propose*move1a
   (state <s> ^io.input-link.my-location.<dir>.content
              { <content> << empty normalfood bonusfood eater >> })
-->
   (<s> ^operator <o> + =)
   (<o> ^name move
        ^direction <dir>
        ^content <content>)}

    Foi adicionada, ao operador (<o> ^content <content>), a informação sobre o conteúdo da célula selecionada. Isso facilitará a tomada de decisão sobre a seleção dos operadores. A variável <content> armazenará essa informação. Esse tipo de teste não é adequado. Se, posteriormente, um novo tipo de comida for acrescentado ao ambiente das criaturas eaters, esta regra deverá ser re-escrita. Para evitar esse problema, o correto seria testar a única condição desfavorável ao movimento da criatura. A versão final das regras de proposição e aplicação do operador move está exibida na figura abaixo:

    Na regra propose*move, o símbolo "<>" que precede o valor wall equivale a "não igual". Dessa forma, qualquer valor, exceto aquele, será aceito pela condição da regra. A regra que aplica o operador é semelhante àquelas desenvolvidadas anteriormente. Também não foi alterada a regra que remove os operadores anteriormente selecionados, mostrada abaixo:

sp {apply*move*remove-move
   (state <s> ^io.output-link <ol>
              ^operator.name move)
   (<ol> ^move <direction>)
   (<direction> ^status complete)
-->
   (<ol> ^move <direction> -)}

    As regras descritas acima geram um comportamento, na criatura, de evitar células com paredes. O comportamento da criatura pode ser enriquecido através de novas regras que criam preferências por certos movimentos. Por exemplo, a criatura poderia preferir mover-se para uma célula com comida bônus em detrimento à comida normal ou uma célula vazia. Ou, mover-se para uma célula com comida normal ao invés de para uma célula vazia ou que contenha outra criatura. A figura abaixo exibe essas regras adicionais que criam preferências pela seleção de operadores:

    A regra select*move*bonusfood-better-than-normalfood-empty indica a preferência por selecionar um operador move cujo movimento leva a criatura para uma célula com comida bônus. Esta regra testa os operadores que foram propostos anteriormente pela regra propose*move, escolhendo entre dois operadores de iguais preferências (state <s> ^operator <o1> + ^operator <o2> +) qual será, de fato, o predileto. A regra realiza isso testando o atributo ^content de cada operador proposto. Caso ocorra de um dos operadores possuir o valor bonusfood (<o1> ^name move ^content bonusfood), a preferência deste operador será aumentada (<s> ^operator <o1> > <o2>). O símbolo ">" indica uma preferência maior pela seleção desse operador.

    A regra select*move*avoid-empty-eater indica a preferência por selecionar um operador move cujo movimento leva a criatura para uma célula com comida normal, caso não haja comida do tipo bônus em nehuma outra célula. Ou seja, se a regra anterior não aumentou a preferência de nenhum operador proposto, a única opção de escolha que resta será preferir uma célula com comida normal em detrimento dos outros valores possíveis. Esta regra é um pouco diferente da anterior. A regra testa se os operadores propostos (state <s> ^operator <o1> +), anteriormente, possuem valores do tipo eater ou empty (<o1> ^name move ^content << empty eater >>). Caso o operador seja de um desses tipos, a preferência de seleção deste operador será diminuída (<s> ^operator <o1> <). O símbolo "<" indica uma preferência menor à seleção do operador.

    Seria interessante explicar um pouco mais esse processo de preferências para seleção de operadores. As preferências se assemelham a filtros, e são processadas na seguinte ordem:

Acceptable (+): Indica que um valor é candidato para a seleção. Apenas valores com preferência aceitável (+) tem o potencial de serem selecionados.

Reject (-): Declara que o valor não é um cadidato para a seleção. Um valor não será selecionado se tem uma preferência deste tipo (-).

Better (>), Worse (<): No caso de A > B essa preferência (>) indica que o valor "A" deve ser selecionado. No caso de A < B a preferência (<) declara a opção de selecionar o valor "B".

Best (>): Um valor com esta preferência (>) terá a preferência de seleção sobre outros valores que contenham preferências apenas aceitáveis (+).

Worst (<): Um valor com esta preferência (<) só será selecionado se não houver outras opções. Valores com preferências aceitáveis (+) terão prioridade sobre este tipo de preferência.

Indifferent (=): Indica uma preferência indiferente (=) na seleção de operadores. Entende-se que o processo de seleção de operadores deverá ser realizado aleatóriamente.

    A figura, a seguir, mostra o comportamento da criatura eater gerado pela produção move. Não são exibidos os comportamentos iniciais da criatura. Inicialmente, a criatura realmente evita se mover para células com paredes, e sempre escohe as células com comida bônus ao invés de células com comida normal. (Neste exemplo, não foi usado um segundo eater). No entanto, a figura a seguir demonstra um comportamento interessante da criatura: a criatura tende a ficar presa numa vasta região que não tenha comida ao seu redor. Isto é, a criatura exibe um comportamento do tipo "ir e vir", tentando avidamente encontrar novas células com comida. Este comportamento precisa ser melhorado, conforme será visto nas seções seguintes.

    A figura, a seguir, demonstra o resultado da proposição e seleção de operadores pela produção move. Neste exemplo, foram usados dois agentes (criaturas eaters). Pode-se observar que muitas regras são disparadas e retraídas ao mesmo tempo. O operador selecionado, O32, move a criatura para uma célula com comida bônus (O32 ^content bonusfood +). A figura exibe as preferências dos operadores propostos. O operador O32 tem uma preferência maior sobre outros operadores (O32>O29, O32>O30, O32>O31). Os operadores O30 e O31 referem-se a células com comida normal (O30 ^content normalfood + , O31 ^content normalfood +). O operador O29 tem uma preferência reduzida (S1 ^operator O29 <) visto que refere-se a uma célula vazia (O29 ^content empty +).

 

Operador Advanced Move

    Este operador, advanced-move, visa melhorar o comportamento da criatura eater. Conforme descrito anteriormente, na última seção, o operador move tem uma tendência de gerar comportamentos do tipo "ir e vir" aleatóriamente quando a criatura está cercada por células vazias. Às vezes, ocorre da criatura mover-se para uma célula, vazia, e em seguida retornar para a célula, também vazia, em que já se encontrava. Esse tipo de movimento deve ser evitado. Isso será sanado por meio da criação de estruturas persistentes na memória de trabalho.

    A criatura terá que se recordar do último movimento executado, para não cometer o erro de voltar para a mesma célula de onde acabou de vir. Essa informação consta no operador move que foi aplicado (atributo ^direction). No entanto, este operador é removido da memória de trabalho tão logo tenha acabado de ser aplicado. Algumas estruturas de dados deverão ser criadas para guardar esse tipo de informação (^direction) sobre o último operador aplicado. A regra abaixo implementa a criação de tais estruturas:

sp {initialize*state*directions
   (state <ss> ^type state)
   -->
   (<ss> ^directions <n> <e> <s> <w>)  
   (<n> ^value north ^opposite south)
   (<e> ^value east  ^opposite west)
   (<s> ^value south ^opposite north)
   (<w> ^value west  ^opposite east)}

    A regra cria uma estrutura no estado (<ss>) na memória de trabalho que armazena as informações sobre cada direção (<n>, <e>, <s>, <w>), sendo que cada direção possui um atributo (^opposite) que indica a direção contrária a esta. As direções são armazenadas no atributo ^directions do estado (<ss>). Esta regra disparará no primeiro ciclo da execução do programa SOAR, quando o estado (s1 ^type state) é adicionado à memória de trabalho. E tais estruturas não serão mais removidas da memória de trabalho porque suas condições sempre casam durante a existência da criatura eater.

    As seguintes regras devem ser adicionadas à produção move, criando a nova produção advanced-move, para gravar as informações sobre o último movimento realizado pela criatura. A regra apply*move*create*last-direction usa uma variável <direction> para armazenar a direção (^direction) do operador selecionado, e, grava essa informação no atributo ^last-direction do estado (<s>). A regra apply*move*remove*last-direction remove o WME que gravou a direção do último operador aplicado, quando um novo operador é selecionado. Esta regra testa se o atributo ^last-direction não tem o mesmo valor do atributo ^direction do operador atual selecionado. A figura abaixo ilustra estas regras:

    Para usar as novas estruturas criadas na memória de trabalho, a informação sobre a direção do último movimento da criatura, há a necessidade de escrever duas novas regras: propose*move*no-backward (modificação da antiga regra de proposição) e select*move*reject*backward.

sp {propose*move*no-backward
   (state <s> ^io.input-link.my-location.<dir>.content { <co> <> wall }
              ^directions <d>
             -^last-direction <o-dir>)
   (<d> ^value <dir>
        ^opposite <o-dir>)
-->
   (<s> ^operator <o> +, =)
   (<o> ^name move
        ^direction <dir>
        ^content <co>)}

    Essa regra, acima, é semelhante àquela proposta anteriormente (propose*move) exceto por acrescentar uma condição de teste para evitar que a criatura mova-se de volta para a célula anteriormente ocupada pela criatura. Esta regra evita a proposição de um operador que execute tal movimento contrário, de volta a posição anterior. A regra testa se existe uma célula adjacente cuja direção (^direction) não é igual ao oposto (^opposite) da última direção (^last-direction) em que a criatura se moveu (<d> ^value <dir> ^opposite <o-dir>). Ou seja, se a nova direção proposta for igual ao oposto da última direção, significa que esse movimento fará a criatura voltar para trás, para a posição ocupada anteriormente. Para realizar isto, é necessário negar o atributo responsável pela última direção (-^last-direction <o-dir>). Dessa forma, esta regra só não irá disparar quando o oposto da última direção for igual a própria direção proposta.

sp {select*move*reject*backward
   (state <s> ^operator <o> +
              ^directions <d>
              ^last-direction <dir>)
   (<d> ^value <dir>
        ^opposite <o-dir>)
   (<o> ^name move
        ^direction <o-dir>)
-->
   (write | Reject | <o-dir>)
   (<s> ^operator <o> -)}

    A regra, acima, cria uma preferência que impede um operador de ser selecionado, caso este operador execute um movimento de retorno à célula anteriormente ocupada. Essa regra testa o valor dos atributos ^direction, da direção proposta, e ^opposite, da última direção aplicada. Caso haja casamento destes valores, o operador proposto é rejeitado (<s> ^operator <o> -).

    A figura, abaixo, exibe o resultado do uso da produção advanced-move. Pode-se ver que as duas criaturas praticamente acabaram com toda a comida disponível no seu ambiente. As criaturas evitaram uma à outra, evitaram paredes, tiveram preferências por células com comida bônus, e evitaram células vazias, preferindo células com comida normal. As criaturas não ficaram presas em áreas repletas de células vazias. Elas também não executaram movimentos do tipo "ir e vir" pelo seu ambiente.

    Pode-se observar, na figura seguinte, que alguns operadores foram rejeitados. Esses são aqueles operadores que propunham movimentos de retorno à células anteriormente ocupadas pela criatura. Por exemplo, foi selecionado um operador O530 cuja direção de movimento da criatura é o sul (south). Na sequência, foi proposto um operador cuja direção de movimento era para o norte, ou seja, esse operador faria a criatura retornar para a sua posição anterior. Este operador foi rejeitado (Reject north). Um outro operador proposto, O532 (direção west), foi selecionado e aplicado.

 

Operador Jump

    Os exemplos anteriores de produções executavam um único movimento move, para mover a criatura de uma célula para outra célula adjacente a esta. Essas produções aplicavam sempre o mesmo tipo de operador. Nesta seção, será desenvolvido um segundo tipo de operador, o operador jump. Este operador permitirá às criaturas saltar de uma célula para outra célula que esteja à duas células de distância da anterior. No entanto, este movimento custará 5 pontos para a criatura. A criatura poderá saltar sobre uma parede, mas, não pode saltar para uma célula que contenha uma parede.

    Diferente do operador move, que testava se as células adjacentes não continham uma parede, o operador jump testa se as células a dois movimentos a frente não contêm uma parede. A figura, abaixo, ilustra como esse teste é realizado. Cada célula possui um atributo <dir> que aponta para as células adjacentes a esta. No entanto, cada célula apontada por esse atributo também contém seu próprio atributo <dir>. Portanto, a regra testa as células adjacentes a cada célula adjacente à célula em que se encontra a criatura eater (^io.input-link.my-location.<dir>.<dir>.content). A direção para cada teste é a mesma (north.north, south.south, east.east, west.west).

    A regra apply*move é semelhante àquela usada no operador move, exceto pelo fato de que esta regra pode aplicar tanto o operador move quanto o operador jump (<name> << move jump >>). A regra que remove os operadores que foram aplicados, descrita abaixo, também removerá qualquer tipo de operador selecionado (move ou jump).

sp {apply*move*remove-move
   (state <s> ^io.output-link <ol>
              ^operator.name <name>)
   (<ol> ^<name> <direction>)
   (<direction> ^status complete)
-->
   (<ol> ^<name> <direction> -)}

    A figura abaixo exibe o resultado do uso do operador jump:

    Embora não seja possível visualizar, a criatura saltou sobre algumas paredes e teve um comportamento do tipo "ir e vir", saltando. Pois, não há regras para evitar esse tipo de salto "de volta pra tráz". É possível visualizar, na figura, as células vazias alternadas deixadas pelos saltos realizados pela criatura. Apenas usando esse tipo de movimento, de saltar, é provável que a criatura levasse muito tempo para conseguir consumir todo o alimento disponível no seu ambiente. Portanto, seria interessante usar ambos movimentos (operadores), move e jump, numa única produção para gerar um comportamento mais eficiente da criatura (conforme será proposto a seguir). A figura abaixo mostra a tela do SOAR Debugger deste experimento. A figura exibe alguns operadores que foram prospostos e o último operador aplicado (aba output).

    Seria interessante agregar as regras do operador jump com as regras do operador move, criando a produção jump-and-move. Essa produção possui algumas peculiaridades. Há duas regras para propor os operadores. A regra propose*move que testa o conteúdo das células adjacentes à criatura eater, e a regra propose*jump que testa as células a dois movimentos da célula da criatura. Dependendo das condições, um ou ambos operadores podem ser propostos. Porém, existe apenas uma regra para aplicar e apenas uma regra para remover os operadores. Essas regras estão representadas na figura abaixo:

    Nas regras que propõem os operadores, move e jump, foi acrescentado um novo atributo ao operador proposto (<o> ^actions), responsável por armazenar a direção do movimento de cada operador proposto. Na regra propose*move, esse atributo define a direção do movimento do operador move (<o> ^actions.move.direction <dir>). Na regra propose*jump, esse atributo define a direção do movimento do operador jump (<o> ^actions.jump.direction <dir>). A regra que aplica os operadores, conforme figura acima, recupera essas informações (<o> ^actions <act> e <act> ^<att> <value>) para criar os comandos na interface de saída (<ol> ^<att> <value>). O mesmo ocorre (<ol> ^<att> <value> e <value> ^status complete) com a regra que remove (<ol> ^<att> <value> -) os operadores já aplicados.

    A produção jump-and-move também possui algumas modificações para criar as estratégias de seleção de operadores. Novas estruturas de dados (^name-content-value) serão criadas na memória de trabalho para armazenar os valores associados ao conteúdo de uma célula, conforme ilustrado abaixo:

sp {init*elaborate*name-content-value
   (state <s> ^type state)
   -->
   (<s> ^name-content-value <c1> <c2> <c3> <c4> <c5>    
                       <c6> <c7> <c8>)
   (<c1> ^name move ^content empty ^value 0)
   (<c2> ^name move ^content eater ^value 0)
   (<c3> ^name move ^content normalfood ^value 5)
   (<c4> ^name move ^content bonusfood ^value 10)
   (<c5> ^name jump ^content empty ^value -5)
   (<c6> ^name jump ^content eater ^value -5)
   (<c7> ^name jump ^content normalfood ^value 0)
   (<c8> ^name jump ^content bonusfood ^value 5)}

    As regras, descritas na próxima figura, vão comparar os valores de cada operador proposto e criar as preferências para aqueles de maior valor. A regra elaborate*operator*value vai casar o operador proposto (<o>  ^name <name> ^content <content>) com o valor adequado (<ccv> ^name <name> ^content <content> ^value <value>) indicado na estrutura criada (^name-content-value). Em seguida, a regra copia o valor, indicado na estrutura, para o operador proposto (<o> ^value <value>). A regra select*compare*best*value compara o valor associado a cada operador (<o1> ^value <v> e <o2> ^value < <v>) e cria uma melhor preferência aos operadores de maior valor (<s> ^operator <o1> > <o2>).

    A figura abaixo exibe alguns operadores selecionados pela produção jump-and-move. A tela do SOAR Debugger indica que alguns operadores selecionados (e aplicados) são do tipo jump e outros são do tipo move. A aba operator indica que o operador selecionado, atualmente, é do tipo move e moverá a criatura para uma célula com comida normal, e o valor associado a este operador, pela regra de elaboração de operadores, é igual a 5. A criatura, agora, apresenta um comportamento mais inteligente. A criatura tem preferência por células com comida do tipo bônus, evita paredes, escolhe células com comida (normal ou bônus) ao invés de células vazias. A criatura salta paredes e evita regiões com muitas células vazias. Porém, após muitos movimentos, ainda pode ocorrer de a criatura ficar presa numa região sem células com comida ao seu redor. Novas estratégias deverão ser criadas para sanar este problema.

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer