Beispiel #1
0
    def breadth_first_search(self, graph: MatrixGraph, start: tuple, target: tuple,
                             surface: pygame.Surface, display: pygame.Surface) \
            -> list[tuple[int, int]]:
        """
        Return a list of tuples representing the final path if target is found,
        return an empty list otherwise.

        This function is an implementation of the breadth_first_search pathfinding algorithm
        """
        queue = []
        visited = set()
        paths = {}  # A dictionary that maps new nodes to the previous node
        queue.extend(graph.get_valid_neighbours(start[0], start[1]))
        visited.update(graph.get_valid_neighbours(start[0], start[1]))

        # Update paths with the original visited set
        for node in graph.get_valid_neighbours(start[0], start[1]):
            paths[node] = start
        found = False

        # Counter to store current iteration
        counter = 0

        # Pygame clock
        clock = Timer()

        while queue != [] and not found:
            # Draw and update the loop iteration counter
            counter += 1
            iteration_counter = f'Nodes Searched: {counter}'
            self._draw_loop_iterations(iteration_counter, surface)

            # Pop the current node
            curr = queue.pop(0)

            # Visualize step
            _ = pygame.event.get()  # Call event.get to stop program from crashing on clicks
            curr_x = curr[0] + self._maze_x_offset + 1
            curr_y = curr[1] + self._maze_y_offset + 1
            pygame.draw.circle(surface, (255, 0, 0), (curr_x, curr_y), 3)
            display.blit(surface, (0, 0))
            pygame.display.flip()

            if curr == target:
                found = True
            for node in graph.get_valid_neighbours(curr[0], curr[1]):
                if node not in visited:
                    queue.append(node)
                    visited.add(node)

                    # Add the node as a key with the current node as the value
                    paths[node] = curr

            clock.update_time()
            self._draw_timer(clock, surface)

        if found is False:
            return []
        else:
            return self._find_and_draw_final_path(paths, start, target, surface, display)
 def test3(self):
     mg = MatrixGraph([[7, 8, 9, 2, 1], [2, 4, 7, 0, 3], [1, 2, 3, 2, 8],
                       [2, 9, 6, 5, 8]])
     neighbors = mg.get_neighbors((0, 4))
     self.assertEqual(len(neighbors), 3)
     self.assertIn((0, 3), neighbors)
     self.assertIn((1, 3), neighbors)
     self.assertIn((1, 4), neighbors)
 def test4(self):
     mg = MatrixGraph([[7, 8, 9, 2, 1], [2, 4, 7, 0, 3], [1, 2, 3, 2, 8],
                       [2, 9, 6, 5, 8]])
     neighbors = mg.get_neighbors((3, 1))
     self.assertEqual(len(neighbors), 5)
     self.assertIn((2, 0), neighbors)
     self.assertIn((2, 1), neighbors)
     self.assertIn((2, 2), neighbors)
     self.assertIn((3, 0), neighbors)
     self.assertIn((3, 2), neighbors)
 def test2(self):
     mg = MatrixGraph([[7, 8, 9], [2, 4, 7], [1, 2, 3]])
     neighbors = mg.get_neighbors((1, 1))
     self.assertEqual(len(neighbors), 8)
     self.assertIn((0, 0), neighbors)
     self.assertIn((0, 1), neighbors)
     self.assertIn((0, 2), neighbors)
     self.assertIn((1, 0), neighbors)
     self.assertIn((1, 2), neighbors)
     self.assertIn((2, 0), neighbors)
     self.assertIn((2, 1), neighbors)
     self.assertIn((2, 2), neighbors)
Beispiel #5
0
    def depth_first_search_iterative(self, graph: MatrixGraph, start: tuple, target: tuple,
                                     surface: pygame.Surface, display: pygame.Surface) \
            -> list[tuple[int, int]]:
        """
        Return a list of tuples representing the final path if target is found,
        return an empty list otherwise.

        This is an iterative version of depth_first_search, since the recursive version exceeds
        the maximum recursion depth.
        """
        discovered = set()
        stack = [start]  # Stack is a reversed list for now. Later we can make a stack class if we
        # need

        paths = {}  # A dictionary that maps new nodes to the previous node
        found = False

        # Variables and Surfaces used to display the current iterations
        counter = 0

        # Pygame clock
        clock = Timer()

        while stack != [] and not found:
            # Draw and update the loop iteration counter
            counter += 1
            iteration_counter = f'Nodes Searched: {counter}'
            self._draw_loop_iterations(iteration_counter, surface)

            vertex = stack.pop()

            # Visualize step
            _ = pygame.event.get()  # Call event.get to stop program from crashing on clicks
            curr_x = vertex[0] + self._maze_x_offset + 1
            curr_y = vertex[1] + self._maze_y_offset + 1
            pygame.draw.circle(surface, (255, 0, 0), (curr_x, curr_y), 3)
            display.blit(surface, (0, 0))
            pygame.display.flip()

            if vertex == target:
                found = True
            elif vertex not in discovered:
                discovered.add(vertex)
                neighbors = graph.get_valid_neighbours(vertex[0], vertex[1])
                for neighbor in neighbors:
                    if neighbor not in discovered:
                        # Add the neighbor as a key in the path dictionary with vertex as a parent
                        stack.append(neighbor)
                        paths[neighbor] = vertex

            clock.update_time()
            self._draw_timer(clock, surface)

        if found is False:
            return []
        else:
            return self._find_and_draw_final_path(paths, start, target, surface, display)
Beispiel #6
0
 def set_pos(self, graph: MatrixGraph, posx: int, posy: int) -> Union[None, tuple[int, int]]:
     """
     Set the position of the button to be the closest on the path. If the point is too far
     print an error message
     """
     if self.active:
         try:
             return graph.closest_path((posx, posy), 5)
         except IndexError:
             print('Select point closer to path')
             return None
     else:
         return None
Beispiel #7
0
    def a_star(self, graph: MatrixGraph, start: tuple, target: tuple,
               surface: pygame.Surface, display: pygame.Surface) -> list[tuple[int, int]]:
        """
        The heuristic used is the distance from target to the current node
        if f(n) = 0 we have reached our node, our promising choice is the min(f(n)) for each
        neighbour
        """

        open_queue = PriorityQueue()
        open_queue.put((graph.euclidean_distance(start, target), (start, 0)))

        closed = {start}

        paths = {}  # A dictionary that maps new nodes to the previous node
        found = False
        # Variables and Surfaces used to display the current iterations
        counter = 0

        # Pygame clock
        clock = Timer()

        while not open_queue.empty() and not found:
            # Draw and update iteration counter
            counter += 1
            iteration_counter = f'Nodes Searched: {counter}'
            self._draw_loop_iterations(iteration_counter, surface)

            curr = open_queue.get()
            closed.add(curr[1][0])

            _ = pygame.event.get()  # Call event.get to stop program from crashing on clicks
            curr_x = curr[1][0][0] + self._maze_x_offset + 1
            curr_y = curr[1][0][1] + self._maze_y_offset + 1
            pygame.draw.circle(surface, (255, 0, 0), (curr_x, curr_y), 3)
            display.blit(surface, (0, 0))
            pygame.display.flip()

            if curr[1][0] == target:
                found = True

            neighbours = graph.get_valid_neighbours(curr[1][0][0], curr[1][0][1])

            for coord in neighbours:
                if coord in closed:
                    # If the neighbor has already been computed, do nothing
                    continue
                if not any(tup[1][0] == coord for tup in open_queue.queue):
                    # If the neighbor is not in the the open queue, add it

                    # Compute the heuristic and add it to open
                    neighbour_f = curr[1][1] + 1 + graph.euclidean_distance(target, coord)
                    open_queue.put((neighbour_f, (coord, curr[1][1] + 1)))

                    # Track the path
                    paths[coord] = curr[1][0]

            # Update clock
            clock.update_time()
            self._draw_timer(clock, surface)

        if found is False:
            return []
        else:
            return self._find_and_draw_final_path(paths, start, target, surface, display)
 def test5(self):
     mg = MatrixGraph([[4, 5, 2], [5, 1, 5], [8, 1, 1]])
     expected = [[False, False, False], [False, False, False],
                 [True, False, False]]
     self.assertEqual(expected, mg.mark_plateaus())
 def test4(self):
     mg = MatrixGraph([[1, 9, 1], [3, 5, 4], [2, 5, 1]])
     expected = [[False, True, False], [False, False, False],
                 [False, False, False]]
     self.assertEqual(expected, mg.mark_plateaus())
 def test3(self):
     mg = MatrixGraph([[1, 8, 2, 5], [6, 7, 4, 3], [9, 8, 2, 3],
                       [1, 4, 2, 2]])
     expected = [[False, True, False, True], [False, False, False, False],
                 [True, False, False, False], [False, False, False, False]]
     self.assertEqual(expected, mg.mark_plateaus())
 def test2(self):
     mg = MatrixGraph([[3]])
     expected = [[True]]
     self.assertEqual(expected, mg.mark_plateaus())
 def test1(self):
     mg = MatrixGraph([[7]])
     neighbors = mg.get_neighbors((0, 0))
     self.assertEqual(len(neighbors), 0)
 def test1(self):
     mg = MatrixGraph([[4, 5, 2], [1, 5, 5], [2, 3, 1]])
     expected = [[False, True, False], [False, True, True],
                 [False, False, False]]
     self.assertEqual(expected, mg.mark_plateaus())
 def test8(self):
     mg = MatrixGraph([[9, 9, 9], [9, 9, 9], [9, 9, 5]])
     expected = [[True, True, True], [True, True, True],
                 [True, True, False]]
     self.assertEqual(expected, mg.mark_plateaus())
 def test7(self):
     mg = MatrixGraph([[5, 5, 5], [5, 5, 5], [5, 5, 9]])
     expected = [[False, False, False], [False, False, False],
                 [False, False, True]]
     self.assertEqual(expected, mg.mark_plateaus())
Beispiel #16
0
def initialize_maze(
    maze_path: str,
    rectangular: bool = True
) -> tuple[pygame.Surface, pygame.Surface, MatrixGraph, pygame.Surface, int,
           int, None, None, bool]:
    """
    Initialize the program variables with respect to the maze found at maze_path.

    Return a tuple containing: (The Pygame Display, The Surface to draw on, The MatrixGraph for the
    maze, The MatrixGraph Surface layer, The amount of pixels used to center the width, The amount
    of pixels used to center the height, A None value representing the start of the maze, A None
    value representing the end of the maze, and a bool representing if we can run the program once)
    """

    # Initialize pygame and the read the maze image
    pygame.init()
    maze = maze_path
    image = cv2.resize(cv2.imread(maze), (1280, 720))

    # crop only works for rectangular mazes
    if rectangular:
        cropped = crop_image(image)
    else:
        cropped = image

    # Global thresholding using Otsu's binarization
    # Note: Although unpacking like this results in one of the variables to be unused and makes
    # PyTA heavily depressed, this is standard OpenCV notation.
    # For reference, you may check docs.opencv.org/master/d7/d4d/tutorial_py_thresholding.html
    retVal, thresh = cv2.threshold(cv2.cvtColor(cropped, cv2.COLOR_RGB2GRAY),
                                   0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    thinned = np.array(cv2.ximgproc.thinning(thresh)) // 255

    # cut borders
    if not rectangular:
        temp = np.delete(thinned, [0, thinned.shape[1] - 1], axis=1)
        temp = np.delete(thinned, [0, thinned.shape[0] - 1], axis=0)
        graph = MatrixGraph(np.swapaxes(temp, 0, 1))
    else:
        graph = MatrixGraph(np.swapaxes(thinned, 0, 1))

    # Create pygame surfaces for the display, and the maze
    display_surface = pygame.display.set_mode(
        (1280 + PADDING_X, 720 + GUI_Y_OFFSET + PADDING_Y))
    maze_surface = pygame.surfarray.make_surface(np.swapaxes(cropped, 0, 1))

    # Center the maze image
    maze_img_w = maze_surface.get_width()
    maze_img_h = maze_surface.get_height()
    surface_w = display_surface.get_width()
    surface_h = display_surface.get_height()
    maze_centered_width = ((surface_w - maze_img_w) // 2)
    maze_centered_height = ((surface_h - maze_img_h) // 2) + 2 * GUI_Y_OFFSET

    # Draw the maze at the centered location
    display_surface.blit(maze_surface,
                         (maze_centered_width, maze_centered_height))

    # Create the surface to draw the pathing on
    surface = pygame.Surface(
        (1280 + PADDING_X, 720 + GUI_Y_OFFSET + PADDING_Y), pygame.SRCALPHA,
        32)
    surface = surface.convert_alpha()

    display_surface.blit(surface, (0, 0))

    # Initialize starting variables
    start_vertex = None
    end_vertex = None
    run_once = True

    return (display_surface, surface, graph, maze_surface, maze_centered_width,
            maze_centered_height, start_vertex, end_vertex, run_once)