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:
parent
a8ef152847
commit
32696f311d
2 changed files with 178 additions and 45 deletions
141
maze.py
141
maze.py
|
@ -1,10 +1,79 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import random
|
import random
|
||||||
|
from enum import Enum
|
||||||
from graphics import Window
|
from graphics import Window
|
||||||
from cell import Cell
|
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:
|
class Maze:
|
||||||
"""
|
"""
|
||||||
Maze represents a two-dimensional grid of Cells.
|
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
|
draws all the cells on the maze with a short pause between each cell
|
||||||
for animation purposes.
|
for animation purposes.
|
||||||
"""
|
"""
|
||||||
for y in range(self._num_cell_rows):
|
for i in range(self._num_cell_rows):
|
||||||
for x in range(self._num_cells_per_row):
|
for j in range(self._num_cells_per_row):
|
||||||
self._draw_cell(y=y, x=x)
|
self._draw_cell(i=i, j=j)
|
||||||
|
|
||||||
def _open_entrance_and_exit(self) -> None:
|
def _open_entrance_and_exit(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -92,72 +161,70 @@ class Maze:
|
||||||
if self._window:
|
if self._window:
|
||||||
self._draw_cell(0, 0)
|
self._draw_cell(0, 0)
|
||||||
self._draw_cell(
|
self._draw_cell(
|
||||||
y=self._num_cell_rows-1,
|
i=self._num_cell_rows-1,
|
||||||
x=self._num_cells_per_row-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
|
_break_walls_r generates a random maze by traversing through the
|
||||||
cells and randomly knocking down the walls to create the maze's paths.
|
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
|
current_cell.visited_by_maze_generator = True
|
||||||
above, below, left, right = "above", "below", "left", "right"
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
adjacent_cells = {
|
possible_directions: List[MazeDirections] = []
|
||||||
above: (y-1, x),
|
|
||||||
below: (y+1, x),
|
|
||||||
left: (y, x-1),
|
|
||||||
right: (y, x+1),
|
|
||||||
}
|
|
||||||
to_visit: List[str] = []
|
|
||||||
|
|
||||||
for k, value in adjacent_cells.items():
|
for direction in MazeDirections:
|
||||||
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):
|
adjacent_position = current_position.get_adjacent_position(
|
||||||
|
direction)
|
||||||
|
if adjacent_position is None:
|
||||||
continue
|
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
|
continue
|
||||||
|
possible_directions.append(direction)
|
||||||
|
|
||||||
to_visit.append(k)
|
if len(possible_directions) == 0:
|
||||||
|
|
||||||
if len(to_visit) == 0:
|
|
||||||
if self._window:
|
if self._window:
|
||||||
self._draw_cell(y=y, x=x)
|
self._draw_cell(i=i, j=j)
|
||||||
break
|
break
|
||||||
|
|
||||||
next_direction = random.choice(to_visit)
|
chosen_direction = random.choice(possible_directions)
|
||||||
next_cell = self._cells[adjacent_cells[next_direction]
|
next_position = current_position.get_adjacent_position(
|
||||||
[0]][adjacent_cells[next_direction][1]]
|
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)
|
current_cell.configure_walls(top=False)
|
||||||
next_cell.configure_walls(bottom=False)
|
next_cell.configure_walls(bottom=False)
|
||||||
elif next_direction is below:
|
elif chosen_direction is MazeDirections.BELOW:
|
||||||
current_cell.configure_walls(bottom=False)
|
current_cell.configure_walls(bottom=False)
|
||||||
next_cell.configure_walls(top=False)
|
next_cell.configure_walls(top=False)
|
||||||
elif next_direction is left:
|
elif chosen_direction is MazeDirections.LEFT:
|
||||||
current_cell.configure_walls(left=False)
|
current_cell.configure_walls(left=False)
|
||||||
next_cell.configure_walls(right=False)
|
next_cell.configure_walls(right=False)
|
||||||
elif next_direction is right:
|
elif chosen_direction is MazeDirections.RIGHT:
|
||||||
current_cell.configure_walls(right=False)
|
current_cell.configure_walls(right=False)
|
||||||
next_cell.configure_walls(left=False)
|
next_cell.configure_walls(left=False)
|
||||||
|
|
||||||
current_cell.draw()
|
|
||||||
if self._window:
|
if self._window:
|
||||||
self._draw_cell(y=y, x=x)
|
self._draw_cell(i=i, j=j)
|
||||||
|
|
||||||
self._break_walls_r(
|
self._break_walls_r(i=next_position.i, j=next_position.j)
|
||||||
y=adjacent_cells[next_direction][0],
|
|
||||||
x=adjacent_cells[next_direction][1],
|
|
||||||
)
|
|
||||||
|
|
||||||
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.
|
_draw_cell draws the cells in an animated way.
|
||||||
"""
|
"""
|
||||||
self._cells[y][x].draw()
|
self._cells[i][j].draw()
|
||||||
self._window.redraw()
|
self._window.redraw()
|
||||||
sleep(0.05)
|
sleep(0.05)
|
||||||
|
|
82
tests.py
82
tests.py
|
@ -1,6 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
from cell import Cell, CellWallLabel
|
from cell import Cell, CellWallLabel
|
||||||
from maze import Maze
|
import maze
|
||||||
import errors
|
import errors
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class Tests(unittest.TestCase):
|
||||||
]
|
]
|
||||||
|
|
||||||
for case in cases:
|
for case in cases:
|
||||||
maze = Maze(
|
m = maze.Maze(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
case["number_of_cell_rows"],
|
case["number_of_cell_rows"],
|
||||||
|
@ -37,11 +37,11 @@ class Tests(unittest.TestCase):
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(maze._cells),
|
len(m._cells),
|
||||||
case["number_of_cell_rows"],
|
case["number_of_cell_rows"],
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(maze._cells[0]),
|
len(m._cells[0]),
|
||||||
case["number_of_cells_per_row"],
|
case["number_of_cells_per_row"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class Tests(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
number_of_cell_rows = 5
|
number_of_cell_rows = 5
|
||||||
number_of_cells_per_row = 20
|
number_of_cells_per_row = 20
|
||||||
maze = Maze(
|
m = maze.Maze(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
number_of_cell_rows,
|
number_of_cell_rows,
|
||||||
|
@ -63,9 +63,9 @@ class Tests(unittest.TestCase):
|
||||||
None,
|
None,
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
self.assertFalse(maze._cells[0][0].wall_exists(CellWallLabel.TOP))
|
self.assertFalse(m._cells[0][0].wall_exists(CellWallLabel.TOP))
|
||||||
self.assertFalse(
|
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)
|
[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):
|
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.
|
is made to create a Cell that's too small.
|
||||||
"""
|
"""
|
||||||
cases = [
|
cases = [
|
||||||
|
@ -107,6 +107,72 @@ class Tests(unittest.TestCase):
|
||||||
y2=case["y2"]
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in a new issue