263 lines
8.4 KiB
Python
263 lines
8.4 KiB
Python
# main.py
|
|
|
|
import tkinter as tk
|
|
from render import Render
|
|
import time
|
|
import threading
|
|
from maze_generator import generate_maze
|
|
import numpy as np
|
|
from tkinter import messagebox
|
|
from pathfinding_algorithms import bfs_generator, dfs_generator, a_star_generator
|
|
|
|
class MazeSolverApp:
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.render = Render(root, self)
|
|
self.timer_running = False
|
|
self.start_time = None
|
|
self.elapsed_time = 0
|
|
self.timer_id = None
|
|
self.maze = None
|
|
self.solving = False
|
|
|
|
def start_timer(self):
|
|
if not self.timer_running:
|
|
self.timer_running = True
|
|
self.start_time = time.time() - self.elapsed_time
|
|
self.update_timer()
|
|
|
|
def stop_timer(self):
|
|
if self.timer_running:
|
|
self.timer_running = False
|
|
if self.timer_id:
|
|
self.root.after_cancel(self.timer_id)
|
|
|
|
def reset_timer(self):
|
|
self.stop_timer()
|
|
self.elapsed_time = 0
|
|
self.render.update_timer_label("00:00")
|
|
|
|
def update_timer(self):
|
|
if self.timer_running:
|
|
self.elapsed_time = time.time() - self.start_time
|
|
minutes = int(self.elapsed_time // 60)
|
|
seconds = int(self.elapsed_time % 60)
|
|
self.render.update_timer_label(f"{minutes:02d}:{seconds:02d}")
|
|
self.timer_id = self.root.after(1000, self.update_timer)
|
|
|
|
def generate_maze(self):
|
|
params = self.render.get_maze_parameters()
|
|
if params is None:
|
|
return # Invalid parameters, abort
|
|
|
|
self.reset_timer()
|
|
|
|
# Generate maze in a separate thread to avoid freezing the UI
|
|
threading.Thread(target=self._generate_maze_thread, args=(params,)).start()
|
|
|
|
def _generate_maze_thread(self, params):
|
|
try:
|
|
maze = generate_maze(
|
|
rows=params['rows'],
|
|
cols=params['cols'],
|
|
generation_algorithm=params['generation_algorithm'],
|
|
seed=params['seed'],
|
|
wall_density=params['wall_density']
|
|
)
|
|
self.maze = maze
|
|
# Update the UI in the main thread
|
|
self.root.after(0, self.render.draw_maze, self.maze)
|
|
self.root.after(0, self.start_timer)
|
|
print("Maze generated.")
|
|
except Exception as e:
|
|
print(f"Error generating maze: {e}")
|
|
messagebox.showerror("Error", f"An error occurred while generating the maze:\n{e}")
|
|
|
|
def solve_maze(self):
|
|
if not self.maze:
|
|
messagebox.showwarning("No Maze", "Please generate a maze first.")
|
|
return
|
|
if self.solving:
|
|
messagebox.showinfo("Solving", "Maze is already being solved.")
|
|
return
|
|
|
|
self.solving = True
|
|
self.reset_timer()
|
|
self.start_time = time.time()
|
|
self.start_timer()
|
|
self.steps = 0
|
|
|
|
# Get selected algorithm
|
|
algorithm = self.render.update_algorithm_selection()
|
|
|
|
# Initialize the solver generator
|
|
if algorithm == "BFS":
|
|
self.solver_generator = bfs_generator(self.maze, (1, 1), (self.maze.shape[0]-2, self.maze.shape[1]-2))
|
|
elif algorithm == "DFS":
|
|
self.solver_generator = dfs_generator(self.maze, (1, 1), (self.maze.shape[0]-2, self.maze.shape[1]-2))
|
|
elif algorithm == "A*":
|
|
self.solver_generator = a_star_generator(self.maze, (1, 1), (self.maze.shape[0]-2, self.maze.shape[1]-2))
|
|
else:
|
|
messagebox.showerror("Error", f"Unknown algorithm: {algorithm}")
|
|
self.solving = False
|
|
self.stop_timer()
|
|
return
|
|
|
|
# Start the solving process
|
|
self.root.after(0, self._process_solver_step)
|
|
|
|
def _process_solver_step(self):
|
|
try:
|
|
action, cell, steps = next(self.solver_generator)
|
|
self.steps = steps
|
|
|
|
if action == 'visit':
|
|
self.render.mark_visited(cell)
|
|
elif action == 'enqueue':
|
|
self.render.mark_frontier(cell)
|
|
elif action == 'path':
|
|
self.render.mark_path(cell)
|
|
elif action == 'done':
|
|
self.solving = False
|
|
self.stop_timer()
|
|
self.show_summary()
|
|
return
|
|
|
|
# Schedule the next step
|
|
self.root.after(10, self._process_solver_step) # Adjust delay as needed for visualization speed
|
|
except StopIteration:
|
|
self.solving = False
|
|
self.stop_timer()
|
|
self.show_summary()
|
|
|
|
def show_summary(self):
|
|
elapsed_time = time.time() - self.start_time
|
|
params = self.render.get_maze_parameters()
|
|
algorithm = self.render.update_algorithm_selection()
|
|
summary = (
|
|
f"Algorithm: {algorithm}\n"
|
|
f"Time Taken: {elapsed_time:.2f} seconds\n"
|
|
f"Steps: {self.steps}\n"
|
|
f"Maze Size: {params['rows']}x{params['cols']}\n"
|
|
f"Generation Algorithm: {params['generation_algorithm']}\n"
|
|
f"Wall Density: {params['wall_density']}\n"
|
|
f"Seed: {params['seed'] or 'None'}"
|
|
)
|
|
messagebox.showinfo("Maze Solved!", summary)
|
|
|
|
def stop_solving(self):
|
|
if self.solving:
|
|
# Currently, our solving algorithms are not interruptible.
|
|
# Implementing a stoppable solving process would require more complex threading control.
|
|
messagebox.showinfo("Stop Solving", "Stopping the solver is not implemented yet.")
|
|
else:
|
|
messagebox.showinfo("Not Solving", "No solving process is currently running.")
|
|
|
|
# Pathfinding algorithms
|
|
from collections import deque
|
|
import heapq
|
|
|
|
def get_adjacent_cells(cell, maze):
|
|
row, col = cell
|
|
directions = [(-1,0), (1,0), (0,-1), (0,1)]
|
|
neighbors = []
|
|
for dr, dc in directions:
|
|
r, c = row + dr, col + dc
|
|
if 0 <= r < maze.shape[0] and 0 <= c < maze.shape[1] and maze[r][c] == 0:
|
|
neighbors.append((r, c))
|
|
return neighbors
|
|
|
|
def bfs(maze, start, end):
|
|
queue = deque([start])
|
|
visited = set()
|
|
visited.add(start)
|
|
parent = {}
|
|
|
|
while queue:
|
|
current = queue.popleft()
|
|
if current == end:
|
|
break
|
|
for neighbor in get_adjacent_cells(current, maze):
|
|
if neighbor not in visited:
|
|
queue.append(neighbor)
|
|
visited.add(neighbor)
|
|
parent[neighbor] = current
|
|
|
|
# Reconstruct path
|
|
path = []
|
|
if end in parent:
|
|
cell = end
|
|
while cell != start:
|
|
path.append(cell)
|
|
cell = parent[cell]
|
|
path.append(start)
|
|
path.reverse()
|
|
return path
|
|
|
|
def dfs(maze, start, end):
|
|
stack = [start]
|
|
visited = set()
|
|
visited.add(start)
|
|
parent = {}
|
|
|
|
while stack:
|
|
current = stack.pop()
|
|
if current == end:
|
|
break
|
|
for neighbor in get_adjacent_cells(current, maze):
|
|
if neighbor not in visited:
|
|
stack.append(neighbor)
|
|
visited.add(neighbor)
|
|
parent[neighbor] = current
|
|
|
|
# Reconstruct path
|
|
path = []
|
|
if end in parent:
|
|
cell = end
|
|
while cell != start:
|
|
path.append(cell)
|
|
cell = parent[cell]
|
|
path.append(start)
|
|
path.reverse()
|
|
return path
|
|
|
|
def a_star(maze, start, end):
|
|
heap = []
|
|
heapq.heappush(heap, (0, start))
|
|
came_from = {}
|
|
g_score = {start: 0}
|
|
f_score = {start: heuristic(start, end)}
|
|
|
|
while heap:
|
|
current_f, current = heapq.heappop(heap)
|
|
if current == end:
|
|
break
|
|
for neighbor in get_adjacent_cells(current, maze):
|
|
tentative_g = g_score[current] + 1
|
|
if neighbor not in g_score or tentative_g < g_score[neighbor]:
|
|
came_from[neighbor] = current
|
|
g_score[neighbor] = tentative_g
|
|
f_score[neighbor] = tentative_g + heuristic(neighbor, end)
|
|
heapq.heappush(heap, (f_score[neighbor], neighbor))
|
|
|
|
# Reconstruct path
|
|
path = []
|
|
if end in came_from:
|
|
cell = end
|
|
while cell != start:
|
|
path.append(cell)
|
|
cell = came_from[cell]
|
|
path.append(start)
|
|
path.reverse()
|
|
return path
|
|
|
|
def heuristic(a, b):
|
|
# Use Manhattan distance as heuristic
|
|
return abs(a[0] - b[0]) + abs(a[1] - b[1])
|
|
|
|
if __name__ == "__main__":
|
|
root = tk.Tk()
|
|
root.title("Maze Solver")
|
|
app = MazeSolverApp(root)
|
|
root.mainloop()
|