From 9395b9807c20f26322d0a8fd3c1f146d8a73f7ba Mon Sep 17 00:00:00 2001 From: Dima Date: Sun, 24 May 2026 19:17:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D0=BC=D1=8B=20(=D1=83=D1=81=D0=BF=D0=B5=D1=85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nikolaevda/task2/Zadanie2.py | 611 ++++++++++++++++++++++++++++++++++- 1 file changed, 600 insertions(+), 11 deletions(-) diff --git a/nikolaevda/task2/Zadanie2.py b/nikolaevda/task2/Zadanie2.py index cfe968f..b595b84 100644 --- a/nikolaevda/task2/Zadanie2.py +++ b/nikolaevda/task2/Zadanie2.py @@ -298,7 +298,7 @@ class MazeSolver: def solve(self): """ решение лабиринта с использованием текущей стратегии. - возвращает SearchStats (время, посещённые клетки, длина пути) + возвращает время, посещённые клетки, длина пути """ if self._strategy is None: raise ValueError("Стратегия не установлена. Используйте setStrategy()") @@ -321,18 +321,17 @@ class MazeSolver: ) return path, stats - -# ==================== ЭТАП 5: НАБЛЮДАТЕЛЬ (OBSERVER) ==================== + class Observer: - """Интерфейс наблюдателя""" + """интерфейс наблюдателя""" def update(self, event, data): raise NotImplementedError class ConsoleDisplay(Observer): - """Консольная визуализация - наблюдатель""" + """консольная визуализация - наблюдатель""" def __init__(self): self._last_path = None @@ -372,7 +371,7 @@ class ConsoleDisplay(Observer): print("S - старт E - выход # - стена . - проход") def _draw_maze_with_player(self, game_state): - """Отрисовка лабиринта с игроком""" + """отрисовка лабиринта с игроком""" maze = game_state['maze'] player = game_state['player'] @@ -411,7 +410,6 @@ class ConsoleDisplay(Observer): print(f"\n Путь найден! Длина: {len(path)} клеток") -# ==================== ЭТАП 5: КОМАНДА (COMMAND) ==================== class Command: """Интерфейс команды""" @@ -505,7 +503,6 @@ class CommandInvoker: return len(self._history) -# ==================== ЭТАП 5: ИГРОК ==================== class Player: """Игрок, перемещающийся по лабиринту""" @@ -546,7 +543,7 @@ class GameController: self.view.update("maze_loaded", self.maze) print("УПРАВЛЕНИЕ:") - print(" H/J/K/L или ←/↓/↑/→ - движение") + print(" H/J/K/Ll - движение") print(" U - отменить ход") print(" R - повторить ход") print(" B - BFS поиск пути") @@ -562,7 +559,7 @@ class GameController: cmd = input("\nКоманда > ").lower() if cmd == 'q': - print("До свидания!") + print("До встречи!") break elif cmd in ['h', 'j', 'k', 'l']: @@ -665,4 +662,596 @@ class GameController: self.view.update("player_moved", { 'maze': self.maze, 'player': self.player - }) \ No newline at end of file + }) + + + + +class MazeGenerator: + """генератор тестовых лабиринтов различной сложности""" + + @staticmethod + def create_empty_maze(width, height): + """пустой лабиринт без стен""" + maze = Maze(width, height) + for y in range(height): + for x in range(width): + maze.get_cell(x, y).is_wall = False + maze.set_start(0, 0) + maze.set_exit(width - 1, height - 1) + return maze + + @staticmethod + def create_simple_maze(width, height): + """простой лабиринт с прямым путём""" + maze = Maze(width, height) + + # Заполняем стенами + for y in range(height): + for x in range(width): + maze.get_cell(x, y).is_wall = True + + # Создаём прямой путь + for i in range(min(width, height)): + maze.get_cell(i, i).is_wall = False + if i + 1 < width: + maze.get_cell(i + 1, i).is_wall = False + if i + 1 < height: + maze.get_cell(i, i + 1).is_wall = False + + maze.set_start(0, 0) + maze.set_exit(width - 1, height - 1) + return maze + + @staticmethod + def generate_dfs_maze(width, height): + """генерация запутанного лабиринта алгоритмом DFS""" + maze = Maze(width, height) + + # заполняем стенами + for y in range(height): + for x in range(width): + maze.get_cell(x, y).is_wall = True + + start_x, start_y = 1, 1 + maze.get_cell(start_x, start_y).is_wall = False + + stack = [(start_x, start_y)] + visited = {(start_x, start_y)} + directions = [(0, -2), (0, 2), (-2, 0), (2, 0)] + + while stack: + x, y = stack[-1] + neighbors = [] + + for dx, dy in directions: + nx, ny = x + dx, y + dy + if 0 < nx < width - 1 and 0 < ny < height - 1 and (nx, ny) not in visited: + neighbors.append((nx, ny, dx, dy)) + + if neighbors: + import random + nx, ny, dx, dy = random.choice(neighbors) + maze.get_cell(x + dx // 2, y + dy // 2).is_wall = False + maze.get_cell(nx, ny).is_wall = False + visited.add((nx, ny)) + stack.append((nx, ny)) + else: + stack.pop() + + maze.set_start(start_x, start_y) + + # ищем дальнюю точку для выхода + farthest = (start_x, start_y) + max_dist = 0 + for y in range(height): + for x in range(width): + cell = maze.get_cell(x, y) + if not cell.is_wall: + dist = abs(x - start_x) + abs(y - start_y) + if dist > max_dist: + max_dist = dist + farthest = (x, y) + maze.set_exit(farthest[0], farthest[1]) + + return maze + + @staticmethod + def create_no_exit_maze(width, height): + """лабиринт без выхода""" + maze = MazeGenerator.generate_dfs_maze(width, height) + if maze.exit: + maze.exit.is_wall = True + maze.exit.is_exit = False + maze.exit = None + return maze + + @staticmethod + def save_to_file(maze, filename): + """сохранение лабиринта в файл""" + with open(filename, 'w', encoding='utf-8') as f: + for y in range(maze.height): + line = "" + for x in range(maze.width): + cell = maze.get_cell(x, y) + if cell.is_start: + line += 'S' + elif cell.is_exit: + line += 'E' + elif cell.is_wall: + line += '#' + else: + line += '.' + f.write(line + '\n') + + +class ExperimentRunner: + """запуск экспериментов и сбор статистики""" + + def __init__(self, runs_per_experiment=5): + self.runs_per_experiment = runs_per_experiment + self.results = [] + + def run_experiment(self, maze, strategy, maze_name): + """запуск одного эксперимента""" + times = [] + visited = [] + path_lengths = [] + + for _ in range(self.runs_per_experiment): + start_time = time.perf_counter() + path, visited_count = strategy.findPath(maze, maze.start, maze.exit) + end_time = time.perf_counter() + + times.append((end_time - start_time) * 1000) + visited.append(visited_count) + path_lengths.append(len(path) if path else 0) + + return { + 'maze_name': maze_name, + 'strategy': strategy.get_name(), + 'avg_time_ms': sum(times) / len(times), + 'min_time_ms': min(times), + 'max_time_ms': max(times), + 'avg_visited_cells': sum(visited) / len(visited), + 'min_visited_cells': min(visited), + 'max_visited_cells': max(visited), + 'avg_path_length': sum(path_lengths) / len(path_lengths), + 'min_path_length': min(path_lengths), + 'max_path_length': max(path_lengths), + 'path_found': any(pl > 0 for pl in path_lengths), + 'runs': self.runs_per_experiment + } + + def run_all_experiments(self): + """запуск всех экспериментов на всех лабиринтах""" + print("ГЕНЕРАЦИЯ ТЕСТОВЫХ ЛАБИРИНТОВ") + + # Создаём тестовые лабиринты + test_mazes = { + 'tiny_simple (10x10)': MazeGenerator.create_simple_maze(10, 10), + 'small_empty (20x20)': MazeGenerator.create_empty_maze(20, 20), + 'medium_dfs (30x30)': MazeGenerator.generate_dfs_maze(30, 30), + 'medium_complex (40x40)': MazeGenerator.generate_dfs_maze(40, 40), + 'large_dfs (50x50)': MazeGenerator.generate_dfs_maze(50, 50), + 'very_large_dfs (100x100)': MazeGenerator.generate_dfs_maze(100, 100), + 'no_exit (20x20)': MazeGenerator.create_no_exit_maze(20, 20) + } + + # Сохраняем лабиринты в файлы + for name, maze in test_mazes.items(): + filename = f"test_{name.replace(' ', '_').replace('(', '').replace(')', '')}.txt" + MazeGenerator.save_to_file(maze, filename) + print(f" Создан: {filename}") + + # Стратегии для тестирования + strategies = [ + BFSStrategy(), + DFSStrategy(), + AStarStrategy(), + DijkstraStrategy() + ] + + print("ЗАПУСК ЭКСПЕРИМЕНТОВ") + + for maze_name, maze in test_mazes.items(): + print(f"\n Лабиринт: {maze_name}") + print(f" Размер: {maze.width}x{maze.height}") + print(f" Старт: ({maze.start.x}, {maze.start.y})") + print(f" Выход: ({maze.exit.x}, {maze.exit.y})") + + for strategy in strategies: + print(f" → {strategy.get_name()}...", end=" ", flush=True) + result = self.run_experiment(maze, strategy, maze_name) + self.results.append(result) + + status = "✓" if result['path_found'] else "✗" + print(f"{status} {result['avg_time_ms']:.2f}мс, " + f"{result['avg_visited_cells']:.0f} клеток, " + f"{result['avg_path_length']:.1f} шагов") + + def save_to_csv(self, filename="experiment_results.csv"): + import csv + + with open(filename, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = [ + 'maze_name', 'strategy', 'runs', + 'avg_time_ms', 'min_time_ms', 'max_time_ms', + 'avg_visited_cells', 'min_visited_cells', 'max_visited_cells', + 'avg_path_length', 'min_path_length', 'max_path_length', + 'path_found' + ] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + + for result in self.results: + writer.writerow(result) + + print(f"\n Результаты сохранены в {filename}") + + def print_summary(self): + print("СВОДНАЯ СТАТИСТИКА ЭКСПЕРИМЕНТОВ") + + + # Группировка по лабиринтам + grouped = {} + for result in self.results: + name = result['maze_name'] + if name not in grouped: + grouped[name] = [] + grouped[name].append(result) + + for maze_name, results in grouped.items(): + print(f"\n {maze_name}") + print(f"{'Стратегия':<25} {'Время(мс)':<12} {'Посещено':<12} {'Длина пути':<12} {'Найден':<8}") + + for result in sorted(results, key=lambda x: x['avg_time_ms']): + status = "✓" if result['path_found'] else "✗" + print(f"{result['strategy']:<25} " + f"{result['avg_time_ms']:<12.2f} " + f"{result['avg_visited_cells']:<12.0f} " + f"{result['avg_path_length']:<12.1f} " + f"{status:<8}") + + print("ОБЩАЯ СТАТИСТИКА ПО СТРАТЕГИЯМ") + + strategy_stats = {} + for result in self.results: + name = result['strategy'] + if name not in strategy_stats: + strategy_stats[name] = {'times': [], 'visited': [], 'lengths': []} + strategy_stats[name]['times'].append(result['avg_time_ms']) + strategy_stats[name]['visited'].append(result['avg_visited_cells']) + strategy_stats[name]['lengths'].append(result['avg_path_length']) + + print(f"\n{'Стратегия':<25} {'Ср.время(мс)':<15} {'Ср.посещено':<15} {'Ср.длина':<12}") + + for name, stats in strategy_stats.items(): + avg_time = sum(stats['times']) / len(stats['times']) + avg_visited = sum(stats['visited']) / len(stats['visited']) + avg_length = sum(stats['lengths']) / len(stats['lengths']) + print(f"{name:<25} {avg_time:<15.2f} {avg_visited:<15.0f} {avg_length:<12.1f}") + + def print_conclusions(self): + print("ВЫВОДЫ И РЕКОМЕНДАЦИИ") + + + # Находим лучшие стратегии + bfs_results = [r for r in self.results if r['strategy'] == "BFS (Поиск в ширину)" and r['path_found']] + dfs_results = [r for r in self.results if r['strategy'] == "DFS (Поиск в глубину)" and r['path_found']] + astar_results = [r for r in self.results if r['strategy'] == "A* (A Star)" and r['path_found']] + dijkstra_results = [r for r in self.results if r['strategy'] == "Дейкстра (Dijkstra)" and r['path_found']] + + conclusions = [] + + if bfs_results: + avg_bfs_time = sum(r['avg_time_ms'] for r in bfs_results) / len(bfs_results) + avg_bfs_length = sum(r['avg_path_length'] for r in bfs_results) / len(bfs_results) + conclusions.append(f" • BFS: среднее время {avg_bfs_time:.2f}мс, длина пути {avg_bfs_length:.1f}") + + if dfs_results: + avg_dfs_time = sum(r['avg_time_ms'] for r in dfs_results) / len(dfs_results) + avg_dfs_length = sum(r['avg_path_length'] for r in dfs_results) / len(dfs_results) + conclusions.append(f" • DFS: среднее время {avg_dfs_time:.2f}мс, длина пути {avg_dfs_length:.1f}") + + if astar_results: + avg_astar_time = sum(r['avg_time_ms'] for r in astar_results) / len(astar_results) + avg_astar_length = sum(r['avg_path_length'] for r in astar_results) / len(astar_results) + conclusions.append(f" • A*: среднее время {avg_astar_time:.2f}мс, длина пути {avg_astar_length:.1f}") + + if dijkstra_results: + avg_dijkstra_time = sum(r['avg_time_ms'] for r in dijkstra_results) / len(dijkstra_results) + avg_dijkstra_length = sum(r['avg_path_length'] for r in dijkstra_results) / len(dijkstra_results) + conclusions.append(f" • Дейкстра: среднее время {avg_dijkstra_time:.2f}мс, длина пути {avg_dijkstra_length:.1f}") + + print("\n РЕЗУЛЬТАТЫ АНАЛИЗА:\n") + for c in conclusions: + print(c) + + print("\n РЕКОМЕНДАЦИИ:\n") + print(" 1. Для маленьких лабиринтов - любой алгоритм работает быстро") + print(" 2. Для больших лабиринтов - A* даёт лучший компромисс скорость/качество") + print(" 3. BFS гарантирует кратчайший путь, но медленнее на больших картах") + print(" 4. DFS самый быстрый, но путь может быть неоптимальным") + print(" 5. Если путь не существует - BFS и A* эффективнее обнаруживают это") + + +def plot_experiment_results(csv_filename="experiment_results.csv"): + """построение графиков по результатам экспериментов""" + try: + import matplotlib.pyplot as plt + import pandas as pd + import numpy as np + except ImportError: + print("Установите: pip install matplotlib pandas") + return + + if not os.path.exists(csv_filename): + print("Файл результатов не найден") + return + + df = pd.read_csv(csv_filename) + + fig = plt.figure(figsize=(16, 12)) + fig.suptitle('Сравнение алгоритмов поиска в лабиринте', fontsize=16) + + # Время выполнения + ax1 = fig.add_subplot(2, 2, 1) + for strategy in df['strategy'].unique(): + data = df[df['strategy'] == strategy] + ax1.bar(data['maze_name'], data['avg_time_ms'], alpha=0.7, label=strategy) + ax1.set_xlabel('Лабиринт') + ax1.set_ylabel('Время (мс)') + ax1.set_title('Время выполнения алгоритмов') + ax1.legend(loc='upper left', fontsize=8) + ax1.tick_params(axis='x', rotation=45, labelsize=8) + + # Посещённые клетки + ax2 = fig.add_subplot(2, 2, 2) + for strategy in df['strategy'].unique(): + data = df[df['strategy'] == strategy] + ax2.bar(data['maze_name'], data['avg_visited_cells'], alpha=0.7, label=strategy) + ax2.set_xlabel('Лабиринт') + ax2.set_ylabel('Посещено клеток') + ax2.set_title('Эффективность поиска') + ax2.legend(loc='upper left', fontsize=8) + ax2.tick_params(axis='x', rotation=45, labelsize=8) + + # Длина пути + ax3 = fig.add_subplot(2, 2, 3) + for strategy in df['strategy'].unique(): + data = df[df['strategy'] == strategy] + ax3.bar(data['maze_name'], data['avg_path_length'], alpha=0.7, label=strategy) + ax3.set_xlabel('Лабиринт') + ax3.set_ylabel('Длина пути') + ax3.set_title('Оптимальность пути') + ax3.legend(loc='upper left', fontsize=8) + ax3.tick_params(axis='x', rotation=45, labelsize=8) + + # Радар-диаграмма + ax4 = fig.add_subplot(2, 2, 4, projection='polar') + + strategies = df['strategy'].unique() + metrics = ['avg_time_ms', 'avg_visited_cells', 'avg_path_length'] + metric_labels = ['Время', 'Посещено', 'Длина пути'] + + for strategy in strategies: + data = df[df['strategy'] == strategy] + values = [] + for metric in metrics: + val = data[metric].mean() + max_val = df[metric].max() + normalized = 1 - (val / max_val) if max_val > 0 else 0.5 + values.append(normalized) + values.append(values[0]) + angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist() + angles += angles[:1] + ax4.plot(angles, values, 'o-', linewidth=2, label=strategy) + ax4.fill(angles, values, alpha=0.25) + + ax4.set_xticks(angles[:-1]) + ax4.set_xticklabels(metric_labels) + ax4.set_ylim(0, 1) + ax4.set_title('Радар-диаграмма (дальше от центра = лучше)') + ax4.legend(loc='upper right', fontsize=7, bbox_to_anchor=(1.3, 1.0)) + + plt.tight_layout() + plt.savefig('algorithm_comparison.png', dpi=150, bbox_inches='tight') + plt.show() + print("График сохранён: algorithm_comparison.png") + + +def plot_by_maze(csv_filename="experiment_results.csv"): + """построение графиков для каждого лабиринта""" + try: + import matplotlib.pyplot as plt + import pandas as pd + except ImportError: + return + + if not os.path.exists(csv_filename): + return + + df = pd.read_csv(csv_filename) + mazes = df['maze_name'].unique() + + fig, axes = plt.subplots(len(mazes), 3, figsize=(15, 4 * len(mazes))) + fig.suptitle('Детальный анализ по каждому лабиринту', fontsize=14) + + if len(mazes) == 1: + axes = axes.reshape(1, -1) + + for i, maze_name in enumerate(mazes): + maze_data = df[df['maze_name'] == maze_name] + + axes[i, 0].bar(maze_data['strategy'], maze_data['avg_time_ms']) + axes[i, 0].set_title(f'{maze_name}\nВремя (мс)') + axes[i, 0].tick_params(axis='x', rotation=45, labelsize=8) + + axes[i, 1].bar(maze_data['strategy'], maze_data['avg_visited_cells']) + axes[i, 1].set_title('Посещено клеток') + axes[i, 1].tick_params(axis='x', rotation=45, labelsize=8) + + bars = axes[i, 2].bar(maze_data['strategy'], maze_data['avg_path_length']) + axes[i, 2].set_title('Длина пути') + axes[i, 2].tick_params(axis='x', rotation=45, labelsize=8) + + for j, (bar, found) in enumerate(zip(bars, maze_data['path_found'])): + if not found: + bar.set_color('red') + + plt.tight_layout() + plt.savefig('maze_detailed_analysis.png', dpi=150, bbox_inches='tight') + plt.show() + print("Детальные графики сохранены: maze_detailed_analysis.png") + + +def print_analysis(csv_filename="experiment_results.csv"): + """вывод анализа эффективности алгоритмов""" + try: + import pandas as pd + except ImportError: + return + + if not os.path.exists(csv_filename): + return + + df = pd.read_csv(csv_filename) + + print("АНАЛИЗ ЭФФЕКТИВНОСТИ АЛГОРИТМОВ") + + + print("\nОбщая статистика по алгоритмам:") + print(f"{'Алгоритм':<25} {'Время(мс)':<12} {'Посещено':<12} {'Длина пути':<12} {'Найден %':<10}") + + for strategy in df['strategy'].unique(): + data = df[df['strategy'] == strategy] + avg_time = data['avg_time_ms'].mean() + avg_visited = data['avg_visited_cells'].mean() + avg_length = data['avg_path_length'].mean() + found_rate = data['path_found'].mean() * 100 + print(f"{strategy:<25} {avg_time:<12.2f} {avg_visited:<12.0f} {avg_length:<12.1f} {found_rate:<10.1f}%") + + + print("АНАЛИЗ ПО ТИПАМ ЛАБИРИНТОВ") + + for maze_name in df['maze_name'].unique(): + maze_data = df[df['maze_name'] == maze_name] + + print(f"\nЛабиринт: {maze_name}") + + if 'tiny' in maze_name: + maze_type = "Маленький (10x10)" + elif 'small' in maze_name: + maze_type = "Небольшой (20x20)" + elif 'medium' in maze_name: + maze_type = "Средний (30x30-40x40)" + elif 'large' in maze_name: + maze_type = "Большой (50x50)" + elif 'no_exit' in maze_name: + maze_type = "Без выхода" + else: + maze_type = "Обычный" + + print(f" Тип: {maze_type}") + + best_time = maze_data.loc[maze_data['avg_time_ms'].idxmin()] + print(f" Самый быстрый: {best_time['strategy']} ({best_time['avg_time_ms']:.2f} мс)") + + best_visited = maze_data.loc[maze_data['avg_visited_cells'].idxmin()] + print(f" Самый экономный: {best_visited['strategy']} ({best_visited['avg_visited_cells']:.0f} клеток)") + + path_data = maze_data[maze_data['path_found'] == True] + if not path_data.empty: + best_path = path_data.loc[path_data['avg_path_length'].idxmin()] + print(f" Самый короткий путь: {best_path['strategy']} ({best_path['avg_path_length']:.1f} шагов)") + else: + print(" Путь не найден ни одним алгоритмом") + + + print("ВЫВОДЫ И РЕКОМЕНДАЦИИ") + + + print(""" +1. BFS (Поиск в ширину): + - Гарантирует кратчайший путь + - Медленнее на больших лабиринтах + - Рекомендуется для маленьких лабиринтов (до 20x20) + +2. DFS (Поиск в глубину): + - Самый быстрый по времени + - Не гарантирует кратчайший путь + - Рекомендуется когда важна скорость, а не оптимальность + +3. A* (A Star): + - Лучший компромисс между скоростью и оптимальностью + - Рекомендуется для больших и сложных лабиринтов (40x40+) + +4. Дейкстра: + - Гарантирует оптимальный путь + - Работает с взвешенными графами + - Медленнее A* на больших лабиринтах + +ИТОГОВЫЕ РЕКОМЕНДАЦИИ: +- Маленькие лабиринты (до 20x20): BFS +- Средние лабиринты (20x20 - 40x40): A* +- Большие лабиринты (40x40+): A* или DFS +- Когда нужен кратчайший путь: BFS или A* +- Когда важна только скорость: DFS +- Лабиринты без выхода: BFS или A* (быстрее обнаруживают) +""") + + +def run_full_analysis(): + """Запуск полного анализа с построением графиков""" + if not os.path.exists("experiment_results.csv"): + print("Результаты не найдены. Запускаем эксперименты...") + runner = ExperimentRunner(runs_per_experiment=5) + runner.run_all_experiments() + runner.save_to_csv() + runner.print_summary() + runner.print_conclusions() + + plot_experiment_results() + plot_by_maze() + print_analysis() + + +def run_experiments(): + """Запуск экспериментов с построением графиков и анализом""" + runner = ExperimentRunner(runs_per_experiment=5) + runner.run_all_experiments() + runner.save_to_csv() + runner.print_summary() + runner.print_conclusions() + + run_full_analysis() + + +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == 'experiment': + run_experiments() + else: + # Интерактивный режим + sample = """############### +#S # +# ### ####### # +# # # # +### # ### # # # +# # # # # +# ### # ### ### +# # E # +###############""" + + with open("maze.txt", "w") as f: + f.write(sample) + + builder = TextFileMazeBuilder() + maze = builder.buildFromFile("maze.txt") + + print(f"Лабиринт загружен: {maze.width}x{maze.height}") + print(f"Старт: ({maze.start.x}, {maze.start.y})") + print(f"Выход: ({maze.exit.x}, {maze.exit.y})") + + game = GameController(maze) + game.run() \ No newline at end of file