DaMaze
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.idea
|
||||||
40
Cell.py
Normal file
40
Cell.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from Line import Line
|
||||||
|
from Point import Point
|
||||||
|
from Window import Window
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, win:Window=None):
|
||||||
|
self.has_left_wall:bool = True
|
||||||
|
self.has_right_wall:bool = True
|
||||||
|
self.has_top_wall:bool = True
|
||||||
|
self.has_bottom_wall:bool = True
|
||||||
|
self.visited:bool = False
|
||||||
|
self._x1:int|None = None
|
||||||
|
self._x2:int|None = None
|
||||||
|
self._y1:int|None = None
|
||||||
|
self._y2:int|None = None
|
||||||
|
self._win:Window = win
|
||||||
|
|
||||||
|
def draw(self, x1:int, y1:int, x2:int, y2:int) -> None:
|
||||||
|
if self._win is None:
|
||||||
|
return
|
||||||
|
self._x1 = x1
|
||||||
|
self._x2 = x2
|
||||||
|
self._y1 = y1
|
||||||
|
self._y2 = y2
|
||||||
|
self._win.draw_line(Line(Point(x1, y1), Point(x1, y2)), "black" if self.has_left_wall else "white")
|
||||||
|
self._win.draw_line(Line(Point(x1, y1), Point(x2, y1)), "black" if self.has_top_wall else "white")
|
||||||
|
self._win.draw_line(Line(Point(x2, y1), Point(x2, y2)), "black" if self.has_right_wall else "white")
|
||||||
|
self._win.draw_line(Line(Point(x1, y2), Point(x2, y2)), "black" if self.has_bottom_wall else "white")
|
||||||
|
|
||||||
|
def draw_move(self, to_cell:Self, undo:bool=False) -> None:
|
||||||
|
half_length = abs(self._x2 - self._x1) // 2
|
||||||
|
p1 = Point(half_length + self._x1, half_length + self._y1)
|
||||||
|
|
||||||
|
half_length2 = abs(to_cell._x2 - to_cell._x1) // 2
|
||||||
|
p2 = Point(half_length2 + to_cell._x1, half_length2 + to_cell._y1)
|
||||||
|
|
||||||
|
line = Line(p1, p2)
|
||||||
|
self._win.draw_line(line, "gray" if undo else "red")
|
||||||
14
Line.py
Normal file
14
Line.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from Point import Point
|
||||||
|
from tkinter import Canvas
|
||||||
|
|
||||||
|
class Line:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
p1:Point,
|
||||||
|
p2:Point,
|
||||||
|
):
|
||||||
|
self.p1:Point = p1
|
||||||
|
self.p2:Point = p2
|
||||||
|
|
||||||
|
def draw(self, canvas:Canvas, fill_color:str="black") -> None:
|
||||||
|
canvas.create_line(self.p1.x, self.p1.y, self.p2.x, self.p2.y, fill=fill_color, width=2)
|
||||||
166
Maze.py
Normal file
166
Maze.py
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
from Cell import Cell
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from Window import Window
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
x1:int,
|
||||||
|
y1:int,
|
||||||
|
num_rows:int,
|
||||||
|
num_cols:int,
|
||||||
|
cell_size_x:int,
|
||||||
|
cell_size_y:int,
|
||||||
|
win:Window|None=None,
|
||||||
|
seed:int|None=None,
|
||||||
|
):
|
||||||
|
self.cells:List[List[Cell]] = []
|
||||||
|
self.x1:int = x1
|
||||||
|
self.y1:int = y1
|
||||||
|
self.num_rows:int = num_rows
|
||||||
|
self.num_cols:int = num_cols
|
||||||
|
self.cell_size_x:int = cell_size_x
|
||||||
|
self.cell_size_y:int = cell_size_y
|
||||||
|
self.win:Window|None = win
|
||||||
|
|
||||||
|
if seed:
|
||||||
|
random.seed(seed)
|
||||||
|
|
||||||
|
self.create_cells()
|
||||||
|
self.break_entrance_and_exit()
|
||||||
|
self.break_walls_r(0, 0)
|
||||||
|
self.reset_cells_visited()
|
||||||
|
|
||||||
|
def create_cells(self) -> None:
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
self.cells.append([])
|
||||||
|
for j in range(self.num_rows):
|
||||||
|
self.cells[i].append(Cell(self.win))
|
||||||
|
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
for j in range(self.num_rows):
|
||||||
|
self.draw_cell(i, j)
|
||||||
|
|
||||||
|
def draw_cell(self, i, j) -> None:
|
||||||
|
if self.win is None:
|
||||||
|
return
|
||||||
|
x1 = self.x1 + i * self.cell_size_x
|
||||||
|
y1 = self.y1 + j * self.cell_size_y
|
||||||
|
x2 = x1 + self.cell_size_x
|
||||||
|
y2 = y1 + self.cell_size_y
|
||||||
|
self.cells[i][j].draw(x1, y1, x2, y2)
|
||||||
|
self.animate()
|
||||||
|
|
||||||
|
def animate(self) -> None:
|
||||||
|
if self.win is None:
|
||||||
|
return
|
||||||
|
self.win.redraw()
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
def reset_cells_visited(self) -> None:
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
for j in range(self.num_rows):
|
||||||
|
self.cells[i][j].visited = False
|
||||||
|
|
||||||
|
def break_entrance_and_exit(self) -> None:
|
||||||
|
self.cells[0][0].has_top_wall = False
|
||||||
|
self.draw_cell(0, 0)
|
||||||
|
self.cells[self.num_cols - 1][self.num_rows - 1].has_bottom_wall = False
|
||||||
|
self.draw_cell(self.num_cols - 1, self.num_rows - 1)
|
||||||
|
|
||||||
|
def break_walls_r(self, i:int, j:int) -> None:
|
||||||
|
self.cells[i][j].visited = True
|
||||||
|
while True:
|
||||||
|
last_index = []
|
||||||
|
|
||||||
|
if i > 0 and not self.cells[i - 1][j].visited:
|
||||||
|
last_index.append((i - 1, j))
|
||||||
|
if i < self.num_cols - 1 and not self.cells[i + 1][j].visited:
|
||||||
|
last_index.append((i + 1, j))
|
||||||
|
if j > 0 and not self.cells[i][j - 1].visited:
|
||||||
|
last_index.append((i, j - 1))
|
||||||
|
if j < self.num_rows - 1 and not self.cells[i][j + 1].visited:
|
||||||
|
last_index.append((i, j + 1))
|
||||||
|
|
||||||
|
if len(last_index) == 0:
|
||||||
|
self.draw_cell(i, j)
|
||||||
|
return
|
||||||
|
|
||||||
|
direction = random.randrange(len(last_index))
|
||||||
|
next_index = last_index[direction]
|
||||||
|
|
||||||
|
if next_index[0] == i + 1:
|
||||||
|
self.cells[i][j].has_right_wall = False
|
||||||
|
self.cells[i + 1][j].has_left_wall = False
|
||||||
|
if next_index[0] == i - 1:
|
||||||
|
self.cells[i][j].has_left_wall = False
|
||||||
|
self.cells[i - 1][j].has_right_wall = False
|
||||||
|
if next_index[1] == j + 1:
|
||||||
|
self.cells[i][j].has_bottom_wall = False
|
||||||
|
self.cells[i][j + 1].has_top_wall = False
|
||||||
|
if next_index[1] == j - 1:
|
||||||
|
self.cells[i][j].has_top_wall = False
|
||||||
|
self.cells[i][j - 1].has_bottom_wall = False
|
||||||
|
|
||||||
|
self.break_walls_r(next_index[0], next_index[1])
|
||||||
|
|
||||||
|
|
||||||
|
def solve_r(self, i:int, j:int) -> bool:
|
||||||
|
self.animate()
|
||||||
|
|
||||||
|
self.cells[i][j].visited = True
|
||||||
|
|
||||||
|
if i == self.num_cols - 1 and j == self.num_rows - 1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if (
|
||||||
|
i > 0
|
||||||
|
and not self.cells[i][j].has_left_wall
|
||||||
|
and not self.cells[i - 1][j].visited
|
||||||
|
):
|
||||||
|
self.cells[i][j].draw_move(self.cells[i - 1][j])
|
||||||
|
if self.solve_r(i - 1, j):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.cells[i][j].draw_move(self.cells[i - 1][j], True)
|
||||||
|
|
||||||
|
if (
|
||||||
|
i < self.num_cols - 1
|
||||||
|
and not self.cells[i][j].has_right_wall
|
||||||
|
and not self.cells[i + 1][j].visited
|
||||||
|
):
|
||||||
|
self.cells[i][j].draw_move(self.cells[i + 1][j])
|
||||||
|
if self.solve_r(i + 1, j):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.cells[i][j].draw_move(self.cells[i + 1][j], True)
|
||||||
|
|
||||||
|
if (
|
||||||
|
j > 0
|
||||||
|
and not self.cells[i][j].has_top_wall
|
||||||
|
and not self.cells[i][j - 1].visited
|
||||||
|
):
|
||||||
|
self.cells[i][j].draw_move(self.cells[i][j - 1])
|
||||||
|
if self.solve_r(i, j - 1):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.cells[i][j].draw_move(self.cells[i][j - 1], True)
|
||||||
|
|
||||||
|
if (
|
||||||
|
j < self.num_rows - 1
|
||||||
|
and not self.cells[i][j].has_bottom_wall
|
||||||
|
and not self.cells[i][j + 1].visited
|
||||||
|
):
|
||||||
|
self.cells[i][j].draw_move(self.cells[i][j + 1])
|
||||||
|
if self.solve_r(i, j + 1):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.cells[i][j].draw_move(self.cells[i][j + 1], True)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def solve(self) -> bool:
|
||||||
|
return self.solve_r(0, 0)
|
||||||
4
Point.py
Normal file
4
Point.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
class Point:
|
||||||
|
def __init__(self, x:int, y:int) -> None:
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
27
Window.py
Normal file
27
Window.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from tkinter import Tk, BOTH, Canvas
|
||||||
|
from Line import Line
|
||||||
|
|
||||||
|
class Window:
|
||||||
|
def __init__(self, width:int, height:int):
|
||||||
|
self.root = Tk()
|
||||||
|
self.root.title("Maze Solver")
|
||||||
|
self.root.protocol("WM_DELETE_WINDOW", self.close)
|
||||||
|
self.canvas = Canvas(self.root, bg="white", height=height, width=width)
|
||||||
|
self.canvas.pack(fill=BOTH, expand=1)
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def redraw(self) -> None:
|
||||||
|
self.root.update_idletasks()
|
||||||
|
self.root.update()
|
||||||
|
|
||||||
|
def wait_for_close(self) -> None:
|
||||||
|
self.running = True
|
||||||
|
while self.running:
|
||||||
|
self.redraw()
|
||||||
|
print("window closed...")
|
||||||
|
|
||||||
|
def draw_line(self, line:Line, fill_color="black") -> None:
|
||||||
|
line.draw(self.canvas, fill_color)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
self.running = False
|
||||||
23
main.py
Normal file
23
main.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from Window import Window
|
||||||
|
from Maze import Maze
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
cell_size_x = 50
|
||||||
|
cell_size_y = 50
|
||||||
|
num_rows = 10
|
||||||
|
num_cols = 15
|
||||||
|
margin = 20
|
||||||
|
screen_x = num_cols * cell_size_x + margin * 2
|
||||||
|
screen_y = num_rows * cell_size_y + margin * 2
|
||||||
|
|
||||||
|
sys.setrecursionlimit(10000)
|
||||||
|
win = Window(screen_x, screen_y)
|
||||||
|
|
||||||
|
maze = Maze(margin, margin, num_rows, num_cols, int(cell_size_x), int(cell_size_y), win, 10)
|
||||||
|
maze.solve()
|
||||||
|
win.wait_for_close()
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
35
tests.py
Normal file
35
tests.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from maze import Maze
|
||||||
|
|
||||||
|
|
||||||
|
class Tests(unittest.TestCase):
|
||||||
|
def test_maze_create_cells(self):
|
||||||
|
num_cols = 12
|
||||||
|
num_rows = 10
|
||||||
|
m1 = Maze(0, 0, num_rows, num_cols, 10, 10)
|
||||||
|
self.assertEqual(
|
||||||
|
len(m1.cells),
|
||||||
|
num_cols,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
len(m1.cells[0]),
|
||||||
|
num_rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_maze_break_entrance_and_exit(self):
|
||||||
|
num_cols = 12
|
||||||
|
num_rows = 10
|
||||||
|
m1 = Maze(0, 0, num_rows, num_cols, 10, 10)
|
||||||
|
self.assertEqual(
|
||||||
|
m1.cells[0][0].has_top_wall,
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
m1.cells[num_cols - 1][num_rows - 1].has_bottom_wall,
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user