diff --git a/maze.py b/maze.py index 7f64d45..4bfc7cc 100644 --- a/maze.py +++ b/maze.py @@ -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) diff --git a/tests.py b/tests.py index 7969bd5..5710a2f 100644 --- a/tests.py +++ b/tests.py @@ -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()