4

I'm trying to make a snake game in pygame, but I'm having trouble making the snake longer once the snake touches the dot. The game will sometimes freeze when I touch the dot and on rare occasions it gives me a memory error.

Also, the snake doesn't get any longer when I successfully eat the dot.

Any help would be appreciated. Thank you!

My code:

import pygame
import sys
import random

pygame.init()

width = 800
height = 800
snake_pos = [width/2, height/2]
snake_list = []
color_red = (255, 0, 0)
snake_size = 20
game_over = False
screen = pygame.display.set_mode((width, height))
cookie_pos = [random.randint(0, width), random.randint(0, height)]
cookie_size = 10
color_white = (255, 255, 255)
cookie = []
direction = ''

game_over = False

def draw_snake():
    for snake in snake_list:
        pygame.draw.rect(screen, color_red, (snake[0], snake[1], snake_size, snake_size))

    if not snake_list:
        snake_list.append([snake_pos[0], snake_pos[1]])

def create_snake(direction):
    length = len(snake_list)
    for snake in snake_list:
        if direction == 'left':
            snake_list.append([snake[0] + (length * snake_size), snake[1]])
        elif direction == 'right':
            snake_list.append([snake[0] - (length * snake_size), snake[1]])
        elif direction == 'top':
            snake_list.append([snake[0], snake[1] + (length * snake_size)])
        elif direction == 'bottom':
            snake_list.append([snake[0], snake[1] - (length * snake_size)])

def create_cookie():
    cookie.append([random.randint(0, width), random.randint(0, height)])
    draw_cookie()

def draw_cookie():
    for cookie_pos in cookie:
        pygame.draw.rect(screen, color_white, (cookie_pos[0], cookie_pos[1], cookie_size, cookie_size))

def check_cookie(direction):
    for snake_pos in snake_list:
        for cookie_pos in cookie:
            p_x = snake_pos[0]
            p_y = snake_pos[1]

            e_x = cookie_pos[0]
            e_y = cookie_pos[1]

            if e_x >= p_x and e_x < (p_x + snake_size) or p_x >= e_x and p_x < (e_x + cookie_size):
                if e_y >= p_y and e_y < (p_y + snake_size) or p_y >= e_y and p_y < (e_y + cookie_size):
                    cookie.pop(0)
                    create_cookie()
                    create_snake(direction)

    if not cookie:
        cookie.append([random.randint(0, width), random.randint(0, height)])

def update_snake():
    pass

def move_snake(direction):
    keys = pygame.key.get_pressed()
    for snake_pos in snake_list:
        if keys[pygame.K_LEFT]:
            direction = 'left'
            snake_pos[0] -= 0.2
        if keys[pygame.K_RIGHT]:
            direction = 'right'
            snake_pos[0] += 0.2
        if keys[pygame.K_UP]:
            direction = 'up'
            snake_pos[1] -= 0.2
        if keys[pygame.K_DOWN]:
            direction = 'down'
            snake_pos[1] += 0.2
    screen.fill((0,0,0))
    return direction

def main_game(direction):

    while not game_over:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        draw_snake()
        check_cookie(direction)
        draw_cookie()
        pygame.display.update()
        direction = move_snake(direction)

main_game(direction)
Julk
  • 169
  • 7
  • Indeed it is. I looked over your code and understood what I was doing wrong. Thank you! – Julk Aug 14 '19 at 22:52

2 Answers2

2

If you want ot write to a global variable, then you've to use the global statement.
Use this to in the event loop to set game_over. Further note, that clearing the display should be done in the main loop:

def main_game(direction):
    global game_over
    while not game_over:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_over = True

        # update snake and cookies
        direction = move_snake(direction)
        check_cookie(direction)

        # draw the scene
        screen.fill(0)
        draw_snake()
        draw_cookie()
        pygame.display.update()

Teach part of the snake has a size of snake_size = 20. But the snake moves 0.2 per frame. So it is not possible to draw the 2nd part of the snake on the position of the previous frame, because the distance to the previous position is 0.2. That is almost the same position and would cause nearly completely self covering parts.
The proper position for the 2nd part of the snake is the position, where the head was 100 (=20/0.2) frames ago.

Track all the positions of the snake in a list:

def move_snake(direction):
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        direction = 'left'
    if keys[pygame.K_RIGHT]:
        direction = 'right'
    if keys[pygame.K_UP]:
        direction = 'up'
    if keys[pygame.K_DOWN]:
        direction = 'down'

    if snake_list:
        new_pos = snake_list[0][:]
        if direction == 'left':
            new_pos[0] -= 0.2
        if direction == 'right':
            new_pos[0] += 0.2
        if direction == 'up':
            new_pos[1] -= 0.2
        if direction == 'down':
            new_pos[1] += 0.2
        if direction != '':
            snake_list.insert(0, new_pos)
    return direction

Create a global variable, which stores the number of parts of the snake (snake_len) and init it by 1. Use pygame.Rect objects and .colliderect() to check if the snake eats a cookie and increment the number of parts:

snake_len = 1

def check_cookie(direction):
    global snake_len, cookie

    if snake_list:
        for i, cookie_pos in enumerate(cookie):
            cookie_rect = pygame.Rect(*cookie_pos, cookie_size, cookie_size)
            snake_rect = pygame.Rect(*snake_list[0], snake_size, snake_size)
            if snake_rect.colliderect(cookie_rect):
                snake_len += 1
                del cookie[i]
                break

    if not cookie:
        cookie.append([random.randint(0, width), random.randint(0, height)])

The snake consists of snake_len parts. Each part of the snake has an index. This index has to be associated to a position stored in snake_list:

pos_i = round(snake_size * i / 0.2)
pos = snake_list[pos_i]

Draw the parts on the snake on the proper positions stored in snake_list and delete the tail of the list, which is not further needed:

def draw_snake():
    global snake_list

    if not snake_list:
        snake_list.append(snake_pos[:])

    for i in range(snake_len):
        pos_i = round(snake_size * i / 0.2)
        if pos_i < len(snake_list):
            pygame.draw.rect(screen, color_red, (*snake_list[pos_i], snake_size, snake_size))
    max_len = round(snake_size * snake_len / 0.2)
    del snake_list[max_len:]

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
0

Your create_snake has two issues:

def create_snake(direction):
    length = len(snake_list)
    for snake in snake_list:
        if direction == 'left':
            new_snake = [snake[0] + (length * snake_size), snake[1]]
        elif direction == 'right':
            new_snake = [snake[0] - (length * snake_size), snake[1]]
        elif direction == 'up':
            new_snake = [snake[0], snake[1] + (length * snake_size)]
        elif direction == 'down':
            new_snake = [snake[0], snake[1] - (length * snake_size)]

    snake_list.append(new_snake)

Your direction checking was flawed: you set the direction to be left/right/up/down, but then check if it is left/right/top/bottom.

More importantly, you are appending to a list while iterating through it, which creates an infinite loop. This is fixed by creating a tmp variable, new_snake, and then appending it. (This is still probably not what you want -- the way you're trying to render the snake seems problematic, and I'd encourage you to rethink it).

Best of luck!

thedch
  • 179
  • 3