Melhorando o Índio

O movimento do índio pode melhorar, podendo dobrar à esquerda e direita.

Para direcionar o índio, modificamos o método anda () para considerar a direção. Adicionamos Os Métodos Direcionais e a Fala

Neste tutorial também incuímos duas novas classes e seus comportamentos, a Oca e o Piche. Ele tem comportamentos que se assemelham entre si e também se parecem com o Vazio. Para tirar proveito disso usamos o mecanismo de heranca da linguagem Python.

See also

Este código é uma modificação do código descrito em Organizando a Taba

Pontos cardeais

Estas tuplas nomeadas formam um vetor de pares ordenados que corespondem às componentes em x e y dos vetores unitários dos pontos cardeais na Rosa dos Ventos.

Rosa dos Ventos
from collections import namedtuple as nt

Ponto = nt("Ponto", "x y")
"""Par de coordenadas na direção horizontal (x) e vertiacal (y)."""
Rosa = nt("Rosa", "n l s o")
"""Rosa dos ventos com as direções norte, leste, sul e oeste."""

Classe Indio - Com Direção

Cria o personagem principal na arena do Kwarwp na posição definida.

param imagem:A figura representando o índio na posição indicada.
param x:Coluna em que o elemento será posicionado.
param y:Linha em que o elemento será posicionado.
param cena:Cena em que o elemento será posicionado.
param taba:Representa a taba onde o índio faz o desafio.
AZIMUTE = Rosa(Ponto(0, -1),Ponto(1, 0),Ponto(0, 1),Ponto(-1, 0),)
"""Constante com os pares ordenados que representam os vetores unitários dos pontos cardeais."""

def __init__(self, imagem, x, y, cena, taba):
    self.lado = lado = Kwarwp.LADO
    self.azimute = self.AZIMUTE.n
    """índio olhando para o norte"""
    self.taba = taba
    self.vaga = self
    self.posicao = (x//lado,y//lado)
    self.indio = Kwarwp.VITOLLINO.a(imagem, w=lado, h=lado, x=x, y=y, cena=cena)
    self.x = x
    """Este x provisoriamente distingue o índio de outras coisas construídas com esta classe"""
    if x:
        self.indio.siz = (lado*3, lado*4)
        """Define as proporções da folha de sprites"""
        self.mostra()

Os Métodos Direcionais e a Fala

O método mostra modifica a exibição da folha de sprites, posicionando o canto superior esquerdo (origem) em uma coordenada negativa em relação à janela de exibição. Desta forma, a posição correta do índio vai aparecer na tela.

folha de sprites do índio

Note

O termo Sprite vem do latim spiritus que significa “Espíritos”, mas também pode significar “fada” ou “duende”. No âmbito da computação gráfica (Quer seja em games ou não) são os quadros de movimento que são desenhados individualmente com uma pequena variação entre si, mas obedecendo um padrão sequencial que quando disposto numa ordem coerente acaba gerando uma animação de movimento quando exibidas em sucessão. Ver externamente Definição de Sprite Sheet

Os métodos esquerda () e direita () trocam a direção (azimute) para o qual o índio está olhando. Eles usam uma propriedade das listas em Python de circularidade dos índices negativos. Quando se tenta acessar uma posição de uma lista com um índice negativo, a posição é contada do fim da lista para o princípio.

graph LR subgraph Positivos s0[0]--> s1[1]--> s2[2]--> s3[3] end subgraph Lista l0[n]--> l1[l]--> l2[s]--> l3[o] end subgraph Negativos n4[-4]--> n3[-3]--> n2[-2]--> n1[-1] end
def mostra(self):
    """ Modifica a figura (Sprite) do índio mostrando para onde está indo.
    """
    sprite_col = sum(self.posicao) % 3
    """Faz com que três casas adjacentes tenha valores diferentes para a coluna do sprite"""
    sprite_lin = self.AZIMUTE.index(self.azimute)
    """A linha do sprite depende da direção dque índio está olhando"""
    self.indio.pos = (-self.lado*sprite_col, -self.lado*sprite_lin)

def esquerda(self):
    """ Faz o índio mudar da direção em que está olhando para a esquerda.
    """
    self.azimute = self.AZIMUTE[self.AZIMUTE.index(self.azimute)-1]
    self.mostra()

def direita(self):
    """ Faz o índio mudar da direção em que está olhando para a direita.
    """
    self.azimute = self.AZIMUTE[self.AZIMUTE.index(self.azimute)-3]
    self.mostra()

def fala(self, texto=""):
    """ O índio fala um texto dado.

    :param texto: O texto a ser falado.
    """
    self.taba.fala(texto)

Os Protocolos de Saída

O ìndio teve que ser modificado para incorporar um novo duplo despacho de saída. Ele terá que consultar primeiro a vaga onde está para saber se pode sair

Ao receber de um evento o comando anda (), ele terá que consultar com um sair () a vaga onde está. Em uma vaga normal ele recebe o siga (), segue em frente e executa o seu _anda () original. Se ele entrou numa vaga que tinha uma armadilha, agora a vaga onde está é a armadilha. Em uma armadilha leniente, segue normalmente. Numa armadilha rígida, o seu pedido de sair () é ignorado e ele não recebe a resposta siga ().

sequenceDiagram participant Evento participant Indio participant Origem participant Ocupante Evento->>Indio: anda # pede para entrar Indio->>Origem: sair # pede para sair Origem->>Indio: siga Note left of Origem: Origem vago <br/>autoriza a saída Indio->>Indio: _anda Note left of Indio: Autorizado pela vaga <br/>executa o anda Indio->>Ocupante: sair Note right of Origem: Origem é ocupante <br/>consulta ocupante Ocupante->>Indio: siga Note left of Ocupante: Ocupante autoriza <br/>indio a seguir Indio->>Indio: _anda Note left of Indio: Autorizado a sair <br/>executa o anda Ocupante->>Ocupante: pass Note left of Ocupante: Ocupante armadilha <br/>indio não segue
def anda(self):
    """Objeto tenta sair, tem que consultar a vaga onde está"""
    self.vaga.sair()

def sair(self):
    """Objeto de posse do índio tenta sair e é autorizado"""
    self.vaga.ocupante.siga()

def siga(self):
    """Objeto tentou sair e foi autorizado"""
    self._anda()

def _anda(self):
    """ Faz o índio caminhar na direção em que está olhando.
    """
    destino = (self.posicao[0]+self.azimute.x, self.posicao[1]+self.azimute.y)
    """A posição para onde o índio vai depende do vetor de azimute corrente"""
    taba = self.taba.taba
    if destino in taba:
        vaga = taba[destino]
        """Recupera na taba a vaga para a qual o índio irá se transferir"""
        vaga.acessa(self)

Kwarwp - Oca e Piche

A classe Kwarwp vai ser modificada para agregar novas fábricas. Além do Classe Indio - Com Direção e do Vazio - A Vaga teremos a Oca - O Destino e o Piche - A Armadilha

Jogo para ensino de programação.

param vitollino:
 Empacota o engenho de jogo Vitollino.
param mapa:Um texto representando o mapa do desafio.
param medidas:Um dicionário usado para redimensionar a tela.
class Kwarwp():
   VITOLLINO = None
   ...
   self.o_indio = None
   """Instância do personagem principal, o índio, vai ser atribuído pela fábrica do índio"""
   ...

See also

Veja o código anterior da classe no tutorial Organizando a Taba

Dicionário com Oca e Piche

O método cria () define as fábricas de componentes.

No dicionário pode se ver que “&” agora remete a maloc e “@” remete a barra. Uma outra alteração é que a construção do sol agora se liga ao tratador de evento esquerda. Isto permite que se experimente andar com o índio no cenário. Note que agora o ceu foi convertido em atributo de instância. Por isso agora ele é referido como self.ceu. O céu será referenciado por outros objetos que precisam escrever textos, e o céu é onde será escrito.

param mapa:Um texto representando o mapa do desafio.
def cria(self, mapa=""):
    """ Fábrica de componentes.

    :param mapa: Um texto representando o mapa do desafio.
    """
    Fab = nt("Fab", "objeto imagem")
    """Esta tupla nomeada serve para definir o objeto construido e sua imagem."""

    fabrica = {
    "&": Fab(self.maloc, f"{IMGUR}dZQ8liT.jpg"), # OCA
    "^": Fab(self.indio, f"{IMGUR}UCWGCKR.png"), # INDIO
    ".": Fab(self.vazio, f"{IMGUR}npb9Oej.png"), # VAZIO
    "_": Fab(self.coisa, f"{IMGUR}sGoKfvs.jpg"), # SOLO
    "#": Fab(self.coisa, f"{IMGUR}ldI7IbK.png"), # TORA
    "@": Fab(self.barra, f"{IMGUR}tLLVjfN.png"), # PICHE
    "~": Fab(self.coisa, f"{IMGUR}UAETaiP.gif"), # CEU
    "*": Fab(self.coisa, f"{IMGUR}PfodQmT.gif"), # SOL
    "|": Fab(self.coisa, f"{IMGUR}uwYPNlz.png")  # CERCA
    }
    """Dicionário que define o tipo e a imagem do objeto para cada elemento."""
    mapa = mapa if mapa != "" else self.mapa
    """Cria um cenário com imagem de terra de chão batido, céu e sol"""
    mapa = self.mapa
    lado = self.lado
    cena = self.v.c(fabrica["_"].imagem)
    self.ceu = self.v.a(fabrica["~"].imagem, w=lado*self.col, h=lado-10, x=0, y=0, cena=cena, vai=self.executa,
                   style={"padding-top": "10px", "text-align": "center"})
    """No argumento *vai*, associamos o clique no céu com o método **executa ()** desta classe.
       O *ceu* agora é um argumento de instância e por isso é referenciado como **self.ceu**.
    """
    sol = self.v.a(fabrica["*"].imagem, w=60, h=60, x=0, y=40, cena=cena, vai=self.esquerda)
    """No argumento *vai*, associamos o clique no sol com o método **esquerda ()** desta classe."""
    self.taba = {(i, j): fabrica[imagem].objeto(fabrica[imagem].imagem, x=i*lado, y=j*lado+lado, cena=cena)
        for j, linha in enumerate(mapa) for i, imagem in enumerate(linha)}
    """Posiciona os elementos segundo suas posições i, j na matriz mapa"""
    cena.vai()
    return cena

Comandos para o Índio

O método fala () é usado por objetos que emitem mensagens. Ele instrumentaliza o céu para que um texto em html seja escrito nele.

O método esquerda () invoca sua contrapartida na instância de Indio. O método executa () invoca sua contrapartida na instância de Indio.

def fala(self, texto=""):
    """ O Kwarwp é aqui usado para falar algo que ficará escrito no céu.
    """
    self.ceu.elt.html = texto
    pass

def esquerda(self, *_):
    """ Ordena a execução do roteiro do índio.
    """
    self.o_indio.esquerda()

def executa(self, *_):
    """ Ordena a execução do roteiro do índio.
    """
    self.o_indio.executa()

Fabricando a Oca e o Piche

O método maloc () invoca a criação da Oca - O Destino. O método barra () invoca a criação do Piche - A Armadilha.

def maloc(self, imagem, x, y, cena):
    """ Cria uma maloca na arena do Kwarwp na posição definida.

    :param x: coluna em que o elemento será posicionado.
    :param y: linha em que o elemento será posicionado.
    :param cena: cena em que o elemento será posicionado.

    Cria uma vaga vazia e coloca o componente dentro dela.
    """
    coisa = Oca(imagem, x=0, y=0, cena=cena, taba=self)
    vaga = Vazio("", x=x, y=y, cena=cena, ocupante=coisa)
    return vaga

def barra(self, imagem, x, y, cena):
    """ Cria uma armadilha na arena do Kwarwp na posição definida.

    :param x: coluna em que o elemento será posicionado.
    :param y: linha em que o elemento será posicionado.
    :param cena: cena em que o elemento será posicionado.

    Cria uma vaga vazia e coloca o componente dentro dela.
    """
    coisa = Piche(imagem, x=0, y=0, cena=cena, taba=self)
    vaga = Vazio("", x=x, y=y, cena=cena, ocupante=coisa)
    return vaga

Ocupante e Vaga Nulos

O Kwarwp é aqui usado como um ocupante objeto nulo, usado ao fabricar espaços vazios O pedido de ocupar é ignorado.

def sai(self, *_):
    """ O Kwarwp é aqui usado como uma vaga falsa, o pedido de sair é ignorado.
    """
    pass

def ocupa(self, *_):
    """ O Kwarwp é aqui usado como um ocupante falso, o pedido de ocupar é ignorado.
    """
    pass

Vazio - A Vaga

O Vazio vai ser atualizado aqui para funcionar como uma vaga leniente, ou seja, deixa sair quem quiser abandonar a vaga.

A principal ideia aqui vai ser usar o Vazio como classe base de uma linhagem de herança, onde outras classes vão herdar o seu comportamento. No diagrama abaixo vemos que Piche herda de Vazio e por sua vez Oca herda de Piche

classDiagram Vazio <|-- Piche Piche <|-- Oca Vazio : +Vaga vaga Vazio : +Coisa ocupante Vazio : +Elemento vazio Vazio: _acessa() Vazio: _valida_acessa() Vazio: _sair() Vazio: _pede_sair() class Piche{ +Vaga vaga +Coisa ocupante +Kwarwp taba +Elemento vazio _acessa() _pede_sair() } class Oca{ _acessa() _pede_sair() }

Note

O principal mecanismo do recurso da herança é permitir que uma classe possa ser derivada de uma classe base, permitindo que um comportamento mais especifico seja implementado na subclasse. A herança, é também uma importante característica para ao reuso de algoritmos e evitar códigos redundantes que possam tornar difícil a manutenção da base de códigos. Ver externamente O Uso da Herança

class Vazio():
    """ Cria um espaço vazio na taba, para alojar os elementos do desafio.

        :param imagem: A figura representando o índio na posição indicada.
        :param x: Coluna em que o elemento será posicionado.
        :param y: Cinha em que o elemento será posicionado.
        :param cena: Cena em que o elemento será posicionado.
    """

    def __init__(self, imagem, x, y, cena, ocupante=None):
        self.lado = lado = Kwarwp.LADO
        self.posicao = (x//lado,y//lado-1)
        self.vazio = Kwarwp.VITOLLINO.a(imagem, w=lado, h=lado, x=x, y=y, cena=cena)
        self._nada = Kwarwp.VITOLLINO.a()
        self.acessa = self._acessa
        """O **acessa ()** é usado como método dinâmico, variando com o estado da vaga.
        Inicialmente tem o comportamento de **_acessa ()** que é o estado vago, aceitando ocupantes"""
        self.ocupante = ocupante or self
        """O ocupante se não for fornecido é encenado pelo próprio vazio, agindo como nulo"""
        self.acessa(ocupante)
        self.sair = self._sair
        """O **sair ()** é usado como método dinâmico, variando com o estado da vaga.
        Inicialmente tem o comportamento de **_sair ()** que é o estado leniente, aceitando saidas"""

O Objeto de Estado Sair

O Vazio - A Vaga tem um outro estado de objeto além do acessa (). Este objeto é o sair (), que assume os estados _sair quando a vaga está livre ou _pede_sair () quando está ocupada.

def _sair(self):
    """Objeto tenta sair e secebe autorização para seguir"""
    self.ocupante.siga()

def _pede_sair(self):
    """Objeto tenta sair e consulta o ocupante para seguir"""
    self.ocupante.sair()

Piche - A Armadilha

O piche vai funcionar como uma forma especializada do Vazio - A Vaga

class Piche(Vazio):
    """ Poça de Piche que gruda o índio se ele cair nela.

        :param imagem: A figura representando o índio na posição indicada.
        :param x: Coluna em que o elemento será posicionado.
        :param y: Cinha em que o elemento será posicionado.
        :param cena: Cena em que o elemento será posicionado.
        :param taba: Representa a taba onde o índio faz o desafio.
    """

def __init__(self, imagem, x, y, cena, taba):
    self.taba = taba
    self.vaga = taba
    self.lado = lado = Kwarwp.LADO
    self.posicao = (x//lado,y//lado-1)
    self.vazio = Kwarwp.VITOLLINO.a(imagem, w=lado, h=lado, x=0, y=0, cena=cena)
    self._nada = Kwarwp.VITOLLINO.a()
    self.acessa = self._acessa
    """O **acessa ()** é usado como método dinâmico, variando com o estado da vaga.
    Inicialmente tem o comportamento de **_acessa ()** que é o estado vago, aceitando ocupantes"""
    self.sair = self._sair
    """O **sair ()** é usado como método dinâmico, variando com o estado da vaga.
    Inicialmente tem o comportamento de **_sair ()** que é o estado vago, aceitando ocupantes"""

def ocupa(self, vaga):
    """ Pedido por uma vaga para que ocupe a posição nela.

    :param vaga: A vaga que será ocupada pelo componente.

    No caso do piche, requisita que a vaga seja ocupada por ele.
    """
    self.vaga.sai()
    self.posicao = vaga.posicao
    vaga.ocupou(self)
    self.vaga = vaga

def _pede_sair(self):
    """Objeto tenta sair mas não é autorizado"""
    self.taba.fala("Você ficou preso no piche")

Oca - O Destino

A Oca vai funcionar como uma forma especializada do Piche - A Armadilha

class Oca(Piche):
    """ A Oca é o destino final do índio, não poderá sair se ele entrar nela.

        :param imagem: A figura representando o índio na posição indicada.
        :param x: Coluna em que o elemento será posicionado.
        :param y: Cinha em que o elemento será posicionado.
        :param cena: Cena em que o elemento será posicionado.
        :param taba: Representa a taba onde o índio faz o desafio.
    """

    def _pede_sair(self):
        """Objeto tenta sair mas não é autorizado"""
        self.taba.fala("Você chegou no seu objetivo")

    def _acessa(self, ocupante):
        """ Atualmente a posição está vaga e pode ser acessada pelo novo ocupante.

        A responsabilidade de ocupar definitivamente a vaga é do candidato a ocupante
        Caso ele esteja realmente apto a ocupar a vaga e deve cahamar de volta ao vazio
        com uma chamada ocupou.

            :param ocupante: O canditato a ocupar a posição corrente.
        """
        self.taba.fala("Você chegou no seu objetivo")
        ocupante.ocupa(self)