CONTENIDO ►
El juego Tetris en Arduino Uno con pantalla OLED i2c 128×64 y botones es un proyecto sencillo para principiantes. El esquema de montaje del juego Tetris en Arduino Uno con pantalla es lo más sencillo posible, por lo que cualquier principiante podrá repetirlo. En esta página hemos colocado el esquema de conexión de la pantalla y los botones al Arduino, así como el programa para crear un juego de tetris en display SSD1306 OLED i2c.
Para esta actividad necesitaremos:
- Arduino Micro / Arduino Mega
- pantalla oled 128×64 i2c
- pulsadores y zumbador
- placa de pruebas
- cables de conexion
- librairies Adafruit_SSD1306.h, Adafruit_GFX.h
Tetris es un juego de ordenador desarrollado por un programador ruso en 1984. El objetivo del juego es colocar en el campo de juego bloques de diferentes formas que van cayendo, de manera que después de cada movimiento se obtenga una línea uniforme sin huecos. Una vez que se llena toda la línea, ésta desaparece y el jugador obtiene puntos. El objetivo del juego es conseguir tantos puntos como sea posible antes de que se llene todo el campo.
El juego Tetris con pantalla OLED y Arduino Uno

| OLED i2c 0,96 | Arduino Uno | Arduino Nano | Arduino Mega |
| GND | GND | GND | GND |
| VDD | 5V | 5V | 5V |
| SDA | A4 | A4 | 20 |
| SCL | A5 | A5 | 21 |
La pantalla OLED está conectada a la placa Arduino a través del protocolo i2c (ver tabla de conexión). Los botones del reloj están conectados a los pines digitales de la placa y los pines se pueden cambiar si es necesario. Los pines para los botones están configurados en modo input_pullup, por lo que no se necesitan resistencias para la conexión. Después de montar el circuito, descarga el código para jugar al Tetris en el Arduino Nano / Uno.
Código Arduino para juego Tetris con pantalla OLED
#include "Wire.h" #include "Adafruit_GFX.h" #include "Adafruit_SSD1306.h" #define WIDTH 64 // ancho de pantalla en píxeles #define HEIGHT 128 // altura de pantalla en píxeles #define BUZZER 12 // puerto de conexión del zumbador #define left 2 // puerto de conexión del pulsador #define right 5 #define change 3 #define down 4 Adafruit_SSD1306 display(HEIGHT, WIDTH, &Wire, -1); // matriz de forma "S" const char pieces_S_l[2][2][4] = {{ {0, 0, 1, 1}, {0, 1, 1, 2} }, { {0, 1, 1, 2}, {1, 1, 0, 0} }}; // matriz de forma "S" const char pieces_S_r[2][2][4] = {{ {1, 1, 0, 0}, {0, 1, 1, 2} }, { {0, 1, 1, 2}, {0, 0, 1, 1} }}; // matriz de forma "L" const char pieces_L_l[4][2][4] = {{ {0, 0, 0, 1}, {0, 1, 2, 2} }, { {0, 1, 2, 2}, {1, 1, 1, 0} }, { {0, 1, 1, 1}, {0, 0, 1, 2} }, { {0, 0, 1, 2}, {1, 0, 0, 0} }}; // matriz de forma "cuadrada" const char pieces_Sq[1][2][4] = {{ {0, 1, 0, 1}, {0, 0, 1, 1} }}; // matriz de forma "T" const char pieces_T[4][2][4] = {{ {0, 0, 1, 0}, {0, 1, 1, 2} }, { {0, 1, 1, 2}, {1, 0, 1, 1} }, { {1, 0, 1, 1}, {0, 1, 1, 2} }, { {0, 1, 1, 2}, {0, 0, 1, 0} }}; // matriz de forma "I" const char pieces_l[2][2][4] = {{ {0, 1, 2, 3}, {0, 0, 0, 0} }, { {0, 0, 0, 0}, {0, 1, 2, 3}}}; const short MARGIN_TOP = 19; const short MARGIN_LEFT = 3; const short SIZE = 5; const short TYPES = 6; const int MELODY_LENGTH = 10; const int MELODY_NOTES[MELODY_LENGTH] = {262, 294, 330, 262}; const int MELODY_DURATIONS[MELODY_LENGTH] = {500, 500, 500, 500}; int click[] = { 1047 }; int click_duration[] = { 100 }; int erase[] = { 2093 }; int erase_duration[] = { 100 }; word currentType, nextType, rotation; short pieceX, pieceY; short piece[2][4]; int interval = 20, score; long timer, delayer; boolean grid[10][18]; boolean b1, b2, b3; // función de comprobación de filas llenas void checkLines() { boolean full; for (short y = 17; y >= 0; y--) { full = true; for (short x = 0; x < 10; x++) { full = full && grid[x][y]; } if (full) { breakLine(y); y++; } } } // función de borrado de filas rellenas void breakLine(short line) { tone(BUZZER, erase[0], 1000 / erase_duration[0]); delay(100); noTone(BUZZER); for (short y = line; y >= 0; y--) { for (short x = 0; x < 10; x++) { grid[x][y] = grid[x][y - 1]; } } for (short x = 0; x < 10; x++) { grid[x][0] = 0; } display.invertDisplay(true); delay(50); display.invertDisplay(false); score += 10; } // función de limpieza de la pantalla void refresh() { display.clearDisplay(); drawLayout(); drawGrid(); drawPiece(currentType, 0, pieceX, pieceY); display.display(); } void drawGrid() { for (short x = 0; x < 10; x++) for (short y = 0; y < 18; y++) if (grid[x][y]) display.fillRect(MARGIN_LEFT + (SIZE + 1)*x, MARGIN_TOP + (SIZE + 1)*y, SIZE, SIZE, WHITE); } // prueba de colisión horizontal boolean nextHorizontalCollision(short piece[2][4], int amount) { for (short i = 0; i < 4; i++) { short newX = pieceX + piece[0][i] + amount; if (newX > 9 || newX < 0 || grid[newX][pieceY + piece[1][i]]) return true; } return false; } // prueba de colisión vertical boolean nextCollision() { for (short i = 0; i < 4; i++) { short y = pieceY + piece[1][i] + 1; short x = pieceX + piece[0][i]; if (y > 17 || grid[x][y]) return true; } return false; } // para generar el siguiente bloque void generate() { currentType = nextType; nextType = random(TYPES); if (currentType != 5) pieceX = random(9); else pieceX = random(7); pieceY = 0; rotation = 0; copyPiece(piece, currentType, rotation); } void drawPiece(short type, short rotation, short x, short y) { for (short i = 0; i < 4; i++) display.fillRect(MARGIN_LEFT + (SIZE + 1) * (x + piece[0][i]), MARGIN_TOP + (SIZE + 1) * (y + piece[1][i]), SIZE, SIZE, WHITE); } void drawNextPiece() { short nPiece[2][4]; copyPiece(nPiece, nextType, 0); for (short i = 0; i < 4; i++) display.fillRect(50 + 3 * nPiece[0][i], 4 + 3 * nPiece[1][i], 2, 2, WHITE); } void copyPiece(short piece[2][4], short type, short rotation) { switch (type) { case 0: //L_l for (short i = 0; i < 4; i++) { piece[0][i] = pieces_L_l[rotation][0][i]; piece[1][i] = pieces_L_l[rotation][1][i]; } break; case 1: //S_l for (short i = 0; i < 4; i++) { piece[0][i] = pieces_S_l[rotation][0][i]; piece[1][i] = pieces_S_l[rotation][1][i]; } break; case 2: //S_r for (short i = 0; i < 4; i++) { piece[0][i] = pieces_S_r[rotation][0][i]; piece[1][i] = pieces_S_r[rotation][1][i]; } break; case 3: //Sq for (short i = 0; i < 4; i++) { piece[0][i] = pieces_Sq[0][0][i]; piece[1][i] = pieces_Sq[0][1][i]; } break; case 4: //T for (short i = 0; i < 4; i++) { piece[0][i] = pieces_T[rotation][0][i]; piece[1][i] = pieces_T[rotation][1][i]; } break; case 5: //l for (short i = 0; i < 4; i++) { piece[0][i] = pieces_l[rotation][0][i]; piece[1][i] = pieces_l[rotation][1][i]; } break; } } short getMaxRotation(short type) { if (type == 1 || type == 2 || type == 5) return 2; else if (type == 0 || type == 4) return 4; else if (type == 3) return 1; else return 0; } boolean canRotate(short rotation) { short piece[2][4]; copyPiece(piece, currentType, rotation); return !nextHorizontalCollision(piece, 0); } void drawLayout() { display.drawLine(0, 15, WIDTH, 15, WHITE); display.drawRect(0, 0, WIDTH, HEIGHT, WHITE); drawNextPiece(); char text[6]; itoa(score, text, 10); drawText(text, getNumberLength(score), 7, 4); } short getNumberLength(int n) { short counter = 1; while (n >= 10) { n /= 10; counter++; } return counter; } void drawText(char text[], short length, int x, int y) { display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(x, y); display.cp437(true); for (short i = 0; i < length; i++) display.write(text[i]); } void setup() { pinMode(left, INPUT_PULLUP); pinMode(right, INPUT_PULLUP); pinMode(change, INPUT_PULLUP); pinMode(down, INPUT_PULLUP); pinMode(BUZZER, OUTPUT); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.setRotation(1); display.clearDisplay(); display.setTextColor(SSD1306_WHITE); display.setCursor(18, 40); display.println("TETRIS"); display.setCursor(15, 60); display.println("ARDUINO"); display.display(); // el juego no empezará hasta que se pulse el botón "change" while (true) { if (!digitalRead(change)) { if (b3) { break; } b3 = false; } else { b3 = true; } } display.clearDisplay(); drawLayout(); display.display(); randomSeed(analogRead(0)); nextType = random(TYPES); generate(); timer = millis(); } void loop() { if (millis() - timer > interval) { checkLines(); refresh(); if (nextCollision()) { for (short i = 0; i < 4; i++) grid[pieceX + piece[0][i]][pieceY + piece[1][i]] = 1; generate(); } else pieceY++; timer = millis(); } // si se pulsa el botón "left" if (!digitalRead(left)) { tone(BUZZER, click[0], 1000 / click_duration[0]); delay(100); noTone(BUZZER); if (b1) { if (!nextHorizontalCollision(piece, -1)) { pieceX--; refresh(); } b1 = false; } } else { b1 = true; } // si se pulsa el botón "right" if (!digitalRead(right)) { tone(BUZZER, click[0], 1000 / click_duration[0]); delay(100); noTone(BUZZER); if (b2) { if (!nextHorizontalCollision(piece, 1)) { pieceX++; refresh(); } b2 = false; } } else { b2 = true; } // si se pulsa el botón "down" if (!digitalRead(down)) { interval = 20; } else { interval = 400; } // si se pulsa el botón "change" if (!digitalRead(change)) { tone(BUZZER, click[0], 1000 / click_duration[0]); delay(100); noTone(BUZZER); if (b3) { if (rotation == getMaxRotation(currentType) - 1 && canRotate(0)) { rotation = 0; } else if (canRotate(rotation + 1)) { rotation++; } copyPiece(piece, currentType, rotation); refresh(); b3 = false; delayer = millis(); } } else if (millis() - delayer > 50) { b3 = true; } }
Explicación del código para juego Tetris con Arduino Nano y OLED:
- tras descargar el programa, hay que pulsar el botón superior (change) para iniciar el juego;
- el zumbador emitirá sonidos al pulsar los botones; si se desea, puede desconectarse.
Conclusión. Una vez iniciado el juego, aparecerán bloques de diferentes formas en la pantalla OLED SSD1306 y el objetivo del jugador es apilar correctamente los bloques que van cayendo. Con la ayuda de los botones, el jugador puede girar y mover los bloques en la pantalla, de modo que no llenen la pantalla hasta el borde superior. El objetivo del juego de tetris es que las piezas que caen no lleguen al borde superior del campo de juego de la pantalla OLED.

Deja una respuesta