/// <summary>
        ///     Wyszukuje najkrótszą ścieżkę pomiędzy dwoma polami.
        ///     Implementacja algorytmu A*.
        /// </summary>
        /// <param name="fields">Tablica pól z zadeklarowną dostępnością. Muszą dziedziczyć po klasie PathFindableField</param>
        /// <param name="start">Pole startowe</param>
        /// <param name="destination">Pole końcowe</param>
        /// <param name="minIndexLimit">Minimalne indeksy pól branych pod uwagę przy wyznaczaniu ścieżki</param>
        /// <param name="maxIndexLimit">Maksymalne indeksy pól branych pod uwagę przy wyznaczaniu ścieżki</param>
        /// <seealso cref="PathFindableField"/>
        /// <exception cref="ArgumentNullException" />
        /// <exception cref="ArgumentOutOfRangeException" />
        /// <returns>Najkrótsza ścieżka (stos)</returns>
        internal static Stack <Vector2Int> FindPath(bool[][] fields, Vector2Int start, Vector2Int destination, Vector2Int minIndexLimit, Vector2Int maxIndexLimit)
        {
#if DEBUG
            CheckArguments(fields, start, destination, minIndexLimit, maxIndexLimit);
#endif
            var openSet   = new FastPriorityQueue <AStarPosition>((maxIndexLimit.x - minIndexLimit.x) * (maxIndexLimit.y - minIndexLimit.y));
            var startNode = new AStarNode();
            InitializeAstarNodes(fields, out AStarNode[][] nodes);
            InitializeParents(fields, out Vector2Int[][] parents);

            AddToOpenSet(openSet, nodes, startNode, start);

            while (openSet.Count > 0)
            {
                var actualNodePosition = openSet.Dequeue();

                // znaleziono ścieżkę
                if (actualNodePosition.position == destination)
                {
                    return(CombinePath(actualNodePosition.position, start, nodes, parents));
                }

                AddToClosedSet(nodes, actualNodePosition.position);

                foreach (var neighborPosition in GetNeighbors(fields, nodes, actualNodePosition.position, minIndexLimit, maxIndexLimit))
                {
                    if (IsInClosedSet(neighborPosition, nodes))
                    {
                        continue;
                    }

                    var actualG = nodes[actualNodePosition.position.x][actualNodePosition.position.y].g + 1;

                    if (IsInOpenSet(neighborPosition, nodes))
                    {
                        var neighbor = nodes[neighborPosition.x][neighborPosition.y];

                        if (actualG < neighbor.g)
                        {
                            parents[neighborPosition.x][neighborPosition.y] = actualNodePosition.position;
                            neighbor.g = actualG;

                            openSet.UpdatePriority(actualNodePosition, neighbor.F);
                        }
                    }
                    else
                    {
                        parents[neighborPosition.x][neighborPosition.y]     = actualNodePosition.position;
                        nodes[neighborPosition.x][neighborPosition.y].g     = actualG;
                        nodes[neighborPosition.x][neighborPosition.y].h     = HexHelper.GetDistance(neighborPosition, destination);
                        nodes[neighborPosition.x][neighborPosition.y].state = AStarNode.States.InOpenSet;
                        openSet.Enqueue(new AStarPosition(neighborPosition), nodes[neighborPosition.x][neighborPosition.y].F);
                    }
                }
            }

            return(new Stack <Vector2Int>());
        }
        /// <summary>
        ///     Wyszukuje najkrótszą ścieżkę pomiędzy dwoma polami.
        ///     Implementacja algorytmu A*.
        /// </summary>
        /// <param name="fields">Tablica pól z zadeklarowną dostępnością. Muszą dziedziczyć po klasie PathFindableField</param>
        /// <param name="start">Pole startowe</param>
        /// <param name="destination">Pole końcowe</param>
        /// <param name="minIndexes">Minimalne indeksy pól branych pod uwagę przy wyznaczaniu ścieżki</param>
        /// <param name="maxIndexes">Maksymalne indeksy pól branych pod uwagę przy wyznaczaniu ścieżki</param>
        /// <seealso cref="PathFindableField"/>
        /// <exception cref="ArgumentNullException" />
        /// <exception cref="ArgumentOutOfRangeException" />
        /// <returns>Najkrótsza ścieżka (stos)</returns>
        internal static Stack <Vector2Int> FindPath(bool[][] fields, Vector2Int start, Vector2Int destination, Vector2Int minIndexes, Vector2Int maxIndexes)
        {
#if DEBUG
            CheckArguments(fields, start, destination, minIndexes, maxIndexes);
#endif

            Node[][] nodes     = InitializeAstarNodes(fields);
            var      openSet   = new FastPriorityQueue <Node>((maxIndexes.x - minIndexes.x) * (maxIndexes.y - minIndexes.y));
            var      startNode = new Node(start);

            AddToOpenSet(openSet, nodes, startNode);

            while (openSet.Count > 0)
            {
                var actualNode = openSet.Dequeue();

                // znaleziono ścieżkę
                if (actualNode.position == destination)
                {
                    return(CombinePath(actualNode));
                }

                AddToClosedSet(nodes, actualNode);

                foreach (var neighborPosition in GetNeighbors(fields, nodes, actualNode.position, minIndexes, maxIndexes))
                {
                    if (IsInClosedSet(neighborPosition, nodes))
                    {
                        continue;
                    }

                    var actualG = actualNode.g + 1;

                    if (IsInOpenSet(neighborPosition, nodes))
                    {
                        var neighbor = nodes[neighborPosition.x][neighborPosition.y];

                        if (actualG < neighbor.g)
                        {
                            neighbor.parent = actualNode;
                            neighbor.g      = actualG;

                            openSet.UpdatePriority(neighbor, neighbor.F);
                        }
                    }
                    else
                    {
                        var neighbor = new Node(neighborPosition)
                        {
                            parent = actualNode,
                            g      = actualG,
                        };

                        neighbor.h = HexHelper.GetDistance(neighbor.position, destination);

                        AddToOpenSet(openSet, nodes, neighbor);
                    }
                }
            }

            return(new Stack <Vector2Int>());
        }