#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#define TFT_CS 10
#define TFT_DC 8
#define TFT_RST 9
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
#define JOY_X A0
#define JOY_Y A1
#define JOY_BTN 2
#define BUZZER 3
#define BLOCK_SIZE 20
#define BOARD_WIDTH 10
#define BOARD_HEIGHT 12
#define START_X 20
#define START_Y 20
int board[BOARD_HEIGHT][BOARD_WIDTH] = {0};
int oldBoard[BOARD_HEIGHT][BOARD_WIDTH] = {0};
struct Piece {
const int blocks[4][2];
int size;
uint16_t color;
};
Piece tetrominoes[] = {
{{{0,0},{1,0},{0,1},{1,1}}, 2, ILI9341_YELLOW}, // O
{{{0,0},{1,0},{2,0},{3,0}}, 4, ILI9341_CYAN}, // I
{{{0,0},{1,0},{2,0},{2,1}}, 3, ILI9341_BLUE}, // L
{{{0,0},{1,0},{2,0},{0,1}}, 3, ILI9341_ORANGE}, // J
{{{0,0},{1,0},{1,1},{2,1}}, 3, ILI9341_GREEN}, // S
{{{1,0},{2,0},{0,1},{1,1}}, 3, ILI9341_MAGENTA}, // Z
{{{1,0},{0,1},{1,1},{2,1}}, 3, ILI9341_RED}, // T
};
int currentPiece, rotation = 0;
int posX = 3, posY = 0;
unsigned long lastFall = 0;
int fallDelay = 500;
void playTone(int freq, int duration) {
tone(BUZZER, freq, duration);
delay(duration);
noTone(BUZZER);
}
void drawBlock(int x, int y, uint16_t color) {
int px = START_X + x * BLOCK_SIZE;
int py = START_Y + y * BLOCK_SIZE;
tft.fillRect(px, py, BLOCK_SIZE - 2, BLOCK_SIZE - 2, color);
}
void clearPiece(int x, int y, int r) {
Piece piece = tetrominoes[currentPiece];
for (int i = 0; i < 4; i++) {
int px = x + rotateX(piece.blocks[i][0], piece.blocks[i][1], r);
int py = y + rotateY(piece.blocks[i][0], piece.blocks[i][1], r);
if (py >= 0) drawBlock(px, py, ILI9341_BLACK);
}
}
void drawPiece(int x, int y, int r) {
Piece piece = tetrominoes[currentPiece];
for (int i = 0; i < 4; i++) {
int px = x + rotateX(piece.blocks[i][0], piece.blocks[i][1], r);
int py = y + rotateY(piece.blocks[i][0], piece.blocks[i][1], r);
if (py >= 0) drawBlock(px, py, piece.color);
}
}
int rotateX(int x, int y, int r) {
switch (r % 4) {
case 0: return x;
case 1: return y;
case 2: return -x;
case 3: return -y;
}
return x;
}
int rotateY(int x, int y, int r) {
switch (r % 4) {
case 0: return y;
case 1: return -x;
case 2: return -y;
case 3: return x;
}
return y;
}
bool checkCollision(int dx, int dy, int r) {
Piece piece = tetrominoes[currentPiece];
for (int i = 0; i < 4; i++) {
int x = posX + dx + rotateX(piece.blocks[i][0], piece.blocks[i][1], r);
int y = posY + dy + rotateY(piece.blocks[i][0], piece.blocks[i][1], r);
if (x < 0 || x >= BOARD_WIDTH || y >= BOARD_HEIGHT || (y >= 0 && board[y][x]))
return true;
}
return false;
}
void fixPiece() {
Piece piece = tetrominoes[currentPiece];
for (int i = 0; i < 4; i++) {
int x = posX + rotateX(piece.blocks[i][0], piece.blocks[i][1], rotation);
int y = posY + rotateY(piece.blocks[i][0], piece.blocks[i][1], rotation);
if (y >= 0) board[y][x] = piece.color;
}
playTone(100, 100);
}
void clearLines() {
bool cleared = false;
for (int y = BOARD_HEIGHT - 1; y >= 0; y--) {
bool full = true;
for (int x = 0; x < BOARD_WIDTH; x++) {
if (!board[y][x]) {
full = false;
break;
}
}
if (full) {
cleared = true;
for (int yy = y; yy > 0; yy--)
for (int x = 0; x < BOARD_WIDTH; x++)
board[yy][x] = board[yy - 1][x];
for (int x = 0; x < BOARD_WIDTH; x++)
board[0][x] = 0;
y++;
}
}
if (cleared)
playTone(400, 150);
}
void spawnPiece() {
currentPiece = random(0, 7);
posX = 3;
posY = -1;
rotation = 0;
if (checkCollision(0, 1, rotation)) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(60, 120);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(3);
tft.println("GAME OVER");
for (int i = 0; i < 3; i++) playTone(200 - i * 50, 200);
while (1);
}
}
void drawBoard() {
for (int y = 0; y < BOARD_HEIGHT; y++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
if (board[y][x] != oldBoard[y][x]) {
drawBlock(x, y, board[y][x] ? board[y][x] : ILI9341_BLACK);
oldBoard[y][x] = board[y][x];
}
}
}
}
void setup() {
pinMode(JOY_BTN, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
Serial.begin(9600);
randomSeed(analogRead(0));
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK);
spawnPiece();
drawBoard();
}
void handleInput() {
int x = analogRead(JOY_X);
int y = analogRead(JOY_Y);
static unsigned long lastMove = 0;
static bool btnPressed = false;
if (millis() - lastMove > 150) {
clearPiece(posX, posY, rotation);
if (x < 400 && !checkCollision(-1, 0, rotation)) {
posX--;
playTone(700, 40);
} else if (x > 600 && !checkCollision(1, 0, rotation)) {
posX++;
playTone(700, 40);
}
if (y > 600 && !checkCollision(0, 1, rotation)) {
posY++;
playTone(600, 40);
}
drawPiece(posX, posY, rotation);
lastMove = millis();
}
if (digitalRead(JOY_BTN) == LOW && !btnPressed) {
int newRot = (rotation + 1) % 4;
if (!checkCollision(0, 0, newRot)) {
clearPiece(posX, posY, rotation);
rotation = newRot;
playTone(800, 40);
drawPiece(posX, posY, rotation);
}
btnPressed = true;
}
if (digitalRead(JOY_BTN) == HIGH) {
btnPressed = false;
}
}
void loop() {
handleInput();
if (millis() - lastFall > fallDelay) {
if (!checkCollision(0, 1, rotation)) {
clearPiece(posX, posY, rotation);
posY++;
drawPiece(posX, posY, rotation);
} else {
fixPiece();
clearLines();
drawBoard();
spawnPiece();
drawPiece(posX, posY, rotation);
}
lastFall = millis();
}
}