[Algorithm]Maze Prim算法与A*寻路算法(中)

来源:互联网 发布:淘宝网秋冬运动套装 编辑:程序博客网 时间:2024/05/29 09:40

附上全文连接:
[Algorithm]Maze Prim算法与A*寻路算法(上)
[Algorithm]Maze Prim算法与A*寻路算法(中)
[Algorithm]Maze Prim算法与A*寻路算法(下)

文章中的所有源码下载链接附在“[Algorithm]Maze Prim算法与A*寻路算法(上) ”文章中。

上一篇中大致讲了下内容,接下来将要讲的是Maze Prim算法。
先附上一段Wiki的讲解:https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Prim.27s_algorithm

其实算法是思路很简单就是不断的找墙拆墙的过程。大致流程如下:
1. 让迷宫所有节点均为墙,即没有路可走。
2. 选择一个偶数列和偶数行的节点设置为通路,然后把它的邻节点(邻墙)(上下左右,不要斜对角的=。=)存入邻节点列表中。
3. 如果邻节点列表中有数据,则执行以下循环。
——1. 从列表中随机选择一个邻节点,如果它相对与对应通路节点的对面节点不是通路的话,则执行以下循环。
————1.把该节点和对面节点设置为通路。
————2.把对面节点的邻节点加入列表。
——2.如果对面的节点已经是通路,则将其移除列表。

程序的大致流程如上,接下来就源码讲解,因为C# 控制台的版本可以直接运行,则以这个工程讲解。
“MazeCSharpTest”工程中只用看“Program.cs”即可,所有实现均在这个文件中。

需要定义节点类型枚举“WallType”和节点信息类“WallInfo”。

public enum WallType{  None = 0,  Up = 1,  Down = 2,  Left = 4,  Right = 8,  UpDown = 3,  LeftRight = 12,  LeftUp = 5,  LeftDown = 6,  RightUp = 9,  RightDown = 10,  LeftUpRight = 13,  UpRightDown = 11,  RigthDownLeft = 14,  DownLeftUp = 7,  LeftUpRightDown = 15}public class WallInfo{    public int x = -1;    public int y = -1;    /// <summary>    /// 是否有墙    /// </summary>    public bool hasWall = true;    /// <summary>    /// 是否是寻路路径点    /// </summary>    public bool isFindRoad = false;    /// <summary>    /// 墙的类型    /// </summary>    public WallType type = WallType.None;    /// <summary>    /// 状态    /// </summary>    public int flag = 0;    public void Set(int x, int y, bool hasWall = true, int flag = 0, bool isFindRoad = false, WallType type = WallType.None)    {        this.x = x;        this.y = y;        this.hasWall = hasWall;        this.type = type;        this.flag = flag;        this.isFindRoad = isFindRoad;    }    public override string ToString()    {        return "[WallInfo:x=" + x + " y=" + y + " hasWall=" + hasWall + " type=" + type + " flag=" + flag + "]";    }}private class BlockWallInfo{    public WallInfo info { get; private set; }    public WallType wallDirect { get; private set; }    public BlockWallInfo(WallInfo info, WallType wallDirect)    {        this.info = info;        this.wallDirect = wallDirect;    }    public override string ToString()    {        return "[BlockWallInfo:wallDirect=" + wallDirect + " info=" + info + "]";    }}

“WallType”枚举用于对墙的类型进行分类,如该墙的上左下有邻墙的话,则该墙的类型为“DownLeftUp”,更加形象点就是“┤”。
“WallInfo”中对“ToString”进行了重写,这有便于查看数据。“flag ”未使用,为扩展信息,当然如果有需要,可以在这个类中继续添加。“isFindRoad ”用于寻路后的路径节点,现不做讲解。
“BlockWallInfo”用于创建Prim迷宫时使用的墙信息。“wallDirect ”为对面的对面节点所在方向。

下面的创建流程中的主要代码。
1.初始化迷宫,将节点全部初始化为墙,且更新节点数及坐标。

private void InitList(){    int nLerp = _x * _y - _maze.Count;    if (nLerp > 0)    {//添加        while (nLerp > 0)        {            _maze.Add(new WallInfo());            --nLerp;        }    }    else if (nLerp < 0)    {//移除        _maze.RemoveRange(0, -nLerp);    }    //初始化    //设置第一个    _maze[0].Set(0, 0);    //设置其他    for (int n = 1, length = _maze.Count; n < length; n++)    {        _maze[n].Set(n % _x, n / _x);    }}

通过这种做法,可以重复使用前面创建的对象,起到优化的作用。
2.创建迷宫。

/// <summary>/// 创建迷宫/// </summary>/// <param name="w">宽(>0)</param>/// <param name="h">高(>0)</param>/// <param name="randSeed">随机数</param>/// <returns>是否创建成功</returns>public bool Create(int w, int h, int randSeed){    if (w < 1 || h < 1)    {        return false;    }    //带墙的格子数    _x = w * 2 + 1;    _y = h * 2 + 1;    //初始化墙列表    InitList();    //初始化随机数    if (_randSeed != randSeed || _rand == null)    {        _rand = new Random(randSeed);    }    //生成迷宫    RandomPrim();    //    //更新类型    UpdateType();    return true;}private void UpdateType(){    for (int y = 0; y < _y; y++)    {        for (int x = 0; x < _x; x++)        {            if (!GetWallInfo(x, y).hasWall)            {                GetWallInfo(x, y).type = WallType.None;                continue;            }            WallType type = WallType.None;            //Left            if (x - 1 >= 0 && GetWallInfo(x - 1, y).hasWall)            {                type |= WallType.Left;            }            //Right            if (x + 1 < _x && GetWallInfo(x + 1, y).hasWall)            {                type |= WallType.Right;            }            //Down            if (y - 1 >= 0 && GetWallInfo(x, y - 1).hasWall)            {                type |= WallType.Down;            }            //Up            if (y + 1 < _y && GetWallInfo(x, y + 1).hasWall)            {                type |= WallType.Up;            }            GetWallInfo(x, y).type = type;        }    }}

输入的宽和高都不是实际的,实际的宽和高都是乘2加1了的。
将Prim算法单独写在一个函数中的好处是,如果还有其他的迷宫算法的话,可以再写一个方法将其替换即可。总之,这个函数起到的作用就是处理已经初始化好的迷宫列表“_maze”。
“UpdateType”作用是在创建完成之后更新节点信息类中的节点类型,位运算还是很实用的:-D。

3.接下来是重点中的重点,RandomPrim算法的实现。

/// <summary>/// 随机普里姆算法生成迷宫/// </summary>private void RandomPrim(){    //墙数组下标极限    int nWidthLimit = _x - 2;    int nHeightLimit = _y - 2;    //起点    int nTarX = 1;    int nTarY = 1;    //标记起点    GetWallInfo(nTarX, nTarY).hasWall = false;    List<BlockWallInfo> neighBlockWallInfo = new List<BlockWallInfo>();    XmWallInfo tempWall = null;    //记录邻墙,初始化需要拆的墙    if (nTarY < nHeightLimit)    {        tempWall = GetWallInfo(nTarX, nTarY + 1);//Up        if (tempWall != null)        {            neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Up));//记录邻墙及其所在方向        }    }    if (nTarY > 1)    {        tempWall = GetWallInfo(nTarX, nTarY - 1);//Down        if (tempWall != null)        {            neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Down));        }    }    if (nTarX < nWidthLimit)    {        tempWall = GetWallInfo(nTarX + 1, nTarY);//Right        if (tempWall != null)        {            neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Right));        }    }    if (nTarX > 1)    {        tempWall = GetWallInfo(nTarX - 1, nTarY);//Left        if (tempWall != null)        {            neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Left));        }    }    int nIndex = 0;    BlockWallInfo tempBlackWall = null;    XmWallInfo tempWall2 = null;    while (neighBlockWallInfo.Count > 0)//是否还有未拆的墙    {        //随机选择一面墙        nIndex = _rand.Next(neighBlockWallInfo.Count);        //找出此墙对面的目标格        tempBlackWall = neighBlockWallInfo[nIndex];        switch (tempBlackWall.wallDirect)        {            case XmWallType.Up:                nTarX = tempBlackWall.info.x;                nTarY = tempBlackWall.info.y + 1;                break;            case XmWallType.Down:                nTarX = tempBlackWall.info.x;                nTarY = tempBlackWall.info.y - 1;                break;            case XmWallType.Left:                nTarX = tempBlackWall.info.x - 1;                nTarY = tempBlackWall.info.y;                break;            case XmWallType.Right:                nTarX = tempBlackWall.info.x + 1;                nTarY = tempBlackWall.info.y;                break;        }        tempWall = GetWallInfo(nTarX, nTarY);//获取目标格        if (tempWall != null && tempWall.hasWall)        {            //连通目标格            tempWall.hasWall = false;            tempBlackWall.info.hasWall = false;            //添加目标格的邻格            if (nTarY > 1)//Down            {                tempWall = GetWallInfo(nTarX, nTarY - 1);//获取目标格下面的邻墙                if (tempWall != null && tempWall.hasWall)                {                    tempWall2 = GetWallInfo(nTarX, nTarY - 2);//获取目标格下面邻墙下面的目标格                    if (tempWall2 != null && tempWall2.hasWall)                    {                        neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Down));//添加邻墙及其方向                    }                }            }            if (nTarY < nHeightLimit)//Up            {                tempWall = GetWallInfo(nTarX, nTarY + 1);                if (tempWall != null && tempWall.hasWall)                {                    tempWall2 = GetWallInfo(nTarX, nTarY + 2);                    if (tempWall2 != null && tempWall.hasWall)                    {                        neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Up));                    }                }            }            if (nTarX < nWidthLimit)//Right            {                tempWall = GetWallInfo(nTarX + 1, nTarY);                if (tempWall != null && tempWall.hasWall)                {                    tempWall2 = GetWallInfo(nTarX + 2, nTarY);                    if (tempWall2 != null && tempWall2.hasWall)                    {                        neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Right));                    }                }            }            if (nTarX > 1)//Left            {                tempWall = GetWallInfo(nTarX - 1, nTarY);                if (tempWall != null && tempWall.hasWall)                {                    tempWall2 = GetWallInfo(nTarX - 2, nTarY);                    if (tempWall2 != null && tempWall2.hasWall)                    {                        neighBlockWallInfo.Add(new BlockWallInfo(tempWall, XmWallType.Left));                    }                }            }        }        //移除此墙        neighBlockWallInfo.RemoveAt(nIndex);        tempWall = null;        tempWall2 = null;        tempBlackWall = null;    }}

算法中主要有一个需要访问的邻节点列表“neighBlockWallInfo ”。
首先会将(1,1)坐标的节点设置为通路即“hasWall”为false,然后将其周边邻节点添加到列表中。这样就初始化了邻节点列表。
然后循环判断列表是否为空,如果不为空,则随机取一个邻节点,并通过“BlockWallInfo ”中的“wallDirect”属性,取得对面的节点。
如果对面的节点不为空且“hasWall”为true即未打通,则打通该邻节点及对面的节点,均设置“hasWall”为false。并将对面节点的周边符合要求的邻节点存入列表中。
最后将这个已将访问了的节点移除列表。
当邻节点列表没有数据、循环退出的时候就是迷宫创建完成。

程序大致就是酱紫!如果有更好的实现方式或疑问请留言~

这里写图片描述

0 0