Last active
July 3, 2025 20:09
-
-
Save MateusAquino/61060fdb46ef21574513591875a61c40 to your computer and use it in GitHub Desktop.
OpenDrop - Tetris
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import processing.serial.*; | |
import static javax.swing.JOptionPane.*; | |
Serial myPort; | |
// Configurable params | |
final boolean WARN_RESERVOIR = true; | |
final boolean ROTATE_CONTROLS = false; | |
final boolean UI_MODE = true; | |
// Grid dimensions | |
final int GRID_WIDTH = 14; | |
final int GRID_HEIGHT = 8; | |
final int WINDOW_WIDTH = 700; | |
final int WINDOW_HEIGHT = 400; | |
// Electrode & locked‑block state | |
boolean[][] electrodes = new boolean[GRID_WIDTH][GRID_HEIGHT]; | |
boolean[][] grid = new boolean[GRID_WIDTH][GRID_HEIGHT]; | |
// Game & Tetris state | |
boolean gameOver = false; | |
boolean paused = false; | |
int score = 0; | |
int dropInterval = 500; | |
int lastDropTime; | |
boolean lastBlink = false; | |
int lastBlinkTime = 0; | |
int blinkInterval = 400; | |
PVector pos; // current piece origin | |
int[][] shape; // current tetromino | |
// Tetromino definitions | |
int[][][] TETROMINOES = { | |
{{1,1,1,1}}, // I | |
{{1,1},{1,1}}, // O | |
{{0,1,0},{1,1,1}}, // T | |
{{0,1,1},{1,1,0}}, // S | |
{{1,1,0},{0,1,1}}, // Z | |
{{1,0,0},{1,1,1}}, // J | |
{{0,0,1},{1,1,1}} // L | |
}; | |
void setup() { | |
noLoop(); // pause draw while we init serial + UI | |
// —— serial init —— | |
String[] ports = Serial.list(); | |
if (ports == null || ports.length == 0) { | |
showMessageDialog(null, | |
"No serial ports found. Connect your device and retry.", | |
"Serial Port Error", ERROR_MESSAGE); | |
exit(); | |
} | |
myPort = new Serial(this, ports[0], 115200); | |
delay(100); | |
// —— initial board fill —— | |
initialTransmit(); | |
if (WARN_RESERVOIR) showMessageDialog(null, | |
"This is an inverted grid. Fill the board (except the reservoir) with liquid *now*, then press OK.", | |
"Initial setup", 2); | |
// —— game init —— | |
if (UI_MODE) windowResize(WINDOW_WIDTH, WINDOW_HEIGHT); | |
frameRate(60); | |
setupGame(); | |
loop(); | |
} | |
void setupGame() { | |
// clear electrodes & locked grid | |
for (int x = 0; x < GRID_WIDTH; x++) | |
for (int y = 0; y < GRID_HEIGHT; y++) { | |
electrodes[x][y] = true; | |
grid[x][y] = false; | |
} | |
score = 0; | |
gameOver = paused = false; | |
spawnNewTetromino(); | |
drawBoard(); | |
transmit(); | |
} | |
void draw() { | |
// drop piece | |
if (!gameOver && !paused && millis() - lastDropTime > dropInterval) { | |
lastBlink = false; | |
move(-1, 0); | |
lastDropTime = millis(); | |
} | |
if (gameOver && millis() - lastBlinkTime > blinkInterval) { | |
lastBlink = !lastBlink; | |
lastBlinkTime = millis(); | |
} | |
// update electrodes → serial → UI | |
drawBoard(); | |
transmit(); | |
if (UI_MODE) renderGrid(); | |
} | |
void initialTransmit() { | |
// fill all electrodes on | |
for (int x = 0; x < GRID_WIDTH; x++) | |
for (int y = 0; y < GRID_HEIGHT; y++) | |
electrodes[x][y] = true; | |
byte[] packet = new byte[32]; | |
packet[0] = 0; | |
for (int x = 0; x < GRID_WIDTH; x++) { | |
int b = 0; | |
for (int y = 0; y < GRID_HEIGHT; y++) | |
if (electrodes[x][y]) | |
b |= 1<<y; | |
packet[x+1] = (byte)b; | |
} | |
packet[GRID_WIDTH+1] = 0; | |
for (byte pb : packet) myPort.write(pb); | |
} | |
void transmit() { | |
byte[] packet = new byte[32]; | |
packet[0] = (byte)0xFF; | |
for (int x = 0; x < GRID_WIDTH; x++) { | |
int b = 0; | |
for (int y = 0; y < GRID_HEIGHT; y++) | |
if (electrodes[x][y]) | |
b |= 1<<y; | |
packet[x+1] = (byte)b; | |
} | |
packet[GRID_WIDTH+1] = (byte)0xFF; | |
for (byte pb : packet) myPort.write(pb); | |
} | |
void renderGrid() { | |
int cellW = width / GRID_WIDTH; | |
int cellH = height / GRID_HEIGHT; | |
stroke(80); | |
for (int x = 0; x < GRID_WIDTH; x++) { | |
for (int y = 0; y < GRID_HEIGHT; y++) { | |
fill(electrodes[x][y] ? color(0,200,0) : 50); | |
rect(x*cellW, y*cellH, cellW-2, cellH-2); | |
} | |
} | |
} | |
// —— Tetris core —— | |
void spawnNewTetromino() { | |
shape = TETROMINOES[int(random(TETROMINOES.length))]; | |
float startX = GRID_WIDTH - shape[0].length; | |
float startY = (GRID_HEIGHT - shape.length) / 2f; | |
pos = new PVector(startX, startY); | |
lastDropTime = millis(); | |
if (collides(shape, (int)pos.x, (int)pos.y)) { | |
gameOver = true; | |
println("Game Over! Final score: " + score); | |
} | |
} | |
boolean collides(int[][] s, int px, int py) { | |
for (int i = 0; i < s.length; i++) { | |
for (int j = 0; j < s[i].length; j++) { | |
if (s[i][j] == 0) continue; | |
int x = px + j, y = py + i; | |
if (x < 0 || x >= GRID_WIDTH | |
|| y < 0 || y >= GRID_HEIGHT) | |
return true; | |
if (grid[x][y]) return true; | |
} | |
} | |
return false; | |
} | |
void move(int dx, int dy) { | |
int nx = (int)pos.x + dx; | |
int ny = (int)pos.y + dy; | |
if (!collides(shape, nx, ny)) { | |
pos.x = nx; pos.y = ny; | |
} else if (dx==-1) { | |
// lock and clear only when drop is blocked | |
for (int i = 0; i < shape.length; i++) { | |
for (int j = 0; j < shape[i].length; j++) { | |
if (shape[i][j] != 0) { | |
int x = (int)pos.x + j, y = (int)pos.y + i; | |
if (x>=0 && x<GRID_WIDTH | |
&& y>=0 && y<GRID_HEIGHT) | |
grid[x][y] = true; | |
} | |
} | |
} | |
clearLines(); | |
spawnNewTetromino(); | |
} | |
} | |
void clearLines() { | |
for (int x = 0; x < GRID_WIDTH; x++) { | |
boolean full = true; | |
for (int y = 0; y < GRID_HEIGHT; y++) { | |
if (!grid[x][y]) { | |
full = false; | |
break; | |
} | |
} | |
if (full) { | |
// shift everything right of column x left one | |
for (int xx = x; xx < GRID_WIDTH - 1; xx++) { | |
for (int y = 0; y < GRID_HEIGHT; y++) { | |
grid[xx][y] = grid[xx + 1][y]; | |
} | |
} | |
// clear rightmost column | |
for (int y = 0; y < GRID_HEIGHT; y++) { | |
grid[GRID_WIDTH - 1][y] = false; | |
} | |
x--; // re‑check this same x (now holds the next column over) | |
score += 100; | |
println("Score: " + score); | |
} | |
} | |
} | |
void rotatePiece() { | |
int h = shape.length, w = shape[0].length; | |
int[][] r = new int[w][h]; | |
for (int i = 0; i < h; i++) | |
for (int j = 0; j < w; j++) | |
r[j][h-1-i] = shape[i][j]; | |
if (!collides(r, (int)pos.x, (int)pos.y)) | |
shape = r; | |
} | |
void drawBoard() { | |
// reset all electrodes on | |
for (int x = 0; x < GRID_WIDTH; x++) | |
for (int y = 0; y < GRID_HEIGHT; y++) | |
electrodes[x][y] = true; | |
// draw locked | |
for (int x = 0; x < GRID_WIDTH; x++) | |
for (int y = 0; y < GRID_HEIGHT; y++) | |
if (grid[x][y]) electrodes[x][y] = lastBlink; | |
// draw active tetromino | |
for (int i = 0; i < shape.length; i++) | |
for (int j = 0; j < shape[i].length; j++) | |
if (shape[i][j] != 0) { | |
int x = (int)pos.x + j, y = (int)pos.y + i; | |
if (x>=0 && x<GRID_WIDTH | |
&& y>=0 && y<GRID_HEIGHT) | |
electrodes[x][y] = lastBlink; | |
} | |
} | |
void keyPressed() { | |
if (gameOver) { | |
if (key == 'r' || key == 'R') { | |
setupGame(); | |
} | |
return; | |
} | |
if (key == 'p' || key == 'P') { | |
paused = !paused; | |
if (!paused) lastDropTime = millis(); | |
return; | |
} | |
if (paused) return; | |
int code = keyCode; | |
if (ROTATE_CONTROLS) { | |
if (code == LEFT) move( 0,-1); | |
else if (code == RIGHT) move( 0, 1); | |
else if (code == DOWN) move( -1,0); | |
else if (code == UP || key == ' ') rotatePiece(); | |
} else { | |
if (code == UP) move(0,-1); // up in UI = up in grid | |
else if (code == DOWN) move(0, 1); // down in UI = down in grid | |
else if (code == LEFT) move(-1,0); // left UI = drop faster | |
else if (code == RIGHT || key == ' ') rotatePiece(); | |
} | |
} | |
void dispose() { | |
// clear electrodes on exit | |
if (myPort != null) { | |
byte[] clear = new byte[32]; | |
for (byte b: clear) myPort.write(b); | |
delay(50); | |
myPort.stop(); | |
} | |
super.dispose(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment