Hier habe ich einen Computer-Spielklassiker auf dem Arduino mit einer WS2812 RGB-LED-Matrix (sog. NeoPixel) nachprogrammiert.
Ziel des Spiels ist es die Spielfigur (=Schlange) möglichst lange am Leben zu erhalten und gleichzeitig
möglich viel Futter fressen zu lassen.
Man steuert eine Schlange über die LED-Matrix, die sich von selbst fortbewegt. Der Spieler kann lediglich die
Richtung des Kopfes ändern. Trifft man auf ein Stück Nahrung (grün), wird die Schlange ein Stück länger.
Berührt man mit dem Kopf der Schlange den Spielfeldrand oder den eigenen Körper, so ist das Spiel vorbei.
Die genaue Art des Aufbaus kann man aus dem Aufbau von WS2812-LEDs und Mikrotastern erfahren.
#define PIN_LED_MATRIX 2
#define PIN_BUTTON_UP 4
#define PIN_BUTTON_DOWN 5
#define PIN_BUTTON_LEFT 6
#define PIN_BUTTON_RIGHT 7
#define DEBOUNCE_TIME 300 // in ms
#define X_MAX 8
#define Y_MAX 8
#define GAME_DELAY 400 // in ms
#define LED_TYPE_SNAKE 1
#define LED_TYPE_OFF 2
#define LED_TYPE_FOOD 3
#define LED_TYPE_BLOOD 4
#define DIRECTION_NONE 0
#define DIRECTION_UP 1
#define DIRECTION_DOWN 2
#define DIRECTION_LEFT 3
#define DIRECTION_RIGHT 4
#define GAME_STATE_RUNNING 1
#define GAME_STATE_END 2
#define GAME_STATE_INIT 3
#define MAX_TAIL_LENGTH X_MAX * Y_MAX
#define MIN_TAIL_LENGTH 3
#include <Adafruit_NeoPixel.h>
struct Coords {
int x;
int y;
};
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(X_MAX*Y_MAX, PIN_LED_MATRIX, NEO_GRB + NEO_KHZ800);
byte incomingByte = 0;
byte userDirection;
byte gameState;
Coords head;
Coords tail[MAX_TAIL_LENGTH];
Coords food;
unsigned long lastDrawUpdate = 0;
unsigned long lastButtonClick;
unsigned int wormLength = 0;
void setup()
{
pinMode(PIN_BUTTON_UP, INPUT_PULLUP);
pinMode(PIN_BUTTON_DOWN, INPUT_PULLUP);
pinMode(PIN_BUTTON_LEFT, INPUT_PULLUP);
pinMode(PIN_BUTTON_RIGHT, INPUT_PULLUP);
Serial.begin(9600);
pixels.begin();
resetLEDs();
gameState = GAME_STATE_END;
}
void loop()
{
switch(gameState)
{
case GAME_STATE_INIT:
initGame();
break;
case GAME_STATE_RUNNING:
checkButtonPressed();
updateGame();
break;
case GAME_STATE_END:
checkButtonPressed();
break;
}
}
void resetLEDs()
{
for(int i=0; i<X_MAX*Y_MAX; i++) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
}
pixels.show();
}
void initGame()
{
resetLEDs();
head.x = 0;
head.y = 0;
food.x = -1;
food.y = -1;
wormLength = MIN_TAIL_LENGTH;
userDirection = DIRECTION_LEFT;
lastButtonClick = millis();
for(int i=0; i<MAX_TAIL_LENGTH; i++) {
tail[i].x = -1;
tail[i].y = -1;
}
updateFood();
gameState = GAME_STATE_RUNNING;
}
void updateGame()
{
if ((millis() - lastDrawUpdate) > GAME_DELAY) {
toggleLed(tail[wormLength-1].x, tail[wormLength-1].y, LED_TYPE_OFF);
switch(userDirection) {
case DIRECTION_RIGHT:
if (head.x > 0) {
head.x--;
}
break;
case DIRECTION_LEFT:
if (head.x < X_MAX-1) {
head.x++;
}
break;
case DIRECTION_DOWN:
if (head.y > 0) {
head.y--;
}
break;
case DIRECTION_UP:
if (head.y < Y_MAX-1) {
head.y++;
}
break;
}
if (isCollision() == true) {
endGame();
return;
}
updateTail();
if (head.x == food.x && head.y == food.y) {
if (wormLength < MAX_TAIL_LENGTH) {
wormLength++;
}
updateFood();
}
lastDrawUpdate = millis();
pixels.show();
}
}
void endGame()
{
gameState = GAME_STATE_END;
toggleLed(head.x, head.y, LED_TYPE_BLOOD);
pixels.show();
}
void updateTail()
{
for(int i=wormLength-1; i>0; i--) {
tail[i].x = tail[i-1].x;
tail[i].y = tail[i-1].y;
}
tail[0].x = head.x;
tail[0].y = head.y;
for(int i=0; i<wormLength; i++) {
if (tail[i].x > -1) {
toggleLed(tail[i].x, tail[i].y, LED_TYPE_SNAKE);
}
}
}
void updateFood()
{
bool found = true;
do {
found = true;
food.x = random(0, X_MAX);
food.y = random(0, Y_MAX);
for(int i=0; i<wormLength; i++) {
if (tail[i].x == food.x && tail[i].y == food.y) {
found = false;
}
}
} while(found == false);
toggleLed(food.x, food.y, LED_TYPE_FOOD);
}
bool isCollision()
{
if (head.x < 0 || head.x >= X_MAX) {
return true;
}
if (head.y < 0 || head.y >= Y_MAX) {
return true;
}
for(int i=1; i<wormLength; i++) {
if (tail[i].x == head.x && tail[i].y == head.y) {
return true;
}
}
return false;
}
void checkButtonPressed()
{
if (millis() - lastButtonClick < DEBOUNCE_TIME) {
return;
}
if (digitalRead(PIN_BUTTON_UP) == LOW) {
if(gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_UP;
}
lastButtonClick = millis();
} else if (digitalRead(PIN_BUTTON_DOWN) == LOW) {
if(gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_DOWN;
}
lastButtonClick = millis();
} else if (digitalRead(PIN_BUTTON_RIGHT) == LOW) {
if(gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_RIGHT;
}
lastButtonClick = millis();
} else if (digitalRead(PIN_BUTTON_LEFT) == LOW) {
if(gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_LEFT;
} else if (gameState == GAME_STATE_END) {
gameState = GAME_STATE_INIT;
}
lastButtonClick = millis();
}
}
void toggleLed(int x, int y, byte type)
{
int ledIndex = y * X_MAX + x;
uint32_t color;
switch(type) {
case LED_TYPE_SNAKE:
color = pixels.Color(0, 10, 10);
break;
case LED_TYPE_OFF:
color = pixels.Color(0, 0, 0);
break;
case LED_TYPE_FOOD:
color = pixels.Color(0, 15, 0);
break;
case LED_TYPE_BLOOD:
color = pixels.Color(15, 0, 0);
break;
}
pixels.setPixelColor(ledIndex, color);
}
Die zweite Version des Snake-Spiels kann man über eine serielle Konsolle (z.B. die der Arduino-IDE) steuern. Man benötigt also keine zusätzliche Eingabe-Hardware (Taster oder Knöpfe)
siehe den Aufbau der ersten Version, jedoch können die Mikrotaster inkl. deren Anschlüsse zum Arduino weggelassen werden.
/**
* (c) 2018 Christian Grieger
* GNU GENERAL PUBLIC LICENSE
*
* Snake game for Arduino and WS2812B-LED-Matrix
* with the serial console as controlling input
*
* Example for serial console in Linuxw/Debian:
* Init: screen /dev/ttyACM0 9600
* Exit: CTRL-A \
*
* Controls:
* b Begin new game
* p Pause running game
* w Snake up
* a Snake left
* s Snake down
* d Snake right
*/
#define PIN_LED_MATRIX 2
#define X_MAX 8
#define Y_MAX 8
#define GAME_DELAY 400 // in ms
#define LED_TYPE_SNAKE 1
#define LED_TYPE_OFF 2
#define LED_TYPE_FOOD 3
#define LED_TYPE_BLOOD 4
#define DIRECTION_NONE 0
#define DIRECTION_UP 1
#define DIRECTION_DOWN 2
#define DIRECTION_LEFT 3
#define DIRECTION_RIGHT 4
#define GAME_STATE_RUNNING 1
#define GAME_STATE_END 2
#define GAME_STATE_INIT 3
#define GAME_STATE_PAUSED 4
#define MAX_TAIL_LENGTH X_MAX * Y_MAX
#define MIN_TAIL_LENGTH 3
#include <Adafruit_NeoPixel.h>
struct Coords {
int x;
int y;
};
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(X_MAX*Y_MAX, PIN_LED_MATRIX, NEO_GRB + NEO_KHZ800);
byte incomingByte = 0;
byte userDirection;
byte gameState;
Coords head;
Coords tail[MAX_TAIL_LENGTH];
Coords food;
unsigned long lastDrawUpdate = 0;
unsigned int wormLength = 0;
void setup()
{
Serial.begin(9600);
pixels.begin();
resetLEDs();
gameState = GAME_STATE_END;
Serial.println("Snake game");
}
void loop()
{
switch(gameState)
{
case GAME_STATE_INIT:
initGame();
break;
case GAME_STATE_RUNNING:
updateGame();
break;
case GAME_STATE_PAUSED:
case GAME_STATE_END:
break;
}
}
void resetLEDs()
{
for(int i=0; i<X_MAX*Y_MAX; i++) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
}
pixels.show();
}
void initGame()
{
resetLEDs();
head.x = 0;
head.y = 0;
food.x = -1;
food.y = -1;
wormLength = MIN_TAIL_LENGTH;
userDirection = DIRECTION_LEFT;
for(int i=0; i<MAX_TAIL_LENGTH; i++) {
tail[i].x = -1;
tail[i].y = -1;
}
updateFood();
gameState = GAME_STATE_RUNNING;
}
void updateGame()
{
if ((millis() - lastDrawUpdate) > GAME_DELAY) {
toggleLed(tail[wormLength-1].x, tail[wormLength-1].y, LED_TYPE_OFF);
switch(userDirection) {
case DIRECTION_RIGHT:
if (head.x > 0) {
head.x--;
}
break;
case DIRECTION_LEFT:
if (head.x < X_MAX-1) {
head.x++;
}
break;
case DIRECTION_DOWN:
if (head.y > 0) {
head.y--;
}
break;
case DIRECTION_UP:
if (head.y < Y_MAX-1) {
head.y++;
}
break;
}
if (isCollision() == true) {
endGame();
return;
}
updateTail();
if (head.x == food.x && head.y == food.y) {
if (wormLength < MAX_TAIL_LENGTH) {
wormLength++;
}
updateFood();
}
lastDrawUpdate = millis();
pixels.show();
}
}
void endGame()
{
gameState = GAME_STATE_END;
toggleLed(head.x, head.y, LED_TYPE_BLOOD);
Serial.println("GAME OVER");
pixels.show();
}
void updateTail()
{
for(int i=wormLength-1; i>0; i--) {
tail[i].x = tail[i-1].x;
tail[i].y = tail[i-1].y;
Serial.println("tail " + String(tail[i].x) + " " + String(tail[i].y));
}
tail[0].x = head.x;
tail[0].y = head.y;
for(int i=0; i<wormLength; i++) {
if (tail[i].x > -1) {
toggleLed(tail[i].x, tail[i].y, LED_TYPE_SNAKE);
}
}
}
void updateFood()
{
bool found = true;
do {
found = true;
food.x = random(0, X_MAX);
food.y = random(0, Y_MAX);
for(int i=0; i<wormLength; i++) {
if (tail[i].x == food.x && tail[i].y == food.y) {
found = false;
}
}
} while(found == false);
toggleLed(food.x, food.y, LED_TYPE_FOOD);
}
bool isCollision()
{
if (head.x < 0 || head.x >= X_MAX) {
return true;
}
if (head.y < 0 || head.y >= Y_MAX) {
return true;
}
for(int i=1; i<wormLength; i++) {
if (tail[i].x == head.x && tail[i].y == head.y) {
return true;
}
}
return false;
}
void serialEvent()
{
while (Serial.available()) {
incomingByte = Serial.read();
}
switch(incomingByte) {
case 'b':
if (gameState == GAME_STATE_END) {
gameState = GAME_STATE_INIT;
Serial.println("BEGIN");
}
break;
case 'p':
if (gameState == GAME_STATE_PAUSED) {
gameState = GAME_STATE_RUNNING;
} else if(gameState == GAME_STATE_RUNNING) {
gameState = GAME_STATE_PAUSED;
Serial.println("PAUSE");
}
break;
case 'a':
if(gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_LEFT;
Serial.println("left");
}
break;
case 'd':
if(gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_RIGHT;
Serial.println("right");
}
break;
case 's':
if(gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_DOWN;
Serial.println("down");
}
break;
case 'w':
if(gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_UP;
Serial.println("up");
}
break;
}
}
void toggleLed(int x, int y, byte type)
{
int ledIndex = y * X_MAX + x;
uint32_t color;
switch(type) {
case LED_TYPE_SNAKE:
color = pixels.Color(0, 10, 10);
break;
case LED_TYPE_OFF:
color = pixels.Color(0, 0, 0);
break;
case LED_TYPE_FOOD:
color = pixels.Color(0, 15, 0);
break;
case LED_TYPE_BLOOD:
color = pixels.Color(15, 0, 0);
break;
}
pixels.setPixelColor(ledIndex, color);
}
Die dritte Version nutzt einen Game-Controller (SNES), um die Schlange zu steuern.
Für diesen Sketch wird die Library
ArduinoGameController
von bitluni verwendet. (Einfach die Datei GameControllers.h
der Library in denselben
Ordner wie die .ino
-Datei legen.)
#define PIN_LED_MATRIX 7
#define PIN_GC_LATCH 8
#define PIN_GC_CLOCK 9
#define PIN_GC_DATA 10
#define DEBOUNCE_TIME 300 // in ms
#define X_MAX 8
#define Y_MAX 8
#define GAME_DELAY 400 // in ms
#define LED_TYPE_SNAKE 1
#define LED_TYPE_OFF 2
#define LED_TYPE_FOOD 3
#define LED_TYPE_BLOOD 4
#define DIRECTION_NONE 0
#define DIRECTION_UP 1
#define DIRECTION_DOWN 2
#define DIRECTION_LEFT 3
#define DIRECTION_RIGHT 4
#define GAME_STATE_RUNNING 1
#define GAME_STATE_END 2
#define GAME_STATE_INIT 3
#define MAX_TAIL_LENGTH X_MAX * Y_MAX
#define MIN_TAIL_LENGTH 3
#include "GameControllers.h"
#include <Adafruit_NeoPixel.h>
struct Coords {
int x;
int y;
};
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(X_MAX*Y_MAX, PIN_LED_MATRIX, NEO_GRB + NEO_KHZ800);
GameControllers controllers;
byte incomingByte = 0;
byte userDirection;
byte gameState;
Coords head;
Coords tail[MAX_TAIL_LENGTH];
Coords food;
unsigned long lastDrawUpdate = 0;
unsigned long lastButtonClick;
unsigned int wormLength = 0;
void setup()
{
Serial.begin(9600);
controllers.init(PIN_GC_LATCH, PIN_GC_CLOCK);
controllers.setController(0, GameControllers::SNES, PIN_GC_DATA);
pixels.begin();
resetLEDs();
gameState = GAME_STATE_END;
}
void loop()
{
switch (gameState)
{
case GAME_STATE_INIT:
initGame();
break;
case GAME_STATE_RUNNING:
checkButtonPressed();
updateGame();
break;
case GAME_STATE_END:
checkButtonPressed();
break;
}
}
void resetLEDs()
{
for (int i = 0; i < X_MAX * Y_MAX; i++) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
}
pixels.show();
}
void initGame()
{
resetLEDs();
head.x = 0;
head.y = 0;
food.x = -1;
food.y = -1;
wormLength = MIN_TAIL_LENGTH;
userDirection = DIRECTION_LEFT;
lastButtonClick = millis();
for (int i = 0; i < MAX_TAIL_LENGTH; i++) {
tail[i].x = -1;
tail[i].y = -1;
}
updateFood();
gameState = GAME_STATE_RUNNING;
}
void updateGame()
{
if ((millis() - lastDrawUpdate) > GAME_DELAY) {
toggleLed(tail[wormLength - 1].x, tail[wormLength - 1].y, LED_TYPE_OFF);
switch (userDirection) {
case DIRECTION_RIGHT:
if (head.x > 0) {
head.x--;
}
break;
case DIRECTION_LEFT:
if (head.x < X_MAX - 1) {
head.x++;
}
break;
case DIRECTION_DOWN:
if (head.y > 0) {
head.y--;
}
break;
case DIRECTION_UP:
if (head.y < Y_MAX - 1) {
head.y++;
}
break;
}
if (isCollision() == true) {
endGame();
return;
}
updateTail();
if (head.x == food.x && head.y == food.y) {
if (wormLength < MAX_TAIL_LENGTH) {
wormLength++;
}
updateFood();
}
lastDrawUpdate = millis();
pixels.show();
}
}
void endGame()
{
gameState = GAME_STATE_END;
toggleLed(head.x, head.y, LED_TYPE_BLOOD);
pixels.show();
}
void updateTail()
{
for (int i = wormLength - 1; i > 0; i--) {
tail[i].x = tail[i - 1].x;
tail[i].y = tail[i - 1].y;
}
tail[0].x = head.x;
tail[0].y = head.y;
for (int i = 0; i < wormLength; i++) {
if (tail[i].x > -1) {
toggleLed(tail[i].x, tail[i].y, LED_TYPE_SNAKE);
}
}
}
void updateFood()
{
bool found = true;
do {
found = true;
food.x = random(0, X_MAX);
food.y = random(0, Y_MAX);
for (int i = 0; i < wormLength; i++) {
if (tail[i].x == food.x && tail[i].y == food.y) {
found = false;
}
}
} while (found == false);
toggleLed(food.x, food.y, LED_TYPE_FOOD);
}
bool isCollision()
{
if (head.x < 0 || head.x >= X_MAX) {
return true;
}
if (head.y < 0 || head.y >= Y_MAX) {
return true;
}
for (int i = 1; i < wormLength; i++) {
if (tail[i].x == head.x && tail[i].y == head.y) {
return true;
}
}
return false;
}
void checkButtonPressed()
{
if (millis() - lastButtonClick < DEBOUNCE_TIME) {
return;
}
controllers.poll();
if (controllers.pressed(0, GameControllers::START)) {
lastButtonClick = millis();
if (gameState == GAME_STATE_END) {
gameState = GAME_STATE_INIT;
}
} else if (controllers.pressed(0, GameControllers::UP)) {
if (gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_UP;
}
lastButtonClick = millis();
} else if (controllers.pressed(0, GameControllers::DOWN)) {
if (gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_DOWN;
}
lastButtonClick = millis();
} else if (controllers.pressed(0, GameControllers::LEFT)) {
if (gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_LEFT;
}
lastButtonClick = millis();
} else if (controllers.pressed(0, GameControllers::RIGHT)) {
if (gameState == GAME_STATE_RUNNING) {
userDirection = DIRECTION_RIGHT;
}
lastButtonClick = millis();
}
}
void toggleLed(int x, int y, byte type)
{
int ledIndex = y * X_MAX + x;
uint32_t color;
switch (type) {
case LED_TYPE_SNAKE:
color = pixels.Color(0, 10, 10);
break;
case LED_TYPE_OFF:
color = pixels.Color(0, 0, 0);
break;
case LED_TYPE_FOOD:
color = pixels.Color(0, 15, 0);
break;
case LED_TYPE_BLOOD:
color = pixels.Color(15, 0, 0);
break;
}
pixels.setPixelColor(ledIndex, color);
}
Nachdem ich eine 16×16-Matrix mit WS2812b-LEDs erhalten hatte, lag es nahe, Snake auch für dieses
Matrix auszuprobieren. Allerdings kam eine erste Schwierigkeit auf, denn die Matrix hatte eine andere
Reihenfolge der LEDs, so dass ein zusätzliches Mapping notwendig war. Daher wurde die Funktion
getLedMapping()
eingeführt. Je nach verwendeter Matrix muss diese Funktion entsprechend angepasst
werden. Zusätzlich ergab sich noch das Problem des geringen Arbeitsspeichers, der bei einer 16×16 Matrix
knapp wurde. Durch ein paar Optimierungen lief das Spiel aber letztendlich gut in dieser Größe.
Hinweis: Es sollte hier (wie auch bei der 8x8 Matrix) eine externe Spannungsquelle
verwendet werden, um den Arduino nicht zu überlasten bzw. zu beschädigen!
#define PIN_LED_MATRIX 7
#define PIN_GC_LATCH 8
#define PIN_GC_CLOCK 9
#define PIN_GC_DATA 10
#define DEBOUNCE_TIME 300 // in ms
#define lmillis() ((long)millis())
#define X_MAX 16
#define Y_MAX 16
#define GAME_DELAY 400 // in ms
#define LED_TYPE_SNAKE 1
#define LED_TYPE_OFF 2
#define LED_TYPE_FOOD 3
#define LED_TYPE_BLOOD 4
#define DIRECTION_NONE 0
#define DIRECTION_UP 1
#define DIRECTION_DOWN 2
#define DIRECTION_LEFT 3
#define DIRECTION_RIGHT 4
#define GAME_STATE_RUNNING 1
#define GAME_STATE_END 2
#define GAME_STATE_INIT 3
#define MAX_TAIL_LENGTH X_MAX*Y_MAX
#define MIN_TAIL_LENGTH 3
#include "GameControllers.h"
#include <Adafruit_NeoPixel.h>
struct Coords {
byte x;
byte y;
};
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(MAX_TAIL_LENGTH, PIN_LED_MATRIX, NEO_GRB + NEO_KHZ800);
GameControllers controllers;
byte userDirection, gameState;
Coords head, food, tail[MAX_TAIL_LENGTH];
long lastDrawUpdate = 0, lastButtonClick = 0;
short wormLength = 0;
void setup()
{
controllers.init(PIN_GC_LATCH, PIN_GC_CLOCK);
controllers.setController(0, GameControllers::SNES, PIN_GC_DATA);
pixels.begin();
resetLEDs();
gameState = GAME_STATE_END;
}
void loop()
{
switch (gameState) {
case GAME_STATE_INIT:
initGame();
break;
case GAME_STATE_RUNNING:
checkButtonPressed();
updateGame();
break;
case GAME_STATE_END:
checkButtonPressed();
break;
}
}
void resetLEDs()
{
for (int i = 0; i < MAX_TAIL_LENGTH; i++) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
}
pixels.show();
}
void initGame()
{
resetLEDs();
head.x = 0;
head.y = 0;
food.x = 0;
food.y = 0;
wormLength = MIN_TAIL_LENGTH;
userDirection = DIRECTION_LEFT;
for (int i = 0; i < MAX_TAIL_LENGTH; i++) {
tail[i].x = -1;
tail[i].y = -1;
}
updateFood();
gameState = GAME_STATE_RUNNING;
}
void updateGame()
{
if (lmillis() - lastDrawUpdate < 0) {
return;
}
toggleLed(tail[wormLength - 1].x, tail[wormLength - 1].y, LED_TYPE_OFF);
switch (userDirection) {
case DIRECTION_RIGHT:
if (head.x > 0) {
head.x--;
}
break;
case DIRECTION_LEFT:
if (head.x < X_MAX - 1) {
head.x++;
}
break;
case DIRECTION_DOWN:
if (head.y > 0) {
head.y--;
}
break;
case DIRECTION_UP:
if (head.y < Y_MAX - 1) {
head.y++;
}
break;
}
if (isCollision() == true) {
endGame();
return;
}
updateTail();
if (head.x == food.x && head.y == food.y) {
if (wormLength < MAX_TAIL_LENGTH) {
wormLength++;
}
updateFood();
}
lastDrawUpdate = lmillis() + GAME_DELAY;
pixels.show();
}
void endGame()
{
gameState = GAME_STATE_END;
toggleLed(head.x, head.y, LED_TYPE_BLOOD);
pixels.show();
}
void updateTail()
{
for (int i = wormLength - 1; i > 0; i--) {
tail[i].x = tail[i - 1].x;
tail[i].y = tail[i - 1].y;
}
tail[0].x = head.x;
tail[0].y = head.y;
for (int i = 0; i < wormLength; i++) {
if (tail[i].x > -1) {
toggleLed(tail[i].x, tail[i].y, LED_TYPE_SNAKE);
}
}
}
void updateFood()
{
bool found = true;
do {
found = true;
food.x = random(0, X_MAX);
food.y = random(0, Y_MAX);
for (int i = 0; i < wormLength; i++) {
if (tail[i].x == food.x && tail[i].y == food.y) {
found = false;
}
}
} while (found == false);
toggleLed(food.x, food.y, LED_TYPE_FOOD);
}
bool isCollision()
{
if (head.x < 0 || head.x >= X_MAX) {
return true;
}
if (head.y < 0 || head.y >= Y_MAX) {
return true;
}
for (int i = 1; i < wormLength; i++) {
if (tail[i].x == head.x && tail[i].y == head.y) {
return true;
}
}
return false;
}
void checkButtonPressed()
{
if (lmillis() - lastButtonClick < 0) {
return;
}
controllers.poll();
if (gameState == GAME_STATE_END && controllers.pressed(0, GameControllers::START)) {
gameState = GAME_STATE_INIT;
lastButtonClick = lmillis() + DEBOUNCE_TIME;
}
if (gameState == GAME_STATE_RUNNING) {
if (controllers.pressed(0, GameControllers::UP)) {
userDirection = DIRECTION_UP;
lastButtonClick = lmillis() + DEBOUNCE_TIME;
} else if (controllers.pressed(0, GameControllers::DOWN)) {
userDirection = DIRECTION_DOWN;
lastButtonClick = lmillis() + DEBOUNCE_TIME;
} else if (controllers.pressed(0, GameControllers::LEFT)) {
userDirection = DIRECTION_RIGHT;
lastButtonClick = lmillis() + DEBOUNCE_TIME;
} else if (controllers.pressed(0, GameControllers::RIGHT)) {
userDirection = DIRECTION_LEFT;
lastButtonClick = lmillis() + DEBOUNCE_TIME;
}
}
}
void toggleLed(int x, int y, byte type)
{
uint32_t color;
switch (type) {
case LED_TYPE_SNAKE:
color = pixels.Color(0, 10, 10);
break;
case LED_TYPE_OFF:
color = pixels.Color(0, 0, 0);
break;
case LED_TYPE_FOOD:
color = pixels.Color(0, 15, 0);
break;
case LED_TYPE_BLOOD:
color = pixels.Color(15, 0, 0);
break;
}
pixels.setPixelColor(getLedMapping(y * X_MAX + x), color);
}
short getLedMapping(int searchIndex)
{
int *tmp[X_MAX];
int mapIdx = 0;
int ledIndex = 0;
for (int x = 0; x < X_MAX; x++) {
for (int y = 0; y < Y_MAX; y++) {
tmp[y] = x * X_MAX + y;
}
if (x % 2 == 1) {
for (int i = (X_MAX - 1); i >= 0; i--) {
ledIndex = tmp[i];
mapIdx++;
if (mapIdx == searchIndex) {
return ledIndex;
}
}
} else {
for (int i = 0; i < X_MAX; i++) {
ledIndex = tmp[i];
mapIdx++;
if (mapIdx == searchIndex) {
return ledIndex;
}
}
}
}
}