PyGame Snake decreases speed as window size increases

Viewed 132

I've been struggling with this problem for a couple days now. I want to eventually convert my snake game script to an exe, for my friends to play. I want the snake to move at the same speed, no matter the size of the window.

For Example: My window size right now is (400, 400). If I increase the size to (800, 800), the snake will move slower. However, the snake velocity is constant at 20 pixels. It almost seems like my main game loop is looping at a slower pace as the window size increases.

I know that the snake has more pixels to traverse with a bigger window size, but how does that affect the snake velocity at all? I'm thinking the solution lies somewhere in the speed at which I'm drawing the snake to the screen, but can't be sure.

import pygame
import sys
import random
import math
import time

pygame.display.set_caption('Snake')

pygame.font.init()

game_running = True

width = 400
height = 400

size = (width, height)

window = pygame.display.set_mode(size) # our surface type

pygame.display.set_caption("Snake Game by Nick Rinaldi")

class Food:
    def __init__(self, block_size, surface, x_loc, y_loc): # pass in color and random_x/random_y. block size is a constant 
        self.block_size = block_size
        self.surface = surface # green
        self.x_loc = x_loc
        self.y_loc = y_loc
        self.mask = pygame.mask.from_surface(self.surface)

    def draw(self, window):
        window.blit(self.surface, (self.x_loc, self.y_loc))

class Snake:

    def __init__(self, block_size, surface, x_loc, y_loc):
        self.block_size = block_size
        self.surface = surface # red
        self.x_loc = x_loc
        self.y_loc = y_loc
        self.body = []
        self.direction = None
        self.velocity = 20
        self.mask = pygame.mask.from_surface(self.surface)
 
    def draw(self, color, window, block_size):
        self.seg = []
        self.head = pygame.Rect(self.x_loc, self.y_loc, block_size, block_size)
        pygame.draw.rect(window, color, self.head)
        if len(self.body) > 0:
            for unit in self.body:
                segment = pygame.Rect(unit[0], unit[1], block_size, block_size)
                pygame.draw.rect(window, color, segment)
                self.seg.append(segment)

    def add_unit(self):
        if len(self.body) != 0:
            index = len(self.body) - 1
            x = self.body[index][0]
            y = self.body[index][1]
            self.body.append([x, y])
        else:
            self.body.append([1000, 1000])

    def move(self, step):
        for index in range(len(self.body) -1, 0, -1):
            x = self.body[index-1][0]
            y = self.body[index-1][1]
            self.body[index] = [x, y]
        if len(self.body) > 0:
            self.body[0] = [self.x_loc, self.y_loc]
        if self.direction == "right": # if specific constant, keep moving in direction
            self.x_loc += self.velocity * step
        if self.direction == "left":
            self.x_loc -= self.velocity * step
        if self.direction == "down":
            self.y_loc += self.velocity * step
        if self.direction == "up":
            self.y_loc -= self.velocity * step

    def collision(self, obj):
        return collide(food)

def gameOver(snake):

    white = pygame.Color(255, 255, 255)

    display = True
    while display:

        window.fill(white)
        score_font = pygame.font.SysFont("Courier New", 16)
        score_label = score_font.render("Your score was: " + str(len(snake.body) + 1), 1, (0, 0, 0))
        replay_label = score_font.render("To replay, click the mouse button", 1, (0, 0, 0))
        window.blit(score_label, (50, 100))
        window.blit(replay_label, (50, 130))
        pygame.display.update()

        for event in pygame.event.get(): # if we hit "x" to close out the game, close out the game.
                if event.type == pygame.QUIT:
                    pygame.quit()
                    exit()
                if event.type == pygame.MOUSEBUTTONDOWN:
                    main()


    pygame.quit()
    sys.exit()


clock = pygame.time.Clock()

def main():

    game_over = False

    x = 20 # x position
    y = 20 # y position

    block_snakes = []

    pygame.init()

    clock = pygame.time.Clock()

    red = pygame.Color(255, 0, 0)
    green = pygame.Color(0, 255, 0)
    white = pygame.Color(255, 255, 255)
    black = pygame.Color(0, 0, 0)

    block_size = 20

    randx_green = random.randrange(0, width, 20)
    randy_green = random.randrange(0, height, 20)
    randx_red = random.randrange(0, width, 20)
    randy_red = random.randrange(0, height, 20)


    red_square = pygame.Surface((block_size, block_size))
    red_square.fill(red)
    green_square = pygame.Surface((block_size, block_size))
    green_square.fill(green)

    snake = Snake(block_size, red_square, 20, 20) # create snake instance
    food = Food(block_size, green_square, randx_green, randy_green) # create food instance

    def redraw_window():

        draw_grid(window, height, width, white)

    while game_running:

        dt = clock.tick(30) # time passed between each call 

        step = dt/1000
        print(step)

        FPS = 60

        window.fill(black)

        food.draw(window)

        snake.draw(red, window, block_size)

        redraw_window()

        for event in pygame.event.get(): # if we hit "x" to close out the game, close out the game.
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()

        keys = pygame.key.get_pressed()

        if keys[pygame.K_RIGHT]: # sets direction attribute as a constant
            snake.direction = "right"
        if keys[pygame.K_LEFT]:
            snake.direction = "left"            
        if keys[pygame.K_DOWN]:
            snake.direction = "down"
        if keys[pygame.K_UP]:
            snake.direction = "up"

        snake.move(step)

        collision = collide_food(snake.x_loc, snake.y_loc, food.x_loc, food.y_loc)

        if collision:
            ac_rand_x = random.randrange(0, width, 20) # after collision, random x
            ac_rand_y = random.randrange(0, height, 20) # after collision, random y
            # check snake.direction. 

            food = Food(block_size, green_square, ac_rand_x, ac_rand_y)
            food.draw(window)

            snake.add_unit()

        wall_collide = collide_wall(snake.x_loc, snake.y_loc)

        if wall_collide:
            gameOver(snake)
            # break

        for block in snake.body:
            if snake.x_loc == block[0] and snake.y_loc == block[1]:
                gameOver(snake)

        pygame.display.update()
        # clock.tick(FPS)
    


def collide_food(snake_x, snake_y, obj_x, obj_y):
    distance = math.sqrt((math.pow(snake_x - obj_x, 2)) + (math.pow(snake_y - obj_y, 2)))
    if distance < 20:
        return True
    else:
        return False

def collide_wall(snake_x, snake_y):
    if snake_x > width:
        game_over = True
        return game_over
    if snake_y > height:
        game_over = True
        return game_over
    if snake_x < 0:
        game_over = True
        return game_over
    if snake_y < 0:
        game_over = True
        return game_over

def collide_self(snake_x, snake_y, body_x, body_y):
    if (snake_x and snake_y) == (body_x and body_y):
        return True
    else:
        return False
    

def draw_grid(window, height, width, color):

    x = 0
    y = 0

    grid_blocks = 20
    for i in range(height):
        x += 20
        pygame.draw.line(window, color, (x, 0), (x, height), 1)
        for j in range(width):
            y += 20
            pygame.draw.line(window, color, (0, y), (height, y), 1)

    # pygame.display.update()


def display_score():
    score_font = pygame.font.SysFont()

def main_menu(width, height):

    clock = pygame.time.Clock()
    FPS = 60

    width = width
    height = height

    run = True
    title_font = pygame.font.SysFont("Courier New", 16)
    title_font.set_bold(True)
    white = pygame.Color(255, 255, 255)

    while run:
        window.fill(white)
        title_label = title_font.render("Snake Game by Nick Rinaldi ", 1, (0, 0, 0))
        sponser_label = title_font.render("Sponsored by @goodproblemsnyc", 1, (0, 0, 0))
        window.blit(title_label, ((width/4, height/4)))
        window.blit(sponser_label, ((width/4, height/4 + 30)))
        pygame.display.update()
        for event in pygame.event.get(): # if we hit "x" to close out the game, close out the game.
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            if event.type == pygame.MOUSEBUTTONDOWN:
                main()

    pygame.quit()

main_menu(width, height)```
2 Answers

The bottleneck in your game is the function draw_grid, which draws far too many lines out of the window.

def draw_grid(window, height, width, color):

   x = 0
   y = 0

   grid_blocks = 20
   for i in range(height):
       x += 20
       pygame.draw.line(window, color, (x, 0), (x, height), 1)
       for j in range(width):
           y += 20
           pygame.draw.line(window, color, (0, y), (height, y), 1)

If you draw a line outside the window, the statement does not draw anything, nevertheless the nested for loops still run.
Furthermore, you don't need a nested loops. You don't want to draw 19 horizontal lines for each vertical line. You want to draw 19 vertical and 19 horizontal lines. Hence 1 for-loop is enough.

Use the step argument of range to define the list of positions for the vertical and horizontal lines

def draw_grid(window, height, width, color):

    tile_size = 20
    for p in range(tile_size, height, tile_size):
        pygame.draw.line(window, color, (p, 0), (p, height), 1)
        pygame.draw.line(window, color, (0, p), (height, p), 1)

if the size is 400 * 400 pixels, the total pixels 160000 so moving at a 20 pixel rate, because there are less pixels the an 800 * 800 board (320000 pixels) it looks like you are going faster because there are less pixels. Find an equation to calculate the correct speed on differently sized boards.

Sincerely, Zac

Related