Go Board Game - Recursive board group check - c#

Creating a Go Board Game but I'm stuck at checking the board for groups of stones that have been surrounded. To do this I thought I'd need to come up with some recursive functionality:
(Updated)
public List<Point> FindSurrounded(Board board, Point p, Player player, List<Point> group)
{
int[,] b = board.board;
for (int dx = -1; dx <= 1; dx++){
for (int dy = -1; dy <= 1; dy++)
{
//check if group allready contain this spot
if (p.X + dx < board.width && p.Y + dy < board.height && p.X + dx > 0 && p.Y + dy > 0 && (dy == 0 || dx == 0) && !(dy == 0 && dx == 0) && !group.Contains(new Point(p.X + dx, p.Y + dy)))
{
// is the spot empty
if (b[p.X + dx, p.Y + dy] == 0)
return null;
//check the suroundings of this spot and add them to the group
//if(b[p.X + dx, p.Y + dy] != player.Identifier)// is there an enemy on this spot
// return new List<Point>();
if (b[p.X + dx, p.Y + dy] != player.Identifier)
{
group.Add(new Point(p.X + dx, p.Y + dy));
List<Point> temp = FindSurrounded(board, new Point(p.X + dx, p.Y + dy), player, new List<Point>());
if (temp == null)
return null;
//group.AddRange(temp);
}
}
}
}
return group;
}
This code however gives me a System.StackOverFlowException error when I put a stone that surrounds stones of the opponent. The error would concern the following line:
if (p.X + dx < board.width && p.Y + dy < board.height && p.X + dx > 0 && p.Y + dy > 0 && (dy == 0 || dx == 0) && !(dy == 0 && dx == 0) && !group.Contains(new Point(p.X + dx, p.Y + dy)))
But I have no idea why.
Does anyone know a way in which I can check whether a group of stones on the board is surrounded?
Thanks in advance!
Best regards,
Skyfe.
EDIT: Forgot to mention I still have to create an array to temporarily store all found stones that form a group together in order to remove them from the board when they're surrounded.

Answer to the specific question:
Does anyone know a way in which I can check whether a group of stones on the board is surrounded?
Try this approach:
Board b;
int N, M; // dimensions
int timer;
int[,] mark; // assign each group of stones a different number
int[,] mov = // constant to look around
{
{ 0, -1 }, { 0, +1 },
{ -1, 0 }, { +1, 0 }
};
// Checks for a group of stones surrounded by enemy stones
// Returns the first group found or null if there is no such group.
public List<Point> CheckForSurrounded()
{
mark = new int[N,M];
for (int i = 0; i < b.SizeX; ++i)
for (int j = 0; j < b.SizeX; ++j)
if (mark[i, j] == 0) // not visited
{
var l = Fill(i, j);
if (l != null)
return l;
}
return null;
}
// Marks all neighboring stones of the same player in cell [x,y]
// Returns the list of stones if they are surrounded
private List<Point> Fill(int x, int y)
{
int head = 0;
int tail = 0;
var L = new List<Point>();
mark[x, y] = ++timer;
L.Add(new Point(x,y));
while (head < tail)
{
x = L[head].X;
y = L[head].Y;
++head;
for (int k = 0; k < 4; ++k)
{
// new coords
int xx = x + mov[k,0];
int yy = y + mov[k,1];
if (xx >= 0 && xx < N && yy >= 0 && yy < M) // inside board
{
if (mark[xx, yy] == 0) // not visited
{
if (b[xx, yy].IsEmpty) // if empty square => not surrounded
return null;
if (b[xx, yy].IsMine)
{
L.Add(new Point(xx,yy)); // add to queue
mark[xx, yy] = timer; // visited
++tail;
}
}
}
}
}
// the group is surrouneded
return L;
}
This method doesn't use recursion so you don't have to deal with stack overflow (the exception not the site).

You could do it with code that does something like this.
pseudocode
isSurrounded(stone){
//mark that you have seen the stone before
stone.seen = true;
//check if all the surrounding spots are surrounded
for(spot in surrounded.getSpotsAround){
//An empty spot means the stone is not surrounded
if(spot = empty){
return false;
}
else{
//don't recheck stones you have seen before or opponents stones
if(spot.stone.seen != true || !stone.belongsToOpponent){
//recursively call this method, if it returns false your stone is not surrounded
if(!isSurrounded(spot.stone){
return false;
}
}
}
}
//if all of the surrounding stones are surrounded,seen already, or belong to other players, this stone is surrounded
return true;
}
Then you would have to remove all the stones where seen = true or reset seen on all stones.

I came up with this
(Updated)
public List<Point> FindSurrounded(int[,] b, Point p, Player player, List<Point> group)
{
for (int dx = -1; dx <= 1; dx++){
for (int dy = -1; dy <= 1; dy++)
{
//check if group allready contain this spot
if ((dy == 0 || dx == 0) && !(dy == 0 && dx == 0) && !group.Contains(new Point(p.X + dx, p.Y + dy))
{
// is the spot empty
if( b[p.X + dx, p.Y + dy] == 0)
return null;
if(b[p.X + dx, p.Y + dy] == player.Identifier)// is this your own stone
{
//If this is my stone add it to the list and check for it
group.Add( new Point( p.X + dx, p.Y + dy ) );
List<Point> temp = removeSurrounded(b, new Point(p.X + dx, p.Y + dy), player, group);
if(temp == null)
return null;
}
}
}
}
return group;
}
This returns null if there is any empty spot near your group else it will return a list of Points which represents your group.
Afterwards you can remove them like that
List<Point> group = FindSurrounded(b, p, player, new List<Point>());
if(group != null)
foreach(Point point in group)
b[point.x, point.y] = 0;

Related

Wrong parameters passed to function C#

I'm trying to make a self-learning snake game and encountered an issue that I'm trying to resolve for the last several hours. Here is the code where I move the snake head:
public void moveTo()
{
int foodX = food.PosX / food.Width;
int foodY = food.PosY / food.Height;
Direction directionOfMaxForCurrentState= new Direction();
int currentX = head.PosX / head.Width;
int currentY = head.PosY / head.Height;
do
{
int tmpX = currentX;
int tmpY = currentY;
previousX = tmpX;
previousY = tmpY;
directionOfMaxForCurrentState = HighestQactionForState(currentX, currentY, previousX, previousY);
if (directionOfMaxForCurrentState == Direction.Up) { head.PosY -= head.Height; }
if (directionOfMaxForCurrentState == Direction.Down) { head.PosY += head.Height; }
if (directionOfMaxForCurrentState == Direction.Left) { head.PosX -= head.Width; }
if (directionOfMaxForCurrentState == Direction.Right) {head.PosX += head.Width; }
currentX = head.PosX / head.Width;
currentY = head.PosY / head.Height;
if (currentX == foodX && currentY == foodY) { snake.Clear(); head = new Cell(1, 1); snake.Add(head); }
} while (head.PosX == food.PosX && head.PosY == food.PosY);
}
And here is HighestQactionForState function:
public Direction HighestQactionForState(int x, int y, int px, int py)
{
var Qaround = new List<decimal>();
var actionsWithMax = new List<Direction>();
Direction toExclude = new Direction();
toExclude = directionToExclude(x, y, px, py);
foreach (Direction action in PosibleActionsForState(x, y).Where(a => a != toExclude).ToList())
{
if (action == Direction.Up && (double)Math.Abs(Q[Tuple.Create(x, y - 1, action)] - MaxQaroundState(x, y)) < 0.000000000000000000000001) actionsWithMax.Add(action);
if (action == Direction.Down && (double)Math.Abs(Q[Tuple.Create(x, y + 1, action)] - MaxQaroundState(x, y)) < 0.000000000000000000000001) actionsWithMax.Add(action);
if (action == Direction.Left && (double)Math.Abs(Q[Tuple.Create(x - 1, y, action)] - MaxQaroundState(x, y)) < 0.000000000000000000000001) actionsWithMax.Add(action);
if (action == Direction.Right && (double)Math.Abs(Q[Tuple.Create(x + 1, y, action)] - MaxQaroundState(x, y)) < 0.000000000000000000000001) actionsWithMax.Add(action);
}
return actionsWithMax.ElementAt(rnd.Next(actionsWithMax.Count));
}
So now when I put the breakpoint in the moveTo function I see that HighestQactionForState gets the right parameters, but in the HighestQactionForState function the parameters are not correct - px always has the same value as int x, and py is the same as y which causing the program to behave wrong. Any help would be appreciated!
If im not mistaken you should do this instead
directionOfMaxForCurrentState = HighestQactionForState(currentX, currentY, previousX, previousY);
previousX = currentX;
previousY = currentY;
You set previous values after you call method so by the meaning of variables they have values set in previous iteration.
Also there is no need for declaring extra temp variables. Your parameters are passed by value not by reference.

recursive stackoverflow minesweeper c#

I am writing a game of minesweeper. Below is code for 3 methods in minesweeper. The first method is to check all the spaces around the button pressed and to count how many bombs are around it. The next method is to be called recursively, in order that if the user pressed a button with 0 buttons around it, it will open all of the squares that also indicate 0 squares around it. The third method is to check that it will be in bound the check. The empty space recursive call is getting me a stackoverflow error, what am I doing wrong?
Thanks!
private int GameLogicChecker(int x, int y)
{
int count = 0;
if (_grid[x, y] != -1)
{
if (x + 1 < SizeX)
{ //Right
if (_grid[x + 1, y] == -1)
count++;
}
if (x - 1 > 0)
{ //Left
if (_grid[x - 1, y] == -1)
count++;
}
if (y + 1 < SizeY)
{ //Upper
if (_grid[x, y + 1] == -1)
count++;
}
if (y - 1 > 0)
{ //Lower
if (_grid[x, y - 1] == -1)
count++;
}
if (x + 1 < SizeX && y + 1 < SizeY)
{ //Right-Upper
if (_grid[x + 1, y + 1] == -1)
count++;
}
if (x + 1 < SizeX && y - 1 > 0)
{ //Right-Lower
if (_grid[x + 1, y - 1] == -1)
count++;
}
if (x - 1 > 0 && y + 1 < SizeY)
{ //Left-Upper
if (_grid[x - 1, y + 1] == -1)
count++;
}
if (x - 1 > 0 && y - 1 > 0)
{ //Left-Lower
if (_grid[x - 1, y - 1] == -1)
count++;
}
}
return count;
}
void OpenEmptySpace(int x, int y)
{
for (var k = -1; k <= 1; k++)
{
for (var l = -1; l <= 1; l++)
{
if (CheckBounds(x + k, y + l) && GameLogicChecker(x + k, y + l) == 0)
{
_buttons[x + k, y + l].Text = "0";
OpenEmptySpace(x + k, y + l);
}
}
}
}
private bool CheckBounds(int x, int y)
{
return x >= 0 && x < SizeX && y >= 0 && y < SizeY;
}
For k = 0 and l = 0, you are calling yourself again and again and again...
Thanks to #BenVoigt for pointing out that two zeroes adjacent to each other will also lead to infinite recursion. So, in order to solve that one method is to create a boolean grid too and set a particular cell's value to true if it has been run through once. Assuming the grid is called Explored, I've added the condition for it in the code below.
If you insist on your current code, try changing the condition to:
if (CheckBounds(x + k, y + l)
&& GameLogicChecker(x + k, y + l) == 0
&& !(k == 0 && l == 0)
&& !Explored[x + k, y + l])
{
Explored[x + k, y + l] = true;
_buttons[x + k, y + l].Text = "0";
OpenEmptySpace(x + k, y + l);
}
Here is another answer for you, rewriting your methods one-by-one following better coding practices. Like in the other answer, a boolean grid called Explored[SizeX, SizeY] has been assumed.
1. GameLogicChecker()
private int GameLogicChecker(int x, int y)
{
if (_grid[x, y] == -1) return 0;
int count = 0;
if (x + 1 < SizeX && _grid[x + 1, y] == -1) //Right
{
count++;
}
if (x - 1 > 0 && _grid[x - 1, y] == -1) //Left
{
count++;
}
if (y + 1 < SizeY && _grid[x, y + 1] == -1) //Upper
{
count++;
}
if (y - 1 > 0 && _grid[x, y - 1] == -1) //Lower
{
count++;
}
if (x + 1 < SizeX && y + 1 < SizeY && _grid[x + 1, y + 1] == -1) //Right-Upper
{
count++;
}
if (x + 1 < SizeX && y - 1 > 0 && _grid[x + 1, y - 1] == -1) //Right-Lower
{
count++;
}
if (x - 1 > 0 && y + 1 < SizeY && _grid[x - 1, y + 1] == -1) //Left-Upper
{
count++;
}
if (x - 1 > 0 && y - 1 > 0 && _grid[x - 1, y - 1] == -1) //Left-Lower
{
count++;
}
return count;
}
What's better? Quicker returning from the method for special case. Reduced nesting in If(...) blocks.
2. OpenEmptySpace()
public/private void OpenEmptySpace(int x, int y)
{
for (var deltaX = -1; deltaX <= 1; deltaX += 2)
{
for (var deltaY = -1; deltaY <= 1; deltaY += 2)
{
var thisX = x + deltaX;
var thisY = y + deltaY;
if (OpeningNotNeeded(thisX, thisY))
{
continue;
}
Explored[thisX, thisY] = true;
_buttons[thisX, thisY].Text = "0";
OpenEmptySpace(thisX, thisY);
}
}
}
private bool OpeningNotNeeded(int x, int y)
{
return !CheckBounds(x, y)
|| GameLogicChecker(x, y) != 0
|| Explored[x, y];
}
What's better? Properly named indexing variables in both loops. Properly written condition (+= 2 instead of ++). Reduced nesting in If(...). Easier to read method call in the If(...) instead of three predicates. Useful temporary variables added which make it clear what x + k and y + l were in the code written earlier.
3. CheckBounds() is written fine.

OutOfMemoryException For Maze Solver of Large Dimensions

The Program Works for arrays upto 20x20 But for anything larger it throws an OutOfMemoryException.
Below is the code:
public static Point GetFinalPath(int x, int y) {
queue.Enqueue(new Point(x,y, null));
while(queue.Count>0) {
Point p = queue.Dequeue();
if (arr[p.x,p.y] == 9) {
Console.WriteLine("Found Destination");
return p;
}
if(IsOpen(p.x+1,p.y)) {
arr[p.x,p.y] = 1;
queue.Enqueue(new Point(p.x+1,p.y, p));
}
//similarly for the other directions
}
return null;
}
public int[,] SolutionMaze()
{
Point p = GetFinalPath(0, 0);
while (p.getParent() != null)
{
solvedarray[p.x, p.y] = 9;
p = p.getParent();
}
return solvedarray;
}
ok people here is the rest of the code
public static Queue<Point> queue=new Queue<Point>();
public static bool IsOpen(int x, int y)
{
//BOUND CHECKING
if ((x >= 0 && x < XMAX) && (y >= 0 && y < YMAX) && (arr[x,y] == 0 || arr[x,y] == 9))
{
return true;
}
return false;
}
public class Point
{
public int x;
public int y;
Point parent;
public Point(int x, int y, Point parent)
{
this.x = x;
this.y = y;
this.parent = parent;
}
public Point getParent()
{
return this.parent;
}
}
Assumes start to be 0,0 and final destination is set as 9 at the constructor.
Help me implement this for an array of size 500x500
Well, looking at your code I found one problem. You perform the wrong check. You should check if your point is already added to a queue. What do you do now? We'll, you are just marking processed cell as not open. It's easy to see that you can add to queue same node twice.
Let's follow my example:
1 | . .
0 | ! .
--+----
yx| 0 1
Queue: point (0,0)
We are starting at point(0,0). At this moment, we are adding points (0, 1) and (1,0) to our queue and mark point(0,0) as processed
1 | . .
0 | X !
--+----
yx| 0 1
Queue: point (0,1), point (1,0)
Now we dequeue point(0,1), marking it processed and adding point(1,1) to queue.
1 | ! .
0 | X X
--+----
yx| 0 1
Queue: point (1,0), point(1,1)
Now we dequeue point(1,0), marking it as processed and adding point(1,1) to queue:
1 | X !
0 | X X
--+----
yx| 0 1
Queue: point (1,1), point (1,1)
And now we have same point twice in a queue. And that is not your last problem. Your points have a reference to all it parents, so your previous points (doubled too) can't be processed with Garbage Collector.
Okay i found an answer to the OutOfMemory. Now the code works even for 500x500 matrix
As it turns out i just implemented a node list which keeps track of added nodes in queue using y*MAX_X_LENGTH + x formula
public static Queue<Point> queue=new Queue<Point>();
public SolveMaze(int[,] array,int staX,int staY,int finX,int finY)
{
//sets Destination as 9
arr = array;
XMAX = array.GetLength(0);
YMAX = array.GetLength(1);
finishY = finX; finishX = finY; startY = staX; startX = staY;
solvedarray = new int[XMAX, YMAX];
}
public static List<int> nodelist=new List<int>();
public void AddPointToQueueIfOpenAndNotAlreadyPresent(Point p,int direction)
{
if (nodelist.Contains(XMAX * p.y + p.x))
return;
else
{
switch(direction){
case 1:
//north
if (IsOpen(p.x, p.y - 1))
{
arr[p.x, p.y] = 1;
queue.Enqueue(new Point(p.x, p.y - 1, p));
nodelist.Add(XMAX * (p.y - 1) + p.x);
}
break;
case 0:
//east
if (IsOpen(p.x + 1, p.y))
{
arr[p.x, p.y] = 1;
queue.Enqueue(new Point(p.x + 1, p.y, p));
nodelist.Add(XMAX * (p.y) + p.x + 1);
}
break;
case 3:
//south
if (IsOpen(p.x, p.y + 1))
{
arr[p.x, p.y] = 1;
queue.Enqueue(new Point(p.x, p.y +1, p));
nodelist.Add(XMAX * (p.y + 1) + p.x);
}
break;
case 2:
//west
if (IsOpen(p.x - 1, p.y))
{
arr[p.x, p.y] = 1;
queue.Enqueue(new Point(p.x - 1, p.y, p));
nodelist.Add(XMAX * (p.y) + p.x-1);
}
break; }
}
}
public Point GetFinalPath(int x, int y) {
if (arr[finishX, finishY] == 0)
arr[finishX, finishY] = 9;
else
return null;
queue.Enqueue(new Point(x, y, null));
nodelist.Add(XMAX * y + x);
while(queue.Count>0) {
Point p = queue.Dequeue();
nodelist.Remove(p.y * XMAX + p.x);
if (arr[p.x,p.y] == 9) {
Console.WriteLine("Exit is reached!");
return p;
}
for (int i = 0; i < 4; i++)
{
AddPointToQueueIfOpenAndNotAlreadyPresent(p, i);
}
}
return null;
}
public static bool IsOpen(int x, int y)
{
//BOUND CHECKING
if ((x >= 0 && x < XMAX) && (y >= 0 && y < YMAX) && (arr[x,y] == 0 || arr[x,y] == 9))
{
return true;
}
return false;
}
public int[,] SolutionMaze()
{
Point p = GetFinalPath(startX, startY);
if(p!=null)
while (p.getParent() != null)
{
solvedarray[p.x, p.y] = 9;
p = p.getParent();
}
return solvedarray;
}
}
public class Point
{
public int x;
public int y;
Point parent;
public Point(int x, int y, Point parent)
{
this.x = x;
this.y = y;
this.parent = parent;
}
public Point getParent()
{
return this.parent;
}
}

Game of Life - Dead

I was bored and had only 30 minutes of free time so I decided to have a crack at making the game of life. I followed that rules on wikipedia and it doesn't seem to be working correctly. Could someone please tell me what I would be doing wrong?
Here are the rules:
Any live cell with fewer than two live neighbours dies, as if caused by under-population.
Any live cell with two or three live neighbours lives on to the next generation.
Any live cell with more than three live neighbours dies, as if by overcrowding.
Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
public void PerformLogic()
{
if (in_game)
{
time_elapsed += rate;
if (time_elapsed > frame_rate)
{
time_elapsed = 0;
for (int x = 0; x < board_width; x++)
{
for (int y = 0; y < board_height; y++)
{
if (board[x, y] == alive)
{
int surrounding_cells = 0;
for (int x2 = -1; x2 <= 1; x2++)
{
for (int y2 = -1; y2 <= 1; y2++)
{
if (!(x2 + x <= -1 || y2 + y <= -1 || x + x2 >= board_width || y + y2 >= board_height))
{
if (board[x + x2, y + y2] == alive)
{
surrounding_cells++;
}
}
}
}
if (surrounding_cells < 2)
{
board[x, y] = dead;
}
if (surrounding_cells == 2 ||
surrounding_cells == 3)
{
board[x, y] = alive;
}
if (surrounding_cells > 3)
{
board[x, y] = dead;
}
}
else if (board[x, y] == dead)
{
int surrounding_cells = 0;
for (int x2 = -1; x2 <= 1; x2++)
{
for (int y2 = -1; y2 <= ; y2++)
{
if (!(x2 + x <= -1 || y2 + y <= -1 || x + x2 >= board_width || y + y2 >= board_height))
{
if (board[x + x2, y + y2] == alive)
{
surrounding_cells++;
}
}
}
}
if (surrounding_cells == 3)
{
board[x, y] = alive;
}
}
}
}
}
}
}
Any ideas?
I believe you are updating the board too early. The game of life should update the board after it finishes scanning the whole board, rather than while scanning.
E.g.:
if (surrounding_cells > 3)
{
board[x, y] = dead;
}
After this, for the cell next to it, this cell would be treated as dead.
Marc is right, too.
for (int x2 = -1; x2 <= 1; x2++)
{
for (int y2 = -1; y2 <= 1; y2++)
{
looks to me like you're including the central cell in this loop, so 9 instead of 8.
I'm not sure the nested for is the best option, but if you are using that, add:
if(x2 == 0 && y2 == 0) continue;
at the start of the inner loop (i.e. after the last line that I've posted above)

Different Methods of Performing FloodFill

OK everyone I have several different methods of performing a FloodFill. All of them cause problems. I will list the 3 methods and explain what happens with each one. If anyone could give me some pointers that would be great. I have seen some similar posts but none of them have been for C#, java, or VB.net (the only languages I know).
The givens for this are that I have a class called PixelData which stores a Color in a CellColor member variable. I have an array that is 50x50 of PixelData objects in size called "pixels". I also have a constant called CANVAS_SIZE which is 50 in this case. Here are the three methods I have tried using.
This one is recursive. It is EXTREMELY prone to stack overflows. I have tried settings a timer that enabled a CanFill member after this method is complete. This still does not prevent the overflows:
private void FloodFill(Point node, Color targetColor, Color replaceColor)
{
//perform bounds checking X
if ((node.X >= CANVAS_SIZE) || (node.X < 0))
return; //outside of bounds
//perform bounds checking Y
if ((node.Y >= CANVAS_SIZE) || (node.Y < 0))
return; //ouside of bounds
//check to see if the node is the target color
if (pixels[node.X, node.Y].CellColor != targetColor)
return; //return and do nothing
else
{
pixels[node.X, node.Y].CellColor = replaceColor;
//recurse
//try to fill one step to the right
FloodFill(new Point(node.X + 1, node.Y), targetColor, replaceColor);
//try to fill one step to the left
FloodFill(new Point(node.X - 1, node.Y), targetColor, replaceColor);
//try to fill one step to the north
FloodFill(new Point(node.X, node.Y - 1), targetColor, replaceColor);
//try to fill one step to the south
FloodFill(new Point(node.X, node.Y + 1), targetColor, replaceColor);
//exit method
return;
}
}
Next I have a method that uses a Queue based fill. This method causes OutOfMemory Exceptions at runtime and is EXTREMELY slow when filling the entire canvas. If just filling a small portion of the canvas, it is somewhat effective:
private void QueueFloodFill(Point node, Color targetColor, Color replaceColor)
{
Queue<Point> points = new Queue<Point>();
if (pixels[node.X, node.Y].CellColor != targetColor)
return;
points.Enqueue(node);
while (points.Count > 0)
{
Point n = points.Dequeue();
if (pixels[n.X, n.Y].CellColor == targetColor)
pixels[n.X, n.Y].CellColor = replaceColor;
if (n.X != 0)
{
if (pixels[n.X - 1, n.Y].CellColor == targetColor)
points.Enqueue(new Point(n.X - 1, n.Y));
}
if (n.X != CANVAS_SIZE - 1)
{
if (pixels[n.X + 1, n.Y].CellColor == targetColor)
points.Enqueue(new Point(n.X + 1, n.Y));
}
if (n.Y != 0)
{
if (pixels[n.X, n.Y - 1].CellColor == targetColor)
points.Enqueue(new Point(n.X, n.Y - 1));
}
if (n.Y != CANVAS_SIZE - 1)
{
if (pixels[n.X, n.Y + 1].CellColor == targetColor)
points.Enqueue(new Point(n.X, n.Y + 1));
}
}
DrawCanvas();
return;
}
The final method that I have tried also uses a queue based floodfill. This method is MUCH faster than the previous queue based floodfill but also eventually causes OutOfMemory exceptions at runtime. Again, I have tried setting a FillDelay timer that would prevent the user from rapidly clicking but this still doesn't stop the exceptions from occurring. Another bug with this one is that it has a hard time properly filling small areas. I see no point in fixing this until I can get it to not crash.
private void RevisedQueueFloodFill(Point node, Color targetColor, Color replaceColor)
{
Queue<Point> q = new Queue<Point>();
if (pixels[node.X, node.Y].CellColor != targetColor)
return;
q.Enqueue(node);
while (q.Count > 0)
{
Point n = q.Dequeue();
if (pixels[n.X, n.Y].CellColor == targetColor)
{
Point e = n;
Point w = n;
while ((w.X != 0) && (pixels[w.X, w.Y].CellColor == targetColor))
{
pixels[w.X, w.Y].CellColor = replaceColor;
w = new Point(w.X - 1, w.Y);
}
while ((e.X != CANVAS_SIZE - 1) && (pixels[e.X, e.Y].CellColor == targetColor))
{
pixels[e.X, e.Y].CellColor = replaceColor;
e = new Point(e.X + 1, e.Y);
}
for (int i = w.X; i <= e.X; i++)
{
Point x = new Point(i, e.Y);
if (e.Y + 1 != CANVAS_SIZE - 1)
{
if (pixels[x.X, x.Y + 1].CellColor == targetColor)
q.Enqueue(new Point(x.X, x.Y + 1));
}
if (e.Y - 1 != -1)
{
if (pixels[x.X, x.Y - 1].CellColor == targetColor)
q.Enqueue(new Point(x.X, x.Y - 1));
}
}
}
}
}
Thanks for everyone's help! All of these methods are based on pseudo code on wikipedia.
EDIT:
I selected the RevisedQueueFloodFill and modified as suggested so that no variables are declared within the loops. An OutOfMemory is still generated. Even with a filldelay timer.
private void RevisedQueueFloodFill(Point node, Color targetColor, Color replaceColor)
{
Queue<Point> q = new Queue<Point>();
if (pixels[node.X, node.Y].CellColor != targetColor)
return;
q.Enqueue(node);
Point n, e, w, x;
while (q.Count > 0)
{
n = q.Dequeue();
if (pixels[n.X, n.Y].CellColor == targetColor)
{
e = n;
w = n;
while ((w.X != 0) && (pixels[w.X, w.Y].CellColor == targetColor))
{
pixels[w.X, w.Y].CellColor = replaceColor;
w = new Point(w.X - 1, w.Y);
}
while ((e.X != CANVAS_SIZE - 1) && (pixels[e.X, e.Y].CellColor == targetColor))
{
pixels[e.X, e.Y].CellColor = replaceColor;
e = new Point(e.X + 1, e.Y);
}
for (int i = w.X; i <= e.X; i++)
{
x = new Point(i, e.Y);
if (e.Y + 1 != CANVAS_SIZE - 1)
{
if (pixels[x.X, x.Y + 1].CellColor == targetColor)
q.Enqueue(new Point(x.X, x.Y + 1));
}
if (e.Y - 1 != -1)
{
if (pixels[x.X, x.Y - 1].CellColor == targetColor)
q.Enqueue(new Point(x.X, x.Y - 1));
}
}
}
}
}
Ok a couple of things:
C# has a recursive limit (determined by stack size) of a few thousand in depth.
This means you can't go DEEPER in recursion downward without causing a stack overflow. As soon as a method returns its pointer is popped off the stack. Your problem is not the same as an OutOfMemoryException. The stack holds pointers not actual memory and as such is not meant to hold thousands of pointers.
Garbage collection is what's causing your out of memory exception. You need to stop declaring variables inside of your loops. The garbage collector sees these as "still in scope" and will not free up the memory space until the loop completes all iterations. But if you use the same memory address, it will just overwrite it each time and hardly use any memory.
To be clear:
for (int i = w.X; i <= e.X; i++)
{
Point x = new Point(i, e.Y);
}
Should be like this:
Point x;
for(int i = w.X; i<= e.X; i++)
{
x = new Point(i, e.Y);
}
This will reuse the memory address like you would want it to.
Hope that helps!
I have no idea if this will work, but my own suspicion is that a lot more memory is being used than necessary due to all the 'new' operators, and perhaps due to the intensive nature of the algorithm, the garbage collector didn't have a chance to kick in?
I've rewritten the algorithm so that all the Point variables just get reused rather, but I've not currently got a way of testing this.
I've also taken the liberty of altering the first few lines of code, but this is because of a pet peeve of mine that most flood-fill algorithms you find out there need the user to specify the target colour, when in fact you can simply get the target colour automatically from the point given in the argument.
Anyway, have a try at using this, or otherwise just laugh at it:
private void RevisedQueueFloodFill(Point node, Color replaceColor)
{
Color targetColor = pixels[node.X, node.Y].CellColor;
if (targetColor == replaceColor) return;
Queue<Point> q = new Queue<Point>();
q.Enqueue(node);
Point n, t, u;
while (q.Count > 0)
{
n = q.Dequeue();
if (pixels[n.X, n.Y].CellColor == targetColor)
{
t = n;
while ((t.X > 0) && (pixels[t.X, t.Y].CellColor == targetColor))
{
pixels[t.X, t.Y].CellColor = replaceColor;
t.X--;
}
int XMin = t.X + 1;
t = n;
t.X++;
while ((t.X < CANVAS_SIZE - 1) &&
(pixels[t.X, t.Y].CellColor == targetColor))
{
pixels[t.X, t.Y].CellColor = replaceColor;
t.X++;
}
int XMax = t.X - 1;
t = n;
t.Y++;
u = n;
u.Y--;
for (int i = XMin; i <= XMax; i++)
{
t.X = i;
u.X = i;
if ((t.Y < CANVAS_SIZE - 1) &&
(pixels[t.X, t.Y].CellColor == targetColor)) q.Enqueue(t);
if ((u.Y >= 0) &&
(pixels[u.X, u.Y].CellColor == targetColor)) q.Enqueue(u);
}
}
}
}
In the third method, you should check the pixels to the immediate west and east of the current point. Instead of checking pixels[w.X, w.Y] you should be checking pixels[w.X - 1, w.Y] and instead of pixels[e.X, e.Y] you should have pixels[e.X + 1, e.Y]. Here is my take on your third method:
private void RevisedQueueFloodFill(Point node, Color targetColor, Color replaceColor)
{
if (pixels[node.X, node.Y].CellColor != targetColor) return;
Queue<Point> Q = new Queue<Point>();
Q.Enqueue(node);
while (Q.Count != 0)
{
Point n = Q.Dequeue();
if (pixels[n.X, n.Y].CellColor == targetColor)
{
int y = n.Y;
int w = n.X;
int e = n.X;
while (w > 0 && pixels[w - 1, y].CellColor == targetColor) w--;
while (e < CANVAS_SIZE - 1 && pixels[e + 1, y].CellColor == targetColor) e++;
for (int x = w; x <= e; x++)
{
pixels[x, y].CellColor = replaceColor;
if (y > 0 && pixels[x, y - 1].CellColor == targetColor)
{
Q.Enqueue(new Point(x, y - 1));
}
if (y < CANVAS_SIZE - 1 && pixels[x, y + 1].CellColor == targetColor)
{
Q.Enqueue(new Point(x, y + 1));
}
}
}
}
}
The issue here with the basic algorithm is that you queue multiple visits to a point and do a breadth-first search. This means that you create several copies of the same point during each pass. This accumulates exponentially, since each point is allowed to spread (queue more points), even if it's not the target color (already been replaced.)
Set the color at the same time that you Enqueue them (rather than on Dequeue), so that you never end up adding them to the Queue twice.

Categories