From 768c8e64f209b64b18c06300df66b2855e88d192 Mon Sep 17 00:00:00 2001 From: SokolovEN <000pulemet000@mail.ru> Date: Sun, 24 May 2026 21:11:08 +0300 Subject: [PATCH] [2]for zadanie 2 --- SokolovEN/docs/data/empty.txt | 49 ++ SokolovEN/docs/data/experiment_results.csv | 13 + SokolovEN/docs/data/experiment_results.png | Bin 0 -> 80163 bytes SokolovEN/docs/data/large.txt | 54 ++ SokolovEN/docs/data/maze.py | 532 +++++++++++++++++++ SokolovEN/docs/data/maze1.txt | 10 + SokolovEN/docs/data/medium.txt | 48 ++ SokolovEN/docs/data/plots.py | 580 +++++++++++++++++++++ SokolovEN/docs/data/small.txt | 10 + 9 files changed, 1296 insertions(+) create mode 100644 SokolovEN/docs/data/empty.txt create mode 100644 SokolovEN/docs/data/experiment_results.csv create mode 100644 SokolovEN/docs/data/experiment_results.png create mode 100644 SokolovEN/docs/data/large.txt create mode 100644 SokolovEN/docs/data/maze.py create mode 100644 SokolovEN/docs/data/maze1.txt create mode 100644 SokolovEN/docs/data/medium.txt create mode 100644 SokolovEN/docs/data/plots.py create mode 100644 SokolovEN/docs/data/small.txt diff --git a/SokolovEN/docs/data/empty.txt b/SokolovEN/docs/data/empty.txt new file mode 100644 index 0000000..6d0a249 --- /dev/null +++ b/SokolovEN/docs/data/empty.txt @@ -0,0 +1,49 @@ +######################################## +#S # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# E# +######################################## \ No newline at end of file diff --git a/SokolovEN/docs/data/experiment_results.csv b/SokolovEN/docs/data/experiment_results.csv new file mode 100644 index 0000000..b9d6ce1 --- /dev/null +++ b/SokolovEN/docs/data/experiment_results.csv @@ -0,0 +1,13 @@ +maze,strategy,time_ms,visited_cells,path_length,success_rate +Small (10x10),BFS,0.052460000733844936,30.0,14.0,1.0 +Small (10x10),DFS,0.0480999966384843,32.0,14.0,1.0 +Small (10x10),A*,0.07206000154837966,23.0,14.0,1.0 +Medium (50x50),BFS,0.2786600001854822,182.0,92.0,1.0 +Medium (50x50),DFS,0.14713999989908189,93.0,92.0,1.0 +Medium (50x50),A*,0.5699400004232302,182.0,92.0,1.0 +Large (100x100),BFS,0.39185999776236713,201.0,149.0,1.0 +Large (100x100),DFS,0.2371800015680492,151.0,149.0,1.0 +Large (100x100),A*,0.5810399976326153,200.0,149.0,1.0 +Empty,BFS,3.187239999533631,1834.0,86.0,1.0 +Empty,DFS,1.9440599950030446,1797.0,922.0,1.0 +Empty,A*,6.751939994865097,1834.0,86.0,1.0 diff --git a/SokolovEN/docs/data/experiment_results.png b/SokolovEN/docs/data/experiment_results.png new file mode 100644 index 0000000000000000000000000000000000000000..bc9929c6607047c9131e4d25823d8fce18f118c1 GIT binary patch literal 80163 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
  • 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

    Hb+E;+~r!6{zjy(927Dpxk;}`5iGB-=HS7T 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 literal 0 HcmV?d00001 diff --git a/SokolovEN/docs/data/large.txt b/SokolovEN/docs/data/large.txt new file mode 100644 index 0000000..90a84ad --- /dev/null +++ b/SokolovEN/docs/data/large.txt @@ -0,0 +1,54 @@ +#################################################################################################### +#S # +# ################################################################################################ # +# # # # +# # ############################################################################################ # # +# # # # # # +# # # ######################################################################################## # # # +# # # # # # # # +# # # # #################################################################################### # # # # +# # # # # # # # # # +# # # # # ################################################################################ # # # # # +# # # # # # # # # # # # +# # # # # # ############################################################################ # # # # # # +# # # # # # # # # # # # # # +# # # # # # # ######################################################################## # # # # # # # +# # # # # # # # # # # # # # # # +# # # # # # # # #################################################################### # # # # # # # # +# # # # # # # # # # # # # # # # # # +# # # # # # # # # ################################################################ # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # ############################################################ # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ######################################################## # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # #################################################### # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # ################################################ # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # ############################################ # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # ######################################## # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # #################################### # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # ################################ # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # ############################ # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # ######################## # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # #################### # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # ################ # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # ############ # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # ######## # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # #### # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #E# +#################################################################################################### \ No newline at end of file diff --git a/SokolovEN/docs/data/maze.py b/SokolovEN/docs/data/maze.py new file mode 100644 index 0000000..a74a98c --- /dev/null +++ b/SokolovEN/docs/data/maze.py @@ -0,0 +1,532 @@ +import sys +from collections import deque +import heapq +import time +import os +from abc import ABC, abstractmethod +from typing import List, Optional, Dict, Any + + +DATA_PATH = r"C:\Users\user\Desktop\2026-rff_mp\SokolovEN\docs\data" + + +class Observer(ABC): + @abstractmethod + def update(self, event: str, data: Any = None): + pass + + +class Observable: + def __init__(self): + self._observers: List[Observer] = [] + + def attach(self, observer: Observer): + self._observers.append(observer) + + def detach(self, observer: Observer): + self._observers.remove(observer) + + def notify(self, event: str, data: Any = None): + for observer in self._observers: + observer.update(event, data) + + +class Tile: + def __init__(self, x: int, y: int): + self._x = x + self._y = y + self._wall = False + self._start = False + self._exit = False + + @property + def x(self) -> int: + return self._x + + @property + def y(self) -> int: + return self._y + + @property + def is_wall(self) -> bool: + return self._wall + + @is_wall.setter + def is_wall(self, v: bool): + self._wall = v + + @property + def is_start(self) -> bool: + return self._start + + @is_start.setter + def is_start(self, v: bool): + self._start = v + + @property + def is_exit(self) -> bool: + return self._exit + + @is_exit.setter + def is_exit(self, v: bool): + self._exit = v + + def passable(self) -> bool: + return not self._wall + + def __hash__(self): + return hash((self._x, self._y)) + + def __eq__(self, other): + if not isinstance(other, Tile): + return False + return self._x == other._x and self._y == other._y + + +class Maze: + def __init__(self, w: int, h: int): + self._w = w + self._h = h + self._cells = [[Tile(x, y) for x in range(w)] for y in range(h)] + self._start: Optional[Tile] = None + self._exit: Optional[Tile] = None + + @property + def width(self) -> int: + return self._w + + @property + def height(self) -> int: + return self._h + + @property + def start(self) -> Optional[Tile]: + return self._start + + @property + def exit(self) -> Optional[Tile]: + return self._exit + + def get_cell(self, x: int, y: int) -> Optional[Tile]: + if 0 <= x < self._w and 0 <= y < self._h: + return self._cells[y][x] + return None + + def set_cell(self, x: int, y: int, kind: str): + c = self.get_cell(x, y) + if not c: + return + if kind == 'wall': + c.is_wall = True + elif kind == 'start': + if self._start: + self._start.is_start = False + c.is_start = True + c.is_wall = False + self._start = c + elif kind == 'exit': + if self._exit: + self._exit.is_exit = False + c.is_exit = True + c.is_wall = False + self._exit = c + elif kind == 'path': + c.is_wall = False + + def neighbours(self, cell: Tile) -> List[Tile]: + result = [] + for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + nx, ny = cell.x + dx, cell.y + dy + nb = self.get_cell(nx, ny) + if nb and nb.passable(): + result.append(nb) + return result + + +class MazeLoader(ABC): + @abstractmethod + def load(self, filename: str) -> Maze: + pass + + +class TextMazeLoader(MazeLoader): + def load(self, filename: str) -> Maze: + with open(filename, 'r', encoding='utf-8') as f: + lines = [line.rstrip('\n') for line in f.readlines()] + + h = len(lines) + w = max(len(line) for line in lines) if h else 0 + + start_count = 0 + exit_count = 0 + maze = Maze(w, h) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if ch == '#': + maze.set_cell(x, y, 'wall') + elif ch == 'S': + maze.set_cell(x, y, 'start') + start_count += 1 + elif ch == 'E': + maze.set_cell(x, y, 'exit') + exit_count += 1 + else: + maze.set_cell(x, y, 'path') + + if start_count != 1 or exit_count != 1: + raise ValueError(f"Maze must have one S and one E. Found: S={start_count}, E={exit_count}") + + return maze + + +class PathFinder(ABC): + def __init__(self): + self._visited = 0 + + @abstractmethod + def find(self, maze: Maze, start: Tile, goal: Tile) -> List[Tile]: + pass + + def _reconstruct(self, parent: Dict[Tile, Optional[Tile]], start: Tile, goal: Tile) -> List[Tile]: + path = [] + current = goal + while current is not None: + path.append(current) + current = parent.get(current) + path.reverse() + return path if path and path[0] == start else [] + + @property + def visited_count(self) -> int: + return self._visited + + +class BFS(PathFinder): + def find(self, maze: Maze, start: Tile, goal: Tile) -> List[Tile]: + queue = deque([start]) + parent = {start: None} + visited = {start} + + while queue: + current = queue.popleft() + + if current == goal: + self._visited = len(visited) + return self._reconstruct(parent, start, goal) + + for neighbor in maze.neighbours(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + queue.append(neighbor) + + self._visited = len(visited) + return [] + + +class DFS(PathFinder): + def find(self, maze: Maze, start: Tile, goal: Tile) -> List[Tile]: + stack = [start] + parent = {start: None} + visited = {start} + + while stack: + current = stack.pop() + + if current == goal: + self._visited = len(visited) + return self._reconstruct(parent, start, goal) + + for neighbor in maze.neighbours(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + stack.append(neighbor) + + self._visited = len(visited) + return [] + + +class AStar(PathFinder): + def _heuristic(self, cell: Tile, goal: Tile) -> int: + return abs(cell.x - goal.x) + abs(cell.y - goal.y) + + def find(self, maze: Maze, start: Tile, goal: Tile) -> List[Tile]: + heap = [] + counter = 0 + start_f = self._heuristic(start, goal) + heapq.heappush(heap, (start_f, counter, start)) + counter += 1 + + parent = {} + g_score = {start: 0} + f_score = {start: start_f} + visited = set() + + while heap: + current_f, _, current = heapq.heappop(heap) + visited.add(current) + + if current == goal: + self._visited = len(visited) + return self._reconstruct(parent, start, goal) + + if current_f > f_score.get(current, float('inf')): + continue + + for neighbor in maze.neighbours(current): + tentative_g = g_score[current] + 1 + + if tentative_g < g_score.get(neighbor, float('inf')): + parent[neighbor] = current + g_score[neighbor] = tentative_g + new_f = tentative_g + self._heuristic(neighbor, goal) + f_score[neighbor] = new_f + heapq.heappush(heap, (new_f, counter, neighbor)) + counter += 1 + + self._visited = len(visited) + return [] + + +class MazeSolver(Observable): + def __init__(self, maze: Maze): + super().__init__() + self._maze = maze + self._algorithm: Optional[PathFinder] = None + + def set_algorithm(self, algorithm: PathFinder): + self._algorithm = algorithm + + def solve(self) -> Optional[Dict[str, Any]]: + if not self._algorithm: + raise ValueError("Algorithm not set") + + start_time = time.perf_counter() + path = self._algorithm.find(self._maze, self._maze.start, self._maze.exit) + end_time = time.perf_counter() + + elapsed_ms = (end_time - start_time) * 1000 + + return { + 'time_ms': elapsed_ms, + 'visited': self._algorithm.visited_count, + 'path_length': len(path), + 'path': path + } + + +class Command(ABC): + @abstractmethod + def execute(self) -> bool: + pass + + @abstractmethod + def undo(self) -> bool: + pass + + +class MoveCommand(Command): + def __init__(self, player: 'Player', dx: int, dy: int, maze: Maze): + self._player = player + self._dx = dx + self._dy = dy + self._maze = maze + self._executed = False + + def execute(self) -> bool: + new_x = self._player.position.x + self._dx + new_y = self._player.position.y + self._dy + target = self._maze.get_cell(new_x, new_y) + + if target and target.passable(): + self._player.move_to(target) + self._executed = True + return True + return False + + def undo(self) -> bool: + if self._executed: + self._player.undo() + self._executed = False + return True + return False + + +class Player: + def __init__(self, start_tile: Tile): + self._position = start_tile + self._previous = None + + @property + def position(self) -> Tile: + return self._position + + def move_to(self, tile: Tile): + self._previous = self._position + self._position = tile + + def undo(self): + if self._previous: + self._position, self._previous = self._previous, None + + +class ConsoleView(Observer): + def __init__(self, maze: Maze, player: Optional[Player] = None): + self._maze = maze + self._player = player + self._current_path: List[Tile] = [] + + def update(self, event: str, data: Any = None): + if event == "solving_finished": + self._current_path = data.get('path', []) + self._display_solution(data) + + def _display_solution(self, stats: Dict): + os.system('cls' if os.name == 'nt' else 'clear') + print("=" * (self._maze.width * 2 + 4)) + print("MAZE SOLUTION") + print("=" * (self._maze.width * 2 + 4)) + + for y in range(self._maze.height): + print(" ", end='') + for x in range(self._maze.width): + cell = self._maze.get_cell(x, y) + if cell == self._maze.start: + print('S', end=' ') + elif cell == self._maze.exit: + print('E', end=' ') + elif cell.is_wall: + print('#', end=' ') + elif self._current_path and cell in self._current_path: + print('●', end=' ') + else: + print('.', end=' ') + print() + + print("=" * (self._maze.width * 2 + 4)) + print(f"Time: {stats['time_ms']:.3f} ms") + print(f"Visited: {stats['visited']}") + print(f"Path length: {stats['path_length']}") + + def display_maze(self): + os.system('cls' if os.name == 'nt' else 'clear') + print("=" * (self._maze.width * 2 + 4)) + print("MAZE") + print("=" * (self._maze.width * 2 + 4)) + + for y in range(self._maze.height): + print(" ", end='') + for x in range(self._maze.width): + cell = self._maze.get_cell(x, y) + if self._player and cell == self._player.position: + print('P', end=' ') + elif cell == self._maze.start: + print('S', end=' ') + elif cell == self._maze.exit: + print('E', end=' ') + elif cell.is_wall: + print('#', end=' ') + else: + print('.', end=' ') + print() + + print("=" * (self._maze.width * 2 + 4)) + print("S - start E - exit # - wall . - path P - player") + + +def interactive_mode(maze: Maze): + player = Player(maze.start) + view = ConsoleView(maze, player) + view.display_maze() + + solver = MazeSolver(maze) + solver.attach(view) + + commands_history: List[Command] = [] + + print("\nControls:") + print("H (←) J (↓) K (↑) L (→) - move") + print("U - undo") + print("B - BFS") + print("D - DFS") + print("A - A*") + print("Q - quit") + print("\n" + "=" * 50) + + while True: + cmd = input("\n> ").lower().strip() + + if cmd == 'q': + break + + elif cmd == 'b': + solver.set_algorithm(BFS()) + result = solver.solve() + if result: + print(f"BFS: {result['time_ms']:.3f} ms, visited={result['visited']}, length={result['path_length']}") + + elif cmd == 'd': + solver.set_algorithm(DFS()) + result = solver.solve() + if result: + print(f"DFS: {result['time_ms']:.3f} ms, visited={result['visited']}, length={result['path_length']}") + + elif cmd == 'a': + solver.set_algorithm(AStar()) + result = solver.solve() + if result: + print(f"A*: {result['time_ms']:.3f} ms, visited={result['visited']}, length={result['path_length']}") + + elif cmd in ['h', 'j', 'k', 'l']: + dir_map = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)} + dx, dy = dir_map[cmd] + move = MoveCommand(player, dx, dy, maze) + + if move.execute(): + commands_history.append(move) + view.display_maze() + + if player.position == maze.exit: + print("\n*** YOU ESCAPED! ***") + print(f"Total moves: {len(commands_history)}") + break + else: + print("Blocked!") + + elif cmd == 'u': + if commands_history: + last_command = commands_history.pop() + last_command.undo() + view.display_maze() + print("Undo successful") + else: + print("Nothing to undo") + + else: + print("Unknown command") + + +def main(): + if len(sys.argv) > 1 and sys.argv[1] == 'experiment': + import subprocess + subprocess.run([sys.executable, 'plots.py']) + return + + loader = TextMazeLoader() + + + maze_file = os.path.join(DATA_PATH, "maze1.txt") + + if not os.path.exists(maze_file): + print(f"ERROR: Maze file not found: {maze_file}") + print(f"Please create maze1.txt in: {DATA_PATH}") + return + + maze = loader.load(maze_file) + interactive_mode(maze) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/SokolovEN/docs/data/maze1.txt b/SokolovEN/docs/data/maze1.txt new file mode 100644 index 0000000..07a3ed5 --- /dev/null +++ b/SokolovEN/docs/data/maze1.txt @@ -0,0 +1,10 @@ +########## +#S # +### ##### +# # E# +# # # # ## +# # # +####### # +# # +# ###### # +########## \ No newline at end of file diff --git a/SokolovEN/docs/data/medium.txt b/SokolovEN/docs/data/medium.txt new file mode 100644 index 0000000..c8df775 --- /dev/null +++ b/SokolovEN/docs/data/medium.txt @@ -0,0 +1,48 @@ +################################################## +#S # +# ############################################# # +# # # # +# # ######################################### # # +# # # # # # +# # # ##################################### # # # +# # # # # # # # +# # # # ################################# # # # # +# # # # # # # # # # +# # # # # ############################# # # # # # +# # # # # # # # # # # # +# # # # # # ######################### # # # # # # +# # # # # # # # # # # # # # +# # # # # # # ##################### # # # # # # # +# # # # # # # # # # # # # # # # +# # # # # # # # ################# # # # # # # # # +# # # # # # # # # # # # # # # # # # +# # # # # # # # # ############# # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # ######### # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ##### # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ##### # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # ######### # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # +# # # # # # # # # ############# # # # # # # # # # +# # # # # # # # # # # # # # # # # # +# # # # # # # # ################# # # # # # # # # +# # # # # # # # # # # # # # # # +# # # # # # # ##################### # # # # # # # +# # # # # # # # # # # # # # +# # # # # # ######################### # # # # # # +# # # # # # # # # # # # +# # # # # ############################# # # # # # +# # # # # # # # # # +# # # # ################################# # # # # +# # # # # # # # +# # # ##################################### # # # +# # # # # # +# # ######################################### # # +# # # # +# ############################################# # +# E# +################################################## \ No newline at end of file diff --git a/SokolovEN/docs/data/plots.py b/SokolovEN/docs/data/plots.py new file mode 100644 index 0000000..36e7b5d --- /dev/null +++ b/SokolovEN/docs/data/plots.py @@ -0,0 +1,580 @@ +import csv +import time +import os +import matplotlib.pyplot as plt +import numpy as np +from collections import deque +import heapq + +from maze import DATA_PATH + + + +class Tile: + def __init__(self, x: int, y: int): + self._x = x + self._y = y + self._wall = False + self._start = False + self._exit = False + + @property + def x(self) -> int: + return self._x + + @property + def y(self) -> int: + return self._y + + @property + def is_wall(self) -> bool: + return self._wall + + @is_wall.setter + def is_wall(self, v: bool): + self._wall = v + + @property + def is_start(self) -> bool: + return self._start + + @is_start.setter + def is_start(self, v: bool): + self._start = v + + @property + def is_exit(self) -> bool: + return self._exit + + @is_exit.setter + def is_exit(self, v: bool): + self._exit = v + + def passable(self) -> bool: + return not self._wall + + def __hash__(self): + return hash((self._x, self._y)) + + def __eq__(self, other): + if not isinstance(other, Tile): + return False + return self._x == other._x and self._y == other._y + + +class Maze: + def __init__(self, w: int, h: int): + self._w = w + self._h = h + self._cells = [[Tile(x, y) for x in range(w)] for y in range(h)] + self._start = None + self._exit = None + + @property + def width(self) -> int: + return self._w + + @property + def height(self) -> int: + return self._h + + @property + def start(self): + return self._start + + @property + def exit(self): + return self._exit + + def get_cell(self, x: int, y: int): + if 0 <= x < self._w and 0 <= y < self._h: + return self._cells[y][x] + return None + + def set_cell(self, x: int, y: int, kind: str): + c = self.get_cell(x, y) + if not c: + return + if kind == 'wall': + c.is_wall = True + elif kind == 'start': + if self._start: + self._start.is_start = False + c.is_start = True + c.is_wall = False + self._start = c + elif kind == 'exit': + if self._exit: + self._exit.is_exit = False + c.is_exit = True + c.is_wall = False + self._exit = c + elif kind == 'path': + c.is_wall = False + + def neighbours(self, cell): + result = [] + for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + nx, ny = cell.x + dx, cell.y + dy + nb = self.get_cell(nx, ny) + if nb and nb.passable(): + result.append(nb) + return result + + +class TextMazeLoader: + def load(self, filename: str): + with open(filename, 'r', encoding='utf-8') as f: + lines = [line.rstrip('\n') for line in f.readlines()] + + h = len(lines) + w = max(len(line) for line in lines) if h else 0 + + start_count = 0 + exit_count = 0 + maze = Maze(w, h) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if ch == '#': + maze.set_cell(x, y, 'wall') + elif ch == 'S': + maze.set_cell(x, y, 'start') + start_count += 1 + elif ch == 'E': + maze.set_cell(x, y, 'exit') + exit_count += 1 + else: + maze.set_cell(x, y, 'path') + + if start_count != 1 or exit_count != 1: + raise ValueError(f"Maze must have one S and one E. Found: S={start_count}, E={exit_count}") + + return maze + + +class BFS: + def __init__(self): + self._visited = 0 + + def find(self, maze, start, goal): + from collections import deque + queue = deque([start]) + parent = {start: None} + visited = {start} + + while queue: + current = queue.popleft() + + if current == goal: + self._visited = len(visited) + return self._reconstruct(parent, start, goal) + + for neighbor in maze.neighbours(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + queue.append(neighbor) + + self._visited = len(visited) + return [] + + def _reconstruct(self, parent, start, goal): + path = [] + current = goal + while current is not None: + path.append(current) + current = parent.get(current) + path.reverse() + return path if path and path[0] == start else [] + + @property + def visited_count(self): + return self._visited + + +class DFS: + def __init__(self): + self._visited = 0 + + def find(self, maze, start, goal): + stack = [start] + parent = {start: None} + visited = {start} + + while stack: + current = stack.pop() + + if current == goal: + self._visited = len(visited) + return self._reconstruct(parent, start, goal) + + for neighbor in maze.neighbours(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + stack.append(neighbor) + + self._visited = len(visited) + return [] + + def _reconstruct(self, parent, start, goal): + path = [] + current = goal + while current is not None: + path.append(current) + current = parent.get(current) + path.reverse() + return path if path and path[0] == start else [] + + @property + def visited_count(self): + return self._visited + + +class AStar: + def __init__(self): + self._visited = 0 + + def _heuristic(self, cell, goal): + return abs(cell.x - goal.x) + abs(cell.y - goal.y) + + def find(self, maze, start, goal): + import heapq + heap = [] + counter = 0 + start_f = self._heuristic(start, goal) + heapq.heappush(heap, (start_f, counter, start)) + counter += 1 + + parent = {} + g_score = {start: 0} + f_score = {start: start_f} + visited = set() + + while heap: + current_f, _, current = heapq.heappop(heap) + visited.add(current) + + if current == goal: + self._visited = len(visited) + return self._reconstruct(parent, start, goal) + + if current_f > f_score.get(current, float('inf')): + continue + + for neighbor in maze.neighbours(current): + tentative_g = g_score[current] + 1 + + if tentative_g < g_score.get(neighbor, float('inf')): + parent[neighbor] = current + g_score[neighbor] = tentative_g + new_f = tentative_g + self._heuristic(neighbor, goal) + f_score[neighbor] = new_f + heapq.heappush(heap, (new_f, counter, neighbor)) + counter += 1 + + self._visited = len(visited) + return [] + + def _reconstruct(self, parent, start, goal): + path = [] + current = goal + while current is not None: + path.append(current) + current = parent.get(current) + path.reverse() + return path if path and path[0] == start else [] + + @property + def visited_count(self): + return self._visited + + +class MazeSolver: + def __init__(self, maze): + self._maze = maze + self._algorithm = None + + def set_algorithm(self, algorithm): + self._algorithm = algorithm + + def solve(self): + if not self._algorithm: + raise ValueError("Algorithm not set") + + start_time = time.perf_counter() + path = self._algorithm.find(self._maze, self._maze.start, self._maze.exit) + end_time = time.perf_counter() + + elapsed_ms = (end_time - start_time) * 1000 + + return { + 'time_ms': elapsed_ms, + 'visited': self._algorithm.visited_count, + 'path_length': len(path), + 'path': path + } + + + + + +DATA_PATH = r"C:\Users\user\Desktop\2026-rff_mp\SokolovEN\docs\data" + + +class ExperimentRunner: + def __init__(self): + self.algorithms = { + "BFS": BFS(), + "DFS": DFS(), + "A*": AStar() + } + self.loader = TextMazeLoader() + + def run_benchmark(self, maze_file: str, algorithm: str, runs: int = 5): + try: + maze = self.loader.load(maze_file) + except Exception as e: + return None + + total_time = 0.0 + total_visited = 0 + total_length = 0 + successes = 0 + + for _ in range(runs): + solver = MazeSolver(maze) + solver.set_algorithm(self.algorithms[algorithm]) + result = solver.solve() + + if result and result['path_length'] > 0: + total_time += result['time_ms'] + total_visited += result['visited'] + total_length += result['path_length'] + successes += 1 + + if successes == 0: + return None + + return { + 'time_ms': total_time / successes, + 'visited_cells': total_visited / successes, + 'path_length': total_length / successes, + 'success_rate': successes / runs + } + + def run_all_experiments(self, runs: int = 5): + mazes_list = [ + (os.path.join(DATA_PATH, "small.txt"), "Small (10x10)"), + (os.path.join(DATA_PATH, "medium.txt"), "Medium (50x50)"), + (os.path.join(DATA_PATH, "large.txt"), "Large (100x100)"), + (os.path.join(DATA_PATH, "empty.txt"), "Empty"), + (os.path.join(DATA_PATH, "no_exit.txt"), "No exit") + ] + + results = [] + + + print("running experiments") + + print(f"Data path: {DATA_PATH}") + + + for maze_file, maze_name in mazes_list: + if not os.path.exists(maze_file): + print(f"\n[warn] File not found: {maze_file}") + continue + + print(f"\nTesting: {maze_name}") + + for algo_name in self.algorithms.keys(): + stats = self.run_benchmark(maze_file, algo_name, runs) + + if stats: + print( + f" {algo_name}: time={stats['time_ms']:.3f}ms, visited={stats['visited_cells']:.0f}, length={stats['path_length']:.0f}") + results.append({ + 'maze': maze_name, + 'strategy': algo_name, + 'time_ms': stats['time_ms'], + 'visited_cells': stats['visited_cells'], + 'path_length': stats['path_length'], + 'success_rate': stats['success_rate'] + }) + else: + print(f" {algo_name}: no path found") + results.append({ + 'maze': maze_name, + 'strategy': algo_name, + 'time_ms': -1, + 'visited_cells': -1, + 'path_length': -1, + 'success_rate': 0 + }) + + return results + + +def create_visualizations(results): + valid_results = [r for r in results if r['time_ms'] > 0] + if not valid_results: + print("no valid results for visualization") + return + + mazes = sorted(set(r['maze'] for r in valid_results)) + algorithms = ['BFS', 'DFS', 'A*'] + + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + fig.suptitle('pathfinding algorithms comparison', fontsize=14) + + x = np.arange(len(mazes)) + width = 0.25 + + # Time chart + for i, algo in enumerate(algorithms): + times = [] + for maze in mazes: + val = next((r['time_ms'] for r in valid_results + if r['maze'] == maze and r['strategy'] == algo), 0) + times.append(val) + bars = axes[0].bar(x + i * width, times, width, label=algo, alpha=0.8) + for bar, val in zip(bars, times): + if val > 0: + axes[0].text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.5, + f'{val:.1f}', ha='center', va='bottom', fontsize=7) + + axes[0].set_title('execution Time (ms)') + axes[0].set_ylabel('time (ms)') + axes[0].set_xticks(x + width) + axes[0].set_xticklabels(mazes, rotation=45, ha='right', fontsize=8) + axes[0].legend() + axes[0].grid(alpha=0.3, axis='y') + + # Visited cells chart + for i, algo in enumerate(algorithms): + visited = [] + for maze in mazes: + val = next((r['visited_cells'] for r in valid_results + if r['maze'] == maze and r['strategy'] == algo), 0) + visited.append(val) + bars = axes[1].bar(x + i * width, visited, width, label=algo, alpha=0.8) + for bar, val in zip(bars, visited): + if val > 0: + axes[1].text(bar.get_x() + bar.get_width() / 2, bar.get_height(), + f'{val:.0f}', ha='center', va='bottom', fontsize=7) + + axes[1].set_title('visited Cells') + axes[1].set_ylabel('count') + axes[1].set_xticks(x + width) + axes[1].set_xticklabels(mazes, rotation=45, ha='right', fontsize=8) + axes[1].legend() + axes[1].grid(alpha=0.3, axis='y') + + # Path length chart + for i, algo in enumerate(algorithms): + lengths = [] + for maze in mazes: + val = next((r['path_length'] for r in valid_results + if r['maze'] == maze and r['strategy'] == algo), 0) + lengths.append(val) + bars = axes[2].bar(x + i * width, lengths, width, label=algo, alpha=0.8) + for bar, val in zip(bars, lengths): + if val > 0: + axes[2].text(bar.get_x() + bar.get_width() / 2, bar.get_height(), + f'{val:.0f}', ha='center', va='bottom', fontsize=7) + + axes[2].set_title('path Length') + axes[2].set_ylabel('steps') + axes[2].set_xticks(x + width) + axes[2].set_xticklabels(mazes, rotation=45, ha='right', fontsize=8) + axes[2].legend() + axes[2].grid(alpha=0.3, axis='y') + + plt.tight_layout() + + output_path = os.path.join(DATA_PATH, 'experiment_results.png') + plt.savefig(output_path, dpi=150, bbox_inches='tight') + print(f"\nPlot saved to: {output_path}") + plt.show() + + +def save_results_to_csv(results, filename='experiment_results.csv'): + if not results: + return + + filepath = os.path.join(DATA_PATH, filename) + with open(filepath, 'w', newline='', encoding='utf-8') as f: + fieldnames = ['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length', 'success_rate'] + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(results) + + print(f"Results saved to: {filepath}") + + +def analyze_efficiency(results): + valid_results = [r for r in results if r['time_ms'] > 0] + if not valid_results: + print("no valid results for analysis") + return + + algo_stats = {} + for algo in ['BFS', 'DFS', 'A*']: + algo_data = [r for r in valid_results if r['strategy'] == algo] + if algo_data: + algo_stats[algo] = { + 'avg_time': sum(r['time_ms'] for r in algo_data) / len(algo_data), + 'avg_visited': sum(r['visited_cells'] for r in algo_data) / len(algo_data), + 'avg_length': sum(r['path_length'] for r in algo_data) / len(algo_data) + } + + + print("average values across all mazes") + print(f"{'Algorithm':<12} {'Time (ms)':<15} {'Visited':<15} {'Path length':<15}") + + for algo, stats in algo_stats.items(): + print(f"{algo:<12} {stats['avg_time']:<15.3f} {stats['avg_visited']:<15.1f} {stats['avg_length']:<15.1f}") + + fastest = min(algo_stats.items(), key=lambda x: x[1]['avg_time']) + optimal = min(algo_stats.items(), key=lambda x: x[1]['avg_length']) + efficient = min(algo_stats.items(), key=lambda x: x[1]['avg_visited']) + + print("conclusions:") + print(f" fastest algorithm: {fastest[0]} ({fastest[1]['avg_time']:.3f} ms avg)") + print(f" optimal path: {optimal[0]} ({optimal[1]['avg_length']:.1f} steps avg)") + print(f" most efficient (fewest visits): {efficient[0]} ({efficient[1]['avg_visited']:.0f} cells avg)") + print("=" * 70) + + +def main(): + + + if not os.path.exists(DATA_PATH): + print(f"\nerr: directory not found: {DATA_PATH}") + print("please create the directory and place maze files there.") + print("\nexpected structure:") + print(f" {DATA_PATH}/") + print(" ├── small.txt") + print(" ├── medium.txt") + print(" ├── large.txt") + print(" ├── empty.txt") + print(" └── no_exit.txt") + return + + runner = ExperimentRunner() + results = runner.run_all_experiments(runs=5) + + if not results: + print("\nNo results. Check if maze files exist in:", DATA_PATH) + return + + save_results_to_csv(results) + analyze_efficiency(results) + create_visualizations(results) + + + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/SokolovEN/docs/data/small.txt b/SokolovEN/docs/data/small.txt new file mode 100644 index 0000000..e21dcdf --- /dev/null +++ b/SokolovEN/docs/data/small.txt @@ -0,0 +1,10 @@ +########## +#S # +### ##### +# # E# +# # # # ## +# # # +####### # +# # +# ###### # +########## \ No newline at end of file