Files
maze_solver/main.py
klein panic aeaa436c25 first commit
2024-10-23 18:44:54 -04:00

206 lines
8.0 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
# 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())
# Generate maze in a separate thread to avoid freezing the UI
threading.Thread(target=self._generate_maze_thread, args=(params, dead_ends, branching_factor)).start()
def _generate_maze_thread(self, params, dead_ends, branching_factor):
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())
# 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, # Pass the dead ends parameter
branching_factor=branching_factor # Pass the branching factor parameter
)
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.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 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:
# 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.")
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