Dino-Run I2C: La Nostalgia Pixelada en tu Arduino Uno

En el vasto universo de la electrónica de pasatiempo y la microprogramación, pocos proyectos combinan la funcionalidad didáctica con el encanto nostálgico como la recreación de videojuegos clásicos. El juego del dinosaurio de Google Chrome (también conocido como T-Rex Game o Dino Runner), que aparece cuando no hay conexión a Internet, es un ícono de la simplicidad y la jugabilidad adictiva.

Este proyecto, el Dino-Run I2C, lleva la esencia de ese juego al mundo físico, utilizando componentes básicos de electrónica: una placa Arduino Uno como cerebro, una pantalla LCD 16×2 con interfaz I2C para la visualización, y un simple pulsador como controlador principal. La implementación en una pantalla LCD monocromática y limitada a 16×2 caracteres es un desafío de programación minimalista, que obliga a crear animaciones y gráficos utilizando únicamente los caracteres personalizados del LCD.



🔩Funcionamiento

El Poder de I2C en la LCD

Tradicionalmente, las pantallas LCD 16×2 requerían hasta 12 cables para funcionar en modo de 4 bits, consumiendo la mayoría de los pines digitales del Arduino. La interfaz I2C (Inter-Integrated Circuit) resuelve esto a través de un pequeño adaptador que contiene un chip (a menudo un PCF8574).

  • Principio: I2C es un protocolo de comunicación serial de dos hilos (SDA y SCL) que permite que múltiples dispositivos (‘esclavos’) se comuniquen con un ‘maestro’ (el Arduino) utilizando solo esos dos pines.
  • Ventaja: En este proyecto, el uso de I2C libera los pines Digitales 2 al 7 para otros usos (como controlar un relé para abrir una cerradura real), permitiendo una escalabilidad del proyecto.

PINES del I2C

Pin del I2CProtocoloNotas Importantes
VCCAlimentaciónUso de 5V.
GNDTierraConexión de referencia.
SDAI2CLínea de Datos Serial.
SCLI2CLínea de Reloj Serial.

🔨Componentes

ComponenteCantidadEspecificaciónFunción
Placa Arduino1Arduino Uno, Nano, etc.Control de componentes(cerebro)
LCD11602a 16×2Mostrar información
Interfaz1I2CReducir pines del LCD
Botón1PushSaltar dinosaurio
Resistencia110kΩPull-down para el botón
Cables de conexiónnUnir los componentes


🔌Conexiones

Conexión LCD 16×2 con I2C

LCD (I2C)PinArduino Pin
GNDGND
VCC5V
SDAA4
SCLA5

Conexión del botón(Configuración Pull-Down):

Arduino PinArduino Pin
5vPin 2 (con resistencia de 10kΩ a GND)

Para asegurar una lectura digital estable, los pulsadores se conectan en configuración Pull-Down: un extremo a 5V, el otro extremo al Pin Digital (2) de Arduino y, crucialmente, una resistencia de 10kΩ entre ese mismo pin digital y GND. Esto garantiza que el pin digital lea un estado LOW cuando el botón está abierto y HIGH (5V) solo cuando es presionado.


0️⃣Código

El corazón del proyecto reside en dos pilares de programación: el uso de la memoria CGRAM para definir los gráficos personalizados y el control del tiempo sin usar la función delay(), lo que garantiza una respuesta fluida al botón de salto.

El LCD 16×2 solo permite 8 caracteres personalizados. Usaremos 3 de ellos para el dinosaurio y el cactus. La memoria CGRAM (Character Generator RAM) permite definir cada carácter como una matriz de 5 x 8 píxeles.

Ejecuta el siguiente código, no olvidar instalar la librería “LiquidCrystal_I2C” por marcoschwartz o similar.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#include <Arduino.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

void saltar();
void iniciarJuego();
void mostrarPantallaInicial();
void moverObstaculo();
void terminarJuego();
bool chequearColision();
void actualizarPantalla();

// Definición de pines y configuración de la pantalla
const int PIN_BOTON = 2;
// ¡ATENCIÓN! Asegúrate de que la dirección 0x27 o 0x3F sea la correcta para tu módulo.
LiquidCrystal_I2C lcd(0x27, 16, 2); 

// Variables del juego
int dinosaurioY = 1;     // Fila del dinosaurio (1: suelo, 0: saltando)
int cactusX = 10;        // Columna del obstáculo
long tiempo_anterior = 0;
int velocidad_juego = 150; // Milisegundos entre movimientos (cuanto menor, más rápido)
unsigned long puntuacion = 0;
bool juego_activo = false;
int contadorSalto = 0; //Cronómetro para mantener al Dino en el aire
int tiempoDinoSaltando = 3;//Tiempo en el que el dino estara en el aire

// -----------------------------------------------------------------
// Definición de Caracteres Personalizados (CGRAM) - Matriz de 5x8
// B11111 significa que todos los 5 píxeles de esa línea están encendidos.
// -----------------------------------------------------------------

// Carácter 0: Dinosaurio en el suelo (Pie Abajo)
byte dino_pie_abajo[8] = { 
  B11110,
  B01011,
  B01100,
  B01111,
  B10110,
  B11111,
  B01110,
  B00111
};

// Carácter 1: Dinosaurio Saltando (Cuerpo en el aire)
byte dino_saltando[8] = {
  B00000,
  B11110,
  B01011,
  B01100,
  B01111,
  B10110,
  B11111,
  B01110
};

// Carácter 2: Cactus (Obstáculo)
byte cactus_grafico[8] = {
  B00000,
  B00000,
  B00000,
  B00110,
  B00110,
  B01110,
  B01110,
  B00100
};

void setup() {
  Serial.begin(9600);
  // Inicialización del LCD
  lcd.init();
  lcd.backlight();
  
  // Carga de caracteres personalizados en la CGRAM
  lcd.createChar(0, dino_pie_abajo);
  lcd.createChar(1, dino_saltando);
  lcd.createChar(2, cactus_grafico);
  
  // Configuración del botón
  pinMode(PIN_BOTON, INPUT); 
  
  mostrarPantallaInicial();
}

void loop() {
  if (juego_activo == true) {
    
    // 1. Detección de Salto: El control de entrada debe ser constante
    // Si el botón es HIGH y el dino está en el suelo (dinosaurioY == 1)
    if (digitalRead(PIN_BOTON) == HIGH && dinosaurioY == 1) {
      saltar();
    }
    
    // 2. Control de Velocidad y Movimiento (Non-blocking timing)
    // Solo mueve el cactus y actualiza la pantalla si ha pasado el tiempo necesario.
    if (millis() - tiempo_anterior >= velocidad_juego) {
      tiempo_anterior = millis();
      
      moverObstaculo();
      
      // Lógica de Caída: Regresa a la fila de suelo (1) después de un ciclo de salto (0)
      if (dinosaurioY == 0) {        
        actualizarPantalla();
        contadorSalto++;
        if(contadorSalto >= tiempoDinoSaltando)
          dinosaurioY = 1;
      }
      
      // 3. Chequeo de Colisión
      if (chequearColision()) {
        terminarJuego();
        return; 
      }
      
      // 4. Actualización de Puntuación y Pantalla
      puntuacion++;
      actualizarPantalla();
      
      // 5. Aumento Progresivo de Velocidad para aumentar la dificultad
      if (puntuacion % 25 == 0 && velocidad_juego > 70) {
        velocidad_juego -= 5; 
      }
    }
    
  } else {
    // Si el juego está inactivo (menú o Game Over), espera el botón
    if (digitalRead(PIN_BOTON) == HIGH) {
      iniciarJuego();
    }
  }
}

// -----------------------------------------------------------------
// Lógica de Funciones del Juego
// -----------------------------------------------------------------

void saltar() { 
  contadorSalto = 0; 
  dinosaurioY = 0; // Pone al dino en la fila superior (saltando)
}

void moverObstaculo() {
  // Limpiar el rastro anterior
  if (cactusX < 16) {
    lcd.setCursor(cactusX, 1);
    lcd.print(" "); 
  }
  
  // Mover una posición a la izquierda
  cactusX--;
  
  // Reiniciar el obstáculo si sale de la pantalla
  if (cactusX < 0) {
    cactusX = 15;
  }
}

bool chequearColision() {
  // Colisión: Cactus en Columna 1 (donde está el dino) Y el dino en el suelo (fila 1)
  if (cactusX == 1 && dinosaurioY == 1) {
    return true; 
  }
  return false;
}

void actualizarPantalla() {
  // Limpia completamente el display para el nuevo frame
  lcd.clear(); 
  // Dibuja el dinosaurio
  int grafico_dino = (dinosaurioY == 0) ? 1 : 0; // Carácter 1 si salta, 0 si está en el suelo
  lcd.setCursor(1, dinosaurioY); // El dino se queda fijo en la Columna 1
  lcd.write(byte(grafico_dino));
  
  // Dibuja el obstáculo
  lcd.setCursor(cactusX, 1);
  lcd.write(byte(2)); // Carácter 2 es el cactus
  
  // Muestra la Puntuación
  if(puntuacion < 10)
    lcd.setCursor(11, 0); 
  else if (puntuacion >= 10 && puntuacion < 99)
    lcd.setCursor(10, 0); 
  else if (puntuacion >= 100 && puntuacion < 999)
    lcd.setCursor(9, 0); 
  else
    lcd.setCursor(8, 0); 
  lcd.print("PTS:");
  lcd.print(puntuacion);
}

void iniciarJuego() {
  puntuacion = 0;
  cactusX = 15;
  dinosaurioY = 1;
  velocidad_juego = 150;
  juego_activo = true;  
  contadorSalto = 0;
  tiempoDinoSaltando = 3;
  lcd.clear();
  actualizarPantalla();
}

void terminarJuego() {
  juego_activo = false;
  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("GAME OVER");
  lcd.setCursor(2, 1);
  lcd.print("Score: ");
  lcd.print(puntuacion);
  delay(3000); // Pequeño delay para que el usuario pueda ver el Game Over
}

void mostrarPantallaInicial() {
  lcd.setCursor(4, 0);
  lcd.print("Dino-Run");
  lcd.setCursor(0, 1);
  lcd.print("Pulsa para Jugar");
  delay(1000);
}


🖌️Diseños


🎬Videos


📑Conclusión

El proyecto Dino-Run I2C es mucho más que una simple recreación; representa un ejercicio magistral de ingeniería minimalista y programación eficiente. Al integrar una placa Arduino Uno, una pantalla LCD 16×2 I2C y un solo pulsador, hemos demostrado que las experiencias de juego más adictivas pueden nacer de recursos limitados.

El éxito de este proyecto radica en tres pilares técnicos fundamentales:

  1. Eficiencia del I2C: La adopción del bus I2C simplificó drásticamente el cableado, liberando recursos del Arduino y haciendo el montaje más limpio y accesible.
  2. Ingenio Gráfico (CGRAM): Superamos la limitación de los caracteres predefinidos del LCD al utilizar la memoria CGRAM. Esto nos permitió diseñar gráficos personalizados del dinosaurio y el cactus, transformando simples bloques de píxeles en elementos visuales reconocibles.
  3. Lógica Reactiva: La implementación de la técnica de tiempo no bloqueante con millis() fue crucial. Al evitar la función delay(), garantizamos que la lectura del botón de salto fuera instantánea y precisa, asegurando una jugabilidad fluida que honra la velocidad y la frustración placentera del juego original de Chrome.

En última instancia, el Dino-Run I2C sirve como una poderosa herramienta de aprendizaje. Demuestra cómo la comprensión profunda de los límites del hardware puede inspirar soluciones de software creativas, transformando un puñado de componentes básicos en una experiencia de usuario completa y satisfactoria. Este proyecto es una prueba viviente de que, en el mundo de la electrónica, la limitación es a menudo la madre de la innovación.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Carrito de compra