def find_path(self, start_node, target_node):
        """
        Implement the A-star path search algorithm
        If you will add a new node to the path, don't forget to set the parent.
        You can find an example in the docstring of Node class
        Please note the shortest path between two nodes may not be unique.
        However all of them have same length!

        @type self: Grid
        @type start_node: Node
           The starting node of the path
        @type target_node: Node
           The target node of the path
        @rtype: None
        """
        #create a copy of the original map
        g = copy.copy(self.map)
        #make an open PriorityQueue to store children
        opens = PriorityQueue(Node.__lt__)
        #set the starting node's g and h costs to be 0 or else it starts around infinity
        start_node.gcost = 0
        start_node.hcost = 0
        #add the starting node to the open Queue
        opens.add(start_node)
        #loop while the open set is not empty
        while not opens.is_empty():
            #remove the value with the lowest fcost built into the PriorityQueue class
            q = opens.remove()
            #create a list of successors
            suc = []
            #add each new successor node in the 8 surrounding points to the list if the index is in range and the point is navigable
            if q.grid_y-1 >= 0 and q.grid_y - 1 < self.height and q.grid_x-1 >= 0 and q.grid_x - 1 < self.width and g[q.grid_x-1][q.grid_y-1].navigable == True:
                suc.append(Node(g[q.grid_x-1][q.grid_y-1].navigable,q.grid_x-1,q.grid_y-1))
            if q.grid_y-1 >= 0 and q.grid_y - 1 < self.height and q.grid_x >= 0 and q.grid_x < self.width and g[q.grid_x][q.grid_y-1].navigable == True:
                suc.append(Node(g[q.grid_x][q.grid_y-1].navigable,q.grid_x,q.grid_y-1))
            if q.grid_y-1 >= 0 and q.grid_y - 1 < self.height and q.grid_x + 1 >= 0 and q.grid_x + 1 < self.width and g[q.grid_x+1][q.grid_y-1].navigable == True:
                suc.append(Node(g[q.grid_x+1][q.grid_y-1].navigable,q.grid_x+1,q.grid_y-1))
            if q.grid_y >= 0 and q.grid_y < self.height and q.grid_x-1 >= 0 and q.grid_x - 1 < self.width and g[q.grid_x-1][q.grid_y].navigable == True:
                suc.append(Node(g[q.grid_x-1][q.grid_y].navigable,q.grid_x-1,q.grid_y))
            if q.grid_y >= 0 and q.grid_y < self.height and q.grid_x+1 >= 0 and q.grid_x + 1 < self.width and g[q.grid_x+1][q.grid_y].navigable == True:
                suc.append(Node(g[q.grid_x+1][q.grid_y].navigable,q.grid_x+1,q.grid_y))
            if q.grid_y+1 >= 0 and q.grid_y + 1 < self.height and q.grid_x-1 >= 0 and q.grid_x - 1 < self.width and g[q.grid_x-1][q.grid_y+1].navigable == True:
                suc.append(Node(g[q.grid_x-1][q.grid_y+1].navigable,q.grid_x-1,q.grid_y+1))
            if q.grid_y+1 >= 0 and q.grid_y + 1 < self.height and q.grid_x >= 0 and q.grid_x < self.width and g[q.grid_x][q.grid_y+1].navigable == True:
                suc.append(Node(g[q.grid_x][q.grid_y+1].navigable,q.grid_x,q.grid_y+1))
            if q.grid_y+1 >= 0 and q.grid_y + 1 < self.height and q.grid_x+1 >= 0 and q.grid_x + 1 < self.width and g[q.grid_x+1][q.grid_y+1].navigable == True:
                suc.append(Node(g[q.grid_x+1][q.grid_y+1].navigable,q.grid_x+1,q.grid_y+1))
            #for each successor
            for i in range(len(suc)):
                #set the parent to be the one that was just removed
                suc[i].set_parent(q)
                #stop the search if the target node is found
                if suc[i] == target_node:
                    #also add the successor to the closed set
                    self.path.add(suc[i])
                    return None
                #set the target's g, h, and f costs
                suc[i].set_gcost(q.gcost + q.distance(suc[i]))
                suc[i].set_hcost(target_node.distance(suc[i]))
                suc[i].fcost()
                #if the successor is in the open set and is lower f than the value in the successor then skip the successsor
                if opens.is_less_than(suc[i]):
                    pass
                #if the successor is in the closed set and is lower f than the value in the successor then skip the successsor
                if self.path.is_less_than(suc[i]):
                    pass
                #otherwise add the successor to the open set
                else:
                    opens.add(suc[i])
                #add the q value to the closed set
                self.path.add(q)
class Grid:
    """
    Represents the world where the action of the game takes place.
    You may define helper methods as you see fit.

    === Attributes: ===
    @type width: int
       represents the width of the game map in characters
       the x-coordinate runs along width
       the leftmost node has x-coordinate zero
    @type height: int
       represents the height of the game map in lines
       the y-coordinate runs along height; the topmost
       line contains nodes with y-coordinate 0
    @type map: List[List[Node]]
       map[x][y] is a Node with x-coordinate equal to x
       running from 0 to width-1
       and y-coordinate running from 0 to height-1
    @type treasure: Node
       a navigable node in the map, the location of the treasure
    @type boat: Node
       a navigable node in the map, the current location of the boat
    @type path: PriorityQueue
        a closed list of all the daughter Nodes in the A* algorithm

    === Representation invariants ===
    - width and height are positive integers
    - map has dimensions width, height
    """

    def __init__(self, file_path, text_grid=None):
        """
        If text_grid is None, initialize a new Grid assuming file_path
        contains pathname to a text file with the following format:
        ..+..++
        ++.B..+
        .....++
        ++.....
        .T....+
        where a dot indicates a navigable Node, a plus indicates a
        non-navigable Node, B indicates the boat, and T the treasure.
        The width of this grid is 7 and height is 5.
        If text_grid is not None, it should be a list of strings
        representing a Grid. One string element of the list represents
        one row of the Grid. For example the grid above, should be
        stored in text_grid as follows:
        ["..+..++", "++.B..+", ".....++", "++.....", ".T....+"]

        @type file_path: str
           - a file pathname. See the above for the file format.
           - it should be ignored if text_grid is not None.
           - the file specified by file_path should exists, so there
             is no need for error handling
           Please call open_grid to open the file
        @type text_grid: List[str]
        @rtype: None
        """
        self.state = "STARTED"
        self.file_path = file_path
        self.text_grid = text_grid
        #set up
        self.path = PriorityQueue(Node.__lt__)
        #set up map using open_grid() function and then splitting it if it is a file path
        if self.text_grid == None:
            self.map = self.open_grid(self.file_path).read().split("\n")
        #set up map if it is a text grid
        else:
            self.map = self.text_grid
        #y length of the map
        self.height = len(self.map)
        #break up the map into a 2 dimensional list of strings
        for i in range(len(self.map)):
            self.map[i] = list(self.map[i])
        #set width to be the horizontal length of the map
        self.width = len(self.map[0][:])
        #find the treasure located in the map
        g = copy.copy(self.map)
        for i, x in enumerate(g):
            if "T" in x:
                self.treasure = Node(True, x.index("T"),i)
        #change x and y values
        self.map  = [list(i) for i in zip(*self.map)]
        self.boat = self.set_boat()
        for i,x in enumerate(self.map):
            for j in range(len(x)):
                self.map[i][j] = Node(self.map[i][j] != "+",i,j)
        

    @classmethod
    def open_grid(self, file_path):
        """
        @rtype TextIOWrapper: 
        """
        return open(file_path)
    
    def __str__(self):
        """
        Return a string representation.

        @type self: Grid
        @rtype: str

        >>> g = Grid("", ["B.++", ".+..", "...T"])
        >>> print(g)
        B.++
        .+..
        ...T
        """
        #join the 2d list twice, once with nothing and the other with linebreaks
        lst = []
        g = self.convert()
        for i in g:
            lst.append("".join(i))
        return"\n".join(lst)

    def move(self, direction):
        """
        Move the boat in a specific direction, if the node
        corresponding to the direction is navigable
        Else do nothing

        @type self: Grid
        @type direction: str
        @rtype: None

        direction may be one of the following:
        N, S, E, W, NW, NE, SW, SE
        (north, south, ...)
        123
        4B5
        678
        1=NW, 2=N, 3=NE, 4=W, 5=E, 6=SW, 7=S, 8=SE
        >>> g = Grid("", ["B.++", ".+..", "...T"])
        >>> g.move("S")
        >>> print(g)
        ..++
        B+..
        ...T
        """
        #create moving direction vectors if the N S E W or a combination is entered
        if direction == "N":
            new = (0,-1)
        elif direction == "S":
            new = (0,1)
        elif direction == "E":
            new = (1,0)
        elif direction == "W":
            new = (-1,0)
        elif direction == "NW":
            new = (-1,-1)
        elif direction == "NE":
            new = (1,-1)
        elif direction == "SE":
            new = (1,1)
        elif direction == "SW":
            new = (-1,1)
        else:
            print("Invalid Command")
            
        #create a copy of the map so as not to disturb the original 
        g = copy.copy(self.map)
        #find the boat's index
        B = (self.boat.grid_x,self.boat.grid_y)
        #if the boat (x,y) + the new direction(x,y) is in the bounds of the map
        if B[0]+new[0] >= 0 and B[0]+new[0] < self.height and B[1]+new[1] >= 0 and B[1]+new[1] < self.width:
            #if the new position of the boat is navigable
            if g[B[0]+new[0]][B[1]+new[1]].navigable == True or g[B[0]+new[0]][B[1]+new[1]] == self.treasure:
                #if the new position is the target position then you win
                if g[B[0]+new[0]][B[1]+new[1]] == self.treasure:
                    self.state = "WON"
                    print(self.state)
                #move change the map's position to an dot on the old one and B on the new
                self.boat = g[B[0]+new[0]][B[1]+new[1]]
                #join the list back together the list we have been working on
                #set the map equal to the new one set boat  equal to new position
                self.map = g
            else:
                print("Cannot Move here")
        else:
            print("Cannot Move here")
        

    def find_path(self, start_node, target_node):
        """
        Implement the A-star path search algorithm
        If you will add a new node to the path, don't forget to set the parent.
        You can find an example in the docstring of Node class
        Please note the shortest path between two nodes may not be unique.
        However all of them have same length!

        @type self: Grid
        @type start_node: Node
           The starting node of the path
        @type target_node: Node
           The target node of the path
        @rtype: None
        """
        #create a copy of the original map
        g = copy.copy(self.map)
        #make an open PriorityQueue to store children
        opens = PriorityQueue(Node.__lt__)
        #set the starting node's g and h costs to be 0 or else it starts around infinity
        start_node.gcost = 0
        start_node.hcost = 0
        #add the starting node to the open Queue
        opens.add(start_node)
        #loop while the open set is not empty
        while not opens.is_empty():
            #remove the value with the lowest fcost built into the PriorityQueue class
            q = opens.remove()
            #create a list of successors
            suc = []
            #add each new successor node in the 8 surrounding points to the list if the index is in range and the point is navigable
            if q.grid_y-1 >= 0 and q.grid_y - 1 < self.height and q.grid_x-1 >= 0 and q.grid_x - 1 < self.width and g[q.grid_x-1][q.grid_y-1].navigable == True:
                suc.append(Node(g[q.grid_x-1][q.grid_y-1].navigable,q.grid_x-1,q.grid_y-1))
            if q.grid_y-1 >= 0 and q.grid_y - 1 < self.height and q.grid_x >= 0 and q.grid_x < self.width and g[q.grid_x][q.grid_y-1].navigable == True:
                suc.append(Node(g[q.grid_x][q.grid_y-1].navigable,q.grid_x,q.grid_y-1))
            if q.grid_y-1 >= 0 and q.grid_y - 1 < self.height and q.grid_x + 1 >= 0 and q.grid_x + 1 < self.width and g[q.grid_x+1][q.grid_y-1].navigable == True:
                suc.append(Node(g[q.grid_x+1][q.grid_y-1].navigable,q.grid_x+1,q.grid_y-1))
            if q.grid_y >= 0 and q.grid_y < self.height and q.grid_x-1 >= 0 and q.grid_x - 1 < self.width and g[q.grid_x-1][q.grid_y].navigable == True:
                suc.append(Node(g[q.grid_x-1][q.grid_y].navigable,q.grid_x-1,q.grid_y))
            if q.grid_y >= 0 and q.grid_y < self.height and q.grid_x+1 >= 0 and q.grid_x + 1 < self.width and g[q.grid_x+1][q.grid_y].navigable == True:
                suc.append(Node(g[q.grid_x+1][q.grid_y].navigable,q.grid_x+1,q.grid_y))
            if q.grid_y+1 >= 0 and q.grid_y + 1 < self.height and q.grid_x-1 >= 0 and q.grid_x - 1 < self.width and g[q.grid_x-1][q.grid_y+1].navigable == True:
                suc.append(Node(g[q.grid_x-1][q.grid_y+1].navigable,q.grid_x-1,q.grid_y+1))
            if q.grid_y+1 >= 0 and q.grid_y + 1 < self.height and q.grid_x >= 0 and q.grid_x < self.width and g[q.grid_x][q.grid_y+1].navigable == True:
                suc.append(Node(g[q.grid_x][q.grid_y+1].navigable,q.grid_x,q.grid_y+1))
            if q.grid_y+1 >= 0 and q.grid_y + 1 < self.height and q.grid_x+1 >= 0 and q.grid_x + 1 < self.width and g[q.grid_x+1][q.grid_y+1].navigable == True:
                suc.append(Node(g[q.grid_x+1][q.grid_y+1].navigable,q.grid_x+1,q.grid_y+1))
            #for each successor
            for i in range(len(suc)):
                #set the parent to be the one that was just removed
                suc[i].set_parent(q)
                #stop the search if the target node is found
                if suc[i] == target_node:
                    #also add the successor to the closed set
                    self.path.add(suc[i])
                    return None
                #set the target's g, h, and f costs
                suc[i].set_gcost(q.gcost + q.distance(suc[i]))
                suc[i].set_hcost(target_node.distance(suc[i]))
                suc[i].fcost()
                #if the successor is in the open set and is lower f than the value in the successor then skip the successsor
                if opens.is_less_than(suc[i]):
                    pass
                #if the successor is in the closed set and is lower f than the value in the successor then skip the successsor
                if self.path.is_less_than(suc[i]):
                    pass
                #otherwise add the successor to the open set
                else:
                    opens.add(suc[i])
                #add the q value to the closed set
                self.path.add(q)
            
    def convert(self):
        """converts the map of nodes into a list of strings
        
        @type self: Grid
        @rtype: list
        """
        #create a copy of the map
        g = copy.copy(self.map)
        #invert the map back to y and x
        g = [list(i) for i in zip(*self.map)]
        #loop for each x y in map set the original value that was given as an input
        for i,x in enumerate(g):
            for j in range(len(x)):
                if g[i][j].navigable and g[i][j] != self.boat and g[i][j] != self.treasure:
                    g[i][j] = "."
                elif g[i][j] == self.boat:
                    g[i][j] = "B"
                elif g[i][j] == self.treasure:
                    g[i][j] = "T"
                else:
                    g[i][j] = "+"
        return g

    def retrace_path(self, start_node, target_node):
        """
        Return a list of Nodes, starting from start_node,
        ending at target_node, tracing the parent
        Namely, start from target_node, and add its parent
        to the list. Keep going until you reach the start_node.
        If the chain breaks before reaching the start_node,
        return an empty list.

        @type self: Grid
        @type start_node: Node
        @type target_node: Node
        @rtype: list[Node]
        """
        #run the find_path function
        self.find_path(start_node,target_node)
        #create an empty list
        lst = []
        #copy the closed set
        nodel = copy.copy(self.path)
        #add the closed set into a list
        while not nodel.is_empty():
            lst.append(nodel.remove())
        #find the target node in the list
        for i in range(len(lst)):
            if lst[i] == target_node:
                tar = i
        #create the path from the target to the starting node by adding the all n parents to the list then return it
        lst2 = []
        lst2.append(lst[tar])
        node = lst[tar]
        while not node == start_node:
            lst2.append(node)
            node = node.parent
        return lst2
        
    def set_boat(self):
        """set the position of the boat
        @type self: Grid
        @rtype: Node
        """
        #create a copy of the map
        g = copy.copy(self.map)
        #search throught the values in the node for the boat
        for i, x in enumerate(g):
            if "B" in x:
                #return a node of the boat
                return Node(True, i, x.index("B"))
        
    def get_treasure(self, s_range):
        """
        Return treasure node if it is located at a distance s_range or
        less from the boat, else return None
        @type s_range: int
        @rtype: Node, None
        """
        #make a copy of the map
        g = copy.copy(self.map)
        #go through all of g to find the value T and set the treasure at that value = node at that point
        for i, x in enumerate(g):
            if "T" in x:
                Treasure = Node(True, i, x.index("T"))
        #return the value if it is <= s_range or else don't
        if self.boat.distance(Treasure) <= s_range:
            return Treasure
        else:
            return None


    def plot_path(self, start_node, target_node):
        """
        Return a string representation of the grid map,
        plotting the shortest path from start_node to target_node
        computed by find_path using "*" characters to show the path
        @type self: Grid
        @type start_node: Node
        @type target_node: Node
        @rtype: str
        >>> g = Grid("", ["B.++", ".+..", "...T"])
        >>> print(g.plot_path(g.boat, g.treasure))
        B*++
        .+*.
        ...T
        """
        #create a copy of the map
        g = copy.copy(self.map)
        #make a path using A*
        paths = self.retrace_path(start_node,target_node)
        #write the path on the grid 
        g = self.convert()
        for p in paths:
            if p != start_node and p!= target_node:
                g[p.grid_y][p.grid_x] = "*"
        new = []
        for i, x in enumerate(g):
            new.append("".join(x))
        return "\n".join(new)