| Teil | Anzahl |
|---|
| Arduino Nano | 1x |
| ST7789 Display (240×320, SPI) | 1x |
| Button links | 1x |
| Button rechts | 1x |
| Stromversorgung (5V) | 1x |
| Bauteil | Arduino |
|---|
| Button links | D2 |
| Button rechts | D3 |
| Display Pin | Arduino Nano |
|---|
| GND | GND |
| VCC / VIN | 5V (oder 3.3V*) |
| SCL / CLK | D13 |
| SDA / MOSI | D11 |
| CS | D10 |
| DC | D9 |
| RST | D8 |
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
// --- Display-Pins ---
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
Adafruit_ST7789 tft(TFT_CS, TFT_DC, TFT_RST);
// --- Buttons (gegen GND, INPUT_PULLUP) ---
#define BTN_LEFT 2 // 90° gegen Uhrzeigersinn
#define BTN_RIGHT 3 // 90° im Uhrzeigersinn
// --- Spielfeld ---
static const uint8_t CELL = 8; // Pixel pro Zelle
static const uint8_t GRID_W = 240 / CELL; // 30
static const uint8_t GRID_H = 320 / CELL; // 40
// --- Farben ---
#define C_BG ST77XX_BLACK
#define C_SNAKE ST77XX_GREEN
#define C_HEAD ST77XX_YELLOW
#define C_FOOD ST77XX_RED
// --- Spielzustand ---
enum Dir { DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT };
struct Point { int8_t x; int8_t y; };
static const uint16_t SNAKE_MAX = 220; // RAM-schonend, aber ausreichend
Point snake[SNAKE_MAX];
uint16_t snakeLen = 5;
Dir curDir = DIR_RIGHT;
Point food = { -1, -1 };
bool gameOver = false;
// --- Timing ---
unsigned long lastTick = 0;
unsigned long tickMs = 120; // Geschwindigkeit
// --- Button-Handling mit Edge Detection ---
const unsigned long DEBOUNCE_MS = 80;
unsigned long lastDebounce = 0;
bool lastLeftState = HIGH;
bool lastRightState = HIGH;
// === Helpers ===
void drawCell(int x, int y, uint16_t color) {
tft.fillRect(x * CELL, y * CELL, CELL, CELL, color);
}
bool samePoint(const Point& a, const Point& b) {
return a.x == b.x && a.y == b.y;
}
bool inBounds(const Point& p) {
return p.x >= 0 && p.x < GRID_W && p.y >= 0 && p.y < GRID_H;
}
void placeFood() {
while (true) {
int8_t fx = random(GRID_W);
int8_t fy = random(GRID_H);
bool onSnake = false;
for (uint16_t i = 0; i < snakeLen; i++) {
if (snake[i].x == fx && snake[i].y == fy) { onSnake = true; break; }
}
if (!onSnake) {
food = { fx, fy };
drawCell(food.x, food.y, C_FOOD);
return;
}
}
}
void resetGame() {
tft.fillScreen(C_BG);
int8_t sx = GRID_W/2 - 2, sy = GRID_H/2;
snakeLen = 5;
for (uint16_t i = 0; i < snakeLen; i++) snake[i] = { (int8_t)(sx + i), sy };
curDir = DIR_RIGHT;
gameOver = false;
for (uint16_t i = 0; i < snakeLen; i++)
drawCell(snake[i].x, snake[i].y, i == snakeLen - 1 ? C_HEAD : C_SNAKE);
placeFood();
}
Dir rotateCCW(Dir d) {
switch (d) {
case DIR_UP: return DIR_LEFT;
case DIR_LEFT: return DIR_DOWN;
case DIR_DOWN: return DIR_RIGHT;
case DIR_RIGHT: return DIR_UP;
} return d;
}
Dir rotateCW(Dir d) {
switch (d) {
case DIR_UP: return DIR_RIGHT;
case DIR_RIGHT: return DIR_DOWN;
case DIR_DOWN: return DIR_LEFT;
case DIR_LEFT: return DIR_UP;
} return d;
}
// --- Buttons auswerten (Flanke erkennen) ---
void readTurnButtons() {
unsigned long now = millis();
if (now - lastDebounce < DEBOUNCE_MS) return;
bool leftNow = (digitalRead(BTN_LEFT) == LOW);
bool rightNow = (digitalRead(BTN_RIGHT) == LOW);
// gleichzeitig gedrückt -> ignorieren
if (leftNow && rightNow) {
lastLeftState = leftNow;
lastRightState = rightNow;
return;
}
// FALLENDE FLANKE links: HIGH -> LOW
if (leftNow && !lastLeftState) {
curDir = rotateCCW(curDir);
lastDebounce = now;
}
// FALLENDE FLANKE rechts: HIGH -> LOW
else if (rightNow && !lastRightState) {
curDir = rotateCW(curDir);
lastDebounce = now;
}
lastLeftState = leftNow;
lastRightState = rightNow;
}
void gameOverScreen() {
tft.fillScreen(C_BG);
tft.setCursor(40, 140);
tft.setTextSize(2);
tft.setTextColor(ST77XX_WHITE);
tft.print(F("GAME OVER"));
tft.setCursor(24, 170);
tft.print(F("Press any key..."));
}
void stepGame() {
if (gameOver) return;
Point head = snake[snakeLen - 1];
Point next = head;
switch (curDir) {
case DIR_UP: next.y--; break;
case DIR_DOWN: next.y++; break;
case DIR_LEFT: next.x--; break;
case DIR_RIGHT: next.x++; break;
}
if (!inBounds(next)) { gameOver = true; gameOverScreen(); return; }
for (uint16_t i = 0; i < snakeLen; i++) {
if (samePoint(snake[i], next)) { gameOver = true; gameOverScreen(); return; }
}
Point tail = snake[0];
drawCell(tail.x, tail.y, C_BG);
for (uint16_t i = 0; i < snakeLen - 1; i++) snake[i] = snake[i + 1];
snake[snakeLen - 1] = next;
drawCell(snake[snakeLen - 2].x, snake[snakeLen - 2].y, C_SNAKE);
drawCell(next.x, next.y, C_HEAD);
if (samePoint(next, food)) {
if (snakeLen < SNAKE_MAX) {
for (uint16_t i = snakeLen; i >= 1; i--) snake[i] = snake[i - 1];
snake[0] = tail; snakeLen++;
}
placeFood();
if (tickMs > 60) tickMs -= 4;
}
}
// === Arduino Lifecycle ===
void setup() {
pinMode(BTN_LEFT, INPUT_PULLUP);
pinMode(BTN_RIGHT, INPUT_PULLUP);
tft.init(240, 320);
tft.setRotation(2);
tft.fillScreen(C_BG);
randomSeed(analogRead(A0));
tft.setCursor(16, 120);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(2);
tft.print(F("SNAKE - Press key"));
while (digitalRead(BTN_LEFT) != LOW && digitalRead(BTN_RIGHT) != LOW) { }
resetGame();
lastTick = millis();
}
void loop() {
readTurnButtons();
unsigned long now = millis();
if (!gameOver) {
if (now - lastTick >= tickMs) {
lastTick = now;
stepGame();
}
} else {
if (digitalRead(BTN_LEFT) == LOW || digitalRead(BTN_RIGHT) == LOW) {
delay(200);
resetGame();
lastTick = millis();
}
}
}