/// <summary> /// Add the given square to the ordered list of uncertain squares. /// </summary> /// <param name="sqe"></param> private void AddUncertainSquare(MazeSquareExtension sqe, int behindPosition) { // Mark sqe's trajectoryDistance as uncertain. if (sqe.trajectoryDistance > 0) { sqe.trajectoryDistance *= -1; } AddSquare(sqe, behindPosition, uncertainSquares); }
/// <summary> /// Add the given square to the ordered list of uncertain squares. /// </summary> /// <param name="sqe"></param> private void AddConfirmedSquare(MazeSquareExtension sqe, int behindPosition) { // Mark sqe's trajectoryDistance as confirmed. if (sqe.trajectoryDistance < 0) { sqe.trajectoryDistance *= -1; } AddSquare(sqe, behindPosition, confirmedSquares); }
/// <summary> /// Update the lowestRelativesPenalty member variable. /// Consider all squares in the list of open paths. /// </summary> private void UpdateLowestPenalty() { this.lowestRelativesPenalty = double.MaxValue; foreach (MazeSquare sq in list) { MazeSquareExtension sqe = mazeExtension[sq.XPos, sq.YPos]; lowestRelativesPenalty = Math.Min(lowestRelativesPenalty, sqe.deadRelativesPenalty); } }
/// <summary> /// Initialize trajectoryDistance of all squares in the mazeExtension. /// </summary> /// <param name="maze"></param> private void InitializeTrajectoryDistances(Maze maze) { List <MazeSquareExtension> list = new List <MazeSquareExtension>(); InitializeEndpoints(maze, list); while (list.Count > 0) { // Take the first item from the list. MazeSquareExtension sqe1 = list[0]; list.RemoveAt(0); if (sqe1.trajectoryDistance <= 0) { for (int i = 0; i < sqe1.neighbors.Count; i++) { MazeSquareExtension sqe2 = sqe1.neighbors[i]; // Set the potential (negative) trajectory distance: // * sqe2's distance is one greater than sqe1's // * but only if this is better than the current potential if (sqe1.trajectoryDistance - 1 > sqe2.trajectoryDistance && !sqe2.isDeadEnd) // && sqe2.trajectoryDistance < 0 -- { // When first encountered: add sqe2 to the list. if (sqe2.trajectoryDistance == int.MinValue) { list.Add(sqe2); sqe2.trajectoryDistance = sqe1.trajectoryDistance - 1; } else { sqe2.trajectoryDistance = sqe1.trajectoryDistance - 1; list.Remove(sqe2); #region Find a new position p in the list int p; for (p = 0; p < list.Count; p++) { // The potential distance must be at least as good... if (sqe2.trajectoryDistance >= list[p].trajectoryDistance) { break; } } #endregion list.Insert(p, sqe2); } } } // Convert a potential distance to an actual distance. sqe1.trajectoryDistance *= -1; } } }
/// <summary> /// All neighbors whose current (incertain) trajectory is supported by the given (confirmed) square /// are also confirmed. /// (That is the case if their distances differ by 1. /// </summary> /// <param name="sqe"></param> private void ReviveUncertainNeighbors(MazeSquareExtension sqe) { for (int i = 0; i < sqe.neighbors.Count; i++) { MazeSquareExtension sqe2 = sqe.neighbors[i]; if (-sqe2.trajectoryDistance == sqe.trajectoryDistance + 1 && !sqe2.isDeadEnd) { AddConfirmedSquare(sqe2, -1); ReviveUncertainNeighbors(sqe2); } } }
private void InitializeEndpoints(Maze maze, List <MazeSquareExtension> endSquares) { // Mark start square as visited. MazeSquareExtension sqeStart = mazeExtension[maze.StartSquare.XPos, maze.StartSquare.YPos]; sqeStart.isDeadEnd = true; sqeStart.trajectoryDistance = -1; // Add end square to the list. MazeSquareExtension sqeEnd = mazeExtension[maze.EndSquare.XPos, maze.EndSquare.YPos]; sqeEnd.trajectoryDistance = 0; endSquares.Add(sqeEnd); }
/// <summary> /// Adjust trajectory distances of the uncertainSquares. /// </summary> private void CollectUncertainSquares() { // Note: Initially, uncertainSquares is an ordered list of the neighbors of a visited square. // As our search for a new trajectory continues, more squares will be marked uncertain // and added to this list. The sort order will be preserved. for (int i = 0; i < uncertainSquares.Count; i++) { MazeSquareExtension sqe1 = uncertainSquares[i]; if (sqe1.trajectoryDistance > 0) { // This square's trajectory has been confirmed. It is not uncertain any more. continue; } if (sqe1.isDeadEnd) { // No chance to revive an already dead square! continue; } // We need to find a neighbor that gives this square a new trajectory. int requiredNeighborDistance = -sqe1.trajectoryDistance - 1; for (int j = 0; j < sqe1.neighbors.Count; j++) { MazeSquareExtension sqe2 = sqe1.neighbors[j]; if (sqe2.trajectoryDistance == requiredNeighborDistance) { // We have confirmed that sqe1 has a neighbor sqe2 giving it a new trajectory. AddConfirmedSquare(sqe1, -1); // Immediately revive any neighbors supported by sqe1. ReviveUncertainNeighbors(sqe1); break; // from for (j) } else if (sqe2.trajectoryDistance > requiredNeighborDistance) { // Add this square to the list of uncertainSquares. AddUncertainSquare(sqe2, i); } } } }
/// <summary> /// Create a DeadEndChecker, based on the given Maze. /// </summary> /// <param name="maze"></param> public DeadEndChecker(Maze maze) { this.mazeExtension = new MazeSquareExtension[maze.XSize, maze.YSize]; #region Create mazeExtension[i, j] for (int i = 0; i < maze.XSize; i++) { for (int j = 0; j < maze.YSize; j++) { mazeExtension[i, j] = new MazeSquareExtension(); } } #endregion // Set up the mazeExtension InitializeMazeExtension(maze); InitializeTrajectoryDistances(maze); }
/// <summary> /// Assign valid (positve) trajectory distances to all uncertain neighbors of the confirmedSquares. /// </summary> private void ReviveConfirmedSquaresNeighbors() { // Note: The confirmedSquares list is ordered by increasing trajectoryDistance. // Additional items will be added while we traverse the adjoining area. for (int i = 0; i < confirmedSquares.Count; i++) { MazeSquareExtension sqe1 = confirmedSquares[i]; for (int j = 0; j < sqe1.neighbors.Count; j++) { MazeSquareExtension sqe2 = sqe1.neighbors[j]; if (sqe2.trajectoryDistance < 0 && !sqe2.isDeadEnd) { sqe2.distanceChanged = (-sqe2.trajectoryDistance != sqe1.trajectoryDistance + 1); sqe2.trajectoryDistance = sqe1.trajectoryDistance + 1; AddConfirmedSquare(sqe2, i); } } } }
/// <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 true if no squares could have possibly been killed by visiting the given square. /// </summary> /// <param name="sqe"></param> /// <returns></returns> /// /// +---+---+---+ +---+---+---+ /// | | | | | | o | x | In these diagrams, sqe is the center square. /// +---+---+---+ +---+---+---+ "x" are dead squares; "o" are alive squares; the rest is irrelevant /// | x | x | x | | x | x | o | These two constellations are critical (not harmless). /// +---+---+---+ +---+---+---+ /// | | | | | | | | The straight constellation is harmless if one of the parallel lines /// +---+---+---+ +---+---+---+ is completely dead. /// private bool HarmlessConstellation(MazeSquareExtension sqe) { int x = sqe.extendedSquare.XPos, y = sqe.extendedSquare.YPos; int w = mazeExtension.GetUpperBound(0); int h = mazeExtension.GetUpperBound(1); #region Identify straight lines. bool deadW = (x == 0 || mazeExtension[x - 1, y].isDeadEnd); bool deadE = (x == w || mazeExtension[x + 1, y].isDeadEnd); bool deadN = (y == 0 || mazeExtension[x, y - 1].isDeadEnd); bool deadS = (y == h || mazeExtension[x, y + 1].isDeadEnd); bool deadNW = (x == 0 || y == 0 || mazeExtension[x - 1, y - 1].isDeadEnd); bool deadNE = (x == w || y == 0 || mazeExtension[x + 1, y - 1].isDeadEnd); bool deadSW = (x == 0 || y == h || mazeExtension[x - 1, y + 1].isDeadEnd); bool deadSE = (x == w || y == h || mazeExtension[x + 1, y + 1].isDeadEnd); if (deadW && deadE) { if (deadN || deadS) { return(true); } return(false); } if (deadN && deadS) { if (deadW || deadE) { return(true); } return(false); } #endregion #region Identify angled lines. if (deadNW && !deadN && !deadW && (deadS || deadE)) { return(false); } if (deadNE && !deadN && !deadE && (deadS || deadW)) { return(false); } if (deadSW && !deadS && !deadW && (deadN || deadE)) { return(false); } if (deadSE && !deadS && !deadE && (deadN || deadW)) { return(false); } #endregion return(true); }
private void AddSquare(MazeSquareExtension sqe, int behindPosition, List <MazeSquareExtension> list) { if (list.Count > 8 * mazeExtension.Length) { throw new Exception("internal list grows too long"); } int key = sqe.trajectoryDistance; if (key < 0) { key = -key; } // The key needs to be compared with the squares in the index range a..b int a = behindPosition + 1, b = list.Count - 1; // Empty region. if (b < a) { list.Add(sqe); return; } int keyA = list[a].trajectoryDistance; if (keyA < 0) { keyA = -keyA; } int keyB = list[b].trajectoryDistance; if (keyB < 0) { keyB = -keyB; } // The square fits at the end of the list. if (keyB <= key) { list.Add(sqe); return; } // The square fits at the start of the list. if (keyA >= key) { list.Insert(a, sqe); return; } // Find a position for insertion into the list. // keyA < key < keyB while (a < b) { int m = (a + b) / 2; int keyM = list[m].trajectoryDistance; if (keyM < 0) { keyM = -keyM; } if (keyM > key) { b = m; } else if (keyM < key) { a = m + 1; } else { a = m + 1; break; } } list.Insert(a, sqe); }
/// <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); }