# Principled Programming / Tim Teitelbaum / Chapter 15. from mrp import MRP import random class RunMaze: """ Rat running. # Methods. main(cls) -> None # See also. Chapter 15 of Principled Programming """ @classmethod def main(cls) -> None: """Run a maze given as input, if possible.""" # Input a maze of arbitrary size, or output “malformed input” # and stop if the input is improper. Input format: TBD. RunMaze._input() # Compute a direct path through the maze, if one exists. RunMaze._solve() # Output the direct path found, or “unreachable” if there is # none. Output format: TBD. RunMaze._output() # Stop execution if path found is not a solution. assert MRP.is_solution(), "internal program error" @classmethod def _input(cls) -> None: """Input a maze of arbitrary size, or output “malformed input” and stop if the input is improper.""" MRP.input() @classmethod def _solve(cls) -> None: """Compute a direct path through the maze, if one exists.""" while not(MRP.is_at_cheese()) and not(MRP.is_about_to_repeat()): if MRP.is_facing_wall(): MRP.turn_clockwise() elif MRP.is_facing_unvisited(): MRP.step_forward() MRP.turn_counter_clockwise() else: RunMaze._retract() @classmethod def _retract(cls) -> None: """Unwind abortive exploration.""" MRP.record_neighbor_and_direction() while not(MRP.is_at_neighbor()): MRP.face_previous() MRP.step_backward() MRP.restore_direction() MRP.turn_counter_clockwise() @classmethod def _output(cls) -> None: """Output the direct path found, or “unreachable” if there is none.""" if not(MRP.is_at_cheese()): print ("Unreachable"); else: MRP.print_maze() @classmethod def exhaustive_test(cls) -> None: """Generate/solve all mazes of sizes up through 3, and validate paths found.""" for N in range(1, 5): for w in range(0, 2 ** (2 * N *(N - 1))): MRP.generate_input(N, w) RunMaze._solve() assert MRP.is_solution(), "internal program error" if (w > 0) and (w%100000 == 0): print(w) # Heartbeat. print("passed for size:", N) print("passed") @classmethod def fuzz_test(cls, f: int, n: int) -> None: """Fuzz F mazes of size N.""" for k in range(0, f): RunMaze.random_test(n) @classmethod def random_test(cls, n: int) -> None: """Create a random maze of size N.""" # Let w be random walls for the n-by-n maze. w = random.randint(0, 2 ** (2 * n * (n - 1))) # Generate maze of size n with walls w, solve, and validate (or abort). print("Size:", n, "Walls:", w) # (Comment out for serious test.) MRP.generate_input(n, w) # Create n-by-n maze with walls w. RunMaze._solve() # Attempt a solution. assert MRP.is_solution() # Validate solution (or abort). MRP.print_maze() # (Comment out for serious test.) # Uncomment to run on builtin example. RunMaze.main() # Uncomment to run on all mazes of size <= 4. #RunMaze.exhaustive_test() # Uncomment to run on 25 random mazes of size 5. #RunMaze.fuzz_test(25, 5)