[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()
|
||||