Pixel Chase: a Retro Arcade Game With ESP32 / Arduino

by anoofc in Circuits > Arduino

588 Views, 5 Favorites, 0 Comments

Pixel Chase: a Retro Arcade Game With ESP32 / Arduino

Untitled (1000 x 800 px).jpg

Pixel Chase, a fun and engaging arcade game built with the ESP32/Arduino microcontroller!

The Challenge:

Pixel Chase presents a simple yet addictive gameplay experience. You'll see four LEDs arranged in a circle. Three will remain stationary, while the fourth will continually move around the perimeter. Your objective? Time your button presses perfectly!

  1. Start the Game: A single press of the button initiates the challenge.
  2. Capture the Pixel: As the moving LED ventures inside the stationary circle formation, press the button again. Capture it at the right moment to score.
  3. Success or Failure?
  4. Correct Timing: If you hit the button with precision, the next color in the sequence will flash momentarily before the moving pixel resumes its circular dance.
  5. Wrong Timing: A misstep results in game over.
  6. Scoring System: Every successful capture updates the score displayed on the 7-segment display. With each point, the moving pixel speeds up, increasing the difficulty.
  7. Victory! Achieve a score of 20, and you'll be rewarded with a dazzling rainbow animation on the LED ring, celebrating your triumph!


Building Your Own Pixel Chase?

This post will guide you through building your very own Pixel Chase game with the ESP32 or Arduino. We'll delve into the required hardware, software setup, and code breakdown, equipping you to create a captivating retro experience. Get ready to unleash your inner arcade champion!

Supplies

Hardware components

Espressif ESP32 Development Board - Developer Edition Espressif ESP32 Development Board - Developer Edition

WS2813 RGB LED Strip Waterproof - 60 LED/m - 1m

Big Red Dome Button SparkFun Big Red Dome Button

7 Segment LED Display, InfoVue 7 Segment LED Display Common Cathod 2 inch

74HC595N Serial In Parallel Out Shift Register IC

5V 2.5A Switching Power Supply 5V 2.5A Switching Power Supply

Hook Up Wire Kit, 22 AWG Hook Up Wire Kit, 22 AWG

Ceramic Disc Capacitor, 0.1 µF Ceramic Disc Capacitor, 0.1 µF

Dot PCB Generic

Software apps and online services

Arduino IDE Arduino IDE

Hand tools and fabrication machines

Wire Stripper, Automatic Wire Stripper

Soldering iron (generic) Soldering iron (generic)

Solder Wire, Lead Free Solder Wire, Lead Free

Get Hardware

  1. Microcontroller: Any Arduino-based microcontroller will be good for this project. I'm using the ESP32 Dev Module from Espressif so that I can expand this project by adding more features by using the possibilities of Wireless communication methods of the ESP32 Module.
  2. Neopixel LED Strip: You'll need a 60 LED/m Addressable LED Strip (Neopixel) with 5v Input voltage. These will be arranged in a circular pattern to create the visual elements of the game.
  3. Push Button: A giant push button will be used to start the game and capture the moving pixel. A 0.1uf (104) Ceramic capacitor is better to be used to avoid mis-triggering of the button due to noise issues.
  4. 7 Segment Display: This will be used to display the player's score throughout the game. I'm using two Large 2" Common Cathode Displays for displaying two-digit scores.
  5. 74HC595N Shift Register: 74HC595N is an 8-bit Serial in, Serial/Parallel out shift register with output latches. You can use it to control 8 outputs at a time while only taking up a few pins on your microcontroller. You can link multiple registers together to extend your output even more. These qualities make it the right choice for driving a 7-segment Display.
  6. Current-Limiting Resistors: Appropriate resistors will be needed to protect the Display segments and ensure they operate within their specified current range. I've taken 100 ohms Resistors as my display is quite large and the IC I'm using to drive cannot supply that much current to burn the LED segments.
  7. 5V Power Supply: A 5V power supply will provide the necessary voltage to power the ESP32, Neopixel Strip, 7-segment display, and other components.
  8. Enclosure: An enclosure can be used to house the components and give the game a more finished appearance. I'm using a structure made by CNC Engraving an MDF wood piece. My colleague Praveen designed the file for CNC engraving Using Solid Works Software. You can Design your enclosure and 3D print it if you don't have access to a CNC Engraving Machine. I will attach the Solid Works file for you if you want to use the same design as me.
  9. Diffusing Top Cover: I used a Light pass Acrylic Sheet with 2 mm thickness cut using laser machine. Also I used small PVC Pieces to separate individual LED pixels.


Connecting Hardware

  1. Push Button: Connect the Push Button to GPIO 26 of ESP32, or any other pin as you like.
  2. LED Strip: Connect the data pin of LED strip to GPIO27, +ve to 5v and Gnd to Ground of ESP32. Be mindful of the Data flow of the Neopixel strip should be as per the arrow mark in the LED Strip. Refer the Connection Diagram if you have any doubts.
  3. 5vAdapter: To power up the whole LED strip and ESP32, I'm using a 5v 2A power adapter, I cut the wire and added a JST connector, so that I can connect easily.
  4. 7 Segment Display: Connect the 7 Segment display Common pins to ground as I'm using a common cathode one. If you have the common anode type connect common to the 5v pin. We need a shift register to drive the 7 Segment Display. I have taken a 74HC595N 8 bit Shift register. Connect the Data pin (SER/14) to the GPIO 23, Clock to GPIO19 and Latch Pin to GPIO21. Refer to the Circuit diagram for more info. For the second digit, everything goes the same except the Data pin is connected to the Data out of first 74hc595 IC.

Software Setup

Configuring Arduino IDE for ESP32 Development

  1. Download and Install Arduino IDE: Visit the Arduino website: Go to https://www.arduino.cc/en/Main/Software. Download the latest version: Choose the appropriate version for your operating system (Windows, macOS, or Linux).
  2. Install the IDE: Follow the on-screen instructions to complete the installation process.
  3. Install ESP32 Board Support: Open Arduino IDE: Launch the Arduino IDE.
  4. Go to "File" -> "Preferences": This will open the preferences window.
  5. Add the ESP32 board manager URL: In the "Additional Boards Manager URLs" field, enter the following URL: https://dl.espressif.com/package/esp32/index.json
  6. Click "OK": Save the changes.
  7. Install ESP32 Board: Go to "Tools" -> "Board": This will open the board manager. Search for "ESP32": Type "ESP32" in the search bar.
  8. Click the "Install" button to download and install the necessary libraries and tools.
  9. Select your ESP32 board: Go to Tools -> Boards -> ESP32 and Choose the specific ESP32 board model you are using (e.g., ESP32-DevKitC, ESP32-Wrover-B).
  10. Select the correct port: Connect your ESP32: Connect your ESP32 board to your computer using a USB cable. Check the port: Go to "Tools" -> "Port" and select the correct serial port that corresponds to your ESP32 board. You may need to consult your device manager or system settings to identify the correct port.
  11. Verify the installation: Create a simple blink example: Open a new Arduino sketch and paste the following code:
void setup() {
pinMode(2, OUTPUT);
}
void loop() {
digitalWrite(2, HIGH); // turn the LED on (HIGH is on).
delay(1000); // wait for a second.
digitalWrite(2, LOW); // turn the LED off (LOW is off).
delay(1000); // wait for a second.
}
  1. Upload the sketch: Click the "Upload" button to compile and upload the sketch to your ESP32.
  2. Observe the LED: If the built-in LED on your ESP32 board blinks, the installation was successful.

Additional tips:

Update the IDE: Keep your Arduino IDE updated to ensure compatibility with the latest ESP32 features and bug fixes.
Consult documentation: Refer to the official ESP32 documentation and community forums for more in-depth information and troubleshooting tips.

Code Breakdown

Libraries Used

  1. Adafruit_NeoPixel.h: Library to control NeoPixel LED strips.
  2. ShiftRegister74HC595.h: Library for controlling shift registers like 74HC595.
#include <Arduino.h> // Include the Arduino library
#include <Adafruit_NeoPixel.h> // Include the Adafruit NeoPixel library
#include <ShiftRegister74HC595.h> // Include Shift Register Libtrary

Pin Configuration

  1. Button Pin: GPIO 26 (BUTTON_PIN)
  2. NeoPixel Pin: GPIO 27 (PIXEL_PIN)
#define BUTTON_PIN 18 // Button pin
#define PIXEL_PIN 27 // Neopixel pin
  1. Shift Register Pins: Data - GPIO 23, Clock - GPIO 21, Latch - GPIO 19
ShiftRegister74HC595 sr (2, 23, 21, 19);

Constants:

  1. NUM_PIXELS - Total number of pixels in your strip
  2. SPEED - Initial Speed (Delay) of the running pixel. Decreases as level up.
  3. FINAL_LEVEL - Final Speed (Delay) of the running pixel.
#define NUM_PIXELS 51 // Number of neopixels
#define SPEED 105 // Speed of the light
#define FINAL_LEVEL 10 // Final level of the speed
#define LEVEL 5 // How much the speed increase

Include Necessary Libraries:

#include <Arduino.h> // Include the Arduino library
#include <Adafruit_NeoPixel.h> // Include the Adafruit NeoPixel library
#include <ShiftRegister74HC595.h>

Create Objects for Initializing Shift register and Neopixel.

ShiftRegister74HC595 sr (2, 23, 21, 19);
Adafruit_NeoPixel pixels(NUM_PIXELS, PIXEL_PIN, NEO_GRB + NEO_KHZ800); // Create a NeoPixel object

Global Variables:

uint8_t digit1, digit2; // Variables for the digits
// Number array for the 7 segment display
uint8_t numberB[] = {
B10101111, // 0
B00001100, // 1
B11000111, // 2
B11001110, // 3
B01101100, // 4
B11101010, // 5
B11101011, // 6
B00001110, // 7
B11101111, // 8
B11101110 // 9
};
uint8_t score = 0; // Score
int8_t x, y, z; // Target color position
int8_t num = 0; // Current color position
int8_t last_num = 0; // Last color position
int now_color = 0; // Current color
int next_color = 1; // Next color
int speed = SPEED; // Speed of the light
uint8_t level = LEVEL; // How much the speed will increase
bool new_target = true; // New target color
bool button_state = false; // Button state
bool game_running = false; // Game state
unsigned long last_time = 0; // Last time of the light

uint32_t colors[] = { // Colors
pixels.Color(255, 0, 0), // RED
pixels.Color(255, 165, 0), // ORANGE
pixels.Color(0, 255, 0), // GREEN
pixels.Color(0, 255, 255), // CYAN
pixels.Color(0, 0, 255), // BLUE
pixels.Color(255, 255, 0), // YELLOW
pixels.Color(128, 0, 128), // PURPLE
pixels.Color(0, 128, 128), // TEAL
pixels.Color(255, 0, 255), // MAGENTA
pixels.Color(0, 255, 128), // SPRING GREEN
pixels.Color(0, 255, 255), // AQUA
pixels.Color(255, 0, 128) // ROSE
};

Blink the Seven Segment Display 3 times

void blink(){
for(int i = 0; i<3; i++){
sr.setAllHigh(); // set all pins LOW
delay(300);
sr.setAllLow(); // set all pins HIGH
delay(300);
}
}

Display Score on Seven Segment Display:

This function takes an unsigned 16-bit integer score as input and displays it on the screen. It extracts the individual digits of the score and maps them to corresponding binary patterns stored in the numberB array. The digits are then passed to the sr.setAll() function to display the score on the screen.

void displayScore(uint16_t score) {
digit1=score % 10 ;
digit2=(score / 10) % 10 ;
uint8_t numberToPrint[]= {numberB[digit1],numberB[digit2]};
sr.setAll(numberToPrint);
}

Wheel Function to create a rainbow color:

  1. This function takes a wheel position as input and returns a color value based on the position.
  2. The wheel position is used to calculate the RGB values of the color.
  3. The color generated follows a specific pattern where the RGB values change based on the wheel position.
  4. byte WheelPos is the position of the wheel (0-255).
  5. Return The color value generated based on the wheel position.
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos; // Wheel position is 255 - Wheel position
if (WheelPos < 85) {
return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3); // Return the color
}
if (WheelPos < 170) { // If the Wheel position is less than 170
WheelPos -= 85;
return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3); // Return the color
}
WheelPos -= 170;
return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0); // Return the color
}

Rainbow cycle function:

  1. This function iterates through each pixel and sets its color based on a cycling rainbow pattern.
  2. The rainbow pattern is created by calculating the color value using the Wheel function.
  3. The function delays for a specified amount of time after setting the color of each pixel.
void rainbowCycle(int wait) {
for (int j = 0; j < 256; j++) { // For loop from 0 to 256
for (int i = 0; i < NUM_PIXELS; i++) { // For loop from 0 to NUM_PIXELS
// Set the pixel color to the Wheel color
pixels.setPixelColor(i, Wheel((i * 256 / NUM_PIXELS + j) & 255));
}
pixels.show();
delay(wait);
}
}

Color chase function:

  1. Animates a color chase effect on a strip of NeoPixels.
  2. This function sets the color of each pixel in a NeoPixel strip to the specified color 'c' and then displays the updated strip.
  3. The function iterates through each pixel in the strip and sets its color to 'c' one by one, creating a chasing effect. The function delays for a specified amount of time after setting the color of each pixel.
void colorChase(uint32_t c, int wait) {
for (int i = 0; i < NUM_PIXELS; i++) { // For loop from 0 to NUM_PIXELS
pixels.setPixelColor(i, c); // Set the pixel color to c
delay(wait);
pixels.show(); // Show the neopixels
}
delay(500);
}

Game over function: Function to handle the game over state. This function is called when the game is over. It performs the following actions:

  1. Initiates a color chase with black color on the neopixels.
  2. Flashes the neopixels three times by filling them with red color and then black color.
  3. Displays the score on a display three times.
  4. Resets the score to 0 and displays it.
  5. Sets the game state to false.
  6. Prints "Game Over" if the DEBUG flag is enabled.
void gameOver() {
colorChase(pixels.Color(0, 0, 0), 10); // Color chase with black color
for (int i = 0; i < 3; i++) { // Flash the neopixels
pixels.fill(pixels.Color(255, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with red color
pixels.show(); // Show the neopixels
delay(300); // Delay 300ms
pixels.fill(pixels.Color(0, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with black color
pixels.show(); =// Show the neopixels
delay(300);
}
delay(1000);
for (uint8_t i = 0; i<3; i++ ){
displayScore(score);
delay(300);
sr.setAllLow(); // set all pins HIGH
delay(300);
}
score = 0; // Score is 0
displayScore(score); // Display the score
game_running = false; // Game state is false
if (DEBUG) { Serial.println("Game Over");} // Print Game Over
}

Button state handler: Handles the state of the button. This function checks if the button is pressed and updates the button state accordingly. If the button is pressed and the button state is false, the button state is set to true.

void butttonStateHandler(){
if (!digitalRead(BUTTON_PIN) && !button_state) { // If the button is pressed and the button state is false
button_state = true; // Button state is true
}
}
  1. newTargetHandler Function :
  2. Handles the creation of a new target in the game.
  3. This function is responsible for generating a new target in the game.
  4. It randomly selects a position between 5 and NUM_PIXELS-5 and sets the corresponding pixels' colors.
  5. The x pixel color is set to white, the y pixel color is set to the next color in the sequence, and the z pixel color is set to white.
  6. If the DEBUG flag is enabled, it also prints the values of x, y, and z to the serial monitor.
void newTargetHandler(){ // New target handler
if (new_target) { // If new target is true
y = random(5, NUM_PIXELS-5); // Random number between 5 and NUM_PIXELS-5
x = y - 1; // x is y - 1
z = y + 1; // z is y + 1
new_target = false; // New target is false
pixels.setPixelColor(x, pixels.Color(255, 255, 255)); // Set the x pixel color to white
pixels.setPixelColor(y, colors[next_color]); // Set the y pixel color to the next col
pixels.setPixelColor(z, pixels.Color(255, 255, 255)); // Set the z pixel color to white
if (DEBUG) { Serial.println("x: " + String(x) + "\t y: " + String(y) + "\t z: " + String(z));} // Print the x, y, z values
}
}

Game running handler:

  1. Handles the running of the game. This function is responsible for running the game. It updates the position of the light on the strip,
  2. checks if the button is pressed, and updates the score and speed accordingly.
  3. If the button is pressed when the light is on the target color, the score is incremented, the neopixels are filled with the next color, and the speed is updated.
  4. If the button is pressed when the light is not on the target color, the game is over, the score is reset, and the speed is set to the initial value.
  5. If the speed reaches the final level, the game is over, and the score is reset.
  6. The function also prints the level, speed, and score to the serial monitor if the DEBUG flag is enabled.
void gameRunningHandler(){
if (millis() - last_time > speed) { // If the current time - last time is greater than speed * 1000
if (num > 0) { // If num is greater than 0
last_num = num - 1; // Last num is num - 1
pixels.setPixelColor(last_num, pixels.Color(0, 0, 0)); // Set the last num pixel color to black
pixels.show(); // Show the neopixels
}

if (last_num == x || last_num == y || last_num == z) { // If the last num is x or y or z
pixels.setPixelColor(x, pixels.Color(255, 255, 255)); // Set the x pixel color to white
pixels.setPixelColor(y, colors[next_color]); // Set the y pixel color to the next color
pixels.setPixelColor(z, pixels.Color(255, 255, 255)); // Set the z pixel color to white
}

if (num < NUM_PIXELS) { // If num is less than NUM_PIXELS
pixels.setPixelColor(num, colors[now_color]); // Set the num pixel color to the now color
pixels.show(); // Show the neopixels
num++; // Increment the num
}

if (num == NUM_PIXELS) { // If num is equal to NUM_PIXELS
last_num = num - 1; // Last num is num - 1
pixels.setPixelColor(last_num, pixels.Color(0, 0, 0)); // Set the last num pixel color to black
pixels.show(); // Show the neopixels
num = 0; // Num is 0
}

if ((last_num == x || last_num == y || last_num == z) && !digitalRead(BUTTON_PIN)) { // If the last num is x or y or z and the button is pressed
button_state = false;
score++; // Increment the score
displayScore(score);
pixels.fill(colors[next_color], 0, NUM_PIXELS); // Fill the neopixels with the next color
pixels.show(); // Show the neopixels
delay(500);
pixels.fill(pixels.Color(0, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with black color
pixels.show();
speed = speed - level; // Speed is speed - level
// level--;
Serial.println("Level: " + String(level) + "\t Speed: " + String(speed)); // Print the level
Serial.println("Score: " + String(score)); // Print the score
next_color = (next_color + 1) % (sizeof(colors)/sizeof(colors[0])); // Next color is next color + 1
now_color = (now_color + 1) % (sizeof(colors)/sizeof(colors[0])); // Now color is now color + 1
new_target = true;
if (DEBUG) { Serial.println("speed is " + String(speed) + "\t Button is " + String (digitalRead(BUTTON_PIN)));} // Print the speed and button state
}

if ((last_num != x && last_num != y && last_num != z) && !digitalRead(BUTTON_PIN)) { // If the last num is not x or y or z and the button is pressed
button_state = false;
pixels.fill(colors[now_color], 0, NUM_PIXELS); // Fill the neopixels with the now color
pixels.show();
gameOver(); // Game over function
num = 0;
pixels.fill(pixels.Color(0, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with black color
pixels.show();
speed = SPEED; // Speed is SPEED
next_color = 1;
now_color = 0;
new_target = true;
if (DEBUG) { Serial.println("speed is " + String(speed) + "\t Button is " + String (digitalRead(BUTTON_PIN)));} // Print the speed and button state
}

if (speed < FINAL_LEVEL) { // If the speed is less than the final level
for (uint8_t i = 0; i<8; i++ ){
rainbowCycle(10);
}
delay(1000);
for (uint8_t i = 0; i<3; i++ ){
displayScore(score);
delay(300);
sr.setAllLow(); // set all pins HIGH
delay(300);
}
score = 0; // Score is 0
game_running = false; // Game state is false
displayScore(score); // Display the score
num = 0; // Num is 0
pixels.fill(pixels.Color(0, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with black color
pixels.show();
speed = SPEED; // Speed is SPEED
next_color = 1; // Next color is 1
now_color = 0; // Now color is 0
new_target = true; // New target is true
}

last_time = millis(); // Last time is millis
}
}

Setup function: This function initializes the serial communication, sets the button pin as input pullup, begins the neopixels, sets the brightness of the neopixels, and calls the blink and displayScore functions.

void setup() {
Serial.begin(9600); // Begin the serial communication
pinMode(BUTTON_PIN, INPUT_PULLUP); // Set the button pin as input pullup
pixels.begin(); // Begin the neopixels
pixels.setBrightness(255); // Set the brightness of the neopixels
blink();
displayScore(score);
}

Loop function: This function checks if the game is running and calls the buttonStateHandler, newTargetHandler, and gameRunningHandler functions accordingly.

If the game is not running and the button is pressed, the game state is set to true.

void loop() {
if (!game_running) { // If the game is not running
if (!digitalRead(BUTTON_PIN)) { // If the button is pressed
game_running = true; // Game state is true
delay(500);
}
}
if (game_running){ // If the game is running
butttonStateHandler(); // Button state handler
newTargetHandler(); // New target handler
gameRunningHandler(); // Game running handler
}
}




Game Logic and Flow

  1. Game Start: The game starts when the button is pressed. The initial speed of the moving light is set, and the NeoPixel strip is ready.
  2. Game Running: A target color is randomly selected on the NeoPixel strip. The light moves along the strip. The player's goal is to press the button when the light reaches the target color. If the player presses the button at the correct time, the score increases, and the speed of the light increases, making the game more challenging. If the player presses the button when the light is not on the target color, the game ends. If the speed exceeds a certain threshold, the game also ends.
  3. Game Over: When the game ends, a rainbow effect is displayed on the NeoPixel strip, indicating the end of the game. The score is reset, and the game can be restarted.
  4. Score Display: The current score is displayed on a 7-segment display using a shift register.


Click to Download the code

Final Code

// Created: 03-08-2024 10:45:00
// Last Modified: 03-08-2024 16:00:00
// Author: ANOOF C
// Company: Interactive Technical Services LLC (ITS) Dubai
// File Description: A simple game for ESP32 with a button and a neopixel strip.
// The game is to press the button when the light is on the target color.
// The speed of the light will increase as the game goes on.
// The game will end if the button is pressed when the light is not on the target color.
// The game will restart if the button is pressed when the light is on the target color.
// The game will restart if the speed is too fast.
// The game will show a rainbow cycle when the game is over.

#define DEBUG 0 // 1 for debug mode, 0 for normal mode


#define BUTTON_PIN 18 // Button pin
#define PIXEL_PIN 27 // Neopixel pin
#define NUM_PIXELS 51 // Number of neopixels
#define SPEED 105 // Speed of the light
#define FINAL_LEVEL 10 // Final level of the speed
#define LEVEL 5 // How much the speed increase

#include <Arduino.h> // Include the Arduino library
#include <Adafruit_NeoPixel.h> // Include the Adafruit NeoPixel library
#include <ShiftRegister74HC595.h>

// Create an instance of the shift register
ShiftRegister74HC595 sr (2, 23, 21, 19);
uint8_t digit1, digit2;

uint8_t numberB[] = {B10101111, //0
B00001100, //1
B11000111, //2
B11001110, //3
B01101100, //4
B11101010, //5
B11101011, //6
B00001110, //7
B11101111, //8
B11101110 //9
};


Adafruit_NeoPixel pixels(NUM_PIXELS, PIXEL_PIN, NEO_GRB + NEO_KHZ800); // Create a NeoPixel object


uint8_t score = 0; // Score

int8_t x, y, z; // Target color position
int8_t num = 0; // Current color position
int8_t last_num = 0; // Last color position
int now_color = 0; // Current color
int next_color = 1; // Next color
int speed = SPEED; // Speed of the light
uint8_t level = LEVEL; // How much the speed will increase
bool new_target = true; // New target color
bool button_state = false; // Button state
bool game_running = false; // Game state
unsigned long last_time = 0; // Last time of the light

uint32_t colors[] = { // Colors
pixels.Color(255, 0, 0), // RED
pixels.Color(255, 165, 0), // ORANGE
pixels.Color(0, 255, 0), // GREEN
pixels.Color(0, 255, 255), // CYAN
pixels.Color(0, 0, 255), // BLUE
pixels.Color(255, 255, 0), // YELLOW
pixels.Color(128, 0, 128), // PURPLE
pixels.Color(0, 128, 128), // TEAL
pixels.Color(255, 0, 255), // MAGENTA
pixels.Color(0, 255, 128), // SPRING GREEN
pixels.Color(0, 255, 255), // AQUA
pixels.Color(255, 0, 128) // ROSE
};


void blink(){
for(int i = 0; i<3; i++){
sr.setAllHigh(); // set all pins LOW
delay(300);
sr.setAllLow(); // set all pins HIGH
delay(300);
}
}

void displayScore(uint16_t score) {
digit1=score % 10 ;
digit2=(score / 10) % 10 ;
uint8_t numberToPrint[]= {numberB[digit1],numberB[digit2]};
sr.setAll(numberToPrint);
}

// Wheel function to create a rainbow color
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos; // Wheel position is 255 - Wheel position
if (WheelPos < 85) {
return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3); // Return the color
}
if (WheelPos < 170) { // If the Wheel position is less than 170
WheelPos -= 85;
return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3); // Return the color
}
WheelPos -= 170;
return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0); // Return the color
}

// Rainbow cycle function
void rainbowCycle(int wait) {
for (int j = 0; j < 256; j++) { // For loop from 0 to 256
for (int i = 0; i < NUM_PIXELS; i++) { // For loop from 0 to NUM_PIXELS
pixels.setPixelColor(i, Wheel((i * 256 / NUM_PIXELS + j) & 255)); // Set the pixel color to the Wheel color
}
pixels.show();
delay(wait);
}
}


// Color chase function
void colorChase(uint32_t c, int wait) {
for (int i = 0; i < NUM_PIXELS; i++) { // For loop from 0 to NUM_PIXELS
pixels.setPixelColor(i, c); // Set the pixel color to c
delay(wait);
pixels.show(); // Show the neopixels
}
delay(500);
}


// Game over function
void gameOver() {
colorChase(pixels.Color(0, 0, 0), 10); // Color chase with black color

for (int i = 0; i < 3; i++) { // Flash the neopixels
pixels.fill(pixels.Color(255, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with red color
pixels.show(); // Show the neopixels
delay(300); // Delay 300ms
pixels.fill(pixels.Color(0, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with black color
pixels.show(); // Show the neopixels
delay(300);
}
delay(1000);
for (uint8_t i = 0; i<3; i++ ){
displayScore(score);
delay(300);
sr.setAllLow(); // set all pins HIGH
delay(300);
}
score = 0; // Score is 0
displayScore(score); // Display the score
game_running = false; // Game state is false
if (DEBUG) { Serial.println("Game Over");} // Print Game Over
}

// Button state handler
void butttonStateHandler(){
if (!digitalRead(BUTTON_PIN) && !button_state) { // If the button is pressed and the button state is false
button_state = true; // Button state is true
}
}

void newTargetHandler(){ // New target handler
if (new_target) { // If new target is true
y = random(5, NUM_PIXELS-5); // Random number between 5 and NUM_PIXELS-5
x = y - 1; // x is y - 1
z = y + 1; // z is y + 1
new_target = false; // New target is false
pixels.setPixelColor(x, pixels.Color(255, 255, 255)); // Set the x pixel color to white
pixels.setPixelColor(y, colors[next_color]); // Set the y pixel color to the next color
pixels.setPixelColor(z, pixels.Color(255, 255, 255)); // Set the z pixel color to white
if (DEBUG) { Serial.println("x: " + String(x) + "\t y: " + String(y) + "\t z: " + String(z));} // Print the x, y, z values
}
}

// Game running handler
void gameRunningHandler(){
if (millis() - last_time > speed) { // If the current time - last time is greater than speed * 1000
if (num > 0) { // If num is greater than 0
last_num = num - 1; // Last num is num - 1
pixels.setPixelColor(last_num, pixels.Color(0, 0, 0)); // Set the last num pixel color to black
pixels.show(); // Show the neopixels
}

if (last_num == x || last_num == y || last_num == z) { // If the last num is x or y or z
pixels.setPixelColor(x, pixels.Color(255, 255, 255)); // Set the x pixel color to white
pixels.setPixelColor(y, colors[next_color]); // Set the y pixel color to the next color
pixels.setPixelColor(z, pixels.Color(255, 255, 255)); // Set the z pixel color to white
}

if (num < NUM_PIXELS) { // If num is less than NUM_PIXELS
pixels.setPixelColor(num, colors[now_color]); // Set the num pixel color to the now color
pixels.show(); // Show the neopixels
num++; // Increment the num
}

if (num == NUM_PIXELS) { // If num is equal to NUM_PIXELS
last_num = num - 1; // Last num is num - 1
pixels.setPixelColor(last_num, pixels.Color(0, 0, 0)); // Set the last num pixel color to black
pixels.show(); // Show the neopixels
num = 0; // Num is 0
}

if ((last_num == x || last_num == y || last_num == z) && !digitalRead(BUTTON_PIN)) { // If the last num is x or y or z and the button is pressed
button_state = false;
score++; // Increment the score
displayScore(score);
pixels.fill(colors[next_color], 0, NUM_PIXELS); // Fill the neopixels with the next color
pixels.show(); // Show the neopixels
delay(500);
pixels.fill(pixels.Color(0, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with black color
pixels.show();
speed = speed - level; // Speed is speed - level
// level--;
Serial.println("Level: " + String(level) + "\t Speed: " + String(speed)); // Print the level
Serial.println("Score: " + String(score)); // Print the score
next_color = (next_color + 1) % (sizeof(colors)/sizeof(colors[0])); // Next color is next color + 1
now_color = (now_color + 1) % (sizeof(colors)/sizeof(colors[0])); // Now color is now color + 1
new_target = true;
if (DEBUG) { Serial.println("speed is " + String(speed) + "\t Button is " + String (digitalRead(BUTTON_PIN)));} // Print the speed and button state
}

if ((last_num != x && last_num != y && last_num != z) && !digitalRead(BUTTON_PIN)) { // If the last num is not x or y or z and the button is pressed
button_state = false;
pixels.fill(colors[now_color], 0, NUM_PIXELS); // Fill the neopixels with the now color
pixels.show();
gameOver(); // Game over function
num = 0;
pixels.fill(pixels.Color(0, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with black color
pixels.show();
speed = SPEED; // Speed is SPEED
next_color = 1;
now_color = 0;
new_target = true;
if (DEBUG) { Serial.println("speed is " + String(speed) + "\t Button is " + String (digitalRead(BUTTON_PIN)));} // Print the speed and button state
}

if (speed < FINAL_LEVEL) { // If the speed is less than the final level
for (uint8_t i = 0; i<8; i++ ){
rainbowCycle(10);
}
delay(1000);
for (uint8_t i = 0; i<3; i++ ){
displayScore(score);
delay(300);
sr.setAllLow(); // set all pins HIGH
delay(300);
}
score = 0; // Score is 0
game_running = false; // Game state is false
displayScore(score); // Display the score
num = 0; // Num is 0
pixels.fill(pixels.Color(0, 0, 0), 0, NUM_PIXELS); // Fill the neopixels with black color
pixels.show();
speed = SPEED; // Speed is SPEED
next_color = 1; // Next color is 1
now_color = 0; // Now color is 0
new_target = true; // New target is true
}

last_time = millis(); // Last time is millis
}
}

void setup() {
Serial.begin(9600); // Begin the serial communication
pinMode(BUTTON_PIN, INPUT_PULLUP); // Set the button pin as input pullup
pixels.begin(); // Begin the neopixels
pixels.setBrightness(255); // Set the brightness of the neopixels
blink();
displayScore(score);
}


void loop() {
if (!game_running) { // If the game is not running
if (!digitalRead(BUTTON_PIN)) { // If the button is pressed
game_running = true; // Game state is true
delay(500);
}
}
if (game_running){ // If the game is running
butttonStateHandler(); // Button state handler
newTargetHandler(); // New target handler
gameRunningHandler(); // Game running handler
}
}

Suggestions/feedback

Customization

You can easily modify the game's difficulty by adjusting the SPEED, FINAL_LEVEL, and LEVEL constants. The colors used in the game can also be changed by modifying the colors[] array.


Suggestions/feedback

If you have any suggestions or feedback feel free to comment down below. If you encounter any problems during the making of the project, don't hesitate to contact me. I will be updating this library to improve efficiency and include more functions soon.

Connect me on LinkedIn: Connect Now