#include <MD_MAX72xx.h>
#include <SPI.h>
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 9
MD_MAX72XX mx =
MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
#define BTN_LEFT 2
#define BTN_RIGHT 3
#define BTN_ROTATE 4
#define BUZZER_PIN 6
#define BOARD_W 8
#define BOARD_H 32
bool board[BOARD_H][BOARD_W];
unsigned long lastFallTime = 0;
const unsigned long NORMAL_FALL = 700;
const unsigned long SOFT_DROP_FALL = 80;
#define LONG_PRESS_TIME 400
unsigned long score = 0;
unsigned int lines = 0;
unsigned int level = 1;
unsigned long highScore = 0;
bool gameOver = false;
bool gameStarted = false;
struct Point {
int x;
int y;
};
struct Tetromino {
Point block[4];
uint8_t color;
};
Tetromino pieces[7] = {
{
{{0,0}, {1,0}, {2,0}, {3,0}},
0
},
{
{{0,0}, {1,0}, {0,1}, {1,1}},
1
},
{
{{1,0}, {0,1}, {1,1}, {2,1}},
2
},
{
{{0,0}, {0,1}, {0,2}, {1,2}},
3
},
{
{{1,0}, {1,1}, {1,2}, {0,2}},
4
},
{
{{1,0}, {2,0}, {0,1}, {1,1}},
5
},
{
{{0,0}, {1,0}, {1,1}, {2,1}},
6
}
};
int currentPiece;
int currentX;
int currentY;
int rotationState = 0;
bool lastLeftState = HIGH;
bool lastRightState = HIGH;
bool lastRotateState = HIGH;
unsigned long rotatePressTime = 0;
bool softDrop = false;
bool rotateHandled = false;
void setPixel(int x, int y, bool state) {
if (x < 0 || x >= BOARD_W ||
y < 0 || y >= BOARD_H) {
return;
}
int physicalColumn = (BOARD_H - 1) - y;
int physicalRow = x;
mx.setPoint(
physicalRow,
physicalColumn,
state
);
}
void clearDisplay() {
mx.clear();
}
void drawBoard() {
clearDisplay();
for (int y = 0; y < BOARD_H; y++) {
for (int x = 0; x < BOARD_W; x++) {
if (board[y][x]) {
setPixel(
x,
y,
true
);
}
}
}
drawCurrentPiece();
}
Point getRotatedPoint(
Point p,
int rotation
) {
Point r;
switch (rotation % 4) {
case 0:
r.x = p.x;
r.y = p.y;
break;
case 1:
r.x = -p.y;
r.y = p.x;
break;
case 2:
r.x = -p.x;
r.y = -p.y;
break;
case 3:
r.x = p.y;
r.y = -p.x;
break;
}
return r;
}
bool checkCollision(
int pieceX,
int pieceY,
int rotation
) {
for (int i = 0; i < 4; i++) {
Point p = getRotatedPoint(
pieces[currentPiece].block[i],
rotation
);
int x = pieceX + p.x;
int y = pieceY + p.y;
if (x < 0 || x >= BOARD_W) {
return true;
}
if (y >= BOARD_H) {
return true;
}
if (y >= 0 && board[y][x]) {
return true;
}
}
return false;
}
void drawCurrentPiece() {
for (int i = 0; i < 4; i++) {
Point p = getRotatedPoint(
pieces[currentPiece].block[i],
rotationState
);
int x = currentX + p.x;
int y = currentY + p.y;
if (x >= 0 && x < BOARD_W &&
y >= 0 && y < BOARD_H) {
setPixel(
x,
y,
true
);
}
}
}
void spawnPiece() {
currentPiece = random(0, 7);
rotationState = 0;
currentX = 3;
currentY = 0;
if (currentPiece == 0) {
currentX = 2;
}
if (checkCollision(
currentX,
currentY,
rotationState)) {
gameOver = true;
if (score > highScore) {
highScore = score;
}
soundGameOver();
}
}
void lockPiece() {
for (int i = 0; i < 4; i++) {
Point p = getRotatedPoint(
pieces[currentPiece].block[i],
rotationState
);
int x = currentX + p.x;
int y = currentY + p.y;
if (x >= 0 && x < BOARD_W &&
y >= 0 && y < BOARD_H) {
board[y][x] = true;
}
}
soundLock();
clearLines();
spawnPiece();
lastFallTime = millis();
}
void clearLines() {
int cleared = 0;
for (int y = BOARD_H - 1; y >= 0; y--) {
bool full = true;
for (int x = 0; x < BOARD_W; x++) {
if (!board[y][x]) {
full = false;
break;
}
}
if (full) {
cleared++;
for (int yy = y; yy > 0; yy--) {
for (int x = 0; x < BOARD_W; x++) {
board[yy][x] =
board[yy - 1][x];
}
}
for (int x = 0; x < BOARD_W; x++) {
board[0][x] = false;
}
y++;
}
}
if (cleared > 0) {
lines += cleared;
switch (cleared) {
case 1:
score += 100 * level;
break;
case 2:
score += 300 * level;
break;
case 3:
score += 500 * level;
break;
case 4:
score += 800 * level;
break;
}
level =
(lines / 10) + 1;
soundLineClear(cleared);
}
}
void moveLeft() {
if (gameOver) {
return;
}
if (!checkCollision(
currentX - 1,
currentY,
rotationState)) {
currentX--;
drawBoard();
soundLeft();
}
}
void moveRight() {
if (gameOver) {
return;
}
if (!checkCollision(
currentX + 1,
currentY,
rotationState)) {
currentX++;
drawBoard();
soundRight();
}
}
void rotatePiece() {
if (gameOver) {
return;
}
int newRotation =
(rotationState + 1) % 4;
if (!checkCollision(
currentX,
currentY,
newRotation)) {
rotationState =
newRotation;
drawBoard();
return;
}
if (!checkCollision(
currentX - 1,
currentY,
newRotation)) {
currentX--;
rotationState =
newRotation;
drawBoard();
return;
}
if (!checkCollision(
currentX + 1,
currentY,
newRotation)) {
currentX++;
rotationState =
newRotation;
drawBoard();
return;
}
}
bool moveDown() {
if (gameOver) {
return false;
}
if (!checkCollision(
currentX,
currentY + 1,
rotationState)) {
currentY++;
return true;
}
lockPiece();
return false;
}
void handleSoftDrop() {
if (gameOver) {
return;
}
static unsigned long lastSoftDrop = 0;
if (millis() - lastSoftDrop >=
SOFT_DROP_FALL) {
lastSoftDrop = millis();
if (moveDown()) {
score += 1;
}
drawBoard();
}
}
void handleNormalFall() {
if (gameOver) {
return;
}
unsigned long fallSpeed;
fallSpeed =
NORMAL_FALL -
((level - 1) * 50);
if (fallSpeed < 100) {
fallSpeed = 100;
}
if (millis() - lastFallTime >=
fallSpeed) {
lastFallTime = millis();
moveDown();
drawBoard();
}
}
void handleButtons() {
bool leftState =
digitalRead(BTN_LEFT);
bool rightState =
digitalRead(BTN_RIGHT);
bool rotateState =
digitalRead(BTN_ROTATE);
if (leftState == LOW &&
lastLeftState == HIGH) {
moveLeft();
}
if (rightState == LOW &&
lastRightState == HIGH) {
moveRight();
}
if (rotateState == LOW &&
lastRotateState == HIGH) {
rotatePressTime = millis();
rotateHandled = false;
}
if (rotateState == LOW) {
if (!rotateHandled &&
millis() - rotatePressTime >=
LONG_PRESS_TIME) {
rotateHandled = true;
softDrop = true;
}
}
if (rotateState == HIGH &&
lastRotateState == LOW) {
unsigned long pressDuration =
millis() - rotatePressTime;
if (pressDuration <
LONG_PRESS_TIME &&
!rotateHandled) {
rotatePiece();
soundRotate();
}
softDrop = false;
}
lastLeftState =
leftState;
lastRightState =
rightState;
lastRotateState =
rotateState;
}
void playStartMusic() {
clearDisplay();
for (int y = 0; y < BOARD_H; y++) {
for (int x = 0; x < BOARD_W; x++) {
setPixel(
x,
y,
true
);
}
if (y == 0) {
tone(BUZZER_PIN, 523, 100);
}
if (y == 4) {
tone(BUZZER_PIN, 659, 100);
}
if (y == 8) {
tone(BUZZER_PIN, 784, 100);
}
if (y == 12) {
tone(BUZZER_PIN, 1047, 150);
}
if (y == 18) {
tone(BUZZER_PIN, 784, 100);
}
if (y == 22) {
tone(BUZZER_PIN, 1047, 100);
}
if (y == 26) {
tone(BUZZER_PIN, 1319, 180);
}
delay(18);
}
delay(100);
clearDisplay();
tone(BUZZER_PIN, 784, 120);
delay(150);
tone(BUZZER_PIN, 988, 120);
delay(150);
tone(BUZZER_PIN, 1175, 120);
delay(150);
tone(BUZZER_PIN, 1568, 250);
delay(300);
clearDisplay();
noTone(BUZZER_PIN);
}
void soundLeft() {
tone(
BUZZER_PIN,
650,
70
);
}
void soundRight() {
tone(
BUZZER_PIN,
850,
70
);
}
void soundLock() {
tone(BUZZER_PIN, 450, 120);
delay(130);
tone(BUZZER_PIN, 300, 100);
}
void soundRotate() {
tone(
BUZZER_PIN,
1200,
100
);
}
void soundLineClear(int count) {
if (count == 1) {
tone(
BUZZER_PIN,
1000,
120
);
delay(150);
tone(
BUZZER_PIN,
1400,
160
);
}
else if (count == 2) {
tone(
BUZZER_PIN,
1000,
100
);
delay(120);
tone(
BUZZER_PIN,
1300,
100
);
delay(120);
tone(
BUZZER_PIN,
1700,
180
);
}
else {
tone(
BUZZER_PIN,
1200,
100
);
delay(120);
tone(
BUZZER_PIN,
1500,
100
);
delay(120);
tone(
BUZZER_PIN,
1800,
100
);
delay(120);
tone(
BUZZER_PIN,
2200,
250
);
}
}
void soundGameOver() {
tone(
BUZZER_PIN,
700,
180
);
delay(210);
tone(
BUZZER_PIN,
500,
180
);
delay(210);
tone(
BUZZER_PIN,
350,
220
);
delay(250);
tone(
BUZZER_PIN,
220,
450
);
delay(500);
noTone(BUZZER_PIN);
}
void gameOverAnimation() {
for (int i = 0; i < 4; i++) {
drawBoard();
delay(250);
clearDisplay();
delay(250);
}
}
void resetGame() {
for (int y = 0; y < BOARD_H; y++) {
for (int x = 0; x < BOARD_W; x++) {
board[y][x] = false;
}
}
score = 0;
lines = 0;
level = 1;
gameOver = false;
spawnPiece();
lastFallTime =
millis();
drawBoard();
}
void setup() {
Serial.begin(115200);
pinMode(
BTN_LEFT,
INPUT_PULLUP
);
pinMode(
BTN_RIGHT,
INPUT_PULLUP
);
pinMode(
BTN_ROTATE,
INPUT_PULLUP
);
pinMode(
BUZZER_PIN,
OUTPUT
);
mx.begin();
mx.control(
MD_MAX72XX::INTENSITY,
5
);
mx.control(
MD_MAX72XX::UPDATE,
true
);
mx.clear();
randomSeed(
analogRead(A0)
);
playStartMusic();
resetGame();
gameStarted = true;
}
void loop() {
if (!gameStarted) {
return;
}
if (gameOver) {
gameOverAnimation();
delay(500);
resetGame();
return;
}
handleButtons();
if (softDrop) {
handleSoftDrop();
}
else {
handleNormalFall();
}
}