From e9ede2ecb35ee1208295c80548a71c2537ba486e Mon Sep 17 00:00:00 2001 From: ZhuravlevDV Date: Mon, 25 May 2026 13:03:14 +0300 Subject: [PATCH] [2] secondex --- .../docs/data/secondex/wayoutoflabirint.py | 395 ++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 ZhuravlevDV/docs/data/secondex/wayoutoflabirint.py diff --git a/ZhuravlevDV/docs/data/secondex/wayoutoflabirint.py b/ZhuravlevDV/docs/data/secondex/wayoutoflabirint.py new file mode 100644 index 0000000..84734a9 --- /dev/null +++ b/ZhuravlevDV/docs/data/secondex/wayoutoflabirint.py @@ -0,0 +1,395 @@ +import heapq +import time +import csv +from abc import ABC, abstractmethod + +class Cell: + def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False): + self.x = x + self.y = y + self.is_wall = is_wall + self.is_start = is_start + self.is_exit = is_exit + + def is_passable(self): + return not self.is_wall + +class Maze: + def __init__(self, width, height): + self.width = width + self.height = height + self.grid = [[None for _ in range(width)] for _ in range(height)] + self.start = None + self.exit = None + + def set_cell(self, x, y, cell): + self.grid[y][x] = cell + if cell.is_start: + self.start = cell + if cell.is_exit: + self.exit = cell + + def get_cell(self, x, y): + if 0 <= x < self.width and 0 <= y < self.height: + return self.grid[y][x] + return None + + def get_neighbors(self, cell): + neighbors = [] + directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] + for dx, dy in directions: + nx, ny = cell.x + dx, cell.y + dy + neighbor = self.get_cell(nx, ny) + if neighbor and neighbor.is_passable(): + neighbors.append(neighbor) + return neighbors + +class MazeBuilder(ABC): + @abstractmethod + def build_from_file(self, filename): + pass + +class TextFileMazeBuilder(MazeBuilder): + def build_from_file(self, filename): + with open(filename, 'r', encoding='utf-8') as f: + lines = [line.rstrip('\n') for line in f.readlines()] + + height = len(lines) + width = len(lines[0]) if height > 0 else 0 + maze = Maze(width, height) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + is_wall = (ch == '#') + is_start = (ch == 'S') + is_exit = (ch == 'E') + is_passable = (ch == ' ' or is_start or is_exit) + + cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit) + maze.set_cell(x, y, cell) + + return maze + +class PathFindingStrategy(ABC): + @abstractmethod + def find_path(self, maze, start, exit): + pass + +class BFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit): + if not start or not exit: + return [] + + queue = [(start, [start])] + visited = set() + + while queue: + current, path = queue.pop(0) + + if current == exit: + return path + + if current in visited: + continue + + visited.add(current) + + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + queue.append((neighbor, path + [neighbor])) + + return [] + +class DFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit): + if not start or not exit: + return [] + + stack = [(start, [start])] + visited = set() + + while stack: + current, path = stack.pop() + + if current == exit: + return path + + if current in visited: + continue + + visited.add(current) + + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + stack.append((neighbor, path + [neighbor])) + + return [] + +class AStarStrategy(PathFindingStrategy): + def heuristic(self, cell, exit): + return abs(cell.x - exit.x) + abs(cell.y - exit.y) + + def find_path(self, maze, start, exit): + if not start or not exit: + return [] + + open_set = [(0, id(start), start)] + came_from = {} + g_score = {start: 0} + f_score = {start: self.heuristic(start, exit)} + + while open_set: + _, _, current = heapq.heappop(open_set) + + if current == exit: + path = [] + while current in came_from: + path.append(current) + current = came_from[current] + path.append(start) + path.reverse() + return path + + for neighbor in maze.get_neighbors(current): + tentative_g = g_score[current] + 1 + + if neighbor not in g_score or tentative_g < g_score[neighbor]: + came_from[neighbor] = current + g_score[neighbor] = tentative_g + f_score[neighbor] = tentative_g + self.heuristic(neighbor, exit) + heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor)) + + return [] + +class DijkstraStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit): + if not start or not exit: + return [] + + pq = [(0, id(start), start)] + distances = {start: 0} + came_from = {} + + while pq: + dist, _, current = heapq.heappop(pq) + + if current == exit: + path = [] + while current in came_from: + path.append(current) + current = came_from[current] + path.append(start) + path.reverse() + return path + + if dist > distances.get(current, float('inf')): + continue + + for neighbor in maze.get_neighbors(current): + new_dist = dist + 1 + + if new_dist < distances.get(neighbor, float('inf')): + distances[neighbor] = new_dist + came_from[neighbor] = current + heapq.heappush(pq, (new_dist, id(neighbor), neighbor)) + + return [] + +class SearchStats: + def __init__(self, time_ms, visited_cells, path_length, path=None): + self.time_ms = time_ms + self.visited_cells = visited_cells + self.path_length = path_length + self.path = path + +class MazeSolver: + def __init__(self, maze, strategy=None): + self.maze = maze + self.strategy = strategy + self.observers = [] + + def set_strategy(self, strategy): + self.strategy = strategy + + def attach(self, observer): + self.observers.append(observer) + + def notify(self, event): + for observer in self.observers: + observer.update(event) + + def solve(self): + if not self.strategy: + raise ValueError("Strategy not set") + + start_time = time.perf_counter() + path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) + end_time = time.perf_counter() + + time_ms = (end_time - start_time) * 1000 + visited_cells = len(path) if path else 0 + path_length = len(path) if path else 0 + + self.notify(f"Path found with length {path_length} in {time_ms:.2f}ms") + + return SearchStats(time_ms, visited_cells, path_length, path) + +class Observer(ABC): + @abstractmethod + def update(self, event): + pass + +class ConsoleView(Observer): + def __init__(self): + self.last_path = None + + def update(self, event): + print(f"[ConsoleView] {event}") + + def render(self, maze, player_pos=None, path=None): + print("\n" + "=" * (maze.width * 2 + 2)) + for y in range(maze.height): + row = "" + for x in range(maze.width): + cell = maze.get_cell(x, y) + if player_pos and cell == player_pos: + row += "P " + elif path and cell in path: + row += "* " + elif cell.is_start: + row += "S " + elif cell.is_exit: + row += "E " + elif cell.is_wall: + row += "# " + else: + row += ". " + print(row) + print("=" * (maze.width * 2 + 2)) + +class Command(ABC): + @abstractmethod + def execute(self): + pass + + @abstractmethod + def undo(self): + pass + +class Player: + def __init__(self, start_cell): + self.current = start_cell + self.start = start_cell + + def move_to(self, cell): + self.current = cell + +class MoveCommand(Command): + def __init__(self, player, new_cell, maze): + self.player = player + self.new_cell = new_cell + self.old_cell = player.current + self.maze = maze + + def execute(self): + if self.new_cell.is_passable(): + self.player.move_to(self.new_cell) + return True + return False + + def undo(self): + self.player.move_to(self.old_cell) + +def generate_test_mazes(): + mazes = {} + + simple_maze = Maze(5, 5) + for y in range(5): + for x in range(5): + is_wall = (x == 2 and y == 1) or (x == 2 and y == 2) or (x == 2 and y == 3) + is_start = (x == 0 and y == 0) + is_exit = (x == 4 and y == 4) + cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit) + simple_maze.set_cell(x, y, cell) + mazes["simple"] = simple_maze + + empty_maze = Maze(20, 20) + for y in range(20): + for x in range(20): + is_start = (x == 0 and y == 0) + is_exit = (x == 19 and y == 19) + cell = Cell(x, y, is_wall=False, is_start=is_start, is_exit=is_exit) + empty_maze.set_cell(x, y, cell) + mazes["empty"] = empty_maze + + return mazes + +def run_experiments(): + mazes = generate_test_mazes() + strategies = { + "BFS": BFSStrategy(), + "DFS": DFSStrategy(), + "AStar": AStarStrategy(), + "Dijkstra": DijkstraStrategy() + } + + results = [] + + for maze_name, maze in mazes.items(): + for strat_name, strategy in strategies.items(): + solver = MazeSolver(maze, strategy) + + times = [] + visited_counts = [] + path_lengths = [] + + for run in range(5): + stats = solver.solve() + times.append(stats.time_ms) + visited_counts.append(stats.visited_cells) + path_lengths.append(stats.path_length) + + avg_time = sum(times) / len(times) + avg_visited = sum(visited_counts) / len(visited_counts) + avg_length = sum(path_lengths) / len(path_lengths) + + results.append([maze_name, strat_name, avg_time, avg_visited, avg_length]) + print(f"{maze_name} | {strat_name}: {avg_time:.3f}ms, {avg_visited:.0f} cells, {avg_length:.0f} length") + + with open("maze_results.csv", "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerow(["Лабиринт", "Стратегия", "Время_мс", "Посещено_клеток", "Длина_пути"]) + writer.writerows(results) + + return results + +def main(): + print("Testing maze loading...") + builder = TextFileMazeBuilder() + + try: + maze = builder.build_from_file("maze.txt") + print(f"Maze loaded: {maze.width}x{maze.height}") + + solver = MazeSolver(maze) + view = ConsoleView() + solver.attach(view) + + strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()] + + for strategy in strategies: + solver.set_strategy(strategy) + print(f"\n--- {strategy.__class__.__name__} ---") + stats = solver.solve() + view.render(maze, path=stats.path) + print(f"Time: {stats.time_ms:.3f}ms, Path length: {stats.path_length}") + + except FileNotFoundError: + print("maze.txt not found, running experiments with generated mazes instead") + + print("\n" + "="*50) + print("Running experiments...") + run_experiments() + +if __name__ == "__main__": + main() \ No newline at end of file