refactor: add MazeDirections and MazePositions

- Created a new enum type called MazeDirections to represent the
  directions you can make in the maze.
- Created a class called MazePosition to represent the position on the
  maze grid. It has the functionality to calculate and return adjacent
  positions.
- Used the above two additions to refactor the _break_walls_r method and
  make it a bit easier to read.
This commit is contained in:
Dan Anglin 2024-02-15 14:58:51 +00:00
parent a8ef152847
commit 32696f311d
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
2 changed files with 178 additions and 45 deletions

141
maze.py
View file

@ -1,10 +1,79 @@
from typing import List
from time import sleep
import random
from enum import Enum
from graphics import Window
from cell import Cell
class MazeDirections(Enum):
"""
MazeDirection represents the directions you can
take in the maze.
"""
ABOVE = 0
BELOW = 1
LEFT = 2
RIGHT = 3
class MazePosition:
"""
MazePosition represents a position on the maze grid.
"""
def __init__(self, i: int, j: int, max_i: int, max_j: int):
self.i = i
self.j = j
self.max_i = max_i
self.max_j = max_j
def __eq__(self, other) -> bool:
if (self.i == other.i) and (self.j == other.j) and (self.max_i == other.max_i) and (self.max_j == other.max_j):
return True
return False
def get_adjacent_position(
self,
direction: MazeDirections
) -> 'MazePosition':
if direction not in MazeDirections:
raise TypeError(
"The argument does not appear to be a valid MazeDirection"
)
if direction is MazeDirections.ABOVE and (self.i-1 >= 0):
return MazePosition(
i=self.i-1,
j=self.j,
max_i=self.max_i,
max_j=self.max_j,
)
if direction is MazeDirections.BELOW and (self.i+1 <= self.max_i):
return MazePosition(
i=self.i+1,
j=self.j,
max_i=self.max_i,
max_j=self.max_j,
)
if direction is MazeDirections.LEFT and (self.j-1 >= 0):
return MazePosition(
i=self.i,
j=self.j-1,
max_i=self.max_i,
max_j=self.max_j,
)
if direction is MazeDirections.RIGHT and (self.j+1 <= self.max_j):
return MazePosition(
i=self.i,
j=self.j+1,
max_i=self.max_i,
max_j=self.max_j,
)
return None
class Maze:
"""
Maze represents a two-dimensional grid of Cells.
@ -75,9 +144,9 @@ class Maze:
draws all the cells on the maze with a short pause between each cell
for animation purposes.
"""
for y in range(self._num_cell_rows):
for x in range(self._num_cells_per_row):
self._draw_cell(y=y, x=x)
for i in range(self._num_cell_rows):
for j in range(self._num_cells_per_row):
self._draw_cell(i=i, j=j)
def _open_entrance_and_exit(self) -> None:
"""
@ -92,72 +161,70 @@ class Maze:
if self._window:
self._draw_cell(0, 0)
self._draw_cell(
y=self._num_cell_rows-1,
x=self._num_cells_per_row-1
i=self._num_cell_rows-1,
j=self._num_cells_per_row-1
)
def _break_walls_r(self, y: int, x: int) -> None:
def _break_walls_r(self, i: int, j: int) -> None:
"""
_break_walls_r generates a random maze by traversing through the
cells and randomly knocking down the walls to create the maze's paths.
"""
current_cell = self._cells[y][x]
current_position = MazePosition(
i=i,
j=j,
max_i=self._num_cell_rows-1,
max_j=self._num_cells_per_row-1
)
current_cell = self._cells[i][j]
current_cell.visited_by_maze_generator = True
above, below, left, right = "above", "below", "left", "right"
while True:
adjacent_cells = {
above: (y-1, x),
below: (y+1, x),
left: (y, x-1),
right: (y, x+1),
}
to_visit: List[str] = []
possible_directions: List[MazeDirections] = []
for k, value in adjacent_cells.items():
if (value[0] < 0) or (value[1] < 0) or (value[0] > self._num_cell_rows-1) or (value[1] > self._num_cells_per_row-1):
for direction in MazeDirections:
adjacent_position = current_position.get_adjacent_position(
direction)
if adjacent_position is None:
continue
if self._cells[value[0]][value[1]].visited_by_maze_generator:
adjacent_cell = self._cells[adjacent_position.i][adjacent_position.j]
if adjacent_cell.visited_by_maze_generator:
continue
possible_directions.append(direction)
to_visit.append(k)
if len(to_visit) == 0:
if len(possible_directions) == 0:
if self._window:
self._draw_cell(y=y, x=x)
self._draw_cell(i=i, j=j)
break
next_direction = random.choice(to_visit)
next_cell = self._cells[adjacent_cells[next_direction]
[0]][adjacent_cells[next_direction][1]]
chosen_direction = random.choice(possible_directions)
next_position = current_position.get_adjacent_position(
chosen_direction)
next_cell = self._cells[next_position.i][next_position.j]
if next_direction is above:
if chosen_direction is MazeDirections.ABOVE:
current_cell.configure_walls(top=False)
next_cell.configure_walls(bottom=False)
elif next_direction is below:
elif chosen_direction is MazeDirections.BELOW:
current_cell.configure_walls(bottom=False)
next_cell.configure_walls(top=False)
elif next_direction is left:
elif chosen_direction is MazeDirections.LEFT:
current_cell.configure_walls(left=False)
next_cell.configure_walls(right=False)
elif next_direction is right:
elif chosen_direction is MazeDirections.RIGHT:
current_cell.configure_walls(right=False)
next_cell.configure_walls(left=False)
current_cell.draw()
if self._window:
self._draw_cell(y=y, x=x)
self._draw_cell(i=i, j=j)
self._break_walls_r(
y=adjacent_cells[next_direction][0],
x=adjacent_cells[next_direction][1],
)
self._break_walls_r(i=next_position.i, j=next_position.j)
def _draw_cell(self, y: int, x: int) -> None:
def _draw_cell(self, i: int, j: int) -> None:
"""
_draw_cell draws the cells in an animated way.
"""
self._cells[y][x].draw()
self._cells[i][j].draw()
self._window.redraw()
sleep(0.05)

View file

@ -1,6 +1,6 @@
import unittest
from cell import Cell, CellWallLabel
from maze import Maze
import maze
import errors
@ -25,7 +25,7 @@ class Tests(unittest.TestCase):
]
for case in cases:
maze = Maze(
m = maze.Maze(
0,
0,
case["number_of_cell_rows"],
@ -37,11 +37,11 @@ class Tests(unittest.TestCase):
True,
)
self.assertEqual(
len(maze._cells),
len(m._cells),
case["number_of_cell_rows"],
)
self.assertEqual(
len(maze._cells[0]),
len(m._cells[0]),
case["number_of_cells_per_row"],
)
@ -52,7 +52,7 @@ class Tests(unittest.TestCase):
"""
number_of_cell_rows = 5
number_of_cells_per_row = 20
maze = Maze(
m = maze.Maze(
0,
0,
number_of_cell_rows,
@ -63,9 +63,9 @@ class Tests(unittest.TestCase):
None,
True,
)
self.assertFalse(maze._cells[0][0].wall_exists(CellWallLabel.TOP))
self.assertFalse(m._cells[0][0].wall_exists(CellWallLabel.TOP))
self.assertFalse(
maze._cells[number_of_cell_rows - 1]
m._cells[number_of_cell_rows - 1]
[number_of_cells_per_row - 1].wall_exists(CellWallLabel.BOTTOM)
)
@ -90,7 +90,7 @@ class Tests(unittest.TestCase):
def test_cell_too_small_exception(self):
"""
test_cell_too_small_exception tests the excpetion for when an attempt
test_cell_too_small_exception tests the exception for when an attempt
is made to create a Cell that's too small.
"""
cases = [
@ -107,6 +107,72 @@ class Tests(unittest.TestCase):
y2=case["y2"]
)
def test_maze_position_equality(self):
cases = [
{
"m1": maze.MazePosition(i=1, j=3, max_i=10, max_j=100),
"m2": maze.MazePosition(i=1, j=3, max_i=10, max_j=100),
"expected": True,
},
{
"m1": maze.MazePosition(i=1, j=3, max_i=10, max_j=100),
"m2": maze.MazePosition(i=100, j=30, max_i=200, max_j=100),
"expected": False,
}
]
for case in cases:
result = case["m1"] == case["m2"]
self.assertEqual(result, case["expected"])
def test_maze_position_adjacent_positition(self):
cases = [
{
"position": maze.MazePosition(i=3, j=4, max_i=10, max_j=10),
"direction": maze.MazeDirections.ABOVE,
"expected": maze.MazePosition(i=2, j=4, max_i=10, max_j=10),
},
{
"position": maze.MazePosition(i=9, j=4, max_i=10, max_j=10),
"direction": maze.MazeDirections.BELOW,
"expected": maze.MazePosition(i=10, j=4, max_i=10, max_j=10),
},
{
"position": maze.MazePosition(i=1, j=1, max_i=10, max_j=10),
"direction": maze.MazeDirections.LEFT,
"expected": maze.MazePosition(i=1, j=0, max_i=10, max_j=10),
},
{
"position": maze.MazePosition(i=3, j=9, max_i=10, max_j=10),
"direction": maze.MazeDirections.RIGHT,
"expected": maze.MazePosition(i=3, j=10, max_i=10, max_j=10),
},
{
"position": maze.MazePosition(i=0, j=4, max_i=10, max_j=10),
"direction": maze.MazeDirections.ABOVE,
"expected": None,
},
{
"position": maze.MazePosition(i=10, j=4, max_i=10, max_j=10),
"direction": maze.MazeDirections.BELOW,
"expected": None,
},
{
"position": maze.MazePosition(i=1, j=0, max_i=10, max_j=10),
"direction": maze.MazeDirections.LEFT,
"expected": None,
},
{
"position": maze.MazePosition(i=3, j=10, max_i=10, max_j=10),
"direction": maze.MazeDirections.RIGHT,
"expected": None,
},
]
for case in cases:
result = case["position"].get_adjacent_position(case["direction"])
self.assertEqual(result, case["expected"])
if __name__ == "__main__":
unittest.main()