/// <summary> /// Build connections inside maze frame using hunt and kill algorithm. /// </summary> /// <param name="mazeFrame">Maze frame object.</param> /// <param name="huntAfterSegmentLength">Enter hunting mode after segment has grown to this length in world units, /// defaults to unlimited (uses smalles Scale value.</param> /// <param name="huntProbability">Probability of starting hunt when it arrives, defaults to 0.50.</param> protected void BuildMazeFrameUsingHuntAndKill(ref MazeFrame mazeFrame, float huntAfterSegmentLength = float.PositiveInfinity, float huntProbability = 0.50f) { if (mazeRandGen == null) { mazeRandGen = new ConsistentRandom(); } if (randGen == null) { randGen = new ConsistentRandom(); } // Build maze using hunt and kill /* 1) Make the initial node the current node and mark it as visited * 2) While there are unvisited nodes * 1) If the current node has any neighbours which have not been visited * 1) Choose randomly one of the unvisited neighbours * 2) Remove the wall between the current node and the chosen node * 3) Make the chosen node the current node and mark it as visited and add to targets * 2) Find random unvisited node next to visited node * 1) Make it the current node and mark it as visited and add to targets */ // Check start/end node active status if (!mazeFrame.StartNode.IsActive || !mazeFrame.EndNode.IsActive) { throw new System.Exception("Start/end nodes are required to be active."); } // Set storage of visit flags and create list for hunt to target int nNodesVisited = 0; Dictionary <string, bool> isVisited = new Dictionary <string, bool>(mazeFrame.Nodes.Count); foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive) { isVisited.Add(nd.Identifier, false); } else { isVisited.Add(nd.Identifier, true); nNodesVisited++; } } List <MazeNode> targets = new List <MazeNode>(Mathf.RoundToInt(mazeFrame.Nodes.Count / 2f)); // half of full maze should be more than enough // Ignore nodes without neighbors foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.AllNeighbors.Count == 0) { isVisited[nd.Identifier] = true; nNodesVisited++; } } // Determine huntAfterNConnections int huntAfterNConnections = Mathf.RoundToInt(huntAfterSegmentLength / Scale.ComponentMin()); //Initialize near startNode MazeNode currentNode = mazeFrame.StartNode; isVisited[currentNode.Identifier] = true; nNodesVisited++; targets.Add(currentNode); int count = 0; int nConnectionsMade = 0; while (nNodesVisited < mazeFrame.Nodes.Count) { // See if there are unvisited neighbors List <MazeNode> unvisitedNeighbors = new List <MazeNode>(); foreach (MazeNode nb in currentNode.AllNeighbors) { if (!isVisited[nb.Identifier]) { unvisitedNeighbors.Add(nb); } } if (nConnectionsMade >= huntAfterNConnections) { if (mazeRandGen.NextDouble() < huntProbability) { nConnectionsMade = 0; } } if (unvisitedNeighbors.Count > 0 && nConnectionsMade < huntAfterNConnections) { // Select random neighbor (2) MazeNode selNode = unvisitedNeighbors[mazeRandGen.Next(unvisitedNeighbors.Count)]; // Add as connected neighbor to current node object(3) and vice versa currentNode.AddConnectionByReference(selNode); // Switch to new node(4) and record visit currentNode = selNode; isVisited[currentNode.Identifier] = true; nNodesVisited++; targets.Add(currentNode); nConnectionsMade++; } else { // Find random unvisited node neighboring a visited node List <int> targetInd = new List <int>(targets.Count); for (int i = 0; i < targets.Count; i++) { targetInd.Insert(i, i); } bool found = false; Stack <MazeNode> targetsToRemove = new Stack <MazeNode>(); int huntCount = 0; while (!found) { // sample a random target node int randInd = targetInd[mazeRandGen.Next(targetInd.Count)]; MazeNode currTarget = targets[randInd]; // See if target has unvisited neighbors unvisitedNeighbors = new List <MazeNode>(); foreach (MazeNode nb in currTarget.AllNeighbors) { if (!isVisited[nb.Identifier]) { unvisitedNeighbors.Add(nb); } } // if it has no unvisited neighors, remove target from list and go again, otherwise, pick random unvisited neighbor if (unvisitedNeighbors.Count == 0) { targetInd.Remove(randInd); targetsToRemove.Push(currTarget); } else { // Select random neighbor and set as currentNode currentNode = currTarget; found = true; } // Reset connections counter nConnectionsMade = 0; // Safety check huntCount++; if (huntCount > targets.Count) { throw new System.Exception("Hunt did not terminate correctly."); } } // Remove nodes from targets that had no unvisisted neigbors while (targetsToRemove.Count > 0) { targets.Remove(targetsToRemove.Pop()); } } // Safety check count++; if (targets.Count == 0 || count > Mathf.Pow(mazeFrame.Nodes.Count, 2)) { throw new System.Exception("Hunt and kill loop did not terminate correctly."); } } // Sanity check foreach (MazeNode nb in mazeFrame.Nodes) { if (!isVisited[nb.Identifier]) { throw new System.Exception("Hunt and kill loop did not terminate correctly."); } } }
/// <summary> /// /// Build connections inside maze frame using recursive backtracking algorithm. /// </summary> /// <param name="mazeFrame">Maze frame object.</param> protected void BuildMazeFrameUsingRecursiveBacktracking(ref MazeFrame mazeFrame) { if (mazeRandGen == null) { mazeRandGen = new ConsistentRandom(); } if (randGen == null) { randGen = new ConsistentRandom(); } // Build maze using recursive backtracking // From wikipedia: /* 1) Make the initial node the current node and mark it as visited * 2) While there are unvisited nodes * 1) If the current node has any neighbours which have not been visited * 1) Choose randomly one of the unvisited neighbours * 2) Push the current node to the stack * 3) Remove the wall between the current node and the chosen node * 4) Make the chosen node the current node and mark it as visited * 2) Else if stack is not empty * 1) Pop a node from the stack * 2) Make it the current node */ // Check start/end node active status if (!mazeFrame.StartNode.IsActive || !mazeFrame.EndNode.IsActive) { throw new System.Exception("Start/end nodes are required to be active."); } // Set storage of visit flags and create stack for visisted nodes int nNodesVisited = 0; Dictionary <string, bool> isVisited = new Dictionary <string, bool>(mazeFrame.Nodes.Count); foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive) { isVisited.Add(nd.Identifier, false); } else { isVisited.Add(nd.Identifier, true); nNodesVisited++; } } Stack <MazeNode> nodeTrack = new Stack <MazeNode>(mazeFrame.Nodes.Count); // FIXME does preallocating help performance here? // Ignore nodes without neighbors foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.AllNeighbors.Count == 0) { isVisited[nd.Identifier] = true; nNodesVisited++; } } //Initialize near startNode MazeNode currentNode = mazeFrame.StartNode; isVisited[currentNode.Identifier] = true; nNodesVisited++; while (nNodesVisited < mazeFrame.Nodes.Count) { // See if there are unvisited neighbors List <MazeNode> unvisitedNeighbors = new List <MazeNode>(); foreach (MazeNode nb in currentNode.AllNeighbors) { if (!isVisited[nb.Identifier]) { unvisitedNeighbors.Add(nb); } } if (unvisitedNeighbors.Count != 0) { // Add current node to stack (1) nodeTrack.Push(currentNode); // Select random neighbor (2) //MazeNode selNode = RandomlySelectNodeWithBiasTowardsForwards(currentNode, unvisitedNeighbors); //Node selNode = RandomlySelectNodeWithBiasTowardsNode(currentNode, unvisitedNeighbors, endNode.AllNeighbors[0], 2,mazeRandGen); MazeNode selNode = unvisitedNeighbors[mazeRandGen.Next(unvisitedNeighbors.Count)]; // Add as connected neighbor to current node object(3) and vice versa currentNode.AddConnectionByReference(selNode); // Switch to new node(4) and record visit currentNode = selNode; isVisited[currentNode.Identifier] = true; nNodesVisited++; } else if ((unvisitedNeighbors.Count == 0) && (nodeTrack.Count != 0)) { currentNode = nodeTrack.Pop(); } // Safety check if ((nodeTrack.Count == 0) && (nNodesVisited > mazeFrame.Nodes.Count)) { throw new System.Exception("Recursive backtracking loop did not terminate correctly."); } } // Sanity check foreach (MazeNode nb in mazeFrame.Nodes) { if (isVisited[nb.Identifier] == false) { throw new System.Exception("Recursive backtracking loop did not terminate correctly."); } } }