From: Skullheadx <94652084+Skullheadx@users.noreply.github.com> Date: Thu, 22 Dec 2022 04:36:13 +0000 (-0500) Subject: Snake Game X-Git-Url: http://git.skullheadx.com/nixos/static/phil/index.html?a=commitdiff_plain;h=06df36bb4810eadcb2999cccb05a1535fce6661d;p=Snake.git Snake Game --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9320e08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +.idea/ +__pycache__/ +build/ +dist/ +main.spec + +main.spec diff --git a/Snake.exe b/Snake.exe new file mode 100644 index 0000000..c3eaebd Binary files /dev/null and b/Snake.exe differ diff --git a/assets/apple.png b/assets/apple.png new file mode 100644 index 0000000..32b3c77 Binary files /dev/null and b/assets/apple.png differ diff --git a/assets/logo.ico b/assets/logo.ico new file mode 100644 index 0000000..588405c Binary files /dev/null and b/assets/logo.ico differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..e0de49a Binary files /dev/null and b/assets/logo.png differ diff --git a/assets/sounds/chill-abstract-intention.wav b/assets/sounds/chill-abstract-intention.wav new file mode 100644 index 0000000..d95d2ea Binary files /dev/null and b/assets/sounds/chill-abstract-intention.wav differ diff --git a/assets/sounds/eating-sound-effect.wav b/assets/sounds/eating-sound-effect.wav new file mode 100644 index 0000000..e83dae9 Binary files /dev/null and b/assets/sounds/eating-sound-effect.wav differ diff --git a/board.py b/board.py new file mode 100644 index 0000000..6be64f5 --- /dev/null +++ b/board.py @@ -0,0 +1,20 @@ +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)) diff --git a/button.py b/button.py new file mode 100644 index 0000000..04e9d42 --- /dev/null +++ b/button.py @@ -0,0 +1,31 @@ +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 diff --git a/container.py b/container.py new file mode 100644 index 0000000..c7015c1 --- /dev/null +++ b/container.py @@ -0,0 +1,50 @@ +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) diff --git a/food.py b/food.py new file mode 100644 index 0000000..b6a1512 --- /dev/null +++ b/food.py @@ -0,0 +1,18 @@ +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) diff --git a/food_manager.py b/food_manager.py new file mode 100644 index 0000000..b611966 --- /dev/null +++ b/food_manager.py @@ -0,0 +1,37 @@ +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) diff --git a/game.py b/game.py new file mode 100644 index 0000000..2ff8f92 --- /dev/null +++ b/game.py @@ -0,0 +1,39 @@ +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) diff --git a/game_over.py b/game_over.py new file mode 100644 index 0000000..64b36f1 --- /dev/null +++ b/game_over.py @@ -0,0 +1,47 @@ +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) diff --git a/help.py b/help.py new file mode 100644 index 0000000..8ffb2a1 --- /dev/null +++ b/help.py @@ -0,0 +1,37 @@ +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) diff --git a/label.py b/label.py new file mode 100644 index 0000000..45a8264 --- /dev/null +++ b/label.py @@ -0,0 +1,33 @@ +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)) diff --git a/main.py b/main.py new file mode 100644 index 0000000..7f31a29 --- /dev/null +++ b/main.py @@ -0,0 +1,35 @@ +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() diff --git a/menu.py b/menu.py new file mode 100644 index 0000000..20c5aa4 --- /dev/null +++ b/menu.py @@ -0,0 +1,40 @@ +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) diff --git a/player.py b/player.py new file mode 100644 index 0000000..b0881a4 --- /dev/null +++ b/player.py @@ -0,0 +1,125 @@ +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) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..da063b1 --- /dev/null +++ b/setup.py @@ -0,0 +1,48 @@ +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)