--- /dev/null
+# Default ignored files
+/shelf/
+/workspace.xml
+.idea/
+__pycache__/
+build/
+dist/
+main.spec
+
+main.spec
--- /dev/null
+from setup import *
+
+
+class Board:
+ colour = {0: LIGHT_GREEN,
+ 1: DARK_GREEN}
+
+ board = pygame.Surface(dimensions)
+ board.fill(DARK_GREEN)
+
+ for i in range(LENGTH):
+ for j in range(HEIGHT):
+ pygame.draw.rect(board, colour[(i + j) % 2],
+ pygame.Rect(i * side_length, j * side_length, side_length, side_length), border_radius=2)
+
+ def __init__(self):
+ pass
+
+ def draw(self, surf):
+ surf.blit(self.board, (0, 0))
--- /dev/null
+from label import Label
+from setup import *
+
+
+class Button(Label):
+
+ def __init__(self, position, text, colour, background_colour, font_size, command):
+ super().__init__(position, text, colour, background_colour, font_size)
+ self.lightened_background = self.background_colour
+ self.darkened_background = (
+ max(background_colour[0] - 25, 0), max(background_colour[1] - 25, 0), max(background_colour[2] - 25, 0))
+ self.command = command
+ self.activated = False
+
+ def update(self, delta, raw_text=None, position=None):
+ super().update(delta, raw_text=raw_text, position=position)
+
+ mx, my = pygame.mouse.get_pos()
+ if self.get_rect().collidepoint(mx, my):
+ self.background_colour = self.darkened_background
+
+ if self.activated:
+ return self.command
+
+ else:
+ self.background_colour = self.lightened_background
+ return None
+
+ def get_input(self, event):
+ if event.type == pygame.MOUSEBUTTONUP:
+ self.activated = True
--- /dev/null
+from button import Button
+from setup import *
+
+
+class Container:
+ inset = 20
+
+ def __init__(self, position, buttons, separation_distance=10):
+ self.position = pygame.Vector2(position)
+ self.buttons = buttons
+ self.separation_distance = separation_distance
+ self.length, self.height = 0, 0
+ self.spread()
+
+ def spread(self):
+ previous_position = self.position.y - self.buttons[0].get_rect().height / 2 + self.separation_distance / 2
+ previous_height = 0
+ self.length = 0
+ self.height = self.buttons[0].get_rect().height / 2
+ for button in self.buttons:
+ button.position.y = previous_position + previous_height + self.separation_distance
+ previous_position = button.position.y
+ previous_height = button.get_rect().height
+ self.height += button.get_rect().height + self.separation_distance
+ self.length = max(self.length, button.get_rect().width)
+
+ def update(self, delta, position=None):
+ if position is not None:
+ self.position = pygame.Vector2(position)
+ self.spread()
+
+ for button in self.buttons:
+ status = button.update(delta)
+ if status is not None:
+ return status
+ return
+
+ def get_input(self, event):
+ for button in self.buttons:
+ if isinstance(button, Button):
+ button.get_input(event)
+
+ def get_rect(self):
+ return pygame.Rect(self.position.x - self.inset - self.length / 2,
+ self.position.y - self.inset - self.buttons[0].get_rect().height,
+ self.length + 2 * self.inset, self.height + 2 * self.inset)
+
+ def draw(self, surf):
+ for button in self.buttons:
+ button.draw(surf)
--- /dev/null
+from setup import *
+
+
+class Food:
+ radius = side_length * 0.9 / 2
+ image = pygame.transform.scale(pygame.image.load("assets/apple.png"), (side_length, side_length))
+
+ def __init__(self, position):
+ self.position = pygame.Vector2(position)
+
+ def update(self, delta):
+ pass
+
+ def draw(self, surf):
+ pygame.draw.circle(surf, RED, self.position + pygame.Vector2(side_length / 2, side_length / 2), self.radius)
+ pygame.draw.circle(surf, BLACK, self.position + pygame.Vector2(side_length / 2, side_length / 2),
+ self.radius, width=3)
+ # surf.blit(self.image,self.position)
--- /dev/null
+import random
+
+from food import Food
+from setup import *
+
+
+class FoodManager:
+
+ def __init__(self):
+ # self.food_list = [Food(center + pygame.Vector2(2 * side_length, 0))]
+ self.food_list = [Food(center + pygame.Vector2(1 * side_length, 0))]
+ self.taken = set()
+ self.poll = [(i * side_length, j * side_length) for i in range(LENGTH) for j in range(HEIGHT)]
+
+ def is_taken(self, pos, poll):
+ poll.remove(pos)
+ if pos in self.taken:
+ return True
+ return False
+
+ def add_food(self):
+ poll = self.poll[:]
+ while len(poll) > 0:
+ pos = random.choice(poll)
+
+ if not self.is_taken(pos, poll):
+ self.food_list.append(Food(pos))
+ return
+
+ def update(self, body):
+ self.taken = set()
+ for i in body:
+ self.taken.add(tuple(i.position))
+
+ def draw(self, surf):
+ for food in self.food_list:
+ food.draw(surf)
--- /dev/null
+from board import Board
+from food_manager import FoodManager
+from label import Label
+from player import Player
+from setup import *
+
+
+class Game:
+
+ def __init__(self):
+ self.board = Board()
+ self.food_manager = FoodManager()
+ self.snake = Player(center - 2 * x_unit)
+ self.fast_forward = False
+ self.ff_label = Label((0, 0), ">>", WHITE, None, 50)
+
+ def update(self, delta):
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ return COMMAND_EXIT
+ elif event.type == pygame.KEYDOWN:
+ self.snake.get_input(event)
+ if event.key == pygame.K_SPACE:
+ self.fast_forward = not self.fast_forward
+ self.ff_label.update(delta)
+ self.food_manager.update(self.snake.body)
+ status = self.snake.update(delta * (3 ** self.fast_forward), self.food_manager)
+ if len(self.snake.body) == LENGTH * HEIGHT:
+ return COMMAND_WIN
+ if status == COMMAND_LOSE:
+ return status
+ return
+
+ def draw(self, surf):
+ self.board.draw(surf)
+ self.snake.draw(surf)
+ self.food_manager.draw(surf)
+ if self.fast_forward:
+ self.ff_label.draw(surf, centered=False)
--- /dev/null
+from button import Button
+from container import Container
+from label import Label
+from setup import *
+
+
+class GameOver:
+ def __init__(self, score, win=False):
+ self.target = pygame.Vector2(center.x, dimensions.y * 1 / 3)
+ self.position = pygame.Vector2(center.x, dimensions.y * 1.25)
+ self.card = pygame.Surface((dimensions.x * 2 / 3, dimensions.y * 2 / 3))
+ self.labels = [Label((center.x, dimensions.y / 3), "GAMEOVER", BLACK, None, 50),
+ Label(center, f"----( SCORE: {score} )----", BLACK, None, 30),
+ Button(center, " PLAY AGAIN ", BLACK, GRAY, 25, COMMAND_START),
+ Button(center, " HELP ", BLACK, GRAY, 25, COMMAND_HELP),
+ Button(center, " MAIN MENU ", BLACK, GRAY, 25, COMMAND_MENU)
+ ]
+ if win:
+ self.labels[0].update(0, raw_text="WIN")
+
+ self.container = Container(self.position, self.labels, separation_distance=7)
+ self.time = 0
+ self.background_surface = None
+
+ def update(self, delta):
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ return COMMAND_EXIT
+ self.container.get_input(event)
+ self.position.y = max(self.target.y, self.position.y - pow(100, self.time / 1000))
+ if self.position.y > center.y:
+ self.time += delta
+ return self.container.update(delta, position=self.position)
+
+ def draw(self, surf):
+ if self.background_surface is None:
+ self.background_surface = pygame.Surface(dimensions)
+ self.background_surface.blit(surf, (0, 0))
+ surf.blit(self.background_surface, (0, 0))
+
+ pygame.draw.rect(surf, YELLOW,
+ self.container.get_rect(),
+ border_radius=10)
+ pygame.draw.rect(surf, BLACK,
+ self.container.get_rect(),
+ width=5, border_radius=10)
+ self.container.draw(surf)
--- /dev/null
+from button import Button
+from container import Container
+from label import Label
+from setup import *
+
+
+class Help:
+ inset = 0.1
+
+ def __init__(self):
+ self.labels = [Label((center.x, dimensions.y / 3), "HELP", BLACK, BLUE, 70),
+ Label(center, "WASD or ARROW Keys to move.", DARK_GRAY, None, 25),
+ Label(center, "Don't run into yourself.", DARK_GRAY, None, 25),
+ Label(center, "Eat apples to grow larger.", DARK_GRAY, None, 25),
+ Label(center, "Survive as long as possible.", DARK_GRAY, None, 25),
+ Button(center, "Return to Main Menu", DARK_GRAY, GRAY, 25, COMMAND_MENU)
+ ]
+ self.container = Container((center.x, dimensions.y * 2 / 5), self.labels, separation_distance=7)
+
+ def update(self, delta):
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ return COMMAND_EXIT
+ self.container.get_input(event)
+
+ return self.container.update(delta)
+
+ def draw(self, surf):
+ surf.fill(DARK_GREEN)
+ pygame.draw.rect(surf, LIGHT_GREEN, pygame.Rect(self.inset * dimensions.x, self.inset * dimensions.y,
+ dimensions.x * (1 - 2 * self.inset),
+ dimensions.y * (1 - 2 * self.inset)), border_radius=15)
+ pygame.draw.rect(surf, LIGHT_GREEN, pygame.Rect(self.inset * dimensions.x, self.inset * dimensions.y,
+ dimensions.x * (1 - 2 * self.inset),
+ dimensions.y * (1 - 2 * self.inset)), width=20,
+ border_radius=15)
+ self.container.draw(surf)
--- /dev/null
+from setup import *
+
+
+class Label:
+ inset = 10
+
+ def __init__(self, position, text, colour, background_colour, font_size):
+ self.position = pygame.Vector2(position)
+ self.raw_text = text
+ self.colour = colour
+ self.background_colour = background_colour
+ self.font = pygame.font.SysFont("arial", font_size)
+ self.text = self.font.render(self.raw_text, True, self.colour)
+ self.length, self.height = self.text.get_size()
+
+ def update(self, delta, raw_text=None, position=None):
+ if raw_text is not None:
+ self.raw_text = raw_text
+ self.text = self.font.render(self.raw_text, True, self.colour)
+ self.length, self.height = self.text.get_size()
+ if position is not None:
+ self.position = pygame.Vector2(position)
+
+ def get_rect(self, centered=True):
+ return pygame.Rect(self.position.x - self.inset - centered * self.length / 2,
+ self.position.y - self.inset - centered * self.height / 2,
+ self.length + 2 * self.inset, self.height + 2 * self.inset)
+
+ def draw(self, surf, centered=True):
+ if self.background_colour is not None:
+ pygame.draw.rect(surf, self.background_colour, self.get_rect(centered), border_radius=5)
+ pygame.draw.rect(surf, BLACK, self.get_rect(centered), width=3, border_radius=5)
+ surf.blit(self.text, self.text.get_rect(center=self.get_rect(centered).center))
--- /dev/null
+from game import Game
+from game_over import GameOver
+from help import Help
+from menu import Menu
+from setup import *
+
+is_running = True
+delta = 0
+clock = pygame.time.Clock()
+screen = pygame.display.set_mode(dimensions)
+scene = Menu()
+
+while is_running:
+
+ status = scene.update(delta)
+ if status == COMMAND_EXIT:
+ is_running = False
+ elif status == COMMAND_LOSE:
+ scene = GameOver(len(scene.snake.body), win=False)
+ elif status == COMMAND_WIN:
+ scene.draw(screen)
+ scene = GameOver(len(scene.snake.body), win=True)
+ elif status == COMMAND_START:
+ scene = Game()
+ elif status == COMMAND_HELP:
+ scene = Help()
+ elif status == COMMAND_MENU:
+ scene = Menu()
+
+ scene.draw(screen)
+
+ pygame.display.flip()
+ delta = clock.tick(60)
+
+pygame.quit()
--- /dev/null
+from button import Button
+from container import Container
+from label import Label
+from setup import *
+
+
+class Menu:
+ inset = 0.1
+
+ def __init__(self):
+ self.labels = [Label((center.x, dimensions.y / 3), "SNAKE", BLACK, BLUE, 100),
+ Button(center, " PLAY ", DARK_GRAY, GRAY, 30, COMMAND_START),
+ Button(center, " HELP ", DARK_GRAY, GRAY, 30, COMMAND_HELP),
+ Button(center, " QUIT ", DARK_GRAY, GRAY, 30, COMMAND_EXIT)
+ ]
+ self.container = Container((center.x, dimensions.y * 2 / 5), self.labels)
+
+ def update(self, delta):
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ return COMMAND_EXIT
+ self.container.get_input(event)
+
+ return self.container.update(delta)
+
+ def draw(self, surf):
+ surf.fill(DARK_GREEN)
+ pygame.draw.rect(surf, LIGHT_GREEN,
+ pygame.Rect(self.inset * dimensions.x,
+ self.inset * dimensions.y,
+ dimensions.x * (1 - 2 * self.inset),
+ dimensions.y * (1 - 2 * self.inset)),
+ border_radius=15)
+ pygame.draw.rect(surf, LIGHT_GREEN,
+ pygame.Rect(self.inset * dimensions.x,
+ self.inset * dimensions.y,
+ dimensions.x * (1 - 2 * self.inset),
+ dimensions.y * (1 - 2 * self.inset)),
+ width=20, border_radius=15)
+ self.container.draw(surf)
--- /dev/null
+from setup import *
+
+
+class Player:
+ move_time = 400 # ms
+ min_time = 100
+ time_decrease = 4
+
+ def __init__(self, position):
+ self.position = pygame.Vector2(position)
+ self.time = 0
+ self.body = [Body(self.position), Body(self.position - x_unit), Body(self.position - 2 * x_unit)]
+ # self.body = [Body(self.position - i * x_unit) for i in range(64)]
+ self.direction = None
+ self.head = self.body[0]
+ self.neck = self.body[1]
+
+ def get_input(self, event):
+ if event.key == pygame.K_w or event.key == pygame.K_UP:
+ if not self.neck.collide_point(mod(self.position - y_unit)):
+ self.direction = "up"
+ if event.key == pygame.K_s or event.key == pygame.K_DOWN:
+ if not self.neck.collide_point(mod(self.position + y_unit)):
+ self.direction = "down"
+ if event.key == pygame.K_a or event.key == pygame.K_LEFT:
+ if not self.neck.collide_point(mod(self.position - x_unit)):
+ self.direction = "left"
+ if event.key == pygame.K_d or event.key == pygame.K_RIGHT:
+ if not self.neck.collide_point(mod(self.position + x_unit)):
+ self.direction = "right"
+
+ def update(self, delta, food_manager):
+ if self.direction is None:
+ return
+ if self.time >= self.move_time:
+ self.time -= self.move_time
+ moved = True
+ match self.direction:
+ case "left":
+ self.position = mod(self.position - x_unit)
+ case "right":
+ self.position = mod(self.position + x_unit)
+ case "up":
+ self.position = mod(self.position - y_unit)
+ case "down":
+ self.position = mod(self.position + y_unit)
+ case _:
+ moved = False
+ if moved:
+ eaten = self.eaten_food(food_manager.food_list)
+ if eaten is not None:
+ self.eat(food_manager, eaten)
+ else:
+ self.move()
+ for part in self.body:
+ if part == self.head:
+ continue
+ if self.head.is_colliding(part):
+ return COMMAND_LOSE
+ self.time += delta
+
+ def eaten_food(self, food_list):
+ for food in food_list:
+ if food.position == self.position:
+ return food
+ return None
+
+ def update_head(self):
+ self.head = self.body[0]
+ self.neck = self.body[1]
+
+ def eat(self, food_manager, food):
+ self.body.insert(0, Body(self.position))
+ food_manager.update(self.body)
+ food_manager.food_list.remove(food)
+ food_manager.add_food()
+ self.update_head()
+ self.move_time = max(self.min_time, self.move_time - self.time_decrease)
+ sounds["eat"].play()
+
+ def move(self):
+ del self.body[-1]
+
+ self.position = mod(self.position)
+
+ self.body.insert(0, Body(self.position))
+ self.update_head()
+
+ def draw(self, surf):
+ for part in self.body:
+ if part == self.head:
+ part.draw_head(surf)
+ else:
+ part.draw(surf)
+
+
+class Body:
+ scale = 1
+ border_radius = 2
+
+ def __init__(self, position):
+ self.position = position.copy()
+ self.length, self.width = side_length * self.scale, side_length * self.scale
+
+ def get_rect(self):
+ return pygame.Rect(self.position.x + (1 - self.scale) * side_length / 2,
+ self.position.y + (1 - self.scale) * side_length / 2, self.length, self.width)
+
+ def is_colliding(self, b):
+ if self.get_rect().colliderect(b.get_rect()):
+ return True
+ return False
+
+ def collide_point(self, pos):
+ if self.position == pos:
+ return True
+ return False
+
+ def draw(self, surf):
+ pygame.draw.rect(surf, LIGHT_BLUE, self.get_rect(), border_radius=self.border_radius)
+ pygame.draw.rect(surf, BLACK, self.get_rect(), width=3, border_radius=self.border_radius)
+
+ def draw_head(self, surf):
+ pygame.draw.rect(surf, DARK_BLUE, self.get_rect(), border_radius=self.border_radius)
+ pygame.draw.rect(surf, BLACK, self.get_rect(), width=3, border_radius=self.border_radius)
--- /dev/null
+import pygame
+
+pygame.init()
+
+LENGTH, HEIGHT = 20,20
+side_length = 640 / LENGTH
+x_unit = pygame.Vector2(side_length, 0)
+y_unit = pygame.Vector2(0, side_length)
+dimensions = pygame.Vector2(side_length * LENGTH, side_length * HEIGHT)
+center = pygame.Vector2(dimensions.x / 2, dimensions.y / 2)
+
+pygame.display.set_caption("Snake Game")
+pygame.display.set_icon(pygame.image.load("assets/logo.png"))
+
+COMMAND_START = 0
+COMMAND_EXIT = 1
+COMMAND_LOSE = 2
+COMMAND_WIN = 3
+COMMAND_HELP = 4
+COMMAND_MENU = 5
+
+WHITE = (255, 255, 255)
+GRAY = (128, 128, 128)
+DARK_GRAY = (58, 58, 58)
+BLACK = (0, 0, 0)
+RED = (255, 0, 0)
+GREEN = (0, 255, 0)
+LIGHT_GREEN = (29, 170, 52)
+DARK_GREEN = (25, 147, 45)
+BLUE = (18, 82, 170)
+LIGHT_BLUE = (10, 37, 211)
+DARK_BLUE = (8, 25, 137)
+YELLOW = (221, 219, 84)
+
+
+def mod(pos):
+ return pygame.Vector2(pos.x % dimensions.x, pos.y % dimensions.y)
+
+
+sounds = {
+ "eat": pygame.mixer.Sound("assets/sounds/eating-sound-effect.wav")
+}
+
+sounds["eat"].set_volume(0.1)
+
+pygame.mixer.music.load("assets/sounds/chill-abstract-intention.wav")
+pygame.mixer.music.set_volume(0.25)
+pygame.mixer.music.play(-1)