我是C#(或一般编码)的新手,我想这个问题确实很愚蠢和令人困惑(我知道我很难做到),但是请帮助我。
我正在尝试使用表单应用程序进行扫雷。我做了一个10 x 10的按钮,如果您单击它,它将显示出周围的地雷数量。如果存在地雷,则会显示“ F”(首字母为“ False”)。
有一个构造函数,其中包含按钮,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);
}
}
答案 0 :(得分:3)
我是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
}
}
使用您的真实代码填充该模板,您将更有可能避免堆栈溢出。我已经写了数十年的递归方法,但我仍然遵循这种模式。
现在,在您的示例中,不需要递归的情况是什么?必须有一个,所以记下它是什么。接下来,您将如何保证递归解决了一个较小的问题?这通常是艰难的一步!考虑一下。
答案 1 :(得分:0)
由于zeroReveal
永远递归调用自身而发生了堆栈溢出。要解决此问题,我们需要找到不需要它的方式来进一步对其自身进行调用。
方法的名称为我们提供了一个线索。如果该正方形已经被显示出来,那么该方法肯定不需要做任何事情,因为它已经被显示出来了。
如果按钮的Text
属性尚未显示,则它看起来像是一个空字符串。因此,请更改foreach
,以使其不会处理已经显示的正方形:
foreach (Square s in list)
{
if (s.getButton().Text == ""))
{
// existing code in the foreach loop goes here
}
}