I have an enum which represents the current state of a cell within a maze, something like this:
[Flags]
public enum CellInfo : ushort
{
None = 0,
NorthWall = 1,
EastWall = 2,
SouthWall = 4,
WestWall = 8,
AllWalls = NorthWall | EastWall | SouthWall | WestWall
}
With it I can track the current state of each cells' walls pretty easily, for example if I want to open a wall east.
var myCell = CellInfo.AllWalls; // initialise a cell with all walls up
myCell ^= CellInfo.EastWall; // east wall now down!
Easy! But, each cell is next to another cell, which has its own wall, so in actual fact I always know im moving from cell a
to cell b
, and for arguments sake lets say this is an eastwards movement, I need to take down the east wall of a
and the west wall of b
:
var a = CellInfo.AllWalls;
var b = CellInfo.AllWalls;
// assume a->b is eastwards
a ^= CellInfo.EastWall;
b ^= CellInfo.WestWall;
Now, I have a generic way of handling this movement, however I don't like it, its got a code smell! Too many if
statements, and I was wondering if I've missed something obvious - some bitwise logic I may not have spotted perhaps? I have pasted my KnockDownWall
method below and it takes the grid of cells (CellInfo[,]
) along with the grid position of a
and b
expressed as Tuple<int,int>
(x,y position)
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;
}
}
}
UPDATE
Plenty of useful info so farin answers - however I fear I oversimplified the problem to start with. CellInfo
has many more values which store such things as N/S/E/W border (Allows me freedom to shape maze, and fence off areas), N/S/E/W solution and backtrack info - this all feeds into the model which draws the maze. Treating everything as cells rather than walls has some benefit.
The problem with your approach to modeling the maze is that each inner wall is represented twice, requiring significant effort for maintaining internal consistency. A better approach is to expand the maze by an extra row at the south side and an extra column at the east side, and use two wall types instead of four - i.e. the wall on the north and the wall on the east side of the cell:
This picture shows a grid of 2 rows by 3 columns, extended to 3 rows by 4 columns. Red lines show the walls to be taken down.
In order to deal with walls in all four directions from a specific cell, make a method that recognizes south and west walls as north and east walls of the adjacent cell.
Finally, you could make two 2D arrays of bool
s - one representing horizontal walls, and one representing vertical walls. This way the individual cells would not have to store their own walls at all, and the maze would always be internally consistent:
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];
}
}
If you must work with the current design, but would like to get rid of the conditionals in the KnockDownWall
, you could make two 3×3 arrays with values of CellInfo.XyzWall
from your implementation, and index them with offsetTarget.Item1+1, offsetTarget.Item2+1
:
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);
cells[target.Item1, target.Item2] ^= targetLookup[offsetTarget.Item1+1, offsetTarget.Item2+1];
cells[neighbour.Item1, neighbour.Item2] ^= neighborLookup[offsetTarget.Item1+1, offsetTarget.Item2+1];
}