对抗搜索问题

我正在编写一个使用对抗搜索技术的 Connect4 游戏,并且遇到了瓶颈。我觉得我离解决方案不远了,但可能存在一个问题,我可能在切换视角(即:我基于哪个参与者的视角来评估分数),或者在某个地方缺少一个负号。

问题在于,在我尝试过的变体中,AI要么阻止玩家的三连,但在其他情况下,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;}

我不认为问题出在这个函数中,但也有可能。

评估

我基于这样一个事实来设计评估函数:在 7×6 的棋盘上只有 69 种可能的四连方式。我有一个包含大约 350 个项目的查找表,其中包含每个列和行所属的硬编码信息,这些信息是哪个获胜组合的一部分。例如,对于第 0 行和第 0 列,该表如下所示:

//c1r1table[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,当然会执行相反的操作。

因此,在 Player.Human 在第 0 列,第 0 行上进行移动后,该表将在索引 21、27 和 61 处填充值为 1。如果我在也属于获胜组合 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 的角度来看,等等。但是我遇到了死胡同,我尝试过的每种组合都存在很大的缺陷。

所以:

  1. 我的评估逻辑基础是否扎实?
  2. 我应该什么时候“切换视角”?

非常感谢任何帮助。

更新

我已经实现了 Brennan 在下面的建议,虽然它已经有了很大的改进,但由于某种原因,它不会阻止任何列上的三连,除了最左边和最右边的两列,并且只有当搜索深度不均匀时。AI 在偶数搜索深度下是无敌的,但只有在深度 8 及以上时。然后它再次拒绝阻止。这很能说明我可能非常接近了,但仍然存在一些关键缺陷。

也许这与我设置 AI 应该放置棋子的列有关,正如 Brennan 评论的那样,但我不知道什么时候该设置它。仅在深度 0 处设置它不起作用。

更新 2

编辑了代码,使其看起来像现在这样,并使用了 Brennan 的更改。

更新 3

创建了一个包含完整代码的 Github 仓库。如果您不知道如何使用 Git,只需从 这里 下载一个 zip 文件即可。

这是一个 .NET 4.0 项目,运行它将在您的 documents/logs 目录中创建 negamax 算法的日志文件。该解决方案还包含一个测试项目,其中包含每个棋盘列的测试,用于测试当玩家在该处有三连时,AI 是否选择阻止玩家。


回答:

这些东西让我的大脑很受伤,所以我不能确定这个答案是正确的,但还是说一下吧。

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

同样,我不确定这是对的。但我希望你尝试这两个更改,并告诉我它是否有效。我很好奇!

Related Posts

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注