Tetris su Arduino con display OLED 128×64

Il gioco del Tetris su Arduino Uno con OLED 128×64 i2c e pulsanti è un progetto semplice, ma anche interessante per i principianti. Lo schema di assemblaggio del gioco “Tetris” su Arduino Uno con schermo è stato semplificato al massimo, in modo che qualsiasi principiante sia in grado di ripeterlo. Abbiamo incluso qui lo schema di collegamento dello schermo al microcontrollore Arduino e il programma per creare il gioco Tetris sul display OLED SSD1306.




Per questa attività sono necessari:

  • Arduino Uno / Arduino Nano / Arduino Mega
  • modulo display OLED i2c 128×64
  • pulsanti
  • breadboard
  • cavi di collegamento
  • libreria Adafruit_SSD1306.h, Adafruit_GFX.h

Arduino gioco Tetris su OLED display 128×64

Il gioco Tetris fu creato nel 1984 dal programmatore sovietico Alexei Pazhitnov per il computer Electronica-60. Tetris è un rompicapo in cui il giocatore deve impilare blocchi cadenti di forme diverse in modo che riempiano le linee orizzontali sullo schermo. Nel 1989, Tetris è stato pubblicato negli Stati Uniti da Spectrum HoloByte, che ha ottenuto i diritti di distribuzione del gioco. Nel 1990, i diritti del gioco Tetris sono stati acquisiti da Mirrorsoft.

Schema per la costruzione di Tetris sul OLED Arduino

Schema per la costruzione di Tetris sul display OLED Arduino
Schema per la costruzione di Tetris sul display OLED Arduino
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

Il display OLED SSD1306 è collegato alla scheda Arduino Uno tramite protocollo i2c (vedere la tabella precedente per il collegamento del display ad Arduino Mega). I pulsanti sono collegati ai pin digitali della scheda; se necessario, i pin possono essere modificati nello sketch. Le porte dei pulsanti sono configurate in modalità input_pullup, quindi non sono necessarie resistenze. Dopo aver assemblato il circuito, caricare il codice di Tetris in Arduino.

Programma Arduino per Tetris su OLED display i2c




#include "Wire.h"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"

#define WIDTH 64
#define HEIGHT 128
#define BUZZER 12
#define left 2
#define right 5
#define change 3
#define down 4

Adafruit_SSD1306 display(HEIGHT, WIDTH, &Wire, -1);

// matrice della 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} }};
// matrice della 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} }};
// matrice della 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} }};
// matrice della forma "carrée"
const char pieces_Sq[1][2][4] = {{ {0, 1, 0, 1}, {0, 0, 1, 1} }};
// matrice della 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} }};
// matrice de la forme "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;

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

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);
  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();
 
  // premere il pulsante "change" per avviare il gioco
  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();
  }

  // se viene premuto il pulsante "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;
  }

  // se si preme il pulsante "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;
  }

  // se si preme il pulsante "down"
  if (!digitalRead(down)) {
    interval = 20;
  } else {
    interval = 400;
  }

  // se si preme il pulsante "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;
  }
}

Spiegazione del codice per Tetris su OLED display Arduino:



  1. le porte di connessione dei pulsanti e del cicalino sono impostate tramite la direttiva #define;
  2. dopo aver caricato il programma per avviare il gioco su OLED 128×64 i2c SSD1306 è necessario premere il pulsante superiore (change).

Conclusione. Lo scopo del gioco è quello di posizionare blocchi cadenti di forme diverse in modo da ottenere, dopo ogni mossa, una linea solida. Quando la linea sullo schermo è completamente riempita, scompare e il giocatore ottiene punti. L’obiettivo del gioco è ottenere il maggior numero di punti possibile prima che l’intero schermo si riempia di forme. Se avete ancora domande sul progetto Arduino Nano Tetris, lasciatele nei commenti.

5/5 - (1 vote)


Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.