input: Write a function to implement the A* algorithm with the provided code.
  assert astar(start_node, goal_node) == [expected_path]
  assert astar(node_A, node_B) == [expected_path_A_to_B]
  assert astar(node_X, node_Y) == [expected_path_X_to_Y]
output: import heapq
  
  def astar(start, goal):
      def heuristic(node, goal):
          return abs(node.x - goal.x) + abs(node.y - goal.y)
  
      open_set = []
      heapq.heappush(open_set, (0, start))
      came_from = {}
      g_score = {start: 0}
      f_score = {start: heuristic(start, goal)}
  
      while open_set:
          _, current = heapq.heappop(open_set)
  
          if current == goal:
              path = []
              while current in came_from:
                  path.append(current)
                  current = came_from[current]
              path.append(start)
              path.reverse()
              return path
  
          for neighbor in current.neighbors:
              tentative_g_score = g_score[current] + current.distance_to(neighbor)
              if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
                  came_from[neighbor] = current
                  g_score[neighbor] = tentative_g_score
                  f_score[neighbor] = tentative_g_score + heuristic(neighbor, goal)
                  heapq.heappush(open_set, (f_score[neighbor], neighbor))
  
      return []