Vấn đề thực hiện *


7

Tôi thừa nhận thất bại, tôi phải hỏi cộng đồng câu hỏi tương tự đã được hỏi hàng triệu lần trước đây: "Điều gì sai với việc tôi thực hiện thuật toán A *?"

Tôi đang nhận được một số kết quả rất hay, đặc biệt là với các vị trí mục tiêu có tọa độ x bằng hoặc nhỏ hơn tọa độ x của diễn viên. Phải có điều gì đó xảy ra với cách tôi tìm kiếm qua hàng xóm, thứ gì đó theo kinh nghiệm của tôi, hoặc thậm chí là thứ gì đó có sử dụng cấu trúc dữ liệu của tôi ...

Dưới đây là một ví dụ về một số đường dẫn lạ mà nó tạo ra: nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Đây là phiên bản A * của riêng tôi đang chết dần trên tôi:

/// <summary>
/// Tries to get a path from the start to the end.
/// </summary>
/// <param name="start">Start of the path.</param>
/// <param name="end">End of the path.</param>
/// <param name="grid">Grid of values indicating whether grid squares are open or closed.</param>
/// <param name="path">Path from start to end.</param>
/// <returns>Whether a valid path could be found.</returns>
public bool TryGetAStarPath(Point start, Point end, out List<Point> path)
{
    // setup the path
    path = new List<Point>();

    // if either start or end is bad then don't find a path
    if (!this.IsSquareOpen(start) || !this.IsSquareOpen(end))
    {
        return false;
    }

    // setup sets
    var closed = new Dictionary<Point, AStarNode>();
    var cameFrom = new Dictionary<Point, AStarNode>();
    var open = new LinkedList<AStarNode>();
    open.InsertNode(new AStarNode(start, null, end));

    // keep going until the open set has nothing in it
    while (open.Count > 0)
    {
        // node currently being examined
        var current = open.GrabAndRemoveFirst();

        // if the end was found then reconstruct the path and return it
        if (current.Position == end)
        {
            // construct the path
            var currentPathNode = current;
            while (currentPathNode != null && currentPathNode.Position != start)
            {
                // add node position to the path
                path.Add(currentPathNode.Position);

                // get the node this node came from
                cameFrom.TryGetValue(currentPathNode.Position, out currentPathNode);
            }

            // the path is currently reversed, correct it
            path.Reverse();

            // successfully found a path
            return currentPathNode.Position == start;
        }

        // add current to the closed set
        closed.Add(current.Position, current);

        // iterate through all the neighbors
        foreach (var neighbor in this.GetNeighbors(current, end))
        {
            // if the neighbor is already in the closed set or the square is not open then skip it
            if (!this.IsSquareOpen(neighbor.Position) || closed.ContainsKey(neighbor.Position))
            {
                continue;
            }

            // if the neighbor is in the open set then compare the g-score
            var testAgainstNode = open.Find(neighbor);
            if (testAgainstNode == null || testAgainstNode.Value.FScore < neighbor.FScore)
            {
                // set the came-from node
                if (cameFrom.ContainsKey(neighbor.Position))
                {
                    cameFrom.Remove(neighbor.Position);
                }

                cameFrom.Add(neighbor.Position, current);

                // add or replace the neighbor on the open set
                if (open.Contains(neighbor))
                {
                    open.Remove(neighbor);
                }

                open.InsertNode(neighbor);
            }
        }
    }

    // if we are here then the path was never found
    return false;
}

Tôi đang sử dụng một tìm kiếm cho hàng xóm trông như thế này:

/// <summary>
/// Gets all the neighboring grid squares to the current grid square based on its location.
/// </summary>
/// <param name="current">Current grid square to find the neighbor locations for.</param>
/// <param name="endPoint">Location of the end-point.</param>
/// <returns>Neighboring grid square locations.</returns>
private IEnumerable<AStarNode> GetNeighbors(AStarNode current, Point endPoint)
{
    // setup the search space
    var searchspace = new int[] { -1, 0, 1 };

    // find the neighbors
    foreach (var x in searchspace)
    {
        foreach (var y in searchspace)
        {
            // skip 0, 0
            if (x == y && x == 0)
            {
                continue;
            }

            // test if in bounds
            var testPoint = new Point(current.Position.X + x, current.Position.Y + y);
            if (testPoint.WithinBounds(this))
            {
                yield return new AStarNode(testPoint, current, endPoint);
            }
        }
    }
}

Và cuối cùng, đối tượng AStarNode của tôi được định nghĩa là:

/// <summary>
/// A-Star node.
/// </summary>
class AStarNode
{
    // Variable declarations elided

    /// <summary>
    /// Instantiates a new instance of the <see cref="AStarNode"/> class.
    /// </summary>
    /// <param name="position">Position of the node on the grid.</param>
    /// <param name="parent">Node that this node comes from.</param>
    /// <param name="endPoint">The goal.</param>
    public AStarNode(Point position, AStarNode parent, Point endPoint)
    {
        // set the position
        this.Position = position;

        // calculate the scores
        this.CalculateScores(parent, endPoint);
    }

    /// <summary>
    /// Calculates the f, g, and h scores for this node.
    /// </summary>
    /// <param name="parent">Node that this node comes from.</param>
    /// <param name="endPoint">The goal.</param>
    private void CalculateScores(AStarNode parent, Point endPoint)
    {
        // h-score is the estimated distance to the end-point
        this.HScore = (float)Math.Sqrt(Math.Pow(endPoint.X - this.Position.X, 2) + Math.Pow(endPoint.Y - this.Position.Y, 2)) * HSCORE_MULTIPLIER;

        // g-score is the actual distance from the start
        if (parent == null)
        {
            this.GScore = 0;
        }
        else
        {
            this.GScore = parent.GScore + (parent.Position.X == this.Position.X || parent.Position.Y == this.Position.Y ? CARDINAL_MOVEMENT_COST : DIAGONAL_MOVEMENT_COST);
        }

        // f-score is g + h
        this.FScore = this.GScore + this.HScore;
    }

    public static bool operator ==(AStarNode left, Point right) => left.Position == right;
    public static bool operator !=(AStarNode left, Point right) => !(left == right);
    public override int GetHashCode() => -425505606 + EqualityComparer<Point>.Default.GetHashCode(Position);
    public override bool Equals(object obj)
    {
        var node = obj as AStarNode;
        return node != null &&
               Position.Equals(node.Position);
    }
}

Ồ, và tôi đoán cũng rất quan trọng để biết cách tôi chèn và xóa khỏi đối tượng LinkedList. Tại sao tôi sử dụng danh sách liên kết? Vì vậy, nút có đường dẫn ngắn nhất luôn ở đầu với chi phí chèn và loại bỏ nhỏ.

/// <summary>
/// Inserts the provided node into the linked list based on it's f-score value.
/// </summary>
/// <param name="list">Linked list to instert into.</param>
/// <param name="node">Node to insert.</param>
public static void InsertNode(this LinkedList<AStarNode> list, AStarNode node)
{
    // if the list is empty then just insert
    if (list.Count == 0)
    {
        list.AddFirst(node);
    }
    else
    {
        // start at the front of the list and move toward the end looking for the right spot to insert
        var compareNode = list.First;
        while (compareNode != null && compareNode.Value.FScore <= node.FScore)
        {
            compareNode = compareNode.Next;
        }

        // if the compare node is null then add to the end, otherwise add before the compare node
        if (compareNode == null)
        {
            list.AddLast(node);
        }
        else
        {
            list.AddBefore(compareNode, node);
        }
    }
}

/// <summary>
/// Grabs and removes the first node from the linked list.
/// </summary>
/// <param name="list">Linked list.</param>
/// <returns>First node.</returns>
public static AStarNode GrabAndRemoveFirst(this LinkedList<AStarNode> list)
{
    // if the list is empty then return null
    if (list.Count == 0)
    {
        return null;
    }

    // get the value to return and remove it from the list
    var toReturn = list.First;
    list.RemoveFirst();

    // return the node
    return toReturn.Value;
}

2
Bạn có thể đưa vào một số ví dụ về kết quả "thắng lợi" mà bạn nhận được không? Đôi khi các triệu chứng có thể giúp thu hẹp vấn đề. Ngoài ra, lý do của bạn để sử dụng một danh sách được liên kết nghe có vẻ giống như một lý do để sử dụng một đống nhỏ, như là điển hình hơn cho việc triển khai Djikstra & A *. Nó cung cấp cho bạn chèn O (log n) chứ không phải O (n) như bạn có bây giờ.
DMGregory

@DMGregory Tôi đã thêm một số hình ảnh, mỗi hình ảnh hiển thị một cái gì đó kỳ lạ. Hình ảnh thứ hai cho thấy theo dõi ngược. Bạn nói đúng về đống nhỏ; Nếu .NET không có triển khai thì tôi có thể làm điều đó tiếp theo.
Timothy Eckstein

A * là loại thuật toán dễ gỡ lỗi trong các trường hợp đơn giản bằng cách "thực thi trên giấy", bạn nên tiếp tục tự sửa nó IMHO (sẽ giúp bạn cảm thấy tốt hơn khi tự mình thực hiện việc này)
Guiroux

1
@Sidar di chuyển theo đường chéo thông qua các khoảng trống hiện đang là hành vi dự kiến. Điều đáng quan tâm là con đường chỉ bắt đầu uốn quanh bức tường ngay trước khi nó chạm vào tường - sau đó nó đi ngược lại (đó là bốn hình vuông được tô sáng tạo thành một hình vuông lớn hơn).
Timothy Eckstein

1
Bên cạnh đó, tôi không thể thấy định nghĩa của HSCORE_MULTIPLIER. Lưu ý rằng để A * là tối ưu, nếu tôi nhớ chính xác, HScore không bao giờ nên cao hơn giá trị thực cần thiết để đạt được mục tiêu và vì vậy, trong trường hợp này, HSCORE_MULTIPLIER không nên nhiều hơn 1, hoặc bạn có thể đánh giá quá cao Giá cả.
Liuka

Câu trả lời:


3

Vì mục tiêu của bạn là triển khai chứ không phải nhà phát triển trò chơi, tôi khuyên bạn nên sử dụng các công cụ viết để kiểm tra việc triển khai của mình.

Như ai đó trong phần bình luận đã chỉ ra, sẽ tốt hơn nếu bạn có thể theo dõi "thực thi trên giấy" một số hình thức "Tôi có thể nhìn vào và xem mọi thứ đang sai ở đâu".

Tôi chắc chắn rằng một số người trong chúng ta có thể trải qua nỗi đau khi xem mã của bạn nhưng tôi nghi ngờ về khả năng này.

Cách duy nhất để tôi giúp bạn là "nhìn vào mã của bạn sau đó thực thi nó trong đầu để hiểu cách thức hoạt động của mã" và điều đó nghe thật đau đớn.

Tôi đặc biệt khuyên bạn nên tạo một cái gì đó như dưới đây để giúp bản thân hiểu rõ hơn về cách hoạt động của mã.

nhập mô tả hình ảnh ở đây

Mặc dù từ cái nhìn nhanh chóng, tôi có thể nói một vài điều sai với việc thực hiện của bạn.

nhập mô tả hình ảnh ở đây

Ở đó bạn đang thực hiện một ngã rẽ chéo, sau đó di chuyển về phía trước sau đó rẽ. Có vẻ như điều của bạn không xử lý đúng cách "di chuyển chéo".

NẾU di chuyển đường chéo của bạn sẽ có giá bằng với gạch liền kề thì đường dẫn của bạn sẽ giống như ... nhập mô tả hình ảnh ở đây

Nếu bạn đang áp dụng cùng một chi phí chuyển động cho hướng di chuyển chéo, nó có thể giải thích trường hợp lỗi thứ 2 của ảnh chụp màn hình của bạn. nhập mô tả hình ảnh ở đây

Không có sự khác biệt về chi phí giữa đường dẫn màu xanh và đường dẫn màu tím vì bạn đặt chuyển động chéo thành chi phí nhiều như chuyển động liền kề.

Nhưng lần gỡ lỗi thứ 2 của bạn, tôi thậm chí không thể hiểu chuyện gì đang xảy ra ở đây. nhập mô tả hình ảnh ở đây Có nhiều hơn ba cách bạn có thể đọc đầu ra này. Hướng và thứ tự mà chương trình của bạn đang thực hiện là gì? Tôi cần bạn nói với tôi.

Nên về cơ bản,

  1. Tôi có thể đoán những gì sai. Tôi hy vọng dự đoán của tôi sẽ giúp.
  2. Nếu bạn thực sự muốn nhận trợ giúp và giải quyết vấn đề này, bạn cần kết quả gỡ lỗi tốt hơn cho chính mình và những người khác để xem xét và hiểu những gì sai với mã của bạn. Nếu không, ai đó cần xem mã của bạn và "chạy nó trong đầu".
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.