###################################### # DCS 229 -- TurtleMaze # Utility file with the TurtleMaze class. # Date: March 13, 2026 # Name: Prof. Andy Ricci # Resources Used: https://runestone.academy/ns/books/published/pythonds/Recursion/ExploringaMaze.html. I also worked with Pat Cohen on the project. ########################################## from __future__ import annotations from enum import Enum import turtle ################################################################################ class Contents(str, Enum): ''' create an enumeration to define what the visual contents of a Cell are; using str as a "mixin" (multiple inheritance) forces all the entries to be strings; using an Enum means no cell entry can be anything other than the options here ''' EMPTY = ' ' START = 'S' GOAL = 'G' PART_OF_PATH = 'O' TRIED = '.' OBSTACLE = '+' DEAD_END = '-' def __str__(self) -> str: return self.value ################################################################################ class Cell: ''' class that allows us to use Cell as a data type -- an ordered triple of row, column, & cell contents (see Contents class enumeration above) ''' def __init__(self, row: int, col: int, contents: Contents): self._contents: Contents = contents self._parent: Cell = None # parent of this Cell during exploration self.x = col self.y = row def getParent(self) -> Cell: ''' method to return the parent of this Cell object as determined during maze exploration Returns: a Cell object corresponding to the cell that considered this cell during the exploration process ''' return self._parent def setParent(self, parent: Cell) -> None: ''' setter method to update this Cell's parent Parameters: parent: a different Cell object Raises: ValueError if self == parent ''' if self == parent: raise ValueError(f"a Cell cannot be its own parent") self._parent = parent def markOnPath(self) -> None: ''' method to identify this cell as being on the path from source to goal ''' self._contents = Contents.PART_OF_PATH def isBlocked(self) -> bool: ''' Boolean method to indicate whether this cell contains a block Returns: True if the cell is blocked (cannot be explored), False o/w ''' return self._contents == Contents.OBSTACLE def isGoal(self) -> bool: ''' Boolean method to indicate whether this cell is the goal Returns: True if the cell is the maze goal, False o/w ''' return self._contents == Contents.GOAL def __str__(self) -> str: ''' creates and returns a string representation of this cell Returns: a string identifying the cell's row, col, and cell contents ''' string = f"({self.x}, {self.y}, {self._contents}) " if self._parent is not None: string += f"Parent is: ({self._parent.x}, {self._parent.y})" return string def __repr__(self) -> str: ''' overriding __repr__ so that printing, e.g., a list of Cell objects (which will call __repr__ for each) will call __str__ for each, printing nicely Returns: a string identifying the cell's row, col, and cell contents ''' return self.__str__() def __eq__(self, other: Cell) -> bool: ''' indicates whether a given other Cell is equal to this Cell Returns: True if this Cell and the other Cell are the same, False o/w ''' return self.x == other.x and \ self.y == other.y ################################################################################ class TurtleMaze: def __init__(self, mazeFileName): rowsInMaze = 0 columnsInMaze = 0 self.maze_grid = [] mazeFile = open(mazeFileName, 'r') rowsInMaze = 0 for line in mazeFile: rowList = [] col = 0 for ch in line[:-1]: # # make new cell: (row, col, contents) cell = Cell(rowsInMaze, col, ch) rowList.append(cell) if ch == Contents.START: self.startRow = rowsInMaze self.startCol = col self._start = cell if ch == Contents.GOAL: self._goal = cell col = col + 1 rowsInMaze = rowsInMaze + 1 self.maze_grid.append(rowList) columnsInMaze = len(rowList) self._num_rows = rowsInMaze self._num_cols = columnsInMaze self.xTranslate = -columnsInMaze / 2 self.yTranslate = rowsInMaze / 2 self.t = turtle.Turtle() self.t.shape('turtle') self.wn = turtle.Screen() self.wn.setworldcoordinates(-(columnsInMaze - 1) / 2 - .5, -(rowsInMaze - 1) / 2 - .5, (columnsInMaze - 1) / 2 + .5, (rowsInMaze - 1) / 2 + .5) def drawMaze(self): self.t.speed(10) self.wn.tracer(0) for y in range(self._num_rows): for x in range(self._num_cols): cell = self.maze_grid[y][x] if cell._contents == Contents.OBSTACLE: self.drawCenteredBox(x + self.xTranslate, -y + self.yTranslate, 'orange') self.updatePosition(self.getStart()) self.t.color('black') self.t.fillcolor('blue') self.wn.update() self.wn.tracer(1) def drawCenteredBox(self, x, y, color): self.t.up() self.t.goto(x - .5, y - .5) self.t.color(color) self.t.fillcolor(color) self.t.setheading(90) self.t.down() self.t.begin_fill() for i in range(4): self.t.forward(1) self.t.right(90) self.t.end_fill() def moveTurtle(self, x, y): self.t.up() self.t.setheading(self.t.towards(x + self.xTranslate, -y + self.yTranslate)) self.t.goto(x + self.xTranslate, -y + self.yTranslate) def dropBreadcrumb(self, color): self.t.dot(10, color) def updatePosition(self, cell, val=None): if val: cell._contents = val self.moveTurtle(cell.x, cell.y) if val == Contents.PART_OF_PATH: color = 'green' elif val == Contents.TRIED: color = 'black' elif val == Contents.DEAD_END: color = 'red' else: color = None if color: self.dropBreadcrumb(color) def getStart(self) -> Cell: ''' accessor method to return the Cell object corresponding to the Maze start Returns: the Cell object at the Maze start location ''' return self._start def getGoal(self) -> Cell: ''' accessor method to return the Cell object corresponding to the Maze goal Returns: the Cell object at the Maze goal location ''' return self._goal def __getitem__(self, idx): """ Magic method for accessing element with square brackets [] Example usage: maze = TurtleMaze(file) maze[4] # the row at index 4 (the 5th row in the maze) maze[4][0] # the first cell in the row at index 4 """ return self.maze_grid[idx] def main(): maze = TurtleMaze('maze_3.txt') maze.drawMaze() # print("row at index 4", maze[4]) # print("cell at index 4, 4", maze[4][4]) maze.updatePosition(maze.getStart()) print(maze.getStart()) maze.t.screen.mainloop() if __name__ == "__main__": main()