Snake the Game
Snake the game
Now that we have adequately discussed and gotten hands on experience with the LED Matrix, it is time to start our main project for this kit. This entails programming our own version of the video game snake. As you may know, the gameplay is quite simple. You start with a snake which is just one dot and you can control the direction of the snake with arrow keys (buttons connected the Pi). There is also always an apple visible on the matrix. If the snakes touches/eats one of the apples it grows by one LED dot. The game is lost if the snake touches the wall or itself. Not only does the snake grow by every apple it eats, the movement also gets a bit faster. Because you only can control the direction, you need to react faster causing the game’s difficulty to rise. This game is perfect for using the LED Matrix with the 64 single leds. The apple is a single LED turned on in the led matrix and every part of the snake is also a LED turned on. After the game has ended we will show the number of points on the matrix. The total points is equal to the number of apples eaten. After that you will be able to begin a new game by pressing any button.
First we will show you the program code and then we will explain how the game logic was implemented. Some parts will be quite familiar because we used them in the previous examples. You can type the program by yourself or just download it using the following command line program.
import RPi.GPIO as gpio import time import max7219.led as led from random import randint gpio.setmode(gpio.BCM) button = [14,15,18,23] nMatrix = 1 matrix = led.matrix(cascaded=nMatrix) height = 7 width = (8*nMatrix)-1 def control(gpio): global direction if(gpio == 14): #rechts direction = [1,0] elif(gpio == 15): #oben direction = [0,-1] elif(gpio == 18): #unten direction = [0,1] elif(gpio == 23): #links direction = [-1,0] for i in button: gpio.setup(i,gpio.IN,pull_up_down=gpio.PUD_UP) gpio.add_event_detect(i, gpio.FALLING, callback=control) def startGame(): global snake, direction, apple snake = [[randint(2,width-4),randint(3,height-3)]] direction = [0,0] while direction == [0,0]: matrix.show_message("READY") newApple() def newApple(): global apple, snake appleSnake = False while appleSnake == False: appleSnake = True apple = [randint(0,width),randint(0,height)] for i in snake: if(i == apple): appleSnake = False print(apple) def endOfGame(): for i in range(0,2): matrix.clear() for i in range(0,width+1): for j in range(0,height+1): matrix.pixel(i,j,1) time.sleep(0.001) time.sleep(0.01) matrix.show_message("GAME OVER") points = len(snake)-1 matrix.show_message(str(points)+" PT") startGame() startGame() while True: noDelay = False newSnake = [snake+direction, snake+direction] for i in snake: if(i == newSnake): endOfGame() pass if(newSnake == apple): newApple() noDelay = True else: snake.pop() snake.insert(0,newSnake) if(snake > width or snake > height or snake < 0 or snake < 0 ): endOfGame() pass matrix.clear() for i in snake: matrix.pixel(i,i, 1) matrix.pixel(apple,apple,1) if(noDelay == False): newLength = (len(snake)-2)*0.01 time.sleep(0.3-newLength) else: time.sleep(0.3)
You should now be able to execute your program and to start a working snake game which you can control via the buttons. We added some stickers with arrows so you can easily mark the buttons. We would recommend you use them ;) So have fun and enjoy the game!
Step by step walkthrough
To sum the program up: We begin the programming by defining multiple variables and functions. The main gameplay and movements are coded later in the game programming in the while loop. The following global variables are available for use:
|buttons||A list with all the gpio pins we used for the buttons.|
|nMatrix||Number of matrix which we will use for the game. In this case: 1|
|height||Is an int in which we save the height of the matrix/field|
|width||Is an int and is used for the width of the matrix/field. This maybe varied be the number of matrix used.|
|matrix||Is the matrix object, we used it before.|
|snake||Is multi dimensional list with the coordinates of the whole snake.|
|direction||Is a list with the x and y coordinates in which the snake is moving|
|apple||Is a list with coordinates for the apple.|
|noDelay||Is a boolean which we need for correct controlling of the speed.|
|newSnake||Is a list with the new first coordinates of the snake.|
|newLength||Is an int which counts the length of the snake and is needed for the controlling the speed.|
Now that we have discussed all of the variables which we will use in the program we will now walk through the rest of the programming so that you can get a deeper understanding of the game’s logic.
In lines 6 to 22 we are programming the controls and declaring all of the necessary variables for the size for the matrix. In line 7 we create the variable buttons. In line 24 we use a for-loop to set all of the variables as inputs and then connect these inputs with the function control().
The function control() is defined in lines 13 through 22. Because we want to change the variable direction globally, we must first define it in line 14. If we don’t do that, we would only change the variable inside the function and it would not have any effect on the value outside of the function. The function gets the number of the gpio pin as a parameter. With this we use an if-condition in lines 15 to 22 to control which button was pressed and change the direction of the snake accordingly. We can change the direction by changing the value in the direction variable. You can enter comments describing which button is for which direction.
In line 28 the second function startGame() begins. In this function all of the variables are set to start the game including the variable for the initial dot of the snake. It would be quite boring if we start the snake at the same spot every time so we use the
randint() function to create a random starting point. Because we don’t want the player to randomly start next to a wall and then die immediately, we create a little safety net based on the height and width variables.
To prevent the game from starting immediately after starting the program, we created a construct in lines 32 and 33 that displays the message “READY” and does not change, until a directional button is pressed. Once pressed, the function newApple() will be called in line 34.
Continuing on with the newApple() function, we not only want to place the new snake on a random field in the matrix, but also the apple. Just as how we didn’t want the snake to be generated near a wall potentially causing imminent doom, we also need to make sure that the apple isn’t created inside the existing snake but inside the matrix. To ensure this, in line 39 we constructed a while loop that remains running as long the variable appleSnake is false. In line 40 this variable is set to true. After that the list apple is filled with random coordinates. In line 42 we use a for loop to go through the whole snake to make sure that the newly created apple isn’t inside the snake. If this is the case then the variable appleSnake will be set to false and the while loop repeats itself until it finds an apple which is not inside the snake. These steps ensure that the apple is always created outside of the snake.
The next function to be discussed is named endOfGame() and is called, like the name suggest, at the end of the game. The purpose of this function is to first create an animation letting every player know that it is the end of the game and then allowing the player to see the total number of points scored in the game. We create these animation in lines 48 to 54 by lighting up the entire led matrix. After that we display the message “GAME OVER” on the matrix. In line 56 we are calculating the amount of points by counting the number of elements in the variable snake and calculating it -1 for the starting point of the snake. In line 59 the function startGame() is called to reset everything again for the next game.
After defining all the functions which are necessary for the game, we need to finally start the game. For this we are call the startGame() function for the first time in line 61.
The while loop for all of the movements and rules in the Game starts on line 63.
First we set the variable noDelay to false. This variable’s importance comes later for the length of the last delay. In lines 65 and 66 a new list is declared with the new position of the head of the snake. TO do this we take the current first position (like the head) and add the current direction. With this we can easily calculate the new position.
This is needed for testing the rules first described above. In lines 67 to 70 we are testing the first rule which says that the game is ended if the snake moves into itself. To do this we again use a for-loop to check every part of the snake and an if-condition to determine the outcome. If it is indeed the case that the snake has run into itself, the function endOfGame() is called and with
pass the while loop starts from its beginning.
In lines 72 to 76 we are checking if the snake reached the apple. If this is the case, we first call the function newApple() in line 73 so that a new gets created. In line 74 the variable noDelay is set to true. This is important because otherwise the player would get the feeling that the snake has stopped.
Because the snake is only growing when it eats an apple, we need to remove the last element of the list if it didn’t eat an apple. For this we are using the
.pop() function on the list.
Before we test the last rule, we add the new head position to the snake in line 77. To carry this out we use the
.insert() function on the list. Because we want to add the new element in the beginning we are using 0 as first parameter and the element as the second parameter.
Lines 79 to 82 are similar to the bit of code above except that this time we are checking if the new head of the snake is outside of the matrix/playing field. If this is the case, we call the endOfGame() function.
In the last part, we first display the snake in lines 85 to 87 and then the apple.
The only thing to take care of now is the speed of the game. If the snake didn’t eat an apple, the speed is simply calculated by the length of the snake. This is done by using the variable newLength and multiplying this by 0.01. This will then get subtracted from the usual 0.8 second delay. Here you can change the speed of the game to slower or faster as you desire. The only exception is when the snake grows by eating an apple in which case the delay is a bit shorter.
This program is a good use case for practicing using functions and how they can make the code more readable and shorter. We would also suggest that you start playing around with the program and customize it with changes to your liking. This could be especially interesting for the delay time and texts.
More than one matrix
Until now we only worked with one LED Matrix at the time but you can easily chain multiple LED matrices together. For this you can simply use the connections on the top of the first LED Matrix to connect it to a second LED matrix. You don’t have to stop at 2 LED matrices but can use multiple ones to make things even more interesting. They have the same pin configuration on the down side. We are using this the same example like before just with a second matrix connected.
Before the snake game will expand to the second LED Matrix, we first need to change the variable nMatrix to 2.
matrixe = 2 # Number of connected led matrix
If you now start the game again, you can also play on the second matrix. This ends up being easy because in line 8-11 we use the number of LED matrices to create the playing field.