我正在使用对抗性搜索技术与AI对手一起编写Connect4游戏,我有点碰壁。我觉得我离解决方案不远,但是我可能会出现问题,我正在转换观点(如:参与者的观点是我的评估分数基础),在某处丢失减号或类似的东西这一点。
问题在于,在我尝试过的变体中,当玩家有三排时,AI会选择而不是来阻挡玩家,否则AI会发挥完美的作用游戏,或者他更喜欢阻止玩家,即使他有机会赢得比赛。搜索深度是一个偶数还是一个不均匀的数字似乎也很重要,因为人工智能在6层搜索中是一种短暂的延迟,这很有说服力。
搜索
使用的算法是使用alpha-beta修剪的negamax,并按如下方式实现:
private int Negamax(int depth, int alpha, int beta, Player player)
{
Player winner;
if (Evaluator.IsLeafNode(game, out winner))
{
return winner == player ? (10000 / depth) : (-10000 / depth);
}
if (depth == Constants.RecursionDepth)
{
return Evaluator.Evaluate(game, depth, player);
}
foreach (var move in moves)
{
int row;
if (board.DoMove(move, player, out row))
{
var value = -Negamax(depth + 1, -beta, -alpha, (Player)1 - (int)player);
board.UndoMove(move, row, player);
if (value > alpha)
{
alpha = value;
if (player == Player.AI)
{
bestColumn = move;
}
}
if (alpha >= beta)
{
return alpha;
}
}
}
return alpha;
}
我不怀疑问题出在这个函数中,但它可能是。
评价
我的评估功能基于以下事实:只有69种可能的方法可以在7x6板上获得四排。我有一个包含大约350个项目的查找表,其中包含每个列和行的硬编码信息,其中行+列是其中的一部分。例如,对于第0行和第0列,表格如下所示:
//c1r1
table[0][0] = new int[3];
table[0][0][0] = 21;
table[0][0][1] = 27;
table[0][0][2] = 61;
这意味着第0列第0行是胜利组合21,27和61的一部分。
我有第二张桌子,其中包含两位玩家在每个胜利组合中有多少石头。当我搬家时,我会做以下事情:
public bool DoMove(int column, Player p, out int row)
{
row = moves[column];
if (row >= 0)
{
Cells[column + row * Constants.Columns] = p;
moves[column]--;
var combinations = this.Game.PlayerCombinations[p];
foreach (int i in TerminalPositionsTable.Get(column,row))
{
combinations[i]++;
}
return true;
}
else
{
return false;
}
}
当然,UndoMove
正在做相反的事情。
因此,在第0列第0行移动Player.Human
后,该表将在索引21,27和61处填充值1.如果我在同样的单元格中执行另一个移动win-combination 27的一部分,然后玩家组合表在索引27到2处递增。
我希望我已经明确表达了这一点,因为它在评估功能中用于快速确定玩家与四连胜得分的接近程度。
评估函数,我怀疑问题所在,如下:
public static int Evaluate(Game game, int depth, Player player)
{
var combinations = game.PlayerCombinations[player];
int score = 0;
for (int i = 0; i < combinations.Length; i++)
{
switch (combinations[i])
{
case 1:
score += 1;
break;
case 2:
score += 5;
break;
case 3:
score += 15;
break;
}
}
return score;
}
因此,我简单地循环了69个可能的胜利组合,并根据它是单个石头,两个一排还是三个来增加分数。
在整个对抗性搜索中,我仍然感到困惑的部分是我是否应该关心哪个玩家正在采取行动?我的意思是,我应该像在这里一样传递球员,还是应该从AI球员的角度来评估棋盘?我尝试了aiScore - humanScore
的许多组合,或者只是从Player.AI
的角度来看,等等。但是我已经走到了尽头,我尝试的每一个组合都是非常有缺陷的。
所以:
非常感谢任何帮助。
更新
我已经在下面实施了Brennan的建议,虽然它确实有很强的很多,但由于某种原因,它不会阻止任何列上的三行,但左右两行-most,且仅在搜索深度不均匀时。人工智能在甚至搜索深度都是无与伦比的,但直到深度8及以上。然后它拒绝再次阻止。这很有说服力,我可能非常接近,但仍有一些关键的缺陷。
也许这与我设置专栏有关,应该像Brennan评论的那样,但我不知道何时设置它。仅在深度0处设置它不起作用。
更新2
使用Brennan的更改编辑现在的代码。
更新3
使用完整代码创建了一个Github仓库。如果您不知道如何使用Git,只需从here下载一个zip文件。
这是一个.NET 4.0项目,运行它将在documents / logs目录中创建negamax算法的日志文件。该解决方案还包含一个测试项目,该测试项目包含每个电路板列的测试,无论AI是否选择在播放器在那里有三个连接时阻止播放器。
答案 0 :(得分:2)
这些东西让我的大脑受到伤害,所以我不肯定这个答案是正确的,但是这里有。
在negamax中,总是相对于当前移动的玩家评估得分。如果它是白色的移动,那么高分对白色有好处。如果它是黑色的移动,那么高分对黑色有好处。因此,如果你有一个叶节点,那么得分是+ inf还是-inf不取决于该节点是白棋还是黑棋,而是它是否是你正在评估的玩家的胜利。替换这个:
return winner == Player.AI ? (10000 / depth) : (-10000 / depth);
用这个:
return winner == player ? (10000 / depth) : (-10000 / depth);
您的评估功能存在类似问题。替换这个:
return player == Player.AI ? score : -score;
用这个:
return score;
同样,我不确定这是对的。但我希望你尝试这两个变化,让我知道它是否有效。我很好奇!
答案 1 :(得分:1)
如果它没有阻止某些组合,那么听起来你的表中有可能获胜的缺陷。
我也在你的评价函数中看到一个问题:它为那些 NO 希望获胜的动作赋予了价值。假设你有xoo.x,你正在玩o。你的惯例表示在这里玩15分是值得的,但实际上它是值得的0.任何已经包含来自两个玩家的牌的胜利模式对任何人都没有价值。
我发现调试这种东西时,调试器没什么价值,因为它不能让你很好地看到大局。尝试将每个检查模式的日志文件写入日志文件 - 将实际图形放入日志中。