From 4c7e50ec39433f0612d4a4920a573f48befbd8e2 Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Sat, 17 Feb 2024 20:06:09 +0000 Subject: [PATCH] checkpoint: Add side panel Add a side panel to allow users to interact with the app. The generate button now generates the maze. --- app.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++ cell.py | 32 ++++++++++---------- graphics.py | 39 +++++++++++------------- main.py | 35 ++++++++++++---------- maze.py | 43 +++++++++++++-------------- tests.py | 1 + 6 files changed, 161 insertions(+), 75 deletions(-) create mode 100644 app.py diff --git a/app.py b/app.py new file mode 100644 index 0000000..4db54d5 --- /dev/null +++ b/app.py @@ -0,0 +1,86 @@ +from tkinter import ttk, Tk, Canvas, StringVar, BooleanVar +from maze import Maze +from solver import Solver +from graphics import Graphics + + +class App(Tk): + def __init__(self): + super().__init__() + + self.title("Maze Solver") + + # Position the window to the centre of the screen + # screen_width = self._root.winfo_screenwidth() + # screen_height = self._root.winfo_screenheight() + # centre_x = int(screen_width/2 - width/2) + # centre_y = int(screen_height/2 - height/2) + # self._root.geometry(f"{width}x{height}+{centre_x}+{centre_y}") + + # Styling + self.style = ttk.Style() + self.style.theme_use("clam") + self.background_colour = "white" + self.cell_grid_colour = "black" + + self.columnconfigure(0, weight=1) + self.columnconfigure(1, weight=1) + + self.canvas = Canvas(self) + self.canvas.config( + bg=self.background_colour, + width=800, + height=800 + ) + self.canvas.grid(column=1, row=0) + + self.graphics = Graphics(self.canvas) + + self.maze = Maze( + x_position=10, + y_position=10, + height=19, + width=19, + cell_height=40, + cell_width=40, + graphics=self.graphics, + ) + + self.solver = Solver(self.maze) + + self.side_panel = self._create_side_panel() + self.side_panel.grid(column=0, row=0) + + def _create_side_panel(self): + frame = ttk.Frame(self) + label = ttk.Label(frame) + label.config(text="Maze Solver", font=(None, 20)) + label.pack() + generate = ttk.Button( + frame, + text="Generate maze", + command=self.maze.generate, + ) + generate.pack() + algorithm = StringVar() + combobox = ttk.Combobox(frame, textvariable=algorithm) + algorithm_label = ttk.Label(frame, text="Searching algorithm:") + algorithm_label.pack() + combobox["values"] = ("Breadth-First Search", "Depth-First Search") + combobox["state"] = "readonly" + combobox.pack() + randomness = BooleanVar() + enable_randomness = ttk.Checkbutton(frame) + enable_randomness.config( + text="Enable Randomness", + variable=randomness, + onvalue=True, + offvalue=False, + ) + enable_randomness.pack() + solve = ttk.Button( + frame, + text="Solve the maze", + ) + solve.pack() + return frame diff --git a/cell.py b/cell.py index 5c6244f..eb69411 100644 --- a/cell.py +++ b/cell.py @@ -1,6 +1,7 @@ from typing import Dict from enum import Enum -from graphics import Window, Point, Line +from tkinter import Canvas +from graphics import Graphics, Point, Line import errors @@ -21,17 +22,17 @@ class CellWall: a Cell's wall. """ - def __init__(self, line: Line, window: Window) -> None: + def __init__(self, line: Line, graphics: Graphics) -> None: self.exists = True self.line = line - self._window = window + self._graphics = graphics def draw(self): - fill_colour = self._window.cell_grid_colour + fill_colour = "black" if not self.exists: - fill_colour = self._window.background_colour + fill_colour = "white" - self._window.draw_line(self.line, fill_colour=fill_colour) + self._graphics.draw_line(self.line, fill_colour=fill_colour) class Cell: @@ -43,7 +44,7 @@ class Cell: self, x1: int, y1: int, x2: int, y2: int, - window: Window = None, + graphics: Graphics = None, ) -> None: # Validation if (x2 < x1) or (y2 < y1): @@ -62,10 +63,10 @@ class Cell: right_wall = Line(Point(x2, y1), Point(x2, y2)) self._walls: Dict[CellWallLabels, CellWall] = { - CellWallLabels.TOP: CellWall(top_wall, window), - CellWallLabels.BOTTOM: CellWall(bottom_wall, window), - CellWallLabels.LEFT: CellWall(left_wall, window), - CellWallLabels.RIGHT: CellWall(right_wall, window), + CellWallLabels.TOP: CellWall(top_wall, graphics), + CellWallLabels.BOTTOM: CellWall(bottom_wall, graphics), + CellWallLabels.LEFT: CellWall(left_wall, graphics), + CellWallLabels.RIGHT: CellWall(right_wall, graphics), } # Calculate the cell's central point @@ -73,8 +74,7 @@ class Cell: centre_y = y1 + ((y2 - y1) / 2) self._centre = Point(centre_x, centre_y) - # A reference to the root Window class for drawing purposes. - self._window = window + self._graphics = graphics self._visited: Dict[str, bool] = { "generator": False, @@ -120,7 +120,7 @@ class Cell: """ draw draws the cell onto the canvas """ - if not self._window: + if not self._graphics: return for label in CellWallLabels: @@ -131,14 +131,14 @@ class Cell: draw_move draws a path between the centre of this cell and the centre of the given cell. """ - if not self._window: + if not self._graphics: return fill_colour = "red" if undo: fill_colour = "grey" line = Line(self.centre(), to_cell.centre()) - self._window.draw_line(line, fill_colour) + self._graphics.draw_line(line, fill_colour) def was_visited_by(self, visitor: str) -> bool: """ diff --git a/graphics.py b/graphics.py index 6efae19..b8875fa 100644 --- a/graphics.py +++ b/graphics.py @@ -1,4 +1,4 @@ -from tkinter import Tk, BOTH, Canvas +from tkinter import Tk, Canvas class Point: @@ -20,16 +20,26 @@ class Line: self.point_a = point_a self.point_b = point_b - def draw(self, canvas: Canvas, fill_colour: str) -> None: + +class Graphics: + def __init__(self, canvas: Canvas) -> None: + self._canvas = canvas + + def draw_line( + self, + line: Line, + fill_colour: str = "black", + width: int = 2 + ) -> None: """ - draw draws a line on a given canvas. + draws a line onto the canvas. """ - canvas.create_line( - self.point_a.x, self.point_a.y, - self.point_b.x, self.point_b.y, - fill=fill_colour, width=2 + self._canvas.create_line( + line.point_a.x, line.point_a.y, + line.point_b.x, line.point_b.y, + fill=fill_colour, + width=width, ) - canvas.pack(fill=BOTH, expand=1) class Window: @@ -66,16 +76,3 @@ class Window: """ self._root.update_idletasks() self._root.update() - - def mainloop(self) -> None: - """ - mainloop calls the root widget's mainloop method to - ensure that the window remains visible on the screen. - """ - self._root.mainloop() - - def draw_line(self, line: Line, fill_colour: str = "black") -> None: - """ - draw_line draws a line on the canvas. - """ - line.draw(self._canvas, fill_colour) diff --git a/main.py b/main.py index b1b3126..a2d4451 100644 --- a/main.py +++ b/main.py @@ -1,29 +1,32 @@ from graphics import Window from maze import Maze from solver import Solver +from app import App def main(): - window = Window(800, 800) + app = App() + app.mainloop() + #window = Window(800, 800) - game = Maze( - x_position=10, - y_position=10, - height=19, - width=19, - cell_height=40, - cell_width=40, - window=window - ) + #game = Maze( + # x_position=10, + # y_position=10, + # height=19, + # width=19, + # cell_height=40, + # cell_width=40, + # window=window + #) - solver = Solver(game) + #solver = Solver(game) - if solver.solve(solver.solve_with_bfs_r, True): - print("Maze solved successfully :)") - else: - print("I'm unable to solve the maze :(") + #if solver.solve(solver.solve_with_bfs_r, True): + # print("Maze solved successfully :)") + #else: + # print("I'm unable to solve the maze :(") - window.mainloop() + #window.mainloop() if __name__ == "__main__": diff --git a/maze.py b/maze.py index 3d29134..0efbcf2 100644 --- a/maze.py +++ b/maze.py @@ -2,7 +2,7 @@ from typing import List from time import sleep import random from enum import Enum -from graphics import Window +from graphics import Graphics from cell import Cell, CellWallLabels @@ -93,7 +93,7 @@ class Maze: width: int, cell_height: int, cell_width: int, - window: Window = None, + graphics: Graphics = None, seed=None, ) -> None: self._x_position = x_position @@ -102,7 +102,7 @@ class Maze: self._width = width self._cell_height = cell_height self._cell_width = cell_width - self._window = window + self._graphics = graphics self._generator = "generator" # initialise the random number generator @@ -112,18 +112,19 @@ class Maze: self._cell_grid: List[List[Cell]] = [] self._create_cell_grid() - # Open up the maze's entrance and exit. - self._open_entrance_and_exit() + def generate(self): + """ + randomly generates a new maze. + """ - start_position = MazePosition( + self._draw_cell_grid() + self._open_entrance_and_exit() + self._break_walls_r(MazePosition( i=0, j=0, last_i=self._height-1, last_j=self._width-1, - ) - - # Generate the maze. - self._break_walls_r(start_position) + )) def get_last_i(self) -> int: "returns the last position of the Maze's outer list." @@ -152,7 +153,7 @@ class Maze: cursor_y, (cursor_x + self._cell_width), (cursor_y + self._cell_height), - self._window + self._graphics, ) cells[j] = cell if j == self._width - 1: @@ -162,9 +163,6 @@ class Maze: self._cell_grid[i] = cells cursor_y += self._cell_height - if self._window: - self._draw_cell_grid() - def _draw_cell_grid(self) -> None: """ draws all the cells on the maze with a short pause between each cell @@ -186,7 +184,7 @@ class Maze: self._cell_grid[self._height-1][self._width - 1].configure_walls(bottom=False) - if self._window: + if self._graphics: self._draw_cell(0, 0) self._draw_cell( i=self._height-1, @@ -222,12 +220,13 @@ class Maze: possible_directions.append(direction) if len(possible_directions) == 0: - if self._window: + if self._graphics: self._draw_cell(i=current_position.i, j=current_position.j) break chosen_direction = random.choice(possible_directions) - next_position = current_position.get_adjacent_position(chosen_direction) + next_position = current_position.get_adjacent_position( + chosen_direction) if chosen_direction is MazeDirection.ABOVE: self._configure_cell_walls( @@ -274,7 +273,7 @@ class Maze: left=False, ) - if self._window: + if self._graphics: self._draw_cell(i=current_position.i, j=current_position.j) self._break_walls_r(next_position) @@ -285,8 +284,8 @@ class Maze: """ self._cell_grid[i][j].draw() - self._window.redraw() - sleep(0.05) + #self._canvas.redraw() + #sleep(0.05) def _draw_path(self, current_cell: Cell, next_cell: Cell, undo: bool = False) -> None: """ @@ -294,8 +293,8 @@ class Maze: """ current_cell.draw_move(next_cell, undo) - self._window.redraw() - sleep(0.05) + #self._canvas.redraw() + #sleep(0.05) def mark_cell_as_visited(self, i: int, j: int, visitor: str) -> None: """ diff --git a/tests.py b/tests.py index 0f83c33..b3a1a3c 100644 --- a/tests.py +++ b/tests.py @@ -65,6 +65,7 @@ class Tests(unittest.TestCase): None, None, ) + m.generate() self.assertFalse(m._cell_grid[0][0].wall_exists(CellWallLabels.TOP)) self.assertFalse( m._cell_grid[number_of_cell_rows - 1]