A document from MCS 275 Spring 2024, instructor Emily Dumas. You can also get the notebook file.

MCS 275 Spring 2024 Worksheet 5 Solutions

  • Course instructor: Emily Dumas
  • Solutions prepared by: Karoline Dubin, Jennifer Vaccaro, Johnny Joyce, Kylash Viswanathan

Topics

The main topic of this worksheet is recursion with backtracking, building on the maze-solving example from lecture.

Resources

These things might be helpful while working on the problems. Remember that for worksheets, we don't strictly limit what resources you can consult, so these are only suggestions.

Reminder about images

The maze module supports saving a maze as an image file, of type SVG or PNG. However, PNG images require the module Pillow to be installed. The following command should install it:

python3 -m pip install pillow

2. Solution history

Add a new function depth_first_maze_solution_history() to solvemaze.py that works similarly to depth_first_maze_solution() but which returns a list of every path considered (rather than just a single path that is a solution). This means the returned value will be a list of lists of Point2 objects.

Thus the last item in the returned list will be a solution, if a solution exists.

Then, write a script that uses depth_first_maze_solution_history to do the following:

  • Take a command line argument, an odd integer n
  • Generate a random n by n maze
  • Solve the maze, keeping track of the history of paths considered
  • Save a sequence of output image files with names like dfs001.png, dfs002.png, etc., that highlight all paths considered by the solver

Run this on a maze of moderate size (e.g. 9x9) and flip through the resulting images in your platform's image viewer to make a crude animation of the recursive process.

Solution

The script below contains the requested function and the demonstration code as well.

In [ ]:
import maze

# Adapted from the 2023 worksheet
def depth_first_maze_solution_history(M,path_list=None,verbose=False):
    """
    Functions similarly to `solvemaze`, but returns a list of all paths
    considered throughout solving process.
    """
    if path_list == None:
        # no path was specified, initialize it with [M.start]
        path_list = [[M.start]] # Use a list of lists for path_list - one list for each path.
    else:
        print("Called with path={}".format(path_list[-1]))
    path = path_list[-1]

    if verbose:
        print("Considering:",path)

    if path[-1] == M.goal:
        # path ends with goal, meaning it's a solution
        return path_list, True # Set the "solved" variable to True

    path = path_list[-1]
    possible_next_locations = M.free_neighbors(path[-1])
    for x in possible_next_locations:
        if x in path:
            # skip x
            continue # do not execute the rest of the loop body
                     # immediately begin the next iteration.
        # x should be considered
        new_path = path + [x]
        # Ask for a solution that continues from new_path
        path_list.append(new_path)
        soln, solved = depth_first_maze_solution_history(M,path_list)
        if solved: 
            return soln, True
        
    # What now? If we end up here, it means no next step leads to a solution
    # Hence `path` leads to only dead ends
    # We therefore BACKTRACK
    if verbose:
        print("GIVING UP ON:",path)
    # Still need to return path_list so we can visualize it later
    return path_list, False # Keep the "solved" variable as False.

if __name__ == "__main__":
    # Run as a script, so make the images of a maze's solution
    # as requested in the problem.
    import sys

    if len(sys.argv) > 1:
        n = int(sys.argv[1])
        if n % 2 != 1: 
            print("Integer n must be odd.")
            exit()
    else:
        print("No command line argument detected. Defaulting to 11.")
        n = 11

    # Make a random maze
    M = maze.PrimRandomMaze(n,n)
    # Find solution and history
    path_list, _ = depth_first_maze_solution_history(M) # `_` represents a boolean variable we don't need anymore
    # Save image files (could also do SVG to avoid Pillow dependency)
    for i,p in enumerate(path_list):
        M.save_png("dfs{:03d}.png".format(i),scale=30,highlight=p)
    print("Saved {} image files ('dfs000.png' to 'dfs{:03d}.png')".format(
        len(path_list),
        len(path_list)-1,
    ))

Revision history

  • 2024-02-09 Initial publication