# als-parcial2-2021_23 (c) 2023 Baltasar MIT License <baltasarq@gmail.com>


import json


"""

    1. (4 pts.) VideoESEI, un joven estudio de videojuegos,
       quiere llegar pronto al mercado con un juego run&gun,
       es decir, el jugador, uno de los Pj (personajes),
       siempre está corriendo, y disparando a enemigos para
       aumentar su puntuación (momento en el que debe ralentizarse).
       Como los plazos son muy justos,
       deciden utilizar Python como lenguaje de programación,
       y llegan a la clase listada más abajo.
       La clase debe almacenar el id (__id) y el delta (__delta) del Pj,
       de forma que el id es una cadena de caracteres que
       se pasa como parámetro, y el delta un número entero que indica
       cuántos píxeles avanza de cada vez, inicializado a cero.
       El método set_delta(nd: int) acepta un entero para cambiar el delta
       por el nd (nuevo delta), y __str_() devuelve la información en el
       formato "<nombre_clase> (<id>): <delta>".
       Hay que tener en cuenta las siguientes cuestiones:
        a) La clase Pj debe ser capaz de responder a las propiedades delta e id,
           aunque por razones de compatibilidad con la versión de Python
           que se distribuirá con el juego, no pueden ser propiedades como tal.
           Así, si p1 = Pj("Corredor"), entonces p1.delta devolverá el
           atributo __delta, y p1.id devuelve su atributo __id,
           a pesar de que no existan las propiedades id o delta.
        b) Cuando se crea p1 como PjCaminante,
           su delta debe inicializarse a PjCorredor.MIN_DELTA // 2.
           En cambio, cuando se crea como PjCorredor, debe inicializarse a
           PjCorredor.MIN_DELTA * 2.
        c) Cuando set_delta(nd) es llamado para modificar el delta con
           nd (nuevo delta), si el nuevo delta es mayor o igual
           que PjCorredor.MIN_DELTA, la clase del objeto debe cambiar
           automáticamente a PjCorredor. Si es más pequeño
           que PjCorredor.MIN_DELTA, debe cambiar a PjCaminante.
           Por ejemplo, si p1 = PjCaminante(“Mario”),
           por tanto type(p1) == PjCaminante;
           después de p1.set_delta(10), type(p1) == PjCorredor.
        d) Cuando dispara() es llamado siendo p1 un PjCorredor,
           automáticamente el delta debe cambiar a PjCorredor.MIN_DELTA // 2,
           y por tanto la clase de p1 debe pasar a ser PjCaminante.
           Así, si p1 = PJCorredor(“Flash”) y p1.dispara() (devuelve “BANG!”),
           entonces type(p1) == PJCaminante debe ser cierto.
           
	2. (3pts.) Cree los métodos to_str_json() -> str
		y from_str_json(s: str) -> Pj.
		El primero escribe la codificación JSON de los datos necesarios
		del objeto a una cadena de caracteres, el segundo (estático),
		toma una cadena de caracteres con el contenido necesario
		en formato JSON y crea un objeto PjCaminante o PjCorredor con dicho contenido,
		según sea apropiado.
		Estos métodos son utilizados desde to_json(f) y from_json(f)
		para volcar o crear los datos desde o hacia un archivo.
"""
class Pj:
    def __init__(self, id):
        self.__id = id
        self.__delta = 0

    def __getattr__(self, item):
        if item == "delta":
            return self.__delta
        elif item == "id":
            return self.__id

        raise AttributeError(item)

    def set_delta(self, value):
        self.__delta = value

    def to_json(self, f) -> str:
        f.write(self.to_str_json())

    def to_str_json(self) -> str:
        return json.dumps(self.__dict__)

    @staticmethod
    def from_json(f):
        return Pj.from_str_json(str.join("\n", f.readlines()))

    @staticmethod
    def from_str_json(s: str):
        toret = PjCaminante("")
        toret.__dict__ = json.loads(s)
        toret.set_delta(toret.delta)
        return toret

    def __str__(self):
        return f"{self.__class__.__name__} ({self.id}): {self.delta}"


class PjCorredor(Pj):
    MIN_DELTA = 4
    def __init__(self, id):
        super().__init__(id)
        self.set_delta(PjCorredor.MIN_DELTA * 2)

    def set_delta(self, value):
        super().set_delta(value)
        if value < PjCorredor.MIN_DELTA:
            self.__class__ = PjCaminante

    def dispara(self):
        self.set_delta(PjCorredor.MIN_DELTA // 2)
        return self.dispara()


class PjCaminante(Pj):
    def __init__(self, id):
        super().__init__(id)
        self.set_delta(PjCorredor.MIN_DELTA // 2)

    def set_delta(self, value):
        super().set_delta(value)

        if value > PjCorredor.MIN_DELTA:
            self.__class__ = PjCorredor

    def dispara(self):
        return "BANG!"


"""
3. (3pts.) Escriba la función filtra_pjs(f: callable, pjs: list[Pj) -> list[Pj],
   que recibe como parámetros una lista de objetos Pj y
   una función que devolverá verdadero o falso según el objeto Pj que se le pase.
   Deben utilizarse exclusivamente lambdas como parámetro de este método.
   Cree la función busca_pjs_veloces(pjs: list[Pj]) -> list[Pj],
   que llama a la función anterior para encontrar a aquellos Pj’s
   que tienen un delta superior a 5.
"""


def filtra_pjs(f: callable, pjs: list[Pj]) -> list[Pj]:
    return [pj for pj in pjs if f(pj)]						# opc1
    # return filter(f, pjs)										# opc2


"""
def busca_pjs_veloces(pjs: list[Pj]) -> list[Pj]:
     return filtra_pjs(lambda pj: pj.delta > 5, pjs)			# opc1
"""

busca_pjs_veloces = lambda pjs: \
					filtra_pjs(lambda pj: pj.delta > 5, pjs)	# opc2


if __name__ == "__main__":
    pj1 = PjCaminante("Mario")
    print(str(pj1))
    pj1.set_delta(8)
    print(str(pj1))
    print(pj1.dispara())
    print(str(pj1))

    print("\nGuardando y recuperando pj1 (Mario)")
    str_pj1 = pj1.to_str_json()
    print(Pj.from_str_json(str_pj1))

    print("\nBuscando a los veloces de entre Mario(2), Flash(8) y Sonic(8)")
    pj2 = PjCorredor("Flash")
    pj3 = PjCorredor("Sonic")
    print(", ".join(str(pj) for pj in busca_pjs_veloces([pj1, pj2, pj3])))

"""
# Tests als-parcial2-2021_23 (c) 2023 Baltasar MIT License <baltasarq@gmail.com>


import unittest

from als_parcial2_2021_23 import Pj, PjCorredor, PjCaminante, filtra_pjs, busca_pjs_veloces


class TestPj(unittest.TestCase):
    def setUp(self):
        self.pj = Pj("Mario")
        self.pjcorredor = PjCorredor("Sonic")
        self.pjcaminante = PjCaminante("Yoshi")

    def test_creacion_pj(self):
        self.assertEqual("Mario", self.pj.id)
        self.assertEqual(0, self.pj.delta)
        self.assertEqual("(Mario): 0", str(self.pj))

    def test_creacion_pj_corredor(self):
        self.assertEqual("Sonic", self.pjcorredor.id)
        self.assertEqual(8, self.pjcorredor.delta)
        self.assertEqual("PjCorredor (Sonic): 8", str(self.pjcorredor))

    def test_creacion_pj_caminante(self):
        self.assertEqual("Yoshi", self.pjcaminante.id)
        self.assertEqual(2, self.pjcaminante.delta)
        self.assertEqual("PjCaminante (Yoshi): 2", str(self.pjcaminante))

    def test_modificacion_delta_pj(self):
        self.pj.set_delta(10)
        self.assertEqual(10, self.pj.delta)
        self.assertEqual(Pj, self.pj.__class__)

    def test_modificacion_delta_corredor(self):
        self.pjcorredor.set_delta(10)
        self.assertEqual(10, self.pjcorredor.delta)
        self.assertEqual(PjCorredor, self.pjcorredor.__class__)

    def test_modificacion_delta_caminante(self):
        self.pjcaminante.set_delta(3)
        self.assertEqual(3, self.pjcaminante.delta)
        self.assertEqual(PjCaminante, self.pjcaminante.__class__)

    def test_modificacion_delta_caminante_a_corredor(self):
        self.pjcaminante.set_delta(10)
        self.assertEqual(10, self.pjcaminante.delta)
        self.assertEqual(PjCorredor, self.pjcaminante.__class__)

    def test_modificacion_delta_corredor_a_caminante(self):
        self.pjcorredor.set_delta(2)
        self.assertEqual(2, self.pjcorredor.delta)
        self.assertEqual(PjCaminante, self.pjcorredor.__class__)

    def test_bang_caminante(self):
        self.assertEqual("BANG!", self.pjcaminante.dispara())
        self.assertEqual(PjCaminante, self.pjcaminante.__class__)

    def test_bang_corredor(self):
        self.assertEqual("BANG!", self.pjcorredor.dispara())
        self.assertEqual(PjCaminante, self.pjcorredor.__class__)
        self.assertEqual(PjCorredor.MIN_DELTA // 2, self.pjcorredor.delta)

    def test_json(self):
        str_pj = self.pj.to_str_json()
        str_pjcaminante = self.pjcaminante.to_str_json()
        str_pjcorredor = self.pjcorredor.to_str_json()

        pj2 = Pj.from_str_json(str_pj)
        pjcaminante2 = Pj.from_str_json(str_pjcaminante)
        pjcorredor2 = Pj.from_str_json(str_pjcorredor)

        self.assertEqual(self.pj.id, pj2.id)
        self.assertEqual(self.pj.delta, pj2.delta)

        self.assertEqual(self.pjcaminante.id, pjcaminante2.id)
        self.assertEqual(self.pjcaminante.delta, pjcaminante2.delta)

        self.assertEqual(self.pjcorredor.id, pjcorredor2.id)
        self.assertEqual(self.pjcorredor.delta, pjcorredor2.delta)


    def test_filtra(self):
        pjs = [self.pj, self.pjcaminante, self.pjcorredor]

        pjs_rapidos = busca_pjs_veloces(pjs)
        self.assertEqual([self.pjcorredor], pjs_rapidos)

        pjs_lentos1 = filtra_pjs(lambda pj: pj.delta > 0 and pj.delta < PjCorredor.MIN_DELTA, pjs)
        pjs_lentos2 = filtra_pjs(lambda pj: type(pj) == PjCaminante, pjs)
        self.assertEqual(pjs_lentos1, pjs_lentos2)


if __name__ == "__main__":
        unittest.main()
"""