C# 敌方搜索问题
我正在和一个AI对手使用对抗性搜索技术编写一个Connect4游戏,我有点碰壁了。我觉得我离解决方案不远了,但可能有一个问题,那就是我在转换观点(比如:我的评估分数是基于哪个参与者的观点),在某个地方漏掉一个负号或类似的东西 问题是,在我尝试过的变体中,AI选择在玩家连续三人时不阻止玩家,但在其他情况下,AI玩的是一个完美的游戏,或者他更喜欢阻止玩家,即使他有机会赢得游戏。搜索深度是偶数还是非偶数似乎也很重要,因为AI在6层搜索中是头戴裤子的,这很能说明问题 搜索 使用的算法是negamax,带有alpha-beta修剪,实现如下:C# 敌方搜索问题,c#,.net,artificial-intelligence,C#,.net,Artificial Intelligence,我正在和一个AI对手使用对抗性搜索技术编写一个Connect4游戏,我有点碰壁了。我觉得我离解决方案不远了,但可能有一个问题,那就是我在转换观点(比如:我的评估分数是基于哪个参与者的观点),在某个地方漏掉一个负号或类似的东西 问题是,在我尝试过的变体中,AI选择在玩家连续三人时不阻止玩家,但在其他情况下,AI玩的是一个完美的游戏,或者他更喜欢阻止玩家,即使他有机会赢得游戏。搜索深度是偶数还是非偶数似乎也很重要,因为AI在6层搜索中是头戴裤子的,这很能说明问题 搜索 使用的算法是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;
}
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;
}
我不怀疑问题出在这个函数中,但它可能是
评估
我的评估函数基于这样一个事实:在7x6板上,只有69种可能的方式可以获得四列。我有一个大约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行是win组合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。如果我在也是win组合27一部分的单元格中进行另一个移动,那么玩家组合表在索引27处增加到2
我希望我已经说清楚了,因为它在评估函数中被用来非常快速地确定一个球员离连续四个进球有多近
我怀疑问题所在的评估函数如下:
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;
}
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;
}
公共静态整数评估(游戏、整数深度、玩家)
{
var组合=游戏。玩家组合[玩家];
智力得分=0;
for(int i=0;i
所以我只是简单地循环了69个可能的赢的组合,然后根据是一块石头、一排两块石头还是三块石头给分数加上一个数字
在这整个对抗性搜索中,我仍然感到困惑的是,我是否应该关心哪个玩家在移动?我的意思是,我应该像这里一样传球给球员,还是应该总是从AI球员的角度来评估棋盘?我尝试了很多组合aiScore-humanScore
,或者总是从Player.AI
的角度来看,诸如此类。但我已经走到了死胡同,我尝试过的每一种组合都有很大的缺陷
因此:
这是一个.NET4.0项目,运行它将在documents/logs目录中创建negamax算法的日志文件。该解决方案还包含一个测试项目,其中包含一个针对每个棋盘列的测试,当玩家有三个棋盘列时,AI是否选择阻止玩家。这些东西会让我的大脑受伤,所以我不确定这个答案是否正确,但接下来就是了 在negamax中,分数总是相对于当前移动的玩家进行评估。如果是怀特的动作,那么高分对怀特来说是好的。如果是布莱克的动作,那么高分对布莱克有利。因此,如果你有一个叶子节点,分数是+inf还是-inf并不取决于该节点是白人还是黑人获胜,而是取决于你当前评估的玩家是否获胜。替换此项:
return winner == Player.AI ? (10000 / depth) : (-10000 / depth);
return player == Player.AI ? score : -score;
为此:
return winner == player ? (10000 / depth) : (-10000 / depth);
return score;
您的评估函数中也存在类似的问题。替换此项:
return winner == Player.AI ? (10000 / depth) : (-10000 / depth);
return player == Player.AI ? score : -score;
为此:
return winner == player ? (10000 / depth) : (-10000 / depth);
return score;
再说一遍,我不确定这是否正确。但我希望你尝试这两个改变,并让我知道它是否有效。我很好奇 如果它没有阻止某些组合,听起来你的可能获胜列表中有一个缺陷 我也看到了你的评估函数中的一个问题:它为那些没有获胜希望的动作提供了价值。假设你有xoo.x,你在玩o。你的套路上说在这里打15分是值得的,而事实上这值0分。任何已经包含两个玩家牌的赢模式对任何人都没有价值 我发现在调试这类事情时,调试器的作用很小