.. note::

    ¡Hola! Bienvenido a la Comunidad de Entusiastas de SunFounder para Raspberry Pi, Arduino y ESP32 en Facebook. Sumérgete junto a otros entusiastas en el fascinante mundo de Raspberry Pi, Arduino y ESP32.

    **¿Por qué unirte?**

    - **Soporte experto**: Resuelve problemas posventa y desafíos técnicos con ayuda de nuestra comunidad y equipo.
    - **Aprende y comparte**: Intercambia consejos y tutoriales para mejorar tus habilidades.
    - **Preestrenos exclusivos**: Accede anticipadamente a anuncios y adelantos de nuevos productos.
    - **Descuentos especiales**: Aprovecha descuentos exclusivos en nuestros productos más recientes.
    - **Promociones y sorteos festivos**: Participa en sorteos y promociones especiales en días festivos.

    👉 ¿Listo para explorar y crear con nosotros? Haz clic en [|link_sf_facebook|] y únete hoy mismo.

.. _3.1.14_py_pi5:

3.1.14 JUEGO – Not Not
========================

Introducción
---------------------

En esta lección, crearemos un divertido dispositivo de juego llamado "Not Not".

Durante el juego, la matriz de puntos mostrará una flecha en una dirección al azar. Debes presionar el botón en la dirección opuesta a la flecha dentro de un tiempo limitado. Si el tiempo se acaba o presionas el botón en la misma dirección de la flecha, pierdes.

Este juego realmente puede ayudarte a practicar el pensamiento inverso. ¿Listo para intentarlo?

Componentes necesarios
------------------------------

En este proyecto, necesitaremos los siguientes componentes.

.. image:: ../python_pi5/img/3.1.14_game_not_not_list.png
    :width: 800
    :align: center

Diagrama de Esquema
------------------------

============ ======== ======== ===
T-Board Name physical wiringPi BCM
GPIO17       Pin 11   0        17
GPIO18       Pin 12   1        18
GPIO27       Pin 13   2        27
GPIO20       Pin 38   28       20
GPIO26       Pin 37   25       26
============ ======== ======== ===

.. image:: ../python_pi5/img/3.1.14_game_not_not_schematic.png
   :align: center

Procedimiento Experimental
-------------------------------

**Paso 1:** Monta el circuito.

.. image:: ../python_pi5/img/3.1.14_game_not_not_circuit.png

**Paso 2:** Abre el archivo de código.

.. raw:: html

   <run></run>

.. code-block::

    cd ~/davinci-kit-for-raspberry-pi/python-pi5


**Paso 3:** Ejecuta el programa.

.. raw:: html

   <run></run>

.. code-block::

    sudo python3 3.1.14_MotionControl.py

Al iniciar el programa, aparece una flecha en la matriz de puntos apuntando 
hacia la derecha o la izquierda. Lo que debes hacer es presionar el botón en 
la dirección opuesta a la flecha dentro de un tiempo límite. Entonces, aparecerá 
un \"**√**\" en la matriz de puntos. Si se acaba el tiempo o presionas el botón 
en la misma dirección que la flecha, pierdes y la matriz de puntos muestra una 
\"x\". También puedes agregar 2 botones nuevos o reemplazarlos por los controles 
de un joystick para tener 4 direcciones: arriba, abajo, izquierda y derecha, 
aumentando así la dificultad del juego.

.. warning::

    Si aparece el mensaje de error ``RuntimeError: Cannot determine SOC peripheral base address``, consulta la sección :ref:`faq_soc` 

**Código**

.. note::

    Puedes **Modificar/Restablecer/Copiar/Ejecutar/Detener** el código a continuación. Sin embargo, antes de hacerlo, dirígete a la ruta de código fuente, como ``davinci-kit-for-raspberry-pi/python-pi5``. Tras realizar cambios, puedes ejecutarlo directamente para ver el efecto.

.. raw:: html

    <run></run>

.. code-block:: python

   #!/usr/bin/env python3
   from gpiozero import OutputDevice, Button
   import time
   import threading
   import random

   # Pines GPIO para el registro de desplazamiento 74HC595
   SDI = OutputDevice(17)   # Entrada de Datos Serial
   RCLK = OutputDevice(18)  # Reloj de Registro
   SRCLK = OutputDevice(27) # Reloj de Registro de Desplazamiento

   # Pines GPIO para los botones
   AButtonPin = Button(20)  # Botón A
   BButtonPin = Button(26)  # Botón B

   # Inicialización de variables del juego
   timerPlay = 0
   timerCheck = 0
   waypoint = "NULL"
   stage = "NULL"

   # Flechas para la matriz LED
   arrow = {
       "right": [0xFF, 0xEF, 0xDF, 0x81, 0xDF, 0xEF, 0xFF, 0xFF],
       "left": [0xFF, 0xF7, 0xFB, 0x81, 0xFB, 0xF7, 0xFF, 0xFF]
   }

   # Iconos de respuesta para respuestas correctas/incorrectas
   check = {
       "wrong": [0xFF, 0xBB, 0xD7, 0xEF, 0xD7, 0xBB, 0xFF, 0xFF],
       "right": [0xFF, 0xFF, 0xF7, 0xEB, 0xDF, 0xBF, 0xFF, 0xFF]
   }

   def hc595_shift(dat):
       """ Shift data to the 74HC595 shift register. """
       for i in range(8):
           SDI.value = 0x80 & (dat << i)
           SRCLK.on()
           SRCLK.off()

   def display(glyphCode):
       """ Display a glyph on the LED matrix. """
       for i in range(0, 8):
           hc595_shift(glyphCode[i])
           hc595_shift(0x80 >> i)
           RCLK.on()
           RCLK.off()

   def creatGlyph():
       """ Create a new glyph for the game and start the play timer. """
       global waypoint, stage, timerPlay
       waypoint = random.choice(list(arrow.keys()))
       stage = "PLAY"
       timerPlay = threading.Timer(2.0, timeOut)
       timerPlay.start()

   def checkPoint(inputKey):
       """ Check player's input and update game state. """
       global waypoint, stage, timerCheck
       if inputKey == "empty" or inputKey == waypoint:
           waypoint = "wrong"
       else:
           waypoint = "right"
       timerPlay.cancel()
       stage = "CHECK"
       timerCheck = threading.Timer(1.0, creatGlyph)
       timerCheck.start()

   def timeOut():
       """ Handle game timeout scenario. """
       checkPoint("empty")

   def getKey():
       """ Detect button press and trigger checkpoint. """
       if AButtonPin.is_pressed and not BButtonPin.is_pressed:
           checkPoint("right")
       elif not AButtonPin.is_pressed and BButtonPin.is_pressed:
           checkPoint("left")

   def main():
       """ Main game loop. """
       creatGlyph()
       while True:
           if stage == "PLAY":
               display(arrow[waypoint])
               getKey()
           elif stage == "CHECK":
               display(check[waypoint])

   def destroy():
       """ Clean up resources on program exit. """
       global timerPlay, timerCheck
       timerPlay.cancel()  # Cancela el temporizador de juego
       timerCheck.cancel()  # Cancela el temporizador de verificación

   # Ejecuta el juego, maneja la interrupción de teclado para salir limpiamente
   try:
       main()
   except KeyboardInterrupt:
       destroy()




**Explicación del Código**

Basado en **1.1.6 Matriz de Puntos LED**, en esta lección se añaden **2** botones para crear un dispositivo de juego divertido. Si aún no estás familiarizado con la matriz de puntos, por favor consulta :ref:`1.1.6_py_pi5`.

#. El código comienza importando las librerías necesarias. Se utiliza ``gpiozero`` para interactuar con pines GPIO como botones y dispositivos de salida. ``time`` permite agregar retrasos, ``threading`` habilita la ejecución de múltiples tareas simultáneamente, y ``random`` es útil para introducir aleatoriedad en el proyecto.

   .. code-block:: python

       #!/usr/bin/env python3
       from gpiozero import OutputDevice, Button
       import time
       import threading
       import random

#. Se inicializan los pines GPIO para el registro de desplazamiento (``SDI``, ``RCLK``, ``SRCLK``) y los botones (``AButtonPin``, ``BButtonPin``). El registro de desplazamiento se utiliza para controlar múltiples LEDs con menos pines GPIO, lo cual es crucial para la pantalla de la matriz LED.

   .. code-block:: python

       # Pines GPIO para el registro de desplazamiento 74HC595
       SDI = OutputDevice(17)   # Entrada de Datos Serial
       RCLK = OutputDevice(18)  # Reloj de Registro
       SRCLK = OutputDevice(27) # Reloj de Registro de Desplazamiento

       # Pines GPIO para los botones
       AButtonPin = Button(20)  # Botón A
       BButtonPin = Button(26)  # Botón B

#. Se inicializan variables utilizadas en la lógica del juego, como temporizadores e indicadores de estado del juego.

   .. code-block:: python

       # Inicialización de variables del juego
       timerPlay = 0
       timerCheck = 0
       waypoint = "NULL"
       stage = "NULL"

#. Se definen patrones binarios para mostrar flechas y respuestas (correcto/incorrecto) en la matriz LED. Cada elemento del arreglo representa una fila de la matriz LED, donde ``1`` y ``0`` indican si el LED está encendido o apagado, respectivamente.

   .. code-block:: python

       # Flechas para la pantalla de la matriz LED
       arrow = {
           "right": [0xFF, 0xEF, 0xDF, 0x81, 0xDF, 0xEF, 0xFF, 0xFF],
           "left": [0xFF, 0xF7, 0xFB, 0x81, 0xFB, 0xF7, 0xFF, 0xFF]
       }

       # Íconos de respuesta para respuestas correctas/incorrectas
       check = {
           "wrong": [0xFF, 0xBB, 0xD7, 0xEF, 0xD7, 0xBB, 0xFF, 0xFF],
           "right": [0xFF, 0xFF, 0xF7, 0xEB, 0xDF, 0xBF, 0xFF, 0xFF]
       }

#. Esta función desplaza un byte de datos hacia el registro de desplazamiento 74HC595. Itera sobre cada bit del byte ``dat``, configurando el pin ``SDI`` alto o bajo según corresponda, y alterna el pin ``SRCLK`` para desplazar el bit en el registro.

   .. code-block:: python

       def hc595_shift(dat):
           """ Shift data to the 74HC595 shift register. """
           for i in range(8):
               SDI.value = 0x80 & (dat << i)
               SRCLK.on()
               SRCLK.off()

#. Esta función muestra un glifo en la matriz LED. Envía cada fila del glifo (representado por ``glyphCode``) y la dirección de la fila al registro de desplazamiento usando ``hc595_shift``, y luego alterna el pin ``RCLK`` para actualizar la pantalla.

   .. code-block:: python

       def display(glyphCode):
           """ Display a glyph on the LED matrix. """
           for i in range(0, 8):
               hc595_shift(glyphCode[i])
               hc595_shift(0x80 >> i)
               RCLK.on()
               RCLK.off()

#. Esta función selecciona un glifo al azar del diccionario ``arrow``, inicia el temporizador de juego y establece el estado del juego en "PLAY". Se utiliza ``threading.Timer`` para el control del tiempo en el juego.

   .. code-block:: python

       def creatGlyph():
           """ Create a new glyph for the game and start the play timer. """
           global waypoint, stage, timerPlay
           waypoint = random.choice(list(arrow.keys()))
           stage = "PLAY"
           timerPlay = threading.Timer(2.0, timeOut)
           timerPlay.start()

#. Esta función verifica la entrada del jugador en comparación con el glifo actual. Si la entrada es correcta, establece el waypoint en "right", de lo contrario en "wrong". Luego cancela el temporizador de juego actual e inicia un nuevo temporizador para el próximo glifo.

   .. code-block:: python

       def checkPoint(inputKey):
           """ Check player's input and update game state. """
           global waypoint, stage, timerCheck
           if inputKey == "empty" or inputKey == waypoint:
               waypoint = "wrong"
           else:
               waypoint = "right"
           timerPlay.cancel()
           stage = "CHECK"
           timerCheck = threading.Timer(1.0, creatGlyph)
           timerCheck.start()

#. Esta función se llama cuando se agota el tiempo del juego. Invoca ``checkPoint`` con "empty" para indicar que no se presionó ningún botón a tiempo.

   .. code-block:: python

       def timeOut():
           """ Handle game timeout scenario. """
           checkPoint("empty")

#. Esta función verifica el estado de los botones. Si ``AButtonPin`` está presionado (y ``BButtonPin`` no), llama a ``checkPoint`` con "right". Si ``BButtonPin`` está presionado (y ``AButtonPin`` no), llama a ``checkPoint`` con "left".

   .. code-block:: python

       def getKey():
           """ Detect button press and trigger checkpoint. """
           if AButtonPin.is_pressed and not BButtonPin.is_pressed:
               checkPoint("right")
           elif not AButtonPin.is_pressed and BButtonPin.is_pressed:
               checkPoint("left")

#. La función ``main`` controla el flujo del juego. Comienza creando un glifo, luego verifica continuamente el estado del juego. Si está en el estado "PLAY", muestra el glifo actual y verifica las pulsaciones de botón. En el estado "CHECK", muestra la retroalimentación según la acción del jugador.

   .. code-block:: python

       def main():
           """ Main game loop. """
           creatGlyph()
           while True:
               if stage == "PLAY":
                   display(arrow[waypoint])
                   getKey()
               elif stage == "CHECK":
                   display(check[waypoint])

#. Esta función cancela cualquier temporizador activo al salir del programa, asegurando un apagado limpio.

   .. code-block:: python

       def destroy():
           """ Clean up resources on program exit. """
           global timerPlay, timerCheck
           timerPlay.cancel()  # Cancela el temporizador de juego
           timerCheck.cancel()  # Cancela el temporizador de verificación

#. El juego se ejecuta en un bloque ``try``. Si ocurre una ``KeyboardInterrupt`` (como presionar Ctrl+C), captura la excepción y llama a ``destroy`` para limpiar antes de salir.

   .. code-block:: python

       # Ejecuta el juego y maneja KeyboardInterrupt para salir limpiamente
       try:
           main()
       except KeyboardInterrupt:
           destroy()


