This is in draft mode....

Moving the Snake

Remove last cell add to start (27:34)

Let’s think about the movement of the snake.

As we said, our snake is just a list of three objects. We can move the snake object by removing the last cell in the list and adding a new cell to the beginning of the list. This will allow us to simulate a movement of the snake object across the grid.

Let’s say we want to move the snake to the right. We remove the last cell from it, and we add another one at the beginning, one cell to the right. Our snake moved one cell to the right.

Let’s say we want to move the snake down. We remove the last cell from it, and we add another one at the beginning, one cell to the bottom. The head of the snake moved one cell downwards. Cool huh! No fancy math required.

Just remove the last cell, and add another one to the direction of the movement.

Because we are using Vector2() objects to represent the segments of the snake's body, we can easily calculate the coordinates of the segment we are adding.

We can perform mathematical operations with Vector2 objects.

For example, let's take the head of the snake, which is a Vector2 object with an x-value of 6 and a y-value of 9. We can add another vector to it. For instance, if we add theVector2(1,0), the resulting vector would be (7,9), which is like moving the head to the right. If we added the Vector2(-1,0), the head would move to the left.

In the same way, if we added a Vector2(0,-1), the head would move upwards, and if we added a Vector2(0, 1), the head would move downwards. Vector2 objects are making our task much easier.

Right Now, let's implement this in code. First, let's make the snake move to the right. Then we will implement user input to control its movement.

We will add another attribute to the snake class. We need to know the direction in which the snake is moving, and this attribute will be a Vector2 object. So we will add the following line to the Snake class:

self.direction = Vector2()

And since we want the snake to move to the right, we will set the values inside the Vector2

constructor like this:

Vector2(1,0)

Now, let’s create an update method where the position of the snake is going to be updated.

line 36: def update(self):

As we said before, moving the snake is just two actions.

First we are going to remove the last segment of the snake’s body. Let’s do it.

def draw(self): self.body = self.body[:-1]

In python, lists can be indexed using square brackets []. The colon ":" is used for slicing, and it allows you to select a range of elements from a list. So, when you use [:-1] it means you are selecting all elements from the beginning of the list up to the second-last element, which effectively removes the last element from the list.

In this case, the self.body list is being reassigned to itself, but with the last element removed. So, this line of code is removing the last segment of the snake's body, as stated before.

Now our snake object's body list has only two elements. We need to add another one to the beginning of the list. To do this, we will use the `insert()`` method for lists, like this:

self.body.insert(0,)

that’s the index of the first element of the list.

Now, we need to add the head of the snake, but moved in the direction specified by the direction attribute. We can do this by adding the snake's head, self.body[0], to the direction vector self.direction.

self.body[0] + self.direction

This will move the snake's head in the direction specified by the direction attribute, and add it to the beginning of the snake's body list. Believe it or not, two lines of code are needed to move the snake. Now let’s call the update method inside the game loop.

Before the drawing part of the game loop let’s type: snake.update() Now, let’s run the game to see what happens!

As you can see the snake moves! But, it moves too quickly and it disappears from the game window.

Correct Snake from moving too fast (32:05)

Let’s first address the first problem. The game loop is executed 60 times per second, so the update() method of the snake object is also called 60 times per second. This causes the snake to move 60 cells to the right every second, which is why it moves so quickly. Instead of calling the update() method of the snake in every execution of the game loop, we need to update it at a slower rate, for example, every 200 milliseconds. This will slow down the snake's movement and make it more visible on the screen. To achieve this, we are going to create a timer event using the pygame library.

First, let's create a custom event for our game and give it a name:

SNAKE_UPDATE = pygame.USEREVENT

This line of code creates a variable SNAKE_UPDATE and assigns it the value of pygame.USEREVENT. USEREVENT is a special event type in pygame that can be used to create custom events.

In this case, it is used to create an event that will be triggered every time the snake's position needs to be updated. Now, let's tell pygame when to trigger this custom event. We want to trigger it every 200 milliseconds. To do this, we will use the set_timer() function from the pygame.time module.

pygame.time.set_timer(SNAKE_UPDATE, 200)

This function creates a timer that will trigger the SNAKE_UPDATE event every 200 milliseconds. The first argument is the event that needs to be triggered and the second argument is the interval in milliseconds. In this way, we are ensuring that the snake is updating its position every 200 milliseconds and not 60 times per second, avoiding the problem of the snake moving too fast.

Now, in the game loop, as we said before, we check for any events that happened since

the last time the game loop was executed. Let's add a check for our custom event.

This is very simple. if event.type == SNAKE_UPDATE: snake.update()

This code checks if the event type is equal to SNAKE_UPDATE and if it is, it calls the update() method of the snake object. This ensures that the snake's position is updated only when the SNAKE_UPDATE event is triggered and not every time the game loop is executed. It's important to remove the call to the snake.update() method that was previously in the game loop, to avoid updating the snake's position more than once per interval.

With this setup, let's run the game once more to see if it works. And as you can see, the snake is moving a lot slower!

Perfect, now the snake's movement is visible and the game is more playable.

Of course, we can’t control the snake yet, so it will disappear from the screen in a few seconds. So, let’s add keyboard controls to the game. In order to control the snake's movement, we need to detect when the player presses certain keys on the keyboard. To do this, we add the following code inside the game loop:

if event.type == pygame.KEYDOWN:

This line of code checks if the event type is equal to the KEYDOWN constant, which means the player has pressed a key.

Now, we need to determine which key the player pressed. We can do this by using the event.key attribute.

This attribute returns a constant representing the key that was pressed. For example, to check if the player pressed the UP arrow key, we can use this if statement:

if event.key == pygame.K_UP: This line of code checks if the event.key

constant is equal to the pygame.K_UP constant, which represents the UP arrow key.

If the player presses the UP arrow key, we have to move the snake upwards. We can do this by updating the snake's direction attribute to move up like this:

snake.direction = Vector2(0, -1) When we set the snake's direction to Vector2(0, -1), we are indicating that the snake should move upwards. The Vector2 class represents a 2D vector with x and y components.

In this case, we are setting the x component to 0 and the y component to -1. The origin of the game window is located at the top-left corner, which means that the y-coordinates increase as we move downwards. So, in order to move upwards, we need to decrease the y-coordinate.

That's why we set the y component of the direction vector to -1, which tells the snake to move upwards on the y-axis. Similarly we detect if the user has pressed any of the other keys on the keyboard and we change the direction of the snake accordingly. if event.key == pygame.K_DOWN: the user has pressed the down key

snake.direction = Vector2(0, 1) if event.key == pygame.K_LEFT: snake.direction = Vector2(-1, 0)

if event.key == pygame.K_RIGHT: snake.direction = Vector2(1, 0)

Great, we can now control the movement of our snake by using the arrow keys. However, there is an issue with our current code.

If the snake is moving to the right and the player presses the left arrow key, the snake will immediately change direction and start moving to the left. To prevent this from happening, we need to add a check to ensure that the snake cannot move in the opposite direction of its current movement. For example, if the snake is currently moving to the right, we should not allow the player to press the left arrow key and change the snake's direction. In the code, we alter the if statements like this:

if event.key == pygame.K_UP and snake.direction != Vector2(0, 1):

We will only allow the snake to change its direction upwards only if it is not moving downwards. We do the same for all the other three if statements.

if event.key == pygame.K_DOWN and snake.direction != Vector2(0, -1):

if event.key == pygame.K_LEFT and snake.direction != Vector2(1, 0):

if event.key == pygame.K_RIGHT and snake.direction != Vector2(-1, 0):

Great work! We have successfully implemented the ability for the player to control the movement of the snake. Now, if we run the game again, we can see that the snake moves smoothly and responds to the player's input as desired. This is a significant milestone in the development of our game.

Refactor into a Game Class (39:32)

Keep up the good work, as we are making steady progress and the remaining tasks should be relatively straightforward to implement. Before moving on to the next step, let's take a moment to organize our code for better management and maintainability. To improve code organization and make it easier to manage in the future, we will create a

Game class to hold the snake and food objects, as well as various methods.

The Game class will serve as a container for all the elements of our game such as the snake, food, and game state. It will also hold methods that manage the game's logic such as updating the snake's position, checking for collisions, etc. By centralizing all the game's functionality within this class, it will be easier to understand, maintain, and expand upon in the future.

Additionally, it will also improve code readability and reduce the risk of bugs by keeping related code together in one place. Overall, creating a Game class will greatly improve the organization and structure of our codebase, making it easier to work with in the future. So, let’s create a new Game class.

class Game: This class will create and hold the Snake object and the Food object.

So in the init method let’s create a Snake and a food object.

Def init(self): self.snake = Snake()

self.food = Food() The Game class will of course have a draw method in which it will draw all the game

objects by calling the draw method of each of the two objects it holds.

So we type: Def draw(self): self.food.draw() self.snake.draw()

We also need an update method that is going to update the positions of the game objects and it will check for collisions. So we type: def update(self):

So far we only move the snake so we only type self.snake.update()

The first version of our Game class is ready. Let’s create a game object and call its methods.

We first delete the food and snake objects here, we don’t need them any more. The game class will create its own food and snake objects.

We now create a game class object. game = GAME() Now inside the game loop we have to make some modifications to the code.

Here, instead of snake.update we need to call the update method of our game class so we type game.update() Also, here, where we check for user input, we need to access the snake object of the game class so we type game. Here and here.

We do the same for all the other if statements, it will take me some time.

Now the last thing we have to do is to remove these two lines of code where we draw the game objects and just call the draw method of the game class.

game.draw()

That’s it. If we now run the game once more, we can see it is working like before, but our code is more readable and easier to work with as you are going to find out later.

Let's move on to the next step of the game development, which is to make the snake eat the food.