# Principled Programming / Tim Teitelbaum / Chapter 16. class MRP: """ # Maze, Rat, and Path (MRP) Representations. # Methods. # Motion turn_clockwise(cls) -> None turn_counter_clockwise(cls) -> None step_forward(cls) -> None step_backward(cls) -> None face_previous(cls) -> None # Predicates is_facing_wall(cls) -> bool is_facing_unvisited(cls) -> bool is_about_to_repeat(cls) -> bool is_at_cheese(cls) -> bool # I/O. input(cls) -> None print_maze(cls) -> None # Tossed arrow abstraction. record_neighbor_and_direction(cls) -> None is_at_neighbor(cls) -> bool restore_direction(cls) -> None # Self-Checking and Validity Checking. is_solution(cls) -> bool generate_input(cls, n int, w: int) -> None # See also. Chapter 15 of Principled Programming Chapter 17 of Principled Programming, which uses elements of MRP for an alternative solution. """ # Maze. Cells of an N-by-N maze are represented by elements of a # (2*N+1)-by(2*N+1) array M. Maze cell ⟨r,c⟩ is represented by array # element M[2*r+1][2*c+1]. The possible walls ⟨top, right, bottom, # left⟩ of the maze cell corresponding to ⟨r,c⟩ are represented by # WALL or NO_WALL in ⟨M[r-1][c], M[r][c+1], M[r+1][c], M[r][c-1]⟩. # The remaining elements of M are unused. lo is 1, and hi is 2*N-1. _N: int = 0 # _N is size of maze. _M: list[list[int]] = [] # _M is _N-by-_N maze, walls, and path. _WALL: int = -1 # _WALL encodes presence of a wall. _NO_WALL: int = 0 # _NO_WALL encodes absence of a wall. _lo: int = 1 # _lo is left and top maze indices. _hi: int = 2 * _N - 1 # _hi is right and bottom maze indices. # Rat. The rat is located in cell M[r][c] facing direction d, where # d=⟨0,1,2,3⟩ represents the orientation ⟨up,right,down,left⟩, # respectively. _r: int = _lo _c: int = _lo _d: int = 0 # Path. When the rat has traveled to cell ⟨r,c⟩ via a given path # through cells of the maze, the elements of M that correspond to # those cells will be 1, 2, 3, etc., and all other elements of M # that correspond to cells of the maze will be UNVISITED. The # number of the last step in the path is move. _UNVISITED: int = 0 _move: int = _lo # Unit vectors in direction d # d = 0, 1, 2, 3 # up, right, down, left _deltaR: list[int] = [ -1, 0, 1, 0 ] _deltaC: list[int] = [ 0, 1, 0, -1 ] # Recorded state. _neighbor_number: int ## Visit number of cell into which the arrow was tossed. _neighbor_direction: int # Direction when the arrow was tossed. # Public interface. # Motion. @classmethod def turn_clockwise(cls) -> None: MRP._d = (MRP._d + 1) % 4 @classmethod def turn_counter_clockwise(cls) -> None: MRP._d = (MRP._d + 3) % 4 @classmethod def step_forward(cls) -> None: MRP._r += 2 * MRP._deltaR[MRP._d]; MRP._c += 2 * MRP._deltaC[MRP._d] MRP._move += 1; MRP._M[MRP._r][MRP._c] = MRP._move @classmethod def step_backward(cls) -> None: MRP._M[MRP._r][MRP._c] = MRP._UNVISITED MRP._move -= 1 MRP._r += 2 * MRP._deltaR[MRP._d] MRP._c += 2 * MRP._deltaC[MRP._d] @classmethod def face_previous(cls) -> None: MRP._d = 0 while MRP.is_facing_wall() or (MRP._M[MRP._r][MRP._c] - 1 != MRP._M[MRP._r + 2 * MRP._deltaR[MRP._d] ][MRP._c + 2 * MRP._deltaC[MRP._d]] ): MRP._d += 1 # Predicates @classmethod def is_facing_wall(cls) -> bool: return MRP._M[MRP._r + MRP._deltaR[MRP._d] ][MRP._c + MRP._deltaC[MRP._d]] == MRP._WALL @classmethod def is_at_cheese(cls) -> bool: return (MRP._r == MRP._hi) and (MRP._c == MRP._hi) @classmethod def is_about_to_repeat(cls) -> bool: return (MRP._r == MRP._lo) and (MRP._c == MRP._lo) and (MRP._d == 3) @classmethod def is_facing_unvisited(cls) -> bool: return MRP._M[MRP._r + 2 * MRP._deltaR[MRP._d] ][MRP._c + 2 * MRP._deltaC[MRP._d]] == MRP._UNVISITED # I/O. @classmethod def input(cls) -> None: """Input N-by-N maze.""" # Input file split into lines. # file = "1\nxxx\nx x\nxxx\nxxx" # 1x1. # file = "2\nxxxxx\nx x x\nx x\nx x\nxxxxx" # 2x2, with obstacle. # file = "3\nxxxxxxx\nx x\nx x x\nx xx x\nx xxxxx\nx x\nxxxxxxx" # 3x3, with room. file = "5\n###########\n# # #\n# ## # # #\n# # # #\n# # ## ###\n# # # #\n# # # ### #\n# # # # #\n# ### #####\n# # #\n###########\n" # 5x5, for Chapter 19. lines = file.split("\n") lines = file.split("\n") # Maze. As per representation invariant. MRP._N = int(lines[0]); del lines[0] MRP._lo = 1; MRP._hi = 2 * MRP._N - 1 MRP._M = [[0 for _ in range(2 * MRP._N + 1)] for _ in range(2 * MRP._N + 1)] for r in range(MRP._lo - 1, MRP._hi + 2): line = lines[0]; del lines[0] for c in range(MRP._lo - 1, MRP._hi + 2): if (r % 2 == 1) and (c % 2 == 1): MRP._M[r][c] = MRP._UNVISITED elif line[c:c+1] == " ": MRP._M[r][c] = MRP._NO_WALL else: MRP._M[r][c] = MRP._WALL # Rat. Place rat in upper-left cell facing up. MRP._r = MRP._lo; MRP._c = MRP._lo; MRP._d = 0 # Path. Establish the rat in the upper-left cell. MRP._move = 1; MRP._M[MRP._r][MRP._c] = MRP._move @classmethod def print_maze(cls) -> None: """Output N-by-N maze, with walls and path.""" for r in range(MRP._lo - 1, MRP._hi + 2): for c in range(MRP._lo - 1, MRP._hi + 2): if MRP._M[r][c] == MRP._WALL: s = "#" elif ((MRP._M[r][c] == MRP._NO_WALL) or (MRP._M[r][c] == MRP._UNVISITED)): s = " " else: s = str(MRP._M[r][c]) + "" print((s + " ")[0:3], end='') print() # Tossed arrow abstraction. @classmethod def record_neighbor_and_direction(cls) -> None: """Toss an arrow into the neighboring cell in the direction faced.""" MRP._neighbor_number = MRP._M[MRP._r + 2 * MRP._deltaR[MRP._d] ][MRP._c + 2 * MRP._deltaC[MRP._d]] MRP._neighbor_direction = MRP._d @classmethod def is_at_neighbor(cls) -> bool: """Detect being in the same cell as the arrow.""" return MRP._M[MRP._r][MRP._c] == MRP._neighbor_number @classmethod def restore_direction(cls) -> None: """'Align direction with the arrow'""" MRP._d = MRP._neighbor_direction # Self-Checking and Validity Checking. @classmethod def is_solution(cls) -> bool: """Return False iff rat reached lower-right cell via an invalid path.""" return MRP._is_valid_path(MRP._hi, MRP._hi) @classmethod def generate_input(cls, n: int, w: int) -> None: """Create an n-by-n maze with walls given by the bits of w.""" # Maze. MRP._N = n; lo = MRP._lo = 1; hi = MRP._hi = 2 * n - 1 MRP._M = [[0 for _ in range(2 * n + 1)] for _ in range(2 * n + 1)] # Set boundary walls. for i in range(0, hi + 2): MRP._M[lo - 1][i] = MRP._M[hi + 1][i] = MRP._WALL MRP._M[i][lo - 1] = MRP._M[i][hi + 1] = MRP._WALL # Set 2*n*(n-1) interior walls to the corresponding bits of w. for r in range(lo, hi + 1): for c in range(lo, hi + 1): if (r % 2 == 0 and c % 2 == 1) or (r % 2 == 1 and c % 2 == 0): if w % 2 == 1: MRP._M[r][c] = MRP._WALL else: MRP._M[r][c] = MRP._NO_WALL w = w // 2 # Rat. MRP._r = lo; MRP._c = lo; MRP._d = 0 # Path. MRP._move = 1; MRP._M[lo][lo] = MRP._move @classmethod def _is_valid_path(cls, r: int, c: int) -> bool: """Return False iff rat reached cell ⟨r,c⟩ via an invalid path.""" if MRP._M[r][c] == MRP._UNVISITED: return True # No claim if UNVISITED. else: while not((r == MRP._lo) and (c == MRP._lo)): # Go to any valid predecessor; return False if there is none. d = 0 while (d < 4) and (( MRP._M[r + MRP._deltaR[d]][c + MRP._deltaC[d]] == MRP._WALL ) or (MRP._M[r + 2 * MRP._deltaR[d]][c + 2 * MRP._deltaC[d]] != (MRP._M[r][c] - 1) )): d += 1 if d == 4: return False r += 2 * MRP._deltaR[d]; c += 2 * MRP._deltaC[d] return True # Reached upper-left cell.