/*
Quick and dirty Tetris implementation.
Non-optimized, most likely horrible code.
*/
#include <Arduino.h>
#include <MD_MAX72xx.h>
#include <initializer_list>
//GENERIC_HW
//ICSTATION_HW
// Define hardware type and connections for ESP32-C3
//#define HARDWARE_TYPE MD_MAX72XX::FC16_HW // Original, working (but not transform())
#define HARDWARE_TYPE MD_MAX72XX::ICSTATION_HW
#define MAX_DEVICES 4 // Number of 8x8 matrices in your module
#define CLK_PIN 4 // Clock pin (SCK) - GPIO4
#define DATA_PIN 6 // Data pin (MOSI) - GPIO6
#define CS_PIN 7 // Chip Select pin (SS) - GPIO7
#define PIN_B1 19
#define PIN_B2 18
#define PIN_UP 10
#define PIN_DOWN 9
#define PIN_LEFT 1
#define PIN_RIGHT 8
// Create MD_MAX72XX object
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
unsigned int count = 1;
bool LEDState = LOW;
static unsigned long lastUpdate = 0;
unsigned long lastTick = 0;
unsigned long tickInterval = 1000;
// Debug stuff
bool dbrd = false; // Draw background
//bool b1_down = false;
const int debounceDelay = 50; // ms
struct Button {
uint8_t pin;
bool lastStableState;
bool lastReading;
unsigned long lastDebounceTime;
bool pressedEvent;
};
// struct Block {
// uint8_t size;
// unsigned char Data[]
// }
// struct Block {
// std::vector<char> Data;
// Block(size_t n) : Data(n) {}
// };
struct Block {
uint8_t size;
uint8_t rows[4];
Block() : size(0), rows{0, 0, 0, 0} {}
Block(std::initializer_list<uint8_t> list) : size(0), rows{0, 0, 0, 0} {
for (uint8_t value : list) {
if (size >= 4) {
break;
}
rows[size++] = value;
}
}
} currentBlock;
Button btn1;
Button btn2;
Button btn_u;
Button btn_d;
Button btn_l;
Button btn_r;
int global_x;
int global_y;
int old_x;
int old_y;
unsigned char board[33] {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000010,
0b00000010,
0b10100010,
0b10100010,
0b11100010,
0b11110010,
0b11111010,
0b11111010,
0b11111010,
0b11111010,
0b11111011,
0b11111011,
0b11111011,
0b11111011,
0b11111011,
0b11111011,
0b11111011,
0b11111011,
0b11111011,
0b11111011,
0b11111011,
0b11111111
};
/*
unsigned char board[128] {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00111100,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b11111111
};
*/
Block block_L {
0b1000,
0b1100,
0b1110,
0b1111
};
Block block_fat {
0b1111,
0b1111,
0b1111,
0b1111
};
//Block blockSet[7]{};
Block block_I {
0b0100,
0b0100,
0b0100,
0b0100,
};
Block block_L1 {
0b0100,
0b0100,
0b1100,
0b0000
};
Block block_L2 {
0b0100,
0b0100,
0b0110,
0b0000
};
Block block_S {
0b0000,
0b0110,
0b0110,
0b0000
};
Block block_T {
0b0000,
0b1110,
0b0100,
0b0000
};
Block block_Z1 {
0b0000,
0b0110,
0b1100,
0b0000
};
Block block_Z2 {
0b0000,
0b1100,
0b0110,
0b0000
};
const Block blockSet[] { block_I, block_L1, block_L2, block_S, block_T, block_Z1, block_Z2 };
Block block_tmp {
0b1000,
0b1100,
0b1110,
0b1111
};
unsigned char block_ground[8] {
0b10000000,
0b10000000,
0b11100000,
0b11100000,
0b11100000,
0b11100001,
0b11100011,
0b11100111
};
unsigned char char_symbol[8] {
0b10000001,
0b01110000,
0b01111100,
0b01111111,
0b00000001,
0b11111111,
0b00000000,
0b10000001
};
unsigned char char_symbol2[8] {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000
};
unsigned char char_A[8] {
0B00000000,
0B00001000,
0B00010100,
0B00100010,
0B01000001,
0B01111111,
0B01000001,
0B01000001
};
unsigned char char_B[8] {
0B00000000,
0B00001000,
0B00010100,
0B00100010,
0B01000001,
0B01111111,
0B01000001,
0B01000001
};
unsigned char char_F[8] = {
0b01000000,
0b01000000,
0b01001000,
0b01001000,
0b01001000,
0b01111111,
0b00000000,
0b00000000
};
unsigned char char_S[8] {
0B00000000,
0B00111110,
0B01000001,
0B01000000,
0B00111110,
0B00000001,
0B01000001,
0B00111110
};
bool CheckCollision(const Block& block, int dx, int dy) {
const int x = global_x + dx;
const int y = global_y + dy;
bool P = true;
for (int section = 0; section < block.size; section++) {
const uint8_t rawSlice = block.rows[section];
const uint8_t shiftedSlice = shiftByte(rawSlice, x);
// Check horizontal walls using the target position.
if (dx > 0 && (shiftedSlice & 0b10000000)) {
if (P) Serial.println("PRE Collision with left wall");
return true;
}
if (dx < 0 && (shiftedSlice & 0b00000001)) {
if (P) Serial.println("PRE Collision with right wall");
return true;
}
const int backgroundIndex = y + section;
// Allow spawn/rotation while partially above top border.
if (backgroundIndex < 0) {
continue;
}
// Bottom overflow is always a collision.
if (backgroundIndex >= 32) {
return true;
}
const uint8_t backgroundSlice = board[backgroundIndex];
if (backgroundSlice & shiftedSlice) {
if (P) Serial.println("Background collision");
return true;
}
}
return false;
}
// =========================================================
// CheckCollision
// =========================================================
bool CheckCollision_OLD(Block block, int dx, int dy) {
bool P = false;
bool hit = false;
uint8_t blockSlice;
int x = global_x + dx;
if (P) Serial.print("global_y: ");
if (P) Serial.print(global_y);
if (P) Serial.print(" global_x: ");
if (P) Serial.print(global_x);
if (P) Serial.print(" dx: ");
if (P) Serial.print(dx);
if (P) Serial.print(" x: ");
if (P) Serial.print(x);
if (P) Serial.println();
if (P) {
if (dy != 0)
Serial.println("blockSlice backgroundSlice bS&bG global_x");
if (dx != 0)
Serial.println("blockSlice backgrnd b&b01 dx");
}
// Compare all slices of the current block to the background array
for (int blockSection=0; blockSection<block.size; blockSection++) {
// Wall collision check
// This MUST be possible to reduce
// Wait, aren't we doing this calc further down?
if (dx >= 0) {
if (global_x >= 0) {
blockSlice = block.rows[blockSection] << global_x;
} else {
blockSlice = block.rows[blockSection] >> -global_x;
}
} else {
if (global_x >= 0) {
blockSlice = block.rows[blockSection] << global_x;
} else {
blockSlice = block.rows[blockSection] >> (-global_x);
}
}
if (dx > 0) {
if (blockSlice & 0b10000000) {
if (P) Serial.println("PRE Collision with left wall");
return true; // Collision with left wall
}
}
if (dx < 0) {
if (blockSlice & 0b00000001) {
Serial.println("PRE Collision with right wall");
return true;
}
}
// Shift the block bits to reflect position in display, add direction
//blockSlice = block.Data[blockSection] << (global_x + dx); // Original
if (dx >= 0) {
if (x >= 0) {
blockSlice = block.rows[blockSection] << x;
} else {
blockSlice = block.rows[blockSection] >> -x;
}
} else {
if (x >= 0) {
blockSlice = block.rows[blockSection] << x;
} else {
blockSlice = block.rows[blockSection] >> (-x);
}
}
// Get the background slice, add direction
uint8_t backgroundIndex = global_y + blockSection + dy;
uint8_t backgroundSlice = board[backgroundIndex];
if (dy != 0) {
if (P) pb(blockSlice);
if (P) Serial.print(" ");
if (P) pb(backgroundSlice);
if (P) Serial.print(" ");
if (P) Serial.print(backgroundSlice & blockSlice);
if (P) Serial.print(" ");
if (P) Serial.print(global_x);
if (P) Serial.println();
}
if (dx != 0) {
if (P) pb(blockSlice);
if (P) Serial.print(" ");
if (P) pb(backgroundSlice);
if (P) Serial.print(" ");
if (P) Serial.print(blockSlice & 0b10000000);
if (P) Serial.print(" ");
if (P) Serial.print(dx);
if (P) Serial.println();
}
// AND slice to background
if (backgroundSlice & blockSlice) {
Serial.println("Background collision");
return true;
}
// Out of bounds checks
//rotate90CW(currentBlock, block_tmp);
//blockSlice = block_tmp.Data[blockSection] << (global_x + dx);
//blockSlice = block.Data[blockSection] << global_x; // Original
// Old position of this code block
// if (dx >= 0) {
// if (global_x >= 0) {
// blockSlice = block.Data[blockSection] << global_x;
// } else {
// blockSlice = block.Data[blockSection] >> -global_x;
// }
// } else {
// if (global_x >= 0) {
// blockSlice = block.Data[blockSection] << global_x;
// } else {
// blockSlice = block.Data[blockSection] >> (-global_x);
// }
// }
// This should be checked first
// if (dx > 0) {
// if (blockSlice & 0b10000000) {
// if (P) Serial.println("Collision with left wall");
// return true; // Collision with left wall
// }
// }
// if (dx < 0) {
// if (blockSlice & 0b00000001) {
// Serial.println("Collision with right wall");
// return true;
// }
// }
}
return false;
}
uint8_t reverseBits8(uint8_t b) {
uint8_t r = 0;
for (uint8_t i = 0; i < 8; i++) {
r = (r << 1) | (b & 1);
b >>= 1; // logical shift because b is unsigned
}
return r;
}
// I stole these from StackOverflow
unsigned char reverse(unsigned char b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
byte reverseBits(byte b) {
byte r = 0;
for (int i = 0; i < 8; i++) {
r <<= 1; // Make room for next bit
r |= (b & 1); // Copy lowest bit
b >>= 1; // Move next bit into position
}
return r;
}
void DrawBoard() {
unsigned char p1;
for (char yy=0; yy<32; yy++) {
p1 = board[yy];
for (char xx=0; xx<8; xx++) {
if (BitIsSet(p1, xx))
mx.setPoint(xx, yy, true);
else
mx.setPoint(xx, yy, false);
}
}
}
void EraseBlock(Block block, char x, char y) {
for (char j=0; j<block.size; j++) {
for (char i=0; i<block.size; i++) {
mx.setPoint(x+i, y+j, false);
}
}
}
void PaintBlock(const Block& block, int8_t x, int8_t y, bool state) {
const uint8_t blockSize = block.size;
for (uint8_t sliceIndex = 0; sliceIndex < blockSize; sliceIndex++) {
const uint8_t blockSlice = block.rows[sliceIndex];
for (uint8_t slice_y = 0; slice_y < blockSize; slice_y++) {
if (!BitIsSet(blockSlice, slice_y)) {
continue;
}
const int16_t screen_x = x + slice_y;
const int16_t screen_y = y + sliceIndex;
if (screen_x < 0 || screen_x > 7 || screen_y < 0 || screen_y > 31) {
continue;
}
mx.setPoint(screen_x, screen_y, state);
}
}
}
// ===========================================================================
// ===========================================================================
// ===========================================================================
// ===========================================================================
// ===========================================================================
void DrawBlock(const Block& block, int8_t dx=0, int8_t dy=0) {
bool P = false;
int screen_x;
int screen_y;
int new_x = global_x + dx;
int new_y = global_y + dy;
// Block has moved, erase old block
if (dx != 0 || dy != 0) {
//if (dy != 0 || dx != 0) {
if (P) Serial.print("Erasing old block. global_x: ");
if (P) Serial.print(global_x);
if (P) Serial.print(" global_y: ");
if (P) Serial.print(global_y);
if (P) Serial.print(" old_x: ");
if (P) Serial.print(old_x);
if (P) Serial.print(" old_y: ");
if (P) Serial.print(old_y);
if (P) Serial.print(" new_x: ");
if (P) Serial.print(new_x);
if (P) Serial.print(" new_y: ");
if (P) Serial.print(new_y);
if (P) Serial.println();
//delay(2000);
PaintBlock(block, global_x, global_y, false);
}
//bool nonMove = (dx == 0 && dy == 0);
//if (P) Serial.println("blockSlice");
//if (true || nonMove) {
if (P) Serial.print("Drawing new block. global_x: ");
if (P) Serial.print(global_x);
if (P) Serial.print(" global_y: ");
if (P) Serial.print(global_y);
if (P) Serial.print(" old_x: ");
if (P) Serial.print(old_x);
if (P) Serial.print(" old_y: ");
if (P) Serial.print(old_y);
if (P) Serial.print(" new_x: ");
if (P) Serial.print(new_x);
if (P) Serial.print(" new_y: ");
if (P) Serial.print(new_y);
if (P) Serial.println();
//delay(2000);
PaintBlock(block, new_x, new_y, true);
old_x = global_x;
old_y = global_y;
global_x = new_x;
global_y = new_y;
if (P) Serial.print("Done. global_x: ");
if (P) Serial.print(global_x);
if (P) Serial.print(" global_y: ");
if (P) Serial.print(global_y);
if (P) Serial.print(" old_x: ");
if (P) Serial.print(old_x);
if (P) Serial.print(" old_y: ");
if (P) Serial.print(old_y);
if (P) Serial.println();
}
// =========================================================
// DrawBlock
// =========================================================
void DrawBlock_OLD(Block block, uint8_t x, int8_t y, int8_t dy = 0, bool start = false) {
uint8_t blockSize = block.size;
bool P = false;
// Range of rows to redraw
int startRow = y;
int endRow = y + blockSize;
// Extend range to erase trailing row from vertical movement
if (dy > 0) {
startRow = y - dy; // block moved down, erase old top row
} else if (dy < 0) {
endRow = y + blockSize - dy; // block moved up, erase lower
}
if (P) Serial.println("blockSlice boardSlice mask global_x");
for (int row = startRow; row < endRow; row++) {
uint8_t boardSlice;
uint8_t mask;
if (P) Serial.print("startRow: ");
if (P) Serial.print(startRow);
if (P) Serial.print(" endRow: ");
if (P) Serial.print(endRow);
if (P) Serial.print(" row: ");
if (P) Serial.print(row);
if (P) Serial.println();
//if (P) delay(1000);
if (row >= (int)y && row < (int)(y + blockSize)) { // Original
//if ((row >=0 ) && row >= (int)y && row < (int)(y + blockSize)) {
// Skip out of bound rows
if (row < 0)
continue;
boardSlice = board[row];
// Check background overlap
// uint8_t blockSlice = block.Data[row - y] << x; // Original
uint8_t blockSlice;
//blockSlice = block.Data[row - y] << x; // Original
if (global_x >= 0) {
blockSlice = block.rows[row - y] << global_x;
} else {
blockSlice = block.rows[row - y] >> -global_x;
}
//uint8_t blockSlice = block.Data[row - y];
mask = boardSlice | blockSlice;
if (P) pb(blockSlice);
if (P) Serial.print(" ");
if (P) pb(boardSlice);
if (P) Serial.print(" ");
if (P) pb(mask);
if (P) Serial.print(" ");
if (P) Serial.print(global_x);
if (P) Serial.println();
} else {
// mask from background
if (P) pb(0);
if (P) Serial.print(" ");
if (P) pb(boardSlice);
if (P) Serial.print(" ");
if (P) pb(mask);
if (P) Serial.print(" ");
if (P) Serial.print(global_x);
if (P) Serial.println(" ###");
mask = boardSlice;
}
for (byte i = 0; i < 8; i++) {
mx.setPoint(i, row, BitIsSet(mask, i));
}
}
}
// ===========================================================
// checkForLineRemoval
// ===========================================================
void checkForLineRemoval() {
bool P = false;
uint8_t index = 0;
// Copy board to temporary buffer
unsigned char tempBoard[32];
memcpy(tempBoard, board, 32);
// Merge current block into tempBoard
for (int section = 0; section < currentBlock.size; section++) {
uint8_t blockSlice = shiftByte(currentBlock.rows[section], global_x);
uint8_t row = global_y + section;
tempBoard[row] |= blockSlice;
if (P) {
Serial.print("Row "); Serial.print(row);
Serial.print(" merge: ");
pb(tempBoard[row]);
Serial.println();
}
}
// Find filled rows
uint8_t removedRows[4];
for (uint8_t row = 0; row < 32; row++) {
if (tempBoard[row] == 0xFF) {
removedRows[index++] = row;
}
}
if (P) {
Serial.print("Removed rows: ");
Serial.println(index);
}
// Blink and remove each completed row
for (uint8_t i = 0; i < index; i++) {
uint8_t row = removedRows[i];
// Blink the line 3 times
for (int j = 0; j < 3; j++) {
for (int col = 0; col < 8; col++) mx.setPoint(col, row, false);
delay(100);
for (int col = 0; col < 8; col++) mx.setPoint(col, row, true);
delay(100);
}
// Shift board down from that row
for (int r = row; r > 0; r--) {
tempBoard[r] = tempBoard[r - 1];
}
tempBoard[0] = 0;
}
// Write final tempBoard back to board
memcpy(board, tempBoard, 32);
DrawBoard();
SpawnBlock();
}
void checkForLineRemoval_OLD() {
bool P = false;
uint8_t index = 0;
// We have to make a copy of the board array and then overlay block on it at its current_x and current_y
// so we can see if lines have been completed with the block in place
unsigned char tempBoard[32];
for (int i = 0; i < 32; i++) {
tempBoard[i] = board[i];
}
if (P) Serial.println("Board copied to tempBoard");
// These can hopefully be removed later
uint8_t boardSlice;
uint8_t mergeSlice;
if (P) Serial.println("boardSlice blockSlice mergeSlice");
// Copy block to tempBoard
for (int blockSection=0; blockSection<currentBlock.size; blockSection++) {
uint8_t blockSlice = currentBlock.rows[blockSection] << global_x; // Original
if (global_x >= 0) {
blockSlice = currentBlock.rows[blockSection] << global_x;
} else {
blockSlice = currentBlock.rows[blockSection] >> -global_x;
}
uint8_t backgroundIndex = global_y + blockSection;
//tempBoard[backgroundIndex] |= blockSlice; // Original
// Dissecting original
boardSlice = tempBoard[backgroundIndex];
mergeSlice = boardSlice | blockSlice;
if (P) pb(boardSlice);
if (P) Serial.print(" ");
if (P) pb(blockSlice);
if (P) Serial.print(" ");
if (P) pb(mergeSlice);
if (P) Serial.println();
tempBoard[backgroundIndex] = mergeSlice;
}
if (P) Serial.println("Block placed in tempBoard");
if (P) {
Serial.println("tempBoard with block in place:");
for (int q=10; q<18; q++) {
if (P) pb(tempBoard[q]);
Serial.println();
}
}
// Check for complete lines and remove them
uint8_t removedRows[4];
for (int row = 0; row < 32; row++) {
if (tempBoard[row] == 0xFF) { // Line is complete
// Remove line and shift everything above down
//for (int r = row; r > 0; r--) {
// board[r] = board[r - 1];
//}
//board[0] = 0; // Clear top line
//DrawBoard(); // Redraw the board after line removal
removedRows[index++] = row;
}
}
if (P) {
Serial.print("Counted removed rows: ");
// for (int pp=0; pp<4; pp++) {
// Serial.print(pp); //removedRows[pp]
// Serial.print(", ");
// }
Serial.println(index);
}
// Blink removed lines for visual feedback
for (int i = 0; i < index; i++) {
uint8_t row = removedRows[i];
if (P) Serial.print("Blinking: ");
if (P) Serial.println(removedRows[i]);
for (int j = 0; j < 3; j++) { // Blink 3 times
for (int col = 0; col < 8; col++) {
mx.setPoint(col, row, false); // Turn off line
}
delay(100);
for (int col = 0; col < 8; col++) {
mx.setPoint(col, row, true); // Turn on line
}
delay(100);
if (P) Serial.print("Blinked: ");
if (P) pb(row);
}
// After blinking, remove the line and shift down
for (int r = row; r > 0; r--) {
tempBoard[r] = tempBoard[r - 1];
}
tempBoard[0] = 0; // Clear top line
if (P) Serial.println("Rows removed");
}
// Copy tempBoard to board for next iteration (if multiple lines removed)
for (int i = 0; i < 32; i++) {
board[i] = tempBoard[i];
}
if (P) Serial.println("tempBoard copied to board");
DrawBoard(); // Redraw the board after line removal
if (P) Serial.println("Board drawn");
// global_x = 2;
// global_y = 0;
// uint8_t blockIndex = random(7);
// currentBlock = blockSet[blockIndex];
// DrawBlock(currentBlock, global_x, global_y);
// Serial.println("Spawned new block");
SpawnBlock();
}
void PrintBlockType(uint8_t index) {
char type[3];
switch(index) {
case 0:
type[0] = 'I';
break;
case 1:
type[0] = 'L';
type[1] = '1';
break;
case 2:
type[0] = 'L';
type[1] = '2';
break;
case 3:
type[0] = 'S';
break;
case 4:
type[0] = 'T';
break;
case 5:
type[0] = 'Z';
type[1] = '1';
break;
case 6:
type[0] = 'Z';
type[1] = '2';
break;
}
Serial.print(type[0]);
Serial.println(type[1]);
}
// Spawn new block
void SpawnBlock() {
bool P = true;
global_x = 2;
uint8_t blockIndex = random(7);
// Debugging
//blockIndex = 5;
if (P) Serial.print("Spawn, index: ");
if (P) Serial.println(blockIndex);
//if (P) delay(2000);
//uint8_t blockIndex = 5;
currentBlock = blockSet[blockIndex];
// Start position the lazy way (some blocks have an empty first row)
if (currentBlock.rows[0] == 0)
global_y = -1;
else
global_y = 0;
old_x = global_x;
old_y = global_y;
//if (CheckCollision(currentBlock, global_x, global_y)) {
if (P) Serial.print("board[0]: ");
if (P) pb(board[0]);
if (P) Serial.println();
if (P) Serial.print("board[1]: ");
if (P) pb(board[1]);
if (P) Serial.println();
if (P) Serial.print("chk1: ");
if (P) Serial.print(board[0] & 0b00111100);
if (P) Serial.print(" chk2: ");
if (P) Serial.print(board[1] & 0b00111100);
if (P) Serial.println();
if ((board[0] & 0b00111100) > 0 || (board[1] & 0b00111100) > 0 ) {
Serial.println("GAME OVER!");
GameOverScreen(true);
GameOverScreen(false);
}
DrawBlock(currentBlock);
if (P) Serial.print("Spawned new block: (");
if (P) Serial.print(blockIndex);
if (P) Serial.print(") ");
if (P) PrintBlockType(blockIndex);
}
void DrawBigBlock(unsigned char block[8], char x, char y) {
unsigned char p1;
// bool isSet = (value & (1 << n)) != 0
for (char j=0; j<8; j++) {
p1 = block[j];
for (char i=0; i<8; i++) {
if (BitIsSet(p1, i))
mx.setPoint(x+i, y+j, true);
else
mx.setPoint(x+i, y+j, false);
}
}
}
void Rotate90CCW(const Block& src, Block& dst) {
//Serial.println("Rotating CCW");
int n = src.size;
dst.size = n;
for (int i = 0; i < 4; i++) dst.rows[i] = 0;
for (int r = 0; r < n; r++) {
for (int c = 0; c < n; c++) {
// Extract bit r from row (n-1 - c)
unsigned char bit = (src.rows[n - 1 - c] >> r) & 1;
// Write it into bit c of row r
dst.rows[r] |= (bit << c);
}
}
}
void rotate90CW(const Block& src, Block& dst) {
//Serial.println("Rotating CW");
int n = src.size;
dst.size = n;
for (int i = 0; i < 4; i++) dst.rows[i] = 0;
for (int r = 0; r < n; r++) {
for (int c = 0; c < n; c++) {
// Extract bit from src[c], bit index (n-1 - r)
unsigned char bit = (src.rows[c] >> (n - 1 - r)) & 1;
// Place it into dst[r], bit index c
dst.rows[r] |= (bit << c);
}
}
}
void updateButton(Button &btn) {
bool reading = digitalRead(btn.pin) == LOW; // active-low
if (reading != btn.lastReading) {
btn.lastDebounceTime = millis();
}
if ((millis() - btn.lastDebounceTime) > debounceDelay) {
if (reading != btn.lastStableState) {
btn.lastStableState = reading;
if (btn.lastStableState) {
btn.pressedEvent = true; // Just pressed
}
}
}
btn.lastReading = reading;
}
/*
unsigned char rotated[4];
rotate90(block_t, rotated);
*/
bool BitIsSet(char value, char n) {
return (value & (1 << n)) != 0;
}
// This takes negative shift values as well, keep it.
inline uint8_t shiftByte(uint8_t value, int shift) {
return (shift >= 0) ? (value << shift) : (value >> -shift);
}
void pb(unsigned char value) {
for (int i = 7; i >= 0; i--) {
Serial.print((value >> i) & 1);
}
}
void GameOverScreen(bool value) {
uint8_t index;
bool side;
for (uint8_t x=0; x<8; x++) {
index= 0;
for (uint8_t y=0; y<32; y++) {
mx.setPoint(side ? x : (7 - x), y, value);
side ^= (++index % 3) == 0;
}
delay(100);
}
}
void setup() {
Serial.begin(115200);
delay(100);
pinMode(PIN_B1, INPUT_PULLUP);
pinMode(PIN_B2, INPUT_PULLUP);
pinMode(PIN_UP, INPUT_PULLUP);
pinMode(PIN_DOWN, INPUT_PULLUP);
pinMode(PIN_LEFT, INPUT_PULLUP);
pinMode(PIN_RIGHT, INPUT_PULLUP);
btn1 = {PIN_B1, false, false, 0, false};
btn2 = {PIN_B2, false, false, 0, false};
btn_u = {PIN_UP, false, false, 0, false};
btn_d = {PIN_DOWN, false, false, 0, false};
btn_l = {PIN_LEFT, false, false, 0, false};
btn_r = {PIN_RIGHT, false, false, 0, false};
// Initialize the display
mx.begin();
mx.clear();
mx.control(MD_MAX72XX::INTENSITY, 2); // Set brightness (0-15)
randomSeed(analogRead(0));
// testing this for rotation
// for (uint8_t i = 0; i < 4; i++) {
// mx.transform(i, MD_MAX72XX::TSR); // TSU = Turn 180°
// }
/*
TSL: Transform Shift Left (rotate display 90° left / counterclockwise)
TSR: Transform Shift Right (rotate display 90° right / clockwise)
TSU: Transform Shift Up (rotate display 180°)
TSD: Transform Shift Down (no rotation, default orientation)
TFLR: Transform Flip Left-Right (mirror horizontally)
TFUD: Transform Flip Up-Down (mirror vertically)
TRC: Transform Rotate Clockwise (rotate 90° right)
TINV: Transform Invert (invert all pixels: on ↔ off)
*/
// END testing
//randomSeed(analogRead(0)); // Seed random number generator
//DrawBlock(block_L, 2,2);
//DrawBigBlock(block_ground, 0, 12);
DrawBoard();
//global_x = 1;
//global_y = 4;
//uint8_t blockIndex = random(8);
//currentBlock = blockSet[blockIndex];
//currentBlock = block_I;
//DrawBlock(currentBlock, global_x, global_y);
SpawnBlock();
Serial.println("Start");
}
void loop() {
// mx.setPoint(0,0,1);
// delay(1000);
// mx.setPoint(7,0,1);
// delay(1000);
// return;
// for (char y=0; y<32; y++) {
// mx.clear();
// DrawBigBlock(char_F, 0, y);
// delay(200);
// }
// return;
// check tick for block falling
if (false && millis() - lastTick >= tickInterval) {
lastTick = millis();
//Serial.println("Tick");
// Move block
//global_x++;
if (CheckCollision(currentBlock, 0, 1)) {
Serial.println("Collision on tick!");
checkForLineRemoval();
} else {
//EraseBlock(currentBlock, global_x, global_y);
if (dbrd) DrawBoard();
//global_y++; // handled in DrawBlock now
DrawBlock(currentBlock, 0, 1);
}
}
updateButton(btn1);
updateButton(btn2);
updateButton(btn_u);
updateButton(btn_d);
updateButton(btn_l);
updateButton(btn_r);
if (btn1.pressedEvent) {
//Serial.println("Btn 1 pressed");
Rotate90CCW(currentBlock, block_tmp);
if (!CheckCollision(block_tmp, 0, 0)) {
//DrawBlock(block_tmp);
// EA 2026-03-09 Erase current block
PaintBlock(currentBlock, global_x, global_y, false);
currentBlock = block_tmp;
// EA 2026-03-09 Draw the now rotated block
PaintBlock(currentBlock, global_x, global_y, true);
} else {
Serial.println(" Rotation blocked by collision");
}
btn1.pressedEvent = false;
}
if (btn2.pressedEvent) {
//Serial.print("Btn 2 pressed");
rotate90CW(currentBlock, block_tmp);
if (!CheckCollision(block_tmp, 0, 0)) {
//DrawBlock(block_tmp);
// EA 2026-03-09 Erase current block
PaintBlock(currentBlock, global_x, global_y, false);
currentBlock = block_tmp;
// EA 2026-03-09 Draw the now rotated block
PaintBlock(currentBlock, global_x, global_y, true);
} else {
Serial.println(" Rotation blocked by collision");
}
btn2.pressedEvent = false;
}
// UP BUTTON ---------------------------
if (btn_u.pressedEvent) {
if (CheckCollision(currentBlock, 1, 0)) {
//Serial.println("Collision! U");
} else {
if (dbrd) DrawBoard();
//global_x++;
DrawBlock(currentBlock, 1, 0);
}
btn_u.pressedEvent = false;
}
// DOWN BUTTON ---------------------------
if (btn_d.pressedEvent) {
if (CheckCollision(currentBlock, -1, 0)) {
Serial.println("Collision! D");
} else {
// x and y are switched on this display
//global_x--;
DrawBlock(currentBlock, -1, 0);
}
btn_d.pressedEvent = false;
}
// LEFT BUTTON ---------------------------
if (btn_l.pressedEvent) {
if (CheckCollision(currentBlock, 0, 1)){
Serial.println("Collision happened, checking lines");
checkForLineRemoval();
} else {
//if (dbrd) DrawBoard();
DrawBlock(currentBlock, 0, 1); // was missing dy
//global_y++;
}
btn_l.pressedEvent = false;
}
// RIGHT BUTTON ---------------------------
if (btn_r.pressedEvent) {
//Serial.println("BL press");
if (CheckCollision(currentBlock, 0, -1)){
Serial.println("Collision happened");
} else {
//EraseBlock(currentBlock, global_x, global_y);
if (dbrd) DrawBoard();
//global_y--;
DrawBlock(currentBlock, 0, -1);
}
btn_r.pressedEvent = false;
}
// Run this every 500 ms
if (millis() - lastUpdate >= 500) {
// Update the display from buffer
//mx.update();
lastUpdate = millis();
//if (++count % 100 == 0)
//Serial.println(digitalRead(PIN_B1));
if (++count > 65000)
count = 0;
// Toggle onboard LED to show activity
//LEDState = !LEDState;
//digitalWrite(LED_PIN, LEDState);
}
}