我正在尝试创建2D洞穴生成系统。在运行程序时,尝试从其自己的类创建新对象后,出现“ System.StackOverflowException”异常。

我的洞穴生成器是这样的:

我创建了一个地图,其中包含不同类型的单元格(例如墙,水或空白空间)的ID(整数)。

首先,我所有的“地图”类都会创建一个填充墙壁的地图,然后在地图的中心创建一个“矿工”对象。矿工挖了地图,开了洞。问题是我想创建更多的矿工。因此,正在挖掘地图的矿工创建了另一个矿工。但是,当我这样做时,会出现“ System.StackOverflowException”异常。

如何跟踪程序中StackOverflow的原因。
这是我的矿工代码:

矿工

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

最佳答案

尽管您可以通过在堆栈上创建太多非常大的对象来获得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,我们很有可能至少成功一次。新矿工最初在与父对象相同的位置启动(x和y不变)。

开始挖掘

这种方法有很多分支。我们感兴趣的主要部分是有关TryToCreateNewMiner调用的条件。让我们看一下分支:

if(state=="Active")

当前这是一个多余的检查(始终处于活动状态)。

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

永远不会触发此终止子句的第一部分。仅将needStop设置为true if(!IsLastMiner)。由于minersCount始终为1,因此始终是最后一个矿工,因此永远不会触发needStop。您使用minersCount的方式表明您认为它在Miner的实例之间共享,而实际上并没有。如果您打算这样做,则可能需要阅读static变量。

终止子句的第二部分是退出循环的唯一途径,如果x或y到达地图的边缘,则触发该子句。

while(dir==0)

这是没有意义的检查,dir只能是1到5之间的数字,因为这是ChooseDirection返回的结果。

if(!AroundIsNothing())

这是在检查矿工可以移动到的位置是否都设置为0。否则,将调用GetIDFromDirection。这是关键。如果矿工当前被0包​​围,则不会设置ID,它将保持先前的值。在刚刚创建矿工的情况下,该名称为-1(我们知道可能会发生,因为所有矿工都在创建矿工的位置创建)。

最后两个check if(ID==1)if(ID==0 && IsLastMiner())保护移动Miner的代码(通过调用dig或move)。因此,如果ID不为0或1,则矿工将不会移动。这可能会导致问题,因为它紧接在调用TryToCreateNewMiner之前,因此,如果程序陷入这种情况,它将陷入矿工不动的循环中,并不断尝试在矿工中创建新的矿工。相同的位置。 8%的时间将起作用,在相同位置创建一个新的矿工,它将执行相同的检查并进入同一循环,再次不动并尝试创建一个新的矿工,直到堆栈用完为止空间和程序崩溃。

您需要查看终止子句和处理ID的方式,如果矿工完全被0包围,您可能不希望其停止做任何事情。

10-01 04:57
查看更多