Snake Game

TeilAnzahl
Arduino Nano1x
ST7789 Display (240×320, SPI)1x
Button links1x
Button rechts1x
Stromversorgung (5V)1x
BauteilArduino
Button linksD2
Button rechtsD3
Display PinArduino Nano
GNDGND
VCC / VIN5V (oder 3.3V*)
SCL / CLKD13
SDA / MOSID11
CSD10
DCD9
RSTD8
#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();
    }
  }
}

Schreibe einen Kommentar