#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <LedControl.h>
#include <EEPROM.h> // Include EEPROM library
// Define The Snake as a Struct
typedef struct Snake {
int head[2]; // the (row, column) of the snake head
int** body; // Dynamic allocation for snake's body
int len; // The length of the snake
int dir[2]; // A direction to move the snake along
} Snake;
// Define The Apple as a Struct
typedef struct Apple {
int rPos; // The row index of the apple
int cPos; // The column index of the apple
} Apple;
// MAX72XX led Matrix
const int DIN = 12;
const int CS = 11;
const int CLK = 10;
const int BUZZER_PIN = 9; // Pin for Piezo Buzzer
LedControl lc = LedControl(DIN, CLK, CS, 4); // Initialize LedControl with 4 modules
const int varXPin = A1; // X Value from Joystick
const int varYPin = A3; // Y Value from Joystick
byte pic[8][4]; // The 8 rows of the LED Matrix, expanded for 32 columns
Snake snake = {{1, 5}, NULL, 2, {1, 0}}; // Initialize a snake object
Apple apple = {(int)random(0, 8), (int)random(0, 32)}; // Initialize an apple object
int i, j; // Counters
unsigned long lastUpdateTime = 0; // Last time the update occurred
unsigned long updateInterval = 300;
const unsigned long minUpdateInterval = 100; // Minimum update interval for maximum speed
// LCD Setup
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16x2 display
// Previous score
int prevScore = 0; // Change the initial value to 0
// Variable to track game over
bool gameOver = false;
// Variable to store game over time
unsigned long gameOverTime = 0;
// Global variables for highest score
int highestScoreAddr = 0; // Address in EEPROM where highest score is stored
int highestScore = 0; // Variable to store the highest score
// Function to retrieve highest score from EEPROM
int getHighestScore() {
return EEPROM.read(highestScoreAddr) * 256 + EEPROM.read(highestScoreAddr + 1);
}
// Function to store highest score in EEPROM
void storeHighestScore(int score) {
EEPROM.write(highestScoreAddr, highByte(score));
EEPROM.write(highestScoreAddr + 1, lowByte(score));
}
// Define the pin for the piezo buzzer
#define PIEZO_PIN 9
// Define the frequencies for each note
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_REST 0
// Define the durations for each note
#define DURATION_QUARTER 500
#define DURATION_HALF 1000
#define DURATION_EIGHTH 250
// Define the melody
int melody[] = {
NOTE_C4, NOTE_G4, NOTE_G4, NOTE_A4, NOTE_G4, NOTE_REST,
NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4, NOTE_C4, NOTE_REST,
NOTE_G4, NOTE_G4, NOTE_F4, NOTE_D4, NOTE_C4
};
// Define the note durations
int noteDurations[] = {
DURATION_QUARTER, DURATION_EIGHTH, DURATION_EIGHTH, DURATION_QUARTER, DURATION_HALF, DURATION_HALF,
DURATION_QUARTER, DURATION_EIGHTH, DURATION_EIGHTH, DURATION_QUARTER, DURATION_HALF, DURATION_HALF,
DURATION_QUARTER, DURATION_EIGHTH, DURATION_EIGHTH, DURATION_QUARTER, DURATION_HALF
};
// Function to play the melody
void playMelody() {
// Iterate over the melody
for (int i = 0; i < sizeof(melody) / sizeof(melody[0]); i++) {
// Calculate the duration
int duration = noteDurations[i];
// If the note is not a rest, play it
if (melody[i] != NOTE_REST) {
tone(PIEZO_PIN, melody[i], duration);
}
// Delay for the duration
delay(duration);
noTone(PIEZO_PIN); // Stop the tone after the duration
}
}
void setup() {
Serial.begin(9600); // Initialize Serial communication for debugging
Serial.println("Initializing...");
// Check if Serial communication is working
Serial.println("Serial communication initialized.");
for (int module = 0; module < 4; module++) {
lc.shutdown(module, false);
// Set the brightness to a medium value
lc.setIntensity(module, 8);
// Clear the display
lc.clearDisplay(module);
}
// Set Joystick Pins as INPUTs
pinMode(varXPin, INPUT);
pinMode(varYPin, INPUT);
pinMode(BUZZER_PIN, OUTPUT); // Set the BUZZER_PIN as an output
// LCD Initialization
lcd.init();
lcd.backlight(); // Turn on the backlight
Serial.println("LCD initialized.");
// Dynamic allocation for snake's body
snake.body = new int*[120];
for (int i = 0; i < 120; ++i)
snake.body[i] = new int[2];
// Retrieve highest score from EEPROM
highestScore = getHighestScore();
Serial.println("Initialization complete.");
// Display "Welcome to LED Worm" and play melody
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Welcome LED Worm");
playMelody();
delay(2000); // Wait for 2 seconds
}
void loop() {
// put your main code here, to run repeatedly:
unsigned long currentTime = millis();
if (!gameOver && currentTime - lastUpdateTime >= updateInterval) {
lastUpdateTime = currentTime;
int xVal = analogRead(varXPin);
int yVal = analogRead(varYPin);
if (xVal < 400 && snake.dir[1] == 0) {
snake.dir[0] = 0;
snake.dir[1] = -1; // Move left
} else if (xVal > 600 && snake.dir[1] == 0) {
snake.dir[0] = 0;
snake.dir[1] = 1; // Move right
} else if (yVal < 400 && snake.dir[0] == 0) {
snake.dir[0] = -1; // Move up
snake.dir[1] = 0;
} else if (yVal > 600 && snake.dir[0] == 0) {
snake.dir[0] = 1; // Move down
snake.dir[1] = 0;
}
// Update the game when necessary
Update();
Render();
updateScore();
}
if (gameOver && currentTime - gameOverTime >= 5000) {
resetGame(); // Reset the game after 5 seconds
}
}
void Update() {
if (gameOver) return; // If game over, return without updating
reset(); // Reset (Clear) the 8x32 LED matrix
int newHead[2] = {snake.head[0] + snake.dir[0], snake.head[1] + snake.dir[1]};
// Handle Borders
if (newHead[0] == 8) {
newHead[0] = 0;
} else if (newHead[0] == -1) {
newHead[0] = 7;
} else if (newHead[1] == 32) {
newHead[1] = 0;
} else if (newHead[1] == -1) {
newHead[1] = 31;
}
// Check If The Snake hits itself
for (j = 0; j < snake.len; j++) {
if (snake.body[j][0] == newHead[0] && snake.body[j][1] == newHead[1]) {
// Optionally, you can add some action here if the snake hits itself
gameOver = true;
gameOverTime = millis(); // Note the time of game over
lcd.clear(); // Clear the LCD screen
lcd.setCursor(0, 0); // Set cursor to the first line
lcd.print("GAME OVER"); // Print "GAME OVER"
lcd.setCursor(0,1);
lcd.print("SAKIT :(");
return;
}
}
// Check if The snake ate the apple
if (newHead[0] == apple.rPos && newHead[1] == apple.cPos) {
// Shift the snake's body one position back
for (int i = snake.len - 1; i > 0; i--) {
snake.body[i][0] = snake.body[i - 1][0];
snake.body[i][1] = snake.body[i - 1][1];
}
// Add new segment to the snake's body
snake.body[0][0] = snake.head[0];
snake.body[0][1] = snake.head[1];
snake.len = snake.len + 1;
apple.rPos = (int)random(0, 8);
apple.cPos = (int)random(0, 32);
// Play sound on Piezo Buzzer
tone(BUZZER_PIN, 1000); // Tune frequency to 1000Hz
delay(100); // Pause for 100 milliseconds
noTone(BUZZER_PIN); // Stop the Piezo Buzzer
// Decrease the updateInterval to increase speed
if (updateInterval > minUpdateInterval) {
updateInterval -= 20; // Adjust this value as needed
}
// Check if snake length reaches
if (snake.len == 32) {
gameOver = true;
gameOverTime = millis(); // Note the time of game over
lcd.clear(); // Clear the LCD screen
lcd.setCursor(0, 0); // Set cursor to the first line
lcd.print("PRESS THE BUTTON"); // Print "GAME OVER"
lcd.setCursor(0, 1);
lcd.print("TO RESTART");
delay(3000);
return;
}
} else {
removeLast(); // Remove the last segment of the snake's body
}
snake.body[snake.len - 1][0] = newHead[0];
snake.body[snake.len - 1][1] = newHead[1];
snake.head[0] = newHead[0];
snake.head[1] = newHead[1];
// Update the pic Array to Display(snake and apple)
for (j = 0; j < snake.len; j++) {
pic[snake.body[j][0]][snake.body[j][1] / 8] |= (128 >> (snake.body[j][1] % 8));
}
pic[apple.rPos][apple.cPos / 8] |= (128 >> (apple.cPos % 8));
}
void Render() {
// Render the first screen (module 0)
for (i = 0; i < 8; i++) {
lc.setRow(0, i, pic[i][0]);
}
// Render the 4th screen (module 3) at the position of the 2nd screen
for (i = 0; i < 8; i++) {
lc.setRow(1, i, pic[i][3]);
}
// Render the 3rd screen (module 2)
for (i = 0; i < 8; i++) {
lc.setRow(2, i, pic[i][2]);
}
// Render the 2nd screen (module 1) at the position of the 4th screen
for (i = 0; i < 8; i++) {
lc.setRow(3, i, pic[i][1]);
}
}
void removeLast() {
for (j = snake.len - 1; j > 0; j--) {
snake.body[j][0] = snake.body[j - 1][0];
snake.body[j][1] = snake.body[j - 1][1];
}
// Update the last segment with the new head position
snake.body[0][0] = snake.head[0];
snake.body[0][1] = snake.head[1];
}
// Reset function definition
void reset() {
for (int i = 0; i < 8; i++) {
for (int module = 0; module < 4; module++) {
pic[i][module] = 0;
}
}
}
// Function to reset the game after "Game Over"
void resetGame() {
gameOver = false; // Reset game over status
reset(); // Clear the display and reset pic array
// Update highest score if current score is higher
if (snake.len > highestScore) {
highestScore = snake.len;
storeHighestScore(highestScore);
}
// Deallocate memory for snake body from the previous game session
if (snake.body != NULL) {
for (int i = 0; i < snake.len; ++i)
delete[] snake.body[i];
delete[] snake.body;
snake.body = NULL;
}
// Reinitialize the snake object
snake.head[0] = 1;
snake.head[1] = 5;
snake.body = new int*[120];
for (int i = 0; i < 120; ++i)
snake.body[i] = new int[2];
snake.len = 2; // Set the length of the snake to 2
snake.dir[0] = 1;
snake.dir[1] = 0;
// Reinitialize the apple object
apple.rPos = (int)random(0, 8);
apple.cPos = (int)random(0, 32);
// Reset the update interval
updateInterval = 300;
// Play sound on Piezo Buzzer
tone(BUZZER_PIN, 5000); // Tune frequency to 2000Hz
delay(1000); // Pause for 100 milliseconds
noTone(BUZZER_PIN); // Stop the Piezo Buzzer
// Clear LCD and print "GAME OVER"
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("GAME OVER");
}
// Function to update and display the score on the LCD
void updateScore() {
// Only update the score if it has changed
if (snake.len != prevScore && !gameOver) {
lcd.clear(); // Clear the LCD screen
lcd.setCursor(0, 0); // Set cursor to the first line
lcd.print("Score: "); // Print "Score: "
lcd.print(snake.len); // Print the length of the snake
lcd.setCursor(0, 1); // Set cursor to the second line
lcd.print("High Score: ");
lcd.print(highestScore); // Print the highest score
prevScore = snake.len; // Update previous score
}
}