/*
ESP32 Tetris su MAX7219 8x32 verticale + joystick
Librerie richieste:
- MD_MAX72xx (MajicDesigns)
- MD_MAXPanel (MajicDesigns)
*/
#include <MD_MAX72xx.h>
#include <MD_MAXPanel.h>
#include <SPI.h>
// ====== PIN DISPLAY ======
#define DATA_PIN 23 // DIN
#define CLK_PIN 18 // CLK
#define CS_PIN 5 // CS/LOAD
// Tipo di modulo (controlla il tuo hardware!)
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
// 8x32 => 4 moduli 8x8 in cascata (1 colonna x 4 righe)
#define MAX_DEVICES 4
// ====== JOYSTICK ======
#define VRX_PIN 34
#define VRY_PIN 35
#define SW_PIN 27
const int ADC_CENTER = 2048;
const int DEADZONE = 600;
const int TH_LEFT = ADC_CENTER - DEADZONE;
const int TH_RIGHT = ADC_CENTER + DEADZONE;
const int TH_UP = ADC_CENTER - DEADZONE;
const int TH_DOWN = ADC_CENTER + DEADZONE;
// ====== DISPLAY OBJECT ======
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
MD_MAXPanel panel(&mx, 1, 4); // 1 colonna x 4 moduli = 8x32
const int WIDTH = 8;
const int HEIGHT = 32;
// ====== GIOCO ======
bool field[WIDTH][HEIGHT];
const uint16_t PIECES[7][4] = {
{0x0F00, 0x2222, 0x00F0, 0x4444}, // I
{0x8E00, 0x6440, 0x0E20, 0x44C0}, // J
{0x2E00, 0x4460, 0x0E80, 0xC440}, // L
{0x6600, 0x6600, 0x6600, 0x6600}, // O
{0x6C00, 0x4620, 0x06C0, 0x8C40}, // S
{0x4E00, 0x4640, 0x0E40, 0x4C40}, // T
{0xC600, 0x2640, 0x0C60, 0x4C80} // Z
};
int curPiece, curRot, curX, curY;
bool gameOver = false;
unsigned long lastFall = 0;
unsigned long fallInterval = 450;
// ====== FUNZIONI DI SUPPORTO ======
bool getMaskBit(uint16_t mask, int r, int c) {
return (mask >> (15 - (r * 4 + c))) & 1;
}
bool collide(int x, int y, int p, int r) {
uint16_t m = PIECES[p][r];
for (int rr = 0; rr < 4; rr++) {
for (int cc = 0; cc < 4; cc++) {
if (getMaskBit(m, rr, cc)) {
int fx = x + cc;
int fy = y + rr;
if (fx < 0 || fx >= WIDTH || fy < 0 || fy >= HEIGHT) return true;
if (field[fx][fy]) return true;
}
}
}
return false;
}
void lockPiece() {
uint16_t m = PIECES[curPiece][curRot];
for (int rr = 0; rr < 4; rr++) {
for (int cc = 0; cc < 4; cc++) {
if (getMaskBit(m, rr, cc))
field[curX + cc][curY + rr] = true;
}
}
}
void clearLines() {
for (int y = 0; y < HEIGHT; y++) {
bool full = true;
for (int x = 0; x < WIDTH; x++)
if (!field[x][y]) full = false;
if (full) {
for (int yy = y; yy > 0; yy--)
for (int x = 0; x < WIDTH; x++)
field[x][yy] = field[x][yy - 1];
for (int x = 0; x < WIDTH; x++) field[x][0] = false;
if (fallInterval > 100) fallInterval -= 15;
}
}
}
void spawnPiece() {
curPiece = random(7);
curRot = 0;
curX = (WIDTH / 2) - 2;
curY = 0;
if (collide(curX, curY, curPiece, curRot))
gameOver = true;
}
void rotatePiece() {
int newRot = (curRot + 1) & 3;
if (!collide(curX, curY, curPiece, newRot))
curRot = newRot;
}
void moveLR(int dx) {
if (!collide(curX + dx, curY, curPiece, curRot))
curX += dx;
}
void softDrop() {
if (!collide(curX, curY + 1, curPiece, curRot))
curY++;
else {
lockPiece();
clearLines();
spawnPiece();
}
}
// ====== INPUT ======
void handleInput() {
static bool prevBtn = true;
int ax = analogRead(VRX_PIN);
int ay = analogRead(VRY_PIN);
bool btn = digitalRead(SW_PIN);
if (btn == LOW && prevBtn == HIGH) rotatePiece();
prevBtn = btn;
if (ax < TH_LEFT) moveLR(-1);
else if (ax > TH_RIGHT) moveLR(+1);
if (ay > TH_DOWN) softDrop();
}
// ====== DISPLAY ======
void drawPixel(int x, int y, bool on) {
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return;
panel.setPixel(x, y, on);
}
void render() {
panel.fillScreen(false);
for (int y = 0; y < HEIGHT; y++)
for (int x = 0; x < WIDTH; x++)
if (field[x][y]) drawPixel(x, y, true);
uint16_t m = PIECES[curPiece][curRot];
for (int rr = 0; rr < 4; rr++)
for (int cc = 0; cc < 4; cc++)
if (getMaskBit(m, rr, cc))
drawPixel(curX + cc, curY + rr, true);
panel.update();
}
// ====== SETUP ======
void setup() {
Serial.begin(115200);
pinMode(SW_PIN, INPUT_PULLUP);
analogReadResolution(12);
mx.begin();
panel.begin();
panel.setRotation(1); // 0,1,2,3 → regola per ottenere la verticale corretta
panel.setIntensity(3); // luminosità 0..15
panel.fillScreen(false);
panel.update();
for (int y = 0; y < HEIGHT; y++)
for (int x = 0; x < WIDTH; x++)
field[x][y] = false;
randomSeed(analogRead(32) ^ micros());
spawnPiece();
lastFall = millis();
}
// ====== LOOP ======
void loop() {
if (gameOver) {
panel.fillScreen(true);
panel.update();
delay(200);
panel.fillScreen(false);
panel.update();
delay(200);
if (digitalRead(SW_PIN) == LOW) {
memset(field, 0, sizeof(field));
gameOver = false;
fallInterval = 450;
spawnPiece();
}
return;
}
handleInput();
if (millis() - lastFall >= fallInterval) {
softDrop();
lastFall = millis();
}
render();
delay(50);
}