[2] Task 2
4
shahovaa/zadanie 2/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
.DS_Store
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
67
shahovaa/zadanie 2/README.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Поиск выхода из лабиринта
|
||||||
|
|
||||||
|
Объектно-ориентированная реализация поиска пути в лабиринте с паттернами GoF:
|
||||||
|
Builder, Strategy, Observer и Command.
|
||||||
|
|
||||||
|
## Что реализовано
|
||||||
|
|
||||||
|
- модель `Cell` и `Maze`;
|
||||||
|
- загрузка лабиринта из текстового файла через `TextFileMazeBuilder`;
|
||||||
|
- стратегии поиска пути: BFS, DFS, A*, Дейкстра;
|
||||||
|
- `MazeSolver`, который измеряет время, число посещенных клеток и длину пути;
|
||||||
|
- консольный `Observer` для сообщений и отрисовки;
|
||||||
|
- `MoveCommand` и `Player` для ручного режима с undo;
|
||||||
|
- генератор тестовых лабиринтов;
|
||||||
|
- экспериментальный скрипт, CSV и SVG-графики;
|
||||||
|
- отчет: `reports/report.md`.
|
||||||
|
|
||||||
|
## Формат лабиринта
|
||||||
|
|
||||||
|
```text
|
||||||
|
# - стена
|
||||||
|
- проход
|
||||||
|
S - старт
|
||||||
|
E - выход
|
||||||
|
2, 3, ~ - проходимые клетки с увеличенным весом
|
||||||
|
```
|
||||||
|
|
||||||
|
Все строки в файле лабиринта должны иметь одинаковую длину.
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/generate_mazes.py
|
||||||
|
python3 main.py --maze data/mazes/small.txt --strategy astar --render
|
||||||
|
```
|
||||||
|
|
||||||
|
Доступные стратегии:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bfs
|
||||||
|
dfs
|
||||||
|
astar
|
||||||
|
dijkstra
|
||||||
|
```
|
||||||
|
|
||||||
|
Ручной режим с командами `W/A/S/D`, undo через `Z`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 main.py --maze data/mazes/small.txt --manual
|
||||||
|
```
|
||||||
|
|
||||||
|
## Эксперименты
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/run_experiments.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт перегенерирует лабиринты, запускает каждую стратегию 10 раз и сохраняет:
|
||||||
|
|
||||||
|
- `reports/results.csv`;
|
||||||
|
- SVG-графики в `reports/charts/`.
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m unittest
|
||||||
|
```
|
||||||
50
shahovaa/zadanie 2/data/mazes/empty.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
##################################################
|
||||||
|
#S #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# E#
|
||||||
|
##################################################
|
||||||
100
shahovaa/zadanie 2/data/mazes/large.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
####################################################################################################
|
||||||
|
#S # # # # # # # # # # # ##
|
||||||
|
### # # ### # # # ####### # # # ### ######### ### # ##### # ############### ### # # # ### ##### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ########### # # # # ##### ### # ### ##### ################### # # # # ##### ####### # ### ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ##### ### ### ##### ##### ### ### ##### ####### # # ### ##### ##### # ### # # # # # # # # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
##### # ######### ### # ### # ### ### # # ####### # ### # # # # # ### # ##### # # # ### ##### # ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### ##### ########### # ####### ##### ######### # ### # # ##### ####### ### # # # ### # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # ##### # # # # ### # # # # ### # ##### ### ### ########### # ##### ### # ### # ### # ####### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ####### # # ####### # # ####### ####### ### ##### # ############### ### # # # # ####### # # ######
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
##### # # # ##### # # ####### ########### # ### # ### # # # ####### ### # ####### # ### # # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### ##### # ############# # # # ### ### # ########### ####### # ### # ######### # ### # ### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### ####### ### # ### # # ##### ### ##### ####### # # ### ### # ######### # # ### ### # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ####### ####### ### # ### ##### # # # # # ##### ##### ### ### # # # ### # ### ### ##### # # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ######### # # # ### ##### ### # ### ### # ##### # ####### ### ##### # # # ##### # ########### # ##
|
||||||
|
# # # # # # # E# # # # # # # # # # # # # # # # ##
|
||||||
|
### ##### # # ####### ### # ### ### ##### ### # # # ### # # ####### # ### ######### # ##### ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ##### ####### # # ##### ######### # # ### ##### # ##### ### # # ####### # # ### # ####### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
####### # # ### ### ### # # # # ############# ##### # # # ##### ### ### ### # # ##### # # # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ####### # # ### ### ### ########### # # # ##### # ##### ### ### # ##### ##### # # # # # # # ######
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ##### # ### ### ########### ############# # # # # # # # ### # ### ##### # # # ##### # # ### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### # # # ### # # ### # # # # # ####### ### # ##### # ### # ####### # # # # # ### # # ####### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### # # # # # ##### ########### # ########### # # ##### ### ##### ### # # # ####### ######### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### ##### # # ##### ##### ########### ##### ### # # ##### ### # # ######### ####### # # ######### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### ### ######### # ### ##### ### # # # ####### ####### # ####### ### # # ####### # # ##### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### ### # ##### # # ### # # ### ### # # # ####### ### # # ##### # ### ### # # ### ### # # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### # # ####### # ### # ### # ############### # ##### # # ##### # ### # # ######### ##### ### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### # ### # # # # ### ##### # ####### ### # ######### # # # # # ##### # # ######### # # ####### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ####### ########### ######### ##### # ### # # ### ####### # ##### ####### # ### ### # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ######### # # ##### # ### ##### ### # ##### # # # # ### ### ### # ### # ### ### # # # ### # # ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### # # # ### # ##### # ##### ######### # # # ### ### # # ### ### # ##### ########### # ### ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### # # # ### ##### # # # ### ### # # # # ### ############# ### # # ### ########### ##### # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ############### # ### # # ##### ##### ### ######### ############# # ####### ##### # # # ######
|
||||||
|
# # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### ############# # # # # ####### ##### # ####### ######### # ### ######### # # ##### ### # # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### # # # # # ######### ####### ### ### # # ### # ### ### # ### ### # ### ##### # # ### ### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### # # ##### # ############### ### ##### # ### ####### # # ### # # ### # # ##### # # # # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### # # # # # # ##### # ####### # ##### # ### ##### ### # # ##### # # ##### # ##### # # # ####### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # # ################### ########### # # ### # ### ##### # # # # ########### ##### ### # # # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ##### ### ######### ########### ### # ########### ### # # ### # # # # # ######### # ### ######
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### # # ##### ### ### # # # # ####### # ##### # ##### # # ### ### ######### ####### # # ##### # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### # # # ### # ### # # ##### ### ##### # ##### # ### # # # # ### # # ### # ############# ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### # ### # # # # # ####### # # # ### ####### # ######### # ### # ### # # ### ##### # # ####### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### # # ######### # ####### # ######### ### # # # ### ### # ##### ### # ##### # ##### # ### ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### # # ##### # ##### # # # ####### ### # ### ### ##### # # # ### # ### # ### # # ######### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### ### ####### ### # # ### ####### # ### ### ##### # # # ### ### # ### # ######### # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ####### ##### # ### # ### ####### ################# ### # # # ##### ### # # # # ### ####### ######
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
##### ##### ##### ##### # ####### # ##### # ### ##### # ### # # ### ########### # # # # # # ### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # # ####### # # # ####### # # # ##### ### ##### ##### ### ########### ### # # # ##### # # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # # # # # ##### ##### # ### # # # ########### ######### ### ########### ### ##### # ### # ######
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### ##### ##### ##### # # # ##### ##### # ################# # ### # ### ######### # # ### # # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### # ##### ### ### # # # # # ### ############# ##### # ##### ##### # # ### # ### # # # # ### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ####### # ##### # ### # ### # ##### ####### # ### # ####### ### ##### # # # # # ##### # # # ### ##
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
50
shahovaa/zadanie 2/data/mazes/medium.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
##################################################
|
||||||
|
#S # # E# # # ##
|
||||||
|
### # ##### # # ########### ### # # # ### ##### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# ### ### # ### ############### ### # # ### # # ##
|
||||||
|
# # # # # # # # # # # ##
|
||||||
|
### ### ##### # ##### ### # # # # ##### # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### # # ### # ### ### # ### # # ### # ######
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
# # ##### ##### # # # # ####### ### ########### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
####### # # ##### # # # # ### ### # # ### ########
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# ### ##### ##### # # ##### # # ############### ##
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
# # ### # ### # ### ### # ### ### # # ##### # ####
|
||||||
|
# # # # # # # # # # # # # # ##
|
||||||
|
# ### ########### ### # ### ### # # ### # # ### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# # ### # ##### # # ######### # # # # # ####### ##
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
# ######### # # # ### ##### # # ##### ### ##### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# ##### # # # ### ##### # ######### ### ##### # ##
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
# # ##### # ### # # # ########### ### # # ### # ##
|
||||||
|
# # # # # # # # # # # # # # # ##
|
||||||
|
# ### # ### # ##### # # ### # ######### # # ######
|
||||||
|
# # # # # # # # # # # ##
|
||||||
|
### # # # ########### ### ############### ##### ##
|
||||||
|
# # # # # # # # # # # ##
|
||||||
|
# ##### ##### # ### ### # # ### # ### # # # # # ##
|
||||||
|
# # # # # # # # # # # # # # ##
|
||||||
|
# ####### # # # # ####### ### ##### ### ##### # ##
|
||||||
|
# # # # # # # # # # # # # # ##
|
||||||
|
# ##### ### # ### # ### # # # # # ##### # # ### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
######### # ######### ####### ########### # # # ##
|
||||||
|
# # # # # # # # ##
|
||||||
|
# ####### ##### # # ####### ######### # ####### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
### # # ### # # # ##### # # # ##### ### # # ######
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
# ############# # # # ##### ######### ### # # # ##
|
||||||
|
# # # # # # # # # # # # # # # ##
|
||||||
|
# ##### # # # ##### # # # ### # # # ### # # # # ##
|
||||||
|
# # # # # # # # # ##
|
||||||
|
##################################################
|
||||||
|
##################################################
|
||||||
30
shahovaa/zadanie 2/data/mazes/no_exit.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
##############################
|
||||||
|
#S############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
##############################
|
||||||
|
############################E#
|
||||||
|
##############################
|
||||||
10
shahovaa/zadanie 2/data/mazes/small.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
##########
|
||||||
|
#S #E#
|
||||||
|
# #### # #
|
||||||
|
# # # #
|
||||||
|
# # #### #
|
||||||
|
# # #
|
||||||
|
# ###### #
|
||||||
|
# #
|
||||||
|
######## #
|
||||||
|
##########
|
||||||
94
shahovaa/zadanie 2/main.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from maze_solver import (
|
||||||
|
AStarStrategy,
|
||||||
|
BFSStrategy,
|
||||||
|
ConsoleView,
|
||||||
|
DFSStrategy,
|
||||||
|
DijkstraStrategy,
|
||||||
|
Direction,
|
||||||
|
MazeSolver,
|
||||||
|
MoveCommand,
|
||||||
|
Player,
|
||||||
|
TextFileMazeBuilder,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
STRATEGIES = {
|
||||||
|
"bfs": BFSStrategy,
|
||||||
|
"dfs": DFSStrategy,
|
||||||
|
"astar": AStarStrategy,
|
||||||
|
"dijkstra": DijkstraStrategy,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description="Find a path through a text maze.")
|
||||||
|
parser.add_argument("--maze", default="data/mazes/small.txt")
|
||||||
|
parser.add_argument(
|
||||||
|
"--strategy",
|
||||||
|
choices=sorted(STRATEGIES),
|
||||||
|
default="astar",
|
||||||
|
help="Path-finding algorithm.",
|
||||||
|
)
|
||||||
|
parser.add_argument("--render", action="store_true", help="Print maze with path.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--manual",
|
||||||
|
action="store_true",
|
||||||
|
help="Manual W/A/S/D mode with Z undo and Q quit.",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
maze = TextFileMazeBuilder().build_from_file(args.maze)
|
||||||
|
strategy = STRATEGIES[args.strategy]()
|
||||||
|
solver = MazeSolver(maze, strategy)
|
||||||
|
view = ConsoleView()
|
||||||
|
solver.add_observer(view)
|
||||||
|
|
||||||
|
stats = solver.solve()
|
||||||
|
print(
|
||||||
|
f"Summary: strategy={stats.strategy_name}, time={stats.time_ms:.3f} ms, "
|
||||||
|
f"visited={stats.visited_cells}, path_length={stats.path_length}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.render:
|
||||||
|
print(view.render(maze, path=stats.path))
|
||||||
|
|
||||||
|
if args.manual:
|
||||||
|
run_manual_mode(maze, view)
|
||||||
|
|
||||||
|
|
||||||
|
def run_manual_mode(maze, view: ConsoleView) -> None:
|
||||||
|
player = Player.at_start(maze)
|
||||||
|
history: list[MoveCommand] = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print(view.render(maze, player_position=player.current_cell))
|
||||||
|
if player.current_cell == maze.exit:
|
||||||
|
print("Exit reached.")
|
||||||
|
return
|
||||||
|
|
||||||
|
key = input("Move W/A/S/D, undo Z, quit Q: ").strip().lower()
|
||||||
|
if key == "q":
|
||||||
|
return
|
||||||
|
if key == "z":
|
||||||
|
if history:
|
||||||
|
history.pop().undo()
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
command = MoveCommand(player, Direction.from_key(key))
|
||||||
|
except ValueError as exc:
|
||||||
|
print(exc)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if command.execute():
|
||||||
|
history.append(command)
|
||||||
|
else:
|
||||||
|
print("Move blocked.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
34
shahovaa/zadanie 2/maze_solver/__init__.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
from .builders import MazeBuilder, TextFileMazeBuilder
|
||||||
|
from .commands import Direction, MoveCommand, Player
|
||||||
|
from .models import Cell, Maze
|
||||||
|
from .observers import ConsoleView, Event, Observer
|
||||||
|
from .solver import MazeSolver, SearchStats
|
||||||
|
from .strategies import (
|
||||||
|
AStarStrategy,
|
||||||
|
BFSStrategy,
|
||||||
|
DFSStrategy,
|
||||||
|
DijkstraStrategy,
|
||||||
|
PathFindingStrategy,
|
||||||
|
PathResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"AStarStrategy",
|
||||||
|
"BFSStrategy",
|
||||||
|
"Cell",
|
||||||
|
"ConsoleView",
|
||||||
|
"DFSStrategy",
|
||||||
|
"DijkstraStrategy",
|
||||||
|
"Direction",
|
||||||
|
"Event",
|
||||||
|
"Maze",
|
||||||
|
"MazeBuilder",
|
||||||
|
"MazeSolver",
|
||||||
|
"MoveCommand",
|
||||||
|
"Observer",
|
||||||
|
"PathFindingStrategy",
|
||||||
|
"PathResult",
|
||||||
|
"Player",
|
||||||
|
"SearchStats",
|
||||||
|
"TextFileMazeBuilder",
|
||||||
|
]
|
||||||
75
shahovaa/zadanie 2/maze_solver/builders.py
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .models import Cell, Maze
|
||||||
|
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def build_from_file(self, filename: str | Path) -> Maze:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def buildFromFile(self, filename: str | Path) -> Maze:
|
||||||
|
return self.build_from_file(filename)
|
||||||
|
|
||||||
|
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
WALL = "#"
|
||||||
|
START = "S"
|
||||||
|
EXIT = "E"
|
||||||
|
PASSAGES = {" ", "."}
|
||||||
|
WEIGHTS = {"1": 1, "2": 2, "3": 3, "~": 3}
|
||||||
|
|
||||||
|
def build_from_file(self, filename: str | Path) -> Maze:
|
||||||
|
path = Path(filename)
|
||||||
|
rows = path.read_text(encoding="utf-8").splitlines()
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
raise ValueError(f"Maze file is empty: {path}")
|
||||||
|
|
||||||
|
width = len(rows[0])
|
||||||
|
if width == 0:
|
||||||
|
raise ValueError("Maze width must be greater than zero")
|
||||||
|
if any(len(row) != width for row in rows):
|
||||||
|
raise ValueError("All maze rows must have the same width")
|
||||||
|
|
||||||
|
cells: list[list[Cell]] = []
|
||||||
|
start: Cell | None = None
|
||||||
|
exit: Cell | None = None
|
||||||
|
|
||||||
|
for y, row in enumerate(rows):
|
||||||
|
cell_row: list[Cell] = []
|
||||||
|
for x, char in enumerate(row):
|
||||||
|
cell = self._create_cell(x, y, char)
|
||||||
|
if cell.is_start:
|
||||||
|
if start is not None:
|
||||||
|
raise ValueError("Maze must contain exactly one start cell")
|
||||||
|
start = cell
|
||||||
|
if cell.is_exit:
|
||||||
|
if exit is not None:
|
||||||
|
raise ValueError("Maze must contain exactly one exit cell")
|
||||||
|
exit = cell
|
||||||
|
cell_row.append(cell)
|
||||||
|
cells.append(cell_row)
|
||||||
|
|
||||||
|
if start is None:
|
||||||
|
raise ValueError("Maze must contain a start cell marked with 'S'")
|
||||||
|
if exit is None:
|
||||||
|
raise ValueError("Maze must contain an exit cell marked with 'E'")
|
||||||
|
|
||||||
|
return Maze(cells, start, exit)
|
||||||
|
|
||||||
|
def _create_cell(self, x: int, y: int, char: str) -> Cell:
|
||||||
|
if char == self.WALL:
|
||||||
|
return Cell(x=x, y=y, is_wall=True, symbol=char)
|
||||||
|
if char == self.START:
|
||||||
|
return Cell(x=x, y=y, is_start=True, symbol=char)
|
||||||
|
if char == self.EXIT:
|
||||||
|
return Cell(x=x, y=y, is_exit=True, symbol=char)
|
||||||
|
if char in self.PASSAGES:
|
||||||
|
return Cell(x=x, y=y, symbol=" ")
|
||||||
|
if char in self.WEIGHTS:
|
||||||
|
return Cell(x=x, y=y, weight=self.WEIGHTS[char], symbol=char)
|
||||||
|
raise ValueError(f"Unsupported maze symbol {char!r} at ({x}, {y})")
|
||||||
79
shahovaa/zadanie 2/maze_solver/commands.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from .models import Cell, Maze
|
||||||
|
|
||||||
|
|
||||||
|
class Direction(Enum):
|
||||||
|
UP = (0, -1)
|
||||||
|
RIGHT = (1, 0)
|
||||||
|
DOWN = (0, 1)
|
||||||
|
LEFT = (-1, 0)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_key(cls, key: str) -> "Direction":
|
||||||
|
mapping = {
|
||||||
|
"w": cls.UP,
|
||||||
|
"d": cls.RIGHT,
|
||||||
|
"s": cls.DOWN,
|
||||||
|
"a": cls.LEFT,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
return mapping[key.lower()]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise ValueError("Use W/A/S/D for movement") from exc
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Player:
|
||||||
|
maze: Maze
|
||||||
|
current_cell: Cell
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def at_start(cls, maze: Maze) -> "Player":
|
||||||
|
return cls(maze=maze, current_cell=maze.start)
|
||||||
|
|
||||||
|
def move_to(self, cell: Cell) -> None:
|
||||||
|
if not cell.is_passable():
|
||||||
|
raise ValueError("Player cannot move into a wall")
|
||||||
|
self.current_cell = cell
|
||||||
|
|
||||||
|
|
||||||
|
class Command(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def undo(self) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
def __init__(self, player: Player, direction: Direction) -> None:
|
||||||
|
self.player = player
|
||||||
|
self.direction = direction
|
||||||
|
self.previous_cell: Cell | None = None
|
||||||
|
self.executed = False
|
||||||
|
|
||||||
|
def execute(self) -> bool:
|
||||||
|
dx, dy = self.direction.value
|
||||||
|
current = self.player.current_cell
|
||||||
|
target = self.player.maze.get_cell(current.x + dx, current.y + dy)
|
||||||
|
if target is None or not target.is_passable():
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.previous_cell = current
|
||||||
|
self.player.move_to(target)
|
||||||
|
self.executed = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
def undo(self) -> bool:
|
||||||
|
if not self.executed or self.previous_cell is None:
|
||||||
|
return False
|
||||||
|
self.player.move_to(self.previous_cell)
|
||||||
|
self.executed = False
|
||||||
|
return True
|
||||||
81
shahovaa/zadanie 2/maze_solver/models.py
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Cell:
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
is_wall: bool = False
|
||||||
|
is_start: bool = False
|
||||||
|
is_exit: bool = False
|
||||||
|
weight: int = 1
|
||||||
|
symbol: str = " "
|
||||||
|
|
||||||
|
def is_passable(self) -> bool:
|
||||||
|
return not self.is_wall
|
||||||
|
|
||||||
|
def isPassable(self) -> bool:
|
||||||
|
return self.is_passable()
|
||||||
|
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, cells: list[list[Cell]], start: Cell, exit: Cell) -> None:
|
||||||
|
if not cells or not cells[0]:
|
||||||
|
raise ValueError("Maze must contain at least one cell")
|
||||||
|
|
||||||
|
width = len(cells[0])
|
||||||
|
if any(len(row) != width for row in cells):
|
||||||
|
raise ValueError("Maze rows must have equal width")
|
||||||
|
|
||||||
|
self.cells = cells
|
||||||
|
self.height = len(cells)
|
||||||
|
self.width = width
|
||||||
|
self.start = start
|
||||||
|
self.exit = exit
|
||||||
|
|
||||||
|
def get_cell(self, x: int, y: int) -> Cell | None:
|
||||||
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||||||
|
return self.cells[y][x]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getCell(self, x: int, y: int) -> Cell | None:
|
||||||
|
return self.get_cell(x, y)
|
||||||
|
|
||||||
|
def get_neighbors(self, cell: Cell) -> list[Cell]:
|
||||||
|
neighbors: list[Cell] = []
|
||||||
|
for dx, dy in ((0, -1), (1, 0), (0, 1), (-1, 0)):
|
||||||
|
neighbor = self.get_cell(cell.x + dx, cell.y + dy)
|
||||||
|
if neighbor is not None and neighbor.is_passable():
|
||||||
|
neighbors.append(neighbor)
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
def getNeighbors(self, cell: Cell) -> list[Cell]:
|
||||||
|
return self.get_neighbors(cell)
|
||||||
|
|
||||||
|
def to_text(self, path: list[Cell] | None = None, player: Cell | None = None) -> str:
|
||||||
|
path_cells = {(cell.x, cell.y) for cell in path or []}
|
||||||
|
lines: list[str] = []
|
||||||
|
|
||||||
|
for row in self.cells:
|
||||||
|
chars: list[str] = []
|
||||||
|
for cell in row:
|
||||||
|
position = (cell.x, cell.y)
|
||||||
|
if player is not None and position == (player.x, player.y):
|
||||||
|
chars.append("@")
|
||||||
|
elif cell.is_start:
|
||||||
|
chars.append("S")
|
||||||
|
elif cell.is_exit:
|
||||||
|
chars.append("E")
|
||||||
|
elif cell.is_wall:
|
||||||
|
chars.append("#")
|
||||||
|
elif position in path_cells:
|
||||||
|
chars.append(".")
|
||||||
|
elif cell.weight > 1:
|
||||||
|
chars.append(str(cell.weight))
|
||||||
|
else:
|
||||||
|
chars.append(" ")
|
||||||
|
lines.append("".join(chars))
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
40
shahovaa/zadanie 2/maze_solver/observers.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .models import Cell, Maze
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Event:
|
||||||
|
event_type: str
|
||||||
|
payload: dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class Observer(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def update(self, event: Event) -> None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleView(Observer):
|
||||||
|
def update(self, event: Event) -> None:
|
||||||
|
if event.event_type == "search_started":
|
||||||
|
print(f"Search started: {event.payload['strategy']}")
|
||||||
|
elif event.event_type in {"path_found", "path_not_found"}:
|
||||||
|
stats = event.payload["stats"]
|
||||||
|
print(
|
||||||
|
f"{event.event_type}: strategy={stats.strategy_name}, "
|
||||||
|
f"time={stats.time_ms:.3f} ms, visited={stats.visited_cells}, "
|
||||||
|
f"path_length={stats.path_length}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(
|
||||||
|
self,
|
||||||
|
maze: Maze,
|
||||||
|
player_position: Cell | None = None,
|
||||||
|
path: list[Cell] | None = None,
|
||||||
|
) -> str:
|
||||||
|
return maze.to_text(path=path, player=player_position)
|
||||||
57
shahovaa/zadanie 2/maze_solver/solver.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .models import Cell, Maze
|
||||||
|
from .observers import Event, Observer
|
||||||
|
from .strategies import PathFindingStrategy
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SearchStats:
|
||||||
|
strategy_name: str
|
||||||
|
time_ms: float
|
||||||
|
visited_cells: int
|
||||||
|
path_length: int
|
||||||
|
path: list[Cell]
|
||||||
|
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze: Maze, strategy: PathFindingStrategy) -> None:
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = strategy
|
||||||
|
self._observers: list[Observer] = []
|
||||||
|
|
||||||
|
def set_strategy(self, strategy: PathFindingStrategy) -> None:
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def setStrategy(self, strategy: PathFindingStrategy) -> None:
|
||||||
|
self.set_strategy(strategy)
|
||||||
|
|
||||||
|
def add_observer(self, observer: Observer) -> None:
|
||||||
|
self._observers.append(observer)
|
||||||
|
|
||||||
|
def remove_observer(self, observer: Observer) -> None:
|
||||||
|
self._observers.remove(observer)
|
||||||
|
|
||||||
|
def solve(self) -> SearchStats:
|
||||||
|
self._notify(Event("search_started", {"strategy": self.strategy.name}))
|
||||||
|
started_at = time.perf_counter()
|
||||||
|
result = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||||||
|
elapsed_ms = (time.perf_counter() - started_at) * 1000
|
||||||
|
|
||||||
|
stats = SearchStats(
|
||||||
|
strategy_name=self.strategy.name,
|
||||||
|
time_ms=elapsed_ms,
|
||||||
|
visited_cells=result.visited_count,
|
||||||
|
path_length=len(result.path),
|
||||||
|
path=result.path,
|
||||||
|
)
|
||||||
|
event_name = "path_found" if result.path else "path_not_found"
|
||||||
|
self._notify(Event(event_name, {"stats": stats}))
|
||||||
|
return stats
|
||||||
|
|
||||||
|
def _notify(self, event: Event) -> None:
|
||||||
|
for observer in self._observers:
|
||||||
|
observer.update(event)
|
||||||
150
shahovaa/zadanie 2/maze_solver/strategies.py
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import heapq
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from collections import deque
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from itertools import count
|
||||||
|
|
||||||
|
from .models import Cell, Maze
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PathResult:
|
||||||
|
path: list[Cell]
|
||||||
|
visited_count: int
|
||||||
|
|
||||||
|
|
||||||
|
class PathFindingStrategy(ABC):
|
||||||
|
name = "abstract"
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> PathResult:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> PathResult:
|
||||||
|
return self.find_path(maze, start, exit)
|
||||||
|
|
||||||
|
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
name = "BFS"
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> PathResult:
|
||||||
|
queue: deque[Cell] = deque([start])
|
||||||
|
parents: dict[Cell, Cell | None] = {start: None}
|
||||||
|
visited = {start}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
if current == exit:
|
||||||
|
return PathResult(_reconstruct_path(parents, exit), len(visited))
|
||||||
|
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
if neighbor not in visited:
|
||||||
|
visited.add(neighbor)
|
||||||
|
parents[neighbor] = current
|
||||||
|
queue.append(neighbor)
|
||||||
|
|
||||||
|
return PathResult([], len(visited))
|
||||||
|
|
||||||
|
|
||||||
|
class DFSStrategy(PathFindingStrategy):
|
||||||
|
name = "DFS"
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> PathResult:
|
||||||
|
stack = [start]
|
||||||
|
parents: dict[Cell, Cell | None] = {start: None}
|
||||||
|
visited = {start}
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
current = stack.pop()
|
||||||
|
if current == exit:
|
||||||
|
return PathResult(_reconstruct_path(parents, exit), len(visited))
|
||||||
|
|
||||||
|
for neighbor in reversed(maze.get_neighbors(current)):
|
||||||
|
if neighbor not in visited:
|
||||||
|
visited.add(neighbor)
|
||||||
|
parents[neighbor] = current
|
||||||
|
stack.append(neighbor)
|
||||||
|
|
||||||
|
return PathResult([], len(visited))
|
||||||
|
|
||||||
|
|
||||||
|
class DijkstraStrategy(PathFindingStrategy):
|
||||||
|
name = "Dijkstra"
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> PathResult:
|
||||||
|
tie_breaker = count()
|
||||||
|
heap: list[tuple[int, int, Cell]] = [(0, next(tie_breaker), start)]
|
||||||
|
distances: dict[Cell, int] = {start: 0}
|
||||||
|
parents: dict[Cell, Cell | None] = {start: None}
|
||||||
|
visited: set[Cell] = set()
|
||||||
|
|
||||||
|
while heap:
|
||||||
|
current_distance, _, current = heapq.heappop(heap)
|
||||||
|
if current in visited:
|
||||||
|
continue
|
||||||
|
visited.add(current)
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
return PathResult(_reconstruct_path(parents, exit), len(visited))
|
||||||
|
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
new_distance = current_distance + neighbor.weight
|
||||||
|
if new_distance < distances.get(neighbor, 10**12):
|
||||||
|
distances[neighbor] = new_distance
|
||||||
|
parents[neighbor] = current
|
||||||
|
heapq.heappush(heap, (new_distance, next(tie_breaker), neighbor))
|
||||||
|
|
||||||
|
return PathResult([], len(visited))
|
||||||
|
|
||||||
|
|
||||||
|
class AStarStrategy(PathFindingStrategy):
|
||||||
|
name = "A*"
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> PathResult:
|
||||||
|
tie_breaker = count()
|
||||||
|
start_heuristic = _manhattan(start, exit)
|
||||||
|
heap: list[tuple[int, int, int, Cell]] = [
|
||||||
|
(start_heuristic, start_heuristic, next(tie_breaker), start)
|
||||||
|
]
|
||||||
|
g_score: dict[Cell, int] = {start: 0}
|
||||||
|
parents: dict[Cell, Cell | None] = {start: None}
|
||||||
|
visited: set[Cell] = set()
|
||||||
|
|
||||||
|
while heap:
|
||||||
|
_, _, _, current = heapq.heappop(heap)
|
||||||
|
if current in visited:
|
||||||
|
continue
|
||||||
|
visited.add(current)
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
return PathResult(_reconstruct_path(parents, exit), len(visited))
|
||||||
|
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
tentative_score = g_score[current] + neighbor.weight
|
||||||
|
if tentative_score < g_score.get(neighbor, 10**12):
|
||||||
|
g_score[neighbor] = tentative_score
|
||||||
|
parents[neighbor] = current
|
||||||
|
heuristic = _manhattan(neighbor, exit)
|
||||||
|
priority = tentative_score + heuristic
|
||||||
|
heapq.heappush(
|
||||||
|
heap,
|
||||||
|
(priority, heuristic, next(tie_breaker), neighbor),
|
||||||
|
)
|
||||||
|
|
||||||
|
return PathResult([], len(visited))
|
||||||
|
|
||||||
|
|
||||||
|
def _reconstruct_path(parents: dict[Cell, Cell | None], end: Cell) -> list[Cell]:
|
||||||
|
path: list[Cell] = []
|
||||||
|
current: Cell | None = end
|
||||||
|
while current is not None:
|
||||||
|
path.append(current)
|
||||||
|
current = parents[current]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def _manhattan(first: Cell, second: Cell) -> int:
|
||||||
|
return abs(first.x - second.x) + abs(first.y - second.y)
|
||||||
28
shahovaa/zadanie 2/reports/charts/empty_time.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Пустой 50x50: среднее время, мс</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">1.01</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">2.02</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">3.03</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">4.04</text>
|
||||||
|
<rect x="109.0" y="124.5" width="96.0" height="177.5" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="157.0" y="116.5" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">2.89</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="293.4" width="96.0" height="8.6" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="327.0" y="285.4" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.14</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="287.4" width="96.0" height="14.6" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="497.0" y="279.4" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.24</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="54.0" width="96.0" height="248.0" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="667.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">4.04</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/empty_visited.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Пустой 50x50: посещенные клетки</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">576.00</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">1152.00</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">1728.00</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">2304.00</text>
|
||||||
|
<rect x="109.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="157.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">2304.00</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="281.9" width="96.0" height="20.1" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="327.0" y="273.9" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">187.00</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="291.8" width="96.0" height="10.2" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="497.0" y="283.8" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">95.00</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="667.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">2304.00</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/large_time.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Большой 100x100: среднее время, мс</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">2.16</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">4.33</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">6.49</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">8.66</text>
|
||||||
|
<rect x="109.0" y="150.2" width="96.0" height="151.8" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="157.0" y="142.2" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">5.30</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="230.3" width="96.0" height="71.7" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="327.0" y="222.3" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">2.50</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="54.0" width="96.0" height="248.0" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="497.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">8.66</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="97.1" width="96.0" height="204.9" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="667.0" y="89.1" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">7.15</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/large_visited.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Большой 100x100: посещенные клетки</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">1200.25</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">2400.50</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">3600.75</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">4801.00</text>
|
||||||
|
<rect x="109.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="157.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">4801.00</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="190.7" width="96.0" height="111.3" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="327.0" y="182.7" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">2155.00</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="54.5" width="96.0" height="247.5" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="497.0" y="46.5" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">4791.00</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="54.1" width="96.0" height="247.9" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="667.0" y="46.1" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">4800.00</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/medium_time.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Средний 50x50: среднее время, мс</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.50</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">1.00</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">1.51</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">2.01</text>
|
||||||
|
<rect x="109.0" y="144.4" width="96.0" height="157.6" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="157.0" y="136.4" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1.28</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="189.6" width="96.0" height="112.4" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="327.0" y="181.6" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.91</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="54.0" width="96.0" height="248.0" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="497.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">2.01</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="91.6" width="96.0" height="210.4" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="667.0" y="83.6" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1.70</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/medium_visited.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Средний 50x50: посещенные клетки</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">287.75</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">575.50</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">863.25</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">1151.00</text>
|
||||||
|
<rect x="109.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="157.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1151.00</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="133.1" width="96.0" height="168.9" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="327.0" y="125.1" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">784.00</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="57.9" width="96.0" height="244.1" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="497.0" y="49.9" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1133.00</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="667.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1151.00</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/no_exit_time.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Без пути 30x30: среднее время, мс</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<rect x="109.0" y="95.3" width="96.0" height="206.7" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="157.0" y="87.3" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.00</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="122.9" width="96.0" height="179.1" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="327.0" y="114.9" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.00</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="81.6" width="96.0" height="220.4" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="497.0" y="73.6" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.00</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="54.0" width="96.0" height="248.0" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="667.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.00</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/no_exit_visited.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Без пути 30x30: посещенные клетки</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.25</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.50</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.75</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">1.00</text>
|
||||||
|
<rect x="109.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="157.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1.00</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="327.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1.00</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="497.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1.00</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="667.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">1.00</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/small_time.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Маленький 10x10: среднее время, мс</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.02</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.03</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.05</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.07</text>
|
||||||
|
<rect x="109.0" y="147.0" width="96.0" height="155.0" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="157.0" y="139.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.04</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="202.0" width="96.0" height="100.0" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="327.0" y="194.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.03</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="54.0" width="96.0" height="248.0" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="497.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.07</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="100.2" width="96.0" height="201.8" fill="#2f6fbb" rx="3"/>
|
||||||
|
<text x="667.0" y="92.2" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">0.06</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
28
shahovaa/zadanie 2/reports/charts/small_visited.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="780" height="360" viewBox="0 0 780 360">
|
||||||
|
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||||
|
<text x="72" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">Маленький 10x10: посещенные клетки</text>
|
||||||
|
<line x1="72" y1="302" x2="752" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="72" y1="54" x2="72" y2="302" stroke="#9aa5b1"/>
|
||||||
|
<line x1="67" y1="302.0" x2="752" y2="302.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="306.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">0.00</text>
|
||||||
|
<line x1="67" y1="240.0" x2="752" y2="240.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="244.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">9.25</text>
|
||||||
|
<line x1="67" y1="178.0" x2="752" y2="178.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="182.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">18.50</text>
|
||||||
|
<line x1="67" y1="116.0" x2="752" y2="116.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="120.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">27.75</text>
|
||||||
|
<line x1="67" y1="54.0" x2="752" y2="54.0" stroke="#edf0f2"/>
|
||||||
|
<text x="62" y="58.0" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">37.00</text>
|
||||||
|
<rect x="109.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="157.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">37.00</text>
|
||||||
|
<text x="157.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">BFS</text>
|
||||||
|
<rect x="279.0" y="141.1" width="96.0" height="160.9" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="327.0" y="133.1" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">24.00</text>
|
||||||
|
<text x="327.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">DFS</text>
|
||||||
|
<rect x="449.0" y="94.2" width="96.0" height="207.8" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="497.0" y="86.2" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">31.00</text>
|
||||||
|
<text x="497.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">A*</text>
|
||||||
|
<rect x="619.0" y="54.0" width="96.0" height="248.0" fill="#2f8f5b" rx="3"/>
|
||||||
|
<text x="667.0" y="46.0" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">37.00</text>
|
||||||
|
<text x="667.0" y="326" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">Dijkstra</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
208
shahovaa/zadanie 2/reports/report.md
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
# Отчет по заданию: поиск выхода из лабиринта
|
||||||
|
|
||||||
|
## 1. Описание задачи и выбранных паттернов
|
||||||
|
|
||||||
|
Цель работы - реализовать расширяемую программу для загрузки лабиринта из файла,
|
||||||
|
поиска пути от старта `S` до выхода `E`, визуализации результата и сравнения
|
||||||
|
алгоритмов на лабиринтах разной сложности.
|
||||||
|
|
||||||
|
В проекте реализованы четыре паттерна GoF:
|
||||||
|
|
||||||
|
| Паттерн | Где реализован | Зачем нужен |
|
||||||
|
|---|---|---|
|
||||||
|
| Builder | `MazeBuilder`, `TextFileMazeBuilder` | Изолирует парсинг и валидацию файла от остального приложения. |
|
||||||
|
| Strategy | `PathFindingStrategy`, `BFSStrategy`, `DFSStrategy`, `AStarStrategy`, `DijkstraStrategy` | Позволяет менять алгоритм поиска без изменения `MazeSolver`. |
|
||||||
|
| Observer | `Observer`, `ConsoleView`, события `search_started`, `path_found`, `path_not_found` | Отделяет вычисления от отображения в консоли. |
|
||||||
|
| Command | `Command`, `MoveCommand`, `Player` | Инкапсулирует ход игрока и поддерживает отмену хода. |
|
||||||
|
|
||||||
|
Диаграмма классов:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Cell {
|
||||||
|
+int x
|
||||||
|
+int y
|
||||||
|
+bool is_wall
|
||||||
|
+bool is_start
|
||||||
|
+bool is_exit
|
||||||
|
+int weight
|
||||||
|
+is_passable() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
class Maze {
|
||||||
|
+list cells
|
||||||
|
+int width
|
||||||
|
+int height
|
||||||
|
+Cell start
|
||||||
|
+Cell exit
|
||||||
|
+get_cell(x, y) Cell
|
||||||
|
+get_neighbors(cell) list
|
||||||
|
+to_text(path, player) str
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeBuilder {
|
||||||
|
<<interface>>
|
||||||
|
+build_from_file(filename) Maze
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextFileMazeBuilder {
|
||||||
|
+build_from_file(filename) Maze
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathFindingStrategy {
|
||||||
|
<<interface>>
|
||||||
|
+find_path(maze, start, exit) PathResult
|
||||||
|
}
|
||||||
|
|
||||||
|
class BFSStrategy
|
||||||
|
class DFSStrategy
|
||||||
|
class AStarStrategy
|
||||||
|
class DijkstraStrategy
|
||||||
|
|
||||||
|
class SearchStats {
|
||||||
|
+str strategy_name
|
||||||
|
+float time_ms
|
||||||
|
+int visited_cells
|
||||||
|
+int path_length
|
||||||
|
+list path
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeSolver {
|
||||||
|
+set_strategy(strategy)
|
||||||
|
+add_observer(observer)
|
||||||
|
+solve() SearchStats
|
||||||
|
}
|
||||||
|
|
||||||
|
class Observer {
|
||||||
|
<<interface>>
|
||||||
|
+update(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleView {
|
||||||
|
+update(event)
|
||||||
|
+render(maze, player_position, path) str
|
||||||
|
}
|
||||||
|
|
||||||
|
class Command {
|
||||||
|
<<interface>>
|
||||||
|
+execute() bool
|
||||||
|
+undo() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveCommand
|
||||||
|
class Player
|
||||||
|
|
||||||
|
MazeBuilder <|.. TextFileMazeBuilder
|
||||||
|
MazeBuilder --> Maze : creates
|
||||||
|
PathFindingStrategy <|.. BFSStrategy
|
||||||
|
PathFindingStrategy <|.. DFSStrategy
|
||||||
|
PathFindingStrategy <|.. AStarStrategy
|
||||||
|
PathFindingStrategy <|.. DijkstraStrategy
|
||||||
|
MazeSolver --> PathFindingStrategy : uses
|
||||||
|
MazeSolver --> Maze : uses
|
||||||
|
MazeSolver --> Observer : notifies
|
||||||
|
Observer <|.. ConsoleView
|
||||||
|
Command <|.. MoveCommand
|
||||||
|
MoveCommand --> Player
|
||||||
|
Player --> Cell
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Ключевые классы
|
||||||
|
|
||||||
|
Основные файлы проекта:
|
||||||
|
|
||||||
|
| Файл | Назначение |
|
||||||
|
|---|---|
|
||||||
|
| `maze_solver/models.py` | Классы `Cell` и `Maze`, поиск соседей, текстовая отрисовка. |
|
||||||
|
| `maze_solver/builders.py` | Интерфейс Builder и загрузка лабиринта из `.txt`. |
|
||||||
|
| `maze_solver/strategies.py` | BFS, DFS, A* и Дейкстра. |
|
||||||
|
| `maze_solver/solver.py` | Оркестратор поиска и сбор статистики. |
|
||||||
|
| `maze_solver/observers.py` | Observer и консольное представление. |
|
||||||
|
| `maze_solver/commands.py` | Command, игрок и undo перемещения. |
|
||||||
|
| `main.py` | CLI для запуска поиска и ручного режима. |
|
||||||
|
| `scripts/run_experiments.py` | Замеры и построение SVG-графиков. |
|
||||||
|
|
||||||
|
Пример запуска:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 main.py --maze data/mazes/small.txt --strategy astar --render
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Результаты экспериментов
|
||||||
|
|
||||||
|
Для каждого лабиринта и каждой стратегии выполнено 10 запусков. В таблице указаны
|
||||||
|
средние значения. Длина пути считается в клетках, включая старт и выход.
|
||||||
|
|
||||||
|
| Лабиринт | Стратегия | Время, мс | Посещено клеток | Длина пути | Путь найден |
|
||||||
|
|---|---:|---:|---:|---:|---|
|
||||||
|
| Маленький 10x10 | BFS | 0.0423 | 37.0 | 20.0 | да |
|
||||||
|
| Маленький 10x10 | DFS | 0.0273 | 24.0 | 22.0 | да |
|
||||||
|
| Маленький 10x10 | A* | 0.0677 | 31.0 | 20.0 | да |
|
||||||
|
| Маленький 10x10 | Dijkstra | 0.0551 | 37.0 | 20.0 | да |
|
||||||
|
| Средний 50x50 | BFS | 1.2769 | 1151.0 | 709.0 | да |
|
||||||
|
| Средний 50x50 | DFS | 0.9106 | 784.0 | 709.0 | да |
|
||||||
|
| Средний 50x50 | A* | 2.0089 | 1133.0 | 709.0 | да |
|
||||||
|
| Средний 50x50 | Dijkstra | 1.7041 | 1151.0 | 709.0 | да |
|
||||||
|
| Большой 100x100 | BFS | 5.2983 | 4801.0 | 1685.0 | да |
|
||||||
|
| Большой 100x100 | DFS | 2.5044 | 2155.0 | 1685.0 | да |
|
||||||
|
| Большой 100x100 | A* | 8.6574 | 4791.0 | 1685.0 | да |
|
||||||
|
| Большой 100x100 | Dijkstra | 7.1532 | 4800.0 | 1685.0 | да |
|
||||||
|
| Пустой 50x50 | BFS | 2.8927 | 2304.0 | 95.0 | да |
|
||||||
|
| Пустой 50x50 | DFS | 0.1404 | 187.0 | 95.0 | да |
|
||||||
|
| Пустой 50x50 | A* | 0.2374 | 95.0 | 95.0 | да |
|
||||||
|
| Пустой 50x50 | Dijkstra | 4.0408 | 2304.0 | 95.0 | да |
|
||||||
|
| Без пути 30x30 | BFS | 0.0015 | 1.0 | 0.0 | нет |
|
||||||
|
| Без пути 30x30 | DFS | 0.0013 | 1.0 | 0.0 | нет |
|
||||||
|
| Без пути 30x30 | A* | 0.0016 | 1.0 | 0.0 | нет |
|
||||||
|
| Без пути 30x30 | Dijkstra | 0.0018 | 1.0 | 0.0 | нет |
|
||||||
|
|
||||||
|
CSV с результатами сохранен в `reports/results.csv`.
|
||||||
|
|
||||||
|
Графики:
|
||||||
|
|
||||||
|
| Лабиринт | Время | Посещенные клетки |
|
||||||
|
|---|---|---|
|
||||||
|
| Маленький |  |  |
|
||||||
|
| Средний |  |  |
|
||||||
|
| Большой |  |  |
|
||||||
|
| Пустой |  |  |
|
||||||
|
| Без пути |  |  |
|
||||||
|
|
||||||
|
## 4. Анализ эффективности
|
||||||
|
|
||||||
|
BFS гарантирует кратчайший путь в невзвешенном лабиринте. Это видно на маленьком
|
||||||
|
лабиринте: BFS, A* и Дейкстра нашли путь длиной 20, а DFS нашел более длинный путь
|
||||||
|
длиной 22. Недостаток BFS - широкий фронт поиска, из-за чего в пустом лабиринте он
|
||||||
|
посетил все 2304 доступные клетки.
|
||||||
|
|
||||||
|
DFS не гарантирует кратчайший путь, но часто работает быстро, потому что уходит
|
||||||
|
глубоко по одному направлению. На маленьком лабиринте это дало путь хуже оптимального.
|
||||||
|
На сгенерированных идеальных лабиринтах путь между двумя клетками единственный, поэтому
|
||||||
|
DFS, BFS, A* и Дейкстра получили одинаковую длину пути.
|
||||||
|
|
||||||
|
A* использует манхэттенскую эвристику. На пустом лабиринте он посетил только 95 клеток,
|
||||||
|
то есть фактически прошел по оптимальному маршруту. В запутанных идеальных лабиринтах
|
||||||
|
эвристика помогает слабее: прямое направление к выходу часто упирается в стены, поэтому
|
||||||
|
A* посещает почти столько же клеток, сколько BFS, а из-за приоритетной очереди тратит
|
||||||
|
больше времени.
|
||||||
|
|
||||||
|
Дейкстра в невзвешенном лабиринте по результату близок к BFS, но работает медленнее
|
||||||
|
из-за приоритетной очереди. Его преимущество проявляется при взвешенных клетках.
|
||||||
|
В проекте Builder уже поддерживает символы `2`, `3` и `~` как клетки с повышенной
|
||||||
|
стоимостью прохода, поэтому Дейкстру и A* можно использовать для дополнительного
|
||||||
|
сравнения на взвешенных картах.
|
||||||
|
|
||||||
|
Лабиринт "Без пути" проверяет корректную обработку отсутствия решения: стратегии
|
||||||
|
возвращают пустой путь, а `MazeSolver` фиксирует длину 0.
|
||||||
|
|
||||||
|
## 5. Выводы
|
||||||
|
|
||||||
|
ООП позволило разделить предметную модель, загрузку данных, алгоритмы и интерфейс.
|
||||||
|
Паттерн Builder делает формат входного файла заменяемым: можно добавить JSON-builder,
|
||||||
|
не меняя `Maze` и стратегии. Strategy позволяет добавлять новые алгоритмы без правок
|
||||||
|
в `MazeSolver`. Observer отделяет вычисления от вывода, а Command показывает, как
|
||||||
|
инкапсулировать пользовательские действия и поддержать undo.
|
||||||
|
|
||||||
|
Без этих паттернов код быстро стал бы монолитным: парсинг файла, поиск, статистика,
|
||||||
|
печать и ручное управление оказались бы в одном месте. Тогда добавление нового формата,
|
||||||
|
алгоритма или режима отображения требовало бы менять уже работающую логику.
|
||||||
21
shahovaa/zadanie 2/reports/results.csv
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути,путь_найден,запусков
|
||||||
|
Маленький 10x10,BFS,0.0423,37.0,20.0,да,10
|
||||||
|
Маленький 10x10,DFS,0.0273,24.0,22.0,да,10
|
||||||
|
Маленький 10x10,A*,0.0677,31.0,20.0,да,10
|
||||||
|
Маленький 10x10,Dijkstra,0.0551,37.0,20.0,да,10
|
||||||
|
Средний 50x50,BFS,1.2769,1151.0,709.0,да,10
|
||||||
|
Средний 50x50,DFS,0.9106,784.0,709.0,да,10
|
||||||
|
Средний 50x50,A*,2.0089,1133.0,709.0,да,10
|
||||||
|
Средний 50x50,Dijkstra,1.7041,1151.0,709.0,да,10
|
||||||
|
Большой 100x100,BFS,5.2983,4801.0,1685.0,да,10
|
||||||
|
Большой 100x100,DFS,2.5044,2155.0,1685.0,да,10
|
||||||
|
Большой 100x100,A*,8.6574,4791.0,1685.0,да,10
|
||||||
|
Большой 100x100,Dijkstra,7.1532,4800.0,1685.0,да,10
|
||||||
|
Пустой 50x50,BFS,2.8927,2304.0,95.0,да,10
|
||||||
|
Пустой 50x50,DFS,0.1404,187.0,95.0,да,10
|
||||||
|
Пустой 50x50,A*,0.2374,95.0,95.0,да,10
|
||||||
|
Пустой 50x50,Dijkstra,4.0408,2304.0,95.0,да,10
|
||||||
|
Без пути 30x30,BFS,0.0015,1.0,0.0,нет,10
|
||||||
|
Без пути 30x30,DFS,0.0013,1.0,0.0,нет,10
|
||||||
|
Без пути 30x30,A*,0.0016,1.0,0.0,нет,10
|
||||||
|
Без пути 30x30,Dijkstra,0.0018,1.0,0.0,нет,10
|
||||||
|
1
shahovaa/zadanie 2/scripts/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
"""Helper scripts for maze generation and experiments."""
|
||||||
126
shahovaa/zadanie 2/scripts/generate_mazes.py
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import random
|
||||||
|
from collections import deque
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
MAZE_DIR = ROOT / "data" / "mazes"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
generate_all()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_all() -> None:
|
||||||
|
MAZE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
_write("small.txt", _small_maze())
|
||||||
|
_write("medium.txt", _perfect_maze(50, 50, seed=2026))
|
||||||
|
_write("large.txt", _perfect_maze(100, 100, seed=2027))
|
||||||
|
_write("empty.txt", _empty_maze(50, 50))
|
||||||
|
_write("no_exit.txt", _no_path_maze(30, 30))
|
||||||
|
|
||||||
|
|
||||||
|
def _write(filename: str, rows: list[str]) -> None:
|
||||||
|
(MAZE_DIR / filename).write_text("\n".join(rows) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _small_maze() -> list[str]:
|
||||||
|
return [
|
||||||
|
"##########",
|
||||||
|
"#S #E#",
|
||||||
|
"# #### # #",
|
||||||
|
"# # # #",
|
||||||
|
"# # #### #",
|
||||||
|
"# # #",
|
||||||
|
"# ###### #",
|
||||||
|
"# #",
|
||||||
|
"######## #",
|
||||||
|
"##########",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _empty_maze(width: int, height: int) -> list[str]:
|
||||||
|
grid = _bordered_grid(width, height, fill=" ")
|
||||||
|
grid[1][1] = "S"
|
||||||
|
grid[height - 2][width - 2] = "E"
|
||||||
|
return _to_rows(grid)
|
||||||
|
|
||||||
|
|
||||||
|
def _no_path_maze(width: int, height: int) -> list[str]:
|
||||||
|
grid = [["#" for _ in range(width)] for _ in range(height)]
|
||||||
|
grid[1][1] = "S"
|
||||||
|
grid[height - 2][width - 2] = "E"
|
||||||
|
return _to_rows(grid)
|
||||||
|
|
||||||
|
|
||||||
|
def _perfect_maze(width: int, height: int, seed: int) -> list[str]:
|
||||||
|
if width < 5 or height < 5:
|
||||||
|
raise ValueError("Maze must be at least 5x5")
|
||||||
|
|
||||||
|
randomizer = random.Random(seed)
|
||||||
|
grid = [["#" for _ in range(width)] for _ in range(height)]
|
||||||
|
start = (1, 1)
|
||||||
|
stack = [start]
|
||||||
|
grid[start[1]][start[0]] = " "
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
x, y = stack[-1]
|
||||||
|
candidates = []
|
||||||
|
for dx, dy in ((0, -2), (2, 0), (0, 2), (-2, 0)):
|
||||||
|
nx, ny = x + dx, y + dy
|
||||||
|
if 1 <= nx < width - 1 and 1 <= ny < height - 1 and grid[ny][nx] == "#":
|
||||||
|
candidates.append((nx, ny, dx, dy))
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
stack.pop()
|
||||||
|
continue
|
||||||
|
|
||||||
|
nx, ny, dx, dy = randomizer.choice(candidates)
|
||||||
|
grid[y + dy // 2][x + dx // 2] = " "
|
||||||
|
grid[ny][nx] = " "
|
||||||
|
stack.append((nx, ny))
|
||||||
|
|
||||||
|
exit_x, exit_y = _farthest_open_cell(grid, start)
|
||||||
|
grid[start[1]][start[0]] = "S"
|
||||||
|
grid[exit_y][exit_x] = "E"
|
||||||
|
return _to_rows(grid)
|
||||||
|
|
||||||
|
|
||||||
|
def _farthest_open_cell(grid: list[list[str]], start: tuple[int, int]) -> tuple[int, int]:
|
||||||
|
queue = deque([start])
|
||||||
|
distances = {start: 0}
|
||||||
|
farthest = start
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
x, y = queue.popleft()
|
||||||
|
if distances[(x, y)] > distances[farthest]:
|
||||||
|
farthest = (x, y)
|
||||||
|
|
||||||
|
for dx, dy in ((0, -1), (1, 0), (0, 1), (-1, 0)):
|
||||||
|
nx, ny = x + dx, y + dy
|
||||||
|
if (nx, ny) not in distances and grid[ny][nx] != "#":
|
||||||
|
distances[(nx, ny)] = distances[(x, y)] + 1
|
||||||
|
queue.append((nx, ny))
|
||||||
|
|
||||||
|
return farthest
|
||||||
|
|
||||||
|
|
||||||
|
def _bordered_grid(width: int, height: int, fill: str) -> list[list[str]]:
|
||||||
|
grid = [[fill for _ in range(width)] for _ in range(height)]
|
||||||
|
for x in range(width):
|
||||||
|
grid[0][x] = "#"
|
||||||
|
grid[height - 1][x] = "#"
|
||||||
|
for y in range(height):
|
||||||
|
grid[y][0] = "#"
|
||||||
|
grid[y][width - 1] = "#"
|
||||||
|
return grid
|
||||||
|
|
||||||
|
|
||||||
|
def _to_rows(grid: list[list[str]]) -> list[str]:
|
||||||
|
return ["".join(row) for row in grid]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
194
shahovaa/zadanie 2/scripts/run_experiments.py
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import statistics
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
from maze_solver import ( # noqa: E402
|
||||||
|
AStarStrategy,
|
||||||
|
BFSStrategy,
|
||||||
|
DFSStrategy,
|
||||||
|
DijkstraStrategy,
|
||||||
|
MazeSolver,
|
||||||
|
TextFileMazeBuilder,
|
||||||
|
)
|
||||||
|
from scripts.generate_mazes import generate_all # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
MAZES = [
|
||||||
|
("small", "Маленький 10x10", ROOT / "data" / "mazes" / "small.txt"),
|
||||||
|
("medium", "Средний 50x50", ROOT / "data" / "mazes" / "medium.txt"),
|
||||||
|
("large", "Большой 100x100", ROOT / "data" / "mazes" / "large.txt"),
|
||||||
|
("empty", "Пустой 50x50", ROOT / "data" / "mazes" / "empty.txt"),
|
||||||
|
("no_exit", "Без пути 30x30", ROOT / "data" / "mazes" / "no_exit.txt"),
|
||||||
|
]
|
||||||
|
|
||||||
|
STRATEGIES = [BFSStrategy, DFSStrategy, AStarStrategy, DijkstraStrategy]
|
||||||
|
REPORTS_DIR = ROOT / "reports"
|
||||||
|
CHARTS_DIR = REPORTS_DIR / "charts"
|
||||||
|
|
||||||
|
|
||||||
|
def main(runs: int = 10) -> None:
|
||||||
|
generate_all()
|
||||||
|
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
CHARTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
rows = _run_experiments(runs)
|
||||||
|
_write_csv(rows)
|
||||||
|
_write_charts(rows)
|
||||||
|
print(f"Wrote {REPORTS_DIR / 'results.csv'}")
|
||||||
|
print(f"Wrote SVG charts to {CHARTS_DIR}")
|
||||||
|
|
||||||
|
|
||||||
|
def _run_experiments(runs: int) -> list[dict[str, object]]:
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
rows: list[dict[str, object]] = []
|
||||||
|
|
||||||
|
for maze_key, maze_name, maze_path in MAZES:
|
||||||
|
maze = builder.build_from_file(maze_path)
|
||||||
|
for strategy_type in STRATEGIES:
|
||||||
|
measurements = []
|
||||||
|
for _ in range(runs):
|
||||||
|
stats = MazeSolver(maze, strategy_type()).solve()
|
||||||
|
measurements.append(stats)
|
||||||
|
|
||||||
|
avg_time = statistics.fmean(item.time_ms for item in measurements)
|
||||||
|
avg_visited = statistics.fmean(item.visited_cells for item in measurements)
|
||||||
|
avg_path = statistics.fmean(item.path_length for item in measurements)
|
||||||
|
found = measurements[-1].path_length > 0
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"key": maze_key,
|
||||||
|
"лабиринт": maze_name,
|
||||||
|
"стратегия": measurements[-1].strategy_name,
|
||||||
|
"время_мс": f"{avg_time:.4f}",
|
||||||
|
"посещено_клеток": f"{avg_visited:.1f}",
|
||||||
|
"длина_пути": f"{avg_path:.1f}",
|
||||||
|
"путь_найден": "да" if found else "нет",
|
||||||
|
"запусков": runs,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def _write_csv(rows: list[dict[str, object]]) -> None:
|
||||||
|
csv_path = REPORTS_DIR / "results.csv"
|
||||||
|
headers = [
|
||||||
|
"лабиринт",
|
||||||
|
"стратегия",
|
||||||
|
"время_мс",
|
||||||
|
"посещено_клеток",
|
||||||
|
"длина_пути",
|
||||||
|
"путь_найден",
|
||||||
|
"запусков",
|
||||||
|
]
|
||||||
|
with csv_path.open("w", encoding="utf-8", newline="") as stream:
|
||||||
|
writer = csv.DictWriter(stream, fieldnames=headers)
|
||||||
|
writer.writeheader()
|
||||||
|
for row in rows:
|
||||||
|
writer.writerow({header: row[header] for header in headers})
|
||||||
|
|
||||||
|
|
||||||
|
def _write_charts(rows: list[dict[str, object]]) -> None:
|
||||||
|
grouped: dict[str, list[dict[str, object]]] = defaultdict(list)
|
||||||
|
for row in rows:
|
||||||
|
grouped[str(row["key"])].append(row)
|
||||||
|
|
||||||
|
for maze_key, group in grouped.items():
|
||||||
|
title = str(group[0]["лабиринт"])
|
||||||
|
_write_bar_chart(
|
||||||
|
CHARTS_DIR / f"{maze_key}_time.svg",
|
||||||
|
title=f"{title}: среднее время, мс",
|
||||||
|
rows=group,
|
||||||
|
metric="время_мс",
|
||||||
|
color="#2f6fbb",
|
||||||
|
)
|
||||||
|
_write_bar_chart(
|
||||||
|
CHARTS_DIR / f"{maze_key}_visited.svg",
|
||||||
|
title=f"{title}: посещенные клетки",
|
||||||
|
rows=group,
|
||||||
|
metric="посещено_клеток",
|
||||||
|
color="#2f8f5b",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_bar_chart(
|
||||||
|
path: Path,
|
||||||
|
title: str,
|
||||||
|
rows: list[dict[str, object]],
|
||||||
|
metric: str,
|
||||||
|
color: str,
|
||||||
|
) -> None:
|
||||||
|
width = 780
|
||||||
|
height = 360
|
||||||
|
left = 72
|
||||||
|
right = 28
|
||||||
|
top = 54
|
||||||
|
bottom = 58
|
||||||
|
chart_width = width - left - right
|
||||||
|
chart_height = height - top - bottom
|
||||||
|
values = [float(row[metric]) for row in rows]
|
||||||
|
max_value = max(values) if values else 1.0
|
||||||
|
max_value = max_value or 1.0
|
||||||
|
bar_area = chart_width / len(rows)
|
||||||
|
bar_width = min(96, bar_area * 0.58)
|
||||||
|
|
||||||
|
parts = [
|
||||||
|
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">',
|
||||||
|
'<rect width="100%" height="100%" fill="#ffffff"/>',
|
||||||
|
f'<text x="{left}" y="30" font-family="Arial" font-size="18" font-weight="700" fill="#1f2933">{_escape(title)}</text>',
|
||||||
|
f'<line x1="{left}" y1="{height - bottom}" x2="{width - right}" y2="{height - bottom}" stroke="#9aa5b1"/>',
|
||||||
|
f'<line x1="{left}" y1="{top}" x2="{left}" y2="{height - bottom}" stroke="#9aa5b1"/>',
|
||||||
|
]
|
||||||
|
|
||||||
|
for tick in range(5):
|
||||||
|
ratio = tick / 4
|
||||||
|
y = height - bottom - ratio * chart_height
|
||||||
|
value = max_value * ratio
|
||||||
|
parts.append(
|
||||||
|
f'<line x1="{left - 5}" y1="{y:.1f}" x2="{width - right}" y2="{y:.1f}" stroke="#edf0f2"/>'
|
||||||
|
)
|
||||||
|
parts.append(
|
||||||
|
f'<text x="{left - 10}" y="{y + 4:.1f}" text-anchor="end" font-family="Arial" font-size="11" fill="#52616b">{value:.2f}</text>'
|
||||||
|
)
|
||||||
|
|
||||||
|
for index, row in enumerate(rows):
|
||||||
|
value = float(row[metric])
|
||||||
|
ratio = value / max_value
|
||||||
|
bar_height = ratio * chart_height
|
||||||
|
x = left + index * bar_area + (bar_area - bar_width) / 2
|
||||||
|
y = height - bottom - bar_height
|
||||||
|
label = str(row["стратегия"])
|
||||||
|
parts.append(
|
||||||
|
f'<rect x="{x:.1f}" y="{y:.1f}" width="{bar_width:.1f}" height="{bar_height:.1f}" fill="{color}" rx="3"/>'
|
||||||
|
)
|
||||||
|
parts.append(
|
||||||
|
f'<text x="{x + bar_width / 2:.1f}" y="{y - 8:.1f}" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">{value:.2f}</text>'
|
||||||
|
)
|
||||||
|
parts.append(
|
||||||
|
f'<text x="{x + bar_width / 2:.1f}" y="{height - bottom + 24}" text-anchor="middle" font-family="Arial" font-size="12" fill="#1f2933">{_escape(label)}</text>'
|
||||||
|
)
|
||||||
|
|
||||||
|
parts.append("</svg>")
|
||||||
|
path.write_text("\n".join(parts), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _escape(value: str) -> str:
|
||||||
|
return (
|
||||||
|
value.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace('"', """)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
1
shahovaa/zadanie 2/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
"""Unit tests for the maze solver project."""
|
||||||
64
shahovaa/zadanie 2/tests/test_solver.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from maze_solver import (
|
||||||
|
AStarStrategy,
|
||||||
|
BFSStrategy,
|
||||||
|
Direction,
|
||||||
|
MazeSolver,
|
||||||
|
MoveCommand,
|
||||||
|
Player,
|
||||||
|
TextFileMazeBuilder,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SIMPLE_MAZE = """\
|
||||||
|
#######
|
||||||
|
#S E#
|
||||||
|
# ### #
|
||||||
|
# #
|
||||||
|
#######"""
|
||||||
|
|
||||||
|
|
||||||
|
class MazeSolverTest(unittest.TestCase):
|
||||||
|
def build_maze(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
path = Path(directory) / "maze.txt"
|
||||||
|
path.write_text(SIMPLE_MAZE, encoding="utf-8")
|
||||||
|
return TextFileMazeBuilder().build_from_file(path)
|
||||||
|
|
||||||
|
def test_builder_reads_start_exit_and_neighbors(self) -> None:
|
||||||
|
maze = self.build_maze()
|
||||||
|
|
||||||
|
self.assertEqual((maze.start.x, maze.start.y), (1, 1))
|
||||||
|
self.assertEqual((maze.exit.x, maze.exit.y), (5, 1))
|
||||||
|
self.assertTrue(maze.get_cell(2, 1).is_passable())
|
||||||
|
self.assertFalse(maze.get_cell(0, 0).is_passable())
|
||||||
|
|
||||||
|
def test_bfs_and_astar_find_shortest_path(self) -> None:
|
||||||
|
maze = self.build_maze()
|
||||||
|
|
||||||
|
bfs_stats = MazeSolver(maze, BFSStrategy()).solve()
|
||||||
|
astar_stats = MazeSolver(maze, AStarStrategy()).solve()
|
||||||
|
|
||||||
|
self.assertEqual(bfs_stats.path_length, 5)
|
||||||
|
self.assertEqual(astar_stats.path_length, 5)
|
||||||
|
self.assertEqual(bfs_stats.path[0], maze.start)
|
||||||
|
self.assertEqual(bfs_stats.path[-1], maze.exit)
|
||||||
|
|
||||||
|
def test_move_command_can_execute_and_undo(self) -> None:
|
||||||
|
maze = self.build_maze()
|
||||||
|
player = Player.at_start(maze)
|
||||||
|
command = MoveCommand(player, Direction.RIGHT)
|
||||||
|
|
||||||
|
self.assertTrue(command.execute())
|
||||||
|
self.assertEqual((player.current_cell.x, player.current_cell.y), (2, 1))
|
||||||
|
self.assertTrue(command.undo())
|
||||||
|
self.assertEqual(player.current_cell, maze.start)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||