# Assignment_4.py # # Maze Game Program # # This Python program implements a maze game where the player navigates a # turtle through a maze. The maze map is loaded from a data file and consists # of an entrance, an exit, walls and roads. This program (Assignment_4.py) # contains functions to read maze data from a file, compute maze dimensions, # set up the game canvas and draw the maze. # In Assignment 5, the player will controls the turtle using arrow keys to # move the turtle through the maze, from its entrance to the its exit. # # Authors: Sitong Zhai and Anne Lavergne # Date: March 8, 2024 import turtle import random def headUp(manualTurtle): """ Set turtle heading up. ***Do not modify the content of this function!*** """ manualTurtle.setheading(90) return def headDown(manualTurtle): """ Set turtle heading down. ***Do not modify the content of this function!*** """ manualTurtle.setheading(270) return def headLeft(manualTurtle): """ Set turtle heading left. ***Do not modify the content of this function!*** """ manualTurtle.setheading(180) return def headRight(manualTurtle): """ Set turtle heading right. ***Do not modify the content of this function!*** """ manualTurtle.setheading(0) return def drawSquare(mazeTurtle, column, row, colour, mazeWidth, mazeHeight, cellSize = 20): """ Draws a square at the specified column and row of the maze using the given colour. This function is used to visually represent different components of the maze (walls, entrance, exit, road). ***Do not modify the content of this function!*** """ # Calculate the top-left corner of the square to be drawn. squareTopX = column * cellSize - mazeWidth * cellSize / 2 squareTopY = mazeHeight * cellSize / 2 - row * cellSize # Position the turtle and start drawing the square. mazeTurtle.penup() mazeTurtle.goto(squareTopX, squareTopY) mazeTurtle.color(colour) mazeTurtle.pendown() mazeTurtle.begin_fill() for i in range(4): mazeTurtle.forward(cellSize) mazeTurtle.right(90) mazeTurtle.end_fill() mazeTurtle.ht() return def setupMazeWindow(mazeWidth, mazeHeight, cellSize = 20): """ Sets up the game window dimensions based on the maze size. ***Do not modify the content of this function!*** """ # Calculate the dimensions of the game canvas gameCanvasWidth = mazeWidth * cellSize + 50 gameCanvasHeight = mazeHeight * cellSize + 100 # Set up the turtle screen turtle.setup(width=gameCanvasWidth, height=gameCanvasHeight) return def readDataFile(mazeFilePath, mazeMatrix, symbolDict): """ This function reads the data from the data file. First, it reads the symbols representing the maze's walls, entrance, exit and roads (paths) from the data file into the dictionary "symbolDict". Then it reads the maze from the data file into "mazeMatrix". Parameters: - mazeFilePath (string): The filename (file path) to the data file. - mazeMatrix (list of lists): The 2D matrix representing the maze layout, where each element indicates a specific maze component (wall, entrance, exit or road). - symbolDict (dictionary): A dictionary containing symbols used in the maze, including keys such as 'Wall', 'Entrance', 'Exit', and 'Road', and their corresponding symbols (the key's value). Returns: - mazeMatrix (list of lists): Updated maze matrix. - symbolDict (dictionary): Updated symbol dictionary containing the loaded symbols (1 character) for walls, entrance, exit, and road. Implementation Details - What you need to do: - Open the specified data file in read mode. - Read the symbols for walls, entrance, exit, and roads from the file and update the symbolDict. - Read the maze line by line (a line is a row, i.e., a list) from the file and append each row to "mazeMatrix". - Close the data file after reading. """ # Your code starts here: # Use as many lines as necessary to write your code # Your code ends here. print('Reading Maze File Successfully!') return mazeMatrix, symbolDict def drawMaze(mazeMatrix, mazeWidth, mazeHeight, symbolDict): """ Draws the entire maze based on the content of "mazeMatrix". More specifically, this function iterates over each row, and for each row, it iterates over each cell of the row. For each cell, at (column,row), it draws a square on the canvas using a particular colour associated with each component of the maze: - a colour to represent the walls, - a colour to represent the entrance, - a colour to represent the exit, and - a colour to represent the roads. Parameters: - mazeMatrix (list of lists): The 2D matrix representing the maze layout, where each element indicates a specific maze component (wall, entrance, exit or road). - mazeWidth (int): The width of the maze, representing the number of columns. - mazeHeight (int): The height of the maze, representing the number of rows. - symbolDict (dictionary): A dictionary containing symbols used in the maze, including keys such as 'Wall', 'Entrance', 'Exit', and 'Road', and their corresponding symbols (the key's value). Returns: - None Implementation Details - What you need to do: - Iterate over each row and column of "mazeMatrix". - Retrieve the content of each cell and determine its corresponding component: is this cell a wall, an entrance, an exit or part of the road (i.e., path)? - Figure out which colour you wish to associate with this component. - Draw the square representing this cell using the drawSquare function and the colour you have associated with the component, i.e., the content, of this current cell in "mazeMatrix". """ # Disable animation for faster rendering. # Use mazeTurtle as the turtle when invoking function drawSquare to draw # each square on the map. turtle.tracer(0) mazeTurtle = turtle.Turtle() mazeTurtle.speed(0) # Your code starts here: # Use as many lines as necessary to write your code # Your code ends here. # Re-enable animation after drawing. turtle.tracer(1) return def computeMazeWidthAndHeight(mazeMatrix): """ This function computes the width and height of the maze based on the dimensions of the maze matrix. It determines the number of columns as the length of the first row of the matrix and the number of rows as the total number of rows in the matrix. Parameter: - mazeMatrix (list of lists): The 2D matrix representing the maze layout, where each element indicates a specific maze component (wall, entrance, exit or road). Returns: - mazeWidth (int): The width of the maze, representing the number of columns. - mazeHeight (int): The height of the maze, representing the number of rows. Implementation Details - What you need to do: - You can assume the size of "mazeMatrix" is "mazeWidth" * "mazeHeight", where each row of "mazeMatrix" has the same number of columns. - Assign the width of the maze, i.e., the length of the first row of "mazeMatrix", to "mazeWidth". Remember: "mazeMatrix" is a list of lists. - Assign the height of the maze, i.e., the total number of rows in "mazeMatrix", to "mazeHeight". """ # Initialize local variables mazeWidth = 0 mazeHeight = 0 # Your code starts here: # Use as many lines as necessary to write your code # Your code ends here. return mazeWidth, mazeHeight def findSymbolPosition(mazeMatrix, mazeWidth, mazeHeight, symbolDict, symbolPosition): """ This function finds the positions of the symbols representing the 'Entrance' and the 'Exit' in "mazeMarix". It does so by iterating over each row and by iterating over each cell of the row, looking for the symbol used to represent the 'Entrance' and the 'Exit' in "mazeMarix". Once found, it computes the position of the symbol, i.e., (column,row), then updates the dictionary "symbolPosition" with these two positions. Parameters: - mazeMatrix (list of lists): The 2D matrix representing the maze layout, where each element indicates a specific maze component (wall, entrance, exit or road). - mazeWidth (int): The width of the maze, representing the number of columns. - mazeHeight (int): The height of the maze, representing the number of rows. - symbolDict (dictionary): A dictionary containing symbols used in the maze, including keys such as 'Wall', 'Entrance', 'Exit', and 'Road', and their corresponding symbols (the key's value). - symbolPosition (dictionary): A dictionary containing the positions of the symbols representing the 'Entrance' and the 'Exit' in "mazeMatrix", expressed as (column,row). Returns: - symbolPosition (dictionary): Updated dictionary containing the positions of the symbols representing the 'Entrance' and the 'Exit' in "mazeMatrix", expressed as (column,row). Implementation Details - What you need to do:: - You can assume there is only one Entracnce and one Exit in the mazeMatrix - Iterate over each row and column of the maze matrix. - Retrieve the content of each cell. - If the content matches the symbol for the entrance or exit as specified in symbolDict, update the corresponding position in symbolPosition with the current row and column. - Return the updated symbolPosition dictionary. """ # Your code starts here: # Use as many lines as necessary to write your code # Your code ends here. return symbolPosition def drawTurtle(manualTurtle, symbolPosition, cellSize = 20): """ This function initializes and configures the turtle that will navigate the maze. It sets the turtle's size, speed, shape, colour, and it sets its position to the entrance of the maze. Additionally, it sets up the turtle's initial heading based on the relative positions of the entrance and the exit in the maze. Finally, it draws the turtle at the 'Entrance' cell, ready for Assignment 5. Parameters: - manualTurtle (Turtle object): The turtle that will navigate the maze. - symbolPosition (dictionary): A dictionary containing the positions of the symbols representing the 'Entrance' and the 'Exit' in "mazeMatrix", expressed as (column,row). - cellSize (int): The size of each cell at (column,row) in the maze. Returns: - turtle_angle (int): The angle at which the turtle is initially facing. Implementation Details - What you need to do: - Retrieve the 'Entrance' and the 'Exit' positions from "symbolPosition" dictionary. - Calculate the initial canvas coordinate (turtle_start_x,turtle_start_y) of the turtle based on the entrance coordinate. In implementating the above step, you may find these equations, from drawSquare(...), useful: # Calculate the top-left corner of the square to be drawn. squareTopX = column * cellSize - mazeWidth * cellSize / 2 squareTopY = mazeHeight * cellSize / 2 - row * cellSize Note: You will have to adapt them to the current situation. - Calculate heading (turtle_heading_angle) of the turtle based on the location of the 'Entrance' and the 'Exit' in the maze. Note: Remember, you want the turtle to be facing toward the exit before you start moving your turtle about the maze (in Assignment 5). """ # Set the turtle's size, speed, shape, colour manualTurtle.pensize(cellSize / 4) manualTurtle.speed(0) manualTurtle.shape('turtle') manualTurtle.color('yellow') manualTurtle.hideturtle() manualTurtle.clear() manualTurtle.penup() # Initialize local variables turtle_heading_angle = 0 turtle_start_x = 0 turtle_start_y = 0 # Your code starts here: # Use as many lines as necessary to write your code # Your code ends here. # Ready the turtle for maze navigation, i.e., Assignment 5. manualTurtle.setheading(turtle_heading_angle) manualTurtle.goto(turtle_start_x, turtle_start_y) manualTurtle.pendown() manualTurtle.showturtle() manualTurtle.width(3) # Bind arrow keys to movement functions. manualTurtle.getscreen().listen() manualTurtle.getscreen().onkeyrelease(headUp(manualTurtle), 'Up') manualTurtle.getscreen().onkeyrelease(headDown(manualTurtle), 'Down') manualTurtle.getscreen().onkeyrelease(headLeft(manualTurtle), 'Left') manualTurtle.getscreen().onkeyrelease(headRight(manualTurtle), 'Right') return turtle_heading_angle # ***Main part of the program ## Initializing variables # 2-dimensional matrix "mazeMatrix" # After reading the data file, "mazeMatrix" must be a 2-dimensional matrix, # i.e., a list of lists. # Here is an example of what "mazeMatrix" may contain after calling # readDataFile(...) # mazeMatrix = [['W', 'W', 'W'], # ['S', '0', 'W'], # ['W', '0', 'E'], # ['W', 'W', 'W']], # As you can see, mazeMatrix[row][column] = symbol. # For example, in the above matrix, mazeMatrix[1][0] = 'S' # "row 1" means the second element of the outer list, i.e., ['S', '0', 'W'] # and "column 0" means the first element of this inner list (['S', '0', 'W']), # i.e., 'S'. # Here, we initialize "mazeMatrix" to an empty list: mazeMatrix = [] # "mazeWidth" signifies the number of columns in "mazeMatrix". # Here, we initialize "mazeWidth" to 0: mazeWidth = 0 # "mazeHeight" signifies the number of rows in "mazeMatrix". # Here, we initialize "mazeHeight" to 0: mazeHeight = 0 # "symbolDict" is a dictionary used to save the symbol indicating # the 'Entrance' to the maze, the symbol indicating the 'Exit' of the maze, # the symbol indicating the 'Wall' of the maze and the symbol indicating # the 'Road' (i.e., the path) within the maze. # These symbols are used in constructing the maze found in the text files. # The format in this dictionary is :. # For example, 'Wall': 'W', where the key is 'Wall' and the value is 'W' # (the symbol indicating the walls of the maze, read from the data file). # Here, we initialize "symbolDict" such that all its keys are set to an # empty string: symbolDict = {'Wall' : '', 'Entrance': '', 'Exit' : '', 'Road' : ''} # "symbolPosition" is also a dictionary used to save the location of the # 'Entrance' and the 'Exit' of the maze. This location is expressed using # the format: (column,row), i.e., a tuple made of two elements. # These two positions are computed in the function computeSymbolPosition(...). # You can add other useful positions in this dictionary, but you # cannot remove 'Entrance': and 'Exit': from this dictionary. symbolPosition = {'Entrance': (0, 0), 'Exit' : (0, 0)} ## Setting the game # Hide the turtle. turtle.hideturtle() # Set up the game canvas. turtle.title('Turtle Maze Game') turtle.setup(width=700, height=650) turtle.colormode(255) turtle.clear() # Get a turtle for the game. manualTurtle = turtle.Turtle() # Prompt the user for a data file via a dialogue box. title = 'Maze Filename' prompt = 'Please, enter the maze filename: ' mazeFile = turtle.textinput(title=title, prompt=prompt) # Call the function readDataFile(...). # This function reads the entrance, exit, wall and road symbols # and "mazeMatrix" from the user-supplied data file. # Notice: this function returns not one , but two "returned values"! mazeMatrix, symbolDict = readDataFile(mazeFile, mazeMatrix, symbolDict) # Call the function computeMazeWidthAndHeight(...). # This function computes and returns "mazeWidth" and "mazeHeight" # using "mazeMatrix". mazeWidth, mazeHeight = computeMazeWidthAndHeight(mazeMatrix) # Call the function setupMazeWindow(...). # This function sets up the game window size based on "mazeWidth" and # "mazeHeight" of "mazeMatrix". setupMazeWindow(mazeWidth, mazeHeight) # Call the function findSymbolPosition(...). # This function finds the position of the symbol indicating the 'Entrance' # to the maze in the "mazeMatrix" as well as the position of the symbol # indicating the 'Exit' of the maze and stores these two positions in the # "symbolPosition" dictionary. symbolPosition = findSymbolPosition(mazeMatrix, mazeWidth, mazeHeight, symbolDict, symbolPosition) # Call the function drawMaze(...). # This function draws the maze. drawMaze(mazeMatrix, mazeWidth, mazeHeight, symbolDict) # Call the function drawTurtle(...). # This function draws the "manualTurtle", i.e., the turtle # that will navigate through the maze, from its entrance to its exit. drawTurtle(manualTurtle, symbolPosition) # This keeps the drawing of the maze displayed on the computer monitor screen. turtle.done()