Hi, I'm working on a flood fill algorithm and half the time pressing the play button causes Unity to crash. I'm guessing it goes into an infinite loop but I can't figure out when. I'm using this on a 2D grid of ints that is filled with 0's and 1's. Code (CSharp): int[,] locations; static int x = 200; static int y = 200; private List<Area> areaList; void Start() { locations= new int[x, y]; CreateAreas(); } private void CreateAreas() { for (int i = 0; i < x; i++) { for (int j = 0; j < y; j++) { if (locations[i, j] == 0) { Area plate= CreateArea(i, j,new Area()); areaList.Add(plate); } } } } private Area CreateArea(int i, int j, Area plate) { if (i > x - 1 || i < 0) return plate; if (j > y - 1 || j < 0) return plate; if (locations[i, j] == 0) { locations[i, j] = 2; plate.addPiste(i, j); CreateArea(i + 1, j, plate); CreateArea(i, j + 1, plate); CreateArea(i - 1, j, plate); CreateArea(i, j - 1, plate); } return plate; } } The Area-class looks like this: Code (CSharp): public class Area { private List<int[]> list; public Area() { list= new List<int[]>(); } public void addPoint(int x, int y) { list.Add(new int[] { x, y }); } } The goal would be to have a bunch of "Area"-objects that would contain all the points that belong in that area and I'm certain it's this recursive part that's causing the trouble. Thanks for any help!
Am I reading this right? You are making a 200x200 array, each cell of which has a 200x200 count of list objects? If so, you're proposing to do about 1.6 billion operations. Even if you have enough RAM to hold all this (you probably do not!) it's gonna take a pretty long time to run. Reduce your X,Y size to 5 and 5 and see if it finishes. To understand recursion, you must first understand recursion.
If I'm reading the code properly, it should only make one area for the whole array, which contains a 200x200 length list. But that's still not quick, and that includes 20 or so resize operations. Which could still freeze up a system and make it appear to be an infinite loop. Working on a small array first is definitely the way to start.
In my "game" there is this x*y grid of 0's that is split into smaller areas that are separated by lines of 1's and I thought using recursion would be an "easy" way of creating objects that would contain the coordinates of all the points in a certain area. I have done a few recursion programs that have worked so in theory I should at least have an idea of what I'm doing. Plus I think recursion is quite fascinating and I'd like to use it to learn more about it although I might have to use some other way to get what I want. Each successful run takes about a second (and the program does some other stuff too) and in the end I have between 10 and 40 Area-objects each of which has between 1 and 10 000 points (The total is less than 40 000). Also looking at the task manager before and after running the memory use increases by about 2MB. But then again, I have no idea what it does while running. Using a similar code in a project outside Unity on a 200x200 array causes stack overflow exception. Might that also be the case here and if so, why wouldn't Unity tell me about it even though it has thrown stack overflows in my previous attempt?
Recursion is fine for many things, but I don't think floodfill is one of them. For a typical floodfill I just use iterations and keep track whether anything changed. So, where 0 is a starting point, -1 is undefined and everything under -1 is not accessible: Code (csharp): // Assuming data, width and height are set int [] dx = new int [] {0, 1, 0, -1}; // Horizontal movements int [] dy = new int [] {1, 0, -1, 0}; // Vertical movements bool updated = true; while (updated) { updated = false; for (int y = 0;y < height;y++) { for (int x = 0;x < width;x++) { if (data [x][y] < 0) continue; // Source has no information (yet) for (int m = 0;m < dx.Length;m++) { int x2 = x + dx [m]; if (x2 < 0) continue; if (x2 >= width) continue; int y2 = y + dy [m]; if (y2 < 0) continue; if (y2 >= height) continue; if (data [x2][y2] < -1) continue; // Target is not accessible if (data [x][y] + 1 >= data [x2][y2]) continue; data [x2][y2] = data [x][y] + 1; // (Shorter) path found updated = true; } } } } Code is 100% untested of course.
Infinite recursion debugging is where you really want to learn to use a debugger. Debug.Log-printing won't work as the logs won't show up until the method finished, which it never does. As long as you're writing code in an IDE (MonoDevelop, Visual Studio, etc. (not Notepad++/gedit)), it should be possible. In Visual Studio, the workflow is: - click in the left margin of your code to create a breakpoint - click the bug "attach to Unity" button - run the code. Now the code will stop executing at your breakpoint, and you'll be able to step through the code line-by-line, and see the values of all variables as you go. That'll give you a really good understanding of how your loop is behaving.
A flood fill is basically a breadth first search. Read Introduction to A* from Red Blob Games, specifically the part near the beginning where it discusses breadth first search and shows the algorithm. It doesn't require recursion, it will be easier to debug, and will likely perform better.
It is like a breadth first search, but a fairly specific one where it is very easy to keep track of what is already done. A sliding puzzle can also be solved with both breadth first search and A*, but you have to take better care of the states you've visited, since they are not readily available in the data itself. (Only if no answer can be found, the algorithm can end in a continuous loop if the visited states are not blocked.) So, even though a breadth first search works fine to solve a floodfill, it is not needed to actually know breadth first search. Or even implement it that way. My floodfill code above is certainly not breadth first, it's more like greedy search with good brakes. The good brakes part is the most important here. Besides that, breadth first search tries to actually find an solution state. Like you've reached the exit of the maze or you've solved this sliding puzzle. In the case of a floodfill, you won't find any solution state. The goal is simply to explore all options.
Yeah a better approach would be something like: Code (csharp): list = new List<int[]>(10000); Or whatever the total count would be... and if it's variable you can then use list.TrimExcess() to get rid of the extra elements. Still "moving" a lot of data and/or pointers but it's a single resize operation.
Since we are throwing them up, here is how I typically approach flood fill type problems. I find using the collections and iterating through them makes it much easier to understand. Code (CSharp): // The collection to investigate List<Node> allNodes; // The collection for the results List<List<Node>> individualAreas; foreach (Node startNode in allNodes) { if (!startNode.visited) List<Node> area = new List<Node>(); individualAreas.Add(area); area.Add(node); startNode.visited = true; int index = 0; while (index < area.Count){ foreach (Node neighbour in area[index].GetNeighbours()){ if (!neighbour.visited) { area.Add(neighbour); neighbour.visited = true; } } index++; } } }