4
\$\begingroup\$

This is my first game, and looking for some help to improve current code because I've identified a lot that I think could be written more efficiently, particularly the segment that checks which key has been pressed but I'm not sure how to improve it.

import pygame
from pygame.locals import *
import random
import sys


pygame.init()

FPS = 30
fpsClock = pygame.time.Clock()

WIN_WIDTH = 680 #width of window
WIN_HEIGHT = 500 #height of window

DISPLAY = (WIN_WIDTH, WIN_HEIGHT) #variable for screen display
DEPTH = 32 #standard
FLAGS = 0 #standard
BLACK = (0, 0, 0) #black
RED = (255, 0, 0) #red
GOLD = (255, 215, 0)
LOL = (14, 18, 194)
YOLO = (155, 98, 245)
WHITE = (255, 255, 255)

screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption('Snaek')                                      

collision_coords = [1]
snake_parts = [1]
Score = 0
speed = 12
snakex = 125
snakey = 70
size = 20


# --- classes ---
class Snake(pygame.Rect):
    def __init__(self, x, y, screen, size, colour):
        pygame.Rect.__init__(self, x, y, size, 20)
        self.screen = screen
        self.colour = colour
        self.x = x
        self.y = y


    def draw(self, screen):
        pygame.draw.rect(self.screen, self.colour, self)

    def coordinates(self):
        return self.x, self.y

class Food(pygame.Rect):
    def __init__(self, x, y, screen):
        pygame.Rect.__init__(self, x, y, 20, 20)
        self.screen = screen

    def draw(self, screen):
        pygame.draw.rect(self.screen, GOLD, self)

class Barrier(pygame.Rect):

    def __init__(self, x, y, screen):
        pygame.Rect.__init__(self, x, y, 40, 20)
        self.screen = screen 
    def draw(self, screen):
        pygame.draw.rect(self.screen, LOL, self)

class GameMenu():
    def __init__(self, screen, options):
        self.screen = screen
        self.options = options



# --- functions ---
def get_food_pos(WIN_WIDTH, WIN_HEIGHT):
    WIN_WIDTH = random.randint(100, WIN_WIDTH-150)
    WIN_HEIGHT = random.randint(100, WIN_HEIGHT-150)
    return WIN_WIDTH, WIN_HEIGHT

def texts(score):
   font=pygame.font.Font(None,30)
   scoretext=font.render("Score:"+' ' + str(score), 1,(255,255,255))
   screen.blit(scoretext, (500, 15))








eaten = True
pressed_right = True
pressed_left = False
pressed_up = False
pressed_down = False
pygame.key.set_repeat(10,10)

level = [
        "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP", 
        "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",                                          
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",
]

def display_menu():
    while True:
        screen.fill(BLACK)
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONUP:
                pos = pygame.mouse.get_pos()
                if 385 > pos[0] > 275:
                    if 202 > pos[1] > 185:
                        return
                    elif 293 > pos[1] > 275:
                        pygame.quit()
                        sys.exit()    
                    else:
                        pass



        font = pygame.font.Font(None, 30)
        play_game = font.render("Play Game", 1, WHITE)
        quit_game = font.render("Quit Game", 1, WHITE)

        screen.blit(play_game, (275, 185))
        screen.blit(quit_game, (275, 275))
        pygame.display.update()
        fpsClock.tick(FPS)    


display_menu()

while True:
    screen.fill(BLACK)

    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()

        elif event.type == pygame.KEYDOWN:          # check for key presses          
            if event.key == pygame.K_LEFT:
                if pressed_right:
                    pressed_right = True# left arrow turns left
                else:
                    pressed_left = True
                    pressed_right = False
                    pressed_up = False
                    pressed_down = False
            elif event.key == pygame.K_RIGHT:
                if pressed_left:
                    pressed_left = True# right arrow turns right
                else: 
                    pressed_right = True
                    pressed_left = False
                    pressed_up = False
                    pressed_down = False
            elif event.key == pygame.K_UP:
                if pressed_down:# up arrow goes up
                    pressed_down = True
                else:
                    pressed_up = True
                    pressed_right = False
                    pressed_left = False
                    pressed_down = False
            elif event.key == pygame.K_DOWN:
                if pressed_up:
                    break
                else:
                    pressed_down = True
                    pressed_right = False
                    pressed_up = False
                    pressed_left = False

    x = snakex
    y = snakey
    collision_coords = [1]



    if pressed_left:
        snakex -= speed
    elif pressed_right:
        snakex += speed
    elif pressed_up:
        snakey -= speed
    elif pressed_down:
        snakey += speed

    snake_parts[0] = Snake(snakex, snakey, screen, int(size), RED)
    collision_coords[0] = snake_parts[0].coordinates()
    snake_parts[0].draw(screen)



    if eaten:
        foodx, foody = get_food_pos(WIN_WIDTH, WIN_HEIGHT)
        eaten = False

    my_food = Food(foodx, foody, screen)
    my_food.draw(screen)

    if snake_parts[0].colliderect(my_food):
        eaten = True
        screen.fill(BLACK)
        a_snake = Snake(snakex, snakey, screen, int(size), RED)
        snake_parts.append(a_snake)
        Score += 1


    for i in range(1, len(snake_parts)):
        tempx, tempy = snake_parts[i].coordinates()
        snake_parts[i] = Snake(x, y, screen, int(size), RED)
        collision_coords.append(snake_parts[i].coordinates())
        snake_parts[i].draw(screen)
        x, y = tempx, tempy





    platform_x = 0
    platform_y = 0

    for row in level:
        for col in row:
            if col == "P":
                col = Barrier(platform_x, platform_y, screen)
                col.draw(screen)
                if snake_parts[0].colliderect(col):
                    pygame.quit()
                    sys.exit()

            platform_x += 15
        platform_y += 20
        platform_x = 0

    for i in range(2, len(collision_coords)):
        if int(collision_coords[0][1]) == int(collision_coords[i][1]) and int(collision_coords[0][0]) == int(collision_coords[i][0]):
            pygame.quit()
            sys.exit()    

    texts(Score)



    pygame.display.update()
    fpsClock.tick(FPS) 
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

There is quite a lot of code, so I'll point out the first few things that I notice.

  • Naming: you have some constants all upper case which is good, but you also have constants that are lower case and one (Score) which is neither. I'd say stick to all upper case for constants.
  • A small typo (Snaek)
  • x and y don't really say much, but you're using them as the old position of the snake, so maybe rename them to orig_x and orig_y
  • pygame.key.set_repeat(10, 10) is useless here, you can remove it.
  • get_food_pos gets as arguments the width and height of the screen, but no need to name them the same way, which is actually quite misleading. Name them simply width and height and return food_x and food_y, not the same variables.

Now for the implementation.

  • Yes, the key press handler can be written with less code. You can have an array of key presses and use the values to determine where you're supposed to go.

You can initialize it like this:

(LEFT, RIGHT, UP, DOWN) = (0, 1, 2, 3)
pressed = [0, 1, 0, 0]

And in the main loop use it like this:

for event in pygame.event.get():
    if event.type == QUIT:
        pygame.quit()
        sys.exit()
    elif event.type == pygame.KEYDOWN:          # check for key presses
        if event.key == pygame.K_LEFT and not pressed[RIGHT]:
            pressed = [-1, 0, 0, 0]
        elif event.key == pygame.K_RIGHT and not pressed[LEFT]:
            pressed = [0, 1, 0, 0]
        elif event.key == pygame.K_UP and not pressed[DOWN]:
            pressed = [0, 0, -1, 0]
        elif event.key == pygame.K_DOWN and not pressed[UP]:
            pressed = [0, 0, 0, 1]

snakex += speed * (pressed[LEFT] + pressed[RIGHT])
snakey += speed * (pressed[UP] + pressed[DOWN])

What happens here is that if one of the keys is pressed, the array will contain all zero values, except for the pressed key. The value there will be negative or positive depending on the direction, so you can simply sum and multiply the result by your speed.

The display menu can also be written differently.

  • First of all I'd rather have that return a value and use that to determine if the users want to quit or not. Then I'd rename it to something like get_menu_choice.
  • There's no need to repaint continuously if you're not changing anything, so your drawing code can be outside of that while loop.
  • If you want to detect collisions between anything and a rectangle there's a specific method for that. You get your mouse position and check if it's collided with a rectangle.

To sum up, something like this:

def get_menu_choice():
    screen.fill(BLACK)
    font = pygame.font.Font(None, 30)
    play_game = font.render("Play Game", 1, WHITE)
    quit_game = font.render("Quit Game", 1, WHITE)

    screen.blit(play_game, (275, 185))
    screen.blit(quit_game, (275, 275))
    pygame.display.update()
    fpsClock.tick(FPS)

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        pos = pygame.mouse.get_pos()
        (mouse_clicked, _, __) = pygame.mouse.get_pressed()
        start_game_rect = pygame.Rect(275, 185, 110, 27)
        quit_game_rect = pygame.Rect(275, 275, 110, 27)
        if mouse_clicked:
            if start_game_rect.collidepoint(pos):
                return 1
            if quit_game_rect.collidepoint(pos):
                return 0

if get_menu_choice() == 0:
    pygame.quit()
    sys.exit()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.