This is the first big project for me and I am not sure which is the correct way to structure the code yet. Is it okay to have so many classes and functions? What else should I improve?
import pygame
import sys
import random
pygame.init()
pygame.font.init()
clock = pygame.time.Clock()
font = pygame.font.SysFont('Monospace', 35)
# COLORS
BLACK = (0, 0, 0)
GREEN = (32, 238, 189)
YELLOW = (255, 230, 0)
RED = (250, 25, 78)
GREEN2 = (33, 239, 33)
WHITE = (255, 255, 255)
# SCREEN
TILE_SIZE = 30
GRID_SIZE = [40, 30]
FPS = 20
WIDTH = GRID_SIZE[0] * TILE_SIZE
HEIGHT = GRID_SIZE[1] * TILE_SIZE
BACKGROUND_COLOR = BLACK
screen = pygame.display.set_mode((WIDTH, HEIGHT))
score = 0
class BodyPart:
def __init__(self, x, y, direction):
self.dir_x, self.dir_y = direction
self.x, self.y = x, y
def set_direction(self, direction):
self.dir_x = direction[0]
self.dir_y = direction[1]
class Snake:
def __init__(self):
self.color = GREEN
self.body = [BodyPart(TILE_SIZE * 3, int(HEIGHT / 2), (1, 0))]
def append_body_part(self, count):
for i in range(count):
x, y, dir_x, dir_y = self.body[-1].x, self.body[-1].y, self.body[-1].dir_x, self.body[-1].dir_y
x -= dir_x * TILE_SIZE
y -= dir_y * TILE_SIZE
self.body.append(BodyPart(x, y, (dir_x, dir_y)))
def update_position(self):
for body_part in self.body:
body_part.x += body_part.dir_x * TILE_SIZE
body_part.y += body_part.dir_y * TILE_SIZE
z = len(self.body) - 1
while z != 0:
self.body[z].dir_x = self.body[z-1].dir_x
self.body[z].dir_y = self.body[z-1].dir_y
z -= 1
def collision(self):
head_x, head_y = self.body[0].x, self.body[0].y
for body_part in snake.body[1:]:
if body_part.x == head_x and body_part.y == head_y:
return True
if head_x < 0 or head_x == WIDTH or head_y < 0 or head_y == HEIGHT:
return True
return False
class Food:
def __init__(self):
self.x = None
self.y = None
self.length_bonus = None
self.color = None
self.__random_coords()
self.__choose_random_food()
def display(self):
pygame.draw.rect(screen, self.color, (self.x, self.y, TILE_SIZE, TILE_SIZE))
def eaten(self):
return (snake.body[0].x, snake.body[0].y) == (self.x, self.y)
def __random_coords(self):
matching = True
while matching:
self.x = random.randrange(TILE_SIZE, WIDTH - TILE_SIZE + 1, TILE_SIZE)
self.y = random.randrange(TILE_SIZE, HEIGHT - TILE_SIZE + 1, TILE_SIZE)
for body_part in snake.body:
if self.x != body_part.x and self.y != body_part.y:
matching = False
def __choose_random_food(self):
self.foods = [self.__banana, self.__apple, self.__watermelon]
random.choice(self.foods)()
def __banana(self):
self.length_bonus = 1
self.color = YELLOW
def __apple(self):
self.length_bonus = 2
self.color = RED
def __watermelon(self):
self.length_bonus = 3
self.color = GREEN2
def redraw_screen():
global food_on_screen, food, score
screen.fill(BACKGROUND_COLOR)
for body_part in snake.body:
pygame.draw.rect(screen, snake.color, (body_part.x, body_part.y, TILE_SIZE, TILE_SIZE))
if not food_on_screen:
food = Food()
food_on_screen = True
food.display()
if food.eaten():
snake.append_body_part(food.length_bonus)
score += food.length_bonus
food_on_screen = False
text = font.render(f"Score: {score}", False, WHITE)
screen.blit(text, (TILE_SIZE, TILE_SIZE))
pygame.display.update()
snake = Snake()
food_on_screen = False
game_over = False
while not game_over:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT and snake.body[0].dir_x != -1:
snake.body[0].set_direction([1, 0])
if event.key == pygame.K_LEFT and snake.body[0].dir_x != 1:
snake.body[0].set_direction([-1, 0])
if event.key == pygame.K_UP and snake.body[0].dir_y != 1:
snake.body[0].set_direction([0, -1])
if event.key == pygame.K_DOWN and snake.body[0].dir_y != -1:
snake.body[0].set_direction([0, 1])
snake.update_position()
if snake.collision():
game_over = True
break
redraw_screen()
print(f"Your final score: {score}")
```