]> Skullheadx's Git Forge - Snake.git/commitdiff
Snake Game
authorSkullheadx <94652084+Skullheadx@users.noreply.github.com>
Thu, 22 Dec 2022 04:36:13 +0000 (23:36 -0500)
committerSkullheadx <94652084+Skullheadx@users.noreply.github.com>
Thu, 22 Dec 2022 04:36:13 +0000 (23:36 -0500)
20 files changed:
.gitignore [new file with mode: 0644]
Snake.exe [new file with mode: 0644]
assets/apple.png [new file with mode: 0644]
assets/logo.ico [new file with mode: 0644]
assets/logo.png [new file with mode: 0644]
assets/sounds/chill-abstract-intention.wav [new file with mode: 0644]
assets/sounds/eating-sound-effect.wav [new file with mode: 0644]
board.py [new file with mode: 0644]
button.py [new file with mode: 0644]
container.py [new file with mode: 0644]
food.py [new file with mode: 0644]
food_manager.py [new file with mode: 0644]
game.py [new file with mode: 0644]
game_over.py [new file with mode: 0644]
help.py [new file with mode: 0644]
label.py [new file with mode: 0644]
main.py [new file with mode: 0644]
menu.py [new file with mode: 0644]
player.py [new file with mode: 0644]
setup.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..9320e08
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
index 0000000..c7015c1
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..b611966
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..64b36f1
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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)