The aim of this project is to implement the famous 'Snake' game in an IoT environment. The game is powered by an ESP-WROOM-32S microcontroller, which is connected to an 8×8 RGB matrix used for display and a joystick with which the user can interact to control the movement. The project leverages multiple key concepts and technologies used in the IoT and embedded design worlds, such as system interrupts, driving signals through pins, reading analog values and converting them to digital values, logging data to the flash memory (through SPIFFS), and using Bluetooth to allow the user to interact with the board and perform various actions through sending commands.
The software architecture is comprised of 4 .ino source files:
When the game starts, the positions of the player and the prey are generated randomly on the RGB matrix. The snake can make a step on the map with a frequency, which is determined by a timer value. When this timer value is reached, an interrupt is triggered, the next position of the snake is computed and then rendered immediately. This is achieved by calling the 'update()' function in the main loop whenever the timer interrupt gets triggered. This function then calls 'update_running()', in the event the game is in the RUNNING state, which then calls the compute_next_position() function which is responsible for updating the snake's position.
The compute_next_position function decides the position of the snake at the next step, based on the current snake's position and a variable called 'direction', which can tell if the snake should go UP, DOWN, LEFT, or RIGHT. Between two timer interrupts, consecutive reads are performed on the Joystick's X and Y axes in order to determine if there is a change in direction. If the read value on one of the axes exceeds a certain threshold, then it can be inferred that the direction of the snake will change at the next rendering, thus the value of 'direction' variable is updated accordingly. Furthermore, after the first read outside the threshold, no more analog reads will be considered until after the next timer interrupt occurs.
The representation of the snake in memory is an array of integers, each integer representing the number of the LED corresponding to a piece of the snake:
uint8_t snake_body[NUMPIXELS];
Rendering consists of turning on the LEDs corresponding to the snake's position (snake_body array) and the prey's position. In order to interact with the RGB matrix, the project uses ”<Adafruit_NeoPixel.h>” library, which provides a simple API to control the LEDs.
Furthermore, the game offers 3 difficulty levels. The difference between all these levels is determined by the snake's movement speed, which is represented by the frequency at which the timer interrupt occurs in order to trigger the update of the snake's position on the matrix.
const unsigned int game_speed[MAX_LEVEL] = {1000000, 500000, 250000};
This array represents the values that a timer can use for a given difficulty level. The timer works by counting from 0 to this value and when the value is reached, the interrupt gets triggered. Thus, the lower the value, the higher the frequency at which the timer gets triggered and the more difficult the game is.
Furthermore, the button on the joystick can be used as an interrupt to increment the difficulty level and to restart the game after the game is over.
SPIFFS is used to log data to the flash memory for persistent usage. The data collected is represented by the scores obtained and the usernames of players who achieved those scores. The structure of the filesystem is simple: in the '/' directory, there can be found files with the format 'username'.txt, which contain the scores obtained by the respective users.
Thus, this module offers some API functions, which help with managing and updating the logged data on the flash.
void write_score(String profile, int score); // appends a new score to the username given as input (basically, it writes a number to the file representing the user) void list_best_score(String profile); // lists the best score obtained by a user void list_all_scores(String profile); void players_rank(); // lists all users, ranked by their highest score void add_user(String profile); // adds a new user to the file system (by creating a file with the username) void switch_user(String profile); // switches the user to an existing user in the file system void delete_user(String profile); // deletes user from file system void list_all_users(); // lists all users from the file system (by iterating on the / directory and listing the files with .txt extension)
The project uses Bluetooth communication as a way for the user to pass commands to the microcontroller and change some game settings or interact with the file system through the use of the API described above.
The user connects with the phone to the advertising ESP device and through a terminal BLE application, they can pass commands and receive responses from the device.
For this, I have used the BluetoothSerial library. In the main loop function, I call a function 'ble_check_receive', which checks if an input from the user has been received. The input is parsed and if it is a valid command, then the proper action is taken. A list of all available commands is given by typing the command 'help' in the terminal.
An overview of the available commands is provided below.
set speed slow - Set speed to slow set speed medium - Set speed to medium set speed fast - Set speed to fast help - Display this help menu best score <profile> - List the best score of a profile all scores <profile> - List all scores of a profile in descending order player rank - List player rankings by best score delete user <profile> - Delete a user profile switch user <profile> - Switch to a different user profile add user <profile> - Add a new user profile get current user - Get the current user profile list all users - List all user profiles pause - Pause the game continue - Continue the game
The implementation of the project can be seen in the following pictures and in the attached video on the top of the page.
The project achieves the proposed objectives: implementing the classic Snake game on a LED matrix and using Bluetooth communication and SPIFFS flash logging to enhance its functionality. The features have been tested and work successfully.
Improvements can be made, though, especially in the hardware-related zone. For example, there are times where the wires do not make full contact with the RGB matrix on the breadboard, which translates to power cutting off and turning the LEDs off, or having random LEDs turning on. To fix this, the matrix has to be adjusted a little bit, until you see it is working again and it must be ensured the matrix stays in a fixed position in order to reduce any possibility of this occuring.
Another improvement is regarding the code size, which consumes close to 90% of the available code memory. This is because the project includes multiple libraries (Bluetooth, SPIFFS, AdaFruit_NeoPixel, etc) that take high amounts of space.