我可以看到这个问题(或者类似的问题)已被问过几次,我已经搜索了很多谷歌,所以我可以尝试理解它,但我肯定会卡住。
我的任务是使用一个递归函数,它使用“goodness”变量来确定哪个是计算机可以做出的最佳移动,我甚至有一个文档可以帮助解决这个问题,但对于我的生活,我只是不明白。
如果有人可能需要一些时间来帮助我或打破我实际需要做的事情,我会非常感激,我会链接到目前为止我的代码,但这是一项任务,所以指导比直接更好答案。我看过MinMax解决方案,这看起来肯定在我的掌握之上,我对编程非常陌生(特别是在C#只有几个月的经验)所以就这么简单!
以下是我想要遵循的建议解决方案:
http://erwnerve.tripod.com/prog/recursion/tictctoe.htm
public partial class Form1 : Form
{
public static string[,] Board = new string[3, 3] { { "1", "2", "3" }, { "4", "5", "6" }, { "7", "8", "9" } };
public bool Winner = false;
public string WinState;
private void Reset()
{
WinState = "";
Winner = false;
Board[0, 0] = "1";
Board[0, 1] = "2";
Board[0, 2] = "3";
Board[1, 0] = "4";
Board[1, 1] = "5";
Board[1, 2] = "6";
Board[2, 0] = "7";
Board[2, 1] = "8";
Board[2, 2] = "9";
btn1.Text = "";
btn2.Text = "";
btn3.Text = "";
btn4.Text = "";
btn5.Text = "";
btn6.Text = "";
btn7.Text = "";
btn8.Text = "";
btn9.Text = "";
}
private void checkWinner()
{
// Top Row
if (Board[0, 0].Equals(Board[0, 1]) && Board[0, 1].Equals(Board[0, 2]))
{
Winner = true;
WinState = Board[0, 0];
}
// Middle Row
if (Board[1, 0].Equals(Board[1, 1]) && Board[1, 1].Equals(Board[1, 2]))
{
Winner = true;
WinState = Board[1, 0];
}
// Bottom Row
if (Board[2, 0].Equals(Board[2, 1]) && Board[2, 1].Equals(Board[2, 2]))
{
Winner = true;
WinState = Board[2, 0];
}
// Left column
if (Board[0, 0].Equals(Board[1, 0]) && Board[1, 0].Equals(Board[2, 0]))
{
Winner = true;
WinState = Board[0, 0];
}
// Middle column
if (Board[0, 1].Equals(Board[1, 1]) && Board[1, 1].Equals(Board[2, 1]))
{
Winner = true;
WinState = Board[0, 1];
}
// Right column
if (Board[0, 2].Equals(Board[1, 2]) && Board[1, 2].Equals(Board[2, 2]))
{
Winner = true;
WinState = Board[0, 2];
}
// Diagonal 1
if (Board[0, 0].Equals(Board[1, 1]) && Board[1, 1].Equals(Board[2, 2]))
{
Winner = true;
WinState = Board[0, 0];
}
// Diagonal 2
if (Board[2, 0].Equals(Board[1, 1]) && Board[1, 1].Equals(Board[0, 2]))
{
Winner = true;
WinState = Board[2, 0];
}
if (Winner == true)
{
if (WinState == "X")
{
MessageBox.Show("Congratulations you win!");
Reset();
}
else if (WinState == "O")
{
MessageBox.Show("Sorry you lose!");
Reset();
}
}
}
private void btn1_Click(object sender, EventArgs e)
{
btn1.Text = "X";
Board[0, 0] = "X";
checkWinner();
}
private void btn2_Click(object sender, EventArgs e)
{
btn2.Text = "X";
Board[0, 1] = "X";
checkWinner();
}
private void btn3_Click(object sender, EventArgs e)
{
btn3.Text = "X";
Board[0, 2] = "X";
checkWinner();
}
private void btn4_Click(object sender, EventArgs e)
{
btn4.Text = "X";
Board[1, 0] = "X";
checkWinner();
}
private void btn5_Click(object sender, EventArgs e)
{
btn5.Text = "X";
Board[1, 1] = "X";
checkWinner();
}
private void btn6_Click(object sender, EventArgs e)
{
btn6.Text = "X";
Board[1, 2] = "X";
checkWinner();
}
private void btn7_Click(object sender, EventArgs e)
{
btn7.Text = "X";
Board[2, 0] = "X";
checkWinner();
}
private void btn8_Click(object sender, EventArgs e)
{
btn8.Text = "X";
Board[2, 1] = "X";
checkWinner();
}
private void btn9_Click(object sender, EventArgs e)
{
btn9.Text = "X";
Board[2, 2] = "X";
checkWinner();
}
}
答案 0 :(得分:11)
不要因为阅读该文档而不理解递归而感觉太糟糕。它在解释递归方面做得不是很好。 (这是一个艰难的概念 - 我可能也不会做得那么好)。最终,你要做的就是尽量让你的程序做你想做的事。我将尝试从这个角度解释它。
递归很有用,因为它允许我们在解决方案中编码(一次)一个步骤,然后使用刚刚计算的结果作为输入重复该步骤。尝试从你的角度看你的问题,而不是一些任意的善意算法。你可能很难理解论文中的算法。
尝试这样思考:在游戏开始时玩家1进行游戏。你的程序必须为玩家2选择一个移动。但玩家2必须考虑游戏的其余部分(每次可能的移动)。
你可以将其改为:
当前玩家为2,为当前玩家的所有剩余选择赋予权重
当前玩家为1,为当前玩家的所有剩余选择赋予权重
当前玩家为2,为当前玩家的所有剩余选择赋予权重
当前玩家为1,为当前玩家的所有剩余选择赋予权重
当前玩家为2,为当前玩家的所有剩余选择赋予权重
当前玩家为1,为当前玩家的所有剩余选择赋予权重
当前玩家为2,为当前玩家的所有剩余选择赋予权重
当前玩家为1,为当前玩家的所有可能剩余选项赋予权重。
您可以将此改为: 给予当前的球员, 切换播放器和为当前播放器的所有可能选择赋予权重 重复直到不再有可能的选择
您可以将此改为: 给予当前的球员, 切换播放器和CheckGoodness() 重复直到不再有可能的选择
所以,回到你的写作。作者使用1和1的玩家。 -1。为什么?因为当你越来越深地传递动作时,你必须交换玩家,并且当你下降这样的等级时,很容易切换玩家(我只是在这里谈论玩家:
public int CheckGoodness(bool playerID)
{
playerID = -playerID;
if (!endConditionMet)
{
goodness = CheckGoodness(playerID);
}
return goodness;
}
与玩家一起,你必须传递代表剩余所有可能移动状态的东西。问题是,如果您传递的内容作为参考传递,您所做的任何更改都将反映在原始数据中。确保没有发生。这可能就是@CodeInChaos建议你克隆的原因。
请注意,在递归中,您必须确保始终有办法结束调用序列。您必须修改您所依赖的最终条件。在这种情况下,您的可能移动数量正在下降。否则你永远打电话,内存不足。
编辑:已添加的董事会类别说明。
从大局考虑。类是现实世界事物(例如对象)的表示。一件事具有描述它的属性。这些是班级的数据。事也行动。这些是方法。我听过的一个类的另一个定义是类是对该数据的数据和操作。
想想游戏中有哪些对象。 2名球员和一名董事会。没什么别的。
玩家可以移动,并且具有唯一的标识符(在这种情况下,可以是' X' O' O'),可以是人或AI。我现在无法想到任何其他事情(重要的),但通常会有更多的事情可以存在但不会影响程序(如眼睛的颜色)。这也是您可以使用继承的地方。你有一个带有抽象移动方法的玩家类。从播放器继承的人类,使用覆盖移动方法接受来自UI的输入,从播放器继承的计算机/ AI类,并通过计算良好评级的移动来覆盖移动方法。
董事会有数据:
董事会的行动可能是:
你可以有一个静态的GoodNess类(可能需要一个更好的名字) 没有数据,只有一个方法(或者这可能是董事会成员的另一种方法:
AI可以在移动之前调用Goodness GetBestMove方法 递归将与GetBestMove方法隔离。
请注意,这些都不是一成不变的。类是由您认为应该在其中定义的。这完全取决于你如何成为解决问题的最佳方法。如果您仍然遇到问题,请使用您尝试过的代码更新您的问题。在开始布置代码时,绘制图表确实很有帮助。
祝你好运,希望这会有所帮助,并且我会尝试更好地监控StackOverflow通知。