Related
Im working on a painting game but i can't get the flood fill algorithm to work on large areas. Its a recursive algorithm and i read that implementing a stack might work however i can get it to work as well. Here's the code;
private void FillCluster(int x, int y, int colorIndex, HashSet<string> traversedCells)
{
Debug.Log(colorIndex);
// Check if this cell is within the bounds of the picture and has a color number and the
if(x < 0 || x >= ActivePictureInfo.XCells ||
y < 0 || y >= ActivePictureInfo.YCells ||
ActivePictureInfo.ColorNumbers[y][x] == -1 ||
ActivePictureInfo.ColorNumbers[y][x] != colorIndex)
{
return;
}
string cellKey = string.Format("{0}_{1}", x, y);
// Check if this cell has already been traversed by FillBlob
if (traversedCells.Contains(cellKey))
{
return;
}
// Check if this cell is already colored in with the correct color
if (!ActivePictureInfo.HasProgress || ActivePictureInfo.Progress[y][x] != -1)
{
ColorCell(x, y, colorIndex);
}
// Add this cells key to the traversed hashset to indicate it has been processed
traversedCells.Add(cellKey);
// Recursively call recursively with the four cells adjacent to this cell
FillCluster(x - 1, y, colorIndex, traversedCells);
FillCluster(x + 1, y, colorIndex, traversedCells);
FillCluster(x, y - 1, colorIndex, traversedCells);
FillCluster(x, y + 1, colorIndex, traversedCells);
FillCluster(x - 1, y - 1, colorIndex, traversedCells);
FillCluster(x - 1, y + 1, colorIndex, traversedCells);
FillCluster(x + 1, y - 1, colorIndex, traversedCells);
FillCluster(x + 1, y + 1, colorIndex, traversedCells);
}
a version without having to remember all visited cells because they are already marked by the color
private void FillCluster(int x, int y, int colorIndex)
{
Debug.Log(colorIndex);
var currentSeam = new Queue<PointDirection>();
if (FillPoint(x, y, colorIndex))
{
currentSeam.Enqueue(new PointDirection(x - 1, y, Direction.Left));
currentSeam.Enqueue(new PointDirection(x + 1, y, Direction.Right));
currentSeam.Enqueue(new PointDirection(x, y - 1, Direction.Up));
currentSeam.Enqueue(new PointDirection(x, y + 1, Direction.Down));
}
while (currentSeam.Count > 0)
{
var current = currentSeam.Dequeue();
if (FillPoint(current.X, current.Y, colorIndex))
{
if (current.Direction != Direction.Right)
currentSeam.Enqueue(new PointDirection(x - 1, y, Direction.Left));
if (current.Direction != Direction.Left)
currentSeam.Enqueue(new PointDirection(x + 1, y, Direction.Right));
if (current.Direction != Direction.Down)
currentSeam.Enqueue(new PointDirection(x, y - 1, Direction.Up));
if (current.Direction != Direction.Up)
currentSeam.Enqueue(new PointDirection(x, y + 1, Direction.Down));
}
}
}
private bool FillPoint(int x, int y, int colorIndex)
{
if (x < 0 || x >= ActivePictureInfo.XCells ||
y < 0 || y >= ActivePictureInfo.YCells ||
ActivePictureInfo.ColorNumbers[y][x] == -1 ||
ActivePictureInfo.ColorNumbers[y][x] == colorIndex)
{
return false;
}
ActivePictureInfo.ColorNumbers[y][x] = colorIndex;
return true;
}
private struct PointDirection
{
public PointDirection(int x, int y, Direction direction)
{
X = x;
Y = y;
Direction = direction;
}
public int X { get; }
public int Y { get; }
public Direction Direction { get; }
}
private enum Direction : byte
{
Up,
Right,
Down,
Left
}
So the reason you are getting a stackOverflow is because the state of the previous iteration is saved on the stack unless both of these condition are met:
You are doing tail recursion (look it up, but it isn't relevant right now)
Your language optimizes tail recursion (C# doesn't)
As the stack size is limited and your algorithm very quickly reaches thousands of calls one after the other, you cannot make the recursive version work in C#, that is impossible, but it seems like you understood this already
Here is a pseudocode solution to do it without recursion. I don't know C# so the synthax is not correct, but the idea is correct, you will have to transform it into proper C#
private void FillCluster(int x, int y, int colorIndex, HashSet<string> traversedCells) {
Debug.Log(colorIndex);
// Check if this cell is within the bounds of the picture and has a color number and the
//Declare a set, add inside the current node
Set<Tuple> nodesToFill = new Set<>()
nodesToFill.add(new Tuple(x, y));
//Replace the recursion by a loop
for (Tuple tuple in nodesToFill) {
//initialize the current position
x = tuple.first
y = tuple.second
//Deal with the current node
if(FillClusterInner(x, y, colorIndex, traversedCells)) {
//Add the new nodes to the set instead of using recursion
nodesToFill.add(new Tuple(x-1, y))
nodesToFill.add(new Tuple(x + 1, y))
nodesToFill.add(new Tuple(x, y - 1))
nodesToFill.add(new Tuple(x, y + 1))
nodesToFill.add(new Tuple(x - 1, y - 1))
nodesToFill.add(new Tuple(x - 1, y + 1))
nodesToFill.add(new Tuple(x + 1, y - 1))
nodesToFill.add(new Tuple(x + 1, y + 1))
}
//Remove the current tuple from the set as you have dealt with it
nodesToFill.remove(tuple)
}
}
//This is a non-recursive method which fills a single specified node
bool FillClusterInner(int x, int y, int colorIndex, HashSet<string> traversedCells) {
if(x < 0 || x >= ActivePictureInfo.XCells ||
y < 0 || y >= ActivePictureInfo.YCells ||
ActivePictureInfo.ColorNumbers[y][x] == -1 ||
ActivePictureInfo.ColorNumbers[y][x] != colorIndex)
{
return false;
}
string cellKey = string.Format("{0}_{1}", x, y);
// Check if this cell has already been traversed by FillBlob
if (traversedCells.Contains(cellKey))
{
return false;
}
// Check if this cell is already colored in with the correct color
if (!ActivePictureInfo.HasProgress || ActivePictureInfo.Progress[y][x] != -1)
{
ColorCell(x, y, colorIndex);
}
// Add this cells key to the traversed hashset to indicate it has been processed
traversedCells.Add(cellKey);
return true;
}
You can see the idea: instead of recursion, we use a set, which contains all the indexes of the nodes we have yet to fill. You add to this set instead of calling the recursive function, and you remove from the set each position you have handled. When the set is empty you have filled every cell that had to be filled.
I'm trying to calculate on a 2D array 8X8 grid all paths and have the algorithm go over all squares on the grid and configure the shortest path.
See code below:
public static int Mileage(int[,] arr, int x, int y, int miles)
{
if (x < 0 || y < 0 || x > 5 || y > 5 || arr[x, y] == 2) return 99;
if (arr[x, y] == 1) return miles;
arr[x, y] = 2;
miles++;
Console.WriteLine(miles);
int want2 = Math.Min(Mileage(arr, x - 1, y, miles), Mileage(arr, x + 1, y, miles));
int want1 = Math.Min(Mileage(arr, x, y - 1, miles), Mileage(arr, x, y + 1, miles));
return Math.Min(want1, want2);
}
Developing a 2D tile-based "boardgame" I'm struggling with the restriction I have to make, when a player rolls the dice(Move 5 tiles if you land a 5 etc.)
I'm trying to use the following logic:
Start on starting point
Check the position to the sides, above and below
Check if the neighbour tiles are walkable, if they are, change them to reachable
Go to neighbour tile, repeat
I've been looking on A* and D* pathing, but it's a new subject to me and they seem more focused on getting from point A to B, not "reach" which is what I need.
How do I do this through code?
I created a 2D array from an array which was holding my tile(I need a normal array of the tilemap for another purpose):
for(int i = 0; i < 27; i++)
{
for(int j = 0; j < 33; j++)
{
tileMap[i, j] = goTile[i * 33 + j];
}
}
I now use the tileMap as my positiong factor, e.g. my players current position is tileMap[2,4].
I then tried to develop a function:
void pathFinding(Vector2 playerPosition, int diceNumber)
{
GameObject currentPos = tileMap[(int)playerPosition.x, (int)playerPosition.y];
for (int i = 0; i < diceNumber; i++) {
if (tileMap[(int)playerPosition.x + 1, (int)playerPosition.y].tag == "walkableGrid")
{
tileMap[(int)playerPosition.x + 1, (int)playerPosition.y].gameObject.tag = "reachable";
playerPosition.x++;
}
if (tileMap[(int)playerPosition.x - 1, (int)playerPosition.y].tag == "walkableGrid")
{
playerPosition.x--;
}
if (tileMap[(int)playerPosition.x, (int)playerPosition.y + 1].tag == "walkableGrid")
{
playerPosition.y++;
}
if (tileMap[(int)playerPosition.x, (int)playerPosition.y - 1].tag == "walkableGrid")
{
playerPosition.y--;
}
}
}
But as finishing this (if it even would work), would require MANY lines of code, and I believe there's a swifter method using a nested for loop maybe?
//I have now edited the code to better reflect your real data
public void ShowMoves(Vector2 playerPosition, int diceNumber, bool[] blocks)
{
int x = (int)playerPosition.x;
int y = (int)playerPosition.y;
if(tileMap.GetUpperBound(0) < x + 1)
{
if(tileMap[x + 1, y].tag == "walkableGrid" && blocks[0])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x + 1, y), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), false, y != tileMap.GetUpperBound(1), y != 0 });
}
}
if(x - 1 >= 0)
{
if(tileMap[x - 1, y].tag == "walkableGrid" && blocks[1])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x - 1, y), diceNumber - 1, new bool[] { false, x != 0, y != tileMap.GetUpperBound(1), y != 0 });
}
}
if(tileMap.GetUpperBound(1) < y + 1)
{
if(tileMap[x, y + 1].tag == "walkableGrid" && blocks[2])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x, y + 1), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), x != 0, y != tileMap.GetUpperBound(1), false });
}
}
if(y - 1 >= 0)
{
if(tileMap[x, y - 1].tag == "walkableGrid" && blocks[3])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x, y - 1), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), x != 0, false, y != 0 });
}
}
}
This code might not compile, but it's an example to help you along - it will loop until there are no available moves, and exhaust every option. It will also not go back on itself due to the blocks boolean array. The input format would be the position they're at two ints, one for x and one for y, the tiles available, the number of moves left in their roll, and the block available from the beginning (always new bool[] {true, true, true, true}
Be careful, there may be errors in my code, I wrote it in SO and have no clue how well it runs, if it runs at all or anything. Even if it does not, it should be a good starting point for you to create your logic and code it all
EDIT: Code has been changed so that it better fits how your code looks and the data types it uses
To avoid always calling the method by inputting a blocks variable of new bool[] {true, true, true, true}; you can make it an optional operator by making this the method parameters
public void ShowMoves(Vector2 playerPosition, int diceNumber, bool[] blocks = new bool[] {true, true, true, true})
I'm stuck with a college project and I wonder if you can help me have a hint on how to do this, I have to do it on c#.
Using an 80x80 matrix I have to go through it only from left to right and from up to down so I can find the path that gives me the lowest number when sum all the values from top left corner to bottom right corner.
As an example on this case the numbers that should be picked up are:
131,201,96,342,746,422,121,37,331 = 2427 the lowest number
It does not matter how many times you move to the right or down but what matters is to get the lowest number.
This is an interesting project in that it illustrates an important technique called dynamic programming: a solution to the entire problem can be constructed from a solution to a smaller sub-problem with a simple computation step.
Start with a recursive solution that wouldn't work for large matrix:
// m is the matrix
// R (uppercase) is the number of rows; C is the number of columns
// r (lowercase) and c are starting row/column
int minSum(int[,] m, int R, int C, int r, int c) {
int res;
if (r == R-1 && c == C-1) {
// Bottom-right corner - one answer
res = m[r,c];
} else if (r == R-1) {
// Bottom row - go right
res = m[r,c] + minSum(m, R, C, r, c+1);
} else if (c == C-1) {
// Rightmost column - go down
res = m[r,c] + minSum(m, R, C, r+1, c);
} else {
// In the middle - try going right, then try going down
int goRight = m[r,c] + minSum(m, R, C, r, c+1);
int goDown = m[r,c] + minSum(m, R, C, r+1, c);
res = Math.Min(goRight, goDown);
}
return res;
}
This will work for a 10×10 matrix, but it would take too long for a 80×80 matrix. However, it provides a template for a working solution: if you add a separate matrix of results you obtained at earlier steps, you would transform it into a faster solution:
// m is the matrix
// R (uppercase) is the number of rows; C is the number of columns
// known is the matrix of solutions you already know
// r (lowercase) and c are starting row/column
int minSum(int[,] m, int R, int C, int?[,] known, int r, int c) {
if (known[r,c].HasValue) {
return known[r,c];
}
int res;
... // Computation of the result goes here
known[r,c] = res;
return res;
}
This particular technique of implementing dynamic programming solutions is called memoization.
First step is always analysis, in particular to try to figure out the scale of the problem.
Ok assuming you can only ever step down or to the right, you will have 79 steps down and 79 steps to the right. 158 steps total of the form 011100101001 (1=move right, 0=move down) etc. Note that the solution space is not as much as 2^158 since not all binary numbers are possible... you must have exactly 79 downs and 79 rights. From combinatorics, this limits the number of possible correct answers to 158!/79!79!, which evaluates to still a very large number, something like 10^46.
You should realize is that this is quite large to brute-force, which methodology otherwise should definitely be a consideration for you if the project does not specifically rule it out, since it invariably makes the algorithm simpler (e.g. by simply iterating all the solution possibilities). I imagine the question has been designed this way in order to require you to use an algorithm that does not brute-force the correct answer.
The way to solve this problem without iterating the whole solution space is to realize that the best path to the lower right corner is the better of the two best paths to the squares immediately to the left of, and above, the lower right corner, and the best path to those is the best path to the next diagonal (numbers 524, 121, 111 in your diagram), and so on.
What you need to do is to treat each cell as a node in a graph and implement shortest path algorithm.
Dijkstra algorithm is one of them. You can find more information here https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
It is really simple, because You can divide the problem into solved and unsolved part and move items from unsolved into solved one by one. Start on top left and move through all "/" diagonals towards bottom right.
int size = 5;
int[,] matrix = new int[,] {
{131,673,234,103,18},
{201,96,342,965,150},
{630,803,746,422,111},
{537,699,497,121,956},
{805,732,524,37,331}
};
//Random rand = new Random();
//for (int y = 0; y < size; ++y)
//{
// for (int x = 0; x < size; ++x)
// {
// matrix[y, x] = rand.Next(10);
// }
//}
int[,] distance = new int[size, size];
distance[0, 0] = matrix[0, 0];
for (int i = 1; i < size * 2 - 1; ++i)
{
int y = Math.Min(i, size - 1);
int x = i - y;
while (x < size && y >= 0)
{
distance[y, x] = Math.Min(
x > 0 ? distance[y, x - 1] + matrix[y, x] : int.MaxValue,
y > 0 ? distance[y - 1, x] + matrix[y, x] : int.MaxValue);
x++;
y--;
}
}
for (int y = 0; y < size; ++y)
{
for (int x = 0; x < size; ++x)
{
Console.Write(matrix[y, x].ToString().PadLeft(5, ' '));
}
Console.WriteLine();
}
Console.WriteLine();
for (int y = 0; y < size; ++y)
{
for (int x = 0; x < size; ++x)
{
Console.Write(distance[y, x].ToString().PadLeft(5, ' '));
}
Console.WriteLine();
}
Console.WriteLine();
I am creating a flowfield for AI units and am trying to speed up some threaded code. My test grid size is 2000 x 2000 and I currently have the generation time down to 5.5 seconds. Profiling the code pointed something out that seems strange. The method that I use for the threads reports on average 140 Inclusive Contentions. 65 of which are for this line.
var neighbours = GetNeighbors4(cell.Point);
and below is the method being called.
private IEnumerable<IntegrationCell> GetNeighbors4(Point point)
{
var sizeX = _size.X - 1;
var sizeY = _size.Y - 1;
var x = point.X;
var y = point.Y;
//corners
if (x == 0 && y == 0)
{
return new[]
{
Cells[1, 0],
Cells[0, 1]
};
}
if (x == sizeX && y == 0)
{
return new[]
{
Cells[sizeX - 1, 0],
Cells[sizeX, 1]
};
}
if (x == 0 && y == sizeY)
{
return new[]
{
Cells[0, sizeY - 1],
Cells[1, sizeY]
};
}
if (x == sizeX && y == sizeY)
{
return new[]
{
Cells[sizeX - 1, sizeY],
Cells[sizeX, sizeY - 1]
};
}
//top row
if (y == 0)
{
return new[]
{
Cells[x - 1, 0],
Cells[x + 1, 0],
Cells[x, 1]
};
}
//bottom row
if (y == sizeY)
{
return new[]
{
Cells[x - 1, y],
Cells[x + 1, y],
Cells[x, y - 1]
};
}
//left column
if (x == 0)
{
return new[]
{
Cells[0, y - 1],
Cells[0, y + 1],
Cells[1, y]
};
}
//right column
if (x == sizeX)
{
return new[]
{
Cells[x, y - 1],
Cells[x, y + 1],
Cells[x - 1, y]
};
}
//everything else
return new[]
{
Cells[x, y - 1],
Cells[x, y + 1],
Cells[x - 1, y],
Cells[x + 1, y]
};
}
Cells is just a simple 2 dimensional array to represent a grid
IntegrationCell[,] Cells;
Now the way it works is that given a target cell in the grid, I step out like a 'wave' or 'ripple' from the target. Each iteration of the wave steps out one further from the target. As I do this, each iteration has more cells as the distance from the target increases. For each cell in each iteration, I spawn a new thread that computes the cells cost and returns a list of new cells that need to be computed/recomputed. There is a lot more that happens, but that's basically it. At one point I peak at roughly 120 threads before I hit the edge of the map, and I begin to have less cells each iteration until there are none left.
This is the full method of the thread run for each cell. (I can have over 100 running at any one time)
private IEnumerable<IntegrationCell> CostStep(IntegrationCell cell)
{
var result = new List<IntegrationCell>(); \\14 contentions
var costBlock = _costfield.Cells[cell.Point.X, cell.Point.Y];
if (costBlock.Cost == 255)
return result;
var neighbours = GetNeighbors4(cell.Point); \\65 contentions
foreach (var neighbour in neighbours) \\18 contentions
{
var newCost = costBlock.Cost + neighbour.Cost;
if (cell.Cost > newCost)
cell.Cost = newCost;
var childCostBlock = _costfield.Cells[neighbour.Point.X, neighbour.Point.Y];
var newChildCost = cell.Cost + childCostBlock.Cost;
if (childCostBlock.Cost == 255)
neighbour.Cost = 255;
else if (neighbour.Cost > newChildCost)
{
neighbour.Cost = newChildCost;
result.Add(neighbour); \\39 contentions
}
}
return result;
}
I have placed comments of the contentions reported against each line. The contentions vary with each run, but what I cant understand is why I would have contentions reading from an array? Yes I'm updating the the array/cell and its neighbors if needed and each cell may be calculated more than once.
For each cell in each iteration, I spawn a new thread that ...
Probably that is the problem. Since you are doing CPU bound calculations, use only as many threads as CPU has your computer, indicated by the static property:
System.Environment.ProcessorCount
Otherwise, you are trying to schedule too much, causing a lot of contention and context switches.
I mean, more threads does not mean faster, actually, it may do the application slower because the thread management overhead. You use more threads when you have I/O bound operations, and therefore many threads are idle waiting for things to happen somewhere else (e.g.: a web service call, a database operation, an incoming request....)
Take a look at this answer: https://stackoverflow.com/a/12021285/307976, and also about how to limit the parallelism with PLINQ.