迷宫小游戏,主要使用二维数组,栈Stack,自定义操作符operator,递归遍历等功能。
在VS2017中,新建控制台应用程序MazeDemo,选择.net framework 4.5。
一、新建一个枚举Direction,用于记录方向。Direction.cs代码如下:
using System;
namespace MazeDemo
{
///
/// 移动方向:默认迷宫单元格为尚未遍历(None)时,可以按照顺时针方向,
/// 以此可为:Up---Right---Down---Left。四个方向都遍历完成时,设置为All
///
public enum Direction
{
///
/// 无方向,尚未遍历。默认值
///
None = 0,
///
/// 向上
///
Up = 1,
///
/// 向右
///
Right = 2,
///
/// 向下
///
Down = 4,
///
/// 向左
///
Left = 8,
///
/// 全部遍历完成。上、右、下、左依次全部遍历
///
All = 16
}
}
二、新建迷宫单元格类MazeGrid,该类主要有属性:RowIndex(行索引),ColumnIndex(列索引),IsWall(是否是障碍墙),IsVisited(是否已访问),Direction(方向),以及==与!=操作符。MazeGrid.cs代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MazeDemo
{
///
/// 表示迷宫中的一个小格子
///
public class MazeGrid
{
///
/// 以行索引、列索引、是否是障碍来初始化一个网格
///
///
///
///
public MazeGrid(int rowIndex, int columnIndex, bool isWall)
{
this.RowIndex = rowIndex;
this.ColumnIndex = columnIndex;
this.IsWall = isWall;
this.Direction = Direction.None;
}
///
/// 方向
///
public Direction Direction { get; set; }
///
/// 网格所在的行的索引
///
public int RowIndex { get; set; }
///
/// 网格所在的列的索引
///
public int ColumnIndex { get; set; }
///
/// 是否是障碍墙,如果是障碍,将无法通过
///
public bool IsWall { get; set; }
///
/// 是否已访问
///
public bool IsVisited { get; set; }
///
/// 比较两个网格是否【在同一个位置】
///
///
///
///
public static bool operator ==(MazeGrid mg1, MazeGrid mg2)
{
return mg1.RowIndex == mg2.RowIndex && mg1.ColumnIndex == mg2.ColumnIndex;
}
///
/// 比较两个网格是否【不在同一个位置】
///
///
///
///
public static bool operator !=(MazeGrid mg1, MazeGrid mg2)
{
return mg1.RowIndex != mg2.RowIndex || mg1.ColumnIndex != mg2.ColumnIndex;
}
///
/// 重写相等比较
///
///
///
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
MazeGrid temp = obj as MazeGrid;
return this == temp;
}
///
/// 打印该网格对象
///
///
public override string ToString()
{
return $"{{Row={RowIndex},Column={ColumnIndex},Direction={Direction}}}";
}
}
}
三、新建关键迷宫逻辑类MazeUtil.cs。
比如 4行5列 网格如图示例,思路如下:按照上Up、右Right、下Down、左Left的顺序进行遍历,比如当前方向是Up,寻找后,将其置为Right继续遍历。当遍历完Left时,认为已完成,此时置为All。
源代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MazeDemo
{
///
/// 迷宫可以认为是一个N*M的行列式,也可以认为是一个二维数组,每个元素都是一个单元格MazeGrid
///
public class MazeUtil
{
///
/// 使用一个障碍墙的二维数组来初始化迷宫地图
///
/// 数组元素的值为1时,代表障碍墙,否则是可通过的
public MazeUtil(int[,] wallArray)
{
if (wallArray == null || wallArray.Length == 0)
{
throw new Exception("初始化数组不能为空");
}
Width = wallArray.GetLength(0);
Height = wallArray.GetLength(1);
//初始化地图
MazeArray = new MazeGrid[Width, Height];
for (int i = 0; i < Width; i++)
{
for (int j = 0; j < Height; j++)
{
MazeArray[i, j] = new MazeGrid(i, j, wallArray[i, j] == 1);
}
}
CurrentGrid = MazeArray[0, 0];
TargetGrid = MazeArray[Width - 1, Height - 1];
}
///
/// 迷宫的宽度,也就是二维数组的行数
///
public int Width { get; set; }
///
/// 迷宫的高度,也就是二维数组的列数
///
public int Height { get; set; }
///
/// 迷宫行列式,由 Width*Height 个网格组成
///
public MazeGrid[,] MazeArray { get; set; }
///
/// 当前网格,起点默认为左上角,即 MazeGrid[0,0]
///
public MazeGrid CurrentGrid { get; set; }
///
/// 终点:目标网格,默认为右下角,即 MazeGrid[Width-1,Height-1]
///
public MazeGrid TargetGrid { get; set; }
///
/// 栈,用于存储移动的路径:找到一个 未访问的 并且 不是障碍,就Push()入栈
///
public Stack
///
/// 以此遍历当前网格的上、右、下、左四个方向。
/// 如果遇到障碍 或者 已访问过,就尝试其他方向。否则就把 无障碍 并且 未访问的网格作为新的网格
///
///
public void MoveNext(MazeGrid mazeGrid)
{
Direction direction = Direction.All;
//按照上、右、下、左【顺时针】顺序以此遍历,当遍历完左Left后,则全部遍历完成,此时置方向为All
switch (mazeGrid.Direction)
{
case Direction.None://当是None时,默认向上Up遍历
mazeGrid.Direction = Direction.Up;
direction = Direction.Up;
break;
case Direction.Up://当是Up时,接着向右Right遍历
mazeGrid.Direction = Direction.Right;
direction = Direction.Right;
break;
case Direction.Right://当是Right时,接着向下Down遍历
mazeGrid.Direction = Direction.Down;
direction = Direction.Down;
break;
case Direction.Down://当是Down时,接着向左Left遍历
mazeGrid.Direction = Direction.Left;
direction = Direction.Left;
break;
case Direction.Left://当是Left时,说明四个方向全部遍历完了,置为All
mazeGrid.Direction = Direction.All;
direction = Direction.All;
break;
}
//对上、右、下、左四个方向进行处理【None 和 All不做处理】
switch (direction)
{
case Direction.Up:
if (mazeGrid.RowIndex - 1 >= 0)
{
MazeGrid upGrid = MazeArray[mazeGrid.RowIndex - 1, mazeGrid.ColumnIndex];
if (!upGrid.IsWall && !upGrid.IsVisited)
{
//如果不是障碍 并且 没有访问过
CurrentGrid = upGrid;
}
else
{
//尝试其他方向
MoveNext(CurrentGrid);
}
}
break;
case Direction.Right:
if (mazeGrid.ColumnIndex + 1 < Height)
{
MazeGrid rightGrid = MazeArray[mazeGrid.RowIndex, mazeGrid.ColumnIndex + 1];
if (!rightGrid.IsWall && !rightGrid.IsVisited)
{
//如果不是障碍 并且 没有访问过
CurrentGrid = rightGrid;
}
else
{
//尝试其他方向
MoveNext(CurrentGrid);
}
}
break;
case Direction.Down:
if (mazeGrid.RowIndex + 1 < Width)
{
MazeGrid downGrid = MazeArray[mazeGrid.RowIndex + 1, mazeGrid.ColumnIndex];
if (!downGrid.IsWall && !downGrid.IsVisited)
{
//如果不是障碍 并且 没有访问过
CurrentGrid = downGrid;
}
else
{
//尝试其他方向
MoveNext(CurrentGrid);
}
}
break;
case Direction.Left:
if (mazeGrid.ColumnIndex - 1 >= 0)
{
MazeGrid leftGrid = MazeArray[mazeGrid.RowIndex, mazeGrid.ColumnIndex - 1];
if (!leftGrid.IsWall && !leftGrid.IsVisited)
{
//如果不是障碍 并且 没有访问过
CurrentGrid = leftGrid;
}
else
{
//尝试其他方向
MoveNext(CurrentGrid);
}
}
break;
}
}
///
/// 查找路径
///
public void FindPath()
{
//如果当前网格没有移动到目标网格。
//这里如果遇到无法到达目标网格的障碍地图时,需要终止
while (CurrentGrid != TargetGrid)
{
if (CurrentGrid.IsVisited)
{
//如果当前网格全部访问完成,则出栈
if (CurrentGrid.Direction == Direction.All)
{
if (stack.Count > 0)
{
stack.Pop();//移除最后一次添加的
}
if (stack.Count > 0)
{
//获取倒数第二次添加的
CurrentGrid = stack.Peek();
}
else
{
Console.WriteLine("无路可走,请检查迷宫障碍设置...");
break;
}
}
else
{
//没有遍历完,继续遍历
MoveNext(CurrentGrid);
}
}
else
{
//如果未访问,则设置为已访问,同时添加入栈
CurrentGrid.IsVisited = true;
stack.Push(MazeArray[CurrentGrid.RowIndex, CurrentGrid.ColumnIndex]);
}
}
//将目标网格添加到顶部
if (stack.Count > 0)
{
stack.Push(TargetGrid);
}
}
///
/// 打印路径
///
public void PrintPath()
{
if (stack.Count == 0)
{
Console.WriteLine("无法到达目的网格,请检查迷宫地图设置...");
return;
}
//因第一个插入的元素是入口,栈是先进后出,入口反而成为最后元素。这里进行反转
IEnumerable
foreach (MazeGrid item in grids)
{
Console.WriteLine(item);
}
}
}
}
四、在项目默认的Program.cs添加如下测试用例。
分死路 和 可连通两种情况。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MazeDemo
{
class Program
{
static void Main(string[] args)
{
//测试无路可走
int[,] NoWayArray = new int[5, 5] { { 0, 0, 1, 0, 0 }, { 0, 1, 0, 1, 0 }, { 0, 1, 0, 1, 0 }, { 0, 1, 0, 1, 0 }, { 0, 0, 0, 1, 0 } };
MazeUtil mazeUtil = new MazeUtil(NoWayArray);
mazeUtil.FindPath();
mazeUtil.PrintPath();
Console.WriteLine();
//测试可到达
int[,] wallArray = new int[5, 5] { { 0, 0, 1, 0, 0 }, { 0, 1, 0, 1, 0 }, { 0, 1, 0, 0, 0 }, { 0, 1, 0, 1, 0 }, { 0, 0, 0, 1, 0 } };
mazeUtil = new MazeUtil(wallArray);
mazeUtil.FindPath();
mazeUtil.PrintPath();
Console.ReadLine();
}
}
}
五、程序运行结果如下图: