Building a Tetris Game With Arduino and OLED

by Skylar Jones in Teachers > 1

31 Views, 0 Favorites, 0 Comments

Building a Tetris Game With Arduino and OLED

PCBX Simulation | Design & Build a Tetris Game with Arduino & OLED

This project implements a classic Tetris game on an SSD1306 OLED display. An Arduino MAGE328P microcontroller controls the game logic and interacts with the display and three buttons. The buttons allow players to control the movement (left, right, down) and rotation of the falling Tetris blocks.

Supplies

PCBX Online Simulation

Arduino Nano

Adafruit_SSD1306

Online Simulation

企业微信截图_20250109170656.png

OLED Display (SSD1306): Used to display the Tetris game. Buttons: Three buttons are used to control the movement direction and rotation of the blocks. Arduino Development Board (MAGE328P): Serves as the control unit. Circuit Connections: SSD1306 OLED Display: VCC is connected to Arduino's 5V. GND is connected to Arduino's GND. SCL is connected to Arduino's A5 (SCL). SDA is connected to Arduino's A4 (SDA). Buttons: One pin of each button is connected to an Arduino digital input pin. The other pin is connected to GND.

To eliminate bouncing, a 10kΩ pull-up resistor can be added between the button and GND.

More project details here: https://www.pcbx.com/community-detail/fae5df4491ac4dafb5af59ee5defcd3f



Code Analysis and Explanation

  1. Include Library Files: At the beginning of the program, three library files are included for graphic display, control of the OLED display, and I2C communication.
  2. Define Constants: Constants are defined for the size of the OLED display, the width and height of the grid, the pixel size of the blocks, and other constants.
  3. Define Tetris Shapes: Seven different Tetris shapes are defined using a two-dimensional array.
  4. Game Grid and Variable Initialization: A boolean two-dimensional array grid is defined to represent the game grid, as well as the current block's position, shape, and rotation state.
  5. setup() Function: Initializes serial communication, I2C communication, and the OLED display, and sets the button modes to input pull-up.
  6. loop() Function: The main loop of the program, responsible for checking if the game is over, handling user input, moving the block, and drawing the game frame.
  7. drawFrame() Function: Clears the display and draws the game grid and the current block.
  8. drawShape() Function: Draws the block based on its position, shape, and rotation state.
  9. drawGrid() Function: Draws the game grid.
  10. moveShapeDown() Function: Moves the block down, and if a collision occurs, it fixes the block and checks if a line is completed.
  11. checkCollision() Function: Checks for collisions after the block is moved.
  12. fixShape() Function: Fixes the block onto the game grid.
  13. newShape() Function: Generates a new block.
  14. handleInput() Function: Handles user button input, including left move, right move, and rotation.
  15. checkLines() Function: Checks and clears completed lines.
  16. isGameOver() Function: Checks if the game is over.
  17. rotateShape() Function: Rotates the block.
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDRESS 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

const int gridWidth = 10;
const int gridHeight = 20;
const int blockPixelSize = 2;

// Tetris shapes in a 4x4 grid
const byte shapes[7][4][4] = {
// I
{{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0}},
// J
{{0, 0, 0, 0},
{1, 1, 1, 0},
{0, 0, 1, 0},
{0, 0, 0, 0}},
// L
{{0, 0, 0, 0},
{1, 1, 1, 0},
{1, 0, 0, 0},
{0, 0, 0, 0}},
// O
{{0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}},
// S
{{0, 0, 0, 0},
{0, 1, 1, 0},
{1, 1, 0, 0},
{0, 0, 0, 0}},
// T
{{0, 0, 0, 0},
{1, 1, 1, 0},
{0, 1, 0, 0},
{0, 0, 0, 0}},
// Z
{{0, 0, 0, 0},
{1, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}}
};

bool grid[gridHeight][gridWidth] = {false};

int currentX = 0;
int currentY = 0;
int currentShape = 0;
int rotation = 0;

unsigned long lastFallTime = 0;
const unsigned long fallSpeed = 500;

void setup() {
Serial.begin(9600);
Wire.begin();

if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}

display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.display();

pinMode(2, INPUT_PULLUP); // Left move button
pinMode(3, INPUT_PULLUP); // Right move button
pinMode(4, INPUT_PULLUP); // Rotate button

newShape();
}

void loop() {
if (isGameOver()) {
display.clearDisplay();
display.setCursor(10, 20);
display.println("Game Over");
display.display();
delay(2000);
memset(grid, false, sizeof(grid));
newShape();
return;
}

handleInput(); // Handle user input

if (millis() - lastFallTime >= fallSpeed) {
lastFallTime = millis();
moveShapeDown();
}

drawFrame();
}

void drawFrame() {
display.clearDisplay();
drawGrid();
drawShape(currentX, currentY, currentShape, rotation);
display.display();
}

void drawShape(int x, int y, int shape, int rot) {
byte rotatedShape[4][4];

rotateShape(shape, rot, rotatedShape);

for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (rotatedShape[i][j]) {
int gridX = x + j;
int gridY = y + i;
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
display.fillRect(gridX * blockPixelSize, gridY * blockPixelSize, blockPixelSize, blockPixelSize, SSD1306_WHITE);
}
}
}
}
}

void drawGrid() {
for (int y = 0; y < gridHeight; y++) {
for (int x = 0; x < gridWidth; x++) {
if (grid[y][x]) {
display.fillRect(x * blockPixelSize, y * blockPixelSize, blockPixelSize, blockPixelSize, SSD1306_WHITE);
}
}
}
}

void moveShapeDown() {
if (!checkCollision(currentX, currentY + 1, currentShape, rotation)) {
currentY++;
} else {
fixShape();
checkLines();
newShape();
}
}

bool checkCollision(int x, int y, int shape, int rot) {
byte rotatedShape[4][4];
rotateShape(shape, rot, rotatedShape);

for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (rotatedShape[i][j]) {
int gridX = x + j;
int gridY = y + i;

if (gridX < 0 || gridX >= gridWidth || gridY >= gridHeight || grid[gridY][gridX]) {
return true;
}
}
}
}
return false;
}

void fixShape() {
byte rotatedShape[4][4];
rotateShape(currentShape, rotation, rotatedShape);

for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (rotatedShape[i][j]) {
int gridX = currentX + j;
int gridY = currentY + i;
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
grid[gridY][gridX] = true;
}
}
}
}
}

void newShape() {
currentX = gridWidth / 2 - 2;
currentY = 0;
currentShape = random(0, 7);
rotation = 0;
}

void handleInput() {
// Handle user button input
if (digitalRead(2) == LOW) { // Left move
if (!checkCollision(currentX - 1, currentY, currentShape, rotation)) {
currentX--;
}
}

if (digitalRead(3) == LOW) { // Right move
if (!checkCollision(currentX + 1, currentY, currentShape, rotation)) {
currentX++;
}
}

if (digitalRead(4) == LOW) { // Rotate
int newRotation = (rotation + 1) % 4;
if (!checkCollision(currentX, currentY, currentShape, newRotation)) {
rotation = newRotation;
}
}

delay(100); // Simple debounce
}

void checkLines() {
for (int y = gridHeight - 1; y >= 0; y--) {
bool full = true;
for (int x = 0; x < gridWidth; x++) {
if (!grid[y][x]) {
full = false;
break;
}
}
if (full) {
for (int dy = y; dy > 0; dy--) {
for (int x = 0; x < gridWidth; x++) {
grid[dy][x] = grid[dy - 1][x];
}
}
for (int x = 0; x < gridWidth; x++) {
grid[0][x] = false;
}
y++; // Recheck this line in the next iteration
}
}
}

bool isGameOver() {
for (int x = 0; x < gridWidth; x++) {
if (grid[0][x]) {
return true;
}
}
return false;
}

void rotateShape(int shape, int rot, byte output[4][4]) {
// Clear output shape array initially
memset(output, 0, 16);

// Perform rotations and store into output
// Basic matrix rotation - 90 degrees clockwise:
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (rot == 1) {
output[i][j] = shapes[shape][3-j][i];
} else if (rot == 2) {
output[i][j] = shapes[shape][3-i][3-j];
} else if (rot == 3) {
output[i][j] = shapes[shape][j][3-i];
} else {
output[i][j] = shapes[shape][i][j];
}
}
}
}

Conclusion

While the 3D simulation feature is still a work in progress, we would love to hear your suggestions and expectations. It's an open-source community; any sharing and feedback is welcome.

Your feedback will help our engineering team enhance the platform and better serve our users.

Join the PCBX Simulation Community and create your own project to win a surprise bag now.