Creating Tetris Game with Arduino and OLED Display

Published  May 8, 2023   1
Tetris Game with Arduino and OLED

If you're up for a challenge, you could create your own Tetris game using an Arduino and an OLED display from scratch. Don't worry if the idea of making your own Tetris game from scratch using an Arduino and an OLED display sounds intimidating. I'm here to help you every step of the way!

To start, you'll need to gather some essential items such as:-

  • Arduino Nano
  • OLED Display
  • 4 push buttons
  • Buzzerss
  • 9V battery
  • Breadboard
  • Jumper wires

OLED Display Pinout Diagram

OLED Display Pinout Diagram

GND Ground - The reference voltage used to complete electrical circuits.

VCC Power supply voltage - The voltage supply pin for the OLED

SCL Serial Clock - The signal that synchronizes the data transfer between OLED and Arduino.

SDA Serial Data - The signal used to transmit data between OLED and Arduino.

Circuit Diagram - Interfacing Arduino Nano and OLED Display for Tetris Game

Circuit Diagram - Interfacing Arduino Nano and OLED Display for Tetris Game

Once you have everything you need, it's time to start connecting the components and have some fun creating your game! You'll need to connect the OLED display to the Arduino Nano using a 4-pin I2C interface.

Simply connect a handful of pins from the OLED display to their corresponding pins on the Nano.

For the push buttons, you'll need to connect them to four digital input pins on the Nano. This process is rather simple - just connect one pin of each button to a digital input pin on the Nano, and the other pin to the GND pin of the Nano.

Next, connect the buzzer to a digital output pin on the Nano. This requires connecting a few pins from the buzzer to the Nano.

Finally, to power up the entire setup, connect a 9V battery to the Vin pin of the Nano (positive wire) and the GND pin of the Nano (negative wire).

Arduino Code for Tetris Game

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

These lines include the necessary libraries for using the OLED display.

#define WIDTH 64
#define HEIGHT 128

These lines define the width and height of the OLED display in pixels.

Adafruit_SSD1306 display(128, 64, &Wire, -1);

This line initializes the OLED display with a width of 128 pixels, height of 64 pixels, using the Wire library for communication, and a reset pin of -1 (which means it's not used).

static const unsigned char PROGMEM mantex_logo [] = {
  0x00, 0x00, 0x18, 0x06, 0x01, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, ……….
……… 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
  };

This is a hexadecimal representation of an image of circuit digest logo

const char pieces_S_l[2][2][4] = {{
                                      {0, 0, 1, 1}, {0, 1, 1, 2}
                                    },
                                    {
                                      {0, 1, 1, 2}, {1, 1, 0, 0}
                                    }};

This is a 3D array that defines the shape of a tetromino piece. Specifically, this array represents the "S" shape when rotated to the left. The first index (2) represents the number of rotations of the piece, the second index (2) represents the number of rows, and the third index (4) represents the number of columns. The values in this array represent the position of the blocks within the piece.

const char pieces_S_r[2][2][4]{{
                                    {1, 1, 0, 0}, {0, 1, 1, 2}
                                  },
                                  {
                                    {0, 1, 1, 2}, {0, 0, 1, 1}
                                  }};

This is another 3D array that represents the "S" shape when rotated to the right.

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}
                                  }};

This 3D array represents the "L" shape when rotated to the left.

const char pieces_Sq[1][2][4] = {{
                                    {0, 1, 0, 1}, {0, 0, 1, 1}
                                  }};

This 3D array represents the "square" shape, which is a tetromino with a 2x2 block configuration. It only needs one rotation.

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}
                                  }};

This 3D array represents the "T" shape when rotated in each of the four possible directions.

const char pieces_l[2][2][4] = {{
                                    {0, 1, 2, 3}, {0, 0, 0, 0}
                                  },
                                  {
                                    {0, 0, 0, 0}, {0, 1, 2, 3}
                                  }};

This 3D array represents the "line" shape, which is a tetromino with a 1x4 block configuration. It includes two rotations, one horizontal and one vertical.

const short MARGIN_TOP = 19; - declares a constant variable MARGIN_TOP with a value of 19 of type short.
const short MARGIN_LEFT = 3; - declares a constant variable MARGIN_LEFT with a value of 3 of type short.
const short SIZE = 5; - declares a constant variable SIZE with a value of 5 of type short.
const short TYPES = 6; - declares a constant variable TYPES with a value of 6 of type short.
#define SPEAKER_PIN 3 - creates a macro with the name SPEAKER_PIN and a value of 3. This allows the code to refer to SPEAKER_PIN throughout the rest of the code and substitute it with the value 3.
const int MELODY_LENGTH = 10; - declares a constant variable MELODY_LENGTH with a value of 10 of type int.
const int MELODY_NOTES[MELODY_LENGTH] = {262, 294, 330, 262}; - declares an array MELODY_NOTES of size MELODY_LENGTH (which is 10), and initializes it with four integer values.
const int MELODY_DURATIONS[MELODY_LENGTH] = {500, 500, 500, 500}; - declares an array MELODY_DURATIONS of size MELODY_LENGTH (which is 10), and initializes it with four integer values.
int click[] = { 1047 }; - declares an array click of size 1, and initializes it with one integer value.
int click_duration[] = { 100 }; - declares an array click_duration of size 1, and initializes it with one integer value.
int erase[] = { 2093 }; - declares an array erase of size 1, and initializes it with one integer value.
int erase_duration[] = { 100 }; - declares an array erase_duration of size 1, and initializes it with one integer value.
word currentType, nextType, rotation; - declares three variables of type word, named currentType, nextType, and rotation.
short pieceX, pieceY; - declares two variables of type short, named pieceX and pieceY.
short piece[2][4]; - declares a two-dimensional array piece of size 2x4 with elements of type short.
int interval = 20, score; - declares two variables, interval and score, of type int. interval is initialized with a value of 20.
long timer, delayer; - declares two variables, timer and delayer, of type long.
boolean grid[10][18]; - declares a two-dimensional array grid of size 10x18 with elements of type boolean.
boolean b1, b2, b3; - declares three variables of type boolean, named b1, b2, and b3.
  int left=11;
  int right=9;
  int change=12;
  int speed=10;

These lines define four integer variables: left, right, change, and speed.

left is initialized to 11, right is initialized to 9, change is initialized to 12, and speed is initialized to 10. These variables are likely used later in the code to represent the keycodes for specific buttons on a game controller or keyboard.

  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++;
      }
    }
  }
  void breakLine(short line){
      tone(SPEAKER_PIN, erase[0], 1000 / erase_duration[0]); 
      delay(100);
      noTone(SPEAKER_PIN);
    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;
  }

This code implements the function checkLines() and breakLine(short line) for clearing out completed lines in the game grid.

checkLines() iterates through each row of the grid from bottom to top, checking if a line is full (all cells are occupied) or not. If it is, it calls the breakLine() function, passing the line number as a parameter, to clear that line.

breakLine(short line) first plays an "erase" tone using the tone() function on the SPEAKER_PIN, indicating that a line has been cleared. It then shifts down all the rows above the cleared line by one cell and clears the top row. It also adds 10 to the score for clearing a line. Lastly, it inverts the display of the LED matrix for 50ms to create a visual effect of the cleared line, and then sets the display back to normal.

  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);
  }
  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;
  }
  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;
  }

refresh(): This function clears the display, draws the layout, draws the grid, and finally draws the current piece on the display.

drawGrid(): This function iterates over the entire grid and if a cell is filled, it draws a white rectangle with the size of the cell.

nextHorizontalCollision(short piece[2][4], int amount): This function checks if there is any collision with the grid on the horizontal direction. It does this by checking each cell in the current piece and adding an amount to its x position. If the new x position is outside of the grid or is already occupied, then there is a collision and the function returns true.

nextCollision(): This function checks if there is any collision with the grid on the vertical direction. It does this by checking each cell in the current piece and adding one to its y position. If the new y position is outside of the grid or is already occupied, then there is a collision and the function returns true.

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);
  }

The generate() function sets the variables for the next Tetris piece that will be dropped onto the game board. It sets the currentType to be the same as the nextType, and then generates a new nextType using the random() function.

If the currentType is not the "O" piece (represented by a type value of 5), then the pieceX value is set to a random number between 0 and 8 (inclusive), since the "O" piece is always centered.

Otherwise, for the "O" piece, the pieceX value is set to a random number between 0 and 6 (inclusive).

The pieceY value is set to 0, indicating that the piece will start at the top of the game board.

The rotation value is set to 0, indicating that the piece has not been rotated.

Finally, the copyPiece() function is called to copy the appropriate piece into the piece array.

The drawPiece() function takes in the type of a Tetris piece, its rotation, and its current x and y coordinates on the game board. It then uses a for loop to loop through the four blocks of the piece and draw each block onto the game board using the display.fillRect() function.

The drawNextPiece() function draws the next Tetris piece in the preview box on the right side of the game board. It first copies the next piece into the nPiece array using the copyPiece() function. It then loops through the four blocks of the piece and draws each block onto the preview box using the display.fillRect() function.

  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);
  }

refresh(): This function is called to update the display of the game. It first clears the display and then draws the layout, grid, and the current piece on the screen.

drawGrid(): This function is called by refresh() to draw the grid on the screen, representing the blocks that have already fallen.

nextHorizontalCollision(): This function checks if the current piece will collide with any block in the next horizontal move. It does this by checking each of the 4 blocks that make up the piece, and determining whether moving it by amount spaces would make it overlap with any blocks that have already fallen.

nextCollision(): This function checks if the current piece will collide with any block if it moves down by one space. It does this by checking each of the 4 blocks that make up the piece, and determining whether moving it down by one space would make it overlap with any blocks that have already fallen, or if it would go out of bounds (i.e. below the game area).

generate(): This function generates a new piece for the player to control. It sets currentType to the type of the next piece, chooses a random position for the piece to start (pieceX), sets pieceY to 0 (the top of the game area), sets the rotation to 0, and calls copyPiece() to fill in the piece array with the appropriate blocks for the new piece.

drawPiece(): This function is called to draw the current piece on the screen. It does this by looping through each of the 4 blocks that make up the piece and drawing a white square for each one at the appropriate position on the screen.

drawNextPiece(): This function is called to draw the next piece on the screen. It does this by copying the block positions for the next piece into a new nPiece array and then drawing a small preview of the piece in the top right corner of the screen.

copyPiece(): This function fills in the piece array with the appropriate blocks for a given piece type and rotation. It does this by switching on type to determine which set of blocks to use, and then copying them over to piece based on the current rotation.

getMaxRotation(): This function returns the maximum number of rotations that a given piece type can have. It returns 2 for types 1, 2, and 5, 4 for types 0 and 4, 1 for type 3, and 0 for any other type.

canRotate(): This function checks if the current piece can be rotated by rotation amount. It does this by calling copyPiece() to create a new piece array with the rotated blocks, and then calling nextHorizontalCollision() to check if the rotated piece would overlap with any fallen blocks.

  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);      // Normal 1:1 pixel scale
    display.setTextColor(WHITE); // Draw white text
    display.setCursor(x, y);     // Start at top-left corner
    display.cp437(true);         // Use full 256 char 'Code Page 437' font
    for(short i = 0; i < length; i++)
      display.write(text[i]);
  }

drawLayout() function is responsible for drawing the basic layout of the game screen, including the border, the separator line, the score, and the next piece. It calls the drawNextPiece() and drawText() functions to draw the next piece and the score, respectively.

getNumberLength(int n) is a helper function that takes an integer as input and returns the number of digits in it. This is useful for determining the length of the score that needs to be drawn.

drawText(char text[], short length, int x, int y) function draws a string of text on the screen at the given x and y coordinates. It takes in the text to be drawn, its length, and the x and y coordinates as parameters. This function sets the text size, color, cursor position, and font before drawing the text.

 void setup() {
    pinMode(left, INPUT_PULLUP);
    pinMode(right, INPUT_PULLUP);
    pinMode(change, INPUT_PULLUP);
    pinMode(speed, INPUT_PULLUP);
    pinMode(SPEAKER_PIN, OUTPUT);
    Serial.begin(9600);
    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
      Serial.println(F("SSD1306 allocation failed"));
      for(;;); // Don't proceed, loop forever
    }
    display.setRotation(1);
    display.clearDisplay();
    display.drawBitmap(3, 23, mantex_logo, 64, 82,  WHITE);
    display.display();
    delay(2000);
    display.clearDisplay();
    drawLayout();
    display.display();
    randomSeed(analogRead(0));
    nextType = random(TYPES);
    generate();
    timer = millis();
  }

The setup() function is a required function in an Arduino sketch that runs once at the beginning of the program. In this particular sketch, it initializes the input and output pins, sets up communication through Serial, initializes and clears the OLED display, and sets the rotation of the display. It then displays a logo for two seconds before clearing the display and calling the drawLayout() function to draw the game layout. Finally, it sets the random seed, generates the first piece, and starts the timer.

  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();
    }
    if(!digitalRead(left)){
      tone(SPEAKER_PIN, click[0], 1000 / click_duration[0]);
      delay(100);
      noTone(SPEAKER_PIN);
      if(b1){
        if(!nextHorizontalCollision(piece, -1)){
          pieceX--;
          refresh();
        }
        b1 = false;
      }
    }else{
      b1 = true;
    }
    if(!digitalRead(right)){
      tone(SPEAKER_PIN, click[0], 1000 / click_duration[0]);
      delay(100);
      noTone(SPEAKER_PIN);
      if(b2){
        if(!nextHorizontalCollision(piece, 1)){
          pieceX++;
          refresh();
        }
        b2 = false;
      }
    }else{
      b2 = true;
    }
    if(!digitalRead(speed)){
      interval = 20;
    } else{
      interval = 400;
      }
    if(!digitalRead(change)){
      tone(SPEAKER_PIN, click[0], 1000 / click_duration[0]);
      delay(100);
      noTone(SPEAKER_PIN);
      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;
    }
  }

The loop() function is the main function in the Arduino sketch that is executed repeatedly after the setup() function is called.

In this particular code, the loop checks whether the time interval has passed and performs necessary actions such as checking and clearing lines or generating a new piece. It also reads input from buttons and handles the movement of the current piece, including rotation and horizontal movement. Finally, it adjusts the speed of the game based on the input from a specific button. The function also generates sound effects using the tone() and noTone() functions.

Conclusion

In this project, we have seen how to create a Tetris game using an Arduino and a 128x64 OLED display. We have used C++ language and the Arduino IDE to program the game.

We started by setting up the hardware, including the display, buttons, and speaker, and then defined the game logic. We used a grid to represent the game board and defined the seven Tetromino shapes as arrays. We also created functions to generate and rotate pieces, check for collisions, and update the game board.

Next, we defined the game loop and implemented the button controls. We also added sound effects using the Arduino tone () function.

Finally, we created a simple user interface for the game using the OLED display. We displayed the current score and the next piece to appear, as well as a Tetris logo at the start of the game.

Overall, this project demonstrates how to create a basic arcade-style game using an Arduino and some simple components. The code can be extended and modified to create more complex games or to add new features.

Projects using Arduino Nano

18650 Lithium Battery Capacity Tester using Arduino
18650 Lithium Battery Capacity Tester using Arduino

Are you looking to test the capacity of your 18650 lithium batteries? Look no further than our latest DIY project using an Arduino board! In this blog post, we'll guide you through the process of building your own battery capacity tester, including all the necessary components, tools, and materials. With our step-by-step instructions and code samples, you'll be able to create a reliable tester that can help you get the most out of your lithium batteries. So, head over to our blog and start exploring the world of battery testing with Arduino today!

Build a DIY Oscilloscope using Arduino Nano and OLED Display
Build a DIY Oscilloscope using Arduino Nano and OLED Display

Are you interested in electronics and looking for a new project? Look no further than building your own oscilloscope using an Arduino Nano and OLED display! With our step-by-step guide, you'll learn how to assemble and program your own oscilloscope, which can measure and display electrical signals in real-time. This project is perfect for both beginners and advanced makers, and it's a great way to explore electronics and programming while also creating a useful tool for your toolbox. Check out our blog for all the details and get started today!

DIY Pulse Oximeter using Arduino and Pulse Sensor
DIY Pulse Oximeter using Arduino and Pulse Sensor

Learn how to build a pulse oximeter using an Arduino board and pulse sensor with our step-by-step guide. This project is a great way to explore electronics and programming while also monitoring your health. Check out our blog for all the details!

Code

#include <Wire.h>

  #include <Adafruit_GFX.h>

  #include <Adafruit_SSD1306.h>

  #define WIDTH 64 // OLED display width, in pixels

  #define HEIGHT 128 // OLED display height, in pixels

  Adafruit_SSD1306 display(128, 64, &Wire, -1);

  static const unsigned char PROGMEM mantex_logo [] = {

  0x00, 0x00, 0x18, 0x06, 0x01, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3c, 0x0f, 0x03, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x03, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x3f, 0x80, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x3e, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x7c, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x78, 0x7f, 0xff, 0xff, 0xe1, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xff, 0xff, 0xff, 0xf8, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xff, 0xff, 0xff, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x7f, 0xf1, 0xff, 0x0f, 0xff, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0xff, 0xf1, 0xfc, 0x01, 0xff, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0xff, 0xf1, 0xf0, 0x00, 0xff, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x7f, 0xf1, 0xf0, 0x00, 0x7f, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x01, 0xf1, 0xe0, 0x70, 0x3f, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xe1, 0xf8, 0x3f, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xe1, 0xff, 0xff, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xe1, 0xff, 0xff, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xc1, 0xff, 0xff, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xe1, 0xe0, 0x07, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x7f, 0xf1, 0xe1, 0xe0, 0x01, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0xff, 0xf1, 0xe1, 0xe0, 0x00, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0xff, 0xf1, 0xe0, 0xe3, 0xf8, 0x7c, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x7f, 0xf1, 0xe0, 0x63, 0xfc, 0x7c, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x3f, 0xf1, 0xf0, 0x23, 0xfc, 0x3c, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xf8, 0x23, 0xfc, 0x3c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xff, 0x63, 0xfc, 0x3c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xff, 0xe3, 0xfc, 0x3c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xff, 0xe3, 0xfc, 0x7c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xff, 0xe3, 0xf8, 0x7c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x7f, 0xf1, 0xff, 0xe1, 0xf0, 0x7c, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0xff, 0xf1, 0xff, 0xe0, 0x00, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0xff, 0xf1, 0xff, 0xe0, 0x03, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0xff, 0xf1, 0xff, 0xe0, 0x1f, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x7f, 0xf1, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf1, 0xff, 0xff, 0xff, 0xf8, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0xf0, 0xff, 0xff, 0xff, 0xf8, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x78, 0x7f, 0xff, 0xff, 0xf0, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x7c, 0x1f, 0xff, 0xff, 0xc1, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x3c, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x3f, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x1f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3c, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x3c, 0x0f, 0x01, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff

  };

  const char pieces_S_l[2][2][4] = {{

                                      {0, 0, 1, 1}, {0, 1, 1, 2}

                                    },

                                    {

                                      {0, 1, 1, 2}, {1, 1, 0, 0}

                                    }};

  const char pieces_S_r[2][2][4]{{

                                    {1, 1, 0, 0}, {0, 1, 1, 2}

                                  },

                                  {

                                    {0, 1, 1, 2}, {0, 0, 1, 1}

                                  }};                            

  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}

                                  }};

  const char pieces_Sq[1][2][4] = {{

                                    {0, 1, 0, 1}, {0, 0, 1, 1}

                                  }};

  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}

                                  }};

  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;

  #define SPEAKER_PIN 3

  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;

  int left=11;

  int right=9;

  int change=12;

  int speed=10;

  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++;

      }

    }

  }

  void breakLine(short line){

      tone(SPEAKER_PIN, erase[0], 1000 / erase_duration[0]);  

      delay(100);

      noTone(SPEAKER_PIN);

    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;

  }

  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);

  }

  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;

  }

  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;

  }

  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);      // Normal 1:1 pixel scale

    display.setTextColor(WHITE); // Draw white text

    display.setCursor(x, y);     // Start at top-left corner

    display.cp437(true);         // Use full 256 char 'Code Page 437' font

    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(speed, INPUT_PULLUP);

    pinMode(SPEAKER_PIN, OUTPUT);

    Serial.begin(9600);

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally

    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64

      Serial.println(F("SSD1306 allocation failed"));

      for(;;); // Don't proceed, loop forever

    }

    display.setRotation(1);

    display.clearDisplay();

    display.drawBitmap(3, 23, mantex_logo, 64, 82,  WHITE);

    display.display();

    delay(2000);

    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();

    }

    if(!digitalRead(left)){

      tone(SPEAKER_PIN, click[0], 1000 / click_duration[0]);

      delay(100);

      noTone(SPEAKER_PIN);

      if(b1){

        if(!nextHorizontalCollision(piece, -1)){

          pieceX--;

          refresh();

        }

        b1 = false;

      }

    }else{ 

      b1 = true;

    }

    if(!digitalRead(right)){

      tone(SPEAKER_PIN, click[0], 1000 / click_duration[0]);

      delay(100);

      noTone(SPEAKER_PIN);

      if(b2){

        if(!nextHorizontalCollision(piece, 1)){

          pieceX++;

          refresh();

        }

        b2 = false;

      }

    }else{ 

      b2 = true;

    }

    if(!digitalRead(speed)){

      interval = 20;

    }else{

      interval = 400;

      }

    if(!digitalRead(change)){

      tone(SPEAKER_PIN, click[0], 1000 / click_duration[0]);

      delay(100);

      noTone(SPEAKER_PIN);

      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;

    }

  }

Have any question realated to this Article?

Ask Our Community Members

Comments