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)
to
snake = [pygame.Vector2(W / 2, H / 2)]
Just above the while loop, introduce the new variable:
grow = 3
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
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
And under that, perform the append step:
snake.append(new_head)
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)
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)
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)
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()
Continues in Part 5
Top comments (0)