Skip to content

Instantly share code, notes, and snippets.

@MateusAquino
Last active July 3, 2025 20:09
Show Gist options
  • Save MateusAquino/61060fdb46ef21574513591875a61c40 to your computer and use it in GitHub Desktop.
Save MateusAquino/61060fdb46ef21574513591875a61c40 to your computer and use it in GitHub Desktop.
OpenDrop - Tetris
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