1#include <Wire.h>
2#include <Adafruit_GFX.h>
3#include <Adafruit_SSD1306.h>
4
5// OLED display settings
6#define SCREEN_WIDTH 128
7#define SCREEN_HEIGHT 64
8#define OLED_RESET -1
9Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
10
11// Joystick Pins for Arduino Mega
12#define JOY_X A0
13#define JOY_Y A1
14#define JOY_BTN 2
15
16// Game variables
17#define BLOCK_SIZE 4
18#define MAX_SNAKE_LENGTH 100
19
20int snakeX[MAX_SNAKE_LENGTH];
21int snakeY[MAX_SNAKE_LENGTH];
22int snakeLength = 5;
23int direction = 1; // 0=Up, 1=Right, 2=Down, 3=Left
24
25int foodX;
26int foodY;
27int score = 0;
28bool gameOver = false;
29
30void setup() {
31 Serial.begin(9600);
32 pinMode(JOY_BTN, INPUT_PULLUP); // Use internal pullup for the button
33
34 // Initialize OLED
35 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
36 Serial.println(F("SSD1306 allocation failed"));
37 for(;;);
38 }
39
40 display.clearDisplay();
41 display.setTextSize(1);
42 display.setTextColor(SSD1306_WHITE);
43
44 // Title screen border
45 display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
46
47 display.setCursor(30, 25);
48 display.print("SNAKE GAME");
49 display.display();
50 delay(2000);
51
52 resetGame();
53}
54
55void loop() {
56 // 1. Check the button FIRST! This lets you restart anytime.
57 if (digitalRead(JOY_BTN) == LOW) {
58 resetGame();
59 delay(200); // A tiny delay to stop accidental double-clicks
60 }
61
62 // 2. If the game is over, just show the screen and stop moving
63 if (gameOver) {
64 displayGameOver();
65 return;
66 }
67
68 // 3. Normal gameplay logic
69 readJoystick();
70 updateSnake();
71 checkCollisions();
72 drawGame();
73
74 // Game Speed
75 delay(40);
76}
77
78void resetGame() {
79 snakeLength = 5;
80 direction = 1; // Start moving right
81 score = 0;
82 gameOver = false;
83
84 // Initial snake position
85 for (int i = 0; i < snakeLength; i++) {
86 snakeX[i] = (SCREEN_WIDTH / 2) - (i * BLOCK_SIZE);
87 snakeY[i] = SCREEN_HEIGHT / 2;
88 }
89
90 spawnFood();
91}
92
93void spawnFood() {
94 // Spawn food inside the grid, avoiding the outer border
95 foodX = random(1, (SCREEN_WIDTH / BLOCK_SIZE) - 1) * BLOCK_SIZE;
96 foodY = random(1, (SCREEN_HEIGHT / BLOCK_SIZE) - 1) * BLOCK_SIZE;
97}
98
99void readJoystick() {
100 int xVal = analogRead(JOY_X);
101 int yVal = analogRead(JOY_Y);
102
103 // Custom Calibration Integration
104 // UP: xVal < 200
105 // DOWN: xVal > 800
106 // LEFT: yVal > 800
107 // RIGHT: yVal < 200
108
109 if (xVal < 200 && direction != 2) {
110 direction = 0; // Up
111 } else if (xVal > 800 && direction != 0) {
112 direction = 2; // Down
113 } else if (yVal > 800 && direction != 1) {
114 direction = 3; // Left
115 } else if (yVal < 200 && direction != 3) {
116 direction = 1; // Right
117 }
118}
119
120void updateSnake() {
121 // Move the body segments
122 for (int i = snakeLength - 1; i > 0; i--) {
123 snakeX[i] = snakeX[i - 1];
124 snakeY[i] = snakeY[i - 1];
125 }
126
127 // Move the head
128 if (direction == 0) snakeY[0] -= BLOCK_SIZE; // Up
129 if (direction == 1) snakeX[0] += BLOCK_SIZE; // Right
130 if (direction == 2) snakeY[0] += BLOCK_SIZE; // Down
131 if (direction == 3) snakeX[0] -= BLOCK_SIZE; // Left
132}
133
134void checkCollisions() {
135 // Wall collision (Border)
136 if (snakeX[0] < 0 || snakeX[0] >= SCREEN_WIDTH ||
137 snakeY[0] < 0 || snakeY[0] >= SCREEN_HEIGHT) {
138 gameOver = true;
139 }
140
141 // Self collision
142 for (int i = 1; i < snakeLength; i++) {
143 if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
144 gameOver = true;
145 }
146 }
147
148 // Food collision
149 if (snakeX[0] == foodX && snakeY[0] == foodY) {
150 score++;
151 if (snakeLength < MAX_SNAKE_LENGTH) {
152 snakeLength++;
153 }
154 spawnFood();
155 }
156}
157
158void drawGame() {
159 display.clearDisplay();
160
161 // Draw border
162 display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
163
164 // Draw food
165 display.fillRect(foodX, foodY, BLOCK_SIZE, BLOCK_SIZE, SSD1306_WHITE);
166
167 // Draw snake
168 for (int i = 0; i < snakeLength; i++) {
169 display.fillRect(snakeX[i], snakeY[i], BLOCK_SIZE, BLOCK_SIZE, SSD1306_WHITE);
170 }
171
172 display.display();
173}
174
175void displayGameOver() {
176 display.clearDisplay();
177
178 // Draw border
179 display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
180
181 display.setTextSize(2);
182 display.setCursor(12, 12);
183 display.print("GAME OVER");
184
185 display.setTextSize(1);
186 display.setCursor(20, 35);
187 display.print("Final Score: ");
188 display.print(score);
189
190 display.setCursor(10, 50);
191 display.print("Click stick to play");
192
193 display.display();
194}