我有一个enum,它代表迷宫中一个细胞的当前状态,如下所示:
[Flags]
public enum CellInfo : ushort
{
None = 0,
NorthWall = 1,
EastWall = 2,
SouthWall = 4,
WestWall = 8,
AllWalls = NorthWall | EastWall | SouthWall | WestWall
}
有了它,我可以很容易地跟踪每个细胞壁的当前状态,例如,如果我想向东开一堵墙。
var myCell = CellInfo.AllWalls; // initialise a cell with all walls up
myCell ^= CellInfo.EastWall; // east wall now down!
轻松!但是,每个单元格都在另一个单元格的旁边,这个单元格有自己的墙,所以实际上我总是知道从单元格a
移动到单元格b
,为了论证,我们可以说它是向东的运动,我需要取下a
的东墙和b
的西墙:
var a = CellInfo.AllWalls;
var b = CellInfo.AllWalls;
// assume a->b is eastwards
a ^= CellInfo.EastWall;
b ^= CellInfo.WestWall;
现在,我有一种处理这种运动的通用方法,但是我不喜欢它,它有一个代码味道!太多if
陈述,我想知道我是否错过了一些明显的东西 - 我可能没有发现一些按位逻辑?我已经粘贴了下面的KnockDownWall
方法,它将单元格网格(CellInfo[,]
)与a
和b
的网格位置一起表达作为Tuple<int,int>
(x,y位置)
protected static void KnockDownWall(CellInfo[,] cells, Tuple<int, int> target, Tuple<int, int> neighbour)
{
var offsetTarget = Tuple.Create(neighbour.Item1 - target.Item1, neighbour.Item2 - target.Item2);
if(offsetTarget.Item1 == 0)
{
if(offsetTarget.Item2 == -1)
{
cells[target.Item1, target.Item2] ^= CellInfo.NorthWall;
cells[neighbour.Item1, neighbour.Item2] ^= CellInfo.SouthWall;
}
else
{
cells[target.Item1, target.Item2] ^= CellInfo.SouthWall;
cells[neighbour.Item1, neighbour.Item2] ^= CellInfo.NorthWall;
}
}
else
{
if (offsetTarget.Item1 == -1)
{
cells[target.Item1, target.Item2] ^= CellInfo.WestWall;
cells[neighbour.Item1, neighbour.Item2] ^= CellInfo.EastWall;
}
else
{
cells[target.Item1, target.Item2] ^= CellInfo.EastWall;
cells[neighbour.Item1, neighbour.Item2] ^= CellInfo.WestWall;
}
}
}
更新
大量有用的信息到目前为止的答案 - 但是我担心我开始时过度简化了问题。 CellInfo
有更多的值存储N / S / E / W边界(允许我自由塑造迷宫,围栏区域),N / S / E / W解决方案和回溯信息 - 这一切进入吸引迷宫的模型。把所有东西当作细胞而不是墙壁来处理都有一些好处。
答案 0 :(得分:2)
您对迷宫进行建模的方法存在的问题是,每个内壁都有两次表示,需要付出很大努力才能保持内部一致性。一个更好的方法是将迷宫扩大到南侧的额外行和东侧的额外列,并使用两种墙类型而不是四种 - 即北面的墙和单元格东侧的墙:
此图显示了2行乘3列的网格,扩展为3行4列。红线显示要拆除的墙壁。
为了处理来自特定细胞的所有四个方向的墙壁,制作一种方法,将南墙和西墙识别为相邻单元格的北墙和东墙。
最后,你可以制作两个Car MyCar = new Car();
MyCar.GetCarByID(5);
s的2D阵列 - 一个代表水平墙,一个代表垂直墙。通过这种方式,单个细胞根本不必存储自己的墙壁,迷宫始终在内部保持一致:
bool
如果您必须使用当前设计,但想要摆脱private const int rows = 10;
private const int cols = 15;
private bool northWalls[,] = new bool[rows+1, cols+1];
private bool westWalls[,] = new bool[rows+1, cols+1];
public bool HasWall(int cellRow, int cellCol, CellInfo dir) {
switch(dir) {
case CellInfo.NorthWall:
return northWalls[cellRow,cellCol];
case CellInfo.WestWall:
return westWalls[cellRow,cellCol];
case CellInfo.EasthWall:
return westWalls[cellRow,cellCol+1];
case CellInfo.SouthhWall:
return northWalls[cellRow+1,cellCol];
}
}
中的条件,您可以从实现中创建两个值为KnockDownWall
的3×3数组,并且用CellInfo.XyzWall
索引它们:
offsetTarget.Item1+1, offsetTarget.Item2+1
答案 1 :(得分:2)
你的模型说每个细胞都有4个墙,都是自己的。但这并不准确,因为这些墙可能与其他细胞共享。你看到了模型问题的痛苦点。此外,您的单元格没有任何其他信息(从您向我们展示的内容):您真的只有墙壁。他们只有一个属性:无论是上升还是被击倒。
我建议你的模型应该基于墙,而不是单元格。
这是你的细胞和墙壁的ASCII表示,正如我想象的那样。
─ ─ ─ ─ ─ ─ // wall row 0, walls 0-5
│ │ │ │ │ │ │ // wall row 1, walls 0-6; cell row 0, cells 0-5
─ ─ ─ ─ ─ ─ // wall row 2, walls 0-5
│ │ │ │ │ │ │ // wall row 3, walls 0-6; cell row 1, cells 0-5
─ ─ ─ ─ ─ ─ // wall row 4, walls 0-5
请注意,交替行的墙数不同。解决这个问题的一种方法是使用锯齿状数组而不是多维数组。
bool[][] walls = // whatever
所以要击倒第1行的最后一个垂直墙,只需:
walls[1][6] = false;
或切换它:
walls[1][6] = !walls[1][6];
如果您需要计算walls
相对于单元格的索引(例如,从单元格1,2
向东的墙),您可以制作一个简单的方法来执行此操作。
如果你有关于每个单元格和墙壁存储的属性,你可以这样:
Wall[][] walls;
Cell[,] cells;
Wall
可能有Active
或Standing
属性,您可以切换。如果Wall
和Cell
个对象知道自己的位置,您甚至可以拥有Cell.Walls
,Cell.NorthWall
(等)或Wall.Cells
属性周围的元素。