#include #include #define BUTTON_A 9 #define BUTTON_B 10 #define BUTTON_UP 11 #define BUTTON_DOWN 12 #define BUTTON_LEFT 13 #define BUTTON_RIGHT 14 #define FRONT_LED_1 23 #define FRONT_LED_2 24 #define FRONT_LED_3 25 #define FRONT_LED_4 26 #define FLASHLIGHT_LED 15 #define SAO_GPIO0 0 #define SAO_GPIO1 1 #define LED_MATRIX_SDA 2 #define LED_MATRIX_SCL 3 #define LED_MATRIX_ADDR 0x74 #define MATRIX_COLS 9 #define MATRIX_ROWS 9 #define IDLE_TIMEOUT 5000 const char* RANDOM_MESSAGES[] = { "Hack the planet!", "0x00 0x01 0x10", "Securityfest 2026!", "RP2040 rocks!", "Buy the dip!", "Nice badge!", "#!.!/#!.!", "0xDEAD 0xBEEF", "Pwned!", "1337 h4x0r", "Kernel panic!", "sudo make me", "CTF{D3bug}", "0-day here", "Badgelife!" }; const int NUM_MESSAGES = sizeof(RANDOM_MESSAGES) / sizeof(RANDOM_MESSAGES[0]); uint8_t matrixBuffer[MATRIX_ROWS][MATRIX_COLS]; unsigned long lastActivity = 0; int currentMenuItem = 0; enum { MODE_MENU, MODE_TETRIS, MODE_SNAKE, MODE_BRICK, MODE_PONG, MODE_TVBGONE, MODE_IDLE } currentMode = MODE_MENU; bool inSubmenu = false; void ledMatrixBegin() { Wire.setSDA(LED_MATRIX_SDA); Wire.setSCL(LED_MATRIX_SCL); Wire.begin(); delay(10); Wire.beginTransmission(LED_MATRIX_ADDR); Wire.write(0x00); Wire.write(0x01); Wire.endTransmission(); } void ledMatrixClear() { memset(matrixBuffer, 0, sizeof(matrixBuffer)); for (int page = 0; page < 2; page++) { Wire.beginTransmission(LED_MATRIX_ADDR); Wire.write(0x00); Wire.write(page); Wire.endTransmission(); Wire.requestFrom(LED_MATRIX_ADDR, MATRIX_COLS); for (int i = 0; i < MATRIX_COLS; i++) { Wire.read(); } for (int c = 0; c < MATRIX_COLS; c++) { Wire.beginTransmission(LED_MATRIX_ADDR); Wire.write(0x01 + c); Wire.write(0x00); Wire.endTransmission(); } } } void ledMatrixSetPixel(int x, int y, uint8_t brightness) { if (x >= 0 && x < MATRIX_COLS && y >= 0 && y < MATRIX_ROWS) { matrixBuffer[y][x] = brightness; Wire.beginTransmission(LED_MATRIX_ADDR); Wire.write(0x01 + x); Wire.write(brightness); Wire.endTransmission(); } } void ledMatrixDrawChar(int x, int y, char c, uint8_t brightness) { const uint8_t font[16][5] = { {0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x5F,0x00,0x00}, {0x00,0x07,0x00,0x07,0x00}, {0x14,0x7F,0x14,0x7F,0x14}, {0x24,0x2A,0x7F,0x2A,0x12}, {0x46,0x26,0x10,0x26,0x46}, {0x08,0x3E,0x28,0x3E,0x08}, {0x00,0x00,0x07,0x00,0x00}, {0x3E,0x3E,0x3E,0x3E,0x3E}, {0x00,0x07,0x07,0x07,0x00}, {0x1C,0x1C,0x1C,0x1C,0x1C}, {0x1C,0x1C,0x1C,0x10,0x00}, {0x00,0x04,0x3E,0x04,0x00}, {0x08,0x1C,0x3E,0x1C,0x08}, {0x1C,0x3E,0x3E,0x3E,0x08}, {0x1C,0x2A,0x2A,0x2A,0x08} }; int idx = 0; if (c >= 'A' && c <= 'Z') idx = c - 'A' + 10; else if (c >= '0' && c <= '9') idx = c - '0'; else if (c == '!') idx = 1; else if (c == '.') idx = 15; else if (c == '(') idx = 13; else if (c == ')') idx = 13; else if (c == '{') idx = 0; else if (c == '}') idx = 0; else return; for (int col = 0; col < 5; col++) { uint8_t colData = font[idx][col]; for (int row = 0; row < 8; row++) { if (colData & (1 << row)) { ledMatrixSetPixel(x + col, y + row, brightness); } } } } void drawText(const char* text, int x, int y, uint8_t brightness) { int cursorX = x; while (*text) { ledMatrixDrawChar(cursorX, y, *text, brightness); cursorX += 6; if (cursorX > MATRIX_COLS) break; text++; } } void drawTextCentered(const char* text, int y, uint8_t brightness) { int len = 0; const char* p = text; while (*p) { len++; p++; } int x = (MATRIX_COLS - len * 6) / 2; if (x < 0) x = 0; drawText(text, x, y, brightness); } void setupButtons() { pinMode(BUTTON_A, INPUT_PULLUP); pinMode(BUTTON_B, INPUT_PULLUP); pinMode(BUTTON_UP, INPUT_PULLUP); pinMode(BUTTON_DOWN, INPUT_PULLUP); pinMode(BUTTON_LEFT, INPUT_PULLUP); pinMode(BUTTON_RIGHT, INPUT_PULLUP); } void setupFrontLEDs() { pinMode(FRONT_LED_1, OUTPUT); pinMode(FRONT_LED_2, OUTPUT); pinMode(FRONT_LED_3, OUTPUT); pinMode(FRONT_LED_4, OUTPUT); } void setupFlashlight() { pinMode(FLASHLIGHT_LED, OUTPUT); digitalWrite(FLASHLIGHT_LED, LOW); } void setupSAO() { pinMode(SAO_GPIO0, OUTPUT); pinMode(SAO_GPIO1, OUTPUT); digitalWrite(SAO_GPIO0, LOW); digitalWrite(SAO_GPIO1, LOW); } bool buttonPressed(int btn) { return digitalRead(btn) == LOW; } bool anyButtonPressed() { return buttonPressed(BUTTON_A) || buttonPressed(BUTTON_B) || buttonPressed(BUTTON_UP) || buttonPressed(BUTTON_DOWN) || buttonPressed(BUTTON_LEFT) || buttonPressed(BUTTON_RIGHT); } void waitForButtonRelease() { while (anyButtonPressed()) delay(10); delay(50); } void recordActivity() { lastActivity = millis(); } void knightRiderSweep(unsigned long interval) { static int pos = 0; static bool dir = true; static unsigned long lastUpdate = 0; static bool leds[4] = {0, 0, 0, 0}; if (millis() - lastUpdate > interval) { lastUpdate = millis(); memset(leds, 0, sizeof(leds)); if (dir) { pos++; if (pos >= 4) { pos = 3; dir = false; } } else { pos--; if (pos < 0) { pos = 1; dir = true; } } if (pos >= 0 && pos < 4) leds[pos] = true; } digitalWrite(FRONT_LED_1, leds[0]); digitalWrite(FRONT_LED_2, leds[1]); digitalWrite(FRONT_LED_3, leds[2]); digitalWrite(FRONT_LED_4, leds[3]); } unsigned long saoToggleTime = 0; bool saoGPIO0State = false; unsigned long saoPWMTime = 0; bool saoPWMDirection = true; float saoPWMValue = 0; void updateSAO(unsigned long now) { if (now - saoToggleTime >= 1000) { saoToggleTime = now; saoGPIO0State = !saoGPIO0State; digitalWrite(SAO_GPIO0, saoGPIO0State); } if (now - saoPWMTime >= 30) { saoPWMTime = now; if (saoPWMDirection) { saoPWMValue += 1.67f; if (saoPWMValue >= 100) { saoPWMValue = 100; saoPWMDirection = false; } } else { saoPWMValue -= 1.67f; if (saoPWMValue <= 0) { saoPWMValue = 0; saoPWMDirection = true; } } analogWrite(SAO_GPIO1, (int)(saoPWMValue * 255.0f / 100.0f)); } } const int TVBGONE_CODES[][2] = { {9000, 4500}, {4500, 4500} }; int tvbgoneStep = 0; unsigned long tvbgoneLastToggle = 0; bool tvbgoneOn = false; void updateTVBGone(unsigned long now) { if (currentMode != MODE_TVBGONE) return; if (now - tvbgoneLastToggle > 20) { tvbgoneLastToggle = now; tvbgoneOn = !tvbgoneOn; digitalWrite(FLASHLIGHT_LED, tvbgoneOn ? HIGH : LOW); } } void showMenu() { ledMatrixClear(); const char* items[] = {"Tetris", "Snake", "Brick", "Pong", "TV-B-Gone"}; for (int i = 0; i < 5; i++) { if (i == currentMenuItem) { ledMatrixSetPixel(0, 1 + i * 2, 255); ledMatrixSetPixel(1, 1 + i * 2, 150); } ledMatrixSetPixel(2, 1 + i * 2, (i == currentMenuItem) ? 255 : 80); ledMatrixSetPixel(3, 1 + i * 2, 80); } char buf[16]; buf[0] = 'A' + currentMenuItem; buf[1] = '\0'; drawText(buf, 3, 1 + currentMenuItem * 2, 255); } void handleMenuInput() { if (buttonPressed(BUTTON_UP)) { recordActivity(); if (currentMenuItem > 0) currentMenuItem--; waitForButtonRelease(); showMenu(); } else if (buttonPressed(BUTTON_DOWN)) { recordActivity(); if (currentMenuItem < 4) currentMenuItem++; waitForButtonRelease(); showMenu(); } else if (buttonPressed(BUTTON_A)) { recordActivity(); waitForButtonRelease(); switch (currentMenuItem) { case 0: currentMode = MODE_TETRIS; break; case 1: currentMode = MODE_SNAKE; break; case 2: currentMode = MODE_BRICK; break; case 3: currentMode = MODE_PONG; break; case 4: currentMode = MODE_TVBGONE; break; } ledMatrixClear(); } } class TetrisGame { public: static const int W = 7; static const int H = 15; static const int BLOCKS[7][4]; uint8_t field[H][W]; int pieceX, pieceY, pieceType, pieceRot; unsigned long lastFall; int score; bool gameOver; TetrisGame() { reset(); } void reset() { memset(field, 0, sizeof(field)); score = 0; gameOver = false; newPiece(); } void newPiece() { pieceType = random(7); pieceX = W / 2 - 2; pieceY = 0; pieceRot = 0; } bool canPlace(int px, int py, int type, int rot) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (getBlock(type, rot, i, j) && (px + j < 0 || px + j >= W || py + i >= H || field[py + i][px + j])) { return false; } } } return true; } bool getBlock(int type, int rot, int x, int y) { static const uint8_t pieces[7][4][4] = { {{0,1,0,0},{0,1,0,0},{0,1,0,0},{0,1,0,0}}, {{0,1,1,0},{0,1,0,0},{0,1,1,0},{0,0,0,0}}, {{0,1,1,0},{0,0,1,0},{0,1,1,0},{0,0,0,0}}, {{0,0,1,0},{0,0,1,0},{0,1,1,0},{0,0,0,0}}, {{0,1,0,0},{0,1,0,0},{0,1,1,0},{0,0,0,0}}, {{0,1,0,0},{0,1,0,0},{0,1,0,0},{0,1,0,0}}, {{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}} }; int r = rot % 4; if (type == 0) { const uint8_t I[4][4] = {{1,1,1,1},{0,0,0,0},{0,0,0,0},{0,0,0,0}}; if (r == 0 && y < 4 && x < 4) return I[y][x]; } if (y < 4 && x < 4) return pieces[type][r][y * 4 + x]; return 0; } void lockPiece() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (getBlock(pieceType, pieceRot, j, i)) { int fx = pieceX + j; int fy = pieceY + i; if (fy >= 0 && fy < H && fx >= 0 && fx < W) { field[fy][fx] = (pieceType + 1) * 30; } } } } clearLines(); newPiece(); } void clearLines() { for (int y = H - 1; y >= 0; y--) { bool full = true; for (int x = 0; x < W; x++) { if (!field[y][x]) { full = false; break; } } if (full) { score += 100; for (int yy = y; yy > 0; yy--) { for (int x = 0; x < W; x++) { field[yy][x] = field[yy - 1][x]; } } for (int x = 0; x < W; x++) field[0][x] = 0; } } } void render() { ledMatrixClear(); for (int y = 0; y < H && y < MATRIX_ROWS; y++) { for (int x = 0; x < W && x < MATRIX_COLS; x++) { if (field[y][x]) { ledMatrixSetPixel(x, y, field[y][x]); } } } for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (getBlock(pieceType, pieceRot, j, i)) { int px = pieceX + j; int py = pieceY + i; if (py >= 0 && py < MATRIX_ROWS && px >= 0 && px < MATRIX_COLS) { ledMatrixSetPixel(px, py, 255); } } } } } void update() { if (gameOver) return; unsigned long now = millis(); if (now - lastFall > 500) { lastFall = now; if (canPlace(pieceX, pieceY + 1, pieceType, pieceRot)) { pieceY++; } else { lockPiece(); if (!canPlace(pieceX, pieceY, pieceType, pieceRot)) { gameOver = true; } } } } void input() { if (buttonPressed(BUTTON_LEFT)) { recordActivity(); if (canPlace(pieceX - 1, pieceY, pieceType, pieceRot)) pieceX--; waitForButtonRelease(); } else if (buttonPressed(BUTTON_RIGHT)) { recordActivity(); if (canPlace(pieceX + 1, pieceY, pieceType, pieceRot)) pieceX++; waitForButtonRelease(); } else if (buttonPressed(BUTTON_UP)) { recordActivity(); if (canPlace(pieceX, pieceY, pieceType, (pieceRot + 1) % 4)) pieceRot = (pieceRot + 1) % 4; waitForButtonRelease(); } else if (buttonPressed(BUTTON_DOWN)) { recordActivity(); if (canPlace(pieceX, pieceY + 1, pieceType, pieceRot)) pieceY++; waitForButtonRelease(); } else if (buttonPressed(BUTTON_B)) { recordActivity(); currentMode = MODE_MENU; waitForButtonRelease(); } } }; class SnakeGame { public: static const int MAX_LEN = 30; int snakeX[MAX_LEN], snakeY[MAX_LEN]; int snakeLen; int dirX, dirY; int foodX, foodY; unsigned long lastMove; int score; bool gameOver; SnakeGame() { reset(); } void reset() { snakeLen = 3; for (int i = 0; i < snakeLen; i++) { snakeX[i] = 4; snakeY[i] = 4 - i; } dirX = 0; dirY = 1; score = 0; gameOver = false; spawnFood(); } void spawnFood() { foodX = random(MATRIX_COLS); foodY = random(MATRIX_ROWS); for (int i = 0; i < snakeLen; i++) { if (snakeX[i] == foodX && snakeY[i] == foodY) { spawnFood(); return; } } } void render() { ledMatrixClear(); ledMatrixSetPixel(foodX, foodY, 255); for (int i = 0; i < snakeLen; i++) { ledMatrixSetPixel(snakeX[i], snakeY[i], i == 0 ? 255 : 150); } } void update() { if (gameOver) return; unsigned long now = millis(); if (now - lastMove > 150) { lastMove = now; int headX = snakeX[0] + dirX; int headY = snakeY[0] + dirY; if (headX < 0 || headX >= MATRIX_COLS || headY < 0 || headY >= MATRIX_ROWS) { gameOver = true; return; } for (int i = 1; i < snakeLen; i++) { if (snakeX[i] == headX && snakeY[i] == headY) { gameOver = true; return; } } for (int i = snakeLen - 1; i > 0; i--) { snakeX[i] = snakeX[i - 1]; snakeY[i] = snakeY[i - 1]; } snakeX[0] = headX; snakeY[0] = headY; if (headX == foodX && headY == foodY) { if (snakeLen < MAX_LEN) snakeLen++; score += 10; spawnFood(); } } } void input() { if (buttonPressed(BUTTON_LEFT)) { recordActivity(); if (dirX != 1) { dirX = -1; dirY = 0; } waitForButtonRelease(); } else if (buttonPressed(BUTTON_RIGHT)) { recordActivity(); if (dirX != -1) { dirX = 1; dirY = 0; } waitForButtonRelease(); } else if (buttonPressed(BUTTON_UP)) { recordActivity(); if (dirY != 1) { dirX = 0; dirY = -1; } waitForButtonRelease(); } else if (buttonPressed(BUTTON_DOWN)) { recordActivity(); if (dirY != -1) { dirX = 0; dirY = 1; } waitForButtonRelease(); } else if (buttonPressed(BUTTON_B)) { recordActivity(); currentMode = MODE_MENU; waitForButtonRelease(); } } }; class BrickGame { public: static const int ROWS = 4; static const int COLS = 5; int bricks[ROWS][COLS]; int ballX, ballY, ballVX, ballVY; int paddleX; unsigned long lastUpdate; int score; bool gameOver; BrickGame() { reset(); } void reset() { memset(bricks, 0, sizeof(bricks)); for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { bricks[r][c] = (r + 1) * 40; } } ballX = 4; ballY = 4; ballVX = 1; ballVY = -1; paddleX = 4; score = 0; gameOver = false; } void render() { ledMatrixClear(); for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { if (bricks[r][c]) { ledMatrixSetPixel(c, r + 1, bricks[r][c]); } } } ledMatrixSetPixel(ballX, ballY, 255); ledMatrixSetPixel(paddleX, 8, 255); if (paddleX > 0) ledMatrixSetPixel(paddleX - 1, 8, 100); if (paddleX < 8) ledMatrixSetPixel(paddleX + 1, 8, 100); } void update() { if (gameOver) return; unsigned long now = millis(); if (now - lastUpdate > 200) { lastUpdate = now; ballX += ballVX; ballY += ballVY; if (ballX < 0 || ballX >= MATRIX_COLS) { ballVX = -ballVX; ballX += ballVX; } if (ballY < 0) { ballVY = -ballVY; ballY += ballVY; } if (ballY == 8) { if (abs(ballX - paddleX) <= 1) { ballVY = -ballVY; score += 10; } else { gameOver = true; } } int bx = ballX; int by = ballY; if (by >= 1 && by <= ROWS && bx >= 0 && bx < COLS) { if (bricks[by - 1][bx]) { bricks[by - 1][bx] = 0; ballVY = -ballVY; score += 10; } } } } void input() { if (buttonPressed(BUTTON_LEFT)) { recordActivity(); if (paddleX > 0) paddleX--; waitForButtonRelease(); } else if (buttonPressed(BUTTON_RIGHT)) { recordActivity(); if (paddleX < 8) paddleX++; waitForButtonRelease(); } else if (buttonPressed(BUTTON_B)) { recordActivity(); currentMode = MODE_MENU; waitForButtonRelease(); } } }; class PongGame { public: int leftY, rightY; int ballX, ballY, ballVX, ballVY; int leftScore, rightScore; unsigned long lastUpdate; bool gameOver; PongGame() { reset(); } void reset() { leftY = 4; rightY = 4; ballX = 4; ballY = 4; ballVX = 1; ballVY = random(2) ? 1 : -1; leftScore = 0; rightScore = 0; gameOver = false; } void render() { ledMatrixClear(); ledMatrixSetPixel(0, leftY, 255); ledMatrixSetPixel(8, rightY, 255); ledMatrixSetPixel(ballX, ballY, 255); } void update() { if (gameOver) return; unsigned long now = millis(); if (now - lastUpdate > 200) { lastUpdate = now; ballX += ballVX; ballY += ballVY; if (ballY < 0 || ballY >= MATRIX_ROWS) { ballVY = -ballVY; } if (ballX == 1 && abs(ballY - leftY) <= 1) { ballVX = -ballVX; leftScore++; } if (ballX == 7 && abs(ballY - rightY) <= 1) { ballVX = -ballVX; rightScore++; } if (ballX < 0 || ballX > 8) { gameOver = true; } } } void input() { if (buttonPressed(BUTTON_LEFT)) { recordActivity(); if (leftY > 0) leftY--; waitForButtonRelease(); } else if (buttonPressed(BUTTON_RIGHT)) { recordActivity(); if (rightY > 0) rightY--; waitForButtonRelease(); } else if (buttonPressed(BUTTON_UP)) { recordActivity(); rightY = leftY; if (rightY > 0) rightY--; waitForButtonRelease(); } else if (buttonPressed(BUTTON_DOWN)) { recordActivity(); rightY = leftY; if (rightY < 8) rightY++; waitForButtonRelease(); } else if (buttonPressed(BUTTON_A)) { recordActivity(); if (leftY < 8) leftY++; waitForButtonRelease(); } else if (buttonPressed(BUTTON_B)) { recordActivity(); currentMode = MODE_MENU; waitForButtonRelease(); } } }; TetrisGame tetrisGame; SnakeGame snakeGame; BrickGame brickGame; PongGame pongGame; void showIdleScreen() { ledMatrixClear(); int msgIdx = random(NUM_MESSAGES); drawTextCentered(RANDOM_MESSAGES[msgIdx], 4, 150); currentMode = MODE_IDLE; lastActivity = millis(); } void setup() { delay(1000); setupButtons(); setupFrontLEDs(); setupFlashlight(); setupSAO(); ledMatrixBegin(); ledMatrixClear(); showMenu(); lastActivity = millis(); } void loop() { unsigned long now = millis(); knightRiderSweep(80); updateSAO(now); updateTVBGone(now); if (anyButtonPressed()) { if (currentMode == MODE_IDLE) { currentMode = MODE_MENU; showMenu(); } lastActivity = millis(); } if (now - lastActivity > IDLE_TIMEOUT) { if (currentMode != MODE_IDLE && currentMode != MODE_TVBGONE) { showIdleScreen(); } } switch (currentMode) { case MODE_MENU: handleMenuInput(); break; case MODE_TETRIS: tetrisGame.input(); tetrisGame.update(); tetrisGame.render(); break; case MODE_SNAKE: snakeGame.input(); snakeGame.update(); snakeGame.render(); break; case MODE_BRICK: brickGame.input(); brickGame.update(); brickGame.render(); break; case MODE_PONG: pongGame.input(); pongGame.update(); pongGame.render(); break; case MODE_TVBGONE: if (buttonPressed(BUTTON_B)) { recordActivity(); currentMode = MODE_MENU; digitalWrite(FLASHLIGHT_LED, LOW); waitForButtonRelease(); showMenu(); } break; case MODE_IDLE: break; } delay(10); }