DEV Community

David Newberry
David Newberry

Posted on

Pygame Snake, Pt. 4

In Parts 1, 2, & 3 we have put together everything, it would seem, besides the actual game. But now we come to the point.

The first set of changes we need to make involve the snake. Instead of a single dot variable, we'll need a snake list. We'll also introduce a variable to allow the snake to grow.

First, modify this line:

dot = pygame.Vector2(W / 2, H / 2)
Enter fullscreen mode Exit fullscreen mode

to

snake = [pygame.Vector2(W / 2, H / 2)]
Enter fullscreen mode Exit fullscreen mode

Just above the while loop, introduce the new variable:

grow = 3
Enter fullscreen mode Exit fullscreen mode

Inside the while loop, we'll make several changes.

A quick conceptual note: Previously, there was a single ("scalar") variable, dot. The animation worked by updating the x and y attributes of dot (via the code dot += vel). Now, the way the animation works will change slightly.

Each frame, the location where the head of the snake should move to in the next frame is calculated and stored in a variable called new_head. This location can be checked for conditions such as the snake running into itself, or into food, e.g.

As long as the user hasn't hit a game-ending condition, this new head position will be appended to the snake list. Then, under normal conditions, the first element is removed from the list. This (append and pop sequence) drives the animation.

For a single dot, this would produce the same effect in a somewhat convoluted way. But it makes it easy to let the snake grow -- simple skip the pop step.

Replace dot += vel with the following:

    new_head = snake[-1] + vel
Enter fullscreen mode Exit fullscreen mode

In Python, negative indexes start from the end of the list, with -1 being the last item, -2 second to last, and so on. It's particularly quick and handy way to reference the last item in the list without having to resort to calling len().

To check whether the snake is going to run into itself, under the above line you can add the following if statement:

    if new_head in snake:
        print("hit self")
        break
Enter fullscreen mode Exit fullscreen mode

And under that, perform the append step:

    snake.append(new_head)
Enter fullscreen mode Exit fullscreen mode

Next comes the pop step, which depends on the value of grow. It's initially set to 3 in order to easily start the snake length at 4 (e.g.), without having to specify the location of each snake segment in advance.

Under the above code, add the following:

    if grow > 0:
        grow -= 1
    else:
        snake.pop(0)
Enter fullscreen mode Exit fullscreen mode

Finally, and crucially, the code needs to be updated to draw the whole snake rather than a single dot. However, it will still drawn piece-by-piece, which can be exploited in updating the code.

Find the two lines of code that draw the dot.

    square = pygame.Rect(dot * S, (S, S))
    screen.fill("black", square)
Enter fullscreen mode Exit fullscreen mode

Add this for loop above those two lines, and then indent them. The result should look like this:

    for dot in snake:
        square = pygame.Rect(dot * S, (S, S))
        screen.fill("black", square)
Enter fullscreen mode Exit fullscreen mode

Try running your code. Your should immediately grow to a length of 4, and move around properly.

Full code at this point:

import pygame

W = 30
H = 30
S = 20

# pygame setup
pygame.init()
screen = pygame.display.set_mode((W * S, H * S))
clock = pygame.time.Clock()
running = True

# game setup
snake = [pygame.Vector2(W / 2, H / 2)]
vel = pygame.Vector2(1, 0)

key_vel = {
    pygame.K_RIGHT: pygame.Vector2(1, 0),
    pygame.K_DOWN: pygame.Vector2(0, 1),
    pygame.K_LEFT: pygame.Vector2(-1, 0),
    pygame.K_UP: pygame.Vector2(0, -1)
}

grow = 3

def place_food():
    global food_pos
    food_pos = pygame.Vector2(
        random.randrange(W),
        random.randrange(H)
    )
    while food_pos in snake:
        food_pos = pygame.Vector2(
            random.randrange(W),
            random.randrange(H)
        )

place_food()

while running:
    # poll for events
    for event in pygame.event.get():
        # pygame.QUIT = user closed window
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN and event.key in key_vel:
            vel = key_vel[event.key]

    # fill buffer with white
    screen.fill("white")

    new_head = snake[-1] + vel

    if new_head in snake:
        print("hit self")
        break

    if new_head == food_pos:
        grow += 1
        place_food()

    snake.append(new_head)

    if grow > 0:
        grow -= 1
    else:
        snake.pop(0)

    for dot in snake:
        square = pygame.Rect(dot * S, (S, S))
        screen.fill("black", square)

    square = pygame.Rect(food_pos * S, (S, S))
    screen.fill("green", square)

    # copy buffer to screen
    pygame.display.flip()

    # limits FPS
    clock.tick(20)

pygame.quit()
Enter fullscreen mode Exit fullscreen mode

Continues in Part 5

Top comments (0)