My friend (Eeman Majumder) impulsively bought the new Arduino UNO R4 WIFI while we were hosting him at our place for a 2-week dev sprint. The day it came, we were thinking of fun things to do with it, and luckily we had a dual-axis joystick controller lying around the house. I put two and two together and built the popular retro snake game on the LED matrix! đ
da setup đď¸
Letâs get some setup out of the way so we can get to the fun stuff ASAP!
First we get the environment and make the electrical connections, for this:
- Either install the Arduino IDE OR use the cloud editor.
- Get a dual-axis joystick module and connect the Arduino's 5V, GND, A0, A1, and D13 to the module's 5V, GND, URX, URY, and SW respectively.
In the code, we define some constants, variables and objects that will set up the game. This includes some library imports, our input pins, the LED matrix grid and its size, the food object, the snake object and its speed, length, and direction, and finally the current and high score.
main game logic đ§
Now that the setup is done, itâs time to implement the core game logic. Yay!
We have to do a couple of things here â handle the joystick, move the snake, check for any collisions, update the screen, generate new food and add a small delay. The flowchart explains this whole process.
Take input from joystick
We constantly input the analog values (0 to 1024) from the joystick, normalize them to -512 to 512 in both axes (this is optional, but it just makes things clearer), then map the value to its corresponding direction.
We also disallow any move from the joystick that is directly opposite of the current direction while the game is running. This is because the snake canât go backwards â which will be considered an illegal move and end the game.
void handleJoystick() {
xValue = analogRead(joystickXPin);
yValue = analogRead(joystickYPin);
swState = not digitalRead(joystickSwPin);
xMap = map(xValue, 0, 1023, -512, 512);
yMap = map(yValue, 0, 1023, 512, -512);
// disallow moving in the opposite direction of current direction while the game is running
if (xMap <= 512 && xMap >= 10 && yMap <= 511 && yMap >= -511) {
if ((!isGameOver && directionPrev != 3) || isGameOver) { direction = 1; } // Right
} else if (xMap >= -512 && xMap <= -10 && yMap <= 511 && yMap >= -511) {
if ((!isGameOver && directionPrev != 1) || isGameOver) { direction = 3; } // Left
} else if (yMap <= 512 && yMap >= 10 && xMap <= 511 && xMap >= -511) {
if ((!isGameOver && directionPrev != 4) || isGameOver) { direction = 2; } // Up
} else if (yMap >= -512 && yMap <= -10 && xMap <= 511 && xMap >= -511) {
if ((!isGameOver && directionPrev != 2) || isGameOver) { direction = 4; } // Down
}
if (!isGameOver) {
directionPrev = direction;
}
}
Move the snake
We have to actually make the snake move, it wonât move on its own. We shift the body of the snake (which is essentially just an array of points having XY coordinates) forward by updating the coordinates of all the points, starting from the end (tail), to the coordinates of the next point.
Given the current direction, we shift the head +1 in that direction. In case it is at the wall, we wrap it around to the other side. We could disable this to make the game harder but for a pleasurable playing experience, I kept it.
void moveSnake() {
// Move the body of the snake
for (int i = snakeLength - 1; i > 0; i--) {
snake[i] = snake[i - 1];
}
// Move the head of the snake
switch (direction) {
case 1: // Right
snake[0].x = (snake[0].x + 1) % matrixSizeX;
break;
case 2: // Up
snake[0].y = (snake[0].y - 1 + matrixSizeY) % matrixSizeY;
break;
case 3: // Left
snake[0].x = (snake[0].x - 1 + matrixSizeX) % matrixSizeX;
break;
case 4: // Down
snake[0].y = (snake[0].y + 1) % matrixSizeY;
break;
}
}
Check if a collision occurs
We have to check two collisions â one with self and one with the food.
If the snake collides with itself, we end the game. We do this by checking if the headâs position is equal to any of the points in the bodyâs position.
If it collides with the food, we increment the snakeâs length and the score and decrement the current speed by 5 if itâs still above the max speed.
void checkCollisions() {
// Check for collision with self
for (int i = 1; i < snakeLength; i++) {
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
// Game over, restart
gameOver();
}
}
// Check for collision with food
if (snake[0].x == food.x && snake[0].y == food.y) {
snakeLength++;
score++;
generateFood();
if (currentSpeed > maxSpeed) {
currentSpeed -= 5;
}
}
}
Generate new food
If the snake eats the food, we will have to put new food on the grid, right? This is simple â we update the foodâs position to a random point in the grid, make sure that it doesnât overlap with the snake, and update the matrix.
void generateFood() {
food.x = random(matrixSizeX);
food.y = random(matrixSizeY);
// Make sure the food does not overlap with the snake
for (int i = 0; i < snakeLength; i++) {
if (food.x == snake[i].x && food.y == snake[i].y) {
generateFood();
return;
}
}
grid[food.y][food.x] = 1;
matrix.renderBitmap(grid, matrixSizeY, matrixSizeX);
}
Update the LED matrix
To show all these changes above, we reset our grid (meaning switch of all LEDs on the matrix), then update the grid based on the snake and food positions, and then render the new grid on the LED matrix.
void updateMatrix() {
resetGrid();
for (int i = 0; i < snakeLength; i++) {
grid[snake[i].y][snake[i].x] = 1;
}
grid[food.y][food.x] = 1;
matrix.renderBitmap(grid, matrixSizeY, matrixSizeX);
}
Game Over
Finally, when the snake bites itself, the game gets over.
Here, we check if we made the high score, play the âGame Overâ text on the LED matrix, display the score for a few seconds, and then ask the player if they want to continue playing the game.
void gameOver() {
isGameOver = true;
resetGrid();
if (score >= highScore) {
highScore = score;
}
printText(" Game Over ", 35);
for (int i = 0; i < 4; i++) {
displayScore(true, false);
}
continuePlaying();
initializeGame();
}
finishing touches đŞ
At this point, our game is functional. We could stop here if we want and call it a day. Or, we could go the extra mile and add some finishing touches for a polished look and an overall satisfying playing experience. For this, I added some aesthetic additions and some qualify-of-life improvements.
Firstly, an ascii art intro animation when the game loads. It says SNAKE-R4.
I ended up designing a numbers font in Arduinoâs LED Matrix editor for displaying the score, because the fonts in the ArduinoGraphics library did not quite align in the center of the LED matrix and it really bugged me.
Then, I added the option to continue playing by selecting Y/N with the joystick. Again, the frames for the animation were designed in the LED Matrix editor and included in the code using the .h files provided by it.
All we have to do in the code is to render the appropriate frame based on the joystick direction (left or right) and play the yes_option or no_option animation sequence when one of them gets selected.
void continuePlaying() {
matrix.loadSequence(continue_playing);
matrix.renderFrame(2);
String selectedOption = "yes";
while (!swState) {
handleJoystick();
if (direction == 3) {
matrix.renderFrame(0);
selectedOption = "yes";
} else if (direction == 1) {
matrix.renderFrame(1);
selectedOption = "no";
}
delay(100);
}
if (selectedOption == "no") {
matrix.loadSequence(no_option);
matrix.play();
delay(1500);
printText(" thx for playing! made by siphyshu ", 35);
while (true) {
displayScore(false, true);
}
} else if (selectedOption == "yes") {
matrix.loadSequence(yes_option);
matrix.play();
delay(1500);
}
}
Finally, if the player chooses no, the game ends with a credit sequence and the high-score is shown on the screen permanently in a never-ending loop.
Tada! đ With these final touches, we are done with Snake-R4. I had a lot of fun building (and playing! :D) this project, and if you did too while reading about it, a follow would be amazing!
View the repository: https://github.com/siphyshu/snake-R4
Follow me on Twitter/X for behind-the-scenes:
Stay safe, have fun, and always be building! đŤď¸
Top comments (2)
Awesome work đŻ @siphyshu
Thankyou! đ