/// <summary> /// Initialize all squares in the mazeExtension (everything but trajectoryDistance). /// </summary> /// <param name="maze"></param> private void InitializeMazeExtension(Maze maze) { for (int i = 0; i < maze.XSize; i++) { for (int j = 0; j < maze.YSize; j++) { MazeSquare sq = maze[i, j]; MazeSquareExtension sqe = mazeExtension[i, j]; // extendedSquare: sqe.extendedSquare = sq; if (sq.isReserved) { // isDeadEnd: sqe.isDeadEnd = true; sqe.trajectoryDistance = -1; } else { // neighbors: sqe.neighbors = new List <MazeSquareExtension>(4); for (WallPosition wp = WallPosition.WP_MIN; wp <= WallPosition.WP_MAX; wp++) { MazeSquare sq2 = sq.NeighborSquare(wp); if (sq2 != null && !sq2.isReserved) { sqe.neighbors.Add(mazeExtension[sq2.XPos, sq2.YPos]); } } // Negative values mean: not initialized sqe.trajectoryDistance = int.MinValue; } } } }
/// <summary> /// Returns the center square of the given sqaure's partition. /// </summary> /// <param name="sq"></param> /// <returns></returns> private MazeSquare ReferenceSquare(MazeSquare sq) { // x and y index of the current square's partition int px = sq.XPos * xPartitions / xSize, py = sq.YPos * yPartitions / ySize; // x and y coordinates of the selected partition's center square int xCenter = (2 * px + 1) * xSize / (2 * xPartitions); int yCenter = (2 * py + 1) * ySize / (2 * yPartitions); // Apply this partition's offset. if (xOffCenter != null && yOffCenter != null) { xCenter += xOffCenter[px, py]; yCenter += yOffCenter[px, py]; } // If defined: Find the vertex closest to the given square. if (vertices != null) { MazeSquare closestVertex = vertices[0]; double closestDistance = Maze.Distance(sq, closestVertex); for (int i = 1; i < vertices.Length; i++) { double d = Maze.Distance(sq, vertices[i]); if (d < closestDistance) { closestVertex = vertices[i]; closestDistance = d; } } xCenter = closestVertex.XPos; yCenter = closestVertex.YPos; } return(new MazeSquare(xCenter, yCenter)); }
/// <summary> /// Setup method: Define the adjoining sqare on the other side of a wall. /// </summary> /// <param name="side"></param> /// <param name="neighbor"></param> internal void SetNeighbor(WallPosition side, MazeSquare neighbor) { this.neighbors[(int)side] = neighbor; }
public override bool[] PreferredDirections(MazeSquare sq) { // A measure of how many template squares' wall are already open. // When positive, this square's wall should also be open. double[] openTemplates = new double[4]; for (int x = sq.XPos % gridWidth; x < maze.XSize; x += gridWidth) { for (int y = sq.YPos % gridHeight; x < maze.YSize; x += gridHeight) { #region Skip the square that is being evaluated. if (x == sq.XPos && y == sq.YPos) { continue; } #endregion for (WallPosition p = WallPosition.WP_MIN; p <= WallPosition.WP_MAX; p++) { #region Skip this wall if it is the maze border or a reserved area border. bool skip = false; switch (p) { case WallPosition.WP_W: skip = (x == 0 || maze[x - 1, y].isReserved); break; case WallPosition.WP_E: skip = (x + 1 == maze.XSize || maze[x + 1, y].isReserved); break; case WallPosition.WP_N: skip = (y == 0 || maze[x, y - 1].isReserved); break; case WallPosition.WP_S: skip = (y + 1 == maze.YSize || maze[x, y + 1].isReserved); break; } if (skip) { continue; } #endregion double dx = x - sq.XPos, dy = y - sq.YPos, d = Math.Sqrt(dx * dx + dy * dy); double increment = 0; // Let the influence of farther regions decrease rather slowly. d = Math.Log(d, Math.E); switch (maze[x, y][p]) { case WallState.WS_OPEN: increment = +1.0 / d; break; case WallState.WS_CLOSED: case WallState.WS_OUTLINE: increment = -1.0 / d; break; default: case WallState.WS_MAYBE: increment = +0.1 / d; break; } openTemplates[(int)p] += increment; } } } bool[] result = new bool[4]; for (int p = 0; p < 4; p++) { result[p] = (openTemplates[p] >= 0); } return(result); }
/// <summary> /// Returns an array four boolean values, indexed by the WallPosition constants. /// The value is true when that wall is a preferred direction for paths from the given square. /// </summary> /// <param name="sq"></param> /// <returns></returns> public abstract bool[] PreferredDirections(MazeSquare sq);
public override bool[] PreferredDirections(MazeSquare sq) { bool[] result = new bool[4] { false, false, false, false }; #region Collect the distances of all neighbor squares (including diagonals) from the reference square. // Actually, this is the (absolute) difference between the distances of a neighbor square and sq itself. double[,] distances = new double[3, 3]; // The distance of the current square to the reference square should be approximated. MazeSquare sq0 = this.ReferenceSquare(sq); double d0 = Maze.Distance(sq, sq0); // Rounding to the next integer will approximate circles in 1 unit steps. if (approximateCircles) { d0 = Math.Round(d0); } for (int i = -1; i <= +1; i++) { for (int j = -1; j <= +1; j++) { // Note: this[x,y] is not defined outside the maze size. MazeSquare sq1 = new MazeSquare(sq.XPos + i, sq.YPos + j); distances[i + 1, j + 1] = (minimizeDistance ? +1 : -1) * Math.Abs(Maze.Distance(sq1, sq0) - d0); } } #endregion #region Find a range of distances including the current square and two of the neighbors. double d1 = double.MaxValue, d2 = double.MaxValue; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (i == 1 && j == 1) // Discard the current square. { continue; } if (distances[i, j] >= d2) // Discard distances greater than the current second best. { continue; } if (distances[i, j] >= d1) // This is a new second best distance. { d2 = distances[i, j]; } else // This is a new best distance. { d2 = d1; d1 = distances[i, j]; } } } #endregion #region Add the directions to the two best neighbors to the result. if (distances[1, 0] <= d2) // straight north { result[(int)WallPosition.WP_N] = true; } if (distances[0, 1] <= d2) // straight west { result[(int)WallPosition.WP_W] = true; } if (distances[1, 2] <= d2) // straight south { result[(int)WallPosition.WP_S] = true; } if (distances[2, 1] <= d2) // straight east { result[(int)WallPosition.WP_E] = true; } // For approximating a diagonal connection, we follow the closer path (unless this logic is inverted). if (distances[0, 0] <= d2) // north-west { result[(int)((distances[1, 0] < distances[0, 1] == followDiagonals) ? WallPosition.WP_N : WallPosition.WP_W)] = true; } if (distances[2, 0] <= d2) // north-east { result[(int)((distances[1, 0] < distances[2, 1] == followDiagonals) ? WallPosition.WP_N : WallPosition.WP_E)] = true; } if (distances[0, 2] <= d2) // south-west { result[(int)((distances[1, 2] < distances[0, 1] == followDiagonals) ? WallPosition.WP_S : WallPosition.WP_W)] = true; } if (distances[2, 2] <= d2) // south-east { result[(int)((distances[1, 2] < distances[2, 1] == followDiagonals) ? WallPosition.WP_S : WallPosition.WP_E)] = true; } #endregion return(result); }
/// <summary> /// Registers the given square as visited. /// </summary> /// <param name="sq"></param> /// <returns>a list of dead end squares</returns> public List <MazeSquare> Visit(MazeSquare sq) { List <MazeSquare> result = new List <MazeSquare>(); MazeSquareExtension sqe = mazeExtension[sq.XPos, sq.YPos]; // Don't process squares that have been visited before. if (sqe.isDeadEnd) { return(result); } // The visited square is marked as dead. sqe.isDeadEnd = true; int d = sqe.trajectoryDistance; if (d > 0) { sqe.trajectoryDistance *= -1; } else { d *= -1; } if (d == 0) { // This is the end square. No need to kill any more squares... return(result); } // Add neighbors to the list of uncertainSquares if their trajectory passed through the visited square. // That is the case if their distance is greater by one than the visited square's distance. for (int i = 0; i < sqe.neighbors.Count; i++) { MazeSquareExtension sqe2 = sqe.neighbors[i]; if (sqe2.trajectoryDistance == d + 1) { AddUncertainSquare(sqe2, -1); } } if (HarmlessConstellation(sqe)) { return(result); } // Add all squares whose trajectory depends on the ones already inserted. CollectUncertainSquares(); // The areas next to all confirmedSquares will receive an adjusted trajectoryDistance. ReviveConfirmedSquaresNeighbors(); // The remaining uncertainSquares with negative (invalid) trajectoryDistance are dead. for (int j = 0; j < uncertainSquares.Count; j++) { MazeSquareExtension sqe2 = uncertainSquares[j]; if (sqe2.trajectoryDistance < 0 && !sqe2.isDeadEnd) { sqe2.isDeadEnd = true; if (sqe2.extendedSquare.MazeId == sq.MazeId) { result.Add(sqe2.extendedSquare); } } } // Empty the internal lists. uncertainSquares.Clear(); confirmedSquares.Clear(); return(result); }
/// <summary> /// Returns true if the given square is marked as dead. /// A square is dead if /// a) it is a reserved area in the maze /// b) it has already been visited /// c) there is no trajectory leading to the maze's end square /// </summary> /// <param name="sq"></param> /// <returns></returns> public bool IsDead(MazeSquare sq) { return(mazeExtension[sq.XPos, sq.YPos].isDeadEnd); }
/// <summary> /// Convert all undecided walls to either closed or open. /// In the resulting maze, there must be a path from every square to every other square. /// There must not be any circles, i.e. the maze must have a tree-like structure. /// </summary> private void BuildMaze() { // We hold a number of active squares in a stack. // Make the initial capacity sufficient to hold all squares. // Stack <MazeSquare> stack = new Stack <MazeSquare>(xSize * ySize); List <MazeSquare> outlineSquares = new List <MazeSquare>(); List <WallPosition> outlineWalls = new List <WallPosition>(); #region Start with a single random cell in the stack. while (true) { int x = random.Next(xSize); int y = random.Next(ySize); MazeSquare sq = this[x, y]; if (sq.MazeId == this.MazeId) { sq.isConnected = true; stack.Push(sq); break; } } #endregion #region Extend the maze by visiting the cells next to those in the stack. while (stack.Count > 0) { List <WallPosition> unresolvedWalls = new List <WallPosition>((int)WallPosition.WP_NUM); MazeSquare sq0 = stack.Pop(); // Collect the unfixed walls of sq0. // for (WallPosition wp = WallPosition.WP_MIN; wp <= WallPosition.WP_MAX; wp++) { switch (sq0[wp]) { case WallState.WS_MAYBE: MazeSquare sq = sq0.NeighborSquare(wp); if (sq.isConnected || sq.MazeId != sq0.MazeId) { sq0[wp] = sq[MazeSquare.OppositeWall(wp)] = WallState.WS_CLOSED; } else { unresolvedWalls.Add(wp); } break; // WS_MAYBE case WallState.WS_OUTLINE: outlineSquares.Add(sq0); outlineWalls.Add(wp); break; // WS_OUTLINE } } // foreach wp // Discard this square if it has no unresolved walls. if (unresolvedWalls.Count == 0) { // Note: This is the only place that may end the loop. // If the stack is empty: Open one outline wall. if (stack.Count == 0) { while (outlineSquares.Count > 0) { // Select a random square with an outline wall. int p = random.Next(outlineSquares.Count); MazeSquare sq = outlineSquares[p]; WallPosition wp = outlineWalls[p]; outlineSquares.RemoveAt(p); outlineWalls.RemoveAt(p); if (sq[wp] == WallState.WS_OUTLINE) { sq[wp] = WallState.WS_MAYBE; stack.Push(sq); // This square will be used in the next iteration. break; // from while(outlineSquares) } } } continue; // no walls to choose from } // Add the current cell to the stack. // Note: Do this before replacing unresolvedWalls with preferredWalls. if (unresolvedWalls.Count > 1) { stack.Push(sq0); } // Use only preferred wall positions. if (unresolvedWalls.Count > 1 && irregularMazeShape != null && (random.Next(100) < irregularMazeShape.ApplicationPercentage(this.irregularity))) { bool[] preferredPositions = irregularMazeShape.PreferredDirections(sq0); List <WallPosition> preferredWalls = new List <WallPosition>(unresolvedWalls.Count); foreach (WallPosition p in unresolvedWalls) { if (preferredPositions[(int)p]) { preferredWalls.Add(p); } } if (preferredWalls.Count > 0) { unresolvedWalls = preferredWalls; } } // Choose one wall. WallPosition wp0 = unresolvedWalls[random.Next(unresolvedWalls.Count)]; MazeSquare sq1 = sq0.NeighborSquare(wp0); // Open the wall. sq0[wp0] = sq1[MazeSquare.OppositeWall(wp0)] = WallState.WS_OPEN; // Add the new cell to the stack. sq1.isConnected = true; stack.Push(sq1); } // while stack is not empty #endregion }
/// <summary> /// Returns the square diametrically opposed to the given square. /// </summary> public MazeSquare GetOpposedSquare(MazeSquare sq) { Rectangle bbox = this.GetBoundingBox(); return(this[bbox.Right - 1 - sq.XPos, bbox.Bottom - 1 - sq.YPos]); }