您的位置:首页 > 编程语言 > Go语言

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

2016-02-06 00:28 387 查看
附上全文连接:

[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
.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。并将对面节点的周边符合要求的邻节点存入列表中。

最后将这个已将访问了的节点移除列表。

当邻节点列表没有数据、循环退出的时候就是迷宫创建完成。

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 prim C# maze 优化