checkpoint: Add side panel
Add a side panel to allow users to interact with the app. The generate button now generates the maze.
This commit is contained in:
parent
ed5f0033fa
commit
4c7e50ec39
6 changed files with 161 additions and 75 deletions
86
app.py
Normal file
86
app.py
Normal file
|
@ -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
|
32
cell.py
32
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:
|
||||
"""
|
||||
|
|
39
graphics.py
39
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)
|
||||
|
|
35
main.py
35
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__":
|
||||
|
|
43
maze.py
43
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:
|
||||
"""
|
||||
|
|
1
tests.py
1
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]
|
||||
|
|
Loading…
Reference in a new issue