Предотвращение исключения System.StackOverflowException при рекурсивном вызове метода - PullRequest
0 голосов
/ 31 августа 2018

Я новичок в c # (или вообще кодирую), и я думаю, что этот вопрос действительно глуп и запутан (я знаю, что делаю это нелегко), но, пожалуйста, помогите мне.

Я пытаюсь сделать тральщик с приложением формы. Я сделал 10 х 10 кнопок, и если вы нажмете на них, будет показано количество мин вокруг него. Если там есть мина, появится буква «F» (первая буква «Ложь»).

Есть конструктор, который содержит кнопку, положение x и y, список окружающих блоков, количество мин вокруг него и логическое значение, которое указывает, есть ли мина или нет.

Я попытался очистить 8 окружающих блоков (из списка), когда игрок щелкнул блок, в котором нет шахты, и если у блока, окружающего этот блок, также нет шахты вокруг него, эти блоки, окружающие этот блок, также будут очищены. Метод использует foreach, чтобы выявить и проверить количество мин вокруг этого блока. Если мин нет, к этому блоку будет применен тот же метод (рекурсивный вызов метода). Проблема в том, что я продолжаю получать System.StackOverflowException.

Я как-то понимаю, почему это происходит, но я просто не могу придумать другой путь.

//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)
                    {
                    }
                }
            }
        }
    }
}

Вот класс Square:

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);
    }
}

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

Я новичок в c # (или кодировании в целом), и я думаю, что этот вопрос действительно глуп и запутан (я знаю, что делаю это нелегко)

Эта тема смущает многих новых разработчиков; не переживайте об этом!

Если мин нет, к этому блоку будет применен тот же метод (рекурсивный вызов метода).

Рекурсивные методы могут сбивать с толку, но если вы создадите их с использованием стандартного шаблона, вы избежите SO-исключений. Вы не разработали свой, используя стандартный шаблон.

Стандартный шаблон для успешных рекурсивных методов:

  • Я в деле, которое не требует рекурсии?
  • Если да, выполните необходимые вычисления для получения желаемого эффекта и возврата. Теперь проблема решена.
  • Если нет, то мы собираемся пройти курс лечения.
  • Разбейте текущую проблему на несколько меньших проблем.
  • Решите каждую меньшую проблему с помощью повторения.
  • Объедините решения меньшей задачи для решения текущей проблемы.
  • Теперь проблема решена, поэтому вернитесь.

Самая важная вещь в проектировании рекурсивного метода заключается в том, что каждая рекурсия должна решать меньшую проблему , а последовательность небольших проблем должна заканчиваться в случае это не требует рекурсии . Если эти два условия не будут выполнены, то вы получите переполнение стека.

Интернализуйте этот шаблон, и каждый раз, когда вы пишете рекурсивный метод, на самом деле выписываете его:

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
   }
 }

Заполните этот шаблон вашим реальным кодом, и вы с большей вероятностью избежите переполнения стека. Я писал рекурсивные методы на протяжении десятилетий, и я до сих пор придерживаюсь этой схемы.

Теперь, в вашем примере, в каком случае не требуется рекурсия? Должен быть один, поэтому запишите, что это такое. Далее как вы будете гарантировать, что рекурсия решит меньшую проблему ? Это часто трудный шаг! Подумайте об этом.

0 голосов
/ 31 августа 2018

Переполнение стека происходит, потому что zeroReveal рекурсивно вызывает себя навсегда. Чтобы это исправить, нам нужно найти способы, в которых он нам не нужен, для дальнейших звонков самому себе.

Название метода дает нам подсказку. Если квадрат уже был обнаружен, то, конечно, метод не должен ничего делать, поскольку он уже был обнаружен.

Похоже, свойство кнопки Text является пустой строкой, если оно еще не было обнаружено. Поэтому измените foreach, чтобы он не обрабатывал уже выявленные квадраты:

foreach (Square s in list)
{
    if (s.getButton().Text == ""))
    {
        // existing code in the foreach loop goes here
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...