#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <EEPROM.h>
// ILI9341 Pin Configuration
#define TFT_DC 9
#define TFT_CS 10
#define TFT_RST 255 // Not connected in your setup
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
// Joystick Configuration
#define JOY_VERT A1
#define JOY_HORIZ A0
#define JOY_SEL 2
int JOY_DEADZONE = 64;
// Buzzer Configuration
#define BUZZER_PIN 4
// Game Variables
const int blockSize = 10;
int snakeX[100], snakeY[100];
int snakeLength = 3;
int snakeDirection = 1; // 0=Up, 1=Right, 2=Down, 3=Left
int score = 0, highScore = 0;
int foodX, foodY;
int level = 1;
bool isRedFood = false;
bool redFood = false;
bool snakeEatsFood();
int lastBarrierMoveTime;
unsigned long lastFoodTime = 0; // To track food appearance time
// Game Settings
int snakeSpeed = 300; // ms
int barrierX, barrierY;
bool gameRunning = false;
// Function Prototypes
void resetGame();
void updateSnake();
void generateFood();
void checkCollision();
void drawScreen();
void menu();
void loadHighScore();
void saveHighScore();
void addBarrier();
void checkLevelProgression();
void readJoystick(); // Function to read joystick input
void playSound(int duration);
void drawBarrier();
void displayCountdown();
bool isNearCenter(int x, int y);
bool isNearBarrier(int x, int y);
void setup() {
tft.begin();
tft.setRotation(0); // Set screen orientation to portrait
pinMode(JOY_SEL, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
loadHighScore();
menu();
}
bool isNearBarrier(int x, int y) {
// Define the logic to check if the given coordinates are near the barrier
// (e.g., within a certain distance).
// Replace this with your actual implementation.
int distance = 10; // Example distance
return abs(x - barrierX) <= distance && abs(y - barrierY) <= distance;
}
bool isNearCenter(int x, int y) {
// Define the logic to check if the given coordinates are near the center of the screen.
// Replace this with your actual implementation.
int centerX = tft.width() / 2;
int centerY = tft.height() / 2;
int distance = 50; // Example distance
return abs(x - centerX) <= distance && abs(y - centerY) <= distance;
}
void readJoystick() {
// Your existing joystick reading logic here
int xVal = analogRead(JOY_HORIZ);
int yVal = analogRead(JOY_VERT);
if (yVal < JOY_DEADZONE) { // Move Up
if (snakeDirection != 0 && snakeDirection != 2) snakeDirection = 2;
} else if (yVal > (1023 - JOY_DEADZONE)) { // Move Down
if (snakeDirection != 2 && snakeDirection != 0) snakeDirection = 0;
}
if (xVal < JOY_DEADZONE) { // Move Left
if (snakeDirection != 3 && snakeDirection != 1) snakeDirection = 1;
} else if (xVal > (1023 - JOY_DEADZONE)) { // Move Right
if (snakeDirection != 1 && snakeDirection != 3) snakeDirection = 3;
}
}
void playSound(int duration) {
tone(BUZZER_PIN, 440, duration); // Play a 440Hz tone for 'duration' milliseconds
}
void loop() {
static unsigned long lastMoveTime = 0;
if (millis() - lastMoveTime >= snakeSpeed) {
lastMoveTime = millis();
if (gameRunning) {
readJoystick();
updateSnake();
checkCollision();
drawScreen();
checkLevelProgression();
}
}
// Display the countdown timer if food is present
if (gameRunning && level >= 3) {
displayCountdown();
}
}
void menu() {
tft.fillScreen(ILI9341_BLACK); // Clear the screen first
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(20, 40);
tft.println("Snake Game");
tft.setCursor(20, 80);
tft.println("Press Joystick");
tft.setCursor(20, 120);
tft.println("High Score: " + String(highScore));
// Wait until the joystick is pressed
while (digitalRead(JOY_SEL) == HIGH) {
delay(100);
}
// Clear the screen and reset game state after joystick press
tft.fillScreen(ILI9341_BLACK); // Clear the start screen
resetGame(); // Start the game
}
void resetGame() {
score = 0;
snakeLength = 3;
snakeSpeed = 300;
level = 1;
//redFood = false;
snakeX[0] = tft.width() / 2;
snakeY[0] = tft.height() / 2;
for (int i = 1; i < snakeLength; i++) {
snakeX[i] = snakeX[i - 1] - blockSize;
snakeY[i] = snakeY[i - 1];
}
generateFood();
gameRunning = true;
}
void updateSnake() {
// Shift body
for (int i = snakeLength - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// Update head based on direction
if (snakeDirection == 0) snakeY[0] -= blockSize; // Up
else if (snakeDirection == 1) snakeX[0] += blockSize; // Right
else if (snakeDirection == 2) snakeY[0] += blockSize; // Down
else if (snakeDirection == 3) snakeX[0] -= blockSize; // Left
// Screen Wrapping
snakeX[0] = (snakeX[0] + tft.width()) % tft.width(); // Wrap horizontally
snakeY[0] = (snakeY[0] + tft.height()) % tft.height(); // Wrap vertically
}
void displayCountdown() {
static uint32_t lastCountdownTime = 0;
int remainingTime = 5 - (millis() - lastFoodTime) / 1000;
if (remainingTime >= 0) {
if (millis() - lastCountdownTime > 1000) { // Update every second
tft.fillRect(200, 0, 50, 20, ILI9341_BLACK); // Clear old countdown
tft.setCursor(200, 0);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.print(remainingTime);
lastCountdownTime = millis();
}
}
}
void generateFood() {
foodX = random(1, 24) * blockSize;
foodY = random(1, 16) * blockSize;
if (level >= 4) {
isRedFood = random(0, 2); // 50% chance for red food
if (isRedFood) {
tft.fillRect(foodX, foodY, blockSize, blockSize, ILI9341_RED);
} else {
tft.fillRect(foodX, foodY, blockSize, blockSize, ILI9341_YELLOW);
}
} else {
isRedFood = false;
tft.fillRect(foodX, foodY, blockSize, blockSize, ILI9341_YELLOW);
}
lastFoodTime = millis(); // Reset the food timer
}
// Function declaration
bool snakeEatsFood();
void checkCollision() {
// Food Collision
if (snakeX[0] == foodX && snakeY[0] == foodY) {
// Check food color before increasing score
if (isRedFood) {
score -= 1; // Deduct points for red food
playSound(100); // Low sound for penalty
} else {
score += 1; // Add points for yellow food
playSound(200); // High sound for reward
}
snakeLength++;
generateFood(); // Generate a new food
playSound(100); // Play sound on eating food
// Level and speed management
if (score % 2 == 0) {
level++;
if (level > 1) snakeSpeed = max(50, snakeSpeed - 20); // Increase speed
if (level == 2) addBarrier(); // Add barrier at Level 2
}
}
// Self Collision
for (int i = 1; i < snakeLength; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
gameOver();
return;
}
}
// Barrier Collision (Level 2 and onwards)
if (level >= 2 && snakeX[0] == barrierX && snakeY[0] == barrierY) {
gameOver();
return;
}
}
// Function definition
bool snakeEatsFood() {
// Logic to determine if the snake eats food
// For example, you can check if snake is at food position
return (snakeX[0] == foodX && snakeY[0] == foodY);
}
void drawBarrier(int x, int y) {
tft.drawCircle(x + blockSize / 2, y + blockSize / 2, blockSize / 2, ILI9341_BLUE); // Hollow circle
}
void drawScreen() {
// Erase tail segment
tft.fillRect(snakeX[snakeLength - 1], snakeY[snakeLength - 1], blockSize, blockSize, ILI9341_BLACK);
// Draw Snake Head
tft.fillRect(snakeX[0], snakeY[0], blockSize, blockSize, ILI9341_GREEN);
// Draw Food
tft.fillRect(foodX, foodY, blockSize, blockSize, isRedFood ? ILI9341_RED : ILI9341_YELLOW);
// Draw Barrier (Level 2 and onwards)
if (level >= 2) {
drawBarrier(barrierX, barrierY);
}
// Update Score and Level (draw over existing text area to minimize updates)
tft.fillRect(0, 0, 80, 40, ILI9341_BLACK); // Clear score/level text area
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(5, 5);
tft.println("Score: " + String(score));
tft.setCursor(5, 20);
tft.println("Level: " + String(level));
}
void gameOver() {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(40, 60);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(2);
tft.println("Game Over");
tft.setCursor(40, 100);
tft.setTextSize(1);
tft.println("Score: " + String(score));
delay(2000);
saveHighScore();
menu();
}
void loadHighScore() {
EEPROM.get(0, highScore);
}
void saveHighScore() {
if (score > highScore) {
highScore = score;
EEPROM.put(0, highScore);
}
}
void addBarrier() {
do {
barrierX = random(0, tft.width() / blockSize) * blockSize;
barrierY = random(0, tft.height() / blockSize) * blockSize;
} while (isNearCenter(barrierX, barrierY) || (barrierX == foodX && barrierY == foodY));
}
void checkLevelProgression() {
// Food disappearance logic (Level 3+)
if (level >= 3 && millis() - lastFoodTime > 5000) {
tft.fillRect(foodX, foodY, blockSize, blockSize, ILI9341_BLACK); // Clear old food
generateFood(); // Generate new food
}
}
void moveBarrier() {
if (millis() - lastBarrierMoveTime > 2000) { // Move every 2 seconds
tft.fillRect(barrierX, barrierY, blockSize, blockSize, ILI9341_BLACK); // Clear old barrier
barrierX = random(1, 24) * blockSize;
barrierY = random(1, 16) * blockSize;
drawBarrier();
lastBarrierMoveTime = millis();
}
}