How to make picturebox move along edges of screen without clipping? - c#
In a sample car game, I want to be able to make the car move within the screen, and I want it to be "bound" by the edges. However, it keeps clipping and goes beyond the screen.
I made the picturebox square and used the dimensions of the picture of the car, which is centered in the picturebox) to base the model of the code. The picturebox is 100x100 and the picture of the car (when facing sideways) is roughly 50x100. For example, if the car is going sideways against the edges of the screen, I would make the Y location of the picturebox -25. However, I feel that this is too inconvenient, because I may need to change the picture of the car.
Point pNewLoc = carLocation;
int iX = Width - pbCar.Width;
int iY = Height - pbCar.Height;
if (pNewLoc.X <= 0 || pNewLoc.X >= iX)
{
if (pNewLoc.X <= 0)
{
if (iDirection == CarDirection.UP || iDirection == CarDirection.DOWN)
{
pNewLoc.X = -25;
}
else
{
pNewLoc.X = 0;
}
}
if (pNewLoc.X >= iX)
{
if (iDirection == CarDirection.UP || iDirection == CarDirection.DOWN)
{
pNewLoc.X = iX + 25;
}
else
{
pNewLoc.X = iX;
}
}
}
if (pNewLoc.Y <= 0 || pNewLoc.Y >= iY)
{
if (pNewLoc.Y <= 0)
{
if (iDirection == CarDirection.LEFT || iDirection == CarDirection.RIGHT)
{
pNewLoc.Y = -25;
}
else
{
pNewLoc.Y = 0;
}
}
if (pNewLoc.Y >= iY)
{
if (iDirection == CarDirection.LEFT || iDirection == CarDirection.RIGHT)
{
pNewLoc.Y = iY + 25;
}
else
{
pNewLoc.Y = iY;
}
}
}
carLocation = pNewLoc;
This code works just fine, but I feel it's too long and is too inconvenient.
If the code is working, then, it can definetly be summarized:
int verticalDirection = Convert.ToInt32(iDirection == CarDirection.UP || iDirection == CarDirection.DOWN);
int horizontalDirection = Convert.ToInt32(iDirection == CarDirection.LEFT || iDirection == CarDirection.RIGHT);
if (pNewLoc.X <= 0)
{
pNewLoc.X = -25 * verticalDirection;
}
else
{
pNewLoc.X = iX + 25 * verticalDirection;
}
if (pNewLoc.Y <= 0)
{
pNewLoc.Y = -25 * horizontalDirection;
}
else
{
pNewLoc.Y = iY + 25 * horizontalDirection;
}
If you change the size of the picture, then you don't have to use a constant like 25 but declare a variable which is dependent on the size of the car picture.
Related
Can I execute only specific portion of nested if else in c#?
This is not the actual code but it's what I want to do. for loop is must but the nested if else loop inside should be executed according to the value of count_final which can be random between 1 to 3. Like if the value of count_final is 3, all if...else loop should be considered. but if the value of count_final is 2, then only if...(1st)else if and else part only be executed. And if count_final=1 then only if and else part is executed (not any else-if). Thought of putting another if...else within every if...else and checking count_final, but what if I'm not getting values of count2 and count3 when count_final=1. Same, when count_final=2, I'm not getting the value of count3. Ask in comment if you don't understand my question. int count_final=Session["abc"]; //count_final=1; //count_final=2; //count_final=3; for(int i=1;i<=10;i++) { if ((count1 <= count5) && (count1 <= count6)) { Label1.Text="Hello1"; } else if (count2 <= count4 && count2 <= count6) { Label2.Text="Hello2"; } else if (count3 <= count4 && count3 <= count5) { Label3.Text="Hello3"; } else { Label1.Text="Hello1"; } }
Seems you have collection of "conditions" where amount of executed conditions depend on value of finalCount. var rules = new Func<string>[] { () => (count1 <= count5 && count1 <= count6) ? "Hello1" : null, () => (count2 <= count4 && count2 <= count6) ? "Hello2" : null, () => (count3 <= count4 && count3 <= count5) ? "Hello3" : null }; Label1.Text = rules.Take(finalCount) .Select(rule => rule()) .Where(result => result != null) .DefaultIfEmpty("Hello1") .First(); Of course this solution is assuming that finalCount is always 1, 2 or 3. DefaultIfEmpty is playing role of last else - will be used all conditions fails.
if i understand right.. which is a little unlikely just add more criteria to your ifs! if ((count1 <= count5) && (count1 <= count6)) { if (count_final == 3 || count_final == 2) Label1.Text="Hello1"; } else if (count2 <= count4 && count2 <= count6) { if (count_final == 3) Label2.Text="Hello2"; } else if (count3 <= count4 && count3 <= count5) { if (count_final == 3) Label3.Text="Hello3"; } else { if (count_final == 3 || count_final == 1) Label1.Text="Hello1"; } Remembering that from what I understood there will be loops in that can achieve nothing, eg, if count_final == 2 and its not less or equal to count5 or count6, nothing will happen, same as for count_final == 1, if it matches any of the first bits the last else wont happen.
I suggest extending the conditions of your else if statements: int count_final=Session["abc"]; //count_final=1; //count_final=2; //count_final=3; for(int i=1; i<=10; i++) { if ((count1 <= count5) && (count1 <= count6)) { Label1.Text="Hello1"; } else if (count_final >= 2 && count2 <= count4 && count2 <= count6) { Label2.Text="Hello2"; } else if (count_final >= 3 && count3 <= count4 && count3 <= count5) { Label3.Text="Hello3"; } else { Label1.Text="Hello1"; } } When count_final == 1 this will not try to evaluate count2, count3 or count4 (which I understand is a requirement) because && will not evaluate its right hand side when there is false on the left.
Is this what you are looking for? To use less if/else statements: int count_final = Session["abc"]; // random between 1 and 3 for(int i=1; i <= 10; i++) { switch(count_final) { case 3: Label1.Text="Hello1"; // no break; so all gets executed case 2: Label2.Text="Hello2"; case 1: Label3.Text="Hello3"; default: Label1.Text="Hello1"; } }
C# Tic-Tac-Toe Minimax
I am currently trying my hand at making a minimax AI for tictactoe. My goal was that it should suffice for larger boards as well. However, I am having quite a hard time wrapping my head around how to implement the algorithm. I have read countless different descriptions of the algorithm but I still don't seem to be able to make it work. My result as of yet is an incredibly stupid AI. Here is my code for it. Edit: The main point I am wondering about is how I make the AI value me not winning over forwarding itself towards the win. As of now it doesn't really care that I will win the next turn. namespace TicTacToe_AI { public class Move //A class for moves { public int x, y, value, MoveNumber; void SetMove(int a, int b) { x = a; y = b; } public Move(int a, int b) { SetMove(a, b); } public Move() { } } class AI //AIClass { //The minimax algorithm public Move CalculateMoves(int[,] Board, int BoardSize, int Depth, Move BestMoveAI, Move BestMovePlayer, int OriginalDepth, int CurrentTurn) { Depth--; //Decrease the depth for each iteration bool Alpha = false; //Alpha-beta pruning - needs improvement bool Beta = false; bool WinningMove = false; if (CurrentTurn == 1) CurrentTurn = 2; if (CurrentTurn == 2) CurrentTurn = 1; List<Move> DifferentMoves = new List<Move>(); List<Move> PossibleMoves = new List<Move>(); for (int i = 0; i < BoardSize; i++) //Add all possible moves to a list { for (int j = 0; j < BoardSize; j++) { if (Board[i, j] == 0) { Move Possible = new Move(i, j); PossibleMoves.Add(Possible); } } } if (CurrentTurn == 2 && Depth >= 0 && Depth < BestMoveAI.MoveNumber) Alpha = true; //Alpha-beta pruning if (CurrentTurn == 1 && Depth >= 0 && Depth < BestMovePlayer.MoveNumber) Beta = true; if(Alpha || Beta) { foreach (Move TryMove in PossibleMoves) //Try every possible move to see if they are a winning move { int[,] Trying = new int[BoardSize, BoardSize]; Trying = (int[,])Board.Clone(); Trying[TryMove.x, TryMove.y] = CurrentTurn; TryMove.MoveNumber = OriginalDepth - Depth; if (Form1.Win(Trying) == 2) { TryMove.value = -1; DifferentMoves.Add(TryMove); if (Depth + 1 == OriginalDepth) { if (TryMove.MoveNumber < BestMoveAI.MoveNumber) BestMoveAI = TryMove; WinningMove = true; break; } else { WinningMove = true; if (TryMove.MoveNumber < BestMoveAI.MoveNumber) BestMoveAI = TryMove; return TryMove; } } else if (Form1.Win(Trying) == 1) { WinningMove = true; TryMove.value = 1; BestMovePlayer = TryMove; DifferentMoves.Add(TryMove); return TryMove; } } if (!WinningMove) // If no winning move was found, try recursively searching for a winning move { if (Alpha || Beta) { foreach (Move TryMove2 in PossibleMoves) { int[,] TestMove = new int[BoardSize, BoardSize]; TestMove = (int[,])Board.Clone(); TestMove[TryMove2.x, TryMove2.y] = CurrentTurn; TryMove2.value = CalculateMoves(TestMove, BoardSize, Depth, BestMoveAI, BestMovePlayer, OriginalDepth, CurrentTurn).value; DifferentMoves.Add(TryMove2); } } } } //Find the best possible move and return it BestMoveAI.value = 0; BestMoveAI.MoveNumber = OriginalDepth; BestMovePlayer.value = 0; BestMovePlayer.MoveNumber = OriginalDepth; if (CurrentTurn == 2) { foreach (Move AllMoves in DifferentMoves) { if (AllMoves.value <= BestMoveAI.value && AllMoves.MoveNumber <= BestMoveAI.MoveNumber) { BestMoveAI = AllMoves; } } return BestMoveAI; } else if(CurrentTurn == 1) { foreach (Move AllMoves in DifferentMoves) { if (AllMoves.value >= BestMovePlayer.value && AllMoves.MoveNumber <= BestMovePlayer.MoveNumber) { BestMovePlayer = AllMoves; } } return BestMovePlayer; } Move BadMove = new Move(); BadMove.value = 0; BadMove.MoveNumber = Depth; return BadMove; } } }
Declare variable in if statement and then using it later in code
I am programming a console "game" and I need to declare a "hp" of character in IF statements which depends on level of this character. if ((char_level > 0) && (char_level < 4)) { char_hp = 100; } if ((level > 4) && (level < 6)) { char_hp = 120; } if ((level > 6) && (level < 8)) { char_hp = 150; } if ((level > 8) && (level < 10)) { char_hp = 180; } Then I need to use it later in code in a fight. After a successful fight character gets a new level and after that, program will get back to check these IF statements and if level is bigger than 4, character's hp will be increased to 120. But declaration of char_hp in IF statements does not change the value of hp in general and when the next fight comes after reaching level 4, character's hp is still like at the end of previous battle was. I am new in C# programming and I have tried everything but I can't solve it, if it is possible. The same problem is with the "hp" of enemy that is randomly generated...then I need to use it in that fight if((level>0) && (level<4)) { random_enemy_hp = RND.Next(89, 111); goto enemy; } if((level>4) && (level<6)) { random_enemy_hp = RND.Next(109, 141); goto enemy; } if ((level > 6) && (level < 8)) { random_enemy_hp = RND.Next(149, 184); goto enemy; } if ((level > 8) && (level < 10)) { random_enemy_hp = RND.Next(189, 221); goto enemy; } EDIT: I meant "saving values to variables" in IF statements, so I can use them later in code. This is how my code starts, then there are "Console.WriteLine()"-s, principe of a fight and statements shown above. string name; int char_hp = 100; int level = 1; int random_enemy_hp; Random RND = new Random();
You're completely on the wrong track. You should be doing something like: int[] charLevelHp = { 100, 100, 100, 100, 120, 120, 120, 150, 150, 180, 180 }; int charLevel = 1; int charHp = charLevelHp[charLevel];
I can't help but notice that you're comparing with char_level for your first couple if-statement, but you're comparing to level for your subsequent if-statements if ((char_level > 0) && (char_level < 4)) { char_hp = 100; } if ((level > 4) && (level < 6)) { char_hp = 120; } I think you might have intended to use char_level for all of the conditions. if ((char_level > 0) && (char_level < 4)) { char_hp = 100; } if ((char_level > 4) && (char_level< 6)) { char_hp = 120; } If that's the issue, it would be consistent with the kinds of errors you're seeing.
how can i drag a 2d level array from a txt file
i am working on improving this tower defence gtame that i finished from this tutorial http://xnatd.blogspot.com/ i now wish to load the levels from a text file but not quite sure how i would do this using a streamreader, any tips? heres the source code for my level class; public class Level { protected int temperature; protected int levelNo; private Queue<Vector2> waypoints = new Queue<Vector2>(); public Queue<Vector2> Waypoints { get { return waypoints; } } int[,] map = new int[,] { {0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,}, {0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,}, {0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,}, {0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,}, {0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,}, {0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,}, {0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,}, {0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,}, {0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,}, {0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,}, {0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,}, {0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,}, {0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,}, {0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,}, {0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,}, {0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,}, }; public int Temperature // get the width of the level (how many numbers are in a row) { get { return temperature; } } public int LevelNo // get the width of the level (how many numbers are in a row) { get { return levelNo; } set { levelNo = value; } } public int Width // get the width of the level (how many numbers are in a row) { get { return map.GetLength(1); } } public int Height // get the height of our level (how many numbers are there in a column) { get { return map.GetLength(0); } } public int GetIndex(int cellX, int cellY) // return the index of the requested cell. { if (cellX < 0 || cellX > Width - 1 || cellY < 0 || cellY > Height - 1) { return 0; } else { return map[cellY, cellX]; } } public List<Texture2D> tileTextures = new List<Texture2D>(); // list to contain all the textures /// <summary> /// CONSTRUCTOR NEW LEVEL /// </summary> public Level() { SetWayPoints(map); this.temperature = 1000; this.levelNo = 2; } private void SetWayPoints(int[,] map) { int currentPosVal = map[0, 0]; int lPos = 0; int rPos = 0; int uPos = 0; int dPos = 0; int storedXPos = 99; int storedYPos = 99; int endstoredXPos = 99; int endstoredYPos = 99; int lastY = 0; int lastX = 0; //Search top ROW for start for (int i = 0; i < Width; i++) { currentPosVal = map[0, i]; if (currentPosVal == 1) { storedXPos = i; storedYPos = 0; waypoints.Enqueue(new Vector2(storedXPos, storedYPos) * 32); lastX = storedXPos; lastY = storedYPos; break; } } //if start not set if (storedXPos == 99 && storedXPos == 99) { //look in 1st coloum for start for (int i = 0; i < Height; i++) { currentPosVal = map[i, 0]; if (currentPosVal == 1) { storedXPos = 0; storedYPos = i; waypoints.Enqueue(new Vector2(storedXPos, storedYPos) * 32); lastX = storedXPos; lastY = storedYPos; break; } } } //search end COLOUM for end for (int i = 0; i < Height; i++) { currentPosVal = map[i, Width - 1]; if (currentPosVal == 1) { endstoredXPos = Width - 1; endstoredYPos = i; } } //If end not set look in bottom row for end if (endstoredXPos == 99 && endstoredYPos == 99) { for (int i = 0; i < Width; i++) { currentPosVal = map[7, i]; if (currentPosVal == 1) { endstoredXPos = i; endstoredYPos = Height - 1; } } if (endstoredXPos == 99 && endstoredYPos == 99) { } } // start midlle loop while (true) { lPos = 0; rPos = 0; uPos = 0; dPos = 0; //If current pos is not down the left hand edge if (storedXPos > 0) { lPos = map[storedYPos, storedXPos - 1]; } //If current pos square is not down the right hand edge if (storedXPos < Width - 1) { rPos = map[storedYPos, storedXPos + 1]; } //If current pos square is not in the top row if (storedYPos > 0) { uPos = map[storedYPos - 1, storedXPos]; } //If current pos square is not in the bottom row if (storedYPos < Height - 1) { dPos = map[storedYPos + 1, storedXPos]; } if (lPos == 1 && (lastX != storedXPos - 1 || lastY != storedYPos)) { lastX = storedXPos; lastY = storedYPos; storedXPos--; waypoints.Enqueue(new Vector2(storedXPos, storedYPos) * 32); } else if (rPos == 1 && (lastX != storedXPos + 1 || lastY != storedYPos)) { lastX = storedXPos; lastY = storedYPos; storedXPos++; waypoints.Enqueue(new Vector2(storedXPos, storedYPos) * 32); } else if (dPos == 1 && (lastX != storedXPos || lastY != storedYPos + 1)) { lastX = storedXPos; lastY = storedYPos; storedYPos++; waypoints.Enqueue(new Vector2(storedXPos, storedYPos) * 32); } else if (uPos == 1 && (lastX != storedXPos || lastY != storedYPos - 1)) { lastX = storedXPos; lastY = storedYPos; storedYPos--; waypoints.Enqueue(new Vector2(storedXPos, storedYPos) * 32); } if (storedXPos == endstoredXPos && storedYPos == endstoredYPos) { break; } } } public void AddTexture(Texture2D texture) // method adds a texture to our texture list. { tileTextures.Add(texture); } //Reads number from array, store its value in textureIndex, Use textureIndex to get the texture from tileTextures, public void Draw(SpriteBatch batch) //Draw appropiate texture, Repeat through all elements of the array { int textureIndex; Texture2D texture; for (int x = 0; x < Width; x++) { for (int y = 0; y < Height; y++) { if (levelNo == 0) { textureIndex = map[y, x]; if (textureIndex == -1) continue; texture = tileTextures[textureIndex]; batch.Draw(texture, new Rectangle(x * 32, y * 32, 32, 32), Color.White); } if (levelNo > 0) { textureIndex = map[y, x]; textureIndex += (levelNo * 2); if (textureIndex == -1) continue; texture = tileTextures[textureIndex]; batch.Draw(texture, new Rectangle(x * 32, y * 32, 32, 32), Color.White); } } } } } }
Since C# is compiled and can't do evals like scripted languages can (not that you should be doing that anyway) you should probably use streamreader to read the data from file (formatted perhaps as delimited text: csv or tsv) Assuming you're loading something similar to the map you have up there then a map file could look something like 0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0 0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0 0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0 0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0 0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0 0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0 0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0 0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0 0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0 0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0 0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0 0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0 0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0 0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0 0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0 0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1 where you would then loop through the file and read each line As each line is read in, you can then split the line by "," to create a 1d array which can then be assigned to an index in the 2d array. Loop for each line to create the entire 2d map array Take a look at this if you need help splitting strings in C#; note that the sample code splits at spaces " " and that you should use s.Split(','); for csv
With a file that small i would use: File.ReadAllLines , and after readling it loop over the lines with a foreach loop.
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.