/*
* Use of Display 8x8 MAX72XX and buttons
* components to print some information on the display.
*
* for more examples:
* URL:
*/
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
//#include <Wire.h>
#define FAST_DROP_DELAY 100 // Time between fast drop movements
// Define the number of devices we have in the chain and the hardware interface
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 4 // 4 modules arranged horizontally (32x8 display)
#define DATA_PIN 11 // DATA or MOSI
#define CLK_PIN 13 // CLK or SCK
#define CS_PIN 10 // CS or SS
//BUTTONS
#define BTN_UP 5 //RIGHT
#define BTN_DOWN 3 //LEFT
#define BTN_RIGHT 6 //ROTATE
#define BTN_LEFT 4 //DOWN
//JOYSTICK
const int SW_pin = 2; // digital pin connected to SW (Select)
const int X_pin = A1; // analog pin connected to VRx (horizontal)
const int Y_pin = A2; // analog pin connected to VRy (vertical)
const int maxY = MAX_DEVICES * 8 - 1; //ALTURA MAXIMA
const int maxX = 7; //ANCHO MAXIMO
//PARLANTE
#define BUZZER A0
// Sound frequencies
#define MOVE_SOUND 100
#define ROTATE_SOUND 200
#define DROP_SOUND 300
#define LINE_SOUND 523
// Button debouncing
#define DEBOUNCE_DELAY 50
unsigned long lastDebounceTime = 0;
byte lastButtonState = HIGH;
byte buttonState = HIGH;
// Create a new instance of the MD_MAX72XX class:
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
MD_Parola matrix = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Tetromino shapes with fixed I-shape (no rotation)
const byte shapes[7][4][4] = {
// L shape (4 rotations)
{
{0b10000000, 0b10000000, 0b11000000, 0b00000000}, // 0°
{0b11100000, 0b10000000, 0b00000000, 0b00000000}, // 90°
{0b11000000, 0b01000000, 0b01000000, 0b00000000}, // 180°
{0b00100000, 0b11100000, 0b00000000, 0b00000000} // 270°
},
// J shape (4 rotations)
{
{0b01000000, 0b01000000, 0b11000000, 0b00000000}, // 0°
{0b10000000, 0b11100000, 0b00000000, 0b00000000}, // 90°
{0b11000000, 0b10000000, 0b10000000, 0b00000000}, // 180°
{0b11100000, 0b00100000, 0b00000000, 0b00000000} // 270°
},
// O shape (same for all rotations)
{
{0b11000000, 0b11000000, 0b00000000, 0b00000000},
{0b11000000, 0b11000000, 0b00000000, 0b00000000},
{0b11000000, 0b11000000, 0b00000000, 0b00000000},
{0b11000000, 0b11000000, 0b00000000, 0b00000000}
},
// T shape (4 rotations)
{
{0b11100000, 0b01000000, 0b00000000, 0b00000000}, // 0°
{0b01000000, 0b11000000, 0b01000000, 0b00000000}, // 90°
{0b01000000, 0b11100000, 0b00000000, 0b00000000}, // 180°
{0b10000000, 0b11000000, 0b10000000, 0b00000000} // 270°
},
// S shape (4 rotations)
{
{0b01100000, 0b11000000, 0b00000000, 0b00000000}, // 0°
{0b10000000, 0b11000000, 0b01000000, 0b00000000}, // 90°
{0b01100000, 0b11000000, 0b00000000, 0b00000000}, // 180°
{0b10000000, 0b11000000, 0b01000000, 0b00000000} // 270°
},
// Z shape (4 rotations)
{
{0b11000000, 0b01100000, 0b00000000, 0b00000000}, // 0°
{0b01000000, 0b11000000, 0b10000000, 0b00000000}, // 90°
{0b11000000, 0b01100000, 0b00000000, 0b00000000}, // 180°
{0b01000000, 0b11000000, 0b10000000, 0b00000000} // 270°
},
// I shape (4 rotations)
{
{0b10000000, 0b10000000, 0b10000000, 0b10000000}, // 0° (vertical)
{0b00000000, 0b00000000, 0b11110000, 0b00000000}, // 90° (horizontal)
{0b10000000, 0b10000000, 0b10000000, 0b10000000}, // 180°
{0b00000000, 0b00000000, 0b11110000, 0b00000000} // 270°
}
};
struct Block {
int x; // Column (0-31)
int y; // Row (0-7)
int shape; // Shape index (0-6)
int rotation; // Current rotation (0-3)
};
Block current;
bool grid[32][8] = {false}; // Playfield grid [columns][rows]
int speed = 500;
int score = 0;
bool gameOver = false;
unsigned long lastMove = 0;
bool showScore = false;
unsigned long scoreTime = 0;
bool displayDirty = true;
//FOR SONG
int speaker_pin = BUZZER;
int length = 99;
char notes[] = "EbCDCbaaCEDCbbCDECaa DFAGFEECEDCbbCDECaa EbCDCbaaCEDCbbCDECaa DFAGFEECEDCbbCDECaa ECDbCab ECDbCEAJ ";
int beats[] = // Som
{
2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 4, 2,
2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 4, 1,
2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 4, 2,
2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 4, 1,
5, 5, 5, 5, 5, 5, 7, 2, 5, 5, 5, 5, 2, 2, 5, 5, 3
};
int tempo = 138; // Tempo
void setup() {
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BTN_LEFT, INPUT_PULLUP);
pinMode(BTN_RIGHT, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
mx.begin();
matrix.begin();
mx.control(MD_MAX72XX::INTENSITY, 5);
mx.control(MD_MAX72XX::SCANLIMIT, 7);
intro_screen(); //Display intro on screen
melody_play(); //Play the melody
mx.clear(); //Clean display
spawnBlock();
lastMove = millis();
}
void loop() {
unsigned long currentTime = millis();
if (gameOver) {
playTone(100, 1000);
displayGameOver();
//if (currentTime - lastMove > 3000) resetGame();
// return;
}
if (showScore) {
if (currentTime - scoreTime > 1000) {
showScore = false;
displayDirty = true;
}
return;
}
checkInput();
if (currentTime - lastMove > speed) {
moveRight();
lastMove = currentTime;
displayDirty = true;
}
if (displayDirty) {
drawScreen();
displayDirty = false;
}
}
void spawnBlock() {
current = {0, 3, random(0, 7), 0}; // Start in the middle
if (!canMove(current.x, current.y)) gameOver = true;
displayDirty = true;
playTone(MOVE_SOUND, 50);
}
bool canMove(int newX, int newY) {
for (int r = 0; r < 4; r++) {
byte row = getShapeRow(r);
for (int c = 0; c < 4; c++) {
if (row & (0b10000000 >> c)) {
int x = newX + c;
int y = newY + r;
if (x >= 32 || y < 0 || y >= 8 || (x >= 0 && grid[x][y]))
return false;
}
}
}
return true;
}
byte getShapeRow(int row) {
return shapes[current.shape][current.rotation][row];
}
void checkInput() {
static bool downPressed = false;
static unsigned long downPressTime = 0;
unsigned long currentTime = millis();
// Debounced button reading
byte reading = (digitalRead(BTN_UP) == LOW) ? 1 : 0;
reading |= (digitalRead(BTN_DOWN) == LOW) ? 2 : 0;
reading |= (digitalRead(BTN_LEFT) == LOW) ? 4 : 0;
reading |= (digitalRead(BTN_RIGHT) == LOW) ? 8 : 0;
if (reading != lastButtonState) {
lastDebounceTime = currentTime;
}
if ((currentTime - lastDebounceTime) > DEBOUNCE_DELAY) {
if (reading != buttonState) {
buttonState = reading;
// UP - move up
if (buttonState & 1 && canMove(current.x, current.y - 1)) {
current.y--;
displayDirty = true;
playTone(MOVE_SOUND, 30);
}
else {
downPressed = false;
}
// DOWN - start fast drop
if (buttonState & 2 && canMove(current.x, current.y + 1)) {
current.y++;
displayDirty = true;
} else {
downPressed = false;
}
// LEFT - move left
if (buttonState & 4 && canMove(current.x + 1, current.y)) {
downPressed = true;
downPressTime = currentTime;
current.x++;
displayDirty = true;
playTone(MOVE_SOUND, 30);
}
// RIGHT - rotate
if (buttonState & 8) {
rotate();
displayDirty = true;
playTone(ROTATE_SOUND, 50);
}
}
}
// Handle continuous down movement
if (downPressed && (currentTime - downPressTime) > FAST_DROP_DELAY) {
if (buttonState & 4 && canMove(current.x + 1, current.y))
{
downPressTime = currentTime;
current.x++;
displayDirty = true;
playTone(MOVE_SOUND, 30);
}
}
lastButtonState = reading;
}
void moveRight() {
if (canMove(current.x + 1, current.y)) {
current.x++;
displayDirty = true;
playTone(MOVE_SOUND, 30);
} else {
placeBlock();
checkLines();
spawnBlock();
playTone(DROP_SOUND, 70);
}
}
void placeBlock() {
for (int r = 0; r < 4; r++) {
byte row = getShapeRow(r);
for (int c = 0; c < 4; c++) {
if (row & (0b10000000 >> c)) {
int x = current.x + c;
int y = current.y + r;
if (x >= 0 && x < 32 && y >= 0 && y < 8) {
grid[x][y] = true;
}
}
}
}
displayDirty = true;
}
void rotate() {
int oldRotation = current.rotation;
int newRotation = (current.rotation + 1) % 4;
// Wall kick table for I-piece
int kicks[4][5][2] = {
{{0,0}, {-2,0}, {1,0}, {-2,-1}, {1,2}}, // 0 -> 1
{{0,0}, {-1,0}, {2,0}, {-1,2}, {2,-1}}, // 1 -> 2
{{0,0}, {2,0}, {-1,0}, {2,1}, {-1,-2}}, // 2 -> 3
{{0,0}, {1,0}, {-2,0}, {1,-2}, {-2,1}} // 3 -> 0
};
// Try each kick position
for (int i = 0; i < 5; i++) {
int newX = current.x + kicks[oldRotation][i][0];
int newY = current.y + kicks[oldRotation][i][1];
if (canMove(newX, newY)) {
current.x = newX;
current.y = newY;
current.rotation = newRotation;
return;
}
}
// If no kick works, revert rotation
current.rotation = oldRotation;
}
void checkLines() {
int lines = 0;
for (int x = 31; x >= 0; x--) {
bool full = true;
for (int y = 0; y < 8; y++) {
if (!grid[x][y]) {
full = false;
break;
}
}
if (full) {
lines++;
// Shift all lines to the left
for (int nx = x; nx > 0; nx--) {
for (int y = 0; y < 8; y++) {
grid[nx][y] = grid[nx - 1][y];
}
}
// Clear first column
for (int y = 0; y < 8; y++) grid[0][y] = false;
x++; // Check same column again
}
}
if (lines > 0) {
score += lines;
if (speed > 100) speed -= 20;
showScore = true;
scoreTime = millis();
//showScoreDisplay();
playTone(LINE_SOUND, 200);
}
displayDirty = true;
}
void drawScreen() {
mx.clear();
// Draw grid (horizontal orientation)
for (int x = 0; x < 32; x++) {
for (int y = 0; y < 8; y++) {
if (grid[x][y]) {
// Calculate module and position within module
int module = x / 8;
int moduleCol = x % 8;
mx.setPoint(7 - y, module * 8 + moduleCol, true);
}
}
}
// Draw current block
for (int r = 0; r < 4; r++) {
byte row = getShapeRow(r);
for (int c = 0; c < 4; c++) {
if (row & (0b10000000 >> c)) {
int x = current.x + c;
int y = current.y + r;
if (x >= 0 && x < 32 && y >= 0 && y < 8) {
int module = x / 8;
int moduleCol = x % 8;
mx.setPoint(7 - y, module * 8 + moduleCol, true);
}
}
}
}
mx.update();
}
void displayGameOver() {
mx.clear();
matrix.displayClear();
matrix.setTextAlignment(PA_CENTER);
matrix.displayText("GAME OVER", PA_CENTER, 100, 500, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
while (!matrix.displayAnimate()) delay(50);
matrix.displayClear();
delay(1000);
String scoreText = "Score: " + String(score);
matrix.displayText(scoreText.c_str(), PA_CENTER, 100, 500, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
while (!matrix.displayAnimate()) delay(50);
delay(1000);
resetGame();
}
void resetGame() {
// Clear grid
for (int x = 0; x < 32; x++) {
for (int y = 0; y < 8; y++) {
grid[x][y] = false;
}
}
score = 0;
speed = 500;
gameOver = false;
showScore = false;
spawnBlock();
lastMove = millis();
}
void playTone(int freq, int duration) {
tone(BUZZER, freq, duration);
delay(duration);
noTone(BUZZER);
}
void playTone2(int tone, int duration) {
for (long i = 0; i < duration * 1000L; i += tone * 2) {
digitalWrite(speaker_pin, HIGH);
delayMicroseconds(tone);
digitalWrite(speaker_pin, LOW);
delayMicroseconds(tone);
}
}
void playNote(char note, int duration) {
char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' , 'D', 'E', 'F', 'G', 'J', 'A', 'B'};
int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956, 850, 760, 716, 637, 603, 568, 507 };
for (int i = 0; i < 14; i++) {
if (names[i] == note) {
playTone2(tones[i], duration);
}
}
}
//Play the melody
void melody_play(){
for (int i = 0; i < length; i++)
{
playNote(notes[i], beats[i] * tempo);
}
}
//DIAPLAY INTRO TEXT
void intro_screen(){
matrix.displayText("TETRIS", PA_CENTER, 100, 500, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
while (!matrix.displayAnimate()) delay(50);
}