El juego del Ping Pong en Arduino

Sin lugar a dudas, una de las cosas que no podré olvidar en años, será la primera vez que interactué con el sistema de Arduino. Arduino es una plataforma de prototipado (sic.) de proyectos electrónicos, de código abierto, y de fácil acceso.

Photo by Harrison Broadbent on Unsplash
Photo by Harrison Broadbent on Unsplash

Mi historia con Arduino

La primera vez que escuché sobre la plataforma, me encontraba en bachillerato, y se presentaba como una opción para trabajar de manera sencilla con circuitos electrónicos. Sin embargo, en ningún momento nos vimos obligados a su uso o aprendizaje, así que fue un momento transitivo.

Un año después de ese primer encuentro con el concepto, ya en la universidad, conocí a un apasionado de de la electrónica, que me recomendaba e invitaba a utilizar la plataforma de Arduino, y así lo hice.

Ese mismo día, empecé a leer al respecto, a documentarme, a averiguar acerca de lo necesario para empezar en este apasionante universo de la electrónica, y desde luego, me procuré buscar proveedores con buenos costes en sus productos.

Un par de semanas después, tenía en casa mi primer placa Arduino Uno R3, algunos leds, un buzzer, un par de sensores, y unos cuantos jumpers. Uno, o quizá dos meses después de mis primeras incursiones, decidí que era hora de presentar información desde la placa, así que me procuré de unas 5 pantallas OLED y un par de potenciómetros.

Y aquí es donde inicia la historia de este proyecto.

Era un martes en la tarde…

Recuerdo que aquél día, me quedé un par de horas de más en la universidad, con aquél apasionado de la electrónica, escuchando y aprendiendo más sobre la placa que tenía entre mis manos, cuando me mostró un video, un video con un pequeño videojuego controlado por una placa Arduino y una pantalla OLED, justo lo que yo tenía para trabajar.

Llegué a casa, dispuesto a programar un videojuego en Arduino. Y me decidí por el clásico Ping Pong.

Por aquél entonces, yo no sabía lo que eran las librerías de Arduino que permitían la interactividad de la placa con la pantalla, así que sí, a nivel de bytes y código hexadecimal, resultó la primer versión, poco eficiente de este proyecto. Y no lo niego, resulta interesante entender el paso a paso de tu código. Recuerdo ir a dormir con un avance del 50%, ¿El problema? La lógica interna ya funcionaba, pero había que codificar cada posibilidad del tablero a nivel de bits, tanto para los jugadores como para la pelota.

Las primeras clases del siguiente día, las llegué a sentir excesivamente aburridas, yo sólo pensaba en seguir programando el pequeño juego, así que, a lápiz y papel, empecé a crear los códigos hexadecimales.

Horas después, ya con un tiempo libre entre clases, pasé todos mis apuntes a la placa Arduino, y ¡voilá! El código funcionó perfectamente. Me sentí totalmente orgulloso.

Un año después

El proyecto antes mencionado, quedó abandonado entre muchos otros proyectos, hasta que me decidí a volver a crearlo, y fue mi compromiso personal lograr mejorarlo, y así lo hice.

Teniendo ya conocimientos sobre las librerías que permiten la conexión entre la placa Arduino y la pantalla OLED, volví a generar el código general, de manera que fuera más legible y entendible para el programador.

Y decidí liberarlo al mundo en su propio repositorio de Github, bajo licencia GNU para Software Libre.

El funcionamiento del código

Tal como se mencionó anteriormente, el código hace uso de cuatro librerías principales: SPI, que permite hacer uso de la comunicación por SPI; Wire, que permite la comunicación I2C, ambas necesarias para establecer la comunicación con la pantalla OLED; y dos librerías de Adafruit, que nos permiten tomar el control del contenido en la pantalla.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Se indica el tipo de pantalla a utilizar, y se inicializan los valores por defecto del juego.

//Declaración del display utilizado.
Adafruit_SSD1306 display(128, 64, &Wire, -1);

// Almacenan posiciones de los jugadores.
int a0, a1;

// Almacenan las posiciones de la pelota en el momento inicial.
int posBolIniX = 63, posBolIniY = 31;

// Almacenan las posiciones actuales (en X y en Y) de la pelota.
int posBolX, posBolY;

// Almacenan la dirección que debe tomar la pelota:
// dirX ->  1 --- Hacia la izquierda
// dirX -> -1 --- Hacia la derecha
// dirY ->  1 --- Hacia la abajo
// dirY -> -1 --- Hacia la arriba
int dirX = 1, dirY = 1;

// Almacenan el puntaje actual de cada jugador (Izquierda y derecha).
int markIzq = 0, markDer = 0;

Requeriremos un método que se encargue de reiniciar el tablero, en lo referente a la posición de la pelota, y además, indicaremos que invierta la dirección en X que llevaba al momento.

void reinicio(){
  posBolX = posBolIniX;
  posBolY = posBolIniY;
  dirX = - dirX;
  delay(1000);
}

El método setup inicializará al Serial, al display, y limpiará la imagen actual del display. Asimismo, llamará al método de reinicio.

Serial.begin(9600);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
reinicio();

El método loop limpiará la pantalla actual, y obtendrá los valores de los potenciómetros; esos valores los mapeará para ir desde [0-1010] hasta [0-56].

Entendamos el por qué de estos valores que parecen arbitrarios. En primer lugar, se valoran los números entre 0 y 1024 para la entrada de los potenciómetros, porque, en las condiciones ideales, de 5V que están alimentados, deberían permitir el ingreso de hasta 1024 al pin que se lee, pero, dado que, ante un fallo, el número podría ser no perfecto, se prefiere arriesgar una parte. En segundo lugar, se valoran los números entre 0 y 56, porque nuestros jugadores ocupan 8px de la pantalla, en una pantalla con resolución máxima en vertical de 64px; por tanto, se requieren tomar 8 bits de maniobra, es decir, 64 – 8 = 56.

display.clearDisplay();
a0 = map(analogRead(A0),0,1010,0,56);
a1 = map(analogRead(A1),0,1010,0,56);

Seguidamente, el juego dibuja una línea sobre el eje X en 0, y sobre el eje Y desde el mapeo a0+1, hasta a0+7; repite el proceso con la línea sobre X en 127, y las operaciones anteriores con el pin a1.

Después dibuja un píxel en las posiciones en que se debe encontrar la pelota, y asigna un tamaño de 1 para el texto siguiente.

  // Gráfica del juego
  display.drawLine(0, a0+1, 0, a0+7, SSD1306_WHITE);
  display.drawLine(127, a1+1, 127, a1+7, SSD1306_WHITE);
  display.drawPixel(posBolX,posBolY, SSD1306_WHITE);
  display.setTextSize(1);

Después, el programa dibujará en X = 51, Y = 0, el puntaje del marcador izquierdo, seguido del puntaje del marcador derecho. Finalmente, dibujará las iniciales del autor en X = 55, Y = 56.

  // Gráfica del marcador
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(51, 0);
  display.cp437(true);
  display.print(markIzq);
  display.print(" - ");
  display.print(markDer);

  display.setCursor(55, 56);
  display.print("CECG");

A continuación, se pintan los nuevos valores, y se incrementa la posición de la pelota en 1 (o -1, según la dirección actual), tanto para el eje X como para el Y.

  // Pintamos el display
  display.display();

  //Movimiento ball
  posBolX += dirX;
  posBolY += dirY;

Y como cierre, el método loop garantiza que, al encontrarse una colisión con la parte inferior o superior de la pantalla, invierta su dirección; garantiza que, al encontrarse con una colisión en la parte izquierda o derecha, si se encuentra un jugador, invierte su dirección, o bien, si no se encuentra el juegador, reinicia el tablero y suma 1 al marcador del jugador opuesto.

  //Movimiento arriba y abajo
  if(posBolY <= 0 || posBolY >=63)
    dirY = -dirY;

  //Movimiento izquierda y derecha (Marcador izquierdo)
  if(posBolX <= 1 && posBolY > a0 && posBolY < a0+8)
    dirX = -dirX;
  else if (posBolX <= 1){
    reinicio();
    if(markIzq<9)
      markIzq+=1;
  }

  //Movimiento izquierda y derecha (Marcador derecho)
  if(posBolX >= 126 && posBolY > a1 && posBolY < a1+8)
    dirX = -dirX;
  else if (posBolX >= 126){
    reinicio();
    if(markDer<9)
      markDer+=1;
  }

Como recordatorio, vale la pena decir que el código se encuentra disponible en su repositorio de Github, bajo licencia GNU para software libre.