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