Ошибка System.StackOverflowException

1

Я пытаюсь создать систему генерации двумерных пещер. Когда я запускаю программу, я получаю исключение "System.StackOverflowException" после того, как я попытаюсь создать новый объект из своего собственного класса.

Мой пещерный генератор работает следующим образом:

Я создаю карту, содержащую идентификаторы (целые числа) различных типов ячеек (например, стена, вода или пустое пространство).

Прежде всего, мой класс "Карта" создает карту, заполненную стенами, а затем в центре карты создает объект "Miner". Шахтер выкапывает карту и делает пещеры. Проблема в том, что я хочу создать больше шахтеров. Итак, мой шахтер, который копает карту, создает другого Шахтера. Однако, когда я это делаю, я получаю исключение "System.StackOverflowException".

Как я могу отслеживать причину StackOverflow в моей программе. Вот мой код шахтёра:

Miner.cs

public class Miner
{
    Random rand = new Random();

    public string state { get; set; }
    public int x { get; set; }
    public int y { get; set; }
    public Map map { get; set; }
    public int minersCount;

    public Miner(Map map, string state, int x, int y)
    {
        this.map = map;
        this.state = state;
        this.x = x;
        this.y = y;
        minersCount++;

        if (state == "Active")
        {
            StartDigging();
        }
    }

    bool IsOutOfBounds(int x, int y)
    {
        if (x == 0 || y == 0)
        {
            return true;
        }
        else if (x > map.mapWidth - 2 || y > map.mapHeight - 2)
        {
            return true;
        }
        return false;
    }

    bool IsLastMiner()
    {
        if (minersCount == 1)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public void StartDigging()
    {
        if (state == "Active")
        {
            int dir = 0;
            bool needStop = false;
            int ID = -1;

            while (!needStop && !IsOutOfBounds(x, y))
            {
                while (dir == 0)
                {
                    dir = ChooseDirection();
                }

                if (!AroundIsNothing())
                {
                    while (ID == -1)
                    {
                        ID = GetIDFromDirection(dir);
                    }
                }
                else
                {
                    if (!IsLastMiner())
                    {
                        needStop = true;
                    }
                }

                if (ID == 1)
                {
                    DigToDirection(dir);
                    dir = 0;
                }

                if (ID == 0 && IsLastMiner())
                {
                    MoveToDirection(dir);
                    dir = 0;
                }

                TryToCreateNewMiner();
            }

            if (needStop)
            {
                state = "Deactive";
            }
        }
    }

    public void TryToCreateNewMiner()
    {
        if (RandomPercent(8))
        {
            Miner newMiner = new Miner(map, "Active", x, y);
        }
        else
        {
            return;
        }
    }

    bool AroundIsNothing()
    {
        if (map.map[x + 1, y] == 0 && map.map[x, y + 1] == 0 &&
            map.map[x - 1, y] == 0 && map.map[x, y - 1] == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    void MoveToDirection(int dir)
    {
        if (dir == 1)
        {
            x = x + 1;
        }
        else if (dir == 2)
        {
            y = y + 1;
        }
        else if (dir == 3)
        {
            x = x - 1;
        }
        else if (dir == 4)
        {
            y = y - 1;
        }
    }

    void DigToDirection(int dir)
    {
        if (dir == 1)
        {
            map.map[x + 1, y] = 0;
            x = x + 1;
        }
        else if (dir == 2)
        {
            map.map[x, y + 1] = 0;
            y = y + 1;
        }
        else if (dir == 3)
        {
            map.map[x - 1, y] = 0;
            x = x - 1;
        }
        else if (dir == 4)
        {
            map.map[x, y - 1] = 0;
            y = y - 1;
        }
    }

    int GetIDFromDirection(int dir)
    {
        if (dir == 1)
        {
            return map.map[x + 1, y];
        }
        else if (dir == 2)
        {
            return map.map[x, y + 1];
        }
        else if (dir == 3)
        {
            return map.map[x - 1, y];
        }
        else if (dir == 4)
        {
            return map.map[x, y - 1];
        }
        else
        {
            return -1;
        }
    }

    int ChooseDirection()
    {
        return rand.Next(1, 5);
    }

    bool RandomPercent(int percent)
    {
        if (percent >= rand.Next(1, 101))
        {
            return true;
        }
        return false;
    }
}
  • 4
    удалите весь код, который не имеет отношения к воспроизведению проблемы.
  • 0
    Где вы видите не соответствующий код ??
Показать ещё 5 комментариев
Теги:
exception
stack-overflow
system

1 ответ

5
Лучший ответ

Хотя вы можете получить StackOverflowExceptions, создав слишком много действительно больших объектов в стеке, это обычно происходит, потому что ваш код попал в состояние, когда он вызывает одну и ту же цепочку функций снова и снова. Итак, чтобы отследить причину в коде, лучшая отправная точка - определить, где ваш код вызывает себя.

Ваш код состоит из нескольких функций, которые вызываются самим классом Miner, большинство из которых тривиально

Тривиальные функции, которые не называют ничего другого в классе. Хотя эти функции могут вносить вклад в состояние, вызывающее проблему, они не являются частью цикла терминальных функций:

IsOutOfBounds(int x, int y)
bool IsLastMiner()
bool AroundIsNothing()
void MoveToDirection(int dir)
void DigToDirection(int dir)
int GetIDFromDirection(int dir)
int ChooseDirection()
bool RandomPercent(int percent)

Это оставляет ваши оставшиеся три функции

public Miner(Map map, string state, int x, int y) // Called by TryToCreateNewMiner
public void StartDigging()                        // Called by constructor
                                                  // Contains main digging loop
public void TryToCreateNewMiner()                 // Called by StartDigging

Эти три функции образуют цикл вызова, поэтому, если логика ветвления в функциях некорректна, это может вызвать цикл без конца и, следовательно, переполнение стека.

Итак, глядя на логику ветвления в функциях

шахтер

Конструктор имеет только одну ветвь, основанную на состоянии "Active". Он всегда активен, так как тот способ, которым объект всегда создается, поэтому конструктор всегда будет вызывать StartDigging. Это похоже на то, что государство не обрабатывается правильно, хотя возможно, что вы собираетесь использовать его для чего-то еще в будущем...

В стороне, как правило, считается плохой практикой делать большую обработку, не требующуюся для создания объекта в конструкторе объектов. Вся ваша обработка происходит в конструкторе, который чувствует себя не так.

TryToCreateNewMiner

У этого есть одна ветвь, 8% времени, она создаст новый шахтер и вызовет конструктор. Так что каждые 10 раз TryToCreateNewMiner, у нас есть хороший шанс, что он TryToCreateNewMiner хотя бы один раз. Первоначально новый шахтер запускается в том же положении, что и родительский объект (x и y не изменяются).

StartDigging

В этом методе есть справедливое разветвление. Основной бит, который нам интересен, - это условия, связанные с вызовами TryToCreateNewMiner. Давайте посмотрим на ветки:

if(state=="Active")

В настоящее время это избыточная проверка (она всегда активна).

while (!needStop && !IsOutOfBounds(x, y)) {

Первая часть этого условия прерывания никогда не срабатывает. requireStop устанавливается только в true, if(!IsLastMiner). Поскольку minersCount всегда 1, он всегда является последним шахтером, поэтому needStop никогда не запускается. Способ использования minersCount предполагает, что вы считаете, что он делится между экземплярами Miner, а это не так. Если это ваше намерение, вы можете прочитать static переменные.

Вторая часть предложения о прекращении является единственным выходом из цикла и запускается, если x или y достигает края карты.

while(dir==0)

Это бессмысленная проверка, dir может быть только числом от 1 до 5, так как это возвращает ChooseDirection.

if(!AroundIsNothing())

Это проверяет, если позиции, в которых может перемещаться майнер, равны 0. Если это не так, то вызывается GetIDFromDirection. Это ключ. Если Miner в настоящее время окружен 0, ID не будет установлен, он останется на прежнем значении. В ситуации, когда только что был создан Шахтер, это будет -1 (мы знаем, что это может произойти, потому что все шахтеры создаются по месту создания Шахтера).

Последние две проверки: if(ID==1) и if(ID==0 && IsLastMiner()) код, который перемещает Miner (либо путем вызова dig, либо перемещения). Таким образом, если идентификатор не равен 0 или 1 на данный момент, Miner не будет перемещаться. Это может вызвать проблему, потому что это происходит непосредственно перед вызовом TryToCreateNewMiner, поэтому, если программа когда-либо попадает в эту ситуацию, она будет застревать в цикле, где Miner не движется и постоянно пытается создать новых Miners в том же положении, 8% времени это будет работать, создавая нового шахтера в том же положении, который будет выполнять одни и те же проверки и попадать в один и тот же цикл, снова не перемещаться и пытаться создать новый шахтер, и поэтому он будет работать до тех пор, пока не закончится стопка пространства и программных сбоев.

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

  • 0
    О, спасибо за хороший ответ, теперь я понимаю, что я делаю не так :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню