Cyberpunk Gameboy using Arduino Nano
This DIY Handheld can play Tetris and other games!
Devices & Components
1
Arduino Nano
Software & Tools
Arduino IDE
Project description
Code
CyberpunkGameboy
1#include <LedControl.h> 2 3LedControl lc = LedControl(11, 13, 10, 3); 4 5#define BTN_ROTATE 3 // D2 = spin 6#define BTN_DOWN 2 // D3 = drop 7#define BTN_RIGHT 5 // D5 = rechts 8#define BTN_LEFT 4 // D6 = links 9 10 11#define WIDTH 8 12#define HEIGHT 24 13 14bool field[HEIGHT][WIDTH]; 15bool frame[HEIGHT][WIDTH]; 16bool lastFrame[HEIGHT][WIDTH]; 17 18bool gameOver = false; 19int score = 0; 20 21unsigned long lastFall = 0; 22unsigned long lastMove = 0; 23unsigned long lastRotate = 0; 24 25bool lastLeft = HIGH; 26bool lastRight = HIGH; 27bool lastRotateBtn = HIGH; 28bool lastDown = HIGH; 29 30int fallSpeed = 500; 31int moveDelay = 120; 32int rotateDelay = 150; 33 34const unsigned long modeHoldTime = 800; 35const unsigned long modeDebounce = 300; 36unsigned long modePressStart = 0; 37unsigned long lastModeSwitch = 0; 38bool modeTriggered = false; 39 40int gameMode = 0; // 0 = Tetris, 1 = Dino 41 42const byte pieces[7][4][4] = { 43 { {0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0} }, 44 { {0,1,1,0},{0,1,1,0},{0,0,0,0},{0,0,0,0} }, 45 { {0,1,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0} }, 46 { {0,0,1,0},{1,1,1,0},{0,0,0,0},{0,0,0,0} }, 47 { {1,0,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0} }, 48 { {0,1,1,0},{1,1,0,0},{0,0,0,0},{0,0,0,0} }, 49 { {1,1,0,0},{0,1,1,0},{0,0,0,0},{0,0,0,0} } 50}; 51 52byte piece[4][4]; 53int currentPiece; 54int nextPiece; 55int currentX = 2; 56int currentY = 0; 57 58// Dino 59int dinoY = 19; 60int dinoJumpTicks = 0; 61bool dinoJumping = false; 62int obstacleY = 0; 63int obstacleX = 6; 64unsigned long lastDinoMove = 0; 65unsigned long dinoMoveDelay = 220; 66 67void spawnPiece(); 68void resetTetris(); 69void resetDino(); 70void switchMode(int newMode); 71void handleModeButton(); 72void resetButtonStates(); 73void handleInput(); 74void renderFrame(); 75void drawGameOver(); 76void drawDino(); 77void drawDinoGameOver(); 78void updateDino(); 79void clearFrame(); 80void drawDisplay(); 81bool isValid(int newX, int newY); 82void placePiece(); 83void clearLines(); 84void rotatePiece(); 85 86void clearFrame() { 87 memset(frame, 0, sizeof(frame)); 88} 89 90void resetButtonStates() { 91 lastLeft = digitalRead(BTN_LEFT); 92 lastRight = digitalRead(BTN_RIGHT); 93 lastRotateBtn = digitalRead(BTN_ROTATE); 94 lastDown = digitalRead(BTN_DOWN); 95} 96 97void resetTetris() { 98 memset(field, 0, sizeof(field)); 99 score = 0; 100 gameOver = false; 101 nextPiece = random(0, 7); 102 spawnPiece(); 103 lastFall = millis(); 104 lastMove = millis(); 105 lastRotate = millis(); 106 resetButtonStates(); 107} 108 109void resetDino() { 110 memset(field, 0, sizeof(field)); 111 clearFrame(); 112 score = 0; 113 gameOver = false; 114 115 dinoY = 19; 116 dinoJumpTicks = 0; 117 dinoJumping = false; 118 obstacleY = 0; 119 obstacleX = 6; 120 lastDinoMove = millis(); 121 122 resetButtonStates(); 123} 124 125void switchMode(int newMode) { 126 gameMode = newMode; 127 modeTriggered = false; 128 modePressStart = 0; 129 lastModeSwitch = millis(); 130 131 if (gameMode == 0) { 132 resetTetris(); 133 } else { 134 resetAvoider(); // statt resetDino() 135 } 136} 137 138void setup() { 139 randomSeed(analogRead(A0)); 140 141 for (int i = 0; i < 3; i++) { 142 lc.shutdown(i, false); 143 lc.setIntensity(i, 8); 144 lc.clearDisplay(i); 145 } 146 147 pinMode(BTN_LEFT, INPUT_PULLUP); 148 pinMode(BTN_RIGHT, INPUT_PULLUP); 149 pinMode(BTN_ROTATE, INPUT_PULLUP); 150 pinMode(BTN_DOWN, INPUT_PULLUP); 151 152 153 memset(lastFrame, 0, sizeof(lastFrame)); 154 switchMode(0); 155} 156 157void drawDisplay() { 158 for (int y = 0; y < HEIGHT; y++) { 159 for (int x = 0; x < WIDTH; x++) { 160 if (frame[y][x] != lastFrame[y][x]) { 161 int rotatedY = HEIGHT - 1 - y; 162 int rotatedX = WIDTH - 1 - x; 163 164 int module = rotatedY / 8; 165 int col = rotatedY % 8; 166 int row = rotatedX; 167 168 lc.setLed(2 - module, row, col, frame[y][x]); 169 } 170 } 171 } 172 memcpy(lastFrame, frame, sizeof(frame)); 173} 174 175bool isValid(int newX, int newY) { 176 for (int py = 0; py < 4; py++) { 177 for (int px = 0; px < 4; px++) { 178 if (piece[py][px]) { 179 int fx = newX + px; 180 int fy = newY + py; 181 if (fx < 0 || fx >= WIDTH || fy >= HEIGHT) return false; 182 if (fy >= 0 && field[fy][fx]) return false; 183 } 184 } 185 } 186 return true; 187} 188 189void placePiece() { 190 for (int py = 0; py < 4; py++) { 191 for (int px = 0; px < 4; px++) { 192 if (piece[py][px]) { 193 int fx = currentX + px; 194 int fy = currentY + py; 195 if (fy >= 0 && fy < HEIGHT && fx >= 0 && fx < WIDTH) field[fy][fx] = true; 196 } 197 } 198 } 199} 200 201void clearLines() { 202 for (int y = HEIGHT - 1; y >= 0; y--) { 203 bool full = true; 204 for (int x = 0; x < WIDTH; x++) { 205 if (!field[y][x]) { 206 full = false; 207 break; 208 } 209 } 210 211 if (full) { 212 score++; 213 for (int ty = y; ty > 0; ty--) { 214 for (int x = 0; x < WIDTH; x++) { 215 field[ty][x] = field[ty - 1][x]; 216 } 217 } 218 memset(field[0], 0, WIDTH); 219 y++; 220 } 221 } 222} 223 224void rotatePiece() { 225 byte temp[4][4]; 226 byte backup[4][4]; 227 memcpy(backup, piece, sizeof(piece)); 228 229 for (int y = 0; y < 4; y++) { 230 for (int x = 0; x < 4; x++) { 231 temp[x][3 - y] = piece[y][x]; 232 } 233 } 234 235 memcpy(piece, temp, sizeof(piece)); 236 237 if (!isValid(currentX, currentY)) { 238 if (isValid(currentX - 1, currentY)) currentX--; 239 else if (isValid(currentX + 1, currentY)) currentX++; 240 else memcpy(piece, backup, sizeof(piece)); 241 } 242} 243 244void spawnPiece() { 245 currentPiece = nextPiece; 246 nextPiece = random(0, 7); 247 248 for (int y = 0; y < 4; y++) { 249 for (int x = 0; x < 4; x++) { 250 piece[y][x] = pieces[currentPiece][y][x]; 251 } 252 } 253 254 currentX = 2; 255 currentY = 0; 256 257 if (!isValid(currentX, currentY)) gameOver = true; 258} 259 260void handleInput() { 261 bool left = digitalRead(BTN_LEFT); 262 bool right = digitalRead(BTN_RIGHT); 263 bool rotateBtn = digitalRead(BTN_ROTATE); 264 bool down = digitalRead(BTN_DOWN); 265 266 if (left == LOW && lastLeft == HIGH && millis() - lastMove > moveDelay) { 267 if (isValid(currentX - 1, currentY)) currentX--; 268 lastMove = millis(); 269 } 270 271 if (right == LOW && lastRight == HIGH && millis() - lastMove > moveDelay) { 272 if (isValid(currentX + 1, currentY)) currentX++; 273 lastMove = millis(); 274 } 275 276 if (rotateBtn == LOW && lastRotateBtn == HIGH && millis() - lastRotate > rotateDelay) { 277 rotatePiece(); 278 lastRotate = millis(); 279 } 280 281 if (down == LOW && lastDown == HIGH) { 282 while (isValid(currentX, currentY + 1)) { 283 currentY++; 284 score += 2; 285 } 286 } 287 288 lastLeft = left; 289 lastRight = right; 290 lastRotateBtn = rotateBtn; 291 lastDown = down; 292} 293 294void renderFrame() { 295 clearFrame(); 296 297 for (int y = 0; y < HEIGHT; y++) { 298 for (int x = 0; x < WIDTH; x++) { 299 if (field[y][x]) frame[y][x] = true; 300 } 301 } 302 303 for (int py = 0; py < 4; py++) { 304 for (int px = 0; px < 4; px++) { 305 if (piece[py][px]) { 306 int x = currentX + px; 307 int y = currentY + py; 308 if (y >= 0 && y < HEIGHT && x >= 0 && x < WIDTH) frame[y][x] = true; 309 } 310 } 311 } 312} 313 314void drawGameOver() { 315 clearFrame(); 316 317 int leftX = 0; 318 int rightX = 4; 319 320auto px = [&](int y, int x, bool val) { 321 int mirroredX = WIDTH - 1 - x; // horizontal zurückspiegeln 322 if (y >= 0 && y < HEIGHT && mirroredX >= 0 && mirroredX < WIDTH) { 323 frame[y][mirroredX] = val; 324 } 325}; 326 327 int y = 0; 328 329 px(y, leftX + 1, 1); px(y, leftX + 2, 1); 330 px(y + 1, leftX, 1); 331 px(y + 2, leftX, 1); px(y + 2, leftX + 2, 1); px(y + 2, leftX + 3, 1); 332 px(y + 3, leftX, 1); px(y + 3, leftX + 3, 1); 333 px(y + 4, leftX + 1, 1); px(y + 4, leftX + 2, 1); px(y + 4, leftX + 3, 1); 334 335 px(y, rightX + 1, 1); px(y, rightX + 2, 1); 336 px(y + 1, rightX, 1); px(y + 1, rightX + 3, 1); 337 px(y + 2, rightX, 1); px(y + 2, rightX + 1, 1); px(y + 2, rightX + 2, 1); px(y + 2, rightX + 3, 1); 338 px(y + 3, rightX, 1); px(y + 3, rightX + 3, 1); 339 px(y + 4, rightX, 1); px(y + 4, rightX + 3, 1); 340 341 y = 6; 342 343 px(y, leftX, 1); px(y, leftX + 3, 1); 344 px(y + 1, leftX, 1); px(y + 1, leftX + 1, 1); px(y + 1, leftX + 2, 1); px(y + 1, leftX + 3, 1); 345 px(y + 2, leftX, 1); px(y + 2, leftX + 3, 1); 346 px(y + 3, leftX, 1); px(y + 3, leftX + 3, 1); 347 px(y + 4, leftX, 1); px(y + 4, leftX + 3, 1); 348 349 px(y, rightX, 1); px(y, rightX + 1, 1); px(y, rightX + 2, 1); px(y, rightX + 3, 1); 350 px(y + 1, rightX, 1); 351 px(y + 2, rightX, 1); px(y + 2, rightX + 1, 1); px(y + 2, rightX + 2, 1); 352 px(y + 3, rightX, 1); 353 px(y + 4, rightX, 1); px(y + 4, rightX + 1, 1); px(y + 4, rightX + 2, 1); px(y + 4, rightX + 3, 1); 354 355 y = 13; 356 357 px(y, leftX + 1, 1); px(y, leftX + 2, 1); 358 px(y + 1, leftX, 1); px(y + 1, leftX + 3, 1); 359 px(y + 2, leftX, 1); px(y + 2, leftX + 3, 1); 360 px(y + 3, leftX, 1); px(y + 3, leftX + 3, 1); 361 px(y + 4, leftX + 1, 1); px(y + 4, leftX + 2, 1); 362 363 px(y, rightX, 1); px(y, rightX + 3, 1); 364 px(y + 1, rightX, 1); px(y + 1, rightX + 3, 1); 365 px(y + 2, rightX + 1, 1); px(y + 2, rightX + 2, 1); 366 px(y + 3, rightX + 1, 1); px(y + 3, rightX + 2, 1); 367 px(y + 4, rightX + 1, 1); 368 369 y = 19; 370 371 px(y, leftX, 1); px(y, leftX + 1, 1); px(y, leftX + 2, 1); px(y, leftX + 3, 1); 372 px(y + 1, leftX, 1); 373 px(y + 2, leftX, 1); px(y + 2, leftX + 1, 1); px(y + 2, leftX + 2, 1); 374 px(y + 3, leftX, 1); 375 px(y + 4, leftX, 1); px(y + 4, leftX + 1, 1); px(y + 4, leftX + 2, 1); px(y + 4, leftX + 3, 1); 376 377 px(y, rightX, 1); px(y, rightX + 1, 1); px(y, rightX + 2, 1); 378 px(y + 1, rightX, 1); px(y + 1, rightX + 3, 1); 379 px(y + 2, rightX, 1); px(y + 2, rightX + 1, 1); px(y + 2, rightX + 2, 1); 380 px(y + 3, rightX, 1); px(y + 3, rightX + 2, 1); 381 px(y + 4, rightX, 1); px(y + 4, rightX + 3, 1); 382 383 drawDisplay(); 384} 385 386// === AVOIDER-MODUS: Hindernisse fallen von oben, Spieler auf Zeile 23 === 387 388int avoiderX = 3; 389 390struct Obstacle { 391 int x; 392 int y; 393 bool active; 394} obstacles[20]; 395 396unsigned long lastObstacleTime = 0; 397unsigned long obstacleInterval = 420; 398unsigned long obstacleFallDelay = 90; 399unsigned long lastObstacleFall = 0; 400unsigned long avoiderStartTime = 0; 401 402void resetAvoider() { 403 memset(field, 0, sizeof(field)); 404 clearFrame(); 405 score = 0; 406 gameOver = false; 407 408 avoiderX = 3; 409 410 for (int i = 0; i < 20; i++) { 411 obstacles[i].active = false; 412 obstacles[i].x = 0; 413 obstacles[i].y = 0; 414 } 415 416 lastObstacleTime = millis(); 417 lastObstacleFall = millis(); 418 avoiderStartTime = millis(); 419 obstacleInterval = 420; 420 obstacleFallDelay = 90; 421 422 resetButtonStates(); 423} 424 425void drawAvoider() { 426 clearFrame(); 427 428 frame[23][avoiderX] = true; 429 430 for (int i = 0; i < 16; i++) { 431 if (obstacles[i].active && obstacles[i].y >= 0 && obstacles[i].y < HEIGHT) { 432 frame[obstacles[i].y][obstacles[i].x] = true; 433 } 434 } 435 436 drawDisplay(); 437} 438 439void updateAvoider() { 440 bool left = digitalRead(BTN_LEFT); 441 bool right = digitalRead(BTN_RIGHT); 442 443 if (left == LOW && lastLeft == HIGH && millis() - lastMove > moveDelay) { 444 if (avoiderX > 0) avoiderX--; 445 lastMove = millis(); 446 } 447 448 if (right == LOW && lastRight == HIGH && millis() - lastMove > moveDelay) { 449 if (avoiderX < WIDTH - 1) avoiderX++; 450 lastMove = millis(); 451 } 452 453 unsigned long survived = millis() - avoiderStartTime; 454 455 obstacleInterval = 420; 456 obstacleFallDelay = 90; 457 458 if (survived > 4000) { 459 obstacleInterval = 360; 460 obstacleFallDelay = 80; 461 } 462 if (survived > 8000) { 463 obstacleInterval = 320; 464 obstacleFallDelay = 72; 465 } 466 if (survived > 12000) { 467 obstacleInterval = 280; 468 obstacleFallDelay = 64; 469 } 470 if (survived > 16000) { 471 obstacleInterval = 240; 472 obstacleFallDelay = 58; 473 } 474 if (survived > 22000) { 475 obstacleInterval = 210; 476 obstacleFallDelay = 52; 477 } 478 479 if (millis() - lastObstacleTime >= obstacleInterval) { 480 lastObstacleTime = millis(); 481 482 int spawnCount = 1; 483 484 if (random(0, 100) < 45) spawnCount = 2; 485 if (random(0, 100) < 18) spawnCount = 3; 486 487 for (int s = 0; s < spawnCount; s++) { 488 for (int i = 0; i < 20; i++) { 489 if (!obstacles[i].active) { 490 obstacles[i].active = true; 491 obstacles[i].x = random(0, WIDTH); 492 obstacles[i].y = 0; 493 break; 494 } 495 } 496 } 497 } 498 499 if (millis() - lastObstacleFall >= obstacleFallDelay) { 500 lastObstacleFall = millis(); 501 502 for (int i = 0; i < 20; i++) { 503 if (!obstacles[i].active) continue; 504 505 obstacles[i].y++; 506 507 if (obstacles[i].y >= 23) { 508 if (obstacles[i].x == avoiderX) { 509 gameOver = true; 510 } else { 511 score++; 512 } 513 obstacles[i].active = false; 514 } 515 } 516 } 517 518 lastLeft = left; 519 lastRight = right; 520} 521 522void drawAvoiderGameOver() { 523 clearFrame(); 524 int cx = 4, cy = 16; 525 frame[cy-1][cx-1] = true; 526 frame[cy-1][cx+1] = true; 527 frame[cy][cx] = true; 528 frame[cy+1][cx-1] = true; 529 frame[cy+1][cx+1] = true; 530 drawDisplay(); 531} 532 533 534 535void loop() { 536 if (gameOver) { 537 drawGameOver(); 538 539 bool left = digitalRead(BTN_LEFT); 540 bool right = digitalRead(BTN_RIGHT); 541 542 if (right == LOW && lastRight == HIGH) { 543 switchMode(0); // Tetris 544 return; 545 } 546 547 if (left == LOW && lastLeft == HIGH) { 548 switchMode(1); // Avoider 549 return; 550 } 551 552 lastLeft = left; 553 lastRight = right; 554 return; 555 } 556 557 if (gameMode == 0) { 558 handleInput(); 559 560 if (millis() - lastFall > fallSpeed) { 561 if (isValid(currentX, currentY + 1)) currentY++; 562 else { 563 placePiece(); 564 clearLines(); 565 spawnPiece(); 566 } 567 lastFall = millis(); 568 } 569 570 renderFrame(); 571 drawDisplay(); 572 } else { 573 updateAvoider(); 574 drawAvoider(); 575 } 576}
Downloadable files
Part 1
Part 1.stl
Part 2
Part 2.stl
Part 3
Part 3.stl
Comments
Only logged in users can leave comments