From 380b683b37ab32cd111e064c2393ff41a5d313a4 Mon Sep 17 00:00:00 2001 From: Muihiro Date: Sun, 24 May 2026 20:48:03 +0300 Subject: [PATCH] [2] for 2-nd ex --- fomichevks/docs/data/empty.txt | 49 ++ fomichevks/docs/data/experiment_results.csv | 13 + fomichevks/docs/data/experiment_results.png | Bin 0 -> 82357 bytes fomichevks/docs/data/large.txt | 54 ++ fomichevks/docs/data/maze.py | 532 ++++++++++++++++++ fomichevks/docs/data/maze1.txt | 10 + fomichevks/docs/data/medium.txt | 48 ++ fomichevks/docs/data/plots.py | 580 ++++++++++++++++++++ fomichevks/docs/data/small.txt | 10 + 9 files changed, 1296 insertions(+) create mode 100644 fomichevks/docs/data/empty.txt create mode 100644 fomichevks/docs/data/experiment_results.csv create mode 100644 fomichevks/docs/data/experiment_results.png create mode 100644 fomichevks/docs/data/large.txt create mode 100644 fomichevks/docs/data/maze.py create mode 100644 fomichevks/docs/data/maze1.txt create mode 100644 fomichevks/docs/data/medium.txt create mode 100644 fomichevks/docs/data/plots.py create mode 100644 fomichevks/docs/data/small.txt diff --git a/fomichevks/docs/data/empty.txt b/fomichevks/docs/data/empty.txt new file mode 100644 index 0000000..6d0a249 --- /dev/null +++ b/fomichevks/docs/data/empty.txt @@ -0,0 +1,49 @@ +######################################## +#S # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# E# +######################################## \ No newline at end of file diff --git a/fomichevks/docs/data/experiment_results.csv b/fomichevks/docs/data/experiment_results.csv new file mode 100644 index 0000000..b9d6ce1 --- /dev/null +++ b/fomichevks/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/fomichevks/docs/data/experiment_results.png b/fomichevks/docs/data/experiment_results.png new file mode 100644 index 0000000000000000000000000000000000000000..c96bc8d406a765117208eafc1a8ad069c3d18654 GIT binary patch literal 82357 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

s&zfGydngz2RI=T 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 NwRY&J#i|~;_&-i237P-^ literal 0 HcmV?d00001 diff --git a/fomichevks/docs/data/large.txt b/fomichevks/docs/data/large.txt new file mode 100644 index 0000000..90a84ad --- /dev/null +++ b/fomichevks/docs/data/large.txt @@ -0,0 +1,54 @@ +#################################################################################################### +#S # +# ################################################################################################ # +# # # # +# # ############################################################################################ # # +# # # # # # +# # # ######################################################################################## # # # +# # # # # # # # +# # # # #################################################################################### # # # # +# # # # # # # # # # +# # # # # ################################################################################ # # # # # +# # # # # # # # # # # # +# # # # # # ############################################################################ # # # # # # +# # # # # # # # # # # # # # +# # # # # # # ######################################################################## # # # # # # # +# # # # # # # # # # # # # # # # +# # # # # # # # #################################################################### # # # # # # # # +# # # # # # # # # # # # # # # # # # +# # # # # # # # # ################################################################ # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # ############################################################ # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ######################################################## # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # #################################################### # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # ################################################ # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # ############################################ # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # ######################################## # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # #################################### # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # ################################ # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # ############################ # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # ######################## # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # #################### # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # ################ # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # ############ # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # ######## # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # #### # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #E# +#################################################################################################### \ No newline at end of file diff --git a/fomichevks/docs/data/maze.py b/fomichevks/docs/data/maze.py new file mode 100644 index 0000000..3581713 --- /dev/null +++ b/fomichevks/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\Kirill\2026-rff_mp\fomichevks\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/fomichevks/docs/data/maze1.txt b/fomichevks/docs/data/maze1.txt new file mode 100644 index 0000000..07a3ed5 --- /dev/null +++ b/fomichevks/docs/data/maze1.txt @@ -0,0 +1,10 @@ +########## +#S # +### ##### +# # E# +# # # # ## +# # # +####### # +# # +# ###### # +########## \ No newline at end of file diff --git a/fomichevks/docs/data/medium.txt b/fomichevks/docs/data/medium.txt new file mode 100644 index 0000000..c8df775 --- /dev/null +++ b/fomichevks/docs/data/medium.txt @@ -0,0 +1,48 @@ +################################################## +#S # +# ############################################# # +# # # # +# # ######################################### # # +# # # # # # +# # # ##################################### # # # +# # # # # # # # +# # # # ################################# # # # # +# # # # # # # # # # +# # # # # ############################# # # # # # +# # # # # # # # # # # # +# # # # # # ######################### # # # # # # +# # # # # # # # # # # # # # +# # # # # # # ##################### # # # # # # # +# # # # # # # # # # # # # # # # +# # # # # # # # ################# # # # # # # # # +# # # # # # # # # # # # # # # # # # +# # # # # # # # # ############# # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # ######### # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ##### # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ##### # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # ######### # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # +# # # # # # # # # ############# # # # # # # # # # +# # # # # # # # # # # # # # # # # # +# # # # # # # # ################# # # # # # # # # +# # # # # # # # # # # # # # # # +# # # # # # # ##################### # # # # # # # +# # # # # # # # # # # # # # +# # # # # # ######################### # # # # # # +# # # # # # # # # # # # +# # # # # ############################# # # # # # +# # # # # # # # # # +# # # # ################################# # # # # +# # # # # # # # +# # # ##################################### # # # +# # # # # # +# # ######################################### # # +# # # # +# ############################################# # +# E# +################################################## \ No newline at end of file diff --git a/fomichevks/docs/data/plots.py b/fomichevks/docs/data/plots.py new file mode 100644 index 0000000..ad41b04 --- /dev/null +++ b/fomichevks/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\Kirill\2026-rff_mp\fomichevks\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/fomichevks/docs/data/small.txt b/fomichevks/docs/data/small.txt new file mode 100644 index 0000000..e21dcdf --- /dev/null +++ b/fomichevks/docs/data/small.txt @@ -0,0 +1,10 @@ +########## +#S # +### ##### +# # E# +# # # # ## +# # # +####### # +# # +# ###### # +########## \ No newline at end of file