1

I am new to c# (or coding in general) and I guess this question is really stupid and confusing (I know I'm doing it a hard way) but please help me.

I'm trying to make a minesweeper with form app. I made a 10 x 10 of buttons and if you click it, number of mines around it will be revealed. If a mine is there "F" (the first letter of "False") will appear.

There's a constructor that contains the button, x and y position, list of surrounding blocks, number of mines around it, and a boolean that indicates if there's a mine or not.

What I tried to do was to make the 8 surrounding blocks (from the list) cleared when the player clicked a block with no mine around it and if the block surrounding that block also doesn't have any mine around it, these blocks that surrounding that block will also be cleared. The method uses foreach to reveal and check the number of mines around that block. If there's no mines, same method will be applied to that block (calling the method recursively). The problem is that I keep getting System.StackOverflowException.

I somehow understand why it's happening but I just can't come up with the other way.

//scroll to the bottom for the method with the problem

private void Form1_Load(object sender, EventArgs e)
{
    Random random = new Random();
    Button[,] buttons = new Button[10, 10]
    {
        { r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, r0c7, r0c8, r0c9 },
        { r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, r1c6, r1c7, r1c8, r1c9 },
        { r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, r2c6, r2c7, r2c8, r2c9 },
        { r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, r3c7, r3c8, r3c9 },
        { r4c0, r4c1, r4c2, r4c3, r4c4, r4c5, r4c6, r4c7, r4c8, r4c9 },
        { r5c0, r5c1, r5c2, r5c3, r5c4, r5c5, r5c6, r5c7, r5c8, r5c9 },
        { r6c0, r6c1, r6c2, r6c3, r6c4, r6c5, r6c6, r6c7, r6c8, r6c9 },
        { r7c0, r7c1, r7c2, r7c3, r7c4, r7c5, r7c6, r7c7, r7c8, r7c9 },
        { r8c0, r8c1, r8c2, r8c3, r8c4, r8c5, r8c6, r8c7, r8c8, r8c9 },
        { r9c0, r9c1, r9c2, r9c3, r9c4, r9c5, r9c6, r9c7, r9c8, r9c9 }
    };

    Square[,] squares = new Square[10, 10];

    for (int i = 0, ii = 0, iii = 0; i < 100; i++, ii++)
    {
        if (ii == 10)
        {
            ii = 0;
            iii++;
        }
        squares[ii, iii] = new Square(i, buttons[ii, iii], ii, iii, 0, true);
    }

    List<int> randoms = new List<int>();
    for (int i = 0; i < 10; i++)
    {
        int ii = random.Next(100);
        if (!randoms.Contains(ii))
        {
            squares[ii % 10, ii / 10].setSafe(false);
        }
        else
        {
            i--;
        }
        randoms.Add(ii);
    }

    for (int i = 0; i < 10; i++)
    {
        for (int ii = 0; ii < 10; ii++)
        {
            for (int iii = -1; iii < 2; iii++)
            {
                for (int iiii = -1; iiii < 2; iiii++)
                {
                    try
                    {
                        if (squares[i + iii, ii + iiii].getSafe() == false)
                            squares[i, ii].addNumber();
                    }
                    catch (System.IndexOutOfRangeException)
                    {
                    }
                }
                //if (squares[i, ii].getSafe() == false) squares[i, ii].getButton().Text = squares[i, ii].getSafe().ToString();
                //else squares[i, ii].getButton().Text = squares[i, ii].getNumber().ToString();
            }
        }
    }

    for (int i = 0; i < 10; i++)
    {
        for (int ii = 0; ii < 10; ii++)
        {
            for (int iii = -1; iii < 2; iii++)
            {
                for (int iiii = -1; iiii < 2; iiii++)
                {
                    try
                    {
                        squares[i, ii].addList(squares[i + iii, ii + iiii]);
                    }
                    catch (System.IndexOutOfRangeException)
                    {
                    }
                }
            }
        }
    }
}

Here's the Square class:

public class Square
{
    int id;
    Button button;
    int x;
    int y;
    int number;
    bool safe;
    List<Square> list = new List<Square>();

    public Square(int id, Button button, int x, int y, int number, bool safe)
    {
        this.id = id;
        this.button = button;
        this.x = x;
        this.y = y;
        this.number = number;
        this.safe = safe;

        button.Text = "";

        button.Click += button_Click;
    }

    public int getId()
    {
        return id;
    }

    public void setId(int i)
    {
        id = i;
    }

    public Button getButton()
    {
        return button;
    }

    public void setButton(Button b)
    {
        button = b;
    }

    public int getX()
    {
        return x;
    }

    public void setX(int i)
    {
        x = i;
    }

    public int getY()
    {
        return y;
    }

    public void setY(int i)
    {
        y = i;
    }

    public int getNumber()
    {
        return number;
    }

    public void setNumber(int i)
    {
        number = i;
    }

    public void addNumber()
    {
        number++;
    }

    public bool getSafe()
    {
        return safe;
    }

    public void setSafe(bool b)
    {
        safe = b;
    }

    private void button_Click(object sender, EventArgs e)
    {
        if (getSafe() == false) button.Text = getSafe().ToString();
        else button.Text = getNumber().ToString();
        if (getNumber() == 0) zeroReveal();
    }

//---------------------------------------------------
// this is the method that reveals surrounding blocks
//---------------------------------------------------

    private void zeroReveal()
    {
        foreach (Square s in list)
        {
            //revealing the blocks
            s.getButton().Text = s.getNumber().ToString();
            //call the same method if there's no mine
            //this is the line that keeps giving me exception
            if (s.getNumber() == 0) s.zeroReveal();
        }
    }

//-----------------------------------------------------

    public List<Square> getList()
    {
        return list;
    }

    public void setList(List<Square> sl)
    {
        list = sl;
    }

    public void addList(Square s)
    {
        list.Add(s);
    }
}
Frank Fajardo
  • 7,034
  • 1
  • 29
  • 47
  • [Relevant Google Easter egg](https://www.google.co.jp/search?q=recursion&oq=recursion&aqs=chrome..69i57j69i61j69i60l3j0.974j0j7&sourceid=chrome&ie=UTF-8). If `a()` calls `b()` calls `c()`, this pushes 3 items to the stack, which are popped off when you return context to b(), and then to a(). If a() calls a() calls a(), etc. you'll end up infinitely pushing items to the stack. The stack is only so big, so you get this error. In short: you need to stop recursing at some point. – ProgrammingLlama Aug 31 '18 at 02:58
  • Possible duplicate of [Recursive Function Calls Throw StackOverFlowException](https://stackoverflow.com/questions/18581502/recursive-function-calls-throw-stackoverflowexception) – ProgrammingLlama Aug 31 '18 at 03:02
  • You should also look at changing those private fields in the `Square` class to public properties. This way you don't need all those `getProperty` and `setProperty` methods. – Rufus L Aug 31 '18 at 03:10

2 Answers2

4

I am new to c# (or coding in general) and I guess this question is really stupid and confusing (I know I'm doing it a hard way)

This topic confuses many a new developer; don't stress out about it!

If there's no mines, same method will be applied to that block (calling the method recursively).

Recursive methods can be confusing but if you design them using the standard pattern, you will avoid SO exceptions. You have not designed yours using the standard pattern.

The standard pattern for successful recursive methods is:

  • Am I in a case that requires no recursion?
  • If yes, do the necessary computations to produce the desired effect and return. The problem is now solved.
  • If no, then we're going to recurse.
  • Break the current problem down into some number of smaller problems.
  • Solve each smaller problem by recursing.
  • Combine the solutions of the smaller problem to solve the current problem.
  • The problem is now solved, so return.

The most important thing about designing a recursive method is that each recursion must be solving a smaller problem, and the sequence of smaller problems must bottom out at a case that does not require recursion. If those two conditions are not met, then you will get a stack overflow.

Internalize that pattern, and every time you write a recursive method, actually write it out:

int Frob(int blah)
{
   if (I am in the base case)
   {
     solve the base case
     return the result
   }
   else
   {
     find smaller problems
     solve them
     combine their solutions
     return the result
   }
 }

Fill in that template with your real code, and you will be much more likely to avoid stack overflows. I've been writing recursive methods for decades, and I still follow this pattern.

Now, in your example, what is the case that does not require recursion? There must be one, so write down what it is. Next, how will you guarantee that the recursion solves a smaller problem? That is often the hard step! Give it some thought.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
0

The stack overflow is occurring because zeroReveal is recursively calling itself forever. To fix this we need to find ways where we do not need it to make further calls to itself.

The name of the method gives us a clue. If the square has already been revealed, then surely the method does not need to do anything, since it has already been revealed.

It looks like the button's Text property is an empty string if it has not yet been revealed. So change the foreach so that it doesn't process squares that have already been revealed:

foreach (Square s in list)
{
    if (s.getButton().Text == ""))
    {
        // existing code in the foreach loop goes here
    }
}
Richardissimo
  • 5,596
  • 2
  • 18
  • 36