I am implementing Breath First Search and trying to obtain the neighbour nodes but I am running into an IndexOutOfRange Error when obtaining neighbours from the Grid (Grid is 100x100). I understand the error but I do not understand why and how it's making its way outside the bounds of the grid. This implementation of finding neighbours works perfectly when running Dijkstra path finding but when I run BFS it seems to give me an IndexOutOfRange.
I have tried checking the current nodes x, z position to make sure its in bounds and then perform finding neighbours in the grid but this did not seem to work for me either, producing the same error. Would appreciate any insight into where I might be going wrong with this.
Grid class Grid.cs
BFS BFS.cs
Neighbours function
public List<Node> GetNeighbours(Node currentNode)
{
var x = Convert.ToInt32(currentNode.Position.x);
var z = Convert.ToInt32(currentNode.Position.z);
var neighbours = new List<Node>() // Unity mentions error occurring here
{
grid[x - 1, z],
grid[x + 1, z],
grid[x, z - 1],
grid[x, z + 1],
grid[x + 1, z + 1],
grid[x - 1, z + 1],
grid[x - 1, z - 1],
grid[x + 1, z - 1]
};
var walkableNeighbours = new List<Node>();
foreach (var neighbour in neighbours)
{
if (!IsCellOccupied(neighbour) && IsInLevelBounds(neighbour))
walkableNeighbours.Add(neighbour);
}
return walkableNeighbours;
}
I have tried checking the bounds before getting grid neighbours but this did not work either, giving me the same error weirdly.
private bool IsIndexInBounds(int x, int z)
{
if (x > 0 && x <= Width - 1 && z > 0 && z <= Height - 1)
return true;
return false;
}
public List<Node> GetNeighbours(Node currentNode)
{
var x = Convert.ToInt32(currentNode.Position.x);
var z = Convert.ToInt32(currentNode.Position.z);
if(IsIndexInBounds(x,z) { ... }
var neighbours = new List<Node>()
{
grid[x - 1, z],
grid[x + 1, z],
grid[x, z - 1],
grid[x, z + 1],
grid[x + 1, z + 1],
grid[x - 1, z + 1],
grid[x - 1, z - 1],
grid[x + 1, z - 1]
};
//...
}
Suggestions
Dynamically finding neighbours surrounding the current point
List<Node> neighbours = new List<Node>();
for (int w = Mathf.Max(0, x - 1); w <= Mathf.Min(x + 1, Width); w++)
{
for (int h = Mathf.Max(0, z - 1); h <= Mathf.Min(z + 1, Height); z++)
{
if (w != x || h != z)
{
neighbours.Add(grid[w, h]);
}
}
}
x - 1 .. z - 1 .. x + 1 .. z + 1 .. there is no check whether these are actually possible
You only check your current position
if(IsIndexInBounds(x, z) { ... }
but you would need to check for each of the neighbours whether it exists like e.g. the brute force way
var neighbours = new List<Node>();
var higherX = x + 1;
var lowerX = x - 1;
var higherZ = z + 1;
var lowerZ = z - 1;
if(IsIndexInBounds(lowerX, z) neighbours.Add(grid[lowerX, z]);
if(IsIndexInBounds(higherX, z) neighbours.Add(grid[higherX, z]);
if(IsIndexInBounds(x, lowerZ) neighbours.Add(grid[x, lowerZ]);
if(IsIndexInBounds(x, higherZ) neighbours.Add(grid[x, higherZ]);
if(IsIndexInBounds(higherX, higherZ) neighbours.Add(grid[higherX, higherZ]);
if(IsIndexInBounds(lowerX, higherZ) neighbours.Add(grid[lowerX, higherZ]);
if(IsIndexInBounds(lowerX, lowerZ) neighbours.Add(grid[lowerX, lowerZ]);
if(IsIndexInBounds(higherX, lowerZ) neighbours.Add(grid[higherX, lowerZ]);
Your IsIndexInBounds is not completely correct btw, indices in c# start with 0 so instead of > 0 you would actually want >= 0. You could also simply make it
private bool IsIndexInBounds(int x, int z)
{
return x >= 0 && x < Width && z >= 0 && z < Height;
}
I believe the problem you are facing is that you are not considering the edge cases, when you are in the x limit for example, there will be no x+1 posibility, so your combinations should only be:
var neighbours = new List<Node>()
{
grid[x - 1, z],
grid[x, z - 1],
grid[x, z + 1],
grid[x - 1, z + 1],
grid[x - 1, z - 1],
};
or if you are in the corner then the only possible neighbours would be [x - 1, z], grid[x, z - 1], grid[x - 1, z - 1]. Your current node creation always tried to find the 8 neighbours sorrounding the point but you have to keep in mind this will not alway be the case.
You can find an algorithm for finding this dynamically here or you can try adding the edge case combinations according to your case, if you prefer.
Related
A 2 dimensional array with a size of NxN, composed of 1 and 0.
A neighbor is a 1 in the north / south / west / east of the index
Recursively find how many neighbors an index in the array has (neighbors that touch other neighbors are also included).
For the array I built I should get 6, but instead I get a stack overflow exception, and I don't get why.
Below is my 7x7 array, that for index 2, 5 should return the value of 6.
Example:
static void Main(string[] args)
{
int[,] arr = {
{ 0,0,0,1,0,0,0 },
{ 1,0,0,1,1,0,0 },
{ 0,0,0,0,1,1,0 },
{ 0,0,0,0,1,0,0 },
{ 0,0,0,0,0,0,0 },
{ 0,1,1,1,1,0,0 },
{ 1,0,0,1,0,0,0 },
};
Console.WriteLine(Recursive(arr,2,5));
Console.ReadLine();
}
Routine under test:
static public int Recursive(int[,] arr, int x, int y)
{
if (x < 0 || y < 0 || x > arr.GetLength(0) || y > arr.GetLength(1))
{
return 0;
}
// check if a 1 has neighbors
if (arr[x, y] == 1)
{
return 1 +
Recursive(arr, x - 1, y) +
Recursive(arr, x + 1, y) +
Recursive(arr, x, y - 1) +
Recursive(arr, x, y + 1);
}
else
{
return 0;
}
}
Please, note that when you compute Recursive(arr, x, y) you call both Recursive(arr, x - 1, y) and Recursive(arr, x + 1, y):
return 1 +
Recursive(arr, x - 1, y) + // <- both x - 1
Recursive(arr, x + 1, y) + // <- and x + 1
Recursive(arr, x, y - 1) +
Recursive(arr, x, y + 1);
On the next recursive call, when you try to compute Recursive(arr, x + 1, y) it calls Recursive(arr, x + 2, y) as well as Recursive(arr, x + 1 - 1, y) which is Recursive(arr, x, y). So you have a vicious circle: to compute Recursive(arr, x, y) you must compute Recursive(arr, x, y) and stack overflow as the the result.
The way out is to break the circle and don't let read the same cell again and again (we can just set it to 0 for this):
public static int Recursive(int[,] arr, int x, int y)
{
// out of the grid
if (x < 0 || y < 0 || x > arr.GetLength(0) || y > arr.GetLength(1))
return 0;
// empty cell
if (arr[x, y] == 0)
return 0;
// do not read the cell again! From now on treat it as an empty cell
arr[x, y] = 0;
int result = 1; // the cell itself
// let have a loop instead of four calls
for (int d = 0; d < 4; ++d)
result += Recursive(arr, x + (d - 2) % 2, y + (d - 1) % 2);
// Restore after the recursive call:
// let us do not damage arr with permanently setting cells to 0
// and return arr to its original state
arr[x, y] = 1;
return result;
}
Edit: non recursive solution, where we memorize in visited all the visited cells:
public static int Memoization(int[,] arr, int x, int y) {
if (x < 0 || y < 0 || x > arr.GetLength(0) || y > arr.GetLength(1))
return 0;
if (arr[x, y] == 0)
return 0;
int result = 0;
var agenda = new Queue<(int x, int y)>();
agenda.Enqueue((x, y));
var visited = new HashSet<(int x, int y)> { (x, y) };
while (agenda.Count > 0) {
result += 1;
var (oldX, oldY) = agenda.Dequeue();
for (int d = 0; d < 4; ++d) {
int newX = oldX + (d - 2) % 2;
int newY = oldY + (d - 1) % 2;
if (newX < 0 || newY < 0 || newX > arr.GetLength(0) || newY > arr.GetLength(1))
continue;
if (arr[newX, newY] == 0)
continue;
if (visited.Add((newX, newY)))
agenda.Enqueue((newX, newY));
}
}
return result;
}
Start by considering the simplest possible input. In this case it would be an array like
int[,] arr = { {1, 1 }}
I.e. an array consisting of only two items.
Start by the first left item, this is one, so next all neighbors recursed to.
All but the right neighbor is outside the array so will be ignored.
The right item is one, so all neighbors to this will be recursed to, including the left item.
Since you end up processing the left item again, you will continue the recursion until you run out of stack space.
The point here is that you need to keep track of all the points you have already visited. For example by using a HashSet<(int x, int y)>.
Calculate the number of points with integer coordinates inside the ellipse1. Hint: check those values for x and y for which
< 1.
Can
and this be fulfilled? And what about y?
There is no point inside the ellipse whose |x| is greater than 13.
If you want to count the number of points with integer coordinates inside the ellipse I would do something like this:
int Points = 0;
for(int x = -13; x <= 13; x++)
{
for(int y = -16; y <= 16; y++)
{
if((Math.Pow(x, 2)/169) + (Math.Pow(y, 2)/256) <= 1)
{
Points++;
}
}
Clarify the question if you want a more detailed answer because it is hard to understand what you are asking.
You can query all the points within
x = {-13 .. 13}
y = {-16 .. 16}
square (follow the hint provided: you should analyze points such that |x| < 13 and |y| < 16). Let's do it with a help of Linq:
int a = 13;
int b = 16;
int result = Enumerable
.Range(-a, a * 2 + 1)
.SelectMany(x => Enumerable
.Range(-b, 2 * b + 1)
.Select(y => (x: x, y: y)))
.Count(p => (double) p.x * p.x / a / a + (double) p.y * p.y / b / b < 1);
Console.Write(result);
Outcome:
647
If you want to include points which on the ellipse (e.g. {13, 0}), just change < 1 into <= 1. In this case you'll have 651 points (points {-13, 0}, {13, 0}, {0, -16}, {0, 16} are added).
I with a team trying to make Tetris Attack/Panel de Pon. I did debug and saw that Debug.Log("Match?"); is not executable. Before foreach (Vector2Int n in exist) Debug working.
How could I change my script?
With this part of code, I am trying to get cells that match with each other horizontally and vertically.
private List<Vector2Int> FindCellNeighbor(int x, int y, List<Vector2Int> matching)
{
matching.Add(new Vector2Int(x, y));
List<Vector2Int> exist = new List<Vector2Int>();
foreach (Vector2Int n in exist)
{
if (!n.Equals(matching))
{
Debug.Log("Match?");
if (x >= 0 && x <= height && y >= 0 && y <= width) // checks if cell coordiantes hits the borders
{
if (data[x, y] == data[x + 1, y])
{
matching.AddRange(FindCellNeighbor(x + 1, y, matching));
exist = matching;
Debug.Log("x + 1");
}
if (data[x, y] == data[x - 1, y])
{
matching.AddRange(FindCellNeighbor(x - 1, y, matching));
exist = matching;
Debug.Log("x - 1");
}
if (data[x, y] == data[x, y + 1])
{
matching.AddRange(FindCellNeighbor(x, y + 1, matching));
exist = matching;
Debug.Log("y + 1");
}
if (data[x, y] == data[x, y - 1])
{
matching.AddRange(FindCellNeighbor(x, y - 1, matching));
exist = matching;
Debug.Log("y - 1");
}
}
}
}
return matching;
}
Full codes GitHub
I'm building a bubble breaker-kinda game. My code uses two 2D arrays, one containing color indexes (1 - 6) to represent colored circles, and one indicating whether the circle has been selected (1 or 0). I can succesfully select a circle, the right value in the second array changes and this is reflected correctly on screen.
This is the method that selects one circle and four adjacent circles. I pass in the X and Y coordinates that the user has selected on the grid. I set that position to selected (from 0 to 1 in the SelectedCircles array. Check whether any of the sides has a circle with the same color, if so, change that circle to selected too.
private void SelectSurroundingCircles(int xPosition, int yPosition)
{
SelectedCircles[yPosition, xPosition] = 1;
int colorKey = Circles[yPosition, xPosition];
int increment = 1;
for (int i = 0; i < Nickles.Length; i++)
{
if (Circles[yPosition - increment, xPosition] == colorKey)
SelectedCircles[yPosition - increment, xPosition] = 1; // TOP
if (Circles[yPosition + increment, xPosition] == colorKey)
SelectedCircles[yPosition + increment, xPosition] = 1; // BOTTOM
if (Circles[yPosition, xPosition + increment] == colorKey)
SelectedCircles[yPosition, xPosition + increment] = 1; // RIGHT
if (Circles[yPosition, xPosition - increment] == colorKey)
SelectedCircles[yPosition, xPosition - increment] = 1; // LEFT
}
}
What I want to achieve is that all circles of the same color that are next to each other get selected. Basically you first look at the circles adjacent as above, look at their adjacent circles, and so on... I tried various other things but somehow I couldn't figure it out. Hopefully someone can help me, I must be overlooking something.
Thanks.
Not sure this fullfills your exact selecting logic but isn't recursion the solution:
if ( SelectedCircles[yPosition - increment, xPosition] != 1 && Circles[yPosition - increment, xPosition] == colorKey) {
SelectSurroundingCircles(xPosition, yPosition - increment)
}
//... same for other 3 directions
The extra check if the position isn't selected already is important to prevent endless recursion
Nevermind, I solved it myself. I checked against the array of selected circles, this worked.
for (int y = 0; y < SelectedCircles.GetLength(0); y++)
{
for (int x = 0; x < SelectedCircles.GetLength(1); x++)
{
if (SelectedCircles[y, x] == 1)
{
if (y - 1 >= 0 && SelectedCircles[y - 1, x] != 1 && Circles[y - 1, x] == colorKey)
SelectedCircles[y - 1, x] = 1; // TOP
if (y + 1 <= 9 && SelectedCircles[y + 1, x] != 1 && Circles[y + 1, x] == colorKey)
SelectedCircles[y + 1, x] = 1; // BOTTOM
if (x + 1 <= 9 && SelectedCircles[y, x + 1] != 1 && Circles[y, x + 1] == colorKey)
SelectedCircles[y, x + 1] = 1; // RIGHT
if (x - 1 >= 0 && SelectedCircles[y, x - 1] != 1 && Circles[y, x - 1] == colorKey)
SelectedCircles[y, x - 1] = 1; // LEFT
}
}
}
}
say array with 12 rows and 10 columns
int[,] array = new int[12,10];
and I select 0,0 it must return all neighbors of 0,0
which will be
0,1
1,1
1,0
say I want neighbors of 2,3 it must return an array of neighbors
1,2
1,3
1,4
2,2
2,4
3,1
3,2
3,3
element [x, y]
neighbor1 = x + 1, y;
neighbor2 = x - 1, y;
neighbor3 = x, y + 1;
neighbor4 = x, y - 1;
neighbor5 = x + 1, y + 1;
neighbor6 = x + 1, y - 1;
neighbor7 = x - 1, y + 1;
neighbor8 = x - 1, y - 1;
Obviously you need to check if those elements coordinates exists just in case the element is in a "border" of the matrix. Hard? I say no.
Braindead and non-performing but illustrative and quick:
int[,] array = new int[12,10];
int refx=0, refy=10;
var neighbours = from x in Enumerable.Range(0,array.GetLength(0)).Where(x => Math.Abs(x - refx)<=1)
from y in Enumerable.Range(0,array.GetLength(1)).Where(y => Math.Abs(y - refy)<=1)
select new {x,y};
neighbours.ToList().ForEach(Console.WriteLine);
alternatively
neighbours = from x in Enumerable.Range(refx-1, 3)
from y in Enumerable.Range(refy-1, 3)
where x>=0 && y>=0 && x<array.GetLength(0) && y<array.GetLength(1)
select new {x,y};
neighbours.ToList().ForEach(Console.WriteLine);