Source code for kwarwp.kwarapp

# kwarwp.kwarwp.main.py
# SPDX-License-Identifier: GPL-3.0-or-later
""" Jogo para ensino de programação Python.

    .. codeauthor:: Carlo Oliveira <carlo@ufrj.br>

    Classes neste módulo:
        - :py:class:`Kwarwp`    Jogo para ensino de programação.
        - :py:class:`Indio`     Personagem principal do jogo.
        - :py:class:`JogoProxy` Proxy que enfileira comandos gráficos.

    Changelog
    ---------

    .. versionadded::    20.08.b1
        Adicionou :class:`JogoProxy` para realizar o passo a passo.
        Capacidade de gerenciar mais de um índio.

    .. versionchanged::    20.08.b0
        Moveu constantes de classe VITOLLINO, LADO para Vazio.
        Moveu :class:`Vazio`, :class:`Oca`, :class:`Piche` para kwarwpart.
        
    .. versionadded::    20.08.a3
        Movimentação do índio para :py:meth:`Indio.esquerda` e 
        :py:meth:`Indio.direita`. Fala do índio: :py:meth:`Indio.fala`.
        classes Oca e Piche, double dispatch para sair.
            
    .. versionadded::    20.08.a2
        Classe Vazio que recebe cada componente do mapa.
        Movimentação do índio é feita pulando para outro vazio.
            
    .. versionadded::    20.08.a1
        Classe Indio que executa roteiro e anda.
            
    .. versionadded::    20.08.a0
        Fábrica de componentes
        classe Indio.
        Usa um mapa de caracteres para colocar os elementos.
            
    .. versionadded::    20.07
        classe Kwarwp.

"""
from collections import namedtuple as nt, deque
from kwarwp.kwarwpart import Vazio, Piche, Oca, Tora, Pedra, NULO

IMGUR = "https://imgur.com/"
"""Prefixo do site imgur."""
MAPA_INICIO = """
..#^¨..
"""
"""Mapa com o posicionamento inicial dos elementos."""
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."""


[docs]class JogoProxy(): """ Proxy que enfileira comandos gráficos. :param vitollino: Empacota o engenho de jogo Vitollino. :param elt: Elemento que vai ser encapsulado pelo proxy. :param proxy: Referência para o objeto proxy parente. :param master: Determina se este elemento vai ser mestre de comandos. """ def __init__(self, vitollino=None, elt=None, proxy=None, master=False): class AdaptaElemento(vitollino.a): """ Adapta um Elemento do Vitollino para agrupar ocupa e pos. """ def ocupa(self, ocupante=None, pos=(0, 0)): ocupante = ocupante or NULO ocupante.pos = pos # print(f"AdaptaElemento pos: {self.pos}") super().ocupa(ocupante) if ocupante else None def fala(self, texto): self.elt.html = texto self.v = vitollino self.proxy = proxy or self self.master = master self._corrente = self self.comandos = [] self._ativa = False """Cria um referência para o jogo do vitollino""" self.ae = AdaptaElemento """Cria um referência o Adapador de Eelementos""" self.elt = elt @property def siz(self): """Propriedade tamanho""" return self.elt.siz
[docs] def a(self, *args, **kwargs): """Método fábrica - Encapsula a criação de elementos :param args: coleção de argumentos posicionais. :param kwargs: coleção de argumentos nominais. :return: Proxy para um Elemento construído com estes argumentos. """ return JogoProxy(elt=self.ae(*args, **kwargs), vitollino=self.v, proxy=self)
[docs] def e(self, *args, **kwargs): """Método fábrica - Encapsula a criação de elementos ativos, que executam scripts :param args: coleção de argumentos posicionais. :param kwargs: coleção de argumentos nominais. :return: Proxy para um Elemento construído com estes argumentos. """ return JogoProxy(elt=self.ae(*args, **kwargs), vitollino=self.v, proxy=self, master=True)
[docs] def cria(self): """Fábrica do JogoProxy""" return self
@property def corrente(self): """Ativa Fábrica do JogoProxy""" return self.proxy._corrente @corrente.setter def corrente(self, mestre): """Ativa Fábrica do JogoProxy""" self._corrente = mestre
[docs] def ativa(self): """Ativa Fábrica do JogoProxy""" # JogoProxy.ATIVA = True self._ativa = True self.proxy.corrente = self
[docs] def lidar(self, metodo): """Lida com modo de operação do JogoProxy""" #self.master.corrente(self) self.ativa() if self.master else None print(self._ativa, self.proxy._ativa, metodo) self.corrente._enfileira(metodo) if self.proxy._ativa else self._executa(metodo)
[docs] def c(self, *args, **kwargs): """Encapsula a criação de cenas - apenas delega. :param args: coleção de argumentos posicionais. :param kwargs: coleção de argumentos nominais. :return: Uma Cena do Vitollino construída com estes argumentos. """ return self.v.c(*args, **kwargs)
@siz.setter def siz(self, value): """Propriedade tamanho""" self.elt.siz = value @property def elt(self): """Propriedade elemento""" return self.elt @property def pos(self): """Propriedade posição""" return self.elt.pos @property def x(self): """Propriedade posição x""" return self.elt.x @property def y(self): """Propriedade posição y""" return self.elt.y @pos.setter def pos(self, value): """Propriedade posição""" def _pos(val=value): self.elt.pos = val self.lidar(_pos)
[docs] def ocupa(self, ocupante=None, pos=(0, 0)): """Muda a posição e atitude de um elemento""" def _pos(val=ocupante): destino = val.elt if val else None self.elt.ocupa(destino, pos) self.lidar(_pos)
def _enfileira(self, metodo): """Coloca um comando na fila""" self.comandos.append(metodo) def _executa(self, metodo): """Executa imediamente um comando, não põe na fila""" metodo()
[docs] def executa(self, *_): """Tira e executa um comando na fila""" self.comandos.pop(0)() if self.comandos else None
[docs]class Indio(): """ 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: 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. """ 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, vitollino=None): self.vitollino = vitollino or Vazio.VITOLLINO self.lado = lado = Vazio.LADO self.azimute = self.AZIMUTE.n """índio olhando para o norte""" self.taba = taba self.vaga = self self.ocupante = NULO self.posicao = (x//lado,y//lado) self.indio = self.vitollino.e(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.gira()
[docs] def ativa(self): """ Ativa o proxy do índio para enfileirar comandos. """ #self.vitollino.ativa() self.indio.ativa()
[docs] def gira(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.ocupa(ocupante=ocupante, pos=(-self.lado*sprite_col, -self.lado*sprite_lin)) # self.indio.pos = (-self.lado*sprite_col, -self.lado*sprite_lin) pos = (-self.lado*sprite_col, -self.lado*sprite_lin) self.indio.pos = pos
[docs] def mostra(self, vaga=None): """ 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.ocupa(ocupante=ocupante, pos=(-self.lado*sprite_col, -self.lado*sprite_lin)) # self.indio.pos = (-self.lado*sprite_col, -self.lado*sprite_lin) pos = (-self.lado*sprite_col, -self.lado*sprite_lin) vaga.ocupou(self, pos) # if vaga else self.indio.ocupa(None,pos=pos)
[docs] 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.gira()
[docs] 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.gira()
[docs] def fala(self, texto=""): """ O índio fala um texto dado. :param texto: O texto a ser falado. """ self.taba.fala(texto)
[docs] def anda(self): """Objeto tenta sair, tem que consultar a vaga onde está""" self.vaga.sair()
[docs] def empurra(self): """Objeto tenta sair, tem que consultar a vaga onde está""" # self.vaga.sair() # esta parte vai ser feita mais tarde. # print("Estou tentanto empurrar para a posição", self.posicao[0]+self.azimute.x, self.posicao[1]+self.azimute.y) # de resto o código é semelhante ao _anda # TODO refatorar o método _anda e empurra, pois tem código duplicado 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.empurrar(self, self.azimute)
[docs] def pega(self): """tenta pegar o objeto que está diante dele""" 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.pegar(self)
[docs] def larga(self): """tenta largar o objeto que está segurando""" 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""" # self.ocupante.largar(vaga) vaga.acessa(self.ocupante)
[docs] def sair(self): """Objeto de posse do índio tenta sair e é autorizado""" self.vaga.ocupante.siga()
[docs] 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)
[docs] def ocupou(self, ocupante): """ O candidato à vaga decidiu ocupá-la e efetivamente entra neste espaço. :param ocupante: O canditato a ocupar a posição corrente. Este ocupante vai entrar no elemento do Vitollino e definitivamente se tornar o ocupante da vaga. Com isso ele troca o estado do método acessa para primeiro consultar a si mesmo, o ocupante corrente usando o protocolo definido em **_valida_acessa ()** """ self.indio.ocupa(ocupante) self.ocupante = ocupante
[docs] def sai(self): """ Rotina de saída falsa, o objeto Indio é usado como uma vaga nula. """ pass
def _executa(self): """ Roteiro do índio. Conjunto de comandos para ele executar. """ self.esquerda() self.anda() self.pega() self.direita() self.anda() self.anda() self.anda() self.esquerda() self.anda() self.larga()
[docs] def executa(self): """ Roteiro do índio. Conjunto de comandos para ele executar. """ self.direita() self.empurra()
[docs] def passo(self): self.indio.executa()
@property def elt(self): """ A propriedade elt faz parte do protocolo do Vitollino para anexar um elemento no outro . No caso do índio, retorna o elt do elemento do atributo **self.indio**. """ return self.indio.elt
[docs] 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 índio, requisita que a vaga seja ocupada por ele. """ self.vaga.sai() self.posicao = vaga.posicao self.mostra(vaga) if self.x else vaga.ocupou(self) self.vaga = vaga
# if self.x: # self.mostra()
[docs] def acessa(self, ocupante): """ Pedido de acesso a essa posição, delegada ao ocupante pela vaga. :param ocupante: O componente candidato a ocupar a vaga já ocupada pelo índio. No caso do índio, ele age como um obstáculo e não prossegue com o protocolo. """ pass
[docs]class Kwarwp(): """ 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. """ KW = None def __init__(self, vitollino=None, mapa=None, medidas={}, indios=()): Kwarwp.KW = self Vazio.VITOLLINO = self.v = vitollino() self.vitollino = vitollino """Referência estática para obter o engenho de jogo.""" self.mapa = (mapa or MAPA_INICIO).split() """Cria um matriz com os elementos descritos em cada linha de texto""" self.taba = {} """Cria um dicionário com os elementos traduzidos a partir da interpretação do mapa""" self.o_indio = NULO self.os_indios = [] """Instância do personagem principal, o índio, vai ser atribuído pela fábrica do índio""" self.lado, self.col, self.lin = 100, len(self.mapa[0]), len(self.mapa)+1 """Largura da casa da arena dos desafios, número de colunas e linhas no mapa""" Vazio.LADO = self.lado """Referência estática para definir o lado do piso da casa.""" w, h = self.col *self.lado, self.lin *self.lado medidas.update(width=w, height=f"{h}px") self.indios = deque(indios or [Indio]) self.cena = self.cria(mapa=self.mapa) if vitollino else None
[docs] 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.indio, f"{IMGUR}nvrwu0r.png"), # INDIA "p": Fab(self.indio, f"{IMGUR}HeiupbP.png"), # PAJE "¨": Fab(self.apedra, f"{IMGUR}Sx3OH66.png"), # PEDRA ".": Fab(self.vazio, f"{IMGUR}npb9Oej.png"), # VAZIO "_": Fab(self.coisa, f"{IMGUR}sGoKfvs.jpg"), # SOLO "#": Fab(self.atora, f"{IMGUR}0jSB27g.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.passo, 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.executa) """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() Kwarwp.KW.fala(Vazio.LADO) #self.fala(Vazio.LADO) return cena
[docs] def fala(self, texto=""): """ O Kwarwp é aqui usado para falar algo que ficará escrito no céu. """ self.ceu.elt.fala(texto) pass
[docs] def sai(self, *_): """ O Kwarwp é aqui usado como uma vaga falsa, o pedido de sair é ignorado. """ pass
[docs] def ocupa(self, *_): """ O Kwarwp é aqui usado como um ocupante falso, o pedido de ocupar é ignorado. """ pass
[docs] def passo(self, *_): """ Ordena a execução do roteiro do índio. """ # self.o_indio.esquerda() # self.v.executa() # self.o_indio.passo() [indio.passo() for indio in self.os_indios]
[docs] def executa(self, *_): """ Ordena a execução do roteiro do índio. """ # self.v.ativa() # JogoProxy.ATIVA = True # self.o_indio.ativa() # self.o_indio.executa() self.v.ativa() [indio.ativa() or indio.executa() for indio in self.os_indios]
# self.os_indios[0].ativa() # self.os_indios[0].executa()
[docs] def atora(self, imagem, x, y, cena): """ Cria uma tora 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 = Tora(imagem, x=0, y=0, cena=cena, taba=self) vaga = Vazio("", x=x, y=y, cena=cena, ocupante=coisa, taba=self) coisa.vazio.vai = lambda *_: self.o_indio.larga() return vaga
[docs] def apedra(self, imagem, x, y, cena): """ Cria uma pedra 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 = Pedra(imagem, x=0, y=0, cena=cena, taba=self) vaga = Vazio("", x=x, y=y, cena=cena, ocupante=coisa, taba=self) coisa.vazio.vai = lambda *_: self.o_indio.larga() return vaga
[docs] 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, taba=self) return vaga
[docs] 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, taba=self) return vaga
[docs] def coisa(self, imagem, x, y, cena): """ Cria um elemento 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 = Indio(imagem, x=0, y=0, cena=cena, taba=self) vaga = Vazio("", x=x, y=y, cena=cena, ocupante=coisa, taba=self) return vaga
[docs] def vazio(self, imagem, x, y, cena): """ Cria um espaço vazio 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. """ vaga = Vazio(imagem, x=x, y=y, cena=cena, ocupante=NULO, taba=self) """ O Kwarwp é aqui usado como um ocupante nulo, que não ocupa uma vaga vazia.""" return vaga
[docs] def indio(self, imagem, x, y, cena): """ Cria o personagem principal 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. """ self.o_indio = self.indios[0](imagem, x=1, y=0, cena=cena, taba=self, vitollino=self.v) """ O índio tem deslocamento zero, pois é relativo à vaga. O **x=1** serve para distinguir o indio de outros derivados. """ self.o_indio.indio.vai = lambda *_: self.o_indio.pega() """o índio.vai é associado ao seu próprio metodo pega""" vaga = Vazio("", x=x, y=y, cena=cena, ocupante=self.o_indio, taba=self) self.os_indios.append(self.o_indio) self.indios.rotate() """recebe a definição do próximo índio""" return vaga
[docs]def main(vitollino, medidas={}, mapa=None, indios=()): """ Rotina principal que invoca a classe Kwarwp. :param vitollino: Empacota o engenho de jogo Vitollino. :param medidas: Um dicionário usado para redimensionar a tela. """ # print(f"main(vitollino={vitollino} medidas={medidas}") JogoProxy.COMANDOS, JogoProxy.ATIVA = [], False # print(f"def main: {JogoProxy} vitollino {vitollino}") vitollino_proxy = JogoProxy(vitollino=vitollino()).cria # print(f"def main vitollino_proxy: {vitollino_proxy}, {vitollino_proxy()}") # mapa, indios = alternate() return Kwarwp(vitollino=vitollino_proxy, medidas=medidas, mapa=mapa, indios=indios)
if __name__ == "__main__": from _spy.vitollino.main import Jogo, STYLE STYLE.update(width=600, height="500px") main(Jogo, STYLE)