@Override
    public List<Node> findPath(Node start, Node goal) {
        Set<Node> closedSet = new HashSet<>();

        Map<Node, Double> gScore = new HashMap<>();
        gScore.put(start, 0.0);

        Map<Node, Double> fScore = new HashMap<>();
        fScore.put(start, gScore.get(start) + heuristicCostEstimate.apply(start, goal));

        Queue<Node> openSet = new PriorityQueue<>((n1, n2) -> Double.compare(fScore.get(n1), fScore.get(n2)));
        openSet.add(start);

        Map<Node, Node> cameFrom = new HashMap<>();

        while (!openSet.isEmpty()) {
            Node current = openSet.remove();

            log.fine(() -> String.format("Examine %s:", current));

            if (current == goal) {
                log.fine(() -> "Goal reached");

                return reconstructPath(cameFrom, goal);
            }

            closedSet.add(current);

            StreamSupport.stream(neighborNodes.apply(current).spliterator(), false).forEach(neighbor -> {
                if (closedSet.contains(neighbor))
                    return;

                double tentativeGScore = gScore.get(current) + distBetween.apply(current, neighbor);

                if (!openSet.contains(neighbor) || tentativeGScore < gScore.get(neighbor)) {
                    log.fine(() -> String.format("Update info for %s", neighbor));

                    cameFrom.put(neighbor, current);
                    gScore.put(neighbor, tentativeGScore);
                    fScore.put(neighbor, gScore.get(neighbor) + heuristicCostEstimate.apply(neighbor, goal));
                    if (!openSet.contains(neighbor))
                        openSet.add(neighbor);
                }
            });
        }

        log.fine(() -> "Path not found");

        return null;
    }