/*
* SnakeGame.cpp
*
* Simply runs the Snake game. It can be controlled by 2 or 4 buttons or by serial input (WASD).
* The experimental Python script in the extras folder converts key presses and game controller input to appropriate serial output for the game.
*
* After 7 seconds of inactivity it runs the Snake demo with a simple AI.
* The game starts in 2 button mode, i.e. one button for turn left and one for turn right.
* If one of the up or down button is used, 4 button mode is entered automatically.
*
* You need to install "Adafruit NeoPixel" library under "Tools -> Manage Libraries..." or "Ctrl+Shift+I" -> use "neoPixel" as filter string
*
* Copyright (C) 2018-2025 Armin Joachimsmeyer
* [email protected]
*
* This file is part of NeoPatterns https://github.com/ArminJo/NeoPatterns.
*
* NeoPatterns is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
*
*/
#include <Arduino.h>
//#define DEBUG // implies also setting of INFO
//#define INFO
#define DO_NOT_SUPPORT_RGBW // saves up to 428 bytes additional program memory for the AllPatternsOnMultiDevices() example.
#define DO_NOT_SUPPORT_BRIGHTNESS // saves up to 428 bytes additional program memory for the AllPatternsOnMultiDevices() example.
//#define DO_NOT_SUPPORT_NO_ZERO_BRIGHTNESS // If activated, disables writing of zero only if brightness or color is zero. Saves up to 144 bytes ...
#define ENABLE_PATTERNS_FOR_SNAKE_AUTORUN
//#define SNAKE_ALLOW_SERIAL_CONTROL // control the snake direction with sending characters a,s,d,f over serial
#include <MatrixSnake.hpp>
// Delay between two SNAKE moves / Speed of game
#define GAME_REFRESH_INTERVAL 400
// Which pin on the Arduino is connected to the NeoPixels?
#define PIN_NEOPIXEL_MATRIX_SNAKE 8
#define MATRIX_NUMBER_OF_COLUMNS 8
#define MATRIX_NUMBER_OF_ROWS 8
#define BRIGHTNESS_INPUT_PIN A0
#define RIGHT_BUTTON_PIN 2
#define LEFT_BUTTON_PIN 3
// If one of the up or down button is used, 4 button mode is entered automatically.
#define UP_BUTTON_PIN 4
#define DOWN_BUTTON_PIN 5
/*
* Specify your matrix geometry as 4th parameter.
* ....BOTTOM ....RIGHT specify the position of the zeroth pixel.
* See MatrixNeoPatterns.h for further explanation.
*/
MatrixSnake NeoPixelMatrixSnake = MatrixSnake(MATRIX_NUMBER_OF_COLUMNS, MATRIX_NUMBER_OF_ROWS, PIN_NEOPIXEL_MATRIX_SNAKE,
NEO_MATRIX_BOTTOM | NEO_MATRIX_RIGHT | NEO_MATRIX_PROGRESSIVE, NEO_GRB + NEO_KHZ800);
/*
* Helper macro for getting a macro definition as string
*/
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
#if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/ \
|| defined(SERIALUSB_PID) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_attiny3217)
delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor!
#endif
// Just to know which program is running on my Arduino
Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_NEOPATTERNS));
Serial.println();
NeoPixelMatrixSnake.printConnectionInfo(&Serial);
Serial.println(
F(
"Allows 2 button mode, using only right and left button ( pin " STR(RIGHT_BUTTON_PIN) " or " STR(LEFT_BUTTON_PIN) " )"));
Serial.println(
F(
"If up or down button ( pin " STR(UP_BUTTON_PIN) " or " STR(DOWN_BUTTON_PIN) " ) is pressed (later), 4 button mode is entered automatically"));
Serial.println();
#if defined(SUPPORT_BRIGHTNESS)
uint8_t tBrightness = NeoPixel::gamma8(analogRead(BRIGHTNESS_INPUT_PIN) >> 2);
randomSeed(tBrightness); // Initialize random value :-)
Serial.println(F("Brightness voltage input is at pin " STR(BRIGHTNESS_INPUT_PIN)));
#else
Serial.println(F("Brightness voltage input at pin " STR(BRIGHTNESS_INPUT_PIN) " is disabled"));
uint8_t tBrightness = 0; // value is ignored :-)
#endif
// This initializes the NeoPixel library and checks if enough memory was available. true for EnableBrightnessNonZeroMode
if (!NeoPixelMatrixSnake.begin(&Serial, tBrightness, true)) {
Serial.println(F("Not enough memory for Snake matrix"));
// Blink forever as error indicator
while (true) {
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
}
// Prepare for 4 button mode. If up and down button are not connected
NeoPixelMatrixSnake.Snake(GAME_REFRESH_INTERVAL, COLOR32_BLUE, RIGHT_BUTTON_PIN, LEFT_BUTTON_PIN, UP_BUTTON_PIN,
DOWN_BUTTON_PIN);
}
void loop() {
static bool sButtonWasPressedOnce = false;
#if defined(SUPPORT_BRIGHTNESS)
static uint8_t sLastBrightness;
#endif
if (NeoPixelMatrixSnake.Direction != DIRECTION_NONE) {
// Direction is DIRECTION_NONE at start => Direction != NONE indicates a pressed button
sButtonWasPressedOnce = true;
}
if (!sButtonWasPressedOnce) {
/*
* Just wait for TIME_TO_SWITCH_TO_AUTO_MODE_MILLIS and then start snake autorun mode.
*/
long tMillis = millis();
if (tMillis > TIME_TO_SWITCH_TO_AUTO_MODE_MILLIS) {
// switch to demo mode after switching delay if snake has not moved
initSnakeAutorun(&NeoPixelMatrixSnake, GAME_REFRESH_INTERVAL / 2, COLOR32_BLUE);
}
}
#if defined(SUPPORT_BRIGHTNESS)
uint8_t tBrightness = NeoPixel::gamma8(analogRead(BRIGHTNESS_INPUT_PIN) >> 2);
if (abs(sLastBrightness - tBrightness) > (tBrightness / 16)) {
sLastBrightness = tBrightness;
Serial.print(F("Brightness="));
Serial.println(tBrightness);
NeoPixelMatrixSnake.updateOrRedraw(true, tBrightness);
} else {
NeoPixelMatrixSnake.updateOrRedraw(false, tBrightness);
}
#else
NeoPixelMatrixSnake.update(); // calls SnakeInputHandler()
#endif
delay(50);
}