From 78ab1dea4268c8d2ef8734e471d42e584f2bfd57 Mon Sep 17 00:00:00 2001
From: DannyAbdi <dannyabdi13@gmail.com>
Date: Thu, 4 Apr 2024 06:14:17 +0100
Subject: [PATCH] Organised classes and images into folders

---
 Classes/aStar.py                | 100 ++++++++++++++
 Classes/bfs.py                  |  90 +++++++++++++
 Classes/button.py               |  63 +++++++++
 Classes/config.py               |  75 +++++++++++
 Classes/dfs.py                  |  70 ++++++++++
 Classes/dijkstra.py             |  78 +++++++++++
 Classes/enemy.py                |  50 +++++++
 Classes/expectimax.py           |  43 ++++++
 Classes/game.py                 |  50 +++++++
 Classes/main.py                 |  65 +++++++++
 Classes/maze.py                 |  64 +++++++++
 Classes/minmax.py               |  93 +++++++++++++
 Classes/player.py               |  22 ++++
 Classes/playerController.py     | 223 +++++++++++++++++++++++++++++++
 Images/blank.png                | Bin 0 -> 239 bytes
 Images/button_easy.png          | Bin 0 -> 2072 bytes
 Images/button_hard.png          | Bin 0 -> 1757 bytes
 Images/button_normal.png        | Bin 0 -> 2395 bytes
 Images/enemy.png                | Bin 0 -> 232 bytes
 Images/goal.png                 | Bin 0 -> 238 bytes
 Images/wall.png                 | Bin 0 -> 144 bytes
 Testing/testAStar.py            |  68 ++++++++++
 Testing/testBFS.py              | 109 +++++++++++++++
 Testing/testButton.py           |  56 ++++++++
 Testing/testDFS.py              |  80 +++++++++++
 Testing/testDijkstra.py         |  79 +++++++++++
 Testing/testEnemy.py            |  46 +++++++
 Testing/testExpectimax.py       |  69 ++++++++++
 Testing/testGame.py             |  60 +++++++++
 Testing/testMaze.py             |  92 +++++++++++++
 Testing/testMinMax.py           | 112 ++++++++++++++++
 Testing/testPlayer.py           |  18 +++
 Testing/testPlayerController.py | 226 ++++++++++++++++++++++++++++++++
 33 files changed, 2101 insertions(+)
 create mode 100644 Classes/aStar.py
 create mode 100644 Classes/bfs.py
 create mode 100644 Classes/button.py
 create mode 100644 Classes/config.py
 create mode 100644 Classes/dfs.py
 create mode 100644 Classes/dijkstra.py
 create mode 100644 Classes/enemy.py
 create mode 100644 Classes/expectimax.py
 create mode 100644 Classes/game.py
 create mode 100644 Classes/main.py
 create mode 100644 Classes/maze.py
 create mode 100644 Classes/minmax.py
 create mode 100644 Classes/player.py
 create mode 100644 Classes/playerController.py
 create mode 100644 Images/blank.png
 create mode 100644 Images/button_easy.png
 create mode 100644 Images/button_hard.png
 create mode 100644 Images/button_normal.png
 create mode 100644 Images/enemy.png
 create mode 100644 Images/goal.png
 create mode 100644 Images/wall.png
 create mode 100644 Testing/testAStar.py
 create mode 100644 Testing/testBFS.py
 create mode 100644 Testing/testButton.py
 create mode 100644 Testing/testDFS.py
 create mode 100644 Testing/testDijkstra.py
 create mode 100644 Testing/testEnemy.py
 create mode 100644 Testing/testExpectimax.py
 create mode 100644 Testing/testGame.py
 create mode 100644 Testing/testMaze.py
 create mode 100644 Testing/testMinMax.py
 create mode 100644 Testing/testPlayer.py
 create mode 100644 Testing/testPlayerController.py

diff --git a/Classes/aStar.py b/Classes/aStar.py
new file mode 100644
index 0000000..091306c
--- /dev/null
+++ b/Classes/aStar.py
@@ -0,0 +1,100 @@
+import heapq
+
+
+class AStar:
+    """
+    Initializes the AStar solver with the given maze.
+
+    :param maze: The maze to solve.
+    """
+    def __init__(self, maze):
+        self.maze = maze
+        self.start = (1, 1)
+        self.corners = set()
+        self.directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
+
+        for i in range(maze.num_rows()):
+            for j in range(maze.num_columns()):
+                if maze[i][j] == 3 or maze[i][j] == 4:
+                    self.corners.add((i, j))
+
+    """
+    Heuristic function for A* search.
+
+    :param current: Current position.
+    :param corners: Set of corner positions.
+    :param visited_corners: Set of visited corner positions.
+    :return: Heuristic value.
+    """
+    def heuristic1(self, current, corners, visited_corners):
+        remaining_corners = sum(1 for corner in corners if corner not in visited_corners)
+        first_corner = next(iter(corners))
+        return abs(current[0] - first_corner[0]) + abs(current[1] - first_corner[1]) + remaining_corners
+
+    """
+    Alternative heuristic function for A* search.
+
+    :param current: Current position.
+    :param corners: Set of corner positions.
+    :param visited_corners: Set of visited corner positions.
+    :return: Heuristic value.
+    """
+    def heuristic2(self, current, corners, visited_corners):
+        remaining_corners = sum(1 for corner in corners if corner not in visited_corners)
+        return ((current[0] - corners[0][0]) ** 2 + (current[1] - corners[0][1]) ** 2) ** 0.5 + remaining_corners
+
+    """
+    Checks if all corners have been visited.
+
+    :param corners: Set of corner positions.
+    :param visited_corners: Set of visited corner positions.
+    :return: True if all corners are visited, False otherwise.
+    """
+    def all_corners_visited(self, corners, visited_corners):
+        return all(corner in visited_corners for corner in corners)
+
+    """
+    Finds the position of the goal in the maze.
+
+    :param maze: The maze to search.
+    :return: The position of the goal or None if not found.
+    """
+    def find_goal_position(self, maze):
+        for i in range(maze.num_rows()):
+            for j in range(len(self.maze[0])):
+                if self.maze[i][j] == 3:
+                    return (i, j)
+        return None
+
+    """
+    Finds the shortest path from the start to the goal using A* search.
+
+    :param heuristic: Heuristic function to use.
+    :return: The shortest path as a list of positions, or None if no path is found.
+    """
+    def find_shortest_path(self, heuristic):
+        start = (1, 1)
+        goal = None
+
+        visited = set()
+        frontier = [(0, start, [])]
+        heapq.heapify(frontier)
+
+        while frontier:
+            total_cost, current, path = heapq.heappop(frontier)
+            if self.maze[current[0]][current[1]] == 3:
+                goal = current
+                break
+            if current not in visited:
+                visited.add(current)
+                for dx, dy in self.directions:
+                    x, y = current[0] + dx, current[1] + dy
+                    if 0 <= x < self.maze.num_rows() and 0 <= y < self.maze.num_columns() and self.maze[x][y] != 1:
+                        new_cost = total_cost + 1
+                        heapq.heappush(frontier,
+                                       (new_cost + heuristic((x, y), self.corners, visited), (x, y), path + [current]))
+
+        if goal:
+            return path + [goal]
+        else:
+            return None
diff --git a/Classes/bfs.py b/Classes/bfs.py
new file mode 100644
index 0000000..8e1753e
--- /dev/null
+++ b/Classes/bfs.py
@@ -0,0 +1,90 @@
+from collections import deque
+
+
+class BFS:
+    """
+    Initializes a BFS (Breadth-First Search) solver object.
+    """
+
+    def __init__(self):
+        self.visited = set()
+        self.queue = deque()
+        self.parent = {}  # Dictionary to store parent relationships for path reconstruction
+        self.path = []  # List to store the final path
+
+    """
+    Returns a list of valid neighbours for a given cell in the maze.
+
+    :param maze: The maze grid.
+    :param i: The row index of the current cell.
+    :param j: The column index of the current cell.
+    :return: A list of valid neighbours.
+    """
+
+    def get_neighbours(self, maze, i, j):
+        neighbours = []
+        directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
+
+        for direction in directions:
+            ni, nj = i + direction[0], j + direction[1]
+            if 0 <= ni < len(maze) and 0 <= nj < len(maze[0]) and maze[ni][nj] != 1:
+                neighbours.append((ni, nj))
+
+        return neighbours
+
+    """
+    Performs Breadth-First Search on the maze to find a path from the start to the goal.
+
+    :param maze: The maze grid.
+    :param start: The starting position.
+    :param goal: The goal position.
+    :return: True if a path is found, False otherwise.
+    """
+
+    def bfs(self, maze, start, goal):
+        self.visited.clear()
+        self.queue.clear()
+        self.parent.clear()  # Clear parent dictionary
+        self.path.clear()  # Clear path
+
+        self.queue.append(start)
+        self.visited.add(start)
+
+        while self.queue:
+            current = self.queue.popleft()
+
+            if current == goal:
+                self.construct_path(start, goal)
+                return True  # Goal found
+
+            for neighbour in self.get_neighbours(maze, current[0], current[1]):
+                if neighbour not in self.visited:
+                    self.queue.append(neighbour)
+                    self.visited.add(neighbour)
+                    self.parent[neighbour] = current
+
+        return False  # Goal not reached
+
+    """
+    Constructs the path from the start to the goal using the parent dictionary.
+
+    :param start: The starting position.
+    :param goal: The goal position.
+    """
+
+    def construct_path(self, start, goal):
+        current = goal
+        while current != start:
+            self.path.append(current)
+            current = self.parent[current]
+
+        self.path.append(start)
+
+    """
+    Returns the reconstructed path from start to goal.
+
+    :return: The path as a list of positions.
+    """
+
+    def get_path(self):
+        return list(reversed(self.path))
diff --git a/Classes/button.py b/Classes/button.py
new file mode 100644
index 0000000..d26239e
--- /dev/null
+++ b/Classes/button.py
@@ -0,0 +1,63 @@
+from config import *
+
+
+class Button:
+    """
+    Initializes a Button object with specified attributes.
+
+    :param image: The image representing the button.
+    :param x: The x-coordinate of the button's position.
+    :param y: The y-coordinate of the button's position.
+    :param level: The level associated with the button.
+    :param player_controller: The player controller associated with the button.
+    """
+
+    def __init__(self, image, x, y, level, player_controller):
+        self.image = image
+        self.x = x
+        self.y = y
+        self.level = level
+        self.is_clicked = False
+        self.player_controller = player_controller
+
+    """
+    Draws the button on the game screen.
+
+    This method scales the button's image, positions it, and blits it onto the screen.
+    """
+    def draw(self):
+        width = self.image.get_width()
+        height = self.image.get_height()
+        scaled_image = pygame.transform.scale(self.image, (int(width * 0.7), int(height * 0.7)))
+        image_rect = scaled_image.get_rect()
+        image_rect.right = self.x
+        image_rect.top = self.y
+        screen.blit(scaled_image, image_rect)
+
+    """
+    Handles mouse clicks on the button.
+
+    If the button is clicked, it sets the maze level, resets the player position, and updates the 'is_clicked' 
+    attribute.
+
+    :param mouse_pos: The current mouse position (x, y).
+    :param maze: The maze associated with the button.
+    """
+    def handle_mouse_click(self, mouse_pos, maze):
+        if self.check_click(mouse_pos):
+            maze.set_level(self.level)
+            self.player_controller.reset_player_position()
+            self.is_clicked = True
+        else:
+            self.is_clicked = False
+
+    """
+    Checks if the mouse click is within the button's boundaries.
+
+    :param mouse_pos: The current mouse position (x, y).
+    :return: True if the mouse click is within the button's boundaries, False otherwise.
+    """
+    def check_click(self, mouse_pos):
+        x, y = mouse_pos
+        return self.x <= x <= self.x + self.image.get_width() and self.y <= y <= self.y + self.image.get_height()
+
diff --git a/Classes/config.py b/Classes/config.py
new file mode 100644
index 0000000..25fad33
--- /dev/null
+++ b/Classes/config.py
@@ -0,0 +1,75 @@
+import pygame
+
+TILE_SIZE = 64
+screen = pygame.display.set_mode([640, 512])
+
+timer = pygame.time.Clock()
+FPS = 60
+
+small_maze = [
+    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+    [1, 4, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 4, 1],
+    [1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1],
+    [1, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1],
+    [1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
+    [1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1],
+    [1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1],
+    [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
+    [1, 0, 1, 0, 1, 0, 2, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1],
+    [1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1],
+    [1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1],
+    [1, 4, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1],
+    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+]
+
+medium_maze = [
+    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+    [1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1],
+    [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1],
+    [1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 1],
+    [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1],
+    [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1],
+    [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1],
+    [1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1],
+    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1],
+    [1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 1],
+    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+]
+
+large_maze = [
+    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+    [1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 4, 1],
+    [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1],
+    [1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],
+    [1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1],
+    [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1],
+    [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1],
+    [1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1],
+    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 2, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1],
+    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 1],
+    [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 2, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1],
+    [1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1],
+    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1],
+    [1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1],
+    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+]
+
+levels = [small_maze, medium_maze, large_maze]
+current_level = levels[0]
+player_model = pygame.Rect(TILE_SIZE, TILE_SIZE, TILE_SIZE, TILE_SIZE)
+tiles = [pygame.image.load('blank.png'), pygame.image.load('wall.png'), pygame.image.load('blank.png'),
+         pygame.image.load('goal.png'), pygame.image.load('blank.png')]
+easy_button = pygame.image.load('button_easy.png').convert_alpha()
+normal_button = pygame.image.load('button_normal.png').convert_alpha()
+hard_button = pygame.image.load('button_hard.png').convert_alpha()
+WIDTH, HEIGHT = TILE_SIZE * len(current_level[0]), TILE_SIZE * len(current_level)
+
diff --git a/Classes/dfs.py b/Classes/dfs.py
new file mode 100644
index 0000000..b01cdd3
--- /dev/null
+++ b/Classes/dfs.py
@@ -0,0 +1,70 @@
+
+class DFS:
+    """
+    Initializes a DFS (Depth-First Search) solver object.
+    """
+    def __init__(self):
+        self.visited = set()
+        self.path = []
+
+    """
+    Returns a list of valid neighbours for a given cell in the maze.
+
+    :param maze: The maze grid.
+    :param i: The row index of the current cell.
+    :param j: The column index of the current cell.
+    :return: A list of valid neighbours.
+    """
+    def get_neighbours(self, maze, i, j):
+        neighbours = []
+        directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
+
+        for direction in directions:
+            ni, nj = i + direction[0], j + direction[1]
+            if 0 <= ni < len(maze) and 0 <= nj < len(maze[0]) and maze[ni][nj] != 1:
+                neighbours.append((ni, nj))
+
+        return neighbours
+
+    """
+    Performs Depth-First Search on the maze to find a path from the start to the goal.
+
+    :param maze: The maze grid.
+    :param start: The starting position.
+    :param goal: The goal position.
+    :return: True if a path is found, False otherwise.
+    """
+    def dfs(self, maze, start, goal):
+        self.visited.clear()
+        self.path.clear()
+        return self._dfs(maze, start, goal)
+
+    """
+    Recursive helper function for DFS.
+
+    :param maze: The maze grid.
+    :param current: The current position.
+    :param goal: The goal position.
+    :return: True if a path is found, False otherwise.
+    """
+    def _dfs(self, maze, current, goal):
+        if current == goal:
+            self.path.append(current)
+            return True
+
+        self.visited.add(current)
+
+        for neighbour in self.get_neighbours(maze, current[0], current[1]):
+            if neighbour not in self.visited and self._dfs(maze, neighbour, goal):
+                self.path.append(current)
+                return True
+
+        return False
+
+    """
+    Returns the reconstructed path from start to goal.
+
+    :return: The path as a list of positions.
+    """
+    def get_path(self):
+        return list(reversed(self.path))
diff --git a/Classes/dijkstra.py b/Classes/dijkstra.py
new file mode 100644
index 0000000..3d29284
--- /dev/null
+++ b/Classes/dijkstra.py
@@ -0,0 +1,78 @@
+import heapq
+
+
+class Dijkstra:
+    """
+    Initializes a Dijkstra object.
+
+    :param maze: The maze used for navigation and solving.
+    """
+    def __init__(self, maze):
+        self.maze = maze
+
+    """
+    Calculates the cost of moving from one cell to another based on the cell type.
+
+    :param cell_type: The type of cell (0 for empty, 1 for wall, 2 for goal, etc.).
+    :return: The cost of moving to the cell.
+    """
+    def calculate_cost(self, cell):
+        if cell == 0:
+            return 1
+        elif cell == 2:
+            return 100
+        elif cell == 3:
+            return 1
+        else:
+            return float('inf')
+
+    """
+    Finds the shortest path from the player's current position to a specific location using Dijkstra's algorithm.
+
+    :param start_position: The (row, column) position of the start.
+    :param goal_position: The (row, column) position of the goal.
+    :return: The shortest path from the start position to the goal position.
+    """
+    def find_shortest_path(self, start_position, goal_position):
+        frontier = [(0, start_position)]
+        came_from = {}
+        cost_so_far = {}
+        came_from[start_position] = None
+        cost_so_far[start_position] = 0
+
+        while frontier:
+            current_cost, current = heapq.heappop(frontier)
+
+            if current == goal_position:
+                break
+
+            for next in self.get_neighbors(current):
+                new_cost = cost_so_far[current] + self.calculate_cost(self.maze.current_level[next[0]][next[1]])
+                if next not in cost_so_far or new_cost < cost_so_far[next]:
+                    cost_so_far[next] = new_cost
+                    priority = new_cost
+                    heapq.heappush(frontier, (priority, next))
+                    came_from[next] = current
+
+        current = goal_position
+        path = []
+        while current != start_position:
+            path.append(current)
+            current = came_from[current]
+        path.append(start_position)
+        path.reverse()
+        return path
+
+    """
+    Gets the valid neighboring cells of a given cell.
+
+    :param cell: The (row, column) position of the cell.
+    :return: A list of valid neighboring cells.
+    """
+    def get_neighbors(self, cell):
+        neighbors = []
+        for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
+            x, y = cell[1] + dx, cell[0] + dy
+            if 0 <= x < len(self.maze.current_level[0]) and 0 <= y < len(self.maze.current_level):
+                neighbors.append((y, x))
+        return neighbors
\ No newline at end of file
diff --git a/Classes/enemy.py b/Classes/enemy.py
new file mode 100644
index 0000000..86df1c2
--- /dev/null
+++ b/Classes/enemy.py
@@ -0,0 +1,50 @@
+import random
+from config import *
+
+"""
+Class representing an enemy agent in the game.
+"""
+
+
+class Enemy:
+    """
+    Initialise the Enemy object.
+
+    :param maze (Maze): The maze object representing the game environment.
+    :param x (int): The x-coordinate of the enemy's position.
+    :param y (int): The y-coordinate of the enemy's position.
+    """
+
+    def __init__(self, maze):
+        self.maze = maze
+        self.x, self.y = self.reset_position()
+
+    """
+    Reset the position of the enemy to a valid starting position in the maze.
+
+    :return: tuple: A tuple containing the x and y coordinates of the enemy's position.
+    """
+
+    def reset_position(self):
+        while True:
+            num_rows = self.maze.num_rows()
+            num_cols = self.maze.num_columns()
+
+            random_row = random.randint(0, num_rows - 1)
+
+            start_col = num_cols - 2
+
+            self.x = start_col * TILE_SIZE
+            self.y = random_row * TILE_SIZE
+
+            if self.maze[random_row][start_col] != 1:
+                return self.x, self.y
+
+    """
+    Draw the enemy on the screen.
+
+    :param screen: The Pygame screen surface to draw on.
+    """
+
+    def draw(self, screen):
+        pygame.draw.rect(screen, 'blue', (self.x, self.y, TILE_SIZE, TILE_SIZE))
diff --git a/Classes/expectimax.py b/Classes/expectimax.py
new file mode 100644
index 0000000..3efea7a
--- /dev/null
+++ b/Classes/expectimax.py
@@ -0,0 +1,43 @@
+from minmax import MinMax
+
+
+class Expectimax(MinMax):
+    """
+    Initializes an Expectimax object.
+
+    :param maze: The maze used for navigation and solving.
+    """
+    def __init__(self, maze):
+        super().__init__(maze)
+
+    """
+    Performs the Expectimax algorithm to determine the best move the enemy can take.
+
+    :param player_position: The current position of the player agent.
+    :param enemy_position: The current position of the enemy agent.
+    :param depth: The depth of the Expectimax search tree.
+    :param maximising_player: True if maximising player (enemy), False if minimising player (player).
+    :return: The best move for the enemy agent.
+    """
+    def expectimax(self, player_position, enemy_position, depth, maximising_player):
+        if depth == 0 or self.game_over():
+            # Evaluate the current game state
+            return self.evaluate(player_position, enemy_position)
+
+        if maximising_player:
+            max_eval = float('-inf')
+            valid_moves = self.get_valid_moves(enemy_position)
+            for move in valid_moves:
+                if self.is_valid_move(move):
+                    eval = self.expectimax(player_position, move, depth - 1, False)
+                    max_eval = max(max_eval, eval)
+            return max_eval
+        else:
+            total_eval = 0
+            valid_moves = self.get_valid_moves(player_position)
+            num_moves = len(valid_moves)
+            for move in valid_moves:
+                if self.is_valid_move(move):
+                    eval = self.expectimax(move, enemy_position, depth - 1, True)
+                    total_eval += eval
+            return total_eval / num_moves
\ No newline at end of file
diff --git a/Classes/game.py b/Classes/game.py
new file mode 100644
index 0000000..469b138
--- /dev/null
+++ b/Classes/game.py
@@ -0,0 +1,50 @@
+"""
+Class representing the main game logic.
+"""
+class Game:
+    """
+    Initialize the Game object.
+
+    :param maze: The maze grid representing the game environment.
+    """
+    def __init__(self, maze):
+        self.maze = maze
+        self.player_position = (1, 1)
+        self.enemy_positions = self.get_enemy_positions()
+
+    """
+    Find the current position of the player in the maze.
+
+    :return: The (row, column) position of the player, or None if not found.
+    """
+    def get_player_position(self):
+        for i in range(len(self.maze)):
+            for j in range(len(self.maze[0])):
+                if self.maze[i][j] == 0:
+                    return i, j
+        return None
+
+    """
+    Find the positions of the enemy agents in the maze.
+
+    :return: A list containing the positions of the enemy agents.
+    """
+    def get_enemy_positions(self):
+        # Implement logic to find the opponents' positions in the maze
+        pass
+
+    """
+    Update the positions of the player and enemy agents based on the current state of the maze.
+    """
+    def update_positions(self):
+        self.player_position = self.get_player_position()
+        self.enemy_positions = self.get_enemy_positions()
+
+    """
+    Check if the game is over.
+
+    :return: True if the game is over, False otherwise.
+    """
+    def check_game_over(self):
+        # Implement logic to check if the game is over (e.g., player reached the goal or caught by opponent)
+        pass
diff --git a/Classes/main.py b/Classes/main.py
new file mode 100644
index 0000000..8f69e29
--- /dev/null
+++ b/Classes/main.py
@@ -0,0 +1,65 @@
+from maze import *
+from player import *
+from button import *
+from playerController import *
+from dfs import *
+from bfs import *
+from dijkstra import *
+from aStar import *
+from enemy import *
+from minmax import *
+
+pygame.init()
+maze = Maze(small_maze)
+player = Player(TILE_SIZE, TILE_SIZE)
+enemy1 = Enemy(maze)
+depth = 3
+
+player_controller = PlayerController(player, maze)
+easy_button = Button(easy_button, 150, 10, small_maze, player_controller)
+normal_button = Button(normal_button, 300, 10, medium_maze, player_controller)
+hard_button = Button(hard_button, 426, 10, large_maze, player_controller)
+dfs_solver = DFS()
+bfs_solver = BFS()
+dijkstra_solver = Dijkstra(maze)
+aStar_solver = AStar(maze)
+minmax_solver = MinMax(maze)
+player_controller.set_dfs_solver(dfs_solver)
+player_controller.set_bfs_solver(bfs_solver)
+player_controller.set_dijkstra_solver(dijkstra_solver)
+player_controller.set_astar_solver(aStar_solver)
+# best_move = minmax_solver.minmax(player_position, enemy_position, depth, True)
+
+run = True
+while run:
+    timer.tick(FPS)
+    maze.draw()
+    player.draw(screen)
+    enemy1.draw(screen)
+    easy_button.draw()
+    normal_button.draw()
+    hard_button.draw()
+
+    # player_controller.move_to_goal_dfs()
+    player_controller.move_to_goal_bfs()
+    # player_controller.move_to_goal_dijkstra()
+    # player_controller.move_to_goal_astar()
+
+    direction = pygame.key.get_pressed()
+    player_controller.move_player(direction)
+
+    for event in pygame.event.get():
+        if event.type == pygame.QUIT:
+            run = False
+        elif event.type == pygame.MOUSEBUTTONDOWN:
+            mouse_pos = pygame.mouse.get_pos()
+            easy_button.handle_mouse_click(mouse_pos, maze)
+            normal_button.handle_mouse_click(mouse_pos, maze)
+            hard_button.handle_mouse_click(mouse_pos, maze)
+
+            if easy_button.is_clicked or normal_button.is_clicked or hard_button.is_clicked:
+                player_controller.reset_player_position()
+                enemy1.reset_position()
+
+    pygame.display.flip()
+pygame.quit()
diff --git a/Classes/maze.py b/Classes/maze.py
new file mode 100644
index 0000000..8dc1cab
--- /dev/null
+++ b/Classes/maze.py
@@ -0,0 +1,64 @@
+from config import *
+
+
+class Maze:
+    """
+    Initializes a Maze object with an initial maze level.
+
+    :param initial_level: The initial maze level.
+    """
+    def __init__(self, initial_level):
+        self.current_level = initial_level
+        self.update_screen_size()
+
+    """
+    Get the length of the maze, which is the number of rows in the maze.
+
+    :return: The number of rows in the maze.
+    """
+    def num_rows(self):
+        return len(self.current_level)
+
+    """
+    Get the number of columns in the maze.
+
+    :return: The number of columns in the maze.
+    """
+    def num_columns(self):
+        return len(self.current_level[0])
+
+    """
+    Retrieves the item at the specified index.
+
+    :param index: The index of the item to retrieve.
+    :return: The item at the specified index.
+    """
+    def __getitem__(self, index):
+        return self.current_level[index]
+
+    """
+    Sets a new maze level and updates the screen size accordingly.
+
+    :param new_level: The new maze level.
+    """
+    def set_level(self, new_level):
+        self.current_level = new_level
+        self.update_screen_size()
+
+    """
+    Updates the size of the game screen based on the current maze level.
+    """
+    def update_screen_size(self):
+        screen_size = (len(self.current_level[0]) * TILE_SIZE, len(self.current_level) * TILE_SIZE)
+        screen = pygame.display.set_mode(screen_size)
+
+    """
+    Draws the current maze level on the game screen.
+    """
+    def draw(self):
+        for row in range(len(self.current_level)):
+            for column in range(len(self.current_level[row])):
+                x = column * TILE_SIZE
+                y = row * TILE_SIZE
+                tile = tiles[self.current_level[row][column]]
+                screen.blit(tile, (x, y))
diff --git a/Classes/minmax.py b/Classes/minmax.py
new file mode 100644
index 0000000..cb745b9
--- /dev/null
+++ b/Classes/minmax.py
@@ -0,0 +1,93 @@
+class MinMax:
+    """
+    Initializes a MinMax object.
+
+    :param maze: The maze used for navigation and solving.
+    """
+    def __init__(self, maze):
+        self.maze = maze
+
+    """
+    Gets the valid moves that an enemy agent can take.
+
+    :param player_position: The current position of the player agent.
+    :return: A list of valid moves for the enemy agent.
+    """
+    def get_valid_moves(self, player_position):
+        valid_moves = []
+        for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
+            x, y = player_position[1] + dx, player_position[0] + dy
+            if (0 <= x < len(self.maze.current_level[0]) and 0 <= y < len(self.maze.current_level) and
+                    self.maze.current_level[y][x] != 1):
+                valid_moves.append((y, x))
+        return valid_moves
+
+    """
+    Checks if a move to a given position is valid.
+
+    :param position: The position to move to.
+    :return: True if the move is valid, False otherwise.
+    """
+    def is_valid_move(self, position):
+        x, y = position[1], position[0]
+        return (0 <= x < len(self.maze.current_level[0]) and 0 <= y < len(self.maze.current_level) and
+                self.maze.current_level[y][x] != 1)
+
+    """
+    Performs the MinMax algorithm with Alpha-beta pruning to determine the best move the enemy can take.
+
+    :param player_position: The current position of the player agent.
+    :param enemy_position: The current position of the enemy agent.
+    :param depth: The depth of the MinMax search tree.
+    :param alpha: The best value that the maximizing player currently can guarantee.
+    :param beta: The best value that the minimizing player currently can guarantee.
+    :param maximizing_player: True if maximizing player (enemy), False if minimizing player (player).
+    :return: The best move for the enemy agent.
+    """
+    def minmax(self, player_position, enemy_position, depth, alpha, beta, maximizing_player):
+        if depth == 0 or self.game_over():
+            # Evaluate the current game state
+            return self.evaluate(player_position, enemy_position)
+
+        if maximizing_player:
+            max_eval = float('-inf')
+            valid_moves = self.get_valid_moves(enemy_position)
+            for move in valid_moves:
+                if self.is_valid_move(move):
+                    eval = self.minmax(player_position, move, depth - 1, alpha, beta, False)
+                    max_eval = max(max_eval, eval)
+                    alpha = max(alpha, eval)
+                    if beta <= alpha:
+                        break
+            return max_eval
+        else:
+            min_eval = float('inf')
+            valid_moves = self.get_valid_moves(player_position)
+            for move in valid_moves:
+                if self.is_valid_move(move):
+                    eval = self.minmax(move, enemy_position, depth - 1, alpha, beta, True)
+                    min_eval = min(min_eval, eval)
+                    beta = min(beta, eval)
+                    if beta <= alpha:
+                        break
+            return min_eval
+
+    """
+    Evaluate the current game state.
+
+    :param player_position: The current position of the player agent.
+    :param enemy_position: The current position of the enemy agent.
+    :return: The evaluation of the current game state.
+    """
+    def evaluate(self, player_position, enemy_position):
+        # Implement the evaluation logic here
+        pass
+
+    """
+    Check if the game is over.
+
+    :return: True if the game is over, False otherwise.
+    """
+    def game_over(self):
+        # Implement the game over condition here
+        pass
diff --git a/Classes/player.py b/Classes/player.py
new file mode 100644
index 0000000..4a4840a
--- /dev/null
+++ b/Classes/player.py
@@ -0,0 +1,22 @@
+from config import *
+
+
+class Player:
+    """
+    Initialises a Player object with a specified position.
+
+    :param x: The x-coordinate of the player's position.
+    :param y: The y-coordinate of the player's position
+    """
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
+
+    """
+    Draws the player on the game screen using Pygame.
+
+    This method utilizes the Pygame library to draw a white rectangle representing the player
+    at the specified (x, y) coordinates with the size of TILE_SIZE.
+    """
+    def draw(self, screen):
+        pygame.draw.rect(screen, 'white', (self.x, self.y, TILE_SIZE, TILE_SIZE))
diff --git a/Classes/playerController.py b/Classes/playerController.py
new file mode 100644
index 0000000..c591ce2
--- /dev/null
+++ b/Classes/playerController.py
@@ -0,0 +1,223 @@
+from config import *
+from dfs import *
+from bfs import *
+from dijkstra import *
+from game import *
+
+
+class PlayerController:
+    """
+    Initializes a PlayerController object.
+
+    :param player: The player controlled by this controller.
+    :param maze: The maze used for navigation and solving.
+    """
+    def __init__(self, player, maze):
+        self.player = player
+        self.maze = maze
+        self.player_position = (1, 1)
+        self.dfs_solver = DFS()
+        self.bfs_solver = BFS()
+        self.dijkstra_solver = None
+        self.astar_solver = None
+
+    """
+    Moves the player in the specified direction based on keyboard input.
+
+    :param direction: A dictionary representing the keys pressed.
+    """
+    def move_player(self, direction):
+        new_x = self.player.x
+        new_y = self.player.y
+
+        if direction[pygame.K_UP]:
+            new_y -= TILE_SIZE
+            new_position = (self.player_position[0] - 1, self.player_position[1])
+        elif direction[pygame.K_DOWN]:
+            new_y += TILE_SIZE
+            new_position = (self.player_position[0] + 1, self.player_position[1])
+        elif direction[pygame.K_LEFT]:
+            new_x -= TILE_SIZE
+            new_position = (self.player_position[0], self.player_position[1] - 1)
+        elif direction[pygame.K_RIGHT]:
+            new_x += TILE_SIZE
+            new_position = (self.player_position[0], self.player_position[1] + 1)
+        else:
+            return
+
+        if self.is_valid_position(new_position):
+            self.player_position = new_position
+
+        if self.check_collision(new_x, new_y):
+            self.player.x = new_x
+            self.player.y = new_y
+
+    """
+    Checks if a given position is valid within the maze.
+
+    :param position: The position to check in the form of (x, y) coordinates.
+    :return: True if the position is valid and does not collide with walls, False otherwise.
+    """
+    def is_valid_position(self, position):
+        x, y = position
+        return 0 <= x < len(self.maze.current_level) and 0 <= y < len(self.maze.current_level[0]) and \
+            self.maze.current_level[x][y] != 1
+
+    """
+    Checks if the player will collide with walls at the specified position.
+
+    :param x: The x-coordinate of the potential position.
+    :param y: The y-coordinate of the potential position.
+    :return: True if collision is detected, False otherwise.
+    """
+    def check_collision(self, x, y):
+        row = y // TILE_SIZE
+        column = x // TILE_SIZE
+        return self.maze.current_level[row][column] != 1
+
+    """Resets the player's position to the starting point."""
+    def reset_player_position(self):
+        self.player.x = TILE_SIZE
+        self.player.y = TILE_SIZE
+
+    """
+    Sets the DFS solver used by the controller.
+
+    :param dfs_solver: The DFS solver object.
+    """
+    def set_dfs_solver(self, dfs_solver):
+        self.dfs_solver = dfs_solver
+
+    """
+    Sets the BFS solver used by the controller.
+
+    :param bfs_solver: The BFS solver object.
+    """
+    def set_bfs_solver(self, bfs_solver):
+        self.bfs_solver = bfs_solver
+
+    """
+    Sets the Dijkstra solver used by the controller.
+
+    :param dijkstra_solver: The Dijkstra solver object.
+    """
+    def set_dijkstra_solver(self, dijkstra_solver):
+        self.dijkstra_solver = dijkstra_solver
+
+    """
+    Sets the A* solver used by the controller.
+
+    :param dijkstra_solver: The A* solver object.
+    """
+    def set_astar_solver(self, astar_solver):
+        self.astar_solver = astar_solver
+
+    """
+    Moves the player towards the goal using DFS.
+    """
+    def move_to_goal_dfs(self):
+        start_position = (self.player.y // TILE_SIZE, self.player.x // TILE_SIZE)
+        goal_position = self.find_goal_position()
+
+        if goal_position:
+            if self.dfs_solver.dfs(self.maze.current_level, start_position, goal_position):
+                path = self.dfs_solver.get_path()
+                self.follow_path(path)
+
+    """
+    Moves the player towards the goal using BFS pathfinding.
+    """
+    def move_to_goal_bfs(self):
+        start_position = (self.player.y // TILE_SIZE, self.player.x // TILE_SIZE)
+        goal_position = self.find_goal_position()
+
+        if goal_position:
+            if self.bfs_solver.bfs(self.maze.current_level, start_position, goal_position):
+                path = self.bfs_solver.get_path()
+                self.follow_path(path)
+
+    """
+    Moves the player towards the goal using Dijkstra's algorithm.
+    """
+    def move_to_goal_dijkstra(self):
+        if self.dijkstra_solver is not None:
+            start_position = (self.player.y // TILE_SIZE, self.player.x // TILE_SIZE)
+            goal_position = self.find_goal_position()
+
+            if goal_position:
+                path = self.dijkstra_solver.find_shortest_path(start_position, goal_position)
+                if path:
+                    self.follow_path(path)
+
+    """
+    Moves the player to all 4 corners using A* algorithm.
+    """
+
+    def move_to_goal_astar(self):
+        if self.astar_solver is None:
+            print("A* solver not initialized. Please call set_astar_solver first.")
+            return
+
+        if not self.maze:
+            print("Maze not initialized.")
+            return
+
+        start_position = (self.player.y // TILE_SIZE, self.player.x // TILE_SIZE)
+        goal_position = self.astar_solver.find_goal_position(self.maze)
+
+        if goal_position:
+            path = self.astar_solver.find_shortest_path(self.astar_solver.heuristic1)
+            if path:
+                self.follow_path(path)
+
+    """
+    Moves the player along the specified path.
+
+    :param path: The path to follow.
+    """
+    def follow_path(self, path):
+        for position in path:
+            goal_y, goal_x = position
+            target_y, target_x = goal_y * TILE_SIZE, goal_x * TILE_SIZE
+
+            while self.player.x != target_x or self.player.y != target_y:
+                direction_x = 1 if target_x > self.player.x else -1 if target_x < self.player.x else 0
+                direction_y = 1 if target_y > self.player.y else -1 if target_y < self.player.y else 0
+
+                new_x, new_y = self.player.x + direction_x * TILE_SIZE, self.player.y + direction_y * TILE_SIZE
+
+                if self.maze.current_level[new_y // TILE_SIZE][new_x // TILE_SIZE] != 1:
+                    # Draw the path on the screen
+                    self.maze.draw()
+                    self.draw_path(path)
+
+                    # Move the player
+                    self.player.x, self.player.y = new_x, new_y
+
+                    pygame.display.flip()
+                    pygame.time.delay(100)
+
+    """
+    Draws the path on the screen for visualization.
+
+    :param path: The path to draw.
+    """
+    def draw_path(self, path):
+        for position in path:
+            pygame.draw.rect(
+                screen,
+                (0, 255, 0),  # Green color for the path
+                (position[1] * TILE_SIZE, position[0] * TILE_SIZE, TILE_SIZE, TILE_SIZE),
+            )
+
+    """
+    Finds the position of the goal in the maze.
+
+    :return: The (row, column) position of the goal or None if not found.
+    """
+    def find_goal_position(self):
+        for i in range(len(self.maze.current_level)):
+            for j in range(len(self.maze.current_level[0])):
+                if self.maze.current_level[i][j] == 3:
+                    return i, j
+        return None
diff --git a/Images/blank.png b/Images/blank.png
new file mode 100644
index 0000000000000000000000000000000000000000..50c9e2a0f81462186450944aba69ee033b822c4a
GIT binary patch
literal 239
zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE<t`_ZS!$7>k44ofy`glX=O&z`&C3
z=<CS9u(6-}Pa-P=0|RG)M`SSr1K(i~W;~w1B87p0p~ch1F{C2y?S+H92MidR9n62f
zpY!aXSiz*D_wR4}y{vRa7<<54z6DXT4qL4nvPv1QzG7H)l`&*BGu0%relrGaVXXhp
T5we_tfq}u()z4*}Q$iB}@cL7P

literal 0
HcmV?d00001

diff --git a/Images/button_easy.png b/Images/button_easy.png
new file mode 100644
index 0000000000000000000000000000000000000000..4601185db715287698992947896da6796e7cf000
GIT binary patch
literal 2072
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

literal 0
HcmV?d00001

diff --git a/Images/button_hard.png b/Images/button_hard.png
new file mode 100644
index 0000000000000000000000000000000000000000..88cf8b3ae18ed0ce2479f2873a0aa554bde73d4c
GIT binary patch
literal 1757
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
Mp00i_>zopr0Do0MYybcN

literal 0
HcmV?d00001

diff --git a/Images/button_normal.png b/Images/button_normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..821d5a79d1c98f2a6680072c39c502a7c0bcef7d
GIT binary patch
literal 2395
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
PU|{fc^>bP0l+XkK`?h_T

literal 0
HcmV?d00001

diff --git a/Images/enemy.png b/Images/enemy.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c40921e35a8caf9813b8644e7dcc76b55487a0b
GIT binary patch
literal 232
zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE<t`_ZS!$7>k44ofy`glX=O&z`&C3
z=<CS9u(6-}Pa-P=0|RG)M`SSr1K(i~W;~w1B87p0q1MyIF{C2y?S+Mm3<?Y_2d@7u
tzjjEhp!M9%d$VsWjpAKEI}~)AJ<yz~E;qf?gn@y9!PC{xWt~$(69BFPNm2j+

literal 0
HcmV?d00001

diff --git a/Images/goal.png b/Images/goal.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a650e44fdceee5be61a68040c1b8838ae83716a
GIT binary patch
literal 238
zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE<t`_ZS!$7>k44ofy`glX=O&z`&C3
z=<CS9u(6-}Pa-P=0|RG)M`SSr1K(i~W;~w1B87p0q1n^LF{C2y?S+H92MidR9n62f
zpY!aXSiz<v_wV1mw}<cQD~4598ADbxuLxrgSj)E{O4ebkRYO)O1GOZ)a_xgnnd*+V
TZ}MkgU|{fc^>bP0l+XkK!go@M

literal 0
HcmV?d00001

diff --git a/Images/wall.png b/Images/wall.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb06cd2a45741ab3b9b0d54f0fa47a7fd6493295
GIT binary patch
literal 144
zcmeAS@N?(olHy`uVBq!ia0y~yV0Z|^9Lx+144k=29t;c&%mF?ju8TeXXFmIq%)r3l
z;pyTSQZeW4!A1rK28P26eg?-s;0R>|aY(@*c#kb&U<~JvUJTOZ>FVdQ&MBb@07Ow6
A2mk;8

literal 0
HcmV?d00001

diff --git a/Testing/testAStar.py b/Testing/testAStar.py
new file mode 100644
index 0000000..1d98f72
--- /dev/null
+++ b/Testing/testAStar.py
@@ -0,0 +1,68 @@
+import unittest
+from aStar import AStar
+
+
+class TestAStar(unittest.TestCase):
+    def setUp(self):
+        self.maze = [
+            [1, 0, 0, 0],
+            [0, 1, 1, 0],
+            [0, 0, 0, 1],
+            [1, 0, 0, 0]
+        ]
+        self.a_star_solver = AStar(self.maze)
+
+    def test_heuristic1(self):
+        current_position = (1, 1)
+        visited_corners = set()
+        heuristic_result = self.a_star_solver.heuristic1(current_position, self.a_star_solver.corners, visited_corners)
+        self.assertEqual(heuristic_result, 3)
+
+        current_position = (2, 2)
+        visited_corners = set()
+        heuristic_result = self.a_star_solver.heuristic1(current_position, self.a_star_solver.corners, visited_corners)
+        self.assertEqual(heuristic_result, ...)
+
+    def test_heuristic2(self):
+        current_position = (1, 1)
+        visited_corners = set()
+        heuristic_result = self.a_star_solver.heuristic2(current_position, self.a_star_solver.corners, visited_corners)
+        self.assertEqual(heuristic_result, ...)
+
+        current_position = (2, 2)
+        visited_corners = set()
+        heuristic_result = self.a_star_solver.heuristic2(current_position, self.a_star_solver.corners, visited_corners)
+        self.assertEqual(heuristic_result, ...)
+
+    def test_all_corners_visited(self):
+        corners = {(1, 1), (2, 2), (3, 3)}
+        visited_corners = {(1, 1), (2, 2)}
+        result = self.a_star_solver.all_corners_visited(corners, visited_corners)
+        self.assertFalse(result)
+
+    def test_find_goal_position(self):
+        maze_with_goal = ...
+        goal_position = self.a_star_solver.find_goal_position(maze_with_goal)
+        self.assertIsNotNone(goal_position)
+
+        maze_without_goal = ...
+        goal_position = self.a_star_solver.find_goal_position(maze_without_goal)
+        self.assertIsNone(goal_position)
+
+    def test_find_shortest_path_with_valid_heuristic(self):
+        def valid_heuristic(current, corners, visited_corners):
+            return 0
+
+        path = self.a_star_solver.find_shortest_path(valid_heuristic)
+        self.assertIsNotNone(path)
+
+    def test_find_shortest_path_with_invalid_heuristic(self):
+        def invalid_heuristic(current, corners, visited_corners):
+            return None
+
+        path = self.a_star_solver.find_shortest_path(invalid_heuristic)
+        self.assertIsNone(path)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Testing/testBFS.py b/Testing/testBFS.py
new file mode 100644
index 0000000..3d5f40a
--- /dev/null
+++ b/Testing/testBFS.py
@@ -0,0 +1,109 @@
+import unittest
+from collections import deque
+from bfs import BFS
+
+
+class TestBFS(unittest.TestCase):
+    def setUp(self):
+        self.bfs_solver = BFS()
+
+    def test_init(self):
+        bfs = BFS()
+        self.assertEqual(len(bfs.visited), 0)
+        self.assertIsInstance(bfs.queue, deque)
+        self.assertEqual(len(bfs.parent), 0)
+        self.assertEqual(len(bfs.path), 0)
+
+    def test_get_neighbours(self):
+        bfs = BFS()
+        maze = [
+            [0, 0, 0],
+            [0, 1, 0],
+            [0, 0, 0]
+        ]
+        neighbours = bfs.get_neighbours(maze, 1, 1)
+        expected_neighbours = [(0, 1), (1, 0), (1, 2), (2, 1)]
+        self.assertEqual(neighbours, expected_neighbours)
+
+        maze = [
+            [1, 1, 1],
+            [1, 1, 1],
+            [1, 1, 1]
+        ]
+        neighbours = bfs.get_neighbours(maze, 1, 1)
+        self.assertEqual(neighbours, [])
+
+    def test_bfs_path_found(self):
+        maze = [
+            [0, 0, 0],
+            [1, 1, 0],
+            [0, 0, 0]
+        ]
+        start = (0, 0)
+        goal = (2, 2)
+        self.assertTrue(self.bfs_solver.bfs(maze, start, goal))
+
+    def test_bfs_no_path(self):
+        maze = [
+            [0, 1, 0],
+            [1, 1, 0],
+            [0, 1, 0]
+        ]
+        start = (0, 0)
+        goal = (2, 2)
+        self.assertFalse(self.bfs_solver.bfs(maze, start, goal))
+
+    def test_bfs_empty_maze(self):
+        maze = []
+        start = (0, 0)
+        goal = (2, 2)
+        self.assertFalse(self.bfs_solver.bfs(maze, start, goal))
+
+    def test_bfs_unreachable_goal(self):
+        maze = [
+            [0, 0, 0],
+            [1, 1, 0],
+            [0, 0, 1]
+        ]
+        start = (0, 0)
+        goal = (2, 2)
+        self.assertFalse(self.bfs_solver.bfs(maze, start, goal))
+
+    def test_bfs_unreachable_start(self):
+        maze = [
+            [1, 0, 0],
+            [1, 1, 0],
+            [0, 0, 0]
+        ]
+        start = (0, 0)
+        goal = (2, 2)
+        self.assertFalse(self.bfs_solver.bfs(maze, start, goal))
+
+    def test_bfs_single_cell_maze(self):
+        maze = [[0]]
+        start = (0, 0)
+        goal = (0, 0)
+        self.assertTrue(self.bfs_solver.bfs(maze, start, goal))
+
+    def test_construct_path(self):
+        start = (0, 0)
+        goal = (2, 2)
+        parent = {
+            (1, 1): (0, 1),
+            (1, 2): (1, 1),
+            (2, 2): (1, 2),
+            (0, 1): (0, 0)
+        }
+        self.bfs_solver.parent = parent
+        self.bfs_solver.construct_path(start, goal)
+        expected_path = [(0, 0), (0, 1), (1, 1), (1, 2), (2, 2)]
+        self.assertEqual(self.bfs_solver.path, expected_path)
+
+    def test_get_path(self):
+        self.bfs_solver.path = [(0, 0), (0, 1), (1, 1), (1, 2), (2, 2)]
+        expected_path = [(2, 2), (1, 2), (1, 1), (0, 1), (0, 0)]
+        self.assertEqual(self.bfs_solver.get_path(), expected_path)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Testing/testButton.py b/Testing/testButton.py
new file mode 100644
index 0000000..c191dbb
--- /dev/null
+++ b/Testing/testButton.py
@@ -0,0 +1,56 @@
+import unittest
+from unittest.mock import Mock
+from button import Button
+
+
+class TestButton(unittest.TestCase):
+    def setUp(self):
+        pygame = Mock()
+        self.mock_screen = pygame.display.set_mode.return_value
+        self.mock_player_controller = Mock()
+        self.mock_maze = Mock()
+        self.mock_image = Mock()
+        self.mock_image.get_width.return_value = 100
+        self.mock_image.get_height.return_value = 50
+        self.button = Button(self.mock_image, 100, 100, 1, None)
+
+    def test_draw(self):
+        mock_image = Mock()
+        button = Button(mock_image, 100, 100, 1, Mock())
+
+        button.draw()
+
+        expected_calls = [
+            ((mock_image, (100, 100)),),
+        ]
+        self.assertEqual(self.mock_screen.blit.call_args_list, expected_calls)
+
+    def test_handle_mouse_click_when_clicked(self):
+        button = Button(Mock(), 100, 100, 1, self.mock_player_controller)
+
+        button.handle_mouse_click((110, 110), self.mock_maze)
+
+        self.mock_maze.set_level.assert_called_once_with(1)
+        self.mock_player_controller.reset_player_position.assert_called_once()
+        self.assertTrue(button.is_clicked)
+
+    def test_handle_mouse_click_when_not_clicked(self):
+        button = Button(Mock(), 100, 100, 1, self.mock_player_controller)
+
+        button.handle_mouse_click((90, 90), self.mock_maze)
+
+        self.assertFalse(self.mock_maze.set_level.called)
+        self.assertFalse(self.mock_player_controller.reset_player_position.called)
+        self.assertFalse(button.is_clicked)
+
+    def test_check_click_within_boundaries(self):
+        result = self.button.check_click((150, 125))
+        self.assertTrue(result)
+
+    def test_check_click_outside_boundaries(self):
+        result = self.button.check_click((50, 75))
+        self.assertFalse(result)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Testing/testDFS.py b/Testing/testDFS.py
new file mode 100644
index 0000000..53afc4f
--- /dev/null
+++ b/Testing/testDFS.py
@@ -0,0 +1,80 @@
+import unittest
+from dfs import DFS
+
+
+class TestDFS(unittest.TestCase):
+    def test_init(self):
+        dfs = DFS()
+        self.assertEqual(dfs.visited, set())
+        self.assertEqual(dfs.path, [])
+
+    def test_get_neighbours(self):
+        maze = [
+            [0, 0, 0, 1],
+            [1, 1, 0, 0],
+            [0, 1, 0, 1],
+            [0, 0, 0, 0]
+        ]
+        dfs = DFS()
+
+        self.assertEqual(dfs.get_neighbours(maze, 0, 0), [(0, 1), (1, 0)])
+        self.assertEqual(dfs.get_neighbours(maze, 1, 1), [(1, 2), (2, 1)])
+        self.assertEqual(dfs.get_neighbours(maze, 2, 2), [(2, 1), (3, 2)])
+
+    def test_dfs(self):
+        maze = [
+            [0, 0, 0, 1],
+            [1, 1, 0, 0],
+            [0, 1, 0, 1],
+            [0, 0, 0, 0]
+        ]
+        dfs = DFS()
+
+        start = (0, 0)
+        goal = (3, 3)
+        self.assertTrue(dfs.dfs(maze, start, goal))
+
+        start = (0, 0)
+        goal = (2, 2)
+        self.assertFalse(dfs.dfs(maze, start, goal))
+
+    def test__dfs(self):
+        maze = [
+            [0, 0, 0, 1],
+            [1, 1, 0, 0],
+            [0, 1, 0, 1],
+            [0, 0, 0, 0]
+        ]
+        dfs = DFS()
+
+        start = (0, 0)
+        goal = (3, 3)
+        self.assertTrue(dfs._dfs(maze, start, goal))
+
+        start = (0, 0)
+        goal = (2, 2)
+        self.assertFalse(dfs._dfs(maze, start, goal))
+
+    def test_get_path(self):
+        maze = [
+            [0, 0, 0, 1],
+            [1, 1, 0, 0],
+            [0, 1, 0, 1],
+            [0, 0, 0, 0]
+        ]
+        dfs = DFS()
+
+        start = (0, 0)
+        goal = (3, 3)
+        dfs._dfs(maze, start, goal)
+        expected_path = [(0, 0), (0, 1), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3)]
+        self.assertEqual(dfs.get_path(), expected_path)
+
+        start = (0, 0)
+        goal = (2, 2)
+        dfs._dfs(maze, start, goal)
+        self.assertEqual(dfs.get_path(), [])
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Testing/testDijkstra.py b/Testing/testDijkstra.py
new file mode 100644
index 0000000..b6e6804
--- /dev/null
+++ b/Testing/testDijkstra.py
@@ -0,0 +1,79 @@
+import unittest
+from dijkstra import Dijkstra
+
+
+class TestDijkstra(unittest.TestCase):
+    def setUp(self):
+        self.dijkstra_solver = Dijkstra(None)
+
+    def test_calculate_cost_empty(self):
+        cell_type = 0
+        cost = self.dijkstra_solver.calculate_cost(cell_type)
+        self.assertEqual(cost, 1)
+
+    def test_calculate_cost_wall(self):
+        cell_type = 1
+        cost = self.dijkstra_solver.calculate_cost(cell_type)
+        self.assertEqual(cost, float('inf'))
+
+    def test_calculate_cost_goal(self):
+        cell_type = 2
+        cost = self.dijkstra_solver.calculate_cost(cell_type)
+        self.assertEqual(cost, 100)
+
+    def test_calculate_cost_start(self):
+        cell_type = 3
+        cost = self.dijkstra_solver.calculate_cost(cell_type)
+        self.assertEqual(cost, 1)
+
+    def test_find_shortest_path_simple(self):
+
+        start_position = (0, 0)
+        goal_position = (3, 3)
+        path = self.dijkstra_solver.find_shortest_path(start_position, goal_position)
+        expected_path = [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (3, 2), (3, 3)]
+        self.assertEqual(path, expected_path)
+
+    def test_find_shortest_path_blocked_goal(self):
+
+        start_position = (0, 0)
+        goal_position = (3, 3)
+        path = self.dijkstra_solver.find_shortest_path(start_position, goal_position)
+        expected_path = []
+        self.assertEqual(path, expected_path)
+
+    def test_find_shortest_path_complex(self):
+
+        start_position = (0, 0)
+        goal_position = (4, 4)
+        path = self.dijkstra_solver.find_shortest_path(start_position, goal_position)
+        expected_path = [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (2, 3), (3, 3), (4, 3), (4, 4)]
+        self.assertEqual(path, expected_path)
+
+    def test_get_neighbors_middle(self):
+        maze = Dijkstra([
+            [0, 0, 0, 1],
+            [1, 1, 0, 0],
+            [0, 1, 0, 1],
+            [0, 0, 0, 0]
+        ])
+        cell = (1, 1)
+        neighbors = maze.get_neighbors(cell)
+        expected_neighbors = [(0, 1), (2, 1), (1, 0), (1, 2)]
+        self.assertEqual(neighbors, expected_neighbors)
+
+    def test_get_neighbors_corner(self):
+        maze = Dijkstra([
+            [0, 0, 0, 1],
+            [1, 1, 0, 0],
+            [0, 1, 0, 1],
+            [0, 0, 0, 0]
+        ])
+        cell = (0, 0)
+        neighbors = maze.get_neighbors(cell)
+        expected_neighbors = [(1, 0), (0, 1)]
+        self.assertEqual(neighbors, expected_neighbors)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Testing/testEnemy.py b/Testing/testEnemy.py
new file mode 100644
index 0000000..048d0ff
--- /dev/null
+++ b/Testing/testEnemy.py
@@ -0,0 +1,46 @@
+import unittest
+from unittest.mock import MagicMock, patch
+from enemy import Enemy
+from config import TILE_SIZE
+
+
+class TestEnemy(unittest.TestCase):
+    @patch('enemy.random')
+    def test_init(self, mock_random):
+        mock_maze = MagicMock()
+
+        mock_random.randint.return_value = 2
+        mock_maze.num_rows.return_value = 5
+        mock_maze.num_columns.return_value = 6
+        mock_maze.__getitem__.return_value = 0
+
+        enemy = Enemy(mock_maze)
+
+        self.assertEqual(enemy.x, 2 * 32)
+        self.assertEqual(enemy.y, 0)
+
+    @patch('enemy.random')
+    def test_reset_position(self, mock_random):
+        mock_maze = MagicMock()
+
+        mock_random.randint.return_value = 2
+        mock_maze.num_rows.return_value = 5
+        mock_maze.num_columns.return_value = 6
+        mock_maze.__getitem__.return_value = 0
+
+        enemy = Enemy(mock_maze)
+        enemy.reset_position()
+
+        self.assertEqual(enemy.x, 2 * 32)
+        self.assertEqual(enemy.y, 0)
+
+    def test_draw(self):
+        mock_screen = MagicMock()
+
+        enemy = Enemy(None)
+        enemy.draw(mock_screen)
+        mock_screen.assert_called_once_with('blue', (enemy.x, enemy.y, enemy.TILE_SIZE, enemy.TILE_SIZE))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Testing/testExpectimax.py b/Testing/testExpectimax.py
new file mode 100644
index 0000000..d456122
--- /dev/null
+++ b/Testing/testExpectimax.py
@@ -0,0 +1,69 @@
+import unittest
+from expectimax import Expectimax
+
+
+class TestExpectimax(unittest.TestCase):
+
+    def test_valid_scenario(self):
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        expectimax_solver = Expectimax(maze)
+        player_position = (0, 0)
+        enemy_position = (2, 2)
+        depth = 3
+        maximizing_player = True
+        result = expectimax_solver.expectimax(player_position, enemy_position, depth, maximizing_player)
+        self.assertIsNotNone(result)
+
+    def test_invalid_scenario_negative_depth(self):
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        expectimax_solver = Expectimax(maze)
+        player_position = (0, 0)
+        enemy_position = (2, 2)
+        depth = -1
+        maximizing_player = True
+        with self.assertRaises(ValueError):
+            expectimax_solver.expectimax(player_position, enemy_position, depth, maximizing_player)
+
+    def test_no_valid_moves(self):
+        maze = [[1, 1, 1],
+                [1, 1, 1],
+                [1, 1, 1]]
+        expectimax_solver = Expectimax(maze)
+        player_position = (0, 0)
+        enemy_position = (2, 2)
+        depth = 3
+        maximizing_player = True
+        result = expectimax_solver.expectimax(player_position, enemy_position, depth, maximizing_player)
+        self.assertEqual(result, 0)
+
+    def test_game_over(self):
+        maze = [[0, 0, 0],
+                [0, 0, 0],
+                [0, 0, 0]]
+        expectimax_solver = Expectimax(maze)
+        player_position = (0, 0)
+        enemy_position = (2, 2)
+        depth = 3
+        maximizing_player = True
+        result = expectimax_solver.expectimax(player_position, enemy_position, depth, maximizing_player)
+        self.assertEqual(result, 0)
+
+    def test_max_depth(self):
+        maze = [[0, 0, 0],
+                [0, 0, 0],
+                [0, 0, 0]]
+        expectimax_solver = Expectimax(maze)
+        player_position = (0, 0)
+        enemy_position = (2, 2)
+        depth = 10
+        maximizing_player = True
+        result = expectimax_solver.expectimax(player_position, enemy_position, depth, maximizing_player)
+        self.assertIsNotNone(result)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Testing/testGame.py b/Testing/testGame.py
new file mode 100644
index 0000000..dbbfc54
--- /dev/null
+++ b/Testing/testGame.py
@@ -0,0 +1,60 @@
+import unittest
+from game import Game
+
+
+class TestGame(unittest.TestCase):
+
+    def setUp(self):
+        self.maze = [
+            [1, 1, 1, 1, 1],
+            [1, 0, 0, 0, 1],
+            [1, 0, 1, 0, 1],
+            [1, 0, 0, 0, 1],
+            [1, 1, 1, 1, 1]
+        ]
+        self.game = Game(self.maze)
+
+    def test_init(self):
+        maze = [
+            [1, 1, 1, 1, 1],
+            [1, 0, 0, 0, 1],
+            [1, 0, 1, 0, 1],
+            [1, 0, 0, 0, 1],
+            [1, 1, 1, 1, 1]
+        ]
+
+        game = Game(maze)
+
+        self.assertEqual(game.maze, maze)
+        self.assertEqual(game.player_position, (1, 1))
+        self.assertEqual(game.enemy_positions, [(2, 2)])
+
+    def test_get_player_position(self):
+        maze = [
+            [1, 1, 1, 1, 1],
+            [1, 0, 0, 0, 1],
+            [1, 0, 1, 0, 1],
+            [1, 0, 0, 0, 1],
+            [1, 1, 1, 1, 1]
+        ]
+
+        game = Game(maze)
+        player_position = game.get_player_position()
+        self.assertEqual(player_position, (1, 1))
+
+    def test_get_enemy_positions(self):
+        enemy_positions = self.game.get_enemy_positions()
+        self.assertEqual(enemy_positions, [(2, 2)])
+
+    def test_update_positions(self):
+        self.game.update_positions()
+        self.assertEqual(self.game.player_position, (1, 1))
+        self.assertEqual(self.game.enemy_positions, [(2, 2)])
+
+    def test_check_game_over(self):
+        game_over = self.game.check_game_over()
+        self.assertFalse(game_over)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Testing/testMaze.py b/Testing/testMaze.py
new file mode 100644
index 0000000..da1c135
--- /dev/null
+++ b/Testing/testMaze.py
@@ -0,0 +1,92 @@
+import unittest
+from unittest.mock import Mock
+
+import pygame
+
+from maze import Maze
+
+
+class TestMaze(unittest.TestCase):
+    def test_num_rows(self):
+        initial_level = [
+            [0, 0, 0],
+            [0, 0, 0],
+            [0, 0, 0]
+        ]
+        maze = Maze(initial_level)
+        self.assertEqual(maze.num_rows(), 3)
+
+    def test_num_columns(self):
+        initial_level = [
+            [0, 0, 0, 0],
+            [0, 0, 0, 0],
+            [0, 0, 0, 0]
+        ]
+        maze = Maze(initial_level)
+        self.assertEqual(maze.num_columns(), 4)
+
+    def test_getitem(self):
+        initial_level = [
+            [0, 0, 0],
+            [0, 0, 0],
+            [0, 0, 0]
+        ]
+        maze = Maze(initial_level)
+        self.assertEqual(maze[0], [0, 0, 0])
+        self.assertEqual(maze[1], [0, 0, 0])
+        self.assertEqual(maze[2], [0, 0, 0])
+
+    def test_set_level(self):
+        initial_level = [
+            [0, 0, 0],
+            [0, 0, 0],
+            [0, 0, 0]
+        ]
+        maze = Maze(initial_level)
+        new_level = [
+            [1, 1, 1],
+            [1, 1, 1],
+            [1, 1, 1]
+        ]
+        maze.set_level(new_level)
+        self.assertEqual(maze.current_level, new_level)
+
+    def test_update_screen_size(self):
+        initial_level = [
+            [0, 0, 0],
+            [0, 0, 0],
+            [0, 0, 0]
+        ]
+        maze = Maze(initial_level)
+        pygame.display.set_mode((800, 600))
+        maze.update_screen_size()
+        self.assertEqual(pygame.display.get_surface().get_size(), (3 * 32, 3 * 32))
+
+    def test_draw(self):
+        initial_level = [
+            [0, 0, 0],
+            [0, 1, 0],
+            [0, 0, 0]
+        ]
+        maze = Maze(initial_level)
+        tiles = {0: Mock(), 1: Mock()}
+        maze.tiles = tiles
+
+        maze.draw()
+
+        expected_calls = [
+            ((tiles[0], (0, 0)),),
+            ((tiles[0], (32, 0)),),
+            ((tiles[0], (64, 0)),),
+            ((tiles[0], (0, 32)),),
+            ((tiles[1], (32, 32)),),
+            ((tiles[0], (64, 32)),),
+            ((tiles[0], (0, 64)),),
+            ((tiles[0], (32, 64)),),
+            ((tiles[0], (64, 64)),),
+        ]
+        self.assertEqual(self.mock_screen.blit.call_args_list, expected_calls)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Testing/testMinMax.py b/Testing/testMinMax.py
new file mode 100644
index 0000000..a3ae88d
--- /dev/null
+++ b/Testing/testMinMax.py
@@ -0,0 +1,112 @@
+import unittest
+from minmax import MinMax
+
+
+class TestMinMax(unittest.TestCase):
+
+    def test_get_valid_moves_middle(self):
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        minmax = MinMax(maze)
+        player_position = (1, 1)
+        valid_moves = minmax.get_valid_moves(player_position)
+        expected_moves = [(0, 1), (2, 1), (1, 0), (1, 2)]
+        self.assertEqual(valid_moves, expected_moves)
+
+    def test_get_valid_moves_corner(self):
+        maze = [[0, 1, 0],
+                [1, 1, 0],
+                [0, 0, 0]]
+        minmax = MinMax(maze)
+        player_position = (0, 0)
+        valid_moves = minmax.get_valid_moves(player_position)
+        expected_moves = [(1, 0), (0, 1)]
+        self.assertEqual(valid_moves, expected_moves)
+
+    def test_valid_move(self):
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        minmax = MinMax(maze)
+        valid_position = (1, 0)
+        self.assertTrue(minmax.is_valid_move(valid_position))
+
+    def test_invalid_move_wall(self):
+        maze = [[0, 1, 0],
+                [1, 1, 0],
+                [0, 0, 0]]
+        minmax = MinMax(maze)
+        wall_position = (0, 1)
+        self.assertFalse(minmax.is_valid_move(wall_position))
+
+    def test_invalid_move_outside(self):
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        minmax = MinMax(maze)
+        outside_position = (3, 0)
+        self.assertFalse(minmax.is_valid_move(outside_position))
+
+    def test_minmax(self):
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        minmax = MinMax(maze)
+        player_position = (0, 0)
+        enemy_position = (2, 2)
+        depth = 3
+        alpha = float('-inf')
+        beta = float('inf')
+        maximizing_player = True
+        result = minmax.minmax(player_position, enemy_position, depth, alpha, beta, maximizing_player)
+        self.assertIsInstance(result, int)
+
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        player_position = (0, 0)
+        enemy_position = (2, 2)
+        depth = 1
+
+        result = minmax.minmax(player_position, enemy_position, depth, float('-inf'), float('inf'), maximizing_player)
+        print("Result:", result)
+
+    def test_evaluate(self):
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        minmax_object = MinMax(maze)
+        print("Testing the evaluate method...")
+
+        player_position = (1, 1)
+        enemy_position = (1, 1)
+        evaluation_result = minmax_object.evaluate(player_position, enemy_position)
+        print("Evaluation result when player and enemy are at the same position:", evaluation_result)
+
+        player_position = (2, 2)
+        enemy_position = (1, 1)
+        evaluation_result = minmax_object.evaluate(player_position, enemy_position)
+        print("Evaluation result when player is closer to the goal than the enemy:", evaluation_result)
+
+        player_position = (1, 1)
+        enemy_position = (2, 2)
+        evaluation_result = minmax_object.evaluate(player_position, enemy_position)
+        print("Evaluation result when enemy is closer to the goal than the player:", evaluation_result)
+
+    def test_game_over(self):
+        maze = [[0, 0, 0],
+                [0, 1, 0],
+                [0, 0, 0]]
+        minmax_object = MinMax(maze)
+        print("Testing the game_over method...")
+
+        game_over_result = minmax_object.game_over()
+        print("Game over result when the game is not over:", game_over_result)
+
+        game_over_result = minmax_object.game_over()
+        print("Game over result when the game is over:", game_over_result)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Testing/testPlayer.py b/Testing/testPlayer.py
new file mode 100644
index 0000000..a3386cc
--- /dev/null
+++ b/Testing/testPlayer.py
@@ -0,0 +1,18 @@
+import unittest
+from unittest.mock import Mock, patch
+from config import TILE_SIZE
+from player import Player
+
+
+class TestPlayer(unittest.TestCase):
+    @patch('pygame.draw.rect')
+    def test_draw(self, mock_rect):
+        # Mock the screen surface
+        screen_surface_mock = Mock()
+        player = Player(10, 20)
+        player.draw(screen_surface_mock)
+        mock_rect.assert_called_once_with(screen_surface_mock, 'white', (10, 20, TILE_SIZE, TILE_SIZE))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Testing/testPlayerController.py b/Testing/testPlayerController.py
new file mode 100644
index 0000000..591c217
--- /dev/null
+++ b/Testing/testPlayerController.py
@@ -0,0 +1,226 @@
+import unittest
+from unittest.mock import Mock
+
+import pygame
+
+from config import TILE_SIZE
+from player import Player
+from playerController import PlayerController  # Import the PlayerController class
+
+
+class TestPlayerController(unittest.TestCase):
+    def setUp(self):
+        self.player_mock = Mock()
+        self.maze_mock = Mock()
+        self.player_controller = PlayerController(self.player_mock, self.maze_mock)
+
+    def test_move_player_up(self):
+        direction = {pygame.K_UP: True, pygame.K_DOWN: False, pygame.K_LEFT: False, pygame.K_RIGHT: False}
+        self.player_controller.move_player(direction)
+        self.assertEqual(self.player_mock.y, self.player_mock.y - TILE_SIZE)
+
+    def test_move_player_down(self):
+        direction = {pygame.K_UP: False, pygame.K_DOWN: True, pygame.K_LEFT: False, pygame.K_RIGHT: False}
+        self.player_controller.move_player(direction)
+        self.assertEqual(self.player_mock.y, self.player_mock.y + TILE_SIZE)
+
+    def test_move_player_left(self):
+        direction = {pygame.K_UP: False, pygame.K_DOWN: False, pygame.K_LEFT: True, pygame.K_RIGHT: False}
+        self.player_controller.move_player(direction)
+        self.assertEqual(self.player_mock.x, self.player_mock.x - TILE_SIZE)
+
+    def test_move_player_right(self):
+        direction = {pygame.K_UP: False, pygame.K_DOWN: False, pygame.K_LEFT: False, pygame.K_RIGHT: True}
+        self.player_controller.move_player(direction)
+        self.assertEqual(self.player_mock.x, self.player_mock.x + TILE_SIZE)
+
+    def test_valid_position_within_maze(self):
+        position = (1, 1)
+        self.assertTrue(self.player_controller.is_valid_position(position))
+
+    def test_position_outside_maze_bounds(self):
+        position = (-1, 1)
+        self.assertFalse(self.player_controller.is_valid_position(position))
+        position = (10, 5)
+        self.assertFalse(self.player_controller.is_valid_position(position))
+        position = (1, 10)
+        self.assertFalse(self.player_controller.is_valid_position(position))
+
+    def test_position_collides_with_wall(self):
+        self.player_controller.maze = Mock(current_level=[[0, 0, 0],
+                                                          [0, 1, 0],
+                                                          [0, 0, 0]])
+        position = (1, 1)
+        self.assertFalse(self.player_controller.is_valid_position(position))
+        position = (0, 1)
+        self.assertTrue(self.player_controller.is_valid_position(position))
+
+    def test_collision_with_wall(self):
+        self.player_controller.maze = [[0, 0, 0],
+                                       [0, 1, 0],
+                                       [0, 0, 0]]
+        x, y = 50, 50
+        self.assertTrue(self.player_controller.check_collision(x, y))
+
+    def test_no_collision_with_wall(self):
+        self.player_controller.maze = [[0, 0, 0],
+                                       [0, 0, 0],
+                                       [0, 0, 0]]
+        x, y = 50, 50
+        self.assertFalse(self.player_controller.check_collision(x, y))
+
+    def test_reset_player_position(self):
+        initial_x, initial_y = 100, 100
+        self.player_controller.player = Player(initial_x, initial_y)
+        self.player_controller.reset_player_position()
+        self.assertEqual(self.player_controller.player.x, TILE_SIZE)
+        self.assertEqual(self.player_controller.player.y, TILE_SIZE)
+
+    def test_set_dfs_solver(self):
+        dfs_solver_mock = Mock()
+        self.player_controller.set_dfs_solver(dfs_solver_mock)
+        self.assertEqual(self.player_controller.dfs_solver, dfs_solver_mock)
+
+    def test_set_bfs_solver(self):
+        bfs_solver_mock = Mock()
+        self.player_controller.set_bfs_solver(bfs_solver_mock)
+        self.assertEqual(self.player_controller.bfs_solver, bfs_solver_mock)
+
+    def test_set_dijkstra_solver(self):
+        dijkstra_solver_mock = Mock()
+        self.player_controller.set_dijkstra_solver(dijkstra_solver_mock)
+        self.assertEqual(self.player_controller.dijkstra_solver, dijkstra_solver_mock)
+
+    def test_set_astar_solver(self):
+        astar_solver_mock = Mock()
+        self.player_controller.set_astar_solver(astar_solver_mock)
+        self.assertEqual(self.player_controller.astar_solver, astar_solver_mock)
+
+    def test_move_to_goal_dfs(self):
+        dfs_solver_mock = Mock()
+        dfs_solver_mock.dfs.return_value = True
+        dfs_solver_mock.get_path.return_value = [(1, 1), (1, 2), (1, 3)]
+
+        self.player_controller.set_dfs_solver(dfs_solver_mock)
+        self.player_controller.maze = Mock()
+        self.player_controller.maze.current_level = [
+            [0, 0, 0, 0],
+            [0, 0, 3, 0],
+            [0, 0, 0, 0],
+        ]
+
+        self.player_controller.player.x = TILE_SIZE
+        self.player_controller.player.y = TILE_SIZE
+        self.player_controller.move_to_goal_dfs()
+
+        expected_path = [(1 * TILE_SIZE, 1 * TILE_SIZE), (1 * TILE_SIZE, 2 * TILE_SIZE), (1 * TILE_SIZE, 3 * TILE_SIZE)]
+        self.assertEqual(
+            [(self.player_controller.player.x, self.player_controller.player.y) for _ in range(len(expected_path))],
+            expected_path)
+
+    def test_move_to_goal_bfs(self):
+        bfs_solver_mock = Mock()
+        bfs_solver_mock.bfs.return_value = True
+        bfs_solver_mock.get_path.return_value = [(1, 1), (1, 2), (1, 3)]
+
+        self.player_controller.set_bfs_solver(bfs_solver_mock)
+        self.player_controller.maze = Mock()
+        self.player_controller.maze.current_level = [
+            [0, 0, 0, 0],
+            [0, 0, 3, 0],
+            [0, 0, 0, 0],
+        ]
+
+        self.player_controller.player.x = TILE_SIZE
+        self.player_controller.player.y = TILE_SIZE
+        self.player_controller.move_to_goal_bfs()
+
+        expected_path = [(1 * TILE_SIZE, 1 * TILE_SIZE), (1 * TILE_SIZE, 2 * TILE_SIZE), (1 * TILE_SIZE, 3 * TILE_SIZE)]
+        self.assertEqual(
+            [(self.player_controller.player.x, self.player_controller.player.y) for _ in range(len(expected_path))],
+            expected_path)
+
+    def test_move_to_goal_dijkstra(self):
+        dijkstra_solver_mock = Mock()
+        dijkstra_solver_mock.find_shortest_path.return_value = [(1, 1), (1, 2), (1, 3)]  # Simulate a path
+
+        self.player_controller.set_dijkstra_solver(dijkstra_solver_mock)
+        self.player_controller.maze = Mock()
+        self.player_controller.maze.current_level = [
+            [0, 0, 0, 0],
+            [0, 0, 3, 0],
+            [0, 0, 0, 0],
+        ]
+
+        self.player_controller.player.x = TILE_SIZE
+        self.player_controller.player.y = TILE_SIZE
+        self.player_controller.move_to_goal_dijkstra()
+
+        expected_path = [(1 * TILE_SIZE, 1 * TILE_SIZE), (1 * TILE_SIZE, 2 * TILE_SIZE), (1 * TILE_SIZE, 3 * TILE_SIZE)]
+        self.assertEqual(
+            [(self.player_controller.player.x, self.player_controller.player.y) for _ in range(len(expected_path))],
+            expected_path)
+
+    def test_move_to_goal_astar(self):
+        astar_solver_mock = Mock()
+        astar_solver_mock.find_goal_position.return_value = (2, 2)  # Simulate a goal position
+        astar_solver_mock.find_shortest_path.return_value = [(1, 1), (1, 2), (1, 3)]  # Simulate a path
+
+        self.player_controller.set_astar_solver(astar_solver_mock)
+        self.player_controller.maze = Mock()
+        self.player_controller.maze.current_level = [
+            [0, 0, 0, 0],
+            [0, 0, 3, 0],
+            [0, 0, 0, 0],
+        ]
+
+        self.player_controller.player.x = TILE_SIZE
+        self.player_controller.player.y = TILE_SIZE
+        self.player_controller.move_to_goal_astar()
+
+        expected_path = [(1 * TILE_SIZE, 1 * TILE_SIZE), (1 * TILE_SIZE, 2 * TILE_SIZE), (1 * TILE_SIZE, 3 * TILE_SIZE)]
+        self.assertEqual(
+            [(self.player_controller.player.x, self.player_controller.player.y) for _ in range(len(expected_path))],
+            expected_path)
+
+    def test_follow_path(self):
+        player_mock = Mock()
+        self.player_controller.player = player_mock
+        maze_mock = Mock()
+        self.player_controller.maze = maze_mock
+        path = [(0, 0), (0, 1), (0, 2)]
+        self.player_controller.follow_path(path)
+        expected_positions = [(0, 0), (0, 1), (0, 2)]
+        self.assertEqual([(args[0], args[1]) for args in player_mock.x.call_args_list], expected_positions)
+
+    def test_draw_path(self):
+        screen_mock = Mock()
+        self.player_controller.screen = screen_mock
+        path = [(0, 0), (0, 1), (1, 1)]
+        self.player_controller.draw_path(path)
+
+        expected_calls = [
+            ((screen_mock,), {'color': (0, 255, 0), 'rect': (0, 0, TILE_SIZE, TILE_SIZE)}),
+            ((screen_mock,), {'color': (0, 255, 0), 'rect': (TILE_SIZE, 0, TILE_SIZE, TILE_SIZE)}),
+            ((screen_mock,), {'color': (0, 255, 0), 'rect': (TILE_SIZE, TILE_SIZE, TILE_SIZE, TILE_SIZE)})
+        ]
+
+        self.assertEqual(screen_mock.mock_calls,
+                         [unittest.mock.call.draw.rect(*args, **kwargs) for args, kwargs in expected_calls])
+
+    def test_find_goal_position(self):
+        mock_maze = Mock()
+        mock_maze.current_level = [
+            [0, 0, 0],
+            [0, 0, 0],
+            [0, 0, 3]
+        ]
+
+        self.player_controller.maze = mock_maze
+        goal_position = self.player_controller.find_goal_position()
+        expected_goal_position = (2, 2)
+        self.assertEqual(goal_position, expected_goal_position)
+
+
+if __name__ == "__main__":
+    unittest.main()
-- 
GitLab