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'', '', f'{_escape(title)}', f'', f'', ] for tick in range(5): ratio = tick / 4 y = height - bottom - ratio * chart_height value = max_value * ratio parts.append( f'' ) parts.append( f'{value:.2f}' ) 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'' ) parts.append( f'{value:.2f}' ) parts.append( f'{_escape(label)}' ) parts.append("") path.write_text("\n".join(parts), encoding="utf-8") def _escape(value: str) -> str: return ( value.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace('"', """) ) if __name__ == "__main__": main()