import pygame import random import numpy as np import csv import time from collections import deque from datetime import datetime import os # Константы цветов (добавим новые цвета для кнопок) WHITE = (255, 255, 255) BLACK = (0, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) GRAY = (128, 128, 128) PURPLE = (128, 0, 128) LIGHT_BLUE = (173, 216, 230) ORANGE = (255, 165, 0) DARK_GREEN = (0, 100, 0) LIGHT_RED = (255, 200, 200) DARK_ORANGE = (200, 120, 0) CYAN = (0, 255, 255) YELLOW = (255, 255, 0) RED = (255, 0, 0) FUKSIA = (255, 0, 255) BUTTON_GREEN = (100, 200, 100) # Зеленый для кнопки "Правильно" BUTTON_RED = (200, 100, 100) # Красный для кнопки "Неправильно" BUTTON_TEXT = (50, 50, 50) # Цвет текста кнопок # ... (остальной код классов остается без изменений до класса MazeGUI) class MazeGenerator: def __init__(self, width, height, difficulty): self.width = width self.height = height self.difficulty = difficulty self.maze = np.ones((height, width), dtype=int) def generate(self): """Генерация лабиринта с использованием алгоритма случайного обхода""" if self.difficulty == "easy": size = 21 elif self.difficulty == "medium": size = 31 else: # hard size = 41 self.width = size self.height = size self.maze = np.ones((self.height, self.width), dtype=int) self._generate_base_maze() start = (self.height // 2, 0) end = (self.height // 2, self.width - 1) self.maze[start[0], start[1]] = 0 self.maze[end[0], end[1]] = 0 self._connect_to_maze(start) self._connect_to_maze(end) self._add_random_complexity() max_attempts = 5 attempt = 0 while not self._has_solution(start, end) and attempt < max_attempts: attempt += 1 self._generate_base_maze() self.maze[start[0], start[1]] = 0 self.maze[end[0], end[1]] = 0 self._connect_to_maze(start) self._connect_to_maze(end) self._add_random_complexity() return self.maze def _generate_base_maze(self): """Генерирует базовую структуру лабиринта алгоритмом случайного обхода""" self.maze = np.ones((self.height, self.width), dtype=int) start_x = random.randrange(1, self.width - 1, 2) start_y = random.randrange(1, self.height - 1, 2) stack = [(start_x, start_y)] self.maze[start_y, start_x] = 0 directions = [(-2, 0), (2, 0), (0, -2), (0, 2)] while stack: cx, cy = stack[-1] random.shuffle(directions) moved = False for dx, dy in directions: nx, ny = cx + dx, cy + dy if (1 <= nx < self.width - 1 and 1 <= ny < self.height - 1 and self.maze[ny, nx] == 1): intermediate_x = cx + dx // 2 intermediate_y = cy + dy // 2 self.maze[intermediate_y, intermediate_x] = 0 self.maze[ny, nx] = 0 stack.append((nx, ny)) moved = True break if not moved: stack.pop() def _connect_to_maze(self, point): """Соединяет точку входа/выхода с основным лабиринтом""" y, x = point if x == 0: # Левый вход for distance in range(1, self.width // 2): if distance < self.width and self.maze[y, distance] == 0: for step in range(distance): self.maze[y, step] = 0 return elif x == self.width - 1: # Правый выход for distance in range(1, self.width // 2): check_x = self.width - 1 - distance if check_x >= 0 and self.maze[y, check_x] == 0: for step in range(distance): self.maze[y, self.width - 1 - step] = 0 return if x == 0: for i in range(1, self.width // 2): self.maze[y, i] = 0 elif x == self.width - 1: for i in range(self.width // 2, self.width - 1): self.maze[y, i] = 0 def _add_random_complexity(self): """Добавляет случайные элементы сложности""" complexity_factor = {"easy": 0.02, "medium": 0.04, "hard": 0.06}[self.difficulty] total_cells = self.width * self.height modifications = int(total_cells * complexity_factor) for _ in range(modifications): x = random.randrange(3, self.width - 3, 2) y = random.randrange(3, self.height - 3, 2) if random.random() < 0.1 and self.maze[y, x] == 1: neighbors = [(y - 1, x), (y + 1, x), (y, x - 1), (y, x + 1)] wall_neighbors = sum(1 for ny, nx in neighbors if 0 <= ny < self.height and 0 <= nx < self.width and self.maze[ny, nx] == 1) if wall_neighbors >= 3: self.maze[y, x] = 0 def _has_solution(self, start, end): """Проверяет наличие решения в лабиринте""" return self._find_path(start, end) is not None def _find_path(self, start, end): """Находит путь от старта к финишу (BFS)""" queue = deque([start]) visited = set([start]) parent = {start: None} while queue: current = queue.popleft() if current == end: path = [] while current: path.append(current) current = parent[current] return path[::-1] for dy, dx in [(0, 1), (0, -1), (1, 0), (-1, 0)]: ny, nx = current[0] + dy, current[1] + dx if (0 <= ny < self.height and 0 <= nx < self.width and self.maze[ny, nx] == 0 and (ny, nx) not in visited): visited.add((ny, nx)) parent[(ny, nx)] = current queue.append((ny, nx)) return None class Individual: def __init__(self, maze, start, end, max_steps=None): self.maze = maze self.start = start self.end = end self.path = [start] self.fitness = 0 self.finished = False self.stuck = False self.steps = 0 self.max_steps = max_steps or (len(maze) * len(maze[0])) self.valid_path = False self.visited = set([start]) self.dead_end_cells = set() self.branch_points = set() self.wrong_turns = set() # Неверные повороты def get_possible_moves(self, pos): y, x = pos moves = [] # Проверка на выход if (y, x) == (self.end[0], self.end[1] - 1): return [self.end] for dy, dx in [(0, 1), (0, -1), (1, 0), (-1, 0)]: ny, nx = y + dy, x + dx if (0 <= ny < len(self.maze) and 0 <= nx < len(self.maze[0]) and self.maze[ny, nx] == 0): moves.append((ny, nx)) return moves def analyze_path(self): """Анализирует путь для определения развилок, тупиков и неверных поворотов""" self.dead_end_cells = set() self.branch_points = set() self.wrong_turns = set() for i, pos in enumerate(self.path): # Получаем все возможные ходы из этой позиции all_moves = self.get_possible_moves(pos) # Исключаем уже пройденные позиции (до текущей) available_moves = [move for move in all_moves if move not in self.path[:i]] # Определяем тип позиции if len(all_moves) > 2: # Развилка (более 2 путей) self.branch_points.add(pos) # Если мы выбрали не лучший путь в развилке - это неверный поворот if i < len(self.path) - 1: next_pos = self.path[i + 1] # Простая эвристика: правильный путь ведет ближе к цели best_move = min(all_moves, key=lambda p: abs(p[0] - self.end[0]) + abs(p[1] - self.end[1])) if next_pos != best_move and next_pos in available_moves: self.wrong_turns.add(pos) # Тупик - если нет доступных ходов (исключая откуда пришли) if len(available_moves) == 0 and pos != self.end: self.dead_end_cells.add(pos) # Помечаем весь путь от последней развилки как тупиковый for j in range(i - 1, -1, -1): prev_pos = self.path[j] if prev_pos in self.branch_points: break self.dead_end_cells.add(prev_pos) def make_move(self): if self.finished or self.stuck: return False current_pos = self.path[-1] if current_pos == self.end: self.finished = True self.valid_path = True self.analyze_path() return True self.steps += 1 if self.steps > self.max_steps: self.stuck = True self.analyze_path() return False possible_moves = [m for m in self.get_possible_moves(current_pos) if m not in self.visited] if not possible_moves: self.stuck = True self.analyze_path() return False # Выбираем ход с эвристикой (ближе к цели) if len(possible_moves) > 1: # Сортируем по расстоянию до цели, но добавляем случайность possible_moves.sort(key=lambda p: abs(p[0] - self.end[0]) + abs(p[1] - self.end[1])) # Выбираем один из 2 лучших ходов next_move = random.choice(possible_moves[:min(2, len(possible_moves))]) else: next_move = possible_moves[0] self.path.append(next_move) self.visited.add(next_move) if next_move == self.end: self.finished = True self.valid_path = True self.analyze_path() return True return False def calculate_fitness(self): if self.valid_path: # Бонус за достижение цели, штраф за длину пути self.fitness = 1000 - len(self.path) + (100 - len(self.wrong_turns) * 10) elif self.stuck: # Штраф за застревание, но учитываем близость к цели current = self.path[-1] distance = abs(current[0] - self.end[0]) + abs(current[1] - self.end[1]) self.fitness = -distance - len(self.dead_end_cells) * 5 else: # Оценка по близости к цели current = self.path[-1] distance = abs(current[0] - self.end[0]) + abs(current[1] - self.end[1]) self.fitness = 100 - distance - len(self.path) * 0.1 return self.fitness class PathMemory: def __init__(self): self.valid_paths = [] # Список успешных путей self.invalid_paths = [] # Список неудачных путей self.dead_ends = set() # Все тупиковые клетки self.branches = set() # Все развилки self.wrong_turns = set() # Неверные повороты self.all_visited = set() # Все посещенные клетки def add_individual(self, individual): """Добавляет данные особи в память""" path_copy = individual.path.copy() if individual.valid_path: self.valid_paths.append(path_copy) else: self.invalid_paths.append(path_copy) # Обновляем наборы клеток self.dead_ends.update(individual.dead_end_cells) self.branches.update(individual.branch_points) self.wrong_turns.update(individual.wrong_turns) self.all_visited.update(path_copy) def clear(self): """Очищает память путей""" self.valid_paths.clear() self.invalid_paths.clear() self.dead_ends.clear() self.branches.clear() self.wrong_turns.clear() self.all_visited.clear() class EvolutionaryMazeSolver: def __init__(self, maze, start, end, population_size=50): self.maze = maze self.start = start self.end = end self.population_size = population_size self.population = [] self.generation = 0 self.best_solution = None self.solution_found = False self.path_memory = PathMemory() self.session_active = True # Флаг активной сессии self.stats = { 'total_attempts': 0, 'success_count': 0, 'error_count': 0, 'dead_ends_count': 0, 'start_time': time.time(), 'end_time': None } def initialize_population(self): """Инициализирует новую популяцию""" self.population = [] self.generation = 0 self.solution_found = False self.best_solution = None self.session_active = True self.path_memory.clear() # Обновляем статистики self.stats = { 'total_attempts': 0, 'success_count': 0, 'error_count': 0, 'dead_ends_count': 0, 'start_time': time.time(), 'end_time': None } for _ in range(self.population_size): ind = Individual(self.maze, self.start, self.end) self.population.append(ind) self.stats['total_attempts'] += 1 def evaluate_population(self): """Оценивает популяцию и обновляет память путей""" for ind in self.population: ind.calculate_fitness() # Добавляем особь в память путей self.path_memory.add_individual(ind) if ind.valid_path: self.stats['success_count'] += 1 if not self.solution_found: self.solution_found = True self.best_solution = ind.path.copy() self.stats['end_time'] = time.time() print(f"Rozwiązanie znalezione! Pokolenie: {self.generation}, Długość: {len(ind.path)}") elif ind.stuck: self.stats['error_count'] += 1 # Обновляем счетчик тупиков self.stats['dead_ends_count'] = len(self.path_memory.dead_ends) def evolve_generation(self): """Эволюция одного поколения""" self.evaluate_population() if self.solution_found: return True # Турнирный отбор selected = [] for _ in range(self.population_size): tournament = random.sample(self.population, min(3, len(self.population))) winner = max(tournament, key=lambda x: x.fitness) selected.append(winner) # Создание нового поколения new_population = [] # Элитизм - лучшие особи переходят в новое поколение elite = sorted(self.population, key=lambda x: x.fitness, reverse=True)[:5] new_population.extend(elite) # Скрещивание и мутация while len(new_population) < self.population_size: parent1, parent2 = random.sample(selected, 2) child = self._crossover(parent1, parent2) self._mutate(child) new_population.append(child) self.stats['total_attempts'] += 1 self.population = new_population self.generation += 1 print(f"Pokolenie {self.generation}: najlepsze fitness = {max(ind.fitness for ind in self.population):.1f}") return False def _crossover(self, parent1, parent2): """Скрещивание двух родителей""" child = Individual(self.maze, self.start, self.end) min_len = min(len(parent1.path), len(parent2.path)) if min_len < 2: return child crossover_point = random.randint(1, min_len - 1) # Берем начало от первого родителя child.path = parent1.path[:crossover_point].copy() child.visited = set(child.path) # Добавляем уникальные точки от второго родителя for point in parent2.path[crossover_point:]: if point not in child.visited: child.path.append(point) child.visited.add(point) return child def _mutate(self, individual): """Мутация особи""" if random.random() < 0.3 and len(individual.path) > 1: # Обрезаем путь в случайном месте cut_point = random.randint(1, len(individual.path) - 1) removed_points = set(individual.path[cut_point:]) individual.path = individual.path[:cut_point] individual.visited -= removed_points def step_simulation(self): """Один шаг симуляции""" if self.solution_found or not self.session_active: return self.solution_found active_individuals = [ind for ind in self.population if not ind.finished and not ind.stuck] # Каждая активная особь делает ход for individual in active_individuals: individual.make_move() # Если мало активных особей, переходим к эволюции if len(active_individuals) < max(2, self.population_size * 0.1): return self.evolve_generation() return False def force_end_session(self): """Принудительно завершает сессию (при нажатии N)""" self.session_active = False if not self.stats['end_time']: self.stats['end_time'] = time.time() print(f"Sesja zakończona przymusowo. Pokolenie: {self.generation}") class MazeGUI: def __init__(self): pygame.init() self.width = 1200 self.height = 800 self.screen = pygame.display.set_mode((self.width, self.height)) pygame.display.set_caption("Evolutionary Maze Solver") self.maze_display_width = 700 self.info_panel_width = self.width - self.maze_display_width self.font = pygame.font.Font(None, 24) self.small_font = pygame.font.Font(None, 18) self.large_font = pygame.font.Font(None, 36) self.difficulty = None self.maze_solver = None self.maze = None self.running = False self.clock = pygame.time.Clock() self.results_saved = False # Кнопки оценки решения self.correct_button_rect = pygame.Rect(0, 0, 150, 40) self.incorrect_button_rect = pygame.Rect(0, 0, 150, 40) self.user_rating = None # Оценка пользователя (True - правильно, False - неправильно) self.rating_submitted = False # Флаг отправки оценки # ... (остальные методы без изменений до метода draw_info_panel) def show_difficulty_menu(self): """Показывает меню выбора сложности""" menu_running = True while menu_running: for event in pygame.event.get(): if event.type == pygame.QUIT: return None if event.type == pygame.KEYDOWN: if event.key == pygame.K_1: return "easy" elif event.key == pygame.K_2: return "medium" elif event.key == pygame.K_3: return "hard" elif event.key == pygame.K_ESCAPE: return None self.screen.fill(WHITE) title = self.large_font.render("Wybierz trudność labiryntu:", True, BLACK) self.screen.blit(title, (self.width // 2 - title.get_width() // 2, 200)) easy = self.font.render("1 - Łatwy (21x21)", True, BLACK) self.screen.blit(easy, (self.width // 2 - easy.get_width() // 2, 300)) medium = self.font.render("2 - Średni (31x31)", True, BLACK) self.screen.blit(medium, (self.width // 2 - medium.get_width() // 2, 350)) hard = self.font.render("3 - Trudny (41x41)", True, BLACK) self.screen.blit(hard, (self.width // 2 - hard.get_width() // 2, 400)) instruction = self.small_font.render("Naciśnij odpowiednią cyfrę", True, GRAY) self.screen.blit(instruction, (self.width // 2 - instruction.get_width() // 2, 500)) exit_hint = self.small_font.render("ESC - Wyjście z programu", True, GRAY) self.screen.blit(exit_hint, (self.width // 2 - exit_hint.get_width() // 2, 550)) pygame.display.flip() self.clock.tick(30) def generate_maze(self, difficulty): """Генерирует новый лабиринт""" generator = MazeGenerator(0, 0, difficulty) self.maze = generator.generate() maze_height, maze_width = self.maze.shape start = (maze_height // 2, 0) end = (maze_height // 2, maze_width - 1) self.maze_solver = EvolutionaryMazeSolver(self.maze, start, end) self.maze_solver.initialize_population() self.results_saved = False print(f"Nowy labirynt {difficulty} ({maze_height}x{maze_width}) wygenerowany") return start, end def draw_maze(self, start, end): """Отрисовывает лабиринт с цветовой кодировкой""" maze_height, maze_width = self.maze.shape cell_size = min(self.maze_display_width // maze_width, 600 // maze_height) maze_surface = pygame.Surface((maze_width * cell_size, maze_height * cell_size)) maze_surface.fill(WHITE) # Рисуем стены и проходы for y in range(maze_height): for x in range(maze_width): rect = pygame.Rect(x * cell_size, y * cell_size, cell_size, cell_size) if self.maze[y, x] == 1: pygame.draw.rect(maze_surface, BLACK, rect) else: pygame.draw.rect(maze_surface, WHITE, rect) pygame.draw.rect(maze_surface, GRAY, rect, 1) # Слой 1: Все посещенные клетки (светло-голубой фон) for y, x in self.maze_solver.path_memory.all_visited: rect = pygame.Rect(x * cell_size + 1, y * cell_size + 1, cell_size - 2, cell_size - 2) pygame.draw.rect(maze_surface, LIGHT_BLUE, rect) # Слой 2: Неудачные пути (оранжевые линии) for path in self.maze_solver.path_memory.invalid_paths: for i, (y, x) in enumerate(path): rect = pygame.Rect(x * cell_size + 3, y * cell_size + 3, cell_size - 6, cell_size - 6) pygame.draw.rect(maze_surface, ORANGE, rect) # Слой 3: Тупиковые участки (красные) for y, x in self.maze_solver.path_memory.dead_ends: rect = pygame.Rect(x * cell_size + 2, y * cell_size + 2, cell_size - 4, cell_size - 4) pygame.draw.rect(maze_surface, RED, rect) # Слой 4: Неверные повороты (темно-оранжевые точки) for y, x in self.maze_solver.path_memory.wrong_turns: center = (x * cell_size + cell_size // 2, y * cell_size + cell_size // 2) pygame.draw.circle(maze_surface, DARK_ORANGE, center, cell_size // 3) # Слой 5: Развилки (бирюзовые круги) for y, x in self.maze_solver.path_memory.branches: center = (x * cell_size + cell_size // 2, y * cell_size + cell_size // 2) pygame.draw.circle(maze_surface, CYAN, center, cell_size // 4) pygame.draw.circle(maze_surface, WHITE, center, cell_size // 4, 2) # Слой 6: Успешные пути (зеленые, кроме лучшего) for path in self.maze_solver.path_memory.valid_paths: if (self.maze_solver.best_solution and path != self.maze_solver.best_solution): for y, x in path: rect = pygame.Rect(x * cell_size + 2, y * cell_size + 2, cell_size - 4, cell_size - 4) pygame.draw.rect(maze_surface, GREEN, rect) # Слой 7: Активные пути (синие) for ind in self.maze_solver.population: if not ind.finished and not ind.stuck and len(ind.path) > 1: for y, x in ind.path: rect = pygame.Rect(x * cell_size + 3, y * cell_size + 3, cell_size - 6, cell_size - 6) pygame.draw.rect(maze_surface, BLUE, rect) # Слой 8: Лучшее решение (темно-зеленое с обводкой) if self.maze_solver.best_solution: for y, x in self.maze_solver.best_solution: rect = pygame.Rect(x * cell_size + 1, y * cell_size + 1, cell_size - 2, cell_size - 2) color = DARK_GREEN if self.maze_solver.solution_found else PURPLE pygame.draw.rect(maze_surface, color, rect) pygame.draw.rect(maze_surface, WHITE, rect, 2) # Слой 9: Старт и финиш (всегда сверху) start_rect = pygame.Rect(start[1] * cell_size, start[0] * cell_size, cell_size, cell_size) end_rect = pygame.Rect(end[1] * cell_size, end[0] * cell_size, cell_size, cell_size) pygame.draw.rect(maze_surface, FUKSIA, start_rect) pygame.draw.rect(maze_surface, WHITE, start_rect, 3) pygame.draw.rect(maze_surface, DARK_GREEN, end_rect) pygame.draw.rect(maze_surface, WHITE, end_rect, 3) # Отображаем лабиринт на экране maze_rect = maze_surface.get_rect() maze_rect.center = (self.maze_display_width // 2, self.height // 2) self.screen.blit(maze_surface, maze_rect) def draw_info_panel(self): """Отрисовывает информационную панель с кнопками оценки""" panel_x = self.maze_display_width panel_surface = pygame.Surface((self.info_panel_width, self.height)) panel_surface.fill(GRAY) y_offset = 20 title = self.font.render("Informacje o rozwiązaniu:", True, BLACK) panel_surface.blit(title, (10, y_offset)) y_offset += 40 if self.maze_solver: # Основная статистика gen_text = f"Pokolenie: {self.maze_solver.generation}" panel_surface.blit(self.font.render(gen_text, True, BLACK), (10, y_offset)) y_offset += 30 attempts_text = f"Łącznie prób: {self.maze_solver.stats['total_attempts']}" panel_surface.blit(self.font.render(attempts_text, True, BLACK), (10, y_offset)) y_offset += 30 success_text = f"Udanych: {self.maze_solver.stats['success_count']}" panel_surface.blit(self.font.render(success_text, True, GREEN), (10, y_offset)) y_offset += 30 error_text = f"Nieudanych: {self.maze_solver.stats['error_count']}" panel_surface.blit(self.font.render(error_text, True, RED), (10, y_offset)) y_offset += 30 # Статистика путей dead_ends_text = f"Znalezionych ślepych zaułków: {len(self.maze_solver.path_memory.dead_ends)}" panel_surface.blit(self.small_font.render(dead_ends_text, True, BLACK), (10, y_offset)) y_offset += 25 branches_text = f"Znalezionych rozwidlenie: {len(self.maze_solver.path_memory.branches)}" panel_surface.blit(self.small_font.render(branches_text, True, BLACK), (10, y_offset)) y_offset += 25 wrong_turns_text = f"Błędnych skrętów: {len(self.maze_solver.path_memory.wrong_turns)}" panel_surface.blit(self.small_font.render(wrong_turns_text, True, BLACK), (10, y_offset)) y_offset += 30 # Время elapsed = (self.maze_solver.stats['end_time'] or time.time()) - self.maze_solver.stats['start_time'] time_text = f"Czas: {elapsed:.1f} sek" if self.maze_solver.solution_found: time_text += " (rozwiązano)" elif not self.maze_solver.session_active: time_text += " (przerwano)" panel_surface.blit(self.font.render(time_text, True, BLACK), (10, y_offset)) y_offset += 40 # Информация о решении if self.maze_solver.best_solution: path_len = len(self.maze_solver.best_solution) if self.maze_solver.solution_found: solution_text = f"Długość rozwiązania: {path_len}" panel_surface.blit(self.font.render(solution_text, True, DARK_GREEN), (10, y_offset)) else: best_text = f"Najlepsza próba: {path_len}" panel_surface.blit(self.font.render(best_text, True, PURPLE), (10, y_offset)) y_offset += 30 # Кнопки оценки решения (только если решение найдено и оценка еще не отправлена) if (self.maze_solver and self.maze_solver.solution_found and not self.rating_submitted and not self.results_saved): rating_title = self.font.render("Oceń rozwiązanie:", True, BLACK) panel_surface.blit(rating_title, (10, y_offset)) y_offset += 30 # Кнопка "Правильно" self.correct_button_rect = pygame.Rect(10, y_offset, 150, 40) button_color = BUTTON_GREEN if self.user_rating is True else (200, 200, 200) pygame.draw.rect(panel_surface, button_color, self.correct_button_rect) pygame.draw.rect(panel_surface, BLACK, self.correct_button_rect, 2) correct_text = self.font.render("Prawidłowo", True, BUTTON_TEXT) panel_surface.blit(correct_text, (self.correct_button_rect.x + 20, self.correct_button_rect.y + 10)) y_offset += 50 # Кнопка "Неправильно" self.incorrect_button_rect = pygame.Rect(10, y_offset, 150, 40) button_color = BUTTON_RED if self.user_rating is False else (200, 200, 200) pygame.draw.rect(panel_surface, button_color, self.incorrect_button_rect) pygame.draw.rect(panel_surface, BLACK, self.incorrect_button_rect, 2) incorrect_text = self.font.render("Nieprawidłowo", True, BUTTON_TEXT) panel_surface.blit(incorrect_text, (self.incorrect_button_rect.x + 15, self.incorrect_button_rect.y + 10)) y_offset += 50 # Сообщение о выборе if self.user_rating is not None: rating_msg = "Wybrałeś: " + ("Prawidłowo" if self.user_rating else "Nieprawidłowo") panel_surface.blit(self.small_font.render(rating_msg, True, BLACK), (10, y_offset)) y_offset += 25 # Легенда legend_y = self.height - 350 legend_title = self.small_font.render("Legenda:", True, BLACK) panel_surface.blit(legend_title, (10, legend_y)) legend_y += 25 legend_items = [ (FUKSIA, "Start"), (DARK_GREEN, "Meta"), (DARK_GREEN, "Znalezione rozwiązanie"), (PURPLE, "Najlepsza próba"), (BLUE, "Aktywne próby"), (GREEN, "Udane ścieżki"), (ORANGE, "Nieudane ścieżki"), (RED, "Ślepe zaułki"), (DARK_ORANGE, "Błędne skręty"), (CYAN, "Rozgałęzienia"), (LIGHT_BLUE, "Odwiedzone komórki"), (BLACK, "Ściany") ] for color, text in legend_items: pygame.draw.rect(panel_surface, color, (10, legend_y, 15, 15)) panel_surface.blit(self.small_font.render(text, True, BLACK), (30, legend_y)) legend_y += 18 # Управление legend_y += 15 controls_title = self.small_font.render("Sterowanie:", True, BLACK) panel_surface.blit(controls_title, (10, legend_y)) legend_y += 25 controls = [ "R - Nowy labirynt", "N - Przerwij i zacznij od nowa", "1/2/3 - Zmień poziom trudności", "ESC - Wyjście" ] for control in controls: panel_surface.blit(self.small_font.render(control, True, BLACK), (10, legend_y)) legend_y += 18 self.screen.blit(panel_surface, (panel_x, 0)) def save_results(self, force_save=False): """Сохраняет результаты в CSV файл с оценкой пользователя""" # Не сохраняем автоматически, если решение найдено и ожидается оценка if (self.maze_solver and self.maze_solver.solution_found and not self.rating_submitted and not force_save): return try: filename = "maze_results.csv" file_exists = os.path.isfile(filename) with open(filename, 'a', newline='', encoding='utf-8') as csvfile: fieldnames = [ 'timestamp', 'difficulty', 'time_sec', 'generations', 'total_attempts', 'success_count', 'error_count', 'dead_ends_found', 'branches_found', 'wrong_turns', 'solution_length', 'solved', 'interrupted', 'user_rating' ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) if not file_exists: writer.writeheader() elapsed = (self.maze_solver.stats['end_time'] or time.time()) - self.maze_solver.stats['start_time'] row_data = { 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'difficulty': self.difficulty, 'time_sec': round(elapsed, 2), 'generations': self.maze_solver.generation, 'total_attempts': self.maze_solver.stats['total_attempts'], 'success_count': self.maze_solver.stats['success_count'], 'error_count': self.maze_solver.stats['error_count'], 'dead_ends_found': len(self.maze_solver.path_memory.dead_ends), 'branches_found': len(self.maze_solver.path_memory.branches), 'wrong_turns': len(self.maze_solver.path_memory.wrong_turns), 'solution_length': len(self.maze_solver.best_solution) if self.maze_solver.best_solution else 0, 'solved': self.maze_solver.solution_found, 'interrupted': not self.maze_solver.session_active and not self.maze_solver.solution_found, 'user_rating': self.user_rating if self.rating_submitted else None } writer.writerow(row_data) print(f"Wyniki zapisane: rozwiązano={self.maze_solver.solution_found}, " + f"przerwano={not self.maze_solver.session_active}, " + f"ocena={'Prawidłowo' if self.user_rating else 'Nieprawidłowo' if self.user_rating is not None else 'Brak oceny'}") except Exception as e: print(f"Błąd podczas zapisywania wyników: {e}") def run(self): """Основной цикл программы""" self.difficulty = self.show_difficulty_menu() if not self.difficulty: return start, end = self.generate_maze(self.difficulty) self.running = True while self.running: for event in pygame.event.get(): if event.type == pygame.QUIT: # При закрытии окна сохраняем результаты, если они не сохранены if not self.results_saved: self.save_results(force_save=True) self.running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_r: # Новый лабиринт # Сохраняем текущие результаты если не сохранены if not self.results_saved: self.save_results(force_save=True) start, end = self.generate_maze(self.difficulty) self.user_rating = None self.rating_submitted = False elif event.key == pygame.K_n: # Прервать и начать заново if self.maze_solver and self.maze_solver.session_active: # Принудительно завершаем сессию self.maze_solver.force_end_session() # Сохраняем результаты с отметкой о прерывании self.save_results(force_save=True) self.results_saved = True # Инициализируем заново self.maze_solver.initialize_population() self.results_saved = False self.user_rating = None self.rating_submitted = False elif event.key == pygame.K_ESCAPE: # При выходе сохраняем результаты, если они не сохранены if not self.results_saved: self.save_results(force_save=True) self.running = False elif event.key in (pygame.K_1, pygame.K_2, pygame.K_3): # Смена сложности new_diff = ["easy", "medium", "hard"][event.key - pygame.K_1] if new_diff != self.difficulty: # Сохраняем текущие результаты if not self.results_saved: self.save_results(force_save=True) self.difficulty = new_diff start, end = self.generate_maze(self.difficulty) self.user_rating = None self.rating_submitted = False elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: # Обработка кликов по кнопкам оценки if (self.maze_solver and self.maze_solver.solution_found and not self.rating_submitted and not self.results_saved): mouse_pos = pygame.mouse.get_pos() # Корректируем позицию мыши относительно панели информации panel_mouse_pos = (mouse_pos[0] - self.maze_display_width, mouse_pos[1]) if self.correct_button_rect.collidepoint(panel_mouse_pos): self.user_rating = True self.rating_submitted = True self.save_results() self.results_saved = True elif self.incorrect_button_rect.collidepoint(panel_mouse_pos): self.user_rating = False self.rating_submitted = True self.save_results() self.results_saved = True self.screen.fill(WHITE) # Шаг симуляции if (self.maze_solver and not self.maze_solver.solution_found and self.maze_solver.session_active): self.maze_solver.step_simulation() # Не сохраняем автоматически при нахождении решения - ждем оценки пользователя self.draw_maze(start, end) self.draw_info_panel() pygame.display.flip() self.clock.tick(60) pygame.quit() if __name__ == "__main__": game = MazeGUI() game.run()