Hemos llegado a la parte final del proyecto Ping Pong. Vamos a incluir en el sketch el código necesario para que se muestre la información del juego en el display LCD.

Pintaremos los puntos del jugador, todos los mensajes que mostramos en el monitor serie en la parte 4 del proyecto e incluiremos alguno más para que visualmente quede mejor.

Sketch

A continuación puedes ver el sketch final que conseguiremos cuando completemos esta parte:

#include <LiquidCrystal_I2C.h>

/*********************
    DECLARACIONES
**********************/
//LEDS:
const int LEDS [] = {0,1,4,5,6,7,8,9,10,11};
int time_led =1000;
//BOTONES:
const byte BUTTON_1 = 3;
const byte BUTTON_2 = 2;
const byte BUTTON_START = 12;
bool btn1_pushed = false;
bool btn2_pushed = false;
//CONTROL DEL JUEGO:
byte turn = 1;
byte last_led = 0;
bool end_play = false;
int score [] = {0,0};
const int LED_PLY1 = 8;
const int LED_PLY2 = 1;
//LCD
LiquidCrystal_I2C lcd(0X3f,16,2);

/*********************
    CONFIGURACIÓN
**********************/
void setup() {
  Serial.begin(9600);
  //LEDS:
  for(int i=0; i<10; i++){
  	pinMode(LEDS[i], OUTPUT);
  }
  //BOTONES:
  pinMode(BUTTON_1, INPUT);
  pinMode(BUTTON_2, INPUT);
  pinMode(BUTTON_START, INPUT);
  attachInterrupt(digitalPinToInterrupt(BUTTON_1), interrBtn1, RISING);
  attachInterrupt(digitalPinToInterrupt(BUTTON_2), interrBtn2, RISING);
  //CONTROL DEL JUEGO
  randomSeed(analogRead(0));
  //LCD
  lcd.init();
  lcd.backlight();
}


/*********************
    FUNCIONES
**********************/
void clean (){
  //LEDS:
  for(int i=0; i<10; i++){
    digitalWrite(LEDS[i], LOW);
  }
  //BOTONES:
  btn1_pushed = false;
  btn2_pushed = false;
}

//LEDS
void led(int i){
  digitalWrite(LEDS[i], HIGH);
  delay(time_led);
  digitalWrite(LEDS[i], LOW);
}

void sequence(){
  int i = last_led;
  int result = 0;
  int led_fail = 0;
  clean ();
  if(turn == 1){
    for(i=last_led; i<10 && !btn1_pushed; i++){
      led(i);
      last_led = i;
    }
    led_fail = LED_PLY1+1;
    result = check(btn1_pushed, last_led, LED_PLY1, led_fail, turn);
    turn = 2;
  }else{
    for(i=last_led; i>=0 && !btn2_pushed; i--){
      led(i);
      last_led = i;
    }
    led_fail = LED_PLY2-1;
    result = check(btn2_pushed, last_led, LED_PLY2, led_fail, turn);
    turn = 1;
  }
  if(result == 1){
    showScore(score[0], score[1]);
  }else if(result == 2){ //led rojo
    digitalWrite(LEDS[led_fail], HIGH);
    end_play = true;
  }
}

//BOTONES
void interrBtn1 (){
  Serial.println("Btn1 pulsado");
  btn1_pushed = true;
}

void interrBtn2 (){
  Serial.println("Btn2 pulsado");
  btn2_pushed = true;
}

//CONTROL DEL JUEGO
void start(){
  clean ();
  Serial.println("Pulsa START para empezar");
  showMessages("PING PONG!", "Pulsa START");
  while(digitalRead(BUTTON_START)==LOW);
  showMessages("PING PONG!", "Preparados?");
  countdown(5, 12, 1);
  showMessages("PING PONG!", "Empezamos!");

  turn = random(2)+1;
  if(turn==1){
    last_led = 2;
  }else{
    last_led = 7;
  }

  end_play = false;

  time_led = 1000;

  score[0] = 0;
  score[1] = 0;
}

int check(bool btn_pushed, int last_led, int led_player, int led_fail, int player){
  if(time_led > 50){
   time_led = time_led - 50;
 }
 if(btn_pushed && last_led == led_player){
   Serial.print("Jugador ");
   Serial.print(player);
   Serial.print(" suma un punto! ");
   score[player-1]++;
   Serial.println(score[player-1]);
   return 1; //el jugador suma punto
 }else if(last_led == led_fail){
   Serial.println("Fin del juego");
   if(score[player-1]>0){
     score[player-1]--;
   }
   return 2; //se termina el juego
 }
 return 3; //el jugador ha presionado antes de tiempo
}

//LCD
void showScore(int score_ply1, int score_ply2){
 Serial.print("JUGADOR 1: ");
 Serial.print(score_ply1);
 Serial.print(" - JUGADOR 2: ");
 Serial.println(score_ply2);

 lcd.setCursor(0, 0);
 lcd.print("Jugador 1: ");
 lcd.setCursor(11, 0);
 lcd.print(score_ply1);
 lcd.setCursor(0, 1);
 lcd.print("Jugador 2: ");
 lcd.setCursor(11, 1);
 lcd.print(score_ply2);

}


void showFinalScore(int score_ply1, int score_ply2){
 showScore(score_ply1, score_ply2);
 if(score_ply1 > score_ply2){
   Serial.println("Gana: Jugador 1");
 }else if(score_ply1 < score_ply2){
   Serial.println("Gana: Jugador 2");
 }else{
   Serial.println("Empate");
 }
 lcd.clear();
 lcd.setCursor(0, 0);
 lcd.print("J1: ");
 lcd.setCursor(4, 0);
 lcd.print(score_ply1);
 lcd.setCursor(7, 0);
 lcd.print("J2: ");
 lcd.setCursor(11, 0);
 lcd.print(score_ply2);
 lcd.setCursor(0, 1);
 if(score_ply1 > score_ply2){
   lcd.print("Gana: Jugador 1");
 }else if(score_ply1 < score_ply2){
   lcd.print("Gana: Jugador 2");
 }else{
   lcd.print("Empate");
 }

}

void showMessages(char line1[], char line2[]){
 lcd.clear();
 lcd.setCursor(0, 0);
 lcd.print(line1);
 lcd.setCursor(0, 1);
 lcd.print(line2);
}

void countdown(int n, int col, int row){
 for(int i=n; i>=0; i--){
   lcd.setCursor(col, row);
   lcd.print(i);
   delay(1000);
 }
}

/*********************
    FUNCIÓN LOOP
**********************/
void loop() {
  start();
  delay(1000);
  showScore(score[0], score[1]);
  while(!end_play){
  	sequence();
  }
  showFinalScore(score[0], score[1]);
  delay(10000);
}

Librerías

Al inicio del sketch vamos a incluir la librería LiquidCristal I2C que nos ayudará a programar el display LCD de una forma más sencilla:

#include <LiquidCrystal_I2C.h>

Declaraciones

Sólo necesitamos un nuevo elemento en el bloque de declaraciones:

LiquidCrystal_I2C lcd(0X3f,16,2);

Es el objeto lcd que utilizaremos para manipular el display LCD.

Si no sabes cómo funciona este objeto es importante que revises el display LCD 1602 I2C.

Configuración

Vamos a incluir dos líneas nuevas justo al final de la función setup():

lcd.init();
lcd.backlight();

Con la primera instrucción iniciamos el LCD y el puerto I2C. Y con la segunda iluminamos el display LCD.

Funciones

Necesitaremos 2 funciones más: showMessages() y countdown().

Y añadir instrucciones en algunas de las funciones que ya teníamos: showScore(), showFinalScore() y start().

Empecemos por la función nueva.

Función showMessages()

La función showMessages() nos permitirá mostrar texto en ambas líneas del LCD siempre que lo necesitemos:

void showMessages(char line1[], char line2[]){
 lcd.clear();
 lcd.setCursor(0, 0);
 lcd.print(line1);
 lcd.setCursor(0, 1);
 lcd.print(line2);
}

Recibe dos parámetros de tipo array de char:

  • line1: Es el texto que se mostrará en la primera línea del display LCD.
  • line2: Es el texto que se mostrará en la segunda linea del display LCD.

Lo primero es limpiar el display:

lcd.clear();

Después ponemos el cursor al inicio de la primera fila (columna 0, fila 0):

lcd.setCursor(0, 0);

Y pintamos el texto de la variable line1 en la primera fila:

lcd.print(line1);

Ahora cambiamos el cursor al inicio de la segunda fila (columna 0, fila 1):

lcd.setCursor(0, 1);

Y mostramos el segundo texto en esa fila:

lcd.print(line2);

Función countdown()

Como su nombre indica, se trata de una cuenta atrás. En realidad no afecta al funcionamiento del juego para nada, pero visualmente quedará muy bien en el display al iniciarse cada partida.

void countdown(int n, int col, int row){
 for(int i=n; i>=0; i--){
   lcd.setCursor(col, row);
   lcd.print(i);
   delay(1000);
 }
}

Recibe 3 parámetros:

  • n: Número desde el que comenzará la cuenta atrás
  • col: Columna del LCD en el que se debe mostrar.
  • row: Fila del LCD en el que se mostrará.

Usamos un bucle for porque es la forma más sencilla de hacer una cuenta atrás. Empieza en n (int i=n) y termina en 0 (i>=0) y va restando una unidad tras cada iteración (i–):

for(int i=n; i>=0; i--)

Por cada repetición del bucle pintamos el valor de la variable i en el display.

Con la función setCursor() del objeto lcd indicamos la fila y la columna en la que se debe colocar el cursor en el display:

lcd.setCursor(col, row);

Después pintamos el valor de i con la función print():

lcd.print(i);

Y esperamos un segundo utilizando un delay() para que cada número de la cuenta atrás permanezca ese tiempo en el LCD antes de pasar al siguiente.

Muy visual, aunque totalmente inútil.

Función showScore()

Esta es una de las funciones que hicimos en la cuarta parte del proyecto:

void showScore(int score_ply1, int score_ply2){
 Serial.print("JUGADOR 1: ");
 Serial.print(score_ply1);
 Serial.print(" - JUGADOR 2: ");
 Serial.println(score_ply2);

 lcd.setCursor(0, 0);
 lcd.print("Jugador 1: ");
 lcd.setCursor(11, 0);
 lcd.print(score_ply1);
 lcd.setCursor(0, 1);
 lcd.print("Jugador 2: ");
 lcd.setCursor(11, 1);
 lcd.print(score_ply2);
}

Añadiremos el nuevo código justo a continuación del que ya teníamos si modificar nada. El código antiguo y el nuevo están separados por un espacio para que quede más claro.

Básicamente estamos mostrando en el LCD lo mismo que se mostraba en el monitor serie.

Los puntos del jugador 1 se muestran en la primera linea del LCD:

lcd.setCursor(0, 0);
lcd.print("Jugador 1: ");
lcd.setCursor(11, 0);
lcd.print(score_ply1);

Primero ponemos el cursos al principio de la primera línea (lcd.setCursor(0, 0)) y mostramos el texto «Jugador 1» con la función print().

Después movemos al cursor en la misma línea a la columna 11 (lcd.setCursor(11, 0)) y mostramos los puntos del jugador 1 (score_ply1) de nuevo con print().

Y los puntos del jugador 2 los mostramos en la segunda línea del display LCD:

lcd.setCursor(0, 1);
lcd.print("Jugador 2: ");
lcd.setCursor(11, 1);
lcd.print(score_ply2);

Ves que es exactamente igual que el código del jugador 1, pero cambiando el segundo parámetro de setCursor() por un 1 para que lo muestre en la segunda linea del display LCD.

Función showFinalScore()

Vamos a modificar esta función para que muestre el marcador final en el display LCD:

void showFinalScore(int score_ply1, int score_ply2){
 showScore(score_ply1, score_ply2);
 if(score_ply1 > score_ply2){
   Serial.println("Gana: Jugador 1");
 }else if(score_ply1 < score_ply2){
   Serial.println("Gana: Jugador 2");
 }else{
   Serial.println("Empate");
 }
 lcd.clear();
 lcd.setCursor(0, 0);
 lcd.print("J1: ");
 lcd.setCursor(4, 0);
 lcd.print(score_ply1);
 lcd.setCursor(7, 0);
 lcd.print("J2: ");
 lcd.setCursor(11, 0);
 lcd.print(score_ply2);
 lcd.setCursor(0, 1);
 if(score_ply1 > score_ply2){
   lcd.print("Gana: Jugador 1");
 }else if(score_ply1 < score_ply2){
   lcd.print("Gana: Jugador 2");
 }else{
   lcd.print("Empate");
 }

}

El código nuevo empieza el la línea 10, lo anterior ya lo teníamos y no lo vamos a cambiar.

Primero limpiamos el display LCD:

lcd.clear();

Después tenemos que mostrar en la línea 1 el marcador final:

lcd.setCursor(0, 0);
lcd.print("J1: ");
lcd.setCursor(4, 0);
lcd.print(score_ply1);
lcd.setCursor(7, 0);
lcd.print("J2: ");
lcd.setCursor(11, 0);
lcd.print(score_ply2);

Utilizamos setCursor() con 0 en el segundo parámetro para desplazarnos por la primera linea antes de escribir con print() el dato que nos interesa. El primer parámetro es la columna en la que vamos a escribir. El texto que se mostrará será algo así: «J1: 6 J2: 7».

Y en la segunda línea mostramos el jugador ganador:

lcd.setCursor(0, 1);
if(score_ply1 > score_ply2){
  lcd.print("Gana: Jugador 1");
}else if(score_ply1 < score_ply2){
  lcd.print("Gana: Jugador 2");
}else{
  lcd.print("Empate");
}

Fíjate que ahora la función setCursor() tiene un 1 en el segundo parámetro, porque nos queremos mover a la segunda línea.

La lógica es similar a la que ya teníamos antes para mostrar los mensajes en el monitor serie. Si los puntos del jugador 1 (score_ply1) son mayores que los del jugador 2 (score_ply2) entonces se mostrará «Gana: Jugador 1». Si es al revés se mostrará «Gana: Jugador 2» y si son iguales se mostrará «Empate».

La única diferencia es que ahora mostramos la información en el display usando el objeto lcd y la función print().

Función start()

En la función start() vamos a incluir el código necesario para mostrar información en el LCD. No hay que modificar el código que ya teníamos:

void start(){
  clean ();
  Serial.println("Pulsa START para empezar");
  showMessages("PING PONG!", "Pulsa START");
  while(digitalRead(BUTTON_START)==LOW);
  showMessages("PING PONG!", "Preparados?");
  countdown(5, 12, 1);
  showMessages("PING PONG!", "Empezamos!");

  turn = random(2)+1;
  if(turn==1){
    last_led = 2;
  }else{
    last_led = 7;
  }
  end_play = false;
  time_led = 1000;
  score[0] = 0;
  score[1] = 0;
}

Antes del bucle while mostramos el mensaje «PING PONG!» en la primera línea y «Pulsa START» en la segunda línea del display LCD utilizando la función showMessages() que ya comentamos anteriormente:

showMessages("PING PONG!", "Pulsa START");

Y después del while añadimos tres instrucciones más:

showMessages("PING PONG!", "Preparados?");
countdown(5, 12, 1);
showMessages("PING PONG!", "Empezamos!");

Con la primera instrucción cambiamos la segunda línea por «Preparados?».

Con la segunda instrucción mostramos una cuenta atrás que comienza en 5. Ya vimos antes cómo funciona la función countdown().

Y con la tercera mostramos «Empezamos!» cuando la cuenta atrás termine.

Función loop

El único cambio que debemos hacer en la función loop es añadir un delay() de 1s después de llamar a la función start() y otro de 10s al final de la función para que se muestre el resultado durante ese tiempo antes de que la función loop() se repita y empiece un nuevo juego:

void loop() {
  start();
  delay(1000);
  showScore(score[0], score[1]);
  while(!end_play){
  	sequence();
  }
  showFinalScore(score[0], score[1]);
  delay(10000);
}

¡Listo! Con esto hemos terminado el proyecto.

Espero que te haya gustado y hayas conseguido hacerlo.

¡Nos vemos en el próximo!