from requerimientos import *

COLORES = dict()                                                #Definimos una serie de colores pre-definidos en un diccionario
COLORES["rojo"] =                   np.array([ 0,       255,   0   ], dtype=np.uint8)
COLORES["blanco"] =                 np.array([ 255,     255,   255 ], dtype=np.uint8)
COLORES["negro"] =                  np.array([ 0,       0,     0   ], dtype=np.uint8)
COLORES["magenta"] =                np.array([ 0,       255,   255 ], dtype=np.uint8)
COLORES["lila"] =                   np.array([ 0,       128,   255 ], dtype=np.uint8)
COLORES["azul"] =                   np.array([ 0,       0,     255 ], dtype=np.uint8)
COLORES["azul_claro"] =             np.array([ 128,     0,     255 ], dtype=np.uint8)
COLORES["cian"] =                   np.array([ 255,     0,     255 ], dtype=np.uint8)
COLORES["turquesa"] =               np.array([ 255,     0,     128 ], dtype=np.uint8)
COLORES["verde"] =                  np.array([ 255,     0,     0   ], dtype=np.uint8)
COLORES["lima"] =                   np.array([ 255,     128,   0   ], dtype=np.uint8)
COLORES["amarillo"] =               np.array([ 255,     255,   0   ], dtype=np.uint8)
COLORES["naranja"] =                np.array([ 128,     255,   0   ], dtype=np.uint8)

cierto = const(True)
falso = const(False)


def dormir(tiempo):
    """ Permite parar el programa un tiempo determinado.
    El tiempo puede ser en segundos 1, 2, 3... o puede ser menor de un segundo -> 0.1 (100ms)
    Ejemplo:
    dormir(50) # Esperará durante 50s
    """
    sleep(tiempo)

def dormir_ms(tiempo):
    """ Permite parar el programa un tiempo determinado.
    El tiempo es en milisegundos, 100ms son 0.1s.
    Ejemplo:
    dormir_ms(50) # Esperará durante 50ms
    """
    sleep_ms(tiempo)


def imprimir(mensaje):
    """ Muestra un mensaje en la consola del ordenador, es útil para ver información rápido
    Si colocas una variable entre corchetes mostrará su valor, ejemplo:
    f"Esto es un mensaje y el valor de X es: {X}"
    """
    print(mensaje)

def esperarParaSiempre():
    """ El programa se parará aquí indefinidamente, hay que pulsar stop para para parar el programa.
    Ejemplo:
        esperarParaSiempre() 
    """
    while True:
        sleep(1)

class MiTiraDeLeds():
    """
    Es la tira con luces, con ella podemos hacer lo siguiente:
    - pintarLed(numero_led, color): Pintar un led de un color
    - iluminar(): Enciende los leds en la tira con los colores que se hayan pintado
    - borrar(): borrar todas las luces
    - moverDerecha(pasos): Mueve todos los colores a la derecha 
    - moverIzquierda(pasos): Mueve todos los colores a la izquierda 
    - pintarArcoiris(): Pinta un arcoiris
    Ejemplo:
        leds = MiTiraDeLeds()   # Para usar la configuración por defecto
        o
        leds = MiTiraDeLeds(cantidad_de_leds=100) #si queremos cambiar la cantidad de leds
    """
    def __init__(self, pin:int=12, cantidad_de_leds:int=50) -> None:
        _pin = pin
        self._cantidad_de_leds = cantidad_de_leds
        __pinNeoPixel = machine.Pin(12, machine.Pin.OUT)
        self.__tira_leds = NeoPixel(__pinNeoPixel, self._cantidad_de_leds)  
        self.__capa = np.zeros((self._cantidad_de_leds,3), dtype=np.uint8)
        self.__brillo = 0.5
        self.__lista_colores_arcoiris = ["naranja", "amarillo","lima", "verde", "turquesa", "cian", "azul_claro" , "azul", "lila", "magenta", "rojo"]

    def __str__(self):#Sobreescribimos el valor str por defecto de la clase con un texto
         return f"Soy una tira de luces con {self._cantidad_de_leds} leds"
    def __len__(self):#Devolvemos un valor al llamar a len(objeto)
        return self._cantidad_de_leds
    
    def iluminar(self):
        """
        Enciende las luces con los colores que se hayan pintado, hay que hacerlo cada vez justo después de pintar para que los podamos ver
        Ejemplo:
        luces.iluminar()
        """
        self.__tira_leds.buf = self.__capa.tobytes()   # Nos saltamos el método __set_item__ y escribimos los colores directamente en formato bytearray
        self.__tira_leds.write() 

    def pintarLed(self, numero_de_led, color:str="azul"):
        """
        Pintamos una luz del color que queramos, hay que decirle el número del led que queremos y el color, ejemplo:
        Ejemplo:
        luces.pintarLed(numero_de_led=1, color="verde")
        o
        luces.pintarLed(1, "verde")

        luces.iluminar()
        """
        if numero_de_led <= self._cantidad_de_leds and numero_de_led > 0:
            self.__capa[numero_de_led-1] = np.array(COLORES[color], dtype=np.uint8)*(self.__brillo)
        else:
            logger.warning(f"El numero de led que se quiere pintar ->{numero_de_led}<- está fuera de rango\nEl número debe ir entre 1 y {self._cantidad_de_leds} ")
    
    def borrar(self):
        """
        Borra todos los colores, para ver las luces apagadas hay que usar iluminar.
        Ejemplo:
        luces.borrar()
        luces.iluminar()
        """
        self.__capa = np.zeros((self._cantidad_de_leds,3), dtype=np.uint8)

    def moverDerecha(self, pasos:int=1):
        """
        Mueve todas las luces a la derecha una cantidad de pasos.
        Ejemplo:
        luces.moverDerecha() <- Moverá un paso a la derecha
        luces.moverDerecha(pasos=2) <- Moverá dos paso a la derecha
        luces.iluminar()
        """
        self.__capa = np.roll(self.__capa,pasos * 3)

    def moverIzquierda(self, pasos:int=1):
        """
        Mueve todas las luces a la izquierda una cantidad de pasos.
        Ejemplo:
        luces.moverIzquierda() <- Moverá un paso a la izquierda
        luces.moverIzquierda(pasos=2) <- Moverá dos paso a la izquierda
        luces.iluminar()
        """
        self.__capa = np.roll(self.__capa,pasos * -3)

    def pintarArcoiris(self):
        """
        Pinta un arcoiris de colores.
        Ejemplo:
        luces.pintarArcoiris()
        luces.iluminar()
        """
        self.__posicion_arcoiris = 0
        self.__capa_arcoiris = np.zeros((self._cantidad_de_leds,3), dtype=np.uint8)
        self.__capa = np.zeros((self._cantidad_de_leds,3), dtype=np.uint8)
        casillas_por_color = int(self._cantidad_de_leds/len(self.__lista_colores_arcoiris))
        casillas_libres = self._cantidad_de_leds - casillas_por_color * len(self.__lista_colores_arcoiris)
        posicion = 0
        for num, color in enumerate(self.__lista_colores_arcoiris):
            self.__capa_arcoiris[posicion:posicion+casillas_por_color+casillas_libres] = np.array(COLORES[color], dtype=np.uint8)*(self.__brillo)
            posicion = posicion + casillas_por_color + casillas_libres
            if num == 0:
                casillas_libres = 0
        self.__capa = self.__capa_arcoiris
        if self.__posicion_arcoiris != 0:
            self.__capa = np.roll(self.__capa_arcoiris, 3*self.__posicion_arcoiris)
        self.__posicion_arcoiris += 1
        if self.__posicion_arcoiris >= self._cantidad_de_leds:
            self.__posicion_arcoiris = 0
    @property
    def cantidad_de_leds(self):
        return self._cantidad_de_leds


class MisMandos():
    """
    Los mandos con los que jugaremos al juego, con ellos podemos:
    - imprimirPasos("SI" o "NO"): Escribe por consola cada vez que se mueve un mando
    - imprimirBotones("SI" o "NO"): Escribe por consola cada vez que se pulsa un mando
    - giroMando1(): Es Verdadero si el mando ha girado, falso si no se ha movido
    - giroMando2(): Es Verdadero si el mando ha girado, falso si no se ha movido
    - posiciones_mando1: Son la cantidad de pasos que el mando se ha movido
    - posiciones_mando2: Son la cantidad de pasos que el mando se ha movido
    - boton1Pulsado(): Es Verdadero si el mando se ha pulsado, falso si no se ha pulsado
    - boton2Pulsado(): Es Verdadero si el mando se ha pulsado, falso si no se ha pulsado
    
    """
    def __init__(self) -> None:
        self.r2 = RotaryIRQ(pin_num_clk=27,
                                 pin_num_dt=14,
                                 min_val=0,
                                 max_val=20,
                                 reverse=False,
                                 range_mode=RotaryIRQ.RANGE_UNBOUNDED)
        self.r1 = RotaryIRQ(pin_num_clk=33,
                                 pin_num_dt=25,
                                 min_val=0,
                                 max_val=20,
                                 reverse=False,
                                 range_mode=RotaryIRQ.RANGE_UNBOUNDED)
        
        self.r1.add_listener(self.__llamada_mando1)
        self.r2.add_listener(self.__llamada_mando2)
        self.imprimir_pasos = "NO"
        self.__posiciones_mando_p1 = 0
        self.__posiciones_mando_p2 = 0
        self.__valor_mando_p2 = 0
        self.__valor_mando_p1 = 0

        self.__pin1 = machine.Pin(32, machine.Pin.IN, machine.Pin.PULL_UP)
        self.__pin1.irq(trigger=machine.Pin.IRQ_FALLING, handler=self.__llamada_boton1)
        self.__pin2 = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP)
        self.__pin2.irq(trigger=machine.Pin.IRQ_FALLING, handler=self.__llamada_boton2)
        self.__pulsacion_b1 = False
        self.__pulsacion_b2 = False
        self.__imprimir_botones = "NO"

        self.__tiempo_antirebote = 200
        self.__temporizadorB1 = Temporizador(self.__tiempo_antirebote)
        self.__temporizadorB2 = Temporizador(self.__tiempo_antirebote)
        self.__temporizadorB1.iniciar(self.__tiempo_antirebote)
        self.__temporizadorB2.iniciar(self.__tiempo_antirebote)

    def __str__(self):#Sobreescribimos el valor str por defecto de la clase con un texto
         return f"Tenemos 2 mandos que son unos encoders rotatorios con pulsadores"


    def imprimirPasos(self, valor:str="SI"):
        """
        Imprime por consola cada vez que un mando se gira si el valor es "SI", si es "NO" no hace nada
        Ejemplo:
        mandos.imprimirPasos("SI")
        """
        self.__imprimir_pasos = valor

    def imprimirBotones(self, valor:str="SI"):
        """
        Imprime por consola cada vez que un mando se pulsa si el valor es "SI", si es "NO" no hace nada
        Ejemplo:
        mandos.imprimirBotones("SI")
        """
        self.__imprimir_botones = valor

    def __llamada_mando1(self):
        if self.__valor_mando_p1 != self.r1.value():
            self.__posiciones_mando_p1 += self.r1.value() - self.__valor_mando_p1
            self.__valor_mando_p1 = self.r1.value()
        if self.__imprimir_pasos == "SI": print(f"Mando 1: Valor={self.r1.value()}, posiciones={self.__posiciones_mando_p1} ")

    def __llamada_mando2(self):
        if self.__valor_mando_p2 != self.r2.value():
                self.__posiciones_mando_p2 += self.r2.value() - self.__valor_mando_p2
                self.__valor_mando_p2 = self.r2.value()
        if self.__imprimir_pasos == "SI": print(f"Mando 2: Valor={self.r2.value()}, posiciones={self.__posiciones_mando_p2} ")

    def __llamada_boton1(self,p):
        if self.__temporizadorB1.fin():
            self.__temporizadorB1.iniciar(self.__tiempo_antirebote)
            self.__pulsacion_b1 = True
            if self.__imprimir_botones == "SI": print(f"Botón 1: Valor={self.__pulsacion_b1}")

    def __llamada_boton2(self,p):
        if self.__temporizadorB1.fin():
            self.__temporizadorB1.iniciar(self.__tiempo_antirebote)
            self.__pulsacion_b2 = True
            if self.__imprimir_botones == "SI": print(f"Botón 2: Valor={self.__pulsacion_b2}")

    def giroMando1(self) -> bool:
        """
        Devuelve Verdadero si el mando ha girado, falso si no se ha movido.
        Ejemplo:
        if mandos.giroMando1():
            imprimir("El mando 1 ha girado")
        """
        if self.__posiciones_mando_p1:
            return True
        else:
            return False

    def giroMando2(self) -> bool:
        """
        Devuelve Verdadero si el mando ha girado, falso si no se ha movido.
        Ejemplo:
        if mandos.giroMando2():
            imprimir("El mando 2 ha girado")
        """
        if self.__posiciones_mando_p2:
            return True
        else:
            return False

    @property
    def posiciones_mando1(self) -> int:
        """
        Devuelve la cantidad de posiciones o pasos que ha girado el mando.
        Ejemplo:
        pasos = mandos.posiciones_mando1
        """
        try:
            return self.__posiciones_mando_p1
        finally:
            self.__posiciones_mando_p1 = 0

    @property
    def posiciones_mando2(self) -> int:
        """
        Devuelve la cantidad de posiciones o pasos que ha girado el mando.
        Ejemplo:
        pasos = mandos.posiciones_mando2
        """        
        try:
            return self.__posiciones_mando_p2
        finally:
            self.__posiciones_mando_p2 = 0
    
    def boton1Pulsado(self):
        """
        Devuelve Verdadero si el mando se ha pulsado, falso si no se ha pulsado.
        Ejemplo:
        if mandos.boton1Pulsado():
            imprimir("El mando 1 ha sido pulsado")
        """
        try:
            if self.__pulsacion_b1:
                return True
        finally:
            self.__pulsacion_b1 = False

    def boton2Pulsado(self):
        """
        Devuelve Verdadero si el mando se ha pulsado, falso si no se ha pulsado.
        Ejemplo:
        if mandos.boton2Pulsado():
            imprimir("El mando 2 ha sido pulsado")
        """
        try:
            if self.__pulsacion_b2:
                return True
        finally:
            self.__pulsacion_b2 = False


class Bola:
    #Constructor, __init__ solo se ejecuta al crearse, osea, una vez
    def __init__(self, posicion:int=8, color:str="blanco"): #Inicializamos la clase y recogemos los argumentos a la hora de crearse

        self.__color = color
        self.__color_temporal = self.__color
        self.__posicion = posicion
        self.__largo = 1

    def __str__(self):#Sobreescribimos el valor str por defecto de la clase con un texto
         return f"Soy una bola de color {self.__color} y estoy en la posición {self.__posicion}"
    def __len__(self):#Devolvemos un valor al llamar a len(objeto)
        return self.__largo

    @property
    def posicion(self):
        return self.__posicion
    @posicion.setter
    def posicion(self, valor:int):
        self.__posicion = valor

    @property
    def color(self):
        return self.__color
    @color.setter
    def color(self, valor:str):
        if valor in COLORES:
            self.__color = valor
        else:
            logger.warning(f"El color elegido no existe")

    def ocultar(self):
        self.__color_temporal = self.__color
        self.__color = "negro"

    def mostrar(self):
        self.__color = self.__color_temporal

    def moverDerecha(self, posiciones:int=1):
        __posiciones = posiciones
        __casilla_destino = self.__posicion + __posiciones
        if __casilla_destino < 50:  # Si estamos dentro del rango
            self.__posicion += __posiciones
            self.__casillas = np.roll(self.__casillas, __posiciones * 3)
            return True
        else:                       # Nos hemos salido del rango, nos movemos solo hasta el extremo
            __posiciones = (len(self.__casillas)) - self.__posicion - 1
            self.__posicion = (len(self.__casillas))-1
            if __posiciones != 0:
                self.__casillas = np.roll(self.__casillas, __posiciones * 3)
            return False

    def moverIzquierda(self, posiciones:int=1):
        __posiciones = posiciones
        __casilla_destino = self.__posicion - __posiciones
        if __casilla_destino >= 0:
            self.__posicion -= __posiciones
            self.__casillas = np.roll(self.__casillas, -__posiciones * 3)
            return True
        else:
            __posiciones = self.__posicion
            self.__posicion = 0
            if __posiciones != 0:
                self.__casillas = np.roll(self.__casillas, -__posiciones * 3)
            return False
        
    @property               # Mejor no usar en bucle porque devuelve una copia de toda la array y es muy lento
    def leer(self):
        return self.__casillas


class Porteria:
    def __init__(self, largo:int=8, posicion:int=8, color:str="rojo"): #Inicializamos la clase y recogemos los argumentos a la hora de crearse
        self.__posicion = posicion
        
        self.__color = color
        self.__largo = largo

    def __str__(self):#Sobreescribimos el valor str por defecto de la clase con un texto
         return f"Soy una porteria de color {self.__color}, hago {self.__largo} de largo y estoy en la posición {self.__posicion}"
    def __len__(self):#Devolvemos un valor al llamar a len(objeto)
        return len(self.__largo)
    
    @property
    def largo(self):
        return self.__largo
    @largo.setter
    def largo(self, valor:int):
        self.__largo = valor

    
    @property
    def color(self):
        return self.__color
    @color.setter
    def color(self, valor:str):
        if valor in COLORES:
            self.__color = valor
        else:
            logger.warning(f"El color elegido no existe")

    @property
    def posicion(self):
        return self.__posicion
    @posicion.setter
    def posicion(self, valor:int):
        self.__posicion = valor

class Temporizador:
    """
    Inicia un temporizador el cual podemos consultar si ha pasado el tiempo
    Ejemplo:
    temporizador.iniciar(tiempo_ms=1000) # El temporizador contara 1 segundo
    """
    def __init__(self,tiempo_ms:int=100) -> None:
        self.__tiempo_ms = tiempo_ms
        self.__t = 0

    def iniciar(self,tiempo_ms:int=100) -> None:
        """
        Inicia el temporizador
        Ejemplo:
            temporizador.iniciar(tiempo_ms=1000) # El temporizador contara 1 segundo
        """
        self.__t = time.ticks_ms()

    def fin(self) -> bool:
        """
        Para comprobar si el temporizador ha finalizado, devuelve Verdadero si ha acabado sino Falso
        Ejemplo:
            if temporizador.fin(): # Si el temporizador ha acabado
        """
        if time.ticks_diff(time.ticks_ms(), self.__t) >= self.__tiempo_ms:
            return True
        else:
            return False

class VelocidadYTiempo:
    def __init__(self, velocidad_min:int=150) -> None:
        self.__tiempo_inicio = 0
        self.__tiempo_juego = 0
        self.__velocidad = velocidad_min
        self.__lista_velocidades = [200, 150, 75, 50, 25, 15, 10, 5, 4, 3, 2, 1, 0]
        self.__posicion_velocidad = 0

    @property
    def tiempo_juego(self):
        return self.__tiempo_juego/1000
    @property
    def velocidad(self):
        return self.__velocidad

    def iniciarTiempo(self):
        self.__tiempo_inicio = time.ticks_ms()
        self.__tiempo_juego = 0
        
        self.generador_tiempo_cambio = self.generarTiemposVelocidad(tiemp_min=1, tiemp_max=5)
        self.__tiempo_siguiente_cambio = next(self.generador_tiempo_cambio)
        # logger.info(f"Iniciar Tiempo:\n self.__tiempo_inicio={self.__tiempo_inicio}, self.__tiempo_juego={self.__tiempo_juego}, self.__tiempo_siguiente_cambio={self.__tiempo_siguiente_cambio}")

    def tiempoCompletado(self) -> bool:
        self.__tiempo_juego = time.ticks_diff(time.ticks_ms(), self.__tiempo_inicio)
        if int(self.__tiempo_juego/1000) > self.__tiempo_siguiente_cambio:
            self.__tiempo_siguiente_cambio = next(self.generador_tiempo_cambio)
            # logger.info(f"tiempoCompletado:\nself.__tiempo_siguiente_cambio={self.__tiempo_siguiente_cambio}")
            return True
        else:
            return False

    def reiniciarVelocidad(self):
        self.iniciarTiempo()
        self.generador_velocidad = self.generarVelocidad()
        self.__velocidad = next(self.generador_velocidad)
        # logger.info(f"reiniciar Velocidad:\nvelocidad={self.__velocidad}")
        return self.__velocidad
    
    def cambiosDeVelocidad(self):
        if self.__velocidad == 0:
            return self.__velocidad
        else:
            if self.tiempoCompletado():
                return self.cambiarVelocidad()   
            else:
                return self.__velocidad

    def cambiarVelocidad(self, pasos:int=1) -> int:
        
        self.__velocidad = next(self.generador_velocidad)
        # logger.info(f"Velocidad CAMBIADA={self.__velocidad}")
        return self.__velocidad

    def incrementarVelocidad(self):
        pass

    def generarVelocidad(self, pasos:int=1) -> int:
        posicion = 0
        while 1:
            if pasos>=0:
                if posicion + pasos >= len(self.__lista_velocidades):
                    posicion = len(self.__lista_velocidades) -1
                else:
                    posicion += pasos
            else:
                if posicion + pasos <= 0:
                    posicion = 0
                else:
                    posicion += pasos
            yield self.__lista_velocidades[posicion]

    def generarListaTiemos(self):
        pass

    def generarTiemposVelocidad(self, tiemp_min:int=3, tiemp_max:int=30) -> int:
        """
        PLANTILLA DE DIFICULTAD
        """
        plantilla = [   # Velocidad
            [5, 10],    # 200
            [5, 10],    # 150
            [5, 10],    # 75
            [15, 30],   # 50
            [15, 30],   # 25
            [15, 30],   # 15
            [15, 30],   # 10
            [15, 30],   # 5
            [20, 40],   # 4
            [20, 40],   # 3
            [20, 60],   # 2
            [20, 80],   # 1
            [60, 80]    # 0
        ]
        t = 0
        posicion = 0
        while 1:
            # t += random.randrange(tiemp_min,tiemp_max)
            try:
                t +=random.randrange(plantilla[posicion][0],plantilla[posicion][1])
                posicion += 1
                if posicion >= len(plantilla):
                    posicion -= 1

            except:
                t += plantilla[12][0]
            yield t

        """
        Necesito una lista de tiempos no lineal y dependiendo de la dificultad
        self.__lista_velocidades = [200, 150, 75, 50, 25, 15, 10, 5, 4, 3, 2, 1, 0]
        """

class Tablero:
    def __init__(self, casillas_totales:int=50, pin_boton_1:int=32, pin_boton_2:int=26) -> None: #Inicializamos la clase y recogemos los argumentos a la hora de crearse
        self.__casillas_totales = casillas_totales

        self.r2 = RotaryIRQ(pin_num_clk=27,
                                 pin_num_dt=14,
                                 min_val=0,
                                 max_val=20,
                                 reverse=False,
                                 range_mode=RotaryIRQ.RANGE_UNBOUNDED)
        self.r1 = RotaryIRQ(pin_num_clk=33,
                                 pin_num_dt=25,
                                 min_val=0,
                                 max_val=20,
                                 reverse=False,
                                 range_mode=RotaryIRQ.RANGE_UNBOUNDED)

        self.myevent = asyncio.Event()
        asyncio.create_task(self.action())
        self.r1.add_listener(self.callback)
        self.r2.add_listener(self.callback)

        self.__pin1 = machine.Pin(pin_boton_1, machine.Pin.IN, machine.Pin.PULL_UP)
        self.__pin1.irq(trigger=machine.Pin.IRQ_FALLING, handler=self.__cbBotonP1)
        self.__pin2 = machine.Pin(pin_boton_2, machine.Pin.IN, machine.Pin.PULL_UP)
        self.__pin2.irq(trigger=machine.Pin.IRQ_FALLING, handler=self.__cbBotonP2)
        self.__temporizadorP1 = Temporizador(tiempo_ms=250)
        self.__temporizadorP2 = Temporizador(tiempo_ms=250)

        self.__brillo_general = 40
        self.__brillo_bola = 100
        self.__brillo_porterias = 75
        self.__brillo_puntos = 75
        self.__brillo_fondo_puntos = 50
        self.__bola = Bola(posicion=25, color="blanco")
        # logger.info(f"self.__bola:\n{self.__bola}")
        self.__capa_bola = np.zeros((self.__casillas_totales,3), dtype=np.uint8)


        self.__lista_colores = ["lima", "verde", "turquesa", "cian", "azul_claro" , "azul", "lila", "magenta", "rojo"]
        self.__lista_colores_arcoiris = ["naranja", "amarillo","lima", "verde", "turquesa", "cian", "azul_claro" , "azul", "lila", "magenta", "rojo"]
        self.color_p1 = "naranja"
        self.color_p2 = "amarillo"
        self.color_fondo_puntos = "rojo"
        self.color_puntos = "verde"
        self.__porteria1 = Porteria(largo=10, posicion=0, color=self.color_p1)
        logger.info(f"self.__porteria1:\n{self.__porteria1}")
        self.__porteria2 = Porteria(largo=10, posicion=self.__casillas_totales-10, color=self.color_p2)
        logger.info(f"self.__porteria2:\n{self.__porteria2}")
        self.__capa_porterias = np.zeros((self.__casillas_totales,3), dtype=np.uint8)
        self.__capa_porterias[0:self.__porteria1.largo] = np.array(COLORES[self.__porteria1.color], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_porterias/100)
        self.__capa_porterias[self.__porteria2.posicion:self.__porteria2.posicion+self.__porteria2.largo] = np.array(COLORES[self.__porteria2.color], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_porterias/100)
        self.capa_salida = np.zeros((self.__casillas_totales,3), dtype=np.uint8)
        self.__velocidad = 250
        self.__direccion = 0
        self.__pulsador = 0
        self.__fase = "brillo"
        self.__dificultad = 5
        self.__empezar_p1 = False
        self.__empezar_p2 = False
        self.__puntos_p1 = 0
        self.__puntos_p2 = 0
        self.__puntos_max = 5
        self.__valor_encoder_p1 = self.r1.value()
        self.__valor_encoder_p2 = self.r2.value()
        self.__posiciones_encoder_p1 = 0
        self.__posiciones_encoder_p2 = 0
        self.__posicion_arcoiris = 0
        self.__tiempoVelocidad = VelocidadYTiempo()

        __pinNeoPixel = machine.Pin(12, machine.Pin.OUT)
        self.__tira_leds = NeoPixel(__pinNeoPixel, self.__casillas_totales)  


    def callback(self):
        self.myevent.set()

    async def action(self):
        while True:
            await self.myevent.wait()
            if self.__valor_encoder_p1 != self.r1.value():
                self.__posiciones_encoder_p1 = self.r1.value() - self.__valor_encoder_p1
                self.__valor_encoder_p1 = self.r1.value()
            if self.__valor_encoder_p2 != self.r2.value():
                self.__posiciones_encoder_p2 = self.r2.value() - self.__valor_encoder_p2
                self.__valor_encoder_p2 = self.r2.value()
            self.myevent.clear()

    def __str__(self):                                                                   # Sobreescribimos el valor str por defecto de la clase con un texto
         return f"Soy una bola de color {self.__color} y estoy en la posición {self.__posicion}"
    def __len__(self):                                                                   # Devolvemos un valor al llamar a len(objeto)
        return len(self.__casillas)

    def __cbBotonP1(self,p):
        if self.__temporizadorP1.fin():
            # logger.info(f"{ticks_ms()}: P1")
            self.__pulsador = 1
            self.__temporizadorP1.iniciar()

    def __cbBotonP2(self,p):
        if self.__temporizadorP2.fin():
            # logger.info(f"{ticks_ms()}: P2")
            self.__pulsador = 2
            self.__temporizadorP2.iniciar()

    @property
    def brillo(self):
        return self.__brillo_general
    @brillo.setter
    def brillo(self, valor:int):
        if valor >= 0 and valor <=100:
            self.__brillo_general = valor
            logger.info(f"El valor de brillo es: {self.__brillo_general}, rango[0-100]")
        else:
            logger.warning(f"El valor de brillo está fuera de rango: valor={valor}, rango[0-100]")

    @property
    def velocidad(self):
        return self.__velocidad
    @velocidad.setter
    def velocidad(self, valor:int):
        if valor >= 0 and valor <=1000:
            self.__velocidad = valor
            logger.info(f"El valor de velocidad es: {self.__velocidad}, rango[0-1000]")
        else:
            logger.warning(f"El valor de velocidad está fuera de rango: valor={valor}, rango[0-1000]")

    @property
    def dificultad(self):
        return self.__dificultad
    @dificultad.setter
    def dificultad(self, valor:int):
        if valor >= 1 and valor <=10:
            self.__dificultad = valor
            logger.info(f"El valor de dificultad es: {self.__dificultad}, rango[1-10]")
        else:
            logger.warning(f"El valor de dificultad está fuera de rango: valor={valor}, rango[1-10]")


    async def moverBolaDerecha(self, posiciones:int=1):
        __posiciones = posiciones
        __casilla_destino = self.__bola.posicion + __posiciones
        if __casilla_destino < self.__casillas_totales:  # Si estamos dentro del rango
            self.__bola.posicion += __posiciones
            self.__capa_bola = np.roll(self.__capa_bola, __posiciones * 3)
            return True
        else:                       # Nos hemos salido del rango, nos movemos solo hasta el extremo
            __posiciones = self.__casillas_totales - self.__bola.posicion - 1
            self.__bola.posicion = self.__casillas_totales-1
            if __posiciones != 0:
                self.__capa_bola = np.roll(self.__capa_bola, __posiciones * 3)
            
            await asyncio.wait_for(self.gol(jugador=1),10)
            return False

    async def moverBolaIzquierda(self, posiciones:int=1):
        __posiciones = posiciones
        __casilla_destino = self.__bola.posicion - __posiciones
        if __casilla_destino >= 0:
            self.__bola.posicion -= __posiciones
            self.__capa_bola = np.roll(self.__capa_bola, -__posiciones * 3)
            return True
        else:
            __posiciones = self.__bola.posicion
            self.__bola.posicion = 0
            if __posiciones != 0:
                self.__capa_bola = np.roll(self.__capa_bola, -__posiciones * 3)
            await asyncio.wait_for(self.gol(jugador=2),10)
            return False
    

    def mostrarPuntuacion(self):

        self.capa_salida = np.zeros((self.__casillas_totales,3), dtype=np.uint8)
        
        self.capa_salida[0:self.__puntos_max] = np.array(COLORES[self.color_fondo_puntos], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_fondo_puntos/100)
        self.capa_salida[self.__casillas_totales-self.__puntos_max:self.__casillas_totales] = np.array(COLORES[self.color_fondo_puntos], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_fondo_puntos/100)
        if self.__puntos_p1>0:
            self.capa_salida[0:self.__puntos_p1] = np.array(COLORES[self.color_puntos], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_puntos/100)
            # logger.info(f"self.capa_salida[{0}:{self.__puntos_p1}] = np.array({COLORES[self.color_puntos]}, dtype=np.uint8)*{(self.__brillo_general/100)*(self.__brillo_puntos/100)}")
        if self.__puntos_p2>0:
            self.capa_salida[self.__casillas_totales-self.__puntos_p2:self.__casillas_totales] = np.array(COLORES[self.color_puntos], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_puntos/100)
            # logger.info(f"self.capa_salida[{self.__casillas_totales-self.__puntos_p2}:{self.__casillas_totales}] = np.array({COLORES[self.color_puntos]}, dtype=np.uint8)*{(self.__brillo_general/100)*(self.__brillo_puntos/100)}")
        self.pintarLeds()
        
        



    def pintarArcoiris(self):
        self.__capa_arcoiris = np.zeros((self.__casillas_totales,3), dtype=np.uint8)
        self.__capa_porterias = np.zeros((self.__casillas_totales,3), dtype=np.uint8)
        casillas_por_color = int(self.__casillas_totales/len(self.__lista_colores_arcoiris))
        casillas_libres = self.__casillas_totales - casillas_por_color * len(self.__lista_colores_arcoiris)
        posicion = 0
        for num, color in enumerate(self.__lista_colores_arcoiris):
            self.__capa_arcoiris[posicion:posicion+casillas_por_color+casillas_libres] = np.array(COLORES[color], dtype=np.uint8)*(self.__brillo_general/100)
            posicion = posicion + casillas_por_color + casillas_libres
            if num == 0:
                casillas_libres = 0
        self.__capa_porterias = self.__capa_arcoiris
        if self.__posicion_arcoiris != 0:
            self.__capa_porterias = np.roll(self.__capa_arcoiris, 3*self.__posicion_arcoiris)
        self.__posicion_arcoiris += 1
        if self.__posicion_arcoiris >= self.__casillas_totales:
            self.__posicion_arcoiris = 0

    def actualizarPorterias(self):
        self.__capa_porterias = np.zeros((self.__casillas_totales,3), dtype=np.uint8)
        self.__capa_porterias[0:self.__porteria1.largo] = np.array(COLORES[self.__porteria1.color], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_porterias/100)
        self.__capa_porterias[self.__porteria2.posicion:self.__porteria2.posicion+self.__porteria2.largo] = np.array(COLORES[self.__porteria2.color], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_porterias/100)    

    async def gol(self, jugador:int, puntos:int=1):
        if jugador == 1:
            self.__direccion = 0
            self.__puntos_p1 +=1
            # logger.info(f"Jugador {jugador} puntos: {self.__puntos_p1}")
            self.reiniciarBola(jugador=2)

        if jugador == 2:
            self.__direccion = 0
            self.__puntos_p2 +=1
            # logger.info(f"Jugador {jugador} puntos: {self.__puntos_p1}")
            self.reiniciarBola(jugador=1)

        self.__velocidad = self.__tiempoVelocidad.reiniciarVelocidad()
        self.desactivarInterrupciones()
        self.__bola.ocultar()
        logger.info(f"Puntuacion: Jugador 1 tiene {self.__puntos_p1} puntos, jugador 2 tiene {self.__puntos_p2} puntos")
        self.mostrarPuntuacion()
        await asyncio.sleep(4)
        self.activarInterrupciones()
        self.__bola.mostrar()
        if self.__puntos_p1 >= self.__puntos_max or self.__puntos_p2 >= self.__puntos_max:
            self.__puntos_p1, self.__puntos_p2 = 0, 0
            self.__fase="brillo"
            self.__bola.ocultar()
            self.reiniciarBola(jugador=3)

    
    def reiniciarBola(self, jugador):
        if jugador == 1:
            self.__bola.posicion = self.__porteria1.posicion
        elif jugador == 2:
            self.__bola.posicion = self.__casillas_totales - 1
        elif jugador == 3:
            self.__bola.posicion = int(self.__casillas_totales/2)
        self.__capa_bola = np.zeros((self.__casillas_totales,3), dtype=np.uint8)
        self.__capa_bola[self.__bola.posicion] = np.array(COLORES[self.__bola.color], dtype=np.uint8)*(self.__brillo_general/100)*(self.__brillo_bola/100)
        logger.info(f"Bola reiniciada para {jugador} en la posicion: {self.__bola.posicion}")

    def procesarCapas(self):
        self.capa_salida = np.where(self.__capa_bola>self.__capa_porterias, self.__capa_bola, self.__capa_porterias) 
        self.pintarLeds()

    def pintarLeds(self):
        self.__tira_leds.buf = self.capa_salida.tobytes()   # Nos saltamos el método __set_item__ y escribimos los colores directamente en formato bytearray
        self.desactivarIrqEncoder()
        self.__tira_leds.write() 
        self.activarIrqEncoder()

    def desactivarIrqPulsadores(self):
        self.__pin1.irq(trigger=0, handler=None)
        self.__pin2.irq(trigger=0, handler=None)
    def activarIrqPulsadores(self):
        self.__pin1.irq(trigger=machine.Pin.IRQ_FALLING, handler=self.__cbBotonP1)
        self.__pin2.irq(trigger=machine.Pin.IRQ_FALLING, handler=self.__cbBotonP2)

    def desactivarIrqEncoder(self):
        self.r1._hal_disable_irq()
        self.r2._hal_disable_irq()
    def activarIrqEncoder(self):
        self.r1._hal_enable_irq()
        self.r2._hal_enable_irq()

    def desactivarInterrupciones(self):
        self.activarIrqPulsadores()
        self.desactivarIrqPulsadores()
    def activarInterrupciones(self):
        self.activarIrqPulsadores()
        self.activarIrqEncoder()

    async def seleccionarBrillo(self):
        try:
            logger.info(f"SELECCIONAR BRILLO")
            self.__bola.ocultar()
            self.__velocidad = self.__tiempoVelocidad.reiniciarVelocidad()
            while self.__fase == "brillo":
                self.__velocidad = self.__tiempoVelocidad.cambiosDeVelocidad()
                if self.__posiciones_encoder_p1 > 0 or self.__posiciones_encoder_p2 > 0:
                    self.__brillo_general -= 10
                    if self.__brillo_general < 10:
                        self.__brillo_general = 1
                    self.__posiciones_encoder_p1 = 0
                    self.__posiciones_encoder_p2 = 0
                elif self.__posiciones_encoder_p1 < 0 or self.__posiciones_encoder_p2 < 0:
                    self.__brillo_general += 10
                    if self.__brillo_general > 100:
                        self.__brillo_general = 100
                    self.__posiciones_encoder_p1 = 0
                    self.__posiciones_encoder_p2 = 0
                if self.__pulsador == 1:
                    self.__empezar_p1 = True
                    self.__pulsador = 0
                if self.__pulsador == 2:
                    self.__empezar_p2 = True
                    self.__pulsador = 0
                if self.__empezar_p1 and self.__empezar_p2 == True:
                    self.__empezar_p1 = False
                    self.__empezar_p2 = False
                    self.__fase = "puntos"
                self.pintarArcoiris()
                self.procesarCapas()
                await asyncio.sleep_ms(self.__velocidad)
        except asyncio.TimeoutError:
            logger.warning(f"Saliendo de seleccionarBrillo por timeout")
            self.__fase="puntos"
        except Exception as e:
            logger.error(f"Error en seleccionarBrillo: {e}")

    async def seleccionarPuntos(self):
        try:
            logger.info(f"SELECCIONAR PUNTOS")
            self.__velocidad = self.__tiempoVelocidad.reiniciarVelocidad()
            while self.__fase == "puntos":
                if self.__posiciones_encoder_p1 > 0 or self.__posiciones_encoder_p2 > 0:
                    self.__puntos_max -= 1
                    if self.__puntos_max < 1:
                        self.__puntos_max = 1
                    self.__posiciones_encoder_p1 = 0
                    self.__posiciones_encoder_p2 = 0
                elif self.__posiciones_encoder_p1 < 0 or self.__posiciones_encoder_p2 < 0:
                    self.__puntos_max += 1
                    if self.__puntos_max > 24:
                        self.__puntos_max = 24
                    self.__posiciones_encoder_p1 = 0
                    self.__posiciones_encoder_p2 = 0
                if self.__pulsador == 1:
                    self.__empezar_p1 = True
                    self.__pulsador = 0
                if self.__pulsador == 2:
                    self.__empezar_p2 = True
                    self.__pulsador = 0
                if self.__empezar_p1 and self.__empezar_p2 == True:
                    self.__empezar_p1 = False
                    self.__empezar_p2 = False
                    self.__fase = "color"
                self.mostrarPuntuacion()
                await asyncio.sleep_ms(self.__velocidad)
        except asyncio.TimeoutError:
            logger.warning(f"Saliendo de seleccionarPuntos por timeout")
            self.__fase="color"
        except Exception as e:
            logger.error(f"Error en seleccionarPuntos: {e}")

    async def seleccionarColor(self):
        try:
            logger.info(f"SELECCIONAR COLOR")
            self.__velocidad = self.__tiempoVelocidad.reiniciarVelocidad()
            largo = 10
            direccion = True
            largop1 = self.__porteria1.largo
            largop2 = self.__porteria2.largo
            while self.__fase == "color":
                if direccion:
                    largo += 1
                    if largo > int(self.__casillas_totales/2)-2: 
                        direccion = not direccion
                else:
                    largo -= 1
                    if largo < 2: 
                        direccion = not direccion
                self.__porteria1.largo = largo
                self.__porteria2.largo = largo
                self.__porteria2.posicion = self.__casillas_totales-largo


                if self.__posiciones_encoder_p1 > 0:
                    self.__lista_colores.append(self.__porteria1.color)
                    self.__porteria1.color = self.__lista_colores.pop(0)
                    self.__posiciones_encoder_p1 = 0
                elif self.__posiciones_encoder_p1 < 0:
                    self.__lista_colores.insert(0,self.__porteria1.color)
                    self.__porteria1.color = self.__lista_colores.pop()
                    self.__posiciones_encoder_p1 = 0

                if self.__posiciones_encoder_p2 > 0:
                    self.__lista_colores.append(self.__porteria2.color)
                    self.__porteria2.color = self.__lista_colores.pop(0)
                    self.__posiciones_encoder_p2 = 0
                elif self.__posiciones_encoder_p2 < 0:
                    self.__lista_colores.insert(0,self.__porteria2.color)
                    self.__porteria2.color = self.__lista_colores.pop()
                    self.__posiciones_encoder_p2 = 0

                if self.__pulsador == 1:
                    self.__empezar_p1 = True
                    self.__pulsador = 0
                if self.__pulsador == 2:
                    self.__empezar_p2 = True
                    self.__pulsador = 0
                if self.__empezar_p1 and self.__empezar_p2 == True:
                    self.__empezar_p1 = False
                    self.__empezar_p2 = False
                    self.__porteria1.largo = largop1
                    self.__porteria2.largo = largop2
                    self.__porteria2.posicion = self.__casillas_totales-self.__porteria2.largo
                    self.__fase = "largo"

                self.actualizarPorterias()
                self.procesarCapas()
                await asyncio.sleep_ms(self.__velocidad)

        except asyncio.TimeoutError:
            logger.warning(f"Saliendo de seleccionarColor por timeout")
            self.__fase="largo"
        except Exception as e:
            logger.error(f"Error en seleccionarColor: {e}")

    async def seleccionarLargo(self):
        try:
            logger.info(f"SELECCIONAR LARGO")
            self.__velocidad = self.__tiempoVelocidad.reiniciarVelocidad()

            while self.__fase == "largo":

                if self.__posiciones_encoder_p1 > 0:
                    self.__porteria1.largo += 1
                    if self.__porteria1.largo > int(self.__casillas_totales/2)-2:
                        self.__porteria1.largo = int(self.__casillas_totales/2)-2
                    self.__posiciones_encoder_p1 = 0
                elif self.__posiciones_encoder_p1 < 0:
                    self.__porteria1.largo -= 1
                    if self.__porteria1.largo < 1:
                        self.__porteria1.largo = 1
                    self.__posiciones_encoder_p1 = 0

                if self.__posiciones_encoder_p2 > 0:
                    self.__porteria2.largo += 1
                    if self.__porteria2.largo > int(self.__casillas_totales/2)-2:
                        self.__porteria2.largo = int(self.__casillas_totales/2)-2
                    self.__porteria2.posicion = self.__casillas_totales-self.__porteria2.largo
                    self.__posiciones_encoder_p2 = 0
                elif self.__posiciones_encoder_p2 < 0:
                    self.__porteria2.largo -= 1
                    if self.__porteria2.largo < 1:
                        self.__porteria2.largo = 1
                    self.__porteria2.posicion = self.__casillas_totales-self.__porteria2.largo
                    self.__posiciones_encoder_p2 = 0

                if self.__pulsador == 1:
                    self.__empezar_p1 = True
                    self.__pulsador = 0
                if self.__pulsador == 2:
                    self.__empezar_p2 = True
                    self.__pulsador = 0
                if self.__empezar_p1 and self.__empezar_p2 == True:
                    self.__empezar_p1 = False
                    self.__empezar_p2 = False
                    self.__fase = "inicio"

                self.actualizarPorterias()
                self.procesarCapas()
                await asyncio.sleep_ms(self.__velocidad)

        except asyncio.TimeoutError:
            logger.warning(f"Saliendo de seleccionarLargo por timeout")
            self.__fase="inicio"
        except Exception as e:
            logger.error(f"Error en seleccionarLargo: {e}")


    async def seleccionarDificultad(self):
        try:
            logger.info(f"SELECCIONAR DIFICULTAD")
            self.__velocidad = self.__tiempoVelocidad.reiniciarVelocidad()
            self.__bola.mostrar()
            self.reiniciarBola(jugador=3)
            while self.__fase == "dificultad":

                if self.__posiciones_encoder_p1 > 0:
                    self.__dificultad += 1
                    if self.__dificultad > 10:
                        self.__dificultad = 10
                    self.__posiciones_encoder_p1 = 0
                elif self.__posiciones_encoder_p1 < 0:
                    self.__dificultad -= 1
                    if self.__dificultad < 1:
                        self.__dificultad = 1
                    self.__posiciones_encoder_p1 = 0
                if self.__posiciones_encoder_p2 > 0:
                    self.__dificultad += 1
                    if self.__dificultad > 10:
                        self.__dificultad = 10
                    self.__posiciones_encoder_p2 = 0
                elif self.__posiciones_encoder_p2 < 0:
                    self.__dificultad -= 1
                    if self.__dificultad < 1:
                        self.__dificultad = 1
                    self.__posiciones_encoder_p2 = 0

                if self.__pulsador == 1:
                    self.__empezar_p1 = True
                    self.__pulsador = 0
                if self.__pulsador == 2:
                    self.__empezar_p2 = True
                    self.__pulsador = 0
                if self.__empezar_p1 and self.__empezar_p2 == True:
                    self.__empezar_p1 = False
                    self.__empezar_p2 = False
                    self.__fase = "inicio"

                self.actualizarPorterias()
                self.procesarCapas()
                await asyncio.sleep_ms(self.__velocidad)

        except asyncio.TimeoutError:
            logger.warning(f"Saliendo de seleccionarDificultad por timeout")
            self.__fase="inicio"
        except Exception as e:
            logger.error(f"Error en seleccionarDificultad: {e}")


    async def inicio(self):
        try:
            self.__velocidad = self.__tiempoVelocidad.reiniciarVelocidad()
            self.__bola.mostrar()
            self.reiniciarBola(jugador=3)
            logger.info(f"FASE INICIO")
            self.actualizarPorterias()
            self.procesarCapas()
            while self.__fase == "inicio":
                if self.__pulsador == 1:
                    self.__direccion = 2
                    self.__pulsador = 0
                    self.__fase = "juego"
                    logger.info(f"FASE JUEGO")
                    break
                if self.__pulsador == 2:
                    self.__direccion = 1
                    self.__pulsador = 0
                    self.__fase = "juego"
                await asyncio.sleep_ms(self.__velocidad)
        except asyncio.TimeoutError:
            logger.warning(f"Saliendo de inicio por timeout")
            self.__fase="brillo"
        except Exception as e:
            logger.error(f"Error en inicio: {e}")

    async def juegoPrincipal(self):
        try:   
            logger.info(f"JUEGO PRINCIPAL")

            self.__tiempoVelocidad.iniciarTiempo()
            while self.__fase == "juego":  
                
                if self.__pulsador == 1 and self.__bola.posicion <= self.__porteria1.posicion+self.__porteria1.largo-1:
                    # logger.info(f"self.__pulsador={self.__pulsador}, self.__bola.posicion={self.__bola.posicion}, self.__porteria1.posicion={self.__porteria1.posicion+self.__porteria1.largo}")
                    self.__direccion = 2
                    self.__pulsador = 0
                    self.__velocidad = self.__tiempoVelocidad.cambiosDeVelocidad()
                if self.__pulsador == 2 and self.__bola.posicion >= self.__porteria2.posicion:
                    self.__direccion = 1
                    self.__pulsador = 0
                    self.__velocidad = self.__tiempoVelocidad.cambiosDeVelocidad()
                    # logger.info(f"self.__pulsador={self.__pulsador}, self.__bola.posicion={self.__bola.posicion}, self.__porteria2.posicion={self.__porteria2.posicion}")
                self.__pulsador = 0

                if self.__direccion == 1:
                    if not await asyncio.wait_for(self.moverBolaIzquierda(),10):
                        self.__direccion = 0
                elif self.__direccion == 2:
                    if not await asyncio.wait_for(self.moverBolaDerecha(),10):  
                        self.__direccion = 0
                else:
                    pass
                self.procesarCapas()
                await asyncio.sleep_ms(self.__velocidad)
        except asyncio.TimeoutError:
            logger.warning(f"Saliendo de juegoPrincipal por timeout")
            self.__fase="brillo"
        except Exception as e:
            logger.error(f"Error en juegoPrincipal: {e}")
            
luces = MiTiraDeLeds()
mandos = MisMandos()
temporizador = Temporizador(tiempo_ms=500)