1. 程式人生 > >2D獵寶行動(類掃雷小遊戲)DAY 7

2D獵寶行動(類掃雷小遊戲)DAY 7

1.建立AStar尋路演算法所需要的資料結構類

新建AstarStructure類用來寫AStar演算法的結構體

public class Point
{
    public int x, y;

    public Point(int x,int y)
    {
        this.x = x;
        this.y = y;
    }

    public new bool Equals(object obj)
    {
        return (obj != null) && (obj is Point) && (((Point)obj).x == x && ((Point)obj).y == y);
    }
}

public class PointData
{
    public Point point;
    public double g, h;
    public PointData parent;

    public PointData(Point point,double g,double h,PointData parent)
    {
        this.point = point;
        this.g = g;
        this.h = h;
        this.parent = parent;
    }

    public double F()
    {
        return g + h;
    }
}

2.設計尋路所需的地圖的形式及挑選H函式

1、首先將起始點新增進“開啟列表”。

2、重複如下步驟:
    
    a) 尋找開啟列表中F值最低的節點。我們稱其為“當前節點”。

    b) 把它從“開啟列表”中移除,並新增進“關閉列表”。

    c) 檢查“當前節點”是否是“目標節點”

       * 如果是,停止搜尋,跳到第 3 步;

       * 如果不是,繼續下面步驟; 

    d) 尋找“當前節點”鄰近的節點

       * 如果它不可通過或者已經在關閉列表中,略過它。反之如下。

       * 如果它不在開啟列表中,把它新增進開啟列表。把當前節點作為這一節點的父節點。記錄這一格的F,G和H值。

       * 如果它已經在開啟列表中,檢查新路徑對它G值的產生的影響
           
           a. 如果它的G值因為新路徑變大,那麼保持原來的狀態,不作任何改變;

           b. 如果G值變小,說明新路徑更好,將其父結點改為“當前結點”,更新G和F值。

    e) 檢查列表是否為空,如果為空,說明路徑未找到,直接返回,不繼續任何步驟。

3、儲存路徑。從目標節點開始,沿著每一節點的父節點移動直到回到起始節點。這就是我們要找的路徑。

根據上述,來寫AStar的尋路演算法。

using System;

public class AStarPathfinding
{

    //	1、首先將起始點新增進“開啟列表”。
    //  2、重複如下步驟:
    //    a) 尋找開啟列表中F值最低的節點。我們稱其為“當前節點”。
    //    b) 把它從“開啟列表”中移除,並新增進“關閉列表”。
    //    c) 檢查“當前節點”是否是“目標節點”
    //       * 如果是,停止搜尋,跳到第 3 步;
    //       * 如果不是,繼續下面步驟; 
    //    d) 尋找“當前節點”鄰近的節點
    //      * 如果它不可通過或者已經在關閉列表中,略過它。反之如下。
    //       * 如果它不在開啟列表中,把它新增進開啟列表。把當前節點作為這一節點的父節點。記錄這一格的F,G和H值。
    //       * 如果它已經在開啟列表中,檢查新路徑對它G值的產生的影響
    //          a.如果它的G值因為新路徑變大,那麼保持原來的狀態,不作任何改變;
    //          b.如果G值變小,說明新路徑更好,將其父結點改為“當前結點”,更新G和F值。
    //   e) 檢查列表是否為空,如果為空,說明路徑未找到,直接返回,不繼續任何步驟。
    //  3、儲存路徑。從目標節點開始,沿著每一節點的父節點移動直到回到起始節點。這就是我們要找的路徑。

    //地圖元素
    private const char START = 'S';      //起點
    private const char END = 'E';        //終點
    private const char SPACE = '.';      //空地
    private const char WALL = 'W';      //牆
    private const char VISTIED = '-';      //被訪問過
    private const char ON_PATH = '@';      //在結果路徑上

    //地圖字串
    public static char[,] MAP = null;
    //地圖最大尺寸
    public static Point MAX_PNT = null;
    //起點
    public static Point START_PNT = null;
    //終點
    public static Point END_PNT = null;

    //TODO 翻譯地圖的方法

    /// <summary>
    /// 曼哈頓距離,小於等於實際值
    /// </summary>
    /// <param name="pnt">當前評估點</param>
    /// <returns>按照曼哈頓距離所評估的H值</returns>
    private static double HManhattanDistance(Point pnt)
    {
        return Math.Abs(pnt.x - END_PNT.x) + Math.Abs(pnt.y - END_PNT.y);

    }

    /// <summary>
    /// 歐式距離距離,小於等於實際值
    /// </summary>
    /// <param name="pnt">當前評估點</param>
    /// <returns>按照歐式距離所評估的H值</returns>
    private static double HEuclidianDistance(Point pnt)
    {
        return Math.Sqrt(Math.Pow(pnt.x - END_PNT.x, 2) + Math.Pow(pnt.y - END_PNT.y, 2));

    }

    /// <summary>
    /// 歐式距離平方,大於等於實際值
    /// </summary>
    /// <param name="pnt">當前評估點</param>
    /// <returns>按照歐式距離平方所評估的H值</returns>
    private static double HPowEuclidianDistance(Point pnt)
    {
        return Math.Pow(pnt.x - END_PNT.x, 2) + Math.Pow(pnt.y - END_PNT.y, 2);
    }

    /// <summary>
    /// H函式
    /// </summary>
    /// <param name="pnt">當前評估點</param>
    /// <returns>按照指定方法評估的H值</returns>
    private static double HFun(Point pnt)
    {
        return HManhattanDistance(pnt);
    }

    /// <summary>
    /// 尋路函式
    /// </summary>
    private void Search()
    {

    }
}

3.編寫AStar尋路演算法(上)

    /// <summary>
    /// 尋路函式
    /// </summary>
    private void Search()
    {
        //用List集合做“開啟列表”來記錄擴充套件的點
        List<PointData> openList = new List<PointData>();
        //八個擴充套件方向
        int[,] directs = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 } };
        //把起始點放入堆
        openList.Add(new PointData(START_PNT, 0, 0, null));
        //找到最後一個點的資料,用來反推路徑
        PointData endPoint = null;
        //找到終點或“開啟列表”為空時退出迴圈
        for(bool finish = false; !finish && openList.Count > 0;)
        {
            //取出f值最小的點
            openList.Sort((x, y) => { return x.F().CompareTo(y.F()); });
            PointData data = openList[0];
            openList.RemoveAt(0);
            Point point = data.point;
            //將取出的點標記為已訪問點
            if(MAP[point.x,point.y] == SPACE)
            {
                MAP[point.x, point.y] = VISTIED;
            }
            //遍歷八個方向的點,Rank返回維數
            for (int i = 0; i < directs.Rank; i++)
            {
                Point newPoint = new Point(point.x + directs[i, 0], point.y + directs[i, 1]);
                if(newPoint.x >= 0 && newPoint.x <= MAX_PNT.x && newPoint.y >= 0 && newPoint.y < MAX_PNT.y)
                {
                    //如果是終點,則跳出迴圈,不用再找
                    char e = MAP[newPoint.x, newPoint.y];
                    if(e == END)
                    {
                        endPoint = data;
                        finish = true;
                        break;
                    }
                    //如果不是空地,就不需要擴充套件
                    if (e != SPACE)
                    {
                        continue;
                    }
                    //如果在“開啟列表”裡,則更新g值
                    PointData tempData = openList.Find((x) => { return x.point.Equals(newPoint); });
                    if(tempData != null)
                    {
                        //更新G值
                    }
                }
            }
        }
    }

4.編寫AStar尋路演算法(下)

    /// <summary>
    /// 尋路函式
    /// </summary>
    private void Search()
    {
        //用List集合做“開啟列表”來記錄擴充套件的點
        List<PointData> openList = new List<PointData>();
        //八個擴充套件方向
        int[,] directs = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 } };
        //把起始點放入堆
        openList.Add(new PointData(START_PNT, 0, 0, null));
        //找到最後一個點的資料,用來反推路徑
        PointData endData = null;
        //找到終點或“開啟列表”為空時退出迴圈
        for(bool finish = false; !finish && openList.Count > 0;)
        {
            //取出f值最小的點
            openList.Sort((x, y) => { return x.F().CompareTo(y.F()); });
            PointData data = openList[0];
            openList.RemoveAt(0);
            Point point = data.point;
            //將取出的點標記為已訪問點
            if(MAP[point.x,point.y] == SPACE)
            {
                MAP[point.x, point.y] = VISTIED;
            }
            //遍歷八個方向的點,Rank返回維數
            for (int i = 0; i < directs.Rank; i++)
            {
                Point newPoint = new Point(point.x + directs[i, 0], point.y + directs[i, 1]);
                if(newPoint.x >= 0 && newPoint.x <= MAX_PNT.x && newPoint.y >= 0 && newPoint.y < MAX_PNT.y)
                {
                    //如果是終點,則跳出迴圈,不用再找
                    char e = MAP[newPoint.x, newPoint.y];
                    if(e == END)
                    {
                        endData = data;
                        finish = true;
                        break;
                    }
                    //如果不是空地,就不需要擴充套件
                    if (e != SPACE)
                    {
                        continue;
                    }
                    //如果在“開啟列表”裡,則更新g值
                    PointData tempData = openList.Find((x) => { return x.point.Equals(newPoint); });
                    if(tempData != null)
                    {
                        //更新G值
                        float goffset;
                        if (Math.Abs(directs[i, 0]) + Math.Abs(directs[i, 1]) > 1)
                        {
                            goffset = 1.4f;
                        }
                        else
                        {
                            goffset = 1.0f;
                        }
                        if(tempData.g > data.g + goffset)
                        {
                            tempData.g = data.g + goffset;
                            tempData.parent = data;
                        }
                    }
                    //如果不在“開啟列表”裡,則放入“開啟列表”中,並計算g,h值
                    else
                    {
                        float goffset;
                        if (Math.Abs(directs[i, 0]) + Math.Abs(directs[i, 1]) > 1)
                        {
                            goffset = 1.4f;
                        }
                        else
                        {
                            goffset = 1.0f;
                        }
                        double h = HFun(newPoint);
                        PointData newData = new PointData(newPoint, data.g + goffset, h, data);
                        openList.Add(newData);
                    }
                }
            }
        }
        //反向找出路徑
        for(PointData pathData = endData;pathData != null;)
        {
            Point point = pathData.point;
            if (MAP[point.x, point.y] == VISTIED)
            {
                MAP[point.x, point.y] = ON_PATH;
            }
            pathData = pathData.parent;
        }
    }

5.將遊戲地圖翻譯為尋路地圖

    //翻譯地圖的方法
    private static bool GenerateMap(Point s,Point e)
    {
        if (MAP[END_PNT.x, END_PNT.y] == WALL)
        {
            return false;
        }
        if (s.Equals(e))
        {
            return false;
        }
        MAX_PNT = new Point(GameManager._instance.w, GameManager._instance.h);
        START_PNT = s;
        END_PNT = e;
        MAP = new char[MAX_PNT.x, MAX_PNT.y];
        for(int i = 0; i < MAX_PNT.x; i++)
        {
            for(int j = 0; j < MAX_PNT.y; j++)
            {
                if(GameManager._instance.mapArray[i,j].elementContent == ElementContent.Door||
                   GameManager._instance.mapArray[i, j].elementContent == ElementContent.Enemy ||
                   GameManager._instance.mapArray[i, j].elementContent == ElementContent.BigWall ||
                   GameManager._instance.mapArray[i, j].elementContent == ElementContent.SmallWall ||
                   GameManager._instance.mapArray[i, j].elementState == ElementState.Marked ||
                   (GameManager._instance.mapArray[i, j].elementContent == ElementContent.Trap && GameManager._instance.mapArray[i,j].elementState == ElementState.Uncovered))
                {
                    MAP[i, j] = WALL;
                }
                else if(GameManager._instance.mapArray[i,j].elementState == ElementState.Uncovered ||
                    (GameManager._instance.mapArray[i,j].elementContent == ElementContent.Tool && ((ToolElement)GameManager._instance.mapArray[i,j]).isHide == false) ||
                    (GameManager._instance.mapArray[i, j].elementContent == ElementContent.Gold && ((GoldElement)GameManager._instance.mapArray[i, j]).isHide == false))
                {
                    MAP[i, j] = SPACE0;
                }
                else
                {
                    MAP[i, j] = SPACE1;
                }
            }
        }
        MAP[START_PNT.x, START_PNT.y] = START;
        MAP[END_PNT.x, END_PNT.y] = END;
        return true;
    }