对抗性搜索问题

时间:2010-07-03 00:35:36

标签: c# .net artificial-intelligence

我正在使用对抗性搜索技术与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的角度来看,等等。但是我已经走到了尽头,我尝试的每一个组合都是非常有缺陷的。

所以:

  1. 我的评估逻辑是否牢固?
  2. 我什么时候应该'切换视角'?
  3. 非常感谢任何帮助。

    更新

    我已经在下面实施了Brennan的建议,虽然它确实有很强的很多,但由于某种原因,它不会阻止任何列上的三行,但左右两行-most,且仅在搜索深度不均匀时。人工智能在甚至搜索深度都是无与伦比的,但直到深度8及以上。然后它拒绝再次阻止。这很有说服力,我可能非常接近,但仍有一些关键的缺陷。

    也许这与我设置专栏有关,应该像Brennan评论的那样,但我不知道何时设置它。仅在深度0处设置它不起作用。

    更新2

    使用Brennan的更改编辑现在的代码。

    更新3

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

    这是一个.NET 4.0项目,运行它将在documents / logs目录中创建negamax算法的日志文件。该解决方案还包含一个测试项目,该测试项目包含每个电路板列的测试,无论AI是否选择在播放器在那里有三个连接时阻止播放器。

2 个答案:

答案 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.任何已经包含来自两个玩家的牌的胜利模式对任何人都没有价值。

我发现调试这种东西时,调试器没什么价值,因为它不能让你很好地看到大局。尝试将每个检查模式的日志文件写入日志文件 - 将实际图形放入日志中。