831 lines
23 KiB
C++
831 lines
23 KiB
C++
#include <Arduino.h>
|
|
#include <Wire.h>
|
|
|
|
#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);
|
|
}
|