Making the Snake Eat

Snake head same cell as food (43:12)

Q: When does the snake eat the food?
A: When its head is in the same cell with the food.

Pretty simple: so the first thing we have to do is to check if the snake’s head cell and the food cell are the same for every update of the game. If that happens, the snake will eat the food. This requires the creation of another Food object in another random location. To do this, create another method in the game class:

def check_collision_with_food(self): 

Now we have to check if the head of the snake is in the same cell with the food.

if self.snake.body[0] that’s the snake’s head == self.food.position: 

Let’s print something to see if this works.

print("Eating food") 

Now we have to call this function in the update method of the game class.

 self.check_collision_with_food() 

Now run the game: As you can see, at the console we can see the message “Eating something.” each time we collide with the food. Now we have to change the position of the food object each time it collides with the snake. We can call the generate_random_pos of the food object to achieve that.

Let’s do it. We can delete the print statement, we don’t need it anymore. Then we can add the following line:

Self.food.position =  self.food.generate_random_pos() 

Let’s run the code once more. As you can see, now, when we “eat” the food, its position on the screen changes to a random location. But, there is a problem. There is a small probability that the new cell position is on top of the snake.

We can’t allow this to happen. So, we have to modify the generate_random_pos() method of the Food class to check if the new position exists in a list of cells. If this is true, it has to generate a new position for the food object.

This list of cells is going to be the snake body list, let’s name it snake_body and pass it in as an argument to the method. Now, we have to add the following to the method:

while position in snake_body: 

which means that the random position we generated is one of the segments of the snake’s body, then we have to create another random position. We copy this block of code in the while loop like this. But this is not a good programming practice, we have duplicate code here.

Instead we are going to create a method for this block of code. Let’s call it

def generate_random_cell(self):
 x = random.randint(0, number_of_cells - 1) y = random.randint(0, number_of_cells - 1) 
 return Vector2(x, y) 

This method returns a random cell position.

Now we modify the generate_random_pos like this:

position = self.generate_random_cell() 
while position in snake_body: Position = self.generate_random_cell() 
return position

The method is now clear and concise. However, since we modified the method arguments, we need to update the method call in all the locations where it is used. In particular, when we first create the food object, we need to pass the snake_body as an argument to the __init__ method and pass it in the call of the generate_random_pos method. In the Game class, we need to update the creation of the Food object to include the snake's body list as an argument:

self.food = Food(self.snake.body) 

In the check_collision_with_food() method, when we call the method to update the Food object's position, we also need to pass in the snake's body list as an argument. By updating the food creation and placement method to take into account the snake's body, we can ensure that the food will never spawn on top of the snake. Now when we test the game again, we can see that it is functioning as intended and the food is placed in a valid location.