Creating Snake Game in Arduino Uno R4 WiFi

This post will teach you how to build a simple snake game using Arduino Uno R4’s built-in LED matrix and joy stick.

As you can see in the video I’m using a joystick to control the snake directions. If you are not yet familiar on how the joystick works, you can read on the explanation below, otherwise you can skip and go directly to the code.

The joystick has 5 pins. Two pins +5V and GND are the supply pins while VRx and VRy corresponds to output voltages on X and Y axis. It is like a potentiometer, if you move the joystick to a certain direction, the output voltage changes. To make it easier to understand I created a diagram to show the output voltages corresponding to the movement of the joystick. As you can see in the diagram, if you move the joystick fully-upward, VRy will be 0V, meanwhile if you move it fully-downward it will be VRy will be 5V. Same goes with fully-left VRx will be 0V and fully-right will result to VRx = 5V. If you do not move the joystick VRx and VRy should be ideally 2.5V. I mentioned, ideally because, sometimes there are imperfections on how the joystick is manufactured so it may not be perfectly at the center during idle (it can deviate +/- 0.x Volts).
The 5th pin is SW, which is the switch or pushbutton,if not pressed SW will have a non-zero volatge but when pressed,SW pin will be 0V.

L E F T ( V R x = 0 V ) D U O P W ( N V ( R V y R y = = 0 V 5 ) V ) R I G H T ( V R x = 5 V )

The output of the joystick is a voltage which is an analog signal, the microcontroller in the Arduino is Digital so the Analog signals VRx and VRy should be converted into digital values. The good news is that Arduino as a built-in Analog to Digital Converters which are easily accessible. At the hardware level, Arduino has Analog pins, A0 to A7. We just need to connect the signal that we want to convert and call the Arduino function analogRead(). For the snake game, we will not be using the SW pin so the connection of joystick to Arduino is as shown in the table below.

Joystick Pin Arduino Pin
GND GND
+5V 5V
VRx A0
VRy A1
SW no connection

We will be using the default ADC resolution in Arduino Uno R4 Wifi, which is 10 bits, that means the voltages will be converted to a 10-bit value. This means that minimum value 0V is b0000000000 and the maximum value 5V is b1111111111 (1023 in decimal notation). I created a diagram below to make it easier to understand. When the joystick is idle and the voltages are at 2.5V, the digital value should be around 511 or 512, bit that can further deviate +/- 5 due to manufaturing imperfections of the joystick. It does not matter though, because we are just making a sanke game and do not need high accuracy.

L E F T ( 0 ) D O W U N P ( ( 1 0 0 ) 2 3 ) R I G H T ( 1 0 2 3 )

I created a function named get_dir() to take care and encapsulate all the code or logic related to calculating the snake directions. You can expand the code block below to see the code. The function is very simple, I read the value at pins A0 for x-axis and A1 for y-axis. If the value read for x-axis is 0 to 300, the function returns LEFT, if the x-axis is 723 to 1023, the function returns RIGHT. Since the snake cannot go directly to the opposite direction, for example, the sanke is moving to the LEFT, then we move the joystick to the RIGHT, it should not take effect because that is not allowed. To do that in the code, we just add && dir != <OPPOSITE_DIRECTION>. If the conditions for all directions (LEFT, RIGHT, UP, DOWN) are not satisfied, the function will just return the current direction (return dir)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#define LEFT 0
#define RIGHT 1
#define UP 2
#define DOWN 3

// Global variables
byte dir = RIGHT;

int get_dir(){
  // Get values from ADC (joy stick)
  int x = analogRead(A0);
  int y = analogRead(A1);

  if((x >= 0) && (x<= 300) && dir != RIGHT){
    return LEFT;
  }
  else if( (x >=723) && (x <= 1023) && dir != LEFT){
    return RIGHT;
  }
  else if((y >= 0) && (y <= 300) && dir != DOWN){
    return UP;
  }
  else if((y >= 723) && (y <= 1023) && dir != UP){
    return DOWN;
  }
  else return dir;
}

void loop() {
  // Get the snake direction
  dir = get_dir();
}

The Arduino Uno R4 Wifi has an on-board 8x12 LED matrix and software library to control the matrix so it is easy to use. To use the library, we first need to include the following line of codes.

1
2
3
4
5
6
7
#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;

void setup() {
  matrix.begin();
}

After calling the matrix.begin(), we can declare an 8x12 array (byte frame[8][12]) to hold the values of each element of the matrix. Value 0 means LED is OFF and 1 means ON. We can visualize the frame[8][12] array like the diagram below. To select an element we need to specify the row and column. Let us say we want to select the top-left, we set the row as 0 and column as 0, it will be frame[0][0]. The the top-right will be frame[0][11], bottom-left is frame[7][0], and bottom-right is frame[7,11]. The row values starts from 0 to 7 (top to bottom) and column starts from 0 to 11 (left to right).

0 7 , , 0 0 8 x 1 2 L E D M a t r i x 0 7 , , 1 1 1 1

I have created a function display_frame() which takes care of the controlling the LED matrix. The function does the following:

  • Clears the frame to ensure that there is no unexpected LED that will be turned on
  • Set the frame elements to 1 for snake
  • Set the frame elements to 1 for food
  • Call the matrix.renderBitmap() to reflect the values of frame array to the actual LED matrix

Expand the code block to see the complete display_frame() code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;

void setup() {
  matrix.begin();
}

// Global variables
byte frame[8][12];
byte dir = RIGHT;
byte snake_len = 2;
byte col_idx[96];
byte row_idx[96];
byte food_col = random(2,12);
byte food_row = random(1,8);

void display_frame(){
  // Clear the frame first - Turn off all LEDs
  for(int i=0; i<8; i++){
    for(int j=0; j<12; j++){
      frame[i][j] = 0;
    }
  }  

  // Turn on LEDs for snake
  for(int i=0; i<snake_len; i++){
    // Turn on LED for Body
    frame[row_idx[i]][col_idx[i]] = 1;
  }

  // Turn on LEDs for food
  frame[food_row][food_col] = 1;

  // Display frame value in the LED matrix
  matrix.renderBitmap(frame, 8, 12);
}

void loop() {
  // Get the snake direction
  dir = get_dir();
  // Display food and snake in the LED Matrix 
  display_frame();
}

I used byte col_idx[96] and byte row_idx[96] to store the snake position. The size of the array is 96 because it is the maximum length of the snake in 8x12 matrix. The first index col_idx[0] and row_idx[0] is the head of the snake and the remaining is the body. In order to animate the movement of the snake, we need to do the following:

  • Display a frame
  • Add a delay (which will be the speed of animation or snake speed)
  • Calculate the next frame then loop back to the first step.

The coordinates of the snake body for the next frame is just the shifted by 1 coordinates of the previous frame. It is expressed in the code as follows:

1
2
3
4
5
  // body
  for(int i=snake_len-1; i>0; i--){
    col_idx[i] = col_idx[i-1];
    row_idx[i] = row_idx[i-1];
  }

The coordinates for the snake head is dependent on the direction dir. If the dir is RIGHT or LEFT, the row_idx[0] should be constant as the snake movement is horizontal then the col_idx[0] is incremented by 1 if RIGHT and decremented by 1 if LEFT. To handle wrap around if dir is RIGHT and the current col_idx[0] == 11 , we should set col_idx[0] back to 0. If the dir is LEFT and the col_idx[0] == 0, col_idx[0] should be set to 11. The same logic is applied to verical movement. You can expand the code block to see the complete code for get_snake_position() function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// Global variables
byte frame[8][12];
byte dir = RIGHT;
byte snake_len = 2;
byte col_idx[96];
byte row_idx[96];
byte speed = 150;

void get_snake_position(){
  // Logic to Calculate the next frame
  // body
  for(int i=snake_len-1; i>0; i--){
    col_idx[i] = col_idx[i-1];
    row_idx[i] = row_idx[i-1];
  }
  // head
  if (dir == RIGHT) {
    // Wrap around to left
    if(col_idx[0] == 11){
      col_idx[0] = 0;
    }
    else {
      col_idx[0] = col_idx[0] + 1;
    }
  }
  if(dir == LEFT) {
    // Wrap around to right
    if(col_idx[0] == 0){
      col_idx[0] = 11;
    }
    else{
      col_idx[0] = col_idx[0] - 1;
    }
  }
  if (dir == DOWN) {
    // Wrap around to top
    if(row_idx[0] == 7){
      row_idx[0] = 0;
    }
    else{
      row_idx[0] = row_idx[0] + 1;
    }
  }
  if(dir == UP) {
    // Wrap around to bottom
    if(row_idx[0] == 0){
      row_idx[0] = 7;
    }
    else{
      row_idx[0] = row_idx[0] - 1;
    }
  }
}

void loop() {
  // Get the snake direction
  dir = get_dir();
  // Display food and snake in the LED Matrix 
  display_frame();
  // Get the snake positions for the next frame
  get_snake_position();
  // Speed of the animation/snake movement
  delay(speed);
}

Generating the snake food is relatively easier compared to the snake as we just need to generate random coordinates in the matrix. I used byte food_col and byte food_row to store the value of the food coordinates. I also used food_hit variable to ensure that the food coordinated will only be randomized if the food is hit hit the snake head.

1
2
3
4
5
6
7
8
9
void gen_food(){
 // Generate coordinates for food
  if(food_hit == 1){
    food_col = random(0,12);
    food_row = random(0,8);
    // Set food_hit to 0 so it will not randomize food coordinates unless hit
    food_hit = 0;
  }
}

To determine if the food is hit, we need to check if the coordinated of the head row_idx[0] and col_idx[0] is the same as food_row and food_col respectively. If that condition is satified, we will need to increment the snake_len by 1 and set the food_hit variable to 1 so a new food coordinates will get randomized.

1
2
3
4
5
6
7
8
9
void chk_food_hit(){
  // Logic to detect food is hit
  if(col_idx[0] == food_col && row_idx[0] == food_row){
    // Increment snake length if food is hit
    snake_len = snake_len + 1;
    // Set food_hit to 1 to enable generation of new food coordinates
    food_hit = 1;
  }
}

To detect game over, we need to determine if the snake head coordinates row_idx[0] and col_idx[0] values is the same with any coordinates of its body. The code to detect if snake noy is hit is shown below.

1
2
3
4
5
6
7
8
void chk_snake_body_hit(){
  for(int i=1; i<snake_len; i++){
    // Check if head hit the body
    if((row_idx[i] == row_idx[0]) && (col_idx[i] == col_idx[0])){
      game_over();
    }
  }
}

If the snake body is hit, we will call the game_over() function which pauses snake movement, blinks 5 times and resets the snake_len to 2. Expand the code block to view the complete game_over() code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void game_over(){
  // Pause Snake movement and blink for 5 times
  for(int i=0; i<5; i++){
    // Display current frame (paused movement)
    matrix.renderBitmap(frame, 8, 12);
    delay(100);
    // Display blank frame (to make LEDs blink)
    matrix.renderBitmap(blank_frame, 8, 12);
    delay(100);
  }
  // Reset the length of snake 
  snake_len = 2;
}

The complete code is available below or can be downloaded here Snake Game

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Simple Snake Game for Arduino Uno R4 Wifi
// Author: Albert Landicho
// Website: eeplayground.com

#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;

void setup() {
  matrix.begin();
}

#define LEFT 0
#define RIGHT 1
#define UP 2
#define DOWN 3

// Global variables
byte frame[8][12];
byte dir = RIGHT;
byte snake_len = 2;
byte col_idx[96];
byte row_idx[96];
byte food_col = random(2,12);
byte food_row = random(1,8);
byte speed = 150;
byte food_hit = 1;
byte blank_frame[8][12];

int get_dir(){
  // Get values from ADC (joy stick)
  int x = analogRead(A0);
  int y = analogRead(A1);

  if((x >= 0) && (x<= 300) && dir != RIGHT){
    return LEFT;
  }
  else if( (x >=723) && (x <= 1023) && dir != LEFT){
    return RIGHT;
  }
  else if((y >= 0) && (y <= 300) && dir != DOWN){
    return UP;
  }
  else if((y >= 723) && (y <= 1023) && dir != UP){
    return DOWN;
  }
  else return dir;
}

void display_frame(){
  // Clear the frame first - Turn off all LEDs
  for(int i=0; i<8; i++){
    for(int j=0; j<12; j++){
      frame[i][j] = 0;
    }
  }  

  // Turn on LEDs for snake
  for(int i=0; i<snake_len; i++){
    // Turn on LED for Body
    frame[row_idx[i]][col_idx[i]] = 1;
  }

  // Turn on LEDs for food
  frame[food_row][food_col] = 1;

  // Display frame value in the LED matrix
  matrix.renderBitmap(frame, 8, 12);
}

void game_over(){
  // Pause Snake movement and blink for 5 times
  for(int i=0; i<5; i++){
    // Display current frame (paused movement)
    matrix.renderBitmap(frame, 8, 12);
    delay(100);
    // Display blank frame (to make LEDs blink)
    matrix.renderBitmap(blank_frame, 8, 12);
    delay(100);
  }
  // Reset the length of snake 
  snake_len = 2;
}

void chk_snake_body_hit(){
  for(int i=1; i<snake_len; i++){
    // Check if head hit the body
    if((row_idx[i] == row_idx[0]) && (col_idx[i] == col_idx[0])){
      game_over();
    }
  }
}

void chk_food_hit(){
  // Logic to detect food is hit
  if(col_idx[0] == food_col && row_idx[0] == food_row){
    // Increment snake length if food is hit
    snake_len = snake_len + 1;
    // Set food_hit to 1 to enable generation of new food coordinates
    food_hit = 1;
  }
}

void get_snake_position(){
  // Logic to Calculate the next frame
  // body
  for(int i=snake_len-1; i>0; i--){
    col_idx[i] = col_idx[i-1];
    row_idx[i] = row_idx[i-1];
  }
  // head
  if (dir == RIGHT) {
    // Wrap around to left
    if(col_idx[0] == 11){
      col_idx[0] = 0;
    }
    else {
      col_idx[0] = col_idx[0] + 1;
    }
  }
  if(dir == LEFT) {
    // Wrap around to right
    if(col_idx[0] == 0){
      col_idx[0] = 11;
    }
    else{
      col_idx[0] = col_idx[0] - 1;
    }
  }
  if (dir == DOWN) {
    // Wrap around to top
    if(row_idx[0] == 7){
      row_idx[0] = 0;
    }
    else{
      row_idx[0] = row_idx[0] + 1;
    }
  }
  if(dir == UP) {
    // Wrap around to bottom
    if(row_idx[0] == 0){
      row_idx[0] = 7;
    }
    else{
      row_idx[0] = row_idx[0] - 1;
    }
  }
}

void gen_food(){
 // Generate coordinates for food
  if(food_hit == 1){
    food_col = random(0,12);
    food_row = random(0,8);
    // Set food_hit to 0 so it will not randomize food coordinates unless hit
    food_hit = 0;
  }
}

void loop() {
  // Get the snake direction
  dir = get_dir();
  // Display food and snake in the LED Matrix 
  display_frame();
  // Check if snake body is hit by its head
  chk_snake_body_hit();
  // Check if the food is hit
  chk_food_hit();
  // Get the snake positions for the next frame
  get_snake_position();
  // Generate random coordinates for food 
  gen_food();
  // Speed of the animation/snake movement
  delay(speed);
}

You can download the complete code in this link

The snake game that I created is basic, you can add more features like the following:

  • Displaying score during game over
  • Storing and displaying high score during game over
  • Make the randomization of food coordinates more sophisticated such that it will present snake coordinates will be excluded
  • Add a super food feature (a bigger food more than 1 LED) that increments snake length more than or makes the snake speed faster
  • Add levels, and increase snake speed as it level up.

There is a lot of fun things you can add to this code, try it out and enjoy!