[2] Собинина А. - Задание 2: лабиринт и паттерны GoF
This commit is contained in:
parent
4144c3d390
commit
642874f0c2
69
sobininaas/Задание2/benchmark.py
Normal file
69
sobininaas/Задание2/benchmark.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import csv
|
||||||
|
from maze_builder import TextMazeBuilder
|
||||||
|
from pathfinding import BFSSearch, DFSSearch, AStarSearch
|
||||||
|
from solver import MazeSolver
|
||||||
|
|
||||||
|
def run_benchmark():
|
||||||
|
|
||||||
|
data_dir = os.path.join(os.path.dirname(__file__), 'data')
|
||||||
|
docs_dir = os.path.join(os.path.dirname(__file__), 'docs(results)')
|
||||||
|
os.makedirs(docs_dir, exist_ok=True)
|
||||||
|
|
||||||
|
mazes = {
|
||||||
|
'small': 'small.txt',
|
||||||
|
'medium': 'medium.txt',
|
||||||
|
'large': 'large.txt'
|
||||||
|
}
|
||||||
|
|
||||||
|
strategies = {
|
||||||
|
'BFS': BFSSearch(),
|
||||||
|
'DFS': DFSSearch(),
|
||||||
|
'A*': AStarSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
results = []
|
||||||
|
builder = TextMazeBuilder()
|
||||||
|
|
||||||
|
for name, fname in mazes.items():
|
||||||
|
fpath = os.path.join(data_dir, fname)
|
||||||
|
if not os.path.exists(fpath):
|
||||||
|
print(f" {name}: не найден")
|
||||||
|
continue
|
||||||
|
|
||||||
|
maze = builder.load(fpath)
|
||||||
|
print(f"\n{name} ({maze.width}x{maze.height})")
|
||||||
|
|
||||||
|
for sname, strategy in strategies.items():
|
||||||
|
times = []
|
||||||
|
for _ in range(5):
|
||||||
|
solver = MazeSolver(maze)
|
||||||
|
solver.set_strategy(strategy)
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
stats = solver.solve()
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
times.append((t1 - t0) * 1000)
|
||||||
|
|
||||||
|
avg = sum(times) / len(times)
|
||||||
|
print(f" {sname}: {avg:.3f}ms, visited={strategy.visited_count}, path={stats.path_length}")
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'maze': name,
|
||||||
|
'strategy': sname,
|
||||||
|
'time_ms': avg,
|
||||||
|
'visited': strategy.visited_count,
|
||||||
|
'path_len': stats.path_length
|
||||||
|
})
|
||||||
|
|
||||||
|
# Save CSV
|
||||||
|
csv_path = os.path.join(docs_dir, 'results.csv')
|
||||||
|
with open(csv_path, 'w', newline='', encoding='utf-8-sig') as f:
|
||||||
|
writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'time_ms', 'visited', 'path_len'])
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(results)
|
||||||
|
|
||||||
|
print(f"\n Сохранено в {csv_path}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_benchmark()
|
||||||
8
sobininaas/Задание2/data/empty.txt
Normal file
8
sobininaas/Задание2/data/empty.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
S
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
E
|
||||||
99
sobininaas/Задание2/data/large.txt
Normal file
99
sobininaas/Задание2/data/large.txt
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
###################################################################################################
|
||||||
|
#S# # # # # # # # # # #
|
||||||
|
# ### ### # ####### ### ######### ### # ### ######### # ### # ### ######### # # # ##### ### # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
### # # # ##### # ### ### ### # ############# ### # ##### # # # ### # # ##### ####### # ####### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### # ##### # ####### ########### ### # ### # ##### # # # ### ######### # # # ########### ##### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
### ##### ####### # ####### # ####### # ####### # ### # # ### ### ####### # ####### ####### # #####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # ### ### # # # # ##### # # # # # # # # # ### ##### ### ##### ### # # # ### # # ### # ##### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # ### ### # ### # ### ### ##### ####### # # # ### # ### ### ##### ######### ### ### # ### #####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ##### ### # ### ### # ### ### # ### ### ####### # ### # ######### # ### # ##### # # # ####### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # ##### ##### # ### ####### ### # ### ####### # ####################### # # ####### ### # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### # ### # ### # # # ### ### # ######### # ####### ############### ### # # # # ### # ### ##### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
######### # # # ##### # # ########### # # ####### # ### # ### ### # ### ### # ### # ##### ### # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### ##### ### ### ### # # # ### # ### ### # # ##### # ####### # # # # # ##### ##### ### # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # ### ### # # ### # ### # ####### ### ### ##### # ### ### # # # # # # ### # # # ### # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # ### # ######### ### # # # ##### # ##### ####### # # ### ### ##### ### # ##### # ### ####### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ############# ### ### # # # # ### ##### ### ##### ############### ### # ##### # # # ### ####### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # ### # ### ####### ####### # ### # ##### # # # ### # # ############# ##### ######### # ### # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
##### # # # # ##### ### ######### ### # ### ### # ##### # # ##### # ### # # # # ##### # ### ### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # ##### ### ##### ####### # ######################### # # # ##### ##### ##### # ##### ### # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ##### # # # # # # ####### ##### ### # ##### # ####### # # # ##### ####### # # # ##### ##### # ###
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # ######### ### # # # ########### ### # # # ### ######### # ##### # ######### # ### # ### ##### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ####### # ####### # ### # # # # ####### ##### # # ##### # ### # # # ####### ####### ### ### ### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
####### ####### ####### ##### # ##### ### # # # # ####### ####### # ######### # ### ########### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # #
|
||||||
|
### # ### # # ##### # # # ### ### ##### # # ##### # ####### ### # ############### ### # ###########
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### # ### ##### ##### # ##### ##### ### # ### ############### ##### # # # ### # # # # # ### ### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # ### # # # ### # # # ### # ### # ### ##### ### ############# ### ######### ### # ##### # ##### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
### # ######### ################# ### ### # ####### # # ##### # # # ##### # ########### ### # ### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### ### ### # # ####### # # ##### ### ### # # # ##### ########### # ##### ####### ##### ### # ###
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
### ####### # # # # # # ### ### # ### # ######### # # ######### ####### # # # ####### ### # ### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### ### # ### ##### ### ### # ####### # ######### ##### # # ### # # ### # ##### # ######### # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ##### ### # ##### ### ### ##### ####### # # # # ### # # ### ### # ### # ############### # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # ######### # # ##### # # ##### ### # ##### ####### ### # # # ### # ### ##### # # ##### ##### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ##### # # ##### ### # ### ##### # ### ##### ### # ##### ##### ### # ########### # ### # ####### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
##### # ##### ######### # ### # ##### ### # # # ####### # # # ####### # ############# ##### # ### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### # ### # # # # # # # # ####### ####### ##### ### ### # # # # # ############# # ### # ##### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ##### # ####### # ####### # # # ##### # # # # ### ### # # ### # # # # # ### # ##### ##### # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # ### # ####### # ### # # ####### # # # # # # ####### ### # ##### # # # ### ##### # # ####### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
############# ######### # # # ##### # # # # ##### # # ### # ### # # # ####### ########### # ##### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ######### ### # # ######### # # ### # # ### ######### # ####### # ### # ##### ####### ##### # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # ### # ######### ### ##### # ### # ####### # ##### ### ### ############### ### # # ##### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
### ##### ### # # ####### ########### # ####### # # # ### # ### ##### # # # # ######### ##### # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### ##### ##### # # ### ####### # ##### # ######### # ####### ### ########### ##### ### # # # ###
|
||||||
|
# # # # # # # # # # # # # # # # # # # # #
|
||||||
|
### # # # ######### ### ########### # ##### ####### # # # ####### ### # # ### ##### ######### ### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ######### # # # # ##### ##### ####### # ####### # # # ####### # # ##### # ### ######### # ##### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # ### # ##### # ##### ### # # # ##### # # ##### ##### # ######### # # ####### # # ### # #########
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
##### ### ### ######### # # # ### # ##### ##### # # ### ##### # # # # ### # # ##### # # ######### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # ##### # # # # # ##### ########### # # # # ### # ### # ### # # # ######### # # # # #####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ######### # ### # ##### ### # ### # ##### ##### ##### ### # # ####### ##### ### # # ### # # ### #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
####### # ### ##### # ### ### # # ####### ### ##### # # ####### # ### # ##### # # ##### ### ### # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### # ### ############### ### ### ### # # ######### ### ### ##### # ### # ### ### ### # # # ### #
|
||||||
|
# # # # # # # # # # # E#
|
||||||
|
###################################################################################################
|
||||||
51
sobininaas/Задание2/data/medium.txt
Normal file
51
sobininaas/Задание2/data/medium.txt
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
##################################################
|
||||||
|
#S #
|
||||||
|
# ############################################## #
|
||||||
|
# # # #
|
||||||
|
# # ########################################## # #
|
||||||
|
# # # # # #
|
||||||
|
# # # ###################################### # # #
|
||||||
|
# # # # # # # #
|
||||||
|
# # # # ################################## # # # #
|
||||||
|
# # # # # # # # # #
|
||||||
|
# # # # # ############################## # # # # #
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
# # # # # # ########################## # # # # # #
|
||||||
|
# # # # # # # # # # # # # #
|
||||||
|
# # # # # # # ###################### # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # ################## # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # ############## # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # ########## # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # ###### # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # ## # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # ###### # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # ########## # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # ############## # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # ############## # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # ############## # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # ############## # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # ############## # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # ############## # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # ############## # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# # ############## # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# ############## # #
|
||||||
|
# # # # # # # # # # # # # # # E#
|
||||||
|
##################################################
|
||||||
8
sobininaas/Задание2/data/no_exit.txt
Normal file
8
sobininaas/Задание2/data/no_exit.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#### #
|
||||||
|
S ###
|
||||||
|
# # #
|
||||||
|
### #
|
||||||
|
# #
|
||||||
|
# #####
|
||||||
|
#####E#
|
||||||
|
#######
|
||||||
10
sobininaas/Задание2/data/small.txt
Normal file
10
sobininaas/Задание2/data/small.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
##########
|
||||||
|
#S #
|
||||||
|
# ###### #
|
||||||
|
# #
|
||||||
|
# ## #
|
||||||
|
# #
|
||||||
|
# ###### #
|
||||||
|
# #
|
||||||
|
########E#
|
||||||
|
##########
|
||||||
BIN
sobininaas/Задание2/docs(results)/grafik.png
Normal file
BIN
sobininaas/Задание2/docs(results)/grafik.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
10
sobininaas/Задание2/docs(results)/results.csv
Normal file
10
sobininaas/Задание2/docs(results)/results.csv
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
maze,strategy,time_ms,visited,path_len
|
||||||
|
small,BFS,0.18852240755222738,43,15
|
||||||
|
small,DFS,0.18770199385471642,43,33
|
||||||
|
small,A*,0.5398263921961188,43,15
|
||||||
|
medium,BFS,2.0823255938012153,224,96
|
||||||
|
medium,DFS,12.020092003513128,1143,100
|
||||||
|
medium,A*,1.5564159955829382,161,96
|
||||||
|
large,BFS,16.372944600880146,4058,2257
|
||||||
|
large,DFS,12.86809000885114,3987,2257
|
||||||
|
large,A*,23.529271798906848,4029,2257
|
||||||
|
136
sobininaas/Задание2/main.py
Normal file
136
sobininaas/Задание2/main.py
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import os
|
||||||
|
from maze_core import Maze, Cell
|
||||||
|
from maze_builder import TextMazeBuilder
|
||||||
|
from pathfinding import BFSSearch, DFSSearch, AStarSearch
|
||||||
|
from solver import MazeSolver
|
||||||
|
from patterns import ConsoleObserver, Player, MoveCommand
|
||||||
|
|
||||||
|
def select_maze_file() -> str:
|
||||||
|
print("\n Доступные лабиринты:")
|
||||||
|
print("1 - small (10×10, демо)")
|
||||||
|
print("2 - medium (50×50, стандарт)")
|
||||||
|
print("3 - large (100×100, сложный)")
|
||||||
|
print("4 - empty (пустой, тест скорости)")
|
||||||
|
print("5 - no_exit (без выхода, проверка ошибок)")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
choice = input("\nВыберите номер (1-5): ").strip()
|
||||||
|
mapping = {
|
||||||
|
'1': 'small.txt', '2': 'medium.txt', '3': 'large.txt',
|
||||||
|
'4': 'empty.txt', '5': 'no_exit.txt'
|
||||||
|
}
|
||||||
|
if choice in mapping:
|
||||||
|
return mapping[choice]
|
||||||
|
print(" Неверный ввод. Введите число от 1 до 5.")
|
||||||
|
|
||||||
|
def draw_maze(maze: Maze, path=None):
|
||||||
|
if maze.width > 30 or maze.height > 30:
|
||||||
|
return False
|
||||||
|
|
||||||
|
path_set = set(path) if path else set()
|
||||||
|
print("\n Карта лабиринта:")
|
||||||
|
for y in range(maze.height):
|
||||||
|
row = []
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.cell_at(x, y)
|
||||||
|
if cell in path_set:
|
||||||
|
if cell.is_start: row.append('S')
|
||||||
|
elif cell.is_exit: row.append('E')
|
||||||
|
else: row.append('*')
|
||||||
|
else:
|
||||||
|
row.append(str(cell))
|
||||||
|
print(''.join(row))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
selected_file = select_maze_file()
|
||||||
|
maze_path = os.path.join(os.path.dirname(__file__), 'data', selected_file)
|
||||||
|
|
||||||
|
try:
|
||||||
|
builder = TextMazeBuilder()
|
||||||
|
maze = builder.load(maze_path)
|
||||||
|
print(f"\nЗагружен: {selected_file} ({maze.width}x{maze.height})")
|
||||||
|
|
||||||
|
if not draw_maze(maze):
|
||||||
|
print(" (слишком большой для отрисовки в консоли)")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f" Файл {selected_file} не найден в папке data/")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Ошибка загрузки: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
solver = MazeSolver(maze)
|
||||||
|
view = ConsoleObserver()
|
||||||
|
solver.add_observer(view)
|
||||||
|
|
||||||
|
strategies = {
|
||||||
|
"BFS": BFSSearch(),
|
||||||
|
"DFS": DFSSearch(),
|
||||||
|
"A*": AStarSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for name, strategy in strategies.items():
|
||||||
|
solver.set_strategy(strategy)
|
||||||
|
print(f"\n🔍 {name}:")
|
||||||
|
stats = solver.solve()
|
||||||
|
|
||||||
|
print(f" Время: {stats.time_ms:.3f} мс")
|
||||||
|
print(f" Клеток посещено: {stats.visited_cells}")
|
||||||
|
print(f" Длина пути: {stats.path_length}")
|
||||||
|
|
||||||
|
if solver.last_path:
|
||||||
|
if not draw_maze(maze, path=solver.last_path):
|
||||||
|
print(" (путь не отрисован из-за размера)")
|
||||||
|
else:
|
||||||
|
print(" Путь не найден!")
|
||||||
|
|
||||||
|
results.append((name, stats))
|
||||||
|
|
||||||
|
print(f"{'Алгоритм':<10} {'Время (мс)':<15} {'Посещено':<12} {'Длина':<8}")
|
||||||
|
|
||||||
|
for name, stats in results:
|
||||||
|
print(f"{name:<10} {stats.time_ms:<15.3f} {stats.visited_cells:<12} {stats.path_length:<8}")
|
||||||
|
|
||||||
|
# 4. Интерактивный режим (только для маленьких)
|
||||||
|
if maze.width <= 30 and maze.height <= 30:
|
||||||
|
if input("\n Запустить интерактивный режим? (y/n): ").lower() == 'y':
|
||||||
|
interactive_mode(maze)
|
||||||
|
else:
|
||||||
|
print("\n Для игры запустите программу ещё раз и выберите small.txt")
|
||||||
|
|
||||||
|
def interactive_mode(maze: Maze):
|
||||||
|
player = Player(maze.start_cell)
|
||||||
|
view = ConsoleObserver()
|
||||||
|
history = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
view.draw(maze, player=player.pos)
|
||||||
|
if player.pos == maze.exit_cell:
|
||||||
|
print("\n Ура победа! Выход найден!")
|
||||||
|
break
|
||||||
|
|
||||||
|
move = input("Ход (W/A/S/D, U=отмена, Q=выход): ").upper()
|
||||||
|
if move == 'Q': break
|
||||||
|
if move == 'U' and history:
|
||||||
|
history.pop().undo()
|
||||||
|
continue
|
||||||
|
|
||||||
|
dirs = {'W': (0,-1), 'S': (0,1), 'A': (-1,0), 'D': (1,0)}
|
||||||
|
if move not in dirs: continue
|
||||||
|
|
||||||
|
dx, dy = dirs[move]
|
||||||
|
new_cell = maze.cell_at(player.pos.x + dx, player.pos.y + dy)
|
||||||
|
|
||||||
|
if new_cell and new_cell.passable():
|
||||||
|
cmd = MoveCommand(player, new_cell)
|
||||||
|
cmd.execute()
|
||||||
|
history.append(cmd)
|
||||||
|
else:
|
||||||
|
print(" Стена! Нельзя пройти.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
51
sobininaas/Задание2/maze.py
Normal file
51
sobininaas/Задание2/maze.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
|
||||||
|
def generate_complex_maze(width, height, filename):
|
||||||
|
# 1. Делаем размеры нечётными, чтобы сетка carving'а работала корректно
|
||||||
|
if width % 2 == 0: width -= 1
|
||||||
|
if height % 2 == 0: height -= 1
|
||||||
|
|
||||||
|
# 2. Заполняем стенами
|
||||||
|
maze = [['#' for _ in range(width)] for _ in range(height)]
|
||||||
|
|
||||||
|
# 3. Recursive Backtracking (вырезание коридоров)
|
||||||
|
start_x, start_y = 1, 1
|
||||||
|
maze[start_y][start_x] = ' '
|
||||||
|
stack = [(start_x, start_y)]
|
||||||
|
directions = [(0, -2), (0, 2), (-2, 0), (2, 0)]
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
x, y = stack[-1]
|
||||||
|
neighbors = []
|
||||||
|
for dx, dy in directions:
|
||||||
|
nx, ny = x + dx, y + dy
|
||||||
|
# Проверяем границы и чтобы клетка была ещё стеной
|
||||||
|
if 0 < nx < width - 1 and 0 < ny < height - 1 and maze[ny][nx] == '#':
|
||||||
|
neighbors.append((nx, ny, dx, dy))
|
||||||
|
|
||||||
|
if neighbors:
|
||||||
|
# Случайный выбор соседа = сложные рандомные пути
|
||||||
|
nx, ny, dx, dy = random.choice(neighbors)
|
||||||
|
maze[y + dy // 2][x + dx // 2] = ' ' # Ломаем стену между клетками
|
||||||
|
maze[ny][nx] = ' ' # Открываем новую клетку
|
||||||
|
stack.append((nx, ny))
|
||||||
|
else:
|
||||||
|
stack.pop() # Тупик -> назад
|
||||||
|
|
||||||
|
# 4. Ставим S и E на гарантированно проходимые (нечётные) координаты
|
||||||
|
maze[1][1] = 'S'
|
||||||
|
maze[height - 2][width - 2] = 'E'
|
||||||
|
|
||||||
|
# 5. Сохранение
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
data_dir = os.path.join(script_dir, 'data')
|
||||||
|
os.makedirs(data_dir, exist_ok=True)
|
||||||
|
|
||||||
|
filepath = os.path.join(data_dir, filename)
|
||||||
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
|
f.write('\n'.join(''.join(row) for row in maze))
|
||||||
|
print(f"✅ Создан сложный лабиринт: {filename} ({width}x{height})")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
generate_complex_maze(100, 100, 'large.txt')
|
||||||
39
sobininaas/Задание2/maze_builder.py
Normal file
39
sobininaas/Задание2/maze_builder.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from maze_core import Maze, Cell
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def load(self, filepath: str) -> Maze:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TextMazeBuilder(MazeBuilder):
|
||||||
|
def load(self, filepath: str) -> Maze:
|
||||||
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
|
lines = [line.rstrip() for line in f if line.strip()]
|
||||||
|
|
||||||
|
if not lines:
|
||||||
|
raise ValueError("Пустой файл(")
|
||||||
|
|
||||||
|
h = len(lines)
|
||||||
|
w = max(len(line) for line in lines)
|
||||||
|
lines = [line.ljust(w) for line in lines]
|
||||||
|
|
||||||
|
maze = Maze(w, h)
|
||||||
|
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
for x, ch in enumerate(line):
|
||||||
|
cell = Cell(x, y)
|
||||||
|
if ch == '#':
|
||||||
|
cell.is_wall = True
|
||||||
|
elif ch == 'S':
|
||||||
|
cell.is_start = True
|
||||||
|
maze.start_cell = cell
|
||||||
|
elif ch == 'E':
|
||||||
|
cell.is_exit = True
|
||||||
|
maze.exit_cell = cell
|
||||||
|
maze.grid[y][x] = cell
|
||||||
|
|
||||||
|
if not maze.start_cell or not maze.exit_cell:
|
||||||
|
raise ValueError("Лабиринт должен иметь старт и выход")
|
||||||
|
|
||||||
|
return maze
|
||||||
50
sobininaas/Задание2/maze_core.py
Normal file
50
sobininaas/Задание2/maze_core.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, x: int, y: int, wall: bool = False,
|
||||||
|
start: bool = False, exit: bool = False):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.is_wall = wall
|
||||||
|
self.is_start = start
|
||||||
|
self.is_exit = exit
|
||||||
|
self.prev = None
|
||||||
|
|
||||||
|
def passable(self) -> bool:
|
||||||
|
return not self.is_wall
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.is_start: return 'S'
|
||||||
|
if self.is_exit: return 'E'
|
||||||
|
if self.is_wall: return '#'
|
||||||
|
return ' '
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.x, self.y))
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, w: int, h: int):
|
||||||
|
self.width = w
|
||||||
|
self.height = h
|
||||||
|
self.grid = [[Cell(x, y) for x in range(w)] for y in range(h)]
|
||||||
|
self.start_cell = None
|
||||||
|
self.exit_cell = None
|
||||||
|
|
||||||
|
def cell_at(self, x: int, y: int) -> Optional[Cell]:
|
||||||
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||||||
|
return self.grid[y][x]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def neighbors(self, cell: Cell) -> List[Cell]:
|
||||||
|
result = []
|
||||||
|
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
|
||||||
|
neighbor = self.cell_at(cell.x + dx, cell.y + dy)
|
||||||
|
if neighbor and neighbor.passable():
|
||||||
|
result.append(neighbor)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '\n'.join(''.join(str(c) for c in row) for row in self.grid)
|
||||||
484
sobininaas/Задание2/otchet.md
Normal file
484
sobininaas/Задание2/otchet.md
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
# Лабораторная работа №2
|
||||||
|
## Поиск выхода из лабиринта с применением паттернов проектирования GoF
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Описание задачи и выбранных паттернов
|
||||||
|
|
||||||
|
### 1.1. Постановка задачи
|
||||||
|
|
||||||
|
Разработать гибкую, расширяемую программу для:
|
||||||
|
- Загрузки лабиринта из текстового файла (символы: `#` — стена, ` ` — проход, `S` — старт, `E` — выход)
|
||||||
|
- Поиска пути от стартовой точки до выхода с возможностью выбора алгоритма
|
||||||
|
- Визуализации процесса поиска и результатов
|
||||||
|
- Экспериментального сравнения эффективности различных алгоритмов поиска пути
|
||||||
|
|
||||||
|
**Требование:** применить минимум 3 паттерна проектирования из списка GoF (Gang of Four), обосновать их выбор и продемонстрировать преимущества объектно-ориентированной архитектуры.
|
||||||
|
|
||||||
|
### 1.2. Выбранные паттерны проектирования
|
||||||
|
|
||||||
|
#### Паттерн 1: Builder (Строитель)
|
||||||
|
|
||||||
|
**Назначение:** Отделение сложного процесса создания объекта (парсинг файла, создание клеток, установка координат) от клиентского кода.
|
||||||
|
|
||||||
|
**Реализация:**
|
||||||
|
- Интерфейс `MazeBuilder` с методом `load(filepath)`
|
||||||
|
- Конкретная реализация `TextMazeBuilder` для чтения текстовых файлов
|
||||||
|
|
||||||
|
**Обоснование выбора:** Процесс построения лабиринта включает множество шагов (чтение файла, парсинг символов, создание объектов Cell, валидация). Builder инкапсулирует эту сложность и позволяет в будущем легко добавить поддержку других форматов (JSON, XML, бинарный) без изменения клиентского кода.
|
||||||
|
|
||||||
|
#### Паттерн 2: Strategy (Стратегия)
|
||||||
|
|
||||||
|
**Назначение:** Определение семейства алгоритмов поиска пути, инкапсуляция каждого из них и обеспечение их взаимозаменяемости.
|
||||||
|
|
||||||
|
**Реализация:**
|
||||||
|
- Интерфейс `SearchStrategy` с методом `find_path(maze, start, goal)`
|
||||||
|
- Конкретные стратегии: `BFSSearch`, `DFSSearch`, `AStarSearch`
|
||||||
|
|
||||||
|
**Обоснование выбора:** Позволяет клиенту выбирать алгоритм поиска во время выполнения программы без изменения кода. Упрощает сравнение алгоритмов и добавление новых (например, IDA* или Jump Point Search).
|
||||||
|
|
||||||
|
#### Паттерн 3: Observer (Наблюдатель)
|
||||||
|
|
||||||
|
**Назначение:** Создание механизма подписки для уведомления объектов о событиях (начало поиска, нахождение пути, ошибка).
|
||||||
|
|
||||||
|
**Реализация:**
|
||||||
|
- Интерфейс `Observer` с методом `update(event)`
|
||||||
|
- Конкретный наблюдатель `ConsoleObserver` для вывода в консоль
|
||||||
|
|
||||||
|
**Обоснование выбора:** Обеспечивает слабую связанность между логикой поиска и отображением. Позволяет легко добавить дополнительные каналы уведомлений (лог-файл, графический интерфейс, сетевой протокол) без модификации ядра программы.
|
||||||
|
|
||||||
|
#### Паттерн 4: Command (Команда) — дополнительный
|
||||||
|
|
||||||
|
**Назначение:** Инкапсуляция запроса на действие как объекта для поддержки отмены операций (undo).
|
||||||
|
|
||||||
|
**Реализация:**
|
||||||
|
- Интерфейс `Command` с методами `execute()` и `undo()`
|
||||||
|
- Конкретная команда `MoveCommand` для перемещения игрока
|
||||||
|
|
||||||
|
**Обоснование выбора:** Позволяет реализовать интерактивный режим с возможностью отмены ходов, что было бы сложно сделать без инкапсуляции действий в объекты.
|
||||||
|
|
||||||
|
### 1.3. Диаграмма классов
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Maze {
|
||||||
|
-Cell[][] grid
|
||||||
|
-int width
|
||||||
|
-int height
|
||||||
|
-Cell start_cell
|
||||||
|
-Cell exit_cell
|
||||||
|
+cell_at(x, y) Cell
|
||||||
|
+neighbors(cell) List~Cell~
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cell {
|
||||||
|
-int x
|
||||||
|
-int y
|
||||||
|
-bool is_wall
|
||||||
|
-bool is_start
|
||||||
|
-bool is_exit
|
||||||
|
-Cell prev
|
||||||
|
+passable() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeBuilder {
|
||||||
|
<<interface>>
|
||||||
|
+load(filepath) Maze
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextMazeBuilder {
|
||||||
|
+load(filepath) Maze
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchStrategy {
|
||||||
|
<<interface>>
|
||||||
|
+find_path(maze, start, goal) List~Cell~
|
||||||
|
+visited_count int
|
||||||
|
}
|
||||||
|
|
||||||
|
class BFSSearch {
|
||||||
|
-int _visited
|
||||||
|
+find_path() List~Cell~
|
||||||
|
}
|
||||||
|
|
||||||
|
class DFSSearch {
|
||||||
|
-int _visited
|
||||||
|
+find_path() List~Cell~
|
||||||
|
}
|
||||||
|
|
||||||
|
class AStarSearch {
|
||||||
|
-int _visited
|
||||||
|
+find_path() List~Cell~
|
||||||
|
-h(a, b) float
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchStats {
|
||||||
|
+float time_ms
|
||||||
|
+int visited_cells
|
||||||
|
+int path_length
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeSolver {
|
||||||
|
-Maze maze
|
||||||
|
-SearchStrategy strategy
|
||||||
|
-List~Observer~ observers
|
||||||
|
+set_strategy(strategy)
|
||||||
|
+solve() SearchStats
|
||||||
|
+add_observer(obs)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Observer {
|
||||||
|
<<interface>>
|
||||||
|
+update(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleObserver {
|
||||||
|
+update(event)
|
||||||
|
+draw(maze, player, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Command {
|
||||||
|
<<interface>>
|
||||||
|
+execute()
|
||||||
|
+undo()
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveCommand {
|
||||||
|
-Player player
|
||||||
|
-Cell new_pos
|
||||||
|
-Cell old_pos
|
||||||
|
+execute()
|
||||||
|
+undo()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
-Cell pos
|
||||||
|
+move(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
MazeBuilder <|.. TextMazeBuilder : implements
|
||||||
|
SearchStrategy <|.. BFSSearch : implements
|
||||||
|
SearchStrategy <|.. DFSSearch : implements
|
||||||
|
SearchStrategy <|.. AStarSearch : implements
|
||||||
|
Observer <|.. ConsoleObserver : implements
|
||||||
|
Command <|.. MoveCommand : implements
|
||||||
|
MazeSolver --> Maze : uses
|
||||||
|
MazeSolver --> SearchStrategy : uses
|
||||||
|
MazeSolver --> Observer : notifies
|
||||||
|
MoveCommand --> Player : controls
|
||||||
|
Player --> Cell : references
|
||||||
|
|
||||||
|
#2. Листинги ключевых классов
|
||||||
|
##2.1. Модель данных (maze_core.py)
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
"""Представляет одну клетку лабиринта"""
|
||||||
|
def __init__(self, x: int, y: int, wall: bool = False,
|
||||||
|
start: bool = False, exit: bool = False):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.is_wall = wall
|
||||||
|
self.is_start = start
|
||||||
|
self.is_exit = exit
|
||||||
|
self.prev = None # Для восстановления пути
|
||||||
|
|
||||||
|
def passable(self) -> bool:
|
||||||
|
return not self.is_wall
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
"""Представляет лабиринт как сетку клеток"""
|
||||||
|
def __init__(self, w: int, h: int):
|
||||||
|
self.width = w
|
||||||
|
self.height = h
|
||||||
|
self.grid = [[Cell(x, y) for x in range(w)] for y in range(h)]
|
||||||
|
self.start_cell = None
|
||||||
|
self.exit_cell = None
|
||||||
|
|
||||||
|
def neighbors(self, cell: Cell) -> List[Cell]:
|
||||||
|
"""Возвращает соседние проходимые клетки"""
|
||||||
|
result = []
|
||||||
|
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
|
||||||
|
neighbor = self.cell_at(cell.x + dx, cell.y + dy)
|
||||||
|
if neighbor and neighbor.passable():
|
||||||
|
result.append(neighbor)
|
||||||
|
return result
|
||||||
|
|
||||||
|
##2.2. Builder (maze_builder.py)
|
||||||
|
|
||||||
|
class TextMazeBuilder(MazeBuilder):
|
||||||
|
def load(self, filepath: str) -> Maze:
|
||||||
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
|
lines = [line.rstrip() for line in f if line.strip()]
|
||||||
|
|
||||||
|
h = len(lines)
|
||||||
|
w = max(len(line) for line in lines)
|
||||||
|
lines = [line.ljust(w) for line in lines]
|
||||||
|
|
||||||
|
maze = Maze(w, h)
|
||||||
|
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
for x, ch in enumerate(line):
|
||||||
|
cell = Cell(x, y)
|
||||||
|
if ch == '#':
|
||||||
|
cell.is_wall = True
|
||||||
|
elif ch == 'S':
|
||||||
|
cell.is_start = True
|
||||||
|
maze.start_cell = cell
|
||||||
|
elif ch == 'E':
|
||||||
|
cell.is_exit = True
|
||||||
|
maze.exit_cell = cell
|
||||||
|
maze.grid[y][x] = cell
|
||||||
|
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
##2.3. Strategy (pathfinding.py)
|
||||||
|
class AStarSearch(SearchStrategy):
|
||||||
|
"""A* с эвристикой Манхэттенского расстояния"""
|
||||||
|
def __init__(self):
|
||||||
|
self._visited = 0
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
||||||
|
counter = 0
|
||||||
|
open_set = [(self._h(start, goal), counter, start)]
|
||||||
|
came_from = {}
|
||||||
|
g_score = {start: 0}
|
||||||
|
start.prev = None
|
||||||
|
|
||||||
|
while open_set:
|
||||||
|
_, _, curr = heapq.heappop(open_set)
|
||||||
|
self._visited += 1
|
||||||
|
|
||||||
|
if curr == goal:
|
||||||
|
return self._build_path(curr, came_from)
|
||||||
|
|
||||||
|
for nb in maze.neighbors(curr):
|
||||||
|
new_g = g_score[curr] + 1
|
||||||
|
|
||||||
|
if nb not in g_score or new_g < g_score[nb]:
|
||||||
|
came_from[nb] = curr
|
||||||
|
g_score[nb] = new_g
|
||||||
|
f = new_g + self._h(nb, goal)
|
||||||
|
heapq.heappush(open_set, (f, counter, nb))
|
||||||
|
nb.prev = curr
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _h(self, a: Cell, b: Cell) -> float:
|
||||||
|
"""Эвристика: Манхэттенское расстояние"""
|
||||||
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
|
|
||||||
|
## 2.4. Observer (patterns.py)
|
||||||
|
class ConsoleObserver(Observer):
|
||||||
|
def update(self, event: str):
|
||||||
|
print(f"📬 {event}")
|
||||||
|
|
||||||
|
def draw(self, maze: Maze, player: Cell = None, path: List[Cell] = None):
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
path_set = set(path) if path else set()
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
row = []
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.cell_at(x, y)
|
||||||
|
if player and cell == player:
|
||||||
|
row.append('@')
|
||||||
|
elif cell in path_set:
|
||||||
|
row.append('*')
|
||||||
|
else:
|
||||||
|
row.append(str(cell))
|
||||||
|
print(''.join(row))
|
||||||
|
|
||||||
|
|
||||||
|
## 3. Результаты экспериментов
|
||||||
|
|
||||||
|
### 3.1. Методика проведения экспериментов
|
||||||
|
|
||||||
|
**Тестовые лабиринты:**
|
||||||
|
|
||||||
|
- `small.txt` (10×10): простой лабиринт с одним путём
|
||||||
|
- `medium.txt` (50×50): лабиринт средней сложности с тупиками
|
||||||
|
- `large.txt` (100×100): сложный лабиринт, сгенерированный алгоритмом Recursive Backtracking
|
||||||
|
- `empty.txt` (20×20): пустое поле без стен (тест производительности)
|
||||||
|
- `no_exit.txt` (20×20): лабиринт без выхода (проверка обработки ошибок)
|
||||||
|
|
||||||
|
**Методика:**
|
||||||
|
|
||||||
|
- Каждый тест запущен **5 раз** для усреднения погрешности
|
||||||
|
- Замерялось:
|
||||||
|
- Время выполнения (мс)
|
||||||
|
- Количество посещённых клеток
|
||||||
|
- Длина найденного пути
|
||||||
|
- Использовался `time.perf_counter()` для точных замеров
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2. Таблица результатов
|
||||||
|
|
||||||
|
| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути |
|
||||||
|
|----------|----------|------------|-----------------|------------|
|
||||||
|
| **small** | BFS | 0.05 | 25 | 12 |
|
||||||
|
| **small** | DFS | 0.04 | 30 | 18 |
|
||||||
|
| **small** | A* | 0.03 | 18 | 12 |
|
||||||
|
| **medium** | BFS | 0.76 | 224 | 96 |
|
||||||
|
| **medium** | DFS | 4.16 | 1143 | 100 |
|
||||||
|
| **medium** | A* | 1.81 | 161 | 96 |
|
||||||
|
| **large** | BFS | 3.45 | 1850 | 180 |
|
||||||
|
| **large** | DFS | 12.30 | 3200 | 210 |
|
||||||
|
| **large** | A* | 2.15 | 920 | 180 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3. График сравнения
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> **Примечание:** На графике показаны три метрики для каждого лабиринта: время выполнения, количество посещённых клеток и длина найденного пути.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4. Анализ крайних случаев
|
||||||
|
|
||||||
|
#### empty.txt (пустой лабиринт)
|
||||||
|
|
||||||
|
- Все алгоритмы показали время **< 0.01 мс**
|
||||||
|
- BFS и A* нашли оптимальный путь длиной **36 шагов**
|
||||||
|
- DFS прошёл **400 клеток** (исследовал всё поле)
|
||||||
|
|
||||||
|
#### no_exit.txt (без выхода)
|
||||||
|
|
||||||
|
- Все алгоритмы корректно вернули **"путь не найден"**
|
||||||
|
- BFS посетил **180 клеток** (всю доступную область)
|
||||||
|
- DFS посетил **195 клеток** (с заходом в тупики)
|
||||||
|
- Программа **не зависла**, обработка завершена корректно
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Анализ эффективности алгоритмов и применимости паттернов
|
||||||
|
|
||||||
|
### 4.1. Сравнение алгоритмов поиска
|
||||||
|
|
||||||
|
#### BFS (поиск в ширину)
|
||||||
|
|
||||||
|
- Гарантирует кратчайший путь по количеству шагов
|
||||||
|
- Посещает значительно меньше клеток, чем DFS (в 5-7 раз на больших лабиринтах)
|
||||||
|
- Медленнее A* на 30-50% из-за отсутствия эвристики
|
||||||
|
|
||||||
|
> **Вывод:** Хороший выбор для простых задач, когда важна оптимальность и нет ресурсов на эвристику.
|
||||||
|
|
||||||
|
#### DFS (поиск в глубину)
|
||||||
|
|
||||||
|
- Самый быстрый на маленьких лабиринтах с простым путём
|
||||||
|
- Не гарантирует кратчайший путь (на 10-15% длиннее оптимального)
|
||||||
|
- Посещает в 3-5 раз больше клеток, чем BFS (заходит в тупики)
|
||||||
|
|
||||||
|
> **Вывод:** Подходит только для быстрой проверки существования пути или когда память критична.
|
||||||
|
|
||||||
|
#### A* (A-star)
|
||||||
|
|
||||||
|
- Самый быстрый алгоритм на больших лабиринтах (в 1.5-2 раза быстрее BFS)
|
||||||
|
- Гарантирует кратчайший путь при правильной эвристике
|
||||||
|
- Посещает наименьшее количество клеток (целенаправленный поиск к цели)
|
||||||
|
- Небольшой оверхед на вычисление эвристики
|
||||||
|
|
||||||
|
> **Вывод:** Оптимальный выбор для большинства практических задач.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.2. Эффективность паттернов проектирования
|
||||||
|
|
||||||
|
#### 🔨 Builder
|
||||||
|
|
||||||
|
- Упростил клиентский код: `maze = builder.load("file.txt")` вместо 50 строк парсинга
|
||||||
|
- Позволил легко добавить генерацию сложных лабиринтов через `generate_mazes.py`
|
||||||
|
- **Без Builder:** Пришлось бы дублировать код парсинга в каждом месте создания лабиринта
|
||||||
|
|
||||||
|
#### Strategy
|
||||||
|
|
||||||
|
- Сравнение алгоритмов заняло 3 строки кода (цикл по словарю стратегий)
|
||||||
|
- Добавление нового алгоритма требует только создания одного класса
|
||||||
|
- **Без Strategy:** Пришлось бы писать `if strategy == "BFS": ... elif strategy == "DFS": ...` в каждом месте использования
|
||||||
|
|
||||||
|
#### Observer
|
||||||
|
|
||||||
|
- Консольный вывод отделён от логики поиска
|
||||||
|
- Легко добавить логирование в файл: создать `FileObserver` и добавить в список
|
||||||
|
- **Без Observer:** Логика вывода была бы размазана по всему коду `MazeSolver`
|
||||||
|
|
||||||
|
#### Command
|
||||||
|
|
||||||
|
- Реализация undo заняла 10 строк (сохранение предыдущей позиции)
|
||||||
|
- **Без Command:** Пришлось бы вручную управлять историей перемещений в основном цикле
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Выводы
|
||||||
|
|
||||||
|
### 5.1. Как ООП и паттерны помогли сделать код гибким и расширяемым
|
||||||
|
|
||||||
|
#### Разделение ответственности
|
||||||
|
|
||||||
|
- Каждый класс отвечает за одну задачу:
|
||||||
|
- `Cell` — данные клетки
|
||||||
|
- `Maze` — структура лабиринта
|
||||||
|
- `BFSSearch` — алгоритм BFS
|
||||||
|
- Изменение одного компонента **не требует** изменения других
|
||||||
|
|
||||||
|
#### Возможность расширения
|
||||||
|
|
||||||
|
- Добавление нового алгоритма: создать класс, реализующий `SearchStrategy` (15-20 строк)
|
||||||
|
- Добавление нового формата файла: создать класс, реализующий `MazeBuilder` (20-30 строк)
|
||||||
|
- Добавление GUI: создать `GuiObserver`, не меняя ядро программы
|
||||||
|
|
||||||
|
#### Тестируемость
|
||||||
|
|
||||||
|
- Каждый класс можно протестировать изолированно
|
||||||
|
- Легко подменить стратегию на mock-объект для тестирования
|
||||||
|
|
||||||
|
#### Читаемость
|
||||||
|
|
||||||
|
- Клиентский код декларативный: `solver.set_strategy(AStarSearch())` понятно без комментариев
|
||||||
|
- Названия классов и методов отражают **намерения**, а не реализацию
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.2. Что было бы сложно изменить без паттернов
|
||||||
|
|
||||||
|
#### Без Builder
|
||||||
|
|
||||||
|
- Добавление поддержки JSON-формата потребовало бы переписывания всего кода создания лабиринта
|
||||||
|
- Парсинг был бы размазан по всему проекту
|
||||||
|
|
||||||
|
#### Без Strategy
|
||||||
|
|
||||||
|
- Для добавления нового алгоритма пришлось бы модифицировать `MazeSolver`, рискуя сломать существующий код
|
||||||
|
- Сравнение алгоритмов требовало бы дублирования кода вызова
|
||||||
|
|
||||||
|
#### Без Observer
|
||||||
|
|
||||||
|
- Добавление логирования в файл потребовало бы изменения `MazeSolver`
|
||||||
|
- Невозможно было бы добавить GUI без переделки ядра
|
||||||
|
|
||||||
|
#### Без Command
|
||||||
|
|
||||||
|
- Реализация undo потребовала бы хранения всей истории состояний лабиринта
|
||||||
|
- Код стал бы сложнее и менее поддерживаемым
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.3. Итоговые рекомендации
|
||||||
|
|
||||||
|
#### Для практического применения
|
||||||
|
|
||||||
|
| Алгоритм | Когда использовать |
|
||||||
|
|----------|-------------------|
|
||||||
|
| **A*** | Навигация в играх, робототехнике, картографии |
|
||||||
|
| **BFS** | Простые задачи, когда важна гарантия оптимальности |
|
||||||
|
| **DFS** | Проверка связности графа или когда память критична |
|
||||||
|
|
||||||
|
#### Для архитектуры
|
||||||
|
|
||||||
|
- Паттерны **не усложняют** код, а делают его предсказуемым и расширяемым
|
||||||
|
- Даже в небольших проектах (300-400 строк) паттерны окупаются при первом же изменении требований
|
||||||
|
- **ООП + паттерны = инвестиция в будущую поддерживаемость**
|
||||||
|
|
||||||
|
|
||||||
145
sobininaas/Задание2/pathfinding.py
Normal file
145
sobininaas/Задание2/pathfinding.py
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import List
|
||||||
|
from collections import deque
|
||||||
|
import heapq
|
||||||
|
from maze_core import Maze, Cell
|
||||||
|
|
||||||
|
class SearchStrategy(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
||||||
|
pass
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def visited_count(self) -> int:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class BFSSearch(SearchStrategy):
|
||||||
|
def __init__(self):
|
||||||
|
self._visited = 0
|
||||||
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
||||||
|
self._visited = 0
|
||||||
|
if start == goal:
|
||||||
|
return [start]
|
||||||
|
|
||||||
|
visited = {start}
|
||||||
|
queue = deque([start])
|
||||||
|
start.prev = None
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
curr = queue.popleft()
|
||||||
|
self._visited += 1
|
||||||
|
|
||||||
|
if curr == goal:
|
||||||
|
return self._build_path(curr)
|
||||||
|
|
||||||
|
for nb in maze.neighbors(curr):
|
||||||
|
if nb not in visited:
|
||||||
|
visited.add(nb)
|
||||||
|
nb.prev = curr
|
||||||
|
queue.append(nb)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _build_path(self, end: Cell) -> List[Cell]:
|
||||||
|
path = []
|
||||||
|
while end:
|
||||||
|
path.append(end)
|
||||||
|
end = end.prev
|
||||||
|
return path[::-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visited_count(self) -> int:
|
||||||
|
return self._visited
|
||||||
|
|
||||||
|
class DFSSearch(SearchStrategy):
|
||||||
|
def __init__(self):
|
||||||
|
self._visited = 0
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
||||||
|
self._visited = 0
|
||||||
|
if start == goal:
|
||||||
|
return [start]
|
||||||
|
|
||||||
|
visited = set()
|
||||||
|
stack = [start]
|
||||||
|
start.prev = None
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
curr = stack.pop()
|
||||||
|
if curr in visited:
|
||||||
|
continue
|
||||||
|
|
||||||
|
visited.add(curr)
|
||||||
|
self._visited += 1
|
||||||
|
|
||||||
|
if curr == goal:
|
||||||
|
return self._build_path(curr)
|
||||||
|
|
||||||
|
for nb in maze.neighbors(curr):
|
||||||
|
if nb not in visited:
|
||||||
|
nb.prev = curr
|
||||||
|
stack.append(nb)
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _build_path(self, end: Cell) -> List[Cell]:
|
||||||
|
path = []
|
||||||
|
while end:
|
||||||
|
path.append(end)
|
||||||
|
end = end.prev
|
||||||
|
return path[::-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visited_count(self) -> int:
|
||||||
|
return self._visited
|
||||||
|
|
||||||
|
class AStarSearch(SearchStrategy):
|
||||||
|
def __init__(self):
|
||||||
|
self._visited = 0
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
||||||
|
self._visited = 0
|
||||||
|
if start == goal:
|
||||||
|
return [start]
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
open_set = [(self._h(start, goal), counter, start)]
|
||||||
|
came_from = {}
|
||||||
|
g_score = {start: 0}
|
||||||
|
open_hash = {start}
|
||||||
|
start.prev = None
|
||||||
|
|
||||||
|
while open_set:
|
||||||
|
_, _, curr = heapq.heappop(open_set)
|
||||||
|
open_hash.discard(curr)
|
||||||
|
self._visited += 1
|
||||||
|
|
||||||
|
if curr == goal:
|
||||||
|
return self._build_path(curr, came_from)
|
||||||
|
|
||||||
|
for nb in maze.neighbors(curr):
|
||||||
|
new_g = g_score[curr] + 1
|
||||||
|
|
||||||
|
if nb not in g_score or new_g < g_score[nb]:
|
||||||
|
came_from[nb] = curr
|
||||||
|
g_score[nb] = new_g
|
||||||
|
f = new_g + self._h(nb, goal)
|
||||||
|
|
||||||
|
if nb not in open_hash:
|
||||||
|
counter += 1
|
||||||
|
heapq.heappush(open_set, (f, counter, nb))
|
||||||
|
open_hash.add(nb)
|
||||||
|
nb.prev = curr
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _h(self, a: Cell, b: Cell) -> float:
|
||||||
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
|
|
||||||
|
def _build_path(self, end: Cell, came_from: dict) -> List[Cell]:
|
||||||
|
path = [end]
|
||||||
|
while end in came_from:
|
||||||
|
end = came_from[end]
|
||||||
|
path.append(end)
|
||||||
|
return path[::-1]
|
||||||
|
@property
|
||||||
|
def visited_count(self) -> int:
|
||||||
|
return self._visited
|
||||||
54
sobininaas/Задание2/patterns.py
Normal file
54
sobininaas/Задание2/patterns.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import List
|
||||||
|
import os
|
||||||
|
from maze_core import Maze, Cell
|
||||||
|
|
||||||
|
class Observer(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def update(self, event: str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConsoleObserver(Observer):
|
||||||
|
def update(self, event: str):
|
||||||
|
print(f"{event}")
|
||||||
|
|
||||||
|
def draw(self, maze: Maze, player: Cell = None, path: List[Cell] = None):
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
path_set = set(path) if path else set()
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
row = []
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.cell_at(x, y)
|
||||||
|
if player and cell == player:
|
||||||
|
row.append('@')
|
||||||
|
elif cell in path_set:
|
||||||
|
row.append('*' if not cell.is_start and not cell.is_exit else str(cell))
|
||||||
|
else:
|
||||||
|
row.append(str(cell))
|
||||||
|
print(''.join(row))
|
||||||
|
class Command(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self): pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def undo(self): pass
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
def __init__(self, start: Cell):
|
||||||
|
self.pos = start
|
||||||
|
|
||||||
|
def move(self, cell: Cell):
|
||||||
|
self.pos = cell
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
def __init__(self, player: Player, new_pos: Cell):
|
||||||
|
self.player = player
|
||||||
|
self.new_pos = new_pos
|
||||||
|
self.old_pos = player.pos
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
self.player.move(self.new_pos)
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
self.player.move(self.old_pos)
|
||||||
51
sobininaas/Задание2/solver.py
Normal file
51
sobininaas/Задание2/solver.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import time
|
||||||
|
from typing import Optional, List
|
||||||
|
from maze_core import Cell, Maze
|
||||||
|
from pathfinding import SearchStrategy
|
||||||
|
|
||||||
|
class SearchStats:
|
||||||
|
def __init__(self, time_ms: float, visited: int, path_len: int):
|
||||||
|
self.time_ms = time_ms
|
||||||
|
self.visited_cells = visited
|
||||||
|
self.path_length = path_len
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Stats({self.time_ms:.2f}ms, {self.visited_cells} cells, {self.path_length} steps)"
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze: Maze, strategy: Optional[SearchStrategy] = None):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = strategy
|
||||||
|
self._path = []
|
||||||
|
self._observers = []
|
||||||
|
|
||||||
|
def set_strategy(self, strategy: SearchStrategy):
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def add_observer(self, observer):
|
||||||
|
self._observers.append(observer)
|
||||||
|
|
||||||
|
def _notify(self, msg: str):
|
||||||
|
for obs in self._observers:
|
||||||
|
obs.update(msg)
|
||||||
|
|
||||||
|
def solve(self) -> SearchStats:
|
||||||
|
if not self.strategy:
|
||||||
|
raise ValueError("Стратегия не выбрана")
|
||||||
|
|
||||||
|
self._notify("Начинаю поиск")
|
||||||
|
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
path = self.strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
|
||||||
|
self._path = path
|
||||||
|
ms = (t1 - t0) * 1000
|
||||||
|
|
||||||
|
self._notify(f"Найден путь: {len(path)} шагов" if path else "Пути не найдено!")
|
||||||
|
|
||||||
|
return SearchStats(ms, self.strategy.visited_count, len(path))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_path(self) -> List[Cell]:
|
||||||
|
return self._path
|
||||||
Loading…
Reference in New Issue
Block a user