214 lines
8.4 KiB
Python
214 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
|
|
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
|
|
self.autogenerated_seed = None # Track the last autogenerated seed
|
|
self.stop_requested = False # Add this flag for stopping the solver
|
|
|
|
# Bind keys to quit the application
|
|
self.root.bind('<Escape>', self.quit_application)
|
|
self.root.bind('q', self.quit_application)
|
|
|
|
def quit_application(self, _=None):
|
|
"""Cleanly quit the application."""
|
|
print("Quitting the application...")
|
|
self.root.quit() # This will close the Tkinter loop
|
|
self.root.destroy() # This will clean up and destroy the window
|
|
print("Application exited.")
|
|
|
|
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()
|
|
|
|
# Get additional parameters from the new sliders
|
|
dead_ends = int(self.render.dead_ends_scale.get())
|
|
branching_factor = int(self.render.branching_factor_scale.get())
|
|
connectedness = int(self.render.connectedness_scale.get()) * 10
|
|
# Generate maze in a separate thread to avoid freezing the UI
|
|
threading.Thread(target=self._generate_maze_thread, args=(params, dead_ends, branching_factor, connectedness)).start()
|
|
|
|
def _generate_maze_thread(self, params, dead_ends, branching_factor, connectedness):
|
|
try:
|
|
# Check if the seed input is empty or if it's the autogenerated seed
|
|
seed_input = params['seed']
|
|
if seed_input is None or str(seed_input) == self.autogenerated_seed:
|
|
seed = None # Force the generator to create a new random seed
|
|
else:
|
|
seed = seed_input # Use the user-provided seed
|
|
|
|
# Get the values from the new sliders
|
|
dead_ends = int(self.render.dead_ends_scale.get())
|
|
branching_factor = int(self.render.branching_factor_scale.get())
|
|
connectedness = int(self.render.connectedness_scale.get()) * 10 # Convert to percentage
|
|
|
|
# Call generate_maze with the seed
|
|
maze, used_seed = generate_maze(
|
|
rows=params['rows'],
|
|
cols=params['cols'],
|
|
generation_algorithm=params['generation_algorithm'],
|
|
seed=seed,
|
|
wall_density=params['wall_density'],
|
|
dead_ends=dead_ends,
|
|
branching_factor=branching_factor,
|
|
connectedness=connectedness
|
|
)
|
|
|
|
self.maze = maze
|
|
|
|
# If the seed was autogenerated (seed=None), update the seed field
|
|
if seed is None:
|
|
self.autogenerated_seed = str(used_seed) # Track the new autogenerated seed
|
|
self.render.seed_entry.delete(0, tk.END)
|
|
self.render.seed_entry.insert(0, str(used_seed)) # Insert the generated seed
|
|
else:
|
|
self.autogenerated_seed = None # Reset if a user-provided seed is used
|
|
|
|
# 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(f"Maze generated with seed: {used_seed}")
|
|
|
|
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 self.maze is None or self.maze.size == 0: # Adjust the maze check to avoid ambiguity
|
|
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.stop_requested = False # Reset the flag when starting
|
|
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):
|
|
if self.stop_requested:
|
|
self.solving = False
|
|
self.stop_timer()
|
|
print("Solver stopped by user.")
|
|
return
|
|
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 if self.start_time else 0
|
|
params = self.render.get_maze_parameters() or {}
|
|
algorithm = self.render.update_algorithm_selection() or "Unknown"
|
|
summary = (
|
|
f"Algorithm: {algorithm}\n"
|
|
f"Time Taken: {elapsed_time:.2f} seconds\n"
|
|
f"Steps: {self.steps or 0}\n"
|
|
f"Maze Size: {params.get('rows', '?')}x{params.get('cols', '?')}\n"
|
|
f"Generation Algorithm: {params.get('generation_algorithm', 'Unknown')}\n"
|
|
f"Wall Density: {params.get('wall_density', 'Unknown')}\n"
|
|
f"Seed: {params.get('seed', 'None')}"
|
|
)
|
|
messagebox.showinfo("Maze Solved!", summary)
|
|
|
|
def stop_solving(self):
|
|
if self.solving:
|
|
self.stop_requested = True # Set the flag to stop the solver
|
|
messagebox.showinfo("Stop Solving", "Maze Solver Should be stopped")
|
|
else:
|
|
messagebox.showinfo("Not Solving", "No solving process is currently running.")
|
|
|
|
if __name__ == "__main__":
|
|
root = tk.Tk()
|
|
root.title("Maze Solver")
|
|
app = MazeSolverApp(root)
|
|
|
|
try:
|
|
root.mainloop() # Start the Tkinter event loop
|
|
except KeyboardInterrupt:
|
|
print("\nKeyboardInterrupt detected. Exiting the application cleanly...")
|
|
app.quit_application() # Call the quit method to clean up
|