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)
1 Answer 1
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
andy
don't really say much, but you're using them as the old position of the snake, so maybe rename them toorig_x
andorig_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 simplywidth
andheight
and returnfood_x
andfood_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()