[2] lab2 #331

Merged
IvanBoy merged 17 commits from borisovmi/2026-rff_mp:lab2 into develop 2026-05-30 11:45:52 +00:00
10 changed files with 1223 additions and 0 deletions

View File

@ -0,0 +1,725 @@
from abc import ABC, abstractclassmethod
from collections import deque
import heapq
import time
import os
import time
import csv
import random
class Cell:
def __init__(self, x, y):
self.x = x
self.y = y
self.isWall = False
self.isStart = False
self.isExit = False
def __eq__(self, other):
if other is None:
return False
return self.x == other.x and self.y == other.y
def __lt__(self, other):
if other is None:
return False
return (self.x, self.y) < (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
def __repr__(self):
return f"Cell({self.x}, {self.y})"
def isPassable(self):
return not self.isWall
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start = None
self.exit = None
def getCell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[x][y]
return None
def getNeighbors(self, cell):
neighbors = []
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dx, dy in directions:
neighbor = self.getCell(cell.x + dx, cell.y + dy)
if neighbor and neighbor.isPassable():
neighbors.append(neighbor)
return neighbors
def setStart(self, x, y):
cell = self.getCell(x, y)
if cell:
cell.isStart = True
self.start = cell
def setExit(self, x, y):
cell = self.getCell(x, y)
if cell:
cell.isExit = True
self.exit = cell
class MazeBuilder(ABC):
def buildFromFile(self, filename):
pass
class TextileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
lines = [line.rstrip('\n\r') for line in lines]
height = len(lines)
width = len(lines[0]) if height > 0 else 0
for line in lines:
if len(line) != width:
raise ValueError("все строки одинаковой длины")
maze = Maze(width, height)
for y in range(height):
for x in range(width):
char = lines[y][x]
cell = maze.getCell(x, y)
if char == '#':
cell.isWall = True
elif char == ' ':
cell.isWall = False
elif char == 's':
cell.isWall = False
cell.isStart = True
maze.start = cell
elif char == 'e':
cell.isWall = False
cell.isExit = True
maze.exit = cell
else:
raise ValueError(f"неизв сим")
if maze.start is None:
raise ValueError("в лабиринте не найден старт")
if maze.exit is None:
raise ValueError("в лабиринте не найден выход")
return maze
class PathFindingStrategy:
def findPath(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
if exit is None:
return []
queue = deque([start])
visited = {start}
parent = {start: None}
while queue:
current = queue.popleft()
if current == exit:
return self._reconstruct_path(parent, start, exit)
for neighbor in maze.getNeighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
return []
def _reconstruct_path(self, parent, start, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = parent[current]
path.reverse()
return path
class DFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
if exit is None:
return []
stack = [start]
visited = {start}
parent = {start: None}
while stack:
current = stack.pop()
if current == exit:
return self._reconstruct_path(parent, start, exit)
for neighbor in maze.getNeighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
stack.append(neighbor)
return []
def _reconstruct_path(self, parent, start, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = parent[current]
path.reverse()
return path
class AStrategy(PathFindingStrategy):
def _heuristic(self, cell, exit):
if exit is None:
return 0
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
def findPath(self, maze, start, exit):
if exit is None:
return []
open_set = []
heapq.heappush(open_set, (0, start))
came_from = {start: None}
g_score = {start: 0}
while open_set:
current = heapq.heappop(open_set)[1]
if current == exit:
return self._reconstruct_path(came_from, start, exit)
for neighbor in maze.getNeighbors(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 = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score, neighbor))
return []
def _reconstruct_path(self, came_from, start, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = came_from[current]
path.reverse()
return path
class SearchStats:
def __init__(self, time_ms=0, visited_cells=0, path_length=0):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Время: {self.time_ms:.3f} мс | Посещено: {self.visited_cells} | Длина пути: {self.path_length}"
class MazeSolver:
def __init__(self, maze):
self.maze = maze
self.strategy = None
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
if self.strategy is None:
raise ValueError("Стратегия не установлена")
start_time = time.perf_counter()
path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
elapsed_ms = (end_time - start_time) * 1000
stats = SearchStats(
time_ms=elapsed_ms,
visited_cells=len(path),
path_length=len(path)
)
return path, stats
class Observer:
def update(self, event):
pass
class ConsoleView(Observer):
def render(self, maze, player_position=None, path=None):
"""отрисовка"""
os.system('cls' if os.name == 'nt' else 'clear')
path_set = set(path) if path else set()
for y in range(maze.height):
for x in range(maze.width):
cell = maze.getCell(x, y)
if player_position and cell == player_position:
print('P', end='')
elif cell == maze.start:
print('S', end='')
elif cell == maze.exit:
print('E', end='')
elif cell in path_set:
print('.', end='')
elif cell.isWall:
print('#', end='')
else:
print(' ', end='')
print()
def update(self, event):
if event['type'] == 'path_found':
print(f"длина пути {len(event['path'])}")
self.render(event['maze'], path=event['path'])
elif event['type'] == 'move':
print(f"шаг {event['step']}")
self.render(event['maze'], event['player'], event['path'])
elif event['type'] == 'maze_loaded':
print("перегрузка")
self.render(event['maze'])
class ObservableMazeSolver:
def __init__(self, maze):
self.maze = maze
self.strategy = None
self.observers = []
def attach(self, observer):
self.observers.append(observer)
def notify(self, event):
for observer in self.observers:
observer.update(event)
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
if self.strategy is None:
raise ValueError("")
path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)
self.notify({
'type': 'path_found',
'maze': self.maze,
'path': path
})
return path
class Player:
def __init__(self, start_cell):
self.currentCell = start_cell
self.previousCell = None
def moveTo(self, cell):
self.previousCell = self.currentCell
self.currentCell = cell
def undoMove(self):
if self.previousCell:
self.currentCell, self.previousCell = self.previousCell, None
return True
return False
class Command:
def execute(self):
pass
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player, direction, maze):
self.player = player
self.dx, self.dy = direction
self.maze = maze
self.executed = False
def execute(self):
new_x = self.player.currentCell.x + self.dx
new_y = self.player.currentCell.y + self.dy
new_cell = self.maze.getCell(new_x, new_y)
if new_cell and new_cell.isPassable():
self.player.moveTo(new_cell)
self.executed = True
return True
return False
def undo(self):
if self.executed:
self.player.undoMove()
self.executed = False
return True
return False
def clear_console():
os.system('cls' if os.name == 'nt' else 'clear')
def render_maze_with_player(maze, player, path=None):
path_set = set(path) if path else set()
for y in range(maze.height):
for x in range(maze.width):
cell = maze.getCell(x, y)
if cell == player.currentCell:
print('P', end='')
elif cell == maze.start:
print('S', end='')
elif cell == maze.exit:
print('E', end='')
elif cell in path_set:
print('.', end='')
elif cell.isWall:
print('#', end='')
else:
print(' ', end='')
print()
def run_game(maze, path=None):
player = Player(maze.start)
history = []
directions = {
'w': (0, -1),
's': (0, 1),
'a': (-1, 0),
'd': (1, 0)
}
print(" W/A/S/D - движение, U - отмена, Q - выход")
if path:
print(f"мин путь {len(path)} шагов")
while True:
print()
render_maze_with_player(maze, player, path)
if player.currentCell == maze.exit:
print("\n*** выход ***")
break
key = input("\n> ").lower()
if key == 'q':
print("выход из игры")
break
elif key == 'u':
if history:
cmd = history.pop()
cmd.undo()
print("отмена хода")
else:
print("нет ходов")
elif key in directions:
cmd = MoveCommand(player, directions[key], maze)
if cmd.execute():
history.append(cmd)
else:
print("стена")
else:
print("неизвестно")
def generate_empty_maze(width, height):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.getCell(x, y).isWall = False
maze.setStart(0, 0)
maze.setExit(width-1, height-1)
return maze
def generate_maze_with_walls(width, height, wall_probability=0.3):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < wall_probability:
maze.getCell(x, y).isWall = True
else:
maze.getCell(x, y).isWall = False
maze.getCell(0, 0).isWall = False
maze.getCell(width-1, height-1).isWall = False
maze.setStart(0, 0)
maze.setExit(width-1, height-1)
return maze
def generate_maze_no_exit(width, height):
maze = generate_maze_with_walls(width, height, 0.3)
exit_cell = maze.getCell(width-1, height-1)
exit_cell.isWall = True
maze.exit = None
return maze
def save_maze_to_file(maze, filename):
with open(filename, 'w') as f:
for y in range(maze.height):
for x in range(maze.width):
cell = maze.getCell(x, y)
if cell == maze.start:
f.write('s')
elif cell == maze.exit:
f.write('e')
elif cell.isWall:
f.write('#')
else:
f.write(' ')
f.write('\n')
def create_test_mazes():
mazes = []
small = generate_maze_with_walls(10, 10, 0.2)
save_maze_to_file(small, "maze_small.txt")
mazes.append(('маленький (10x10)', small))
medium = generate_maze_with_walls(50, 50, 0.3)
save_maze_to_file(medium, "maze_medium.txt")
mazes.append(('средний (50x50)', medium))
large = generate_maze_with_walls(100, 100, 0.3)
save_maze_to_file(large, "maze_large.txt")
mazes.append(('большой (100x100)', large))
empty = generate_empty_maze(50, 50)
save_maze_to_file(empty, "maze_empty.txt")
mazes.append(('пустой (50x50)', empty))
no_exit = generate_maze_no_exit(20, 20)
save_maze_to_file(no_exit, "maze_no_exit.txt")
mazes.append(('без выхода (20x20)', no_exit))
return mazes
def run_experiment(maze, strategy, name, repeats=5):
times = []
visited_counts = []
path_lengths = []
for _ in range(repeats):
solver = MazeSolver(maze)
solver.setStrategy(strategy())
start_time = time.perf_counter()
path, stats = solver.solve()
end_time = time.perf_counter()
times.append((end_time - start_time) * 1000)
visited_counts.append(len(path) if path else 0)
path_lengths.append(len(path) if path else 0)
return {
'лабиринт': name,
'стратегия': strategy.__name__.replace('Strategy', ''),
'время_ср': sum(times) / repeats,
'время_мин': min(times),
'время_макс': max(times),
'посещено_ср': sum(visited_counts) / repeats,
'длина_пути_ср': sum(path_lengths) / repeats,
'путь_найден': path is not None and len(path) > 0
}
def run_all_experiments():
strategies = [BFSStrategy, DFSStrategy, AStrategy]
results = []
mazes = create_test_mazes()
for maze_name, maze in mazes:
for strategy in strategies:
print(f" тест {strategy.__name__}...", end=" ", flush=True)
result = run_experiment(maze, strategy, maze_name)
results.append(result)
print(f"время={result['время_ср']:.2f}мс, путь={result['длина_пути_ср']:.0f}")
save_results_to_csv(results)
return results
def save_results_to_csv(results):
filename = "resultslab.csv"
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=[
'лабиринт', 'стратегия', 'время_ср', 'время_мин', 'время_макс',
'посещено_ср', 'длина_пути_ср', 'путь_найден'
])
writer.writeheader()
writer.writerows(results)
def plot_results(results):
try:
import matplotlib.pyplot as plt
import numpy as np
labyrinths = list(set(r['лабиринт'] for r in results))
strategies = ['BFS', 'DFS', 'A']
n_rows = 3
n_cols = 2
fig, axes = plt.subplots(n_rows, n_cols, figsize=(14, 12))
axes = axes.flatten()
for idx, lab in enumerate(labyrinths):
ax = axes[idx]
times = []
for strat in strategies:
for r in results:
if r['лабиринт'] == lab and r['стратегия'] == strat:
times.append(r['время_ср'])
break
x = np.arange(len(strategies))
bars = ax.bar(x, times, color=['#1a5632', '#0e5fb4', '#051f45'])
ax.set_title(f'{lab}')
ax.set_xticks(x)
ax.set_xticklabels(strategies)
ax.set_ylabel('Время (мс)')
for bar, t in zip(bars, times):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f'{t:.1f}', ha='center', va='bottom', fontsize=8)
if len(labyrinths) < len(axes):
axes[-1].set_visible(False)
plt.tight_layout()
plt.savefig('maze_time_comparison.png', dpi=150)
plt.show()
plt.figure(figsize=(10, 6))
colors = ['#d8d262', '#0e5fb4', '#ed254e']
for idx, strat in enumerate(strategies):
lengths = []
for lab in labyrinths:
for r in results:
if r['лабиринт'] == lab and r['стратегия'] == strat:
lengths.append(r['длина_пути_ср'])
break
plt.plot(labyrinths, lengths, marker='o', label=strat, color=colors[idx]) # добавьте color
plt.xlabel('Лабиринт')
plt.ylabel('Длина пути ')
plt.title('Сравнение длины найденного пути')
plt.legend()
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('maze_path_length.png', dpi=150)
plt.show()
except ImportError:
print("")
def print_analysis(results):
strat_data = {}
for r in results:
strat = r['стратегия']
if strat not in strat_data:
strat_data[strat] = {'time': [], 'visited': [], 'labyrinth': []}
strat_data[strat]['time'].append(r['время_ср'])
strat_data[strat]['visited'].append(r['посещено_ср'])
strat_data[strat]['labyrinth'].append(r['лабиринт'])
for strat, data in strat_data.items():
avg_time = sum(data['time']) / len(data['time'])
print(f" {strat}: среднее время {avg_time:.2f} мс")
print(" BFS медленный на большом лабсамый короткий путить находит")
print(" DFS быстрый, но не всегда самый короткий")
print(" A быстрый и находит самый короткий путь")
print(" без выхода лаб. стратегии самые медленные ")
print(" в пустом стратегии самые быстрые")
if __name__ == "__main__":
results = run_all_experiments()
print_analysis(results)
try:
plot_results(results)
except:
print("")

View File

@ -0,0 +1,50 @@
s
e

View File

@ -0,0 +1,100 @@
s## # # # # # # # ### # ## # # # # # # ### # # # # # ###
# ## # ## ### ## # # ## ## # # # ### ## ## #
# ## # # # # # ## ## # # # ## # ## # # # # ## # # # #
## # # # ##### # # # # # ## # ## # # # # # # # # # # # # #
# ### ## # # # # ## ## ## # # # # # ## #
# # # # # # ## # ### ## ## # ##### # # # # # ### ## # ### # # #
# # # ## ### # # # # # ### # # # ## ##### # #
# # ### # # ## ### # # # # # ## # ## ## # ## # ## ## # #
# # # ## # # # # ## # # # # # # # # # # # ### ## # # #
# # # # # ## # # # # # ## ## ### ###### ## ## ### #
## ## # # # # # # ### # # # ### # ## #
# # ###### # # # ## # # # ## # # # ## #### # # #
## # # # ### # # # # # #### # # # ## # # # #
# # # # ### ## ## # # # ## # # # # # ### # # # ### # # #### # ##
# ## # # # # # # # # # # # # # # ## ### ## # # ## #
### # # # # ## ### # # ## # # # ## # ## # ## #
### # # # # ### # # # # # ## # # # # # ## # # ## #
# # # # ## # # ### # ## ## # ### # # ### ## #
### ## # ## # ## # # # # # # # # # # # ####### ##
## ## # # # # ## # # # ## ### ### # # # ### # # # # ## # ###
### #### ### # # # # ## ## # #### # # # # # # ## # #
### # ## ## # ## ## ## # # # ## # # ## # ## # #
# # # # # # # #### ## # # #### ## # ## ## # # #
# ## # ## # # # # ### # ## # ## # # ## # # # ## # # #### # #
# # ## # # # # # # # # # ## ## # # # ### # #
# # # # # # ## # # # ### # ## # # # # ## # # # # # #
# # # ### # # # # # ## ## # # ## # # ## # # #
## ## ### # # ## # # # # # # # # # # # # # ## ## # # # #
# # # # # # ## # # # # # # # ##
# # # # # # ## # ## # ## # # # ## ## ## ## ### # # # # # #
# # # # # # # #### # ## # # # # ## ## # # # ## # #
## # # # # # # # ###### # # ### # # ## # # # # ### ##
# # ## # # # # #### # #### # # # ## ## ## #
# # # # # # # ## # # # # # ### ### # # # # # # #
# # # # ## # # # # # ## # ## # # ## # ## ### # #
#### # # # # ## # # # # # ## ### # # # # ### # ## #
# # # # ## ## # # # # # # # # # # # # # # # ## ## # # ##
# # # # # # ## # # # ## ## # # # # # # ## #
# # ## ## ### ## # # ## # # # ## # # # # # # # # #
## ## # # # # # # ## ### # # # ## # # ## # ### # ### ##
## ## # # # # # # # # # ## # # ## # ### # # # #
## # # ## ## ## # # ## # # ## # # # # ## # #
## # ## # ## ## # # # # # # # # # # ### # # # # # ## # #
# ## ## # # # # # #### ## # # # # # # # # #
# # # # # # # # ## # # # # # ### # # # # #### ## ### ####
# ## # # #### # # # # #### # # # # # ### # # ### # ## ##
## # ## # ## # # # # # # ### # # # # # ## # # #
# # # # ## # # # ### #### ## # # # # ## ## ## #
## # ## # ## # # # # ## # # # # # # # # # # # ## #
# # ## # # # ### # ## # ## # # ### # # # # ### #
# # ## # ## #### # # # # # # # ## ## # ## ###
# ## # # ## ## # # ## # # # ### # ## # # # # # # # #
# # ## # # ## # # # # # # # # # # # ## # ### ##
# ## # # # # # # # ## # # ## ## ## # # ## ## # # ## ### ### ####
### # # # # # # # ## # # # ## # ## # # # ## # # ## # # # #
# # # # # # # # ## ## ### # # # # # # ## # # # #
# # # # ## # ### # # # # ## # # # ### # ## ## # # # ##
# # # # # ## # ## # # # ### # ## ## # # # # # # # #
## ### ## # # # # ## # # # #### # #### # # ## # ## #
## ## ## # # # # ## # # ## ## ### # # # # # ### # ### ##
# # ### # # # # # # # # # ## # ### # # # ### ## ##
# # ## # ## # ## ## # # # ## ## # ## # # ##
# # ### # ## ## # # ### # # # # # # ## ## # ##
# # #### # # # # # # # ### # # # # # # ## # ### # # ### ###
# # ## # # ##### # ## # # ## ## # # # ## # # # ## ##
# ### # ## # # ###### ### # ## # ## # # ## # # # # ## ## # ## #
# # # # # # # # ## ## # # # ## # # ## ## # # # # #
## # ## ## # ### # # # # # # # # ## # # # # # ###### # ##
## # # # # ### # # ### ## # # ## # # # # ##### #
# # ### # # # # # # ## #### # # ### # # # ## # ##
# # ## ## # ## # #### # ## # # # # # ## ## # # # # ## ## #
## # # # # ## # # ## # # # ## # # ## # # # # # #
# # # ## # # # # ## # ## # # # # # ## # # ##
# ## ## # # # # # # ### # ## # # # # # # # # #
# # ## # # # # # # # ##### ## ## ### # # ###
# # # # # # ## ## ## # # # # # # ## # ##### # ##
# # ## # # # ## # # #### # ## # # # # # ## # # #
# # # # # ## ## # ## # # # # # #### # ##
## # # # # ## # ## ## ## # # ## # # # ## # ## # # #
## # # # # # # # ## ### # # # ## # # ## #
### # ## # # # ## ## # ### # # # # # ### # # # ##### #
## # # ## # ## # # # # # ## # # # ## ####### ### # #
#### # # # # # # # # # # ## # ## # # ### # ## # # #
# # # # # # # # # # ## # # ## # # # # ## # ### # #
# # # # #### ## ## # # # # ## # # # # # # ### ### # ##
#### # ## # # # ### ## # ## ## # ## # # ## # #
# # ## # # # # # # # # ## # # ## # # ### # ##
# # # # ## ## # # ## # # # # ## # ## ##
### ## # # # ## ## ## ## # # # ## ## # # # # # # # # # ## # # #
## # # # # # # # # # # ## #### # # ## ### ### ## # # #
# # ##### # # # ## ## # # ## ## # # ## # #### ##### # # ## ##
# # # # # # ## # # # # # # # # # # ## #
## ### # # ## ## # ## ## ## # # ## # # ### # # ## ### #
# # # ## # ## # # # ## # # # # ## # # # #
# # # # # #### # # # ## # # # ## # # # # # # # # # #
# # # ## # # ## # # ### # # ## # # ## # # ##
# # # ## # # ### # # # # # ## ## ##
# # # # ### # # # # # # # # # # # ## ## # ### # ## # # # #
# ###### # # ## ## ## # ### # # # ## # # # #####
# ## # # # # ## # # # # # # # # #### # # e

View File

@ -0,0 +1,50 @@
s # ## # # ### # ## # # #
## # # ## ## # # # #
# # ## # # # # ##
### # # # # # # ## ## # ## # #
# # # ## # # # # ## # #
# # # # ## # ## # # #
## # # # # # # # ## # #
# ## # # # ## # ## # # # # #
## # # # # ## # # ## # ##
# # # # # ## # # ## # # #
# # # # ## # # # # ## # ## # #
# ## # # # # # # # ## ##
## # ## ### # # # ## # ##
##### ### # # # # ## # # # #
# # ### ## # ## ## #### ###
## # # # # ### # # ## # #
# # ## # # # # # # ##
## # # # ### # ## # # ## # # ## ##
# #### # # # # # ### # ##
# ## # ## # # ## ### ## ### #
# # ### ## # # # ##
# # ## # # # # # # #
# ## # ### #### # ## # ### ## # #
# # ## # # # # # # #
# # ##### # # # # # # # ## # ##
## # # # # ## ## # ## ## #
# # # # # # # ## # # #
## # # # ## # # ## # #
# ### # # # # # # # # # ###
### # # # # # ### # # # # # ##
# # # # # ## # # # # # ##
# ## ## ## # # # # # # ## #
# #### # # # ## # ## #
## # # # # ## # # # # #
## # ## ## # # # ## # # ## #
# # # # # # # # # # ### # # #
# # ## # # # # # ###
# # #### ##
# # ## # # ## ### # # ##
##### # # # # # # # # # #
## # # # # # #
# # ## ## # # # # ## ### # #
# # ### ## ### ### # ## # #
## # ### # ## # # # #
# # # # # ## # # # # #
# # ## # # ## ### # # # #
# # # # # ## # ### #
## # # ## # # #
# # ## # ### # ### # ## # ## # ##
# # # # # # # ## # # e

View File

@ -0,0 +1,20 @@
s ## ###
# # # # # ##
# # # # #
# # ##
# # # # #
# # ### # #
# # # # #
# # ## ## ###
# ## #
# # ###
# # # # #
### # #
# # # #
## # # # #
## # # # # ##
# # #
# #
# # # #
# # #
# # # # ## #

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -0,0 +1,10 @@
s #
#
# #
# #
# #
#
# #
#
# # e

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -0,0 +1,252 @@
# Отчёт: Задание 2 — Поиск выхода из лабиринта
## Цель работы
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов
## Выбранные паттерны и их обоснование
### Builder
Для загрузки лабиринта из файла был использован паттерн Builder.
Создан интерфейс:
class MazeBuilder():
и его реализация:
class TextFileMazeBuilder(MazeBuilder):
Преимущества использования Builder:
пользоватеь не знает деталей создания лабиринта;
можно добавить новые форматы (JSON, XML, бинарный);
код загрузки изолирован от остальной программы.
### Strategy
Для алгоритмов поиска пути использован паттерн Strategy
Создан общий интерфейс:
class PathFindingStrategy():
Реализованы стратегии:
BFSStrategy;
DFSStrategy;
AStrategy;
Каждая стратегия реализует собственный алгоритм поиска пути по правилам.
Преимущества паттерна:
алгоритмы можно менять во время выполнения;
код MazeSolver не зависит от конкретного алгоритма;
новые алгоритмы можно добавлять без изменения существующего кода.
### Observer
Для уведомления интерфейса о событиях использован паттерн Observer
Создан интерфейс:
class Observer():
и реализация:
class ConsoleView(Observer):
MazeSolver хранит список наблюдателей и уведомляет их о событиях:
начало поиска;
окончание поиска;
перемещение игрока.
Преимущества:
логика интерфейса отделена от логики поиска;
можно легко добавить графический интерфейс;
### Command
Для пошагового перемещения игрока использован паттерн Command.
Создан интерфейс:
class Command():
и реализация:
class MoveCommand(Command):
Каждая команда умеет:
execute() — выполнить действие;
undo() — отменить действие
Преимущества:
поддержка undo;
возможность расширения системы команд
## Листинги ключевых классов
### Паттерн Strategy
class PathFindingStrategy:
def findPath(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
if exit is None:
return []
queue = deque([start])
visited = {start}
parent = {start: None}
while queue:
current = queue.popleft()
if current == exit:
return self._reconstruct_path(parent, start, exit)
for neighbor in maze.getNeighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
return []
class AStrategy(PathFindingStrategy):
def _heuristic(self, cell, exit):
if exit is None:
return 0
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
def findPath(self, maze, start, exit):
if exit is None:
return []
open_set = []
heapq.heappush(open_set, (0, start))
came_from = {start: None}
g_score = {start: 0}
while open_set:
current = heapq.heappop(open_set)[1]
if current == exit:
return self._reconstruct_path(came_from, start, exit)
for neighbor in maze.getNeighbors(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 = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score, neighbor))
return []
### Паттерн Command
class Command:
def execute(self): pass
def undo(self): pass
class MoveCommand(Command):
def __init__(self, player, direction, maze):
self.player = player
self.dx, self.dy = direction
self.maze = maze
self.executed = False
def execute(self):
new_x = self.player.currentCell.x + self.dx
new_y = self.player.currentCell.y + self.dy
new_cell = self.maze.getCell(new_x, new_y)
if new_cell and new_cell.isPassable():
self.player.moveTo(new_cell)
self.executed = True
return True
return False
def undo(self):
if self.executed:
self.player.undoMove()
self.executed = False
return True
return False
## Результаты
| Лабиринт | Стратегия | Время (с) | Посещено | Длина пути | Путь найден |
|---|---|---|---|---|---|
| маленький (10x10) | BFS | 0.9148200158961117 | 19.0 | 19.0 | True |
| маленький (10x10) | DFS | 0.717819994315505 | 39.0 | 39.0 | True |
| маленький (10x10) | A | 1.577159995213151 | 19.0 | 19.0 | True |
| средний (50x50) | BFS | 14.496059995144606 | 99.0 | 99.0 | True |
| средний (50x50) | DFS | 8.470179990399629 | 393.0 | 393.0 |True |
| средний (50x50) | A | 9.11291999509558 | 99.0 | 99.0 | True |
| большой (100x100) | BFS | 0.013179995585232973 | 0.0 | 0.0 | False |
| большой (100x100) | A | 0.013079994823783636 | 0.0 | 0.0 | False |
| пустой (50x50) | BFS | 29.2012800113298 | 99.0 | 99.0 | True |
| пустой (50x50) | DFS | 13.176999986171722 | 1275.0 | 1275.0 | True |
| пустой (50x50) | A | 50.366899999789894 | 99.0 | 99.0 | True |
| без выхода (20x20) | BFS | 0.004239997360855341 | 0.0 | 0.0 | False |
| без выхода (20x20) | DFS | 0.006399990525096655 | 0.0 | 0.0 | False |
| без выхода (20x20) | A | 0.008680007886141539 | 0.0 | 0.0 | False |
### Графики
![Сравнение длины](maze_path_length.png)
![Сравнение времён](maze_time_comparison.png)
## Анализ эффективности алгоритмов
В ходе экспериментов были получены следующие результаты.
### BFS
Преимущества:
всегда находит кратчайший путь;
простая реализация.
Недостатки:
посещает большое количество клеток;
требует много памяти.
Выходит, что наиболее эффективен в небольших невзвешенных лабиринтах.
### DFS
Преимущества:
простая реализация;
самым быстрым находит произвольный путь.
Недостатки:
не гарантирует кратчайший путь;
может уходить в тупики.
Подходит для быстрого поиска любого решения.
### A
Преимущества:
высокая скорость;
посещает меньше клеток;
Недостатки:
требует выбора хорошей эвристики.
Показал хорошие результаты на больших лабиринтах.
## Анализ применимости паттернов
### Builder
Без Builder код загрузки лабиринта был бы жёстко связан с классом Maze, а добавление нового формата потребовало бы изменения существующего кода.
Strategy
Без Strategy пришлось бы:
хранить все алгоритмы внутри одного класса;
использовать большое количество условных операторов;
изменять код MazeSolver при добавлении новых алгоритмов
Strategy помог полностью отделить алгоритмы друг от друга.
### Observer
Без Observer логика интерфейса смешивалась бы с логикой поиска.
Это усложнило бы:
добавление GUI;
логирование;
визуализацию.
### Command
Без Command было бы сложно реализовать:
undo;
историю действий;
расширяемую систему управления.
## Выводы
### В проекте были успешно реализованы:
загрузка лабиринта из файла;
несколько алгоритмов поиска пути;
визуализация;
система наблюдателей;
система команд;
экспериментальное сравнение алгоритмов.
### Использование паттернов GoF позволило:
сделать архитектуру гибкой;
уменьшить связанность компонентов;
упростить расширение программы;
облегчить сопровождение кода.

View File

@ -0,0 +1,16 @@
лабиринт,стратегия,время_срремя_мин,время_макс,посещено_ср,длина_пути_ср,путь_найден
маленький (10x10),BFS,0.9148200158961117,0.8840999798849225,0.9673000313341618,19.0,19.0,True
маленький (10x10),DFS,0.717819994315505,0.5779999773949385,0.8650000090710819,39.0,39.0,True
маленький (10x10),A,1.577159995213151,1.531599962618202,1.7019000370055437,19.0,19.0,True
средний (50x50),BFS,14.496059995144606,12.946999981068075,18.392199999652803,99.0,99.0,True
средний (50x50),DFS,8.470179990399629,7.544599997345358,9.55930002965033,393.0,393.0,True
средний (50x50),A,9.11291999509558,8.53859999915585,9.788900031708181,99.0,99.0,True
большой (100x100),BFS,0.013179995585232973,0.009100011084228754,0.026200024876743555,0.0,0.0,False
большой (100x100),DFS,0.012619991321116686,0.008300004992634058,0.026499968953430653,0.0,0.0,False
большой (100x100),A,0.013079994823783636,0.008699949830770493,0.027500034775584936,0.0,0.0,False
пустой (50x50),BFS,29.2012800113298,19.71900003263727,47.252200020011514,99.0,99.0,True
пустой (50x50),DFS,13.176999986171722,12.441499973647296,13.887099979911,1275.0,1275.0,True
пустой (50x50),A,50.366899999789894,47.1535999677144,60.296199982985854,99.0,99.0,True
без выхода (20x20),BFS,0.004239997360855341,0.002700020559132099,0.00909995287656784,0.0,0.0,False
без выхода (20x20),DFS,0.006399990525096655,0.003200024366378784,0.012699980288743973,0.0,0.0,False
без выхода (20x20),A,0.008680007886141539,0.005399982910603285,0.01810002140700817,0.0,0.0,False
1 лабиринт стратегия время_ср время_мин время_макс посещено_ср длина_пути_ср путь_найден
2 маленький (10x10) BFS 0.9148200158961117 0.8840999798849225 0.9673000313341618 19.0 19.0 True
3 маленький (10x10) DFS 0.717819994315505 0.5779999773949385 0.8650000090710819 39.0 39.0 True
4 маленький (10x10) A 1.577159995213151 1.531599962618202 1.7019000370055437 19.0 19.0 True
5 средний (50x50) BFS 14.496059995144606 12.946999981068075 18.392199999652803 99.0 99.0 True
6 средний (50x50) DFS 8.470179990399629 7.544599997345358 9.55930002965033 393.0 393.0 True
7 средний (50x50) A 9.11291999509558 8.53859999915585 9.788900031708181 99.0 99.0 True
8 большой (100x100) BFS 0.013179995585232973 0.009100011084228754 0.026200024876743555 0.0 0.0 False
9 большой (100x100) DFS 0.012619991321116686 0.008300004992634058 0.026499968953430653 0.0 0.0 False
10 большой (100x100) A 0.013079994823783636 0.008699949830770493 0.027500034775584936 0.0 0.0 False
11 пустой (50x50) BFS 29.2012800113298 19.71900003263727 47.252200020011514 99.0 99.0 True
12 пустой (50x50) DFS 13.176999986171722 12.441499973647296 13.887099979911 1275.0 1275.0 True
13 пустой (50x50) A 50.366899999789894 47.1535999677144 60.296199982985854 99.0 99.0 True
14 без выхода (20x20) BFS 0.004239997360855341 0.002700020559132099 0.00909995287656784 0.0 0.0 False
15 без выхода (20x20) DFS 0.006399990525096655 0.003200024366378784 0.012699980288743973 0.0 0.0 False
16 без выхода (20x20) A 0.008680007886141539 0.005399982910603285 0.01810002140700817 0.0 0.0 False