// Original by Anderson Costa:
// https://wokwi.com/arduino/projects/286181658485326348
//
// Modified by Tim2000:
// - Added arrow keys and labels to buttons. After clicking the green Start simulation button, click on the schematic so that key presses are sent to the buttons rather than the text editor.
// - Changed display from wokwi-neopixel-matrix to wokwi-neopixel-canvas.
#include <FastLED.h>
#include "Snake.h"
#define NUM_LEDS 135
#define LEDS_PER_ROW 15
#define DATA_PIN 6
#define SPEAKER_PIN 8
#define BTN_LEFT 2
#define BTN_RIGHT 4
#define BTN_UP 5
#define BTN_DOWN 3
#define NOTE_G3 196
#define NOTE_C4 262
#define NOTE_C5 523
#define NOTE_E4 330
#define NOTE_G4 392
#define NOTE_G5 784
// Initialize the snake field with x=15, y=9, delay=10 ticks
Snake snakeGame(15, 9, 10);
CRGB leds[NUM_LEDS];
unsigned long timerTone = 0;
unsigned long timerReset = 0;
unsigned long timerRefresh = 0;
unsigned long timerMoveTone = 0;
unsigned long timerSnakeRose = 0;
bool isMoveTone = false;
bool isGameReset = false;
bool isMoveToneStop = false;
bool isSnakeRose = false;
bool isSnakeRoseEnd = false;
int lastSnakeLength = 0;
uint8_t direction = 0;
uint8_t lastDirection = 0;
void setup() {
pinMode(BTN_LEFT, INPUT_PULLUP);
pinMode(BTN_RIGHT, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
snakeGame.setBodyColor(255, 0, 255); // Optionally set the color of the snakeparts
snakeGame.setFoodColor(0, 60, 125); // Optionally set the color of the food
snakeGame.setHeadColor(225, 20, 60); // Optionally set the color of the snakeparts
lastSnakeLength = snakeGame.getSnakeLength();
delay(1000);
FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
}
void inputEvent() {
if (digitalRead(BTN_LEFT) == LOW) {
snakeGame.goLeft(); // Snake will go left on the next move
isMoveTone = true;
direction = LEFT;
}
if (digitalRead(BTN_RIGHT) == LOW) {
snakeGame.goRight(); // Snake will go right on the next move
isMoveTone = true;
direction = RIGHT;
}
if (digitalRead(BTN_UP) == LOW) {
snakeGame.goUp(); // Snake will go up on the next move
isMoveTone = true;
direction = UP;
}
if (digitalRead(BTN_DOWN) == LOW) {
snakeGame.goDown(); // Snake will go down on the next move
isMoveTone = true;
direction = DOWN;
}
// Checks whether the snake has changed position
if (isMoveTone && (direction != lastDirection)) {
isMoveTone = false;
isMoveToneStop = true;
lastDirection = direction;
tone(SPEAKER_PIN, NOTE_G4);
timerMoveTone = millis();
}
if ((isMoveToneStop) && ((millis() - timerMoveTone) >= 30)) {
isMoveToneStop = false;
noTone(SPEAKER_PIN);
}
}
byte setPixel(byte x, byte y, byte r, byte g, byte b)
{
byte ledID = NUM_LEDS - (y * LEDS_PER_ROW) - x - 1 ;
leds[ledID].setRGB(r, g, b);
return ledID;
}
void changeRGBtoGBR()
{
for (int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed++)
{
leds[whiteLed].setRGB(leds[whiteLed].g, leds[whiteLed].b, leds[whiteLed].r);
}
}
void clearScreen()
{
for (int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed++)
{
leds[whiteLed].setRGB( 3, 3, 3);
}
}
void loop()
{
inputEvent();
if (!snakeGame.wasGameReset() && (millis() - timerRefresh > 30)) {
timerRefresh = millis();
// This needs to be updated every frame
Snake::pixel* snakeLimbs = snakeGame.getSnakeLimbs();
Snake::pixel* snakeFood = snakeGame.getFoodPositions();
clearScreen();
setPixel(snakeFood[0].posX, 8 - snakeFood[0].posY, snakeFood[0].pixelColor.r, snakeFood[0].pixelColor.g, snakeFood[0].pixelColor.b); // display the food
for (int i = 0; i < snakeGame.getSnakeLength(); i++)
{
// Display the snake, my setpixel method has x=0, y=0 at the top left, but the library has it at bottom left, so I invert the Y-Axis:
setPixel(snakeLimbs[i].posX, 8 - snakeLimbs[i].posY, snakeLimbs[i].pixelColor.r, snakeLimbs[i].pixelColor.g, snakeLimbs[i].pixelColor.b);
}
if (lastSnakeLength != snakeGame.getSnakeLength()) {
lastSnakeLength = snakeGame.getSnakeLength();
isSnakeRose = true;
tone(SPEAKER_PIN, NOTE_G4);
timerSnakeRose = millis();
}
FastLED.show();
snakeGame.tick(); // Main loop for the snake library
}
if ((isSnakeRose) && (millis() - timerSnakeRose) >= 30) {
isSnakeRose = false;
tone(SPEAKER_PIN, NOTE_G5);
isSnakeRoseEnd = true;
timerSnakeRose = millis();
}
if ((isSnakeRoseEnd) && (millis() - timerSnakeRose) >= 30) {
isSnakeRoseEnd = false;
noTone(SPEAKER_PIN);
}
// If the snake bit itself or the wall, flash a little
if (snakeGame.wasGameReset()) {
changeRGBtoGBR();
FastLED.show();
isGameReset = true;
tone(SPEAKER_PIN, NOTE_G3);
timerReset = millis();
}
if ((isGameReset) && (millis() - timerReset) >= 200) {
isGameReset = false;
noTone(SPEAKER_PIN);
}
}