在我的minimax算法中,当计算机上有一个玩家有两种方法赢得计算机时,他只会选择棋盘的第一个打开位置。以下面的例子为例。X可以在0,2和1,0位置获胜。
X | |
__________
| x |
__________
x | o | o
目前,我的算法将把o放置在位置0,1。我相信它会这样做,因为当minimax运行并将o放置在位置0,1时,因为这不是一个胜利,它再次调用minimax,这一次是为x。然后,x移动到位置0,2,为胜利。这个位置返回-10。如果计算机在位置0,2移动,则调用minimax,最终将x放置在位置1,0,该移动也会返回-10。事实上,无论计算机将o放置在何处,-10都会返回,因为无论玩家将赢得什么。因为对于每个位置o,它返回-10,所以计算机将o放置在第一个可用插槽中,该插槽为0,1,因为最大值永远不会从第一个位置更新。我希望它将o放置在位置1,0或0,2,以表明它识别一个块。
我的算法如下。这是一个3x3x3,但概念是一样的。
public int MiniMax(int pGameState[][][], int Depth, boolean IsMax){
FunctionCalls++;
if(CheckForWin(2, pGameState)){ //Max Player (since the computer is always 2)
return 10 - Depth;
}
if(CheckForWin(1, pGameState)){ //Player will win therefore we return -10. If this is the first level of the tree
//then the value return is -10. If the second ply then the value returned is -8.
//It is more important for the computer to win sooner than later.
return -10 - Depth;
}
if(Depth >= 2){
return 0;
}
if(IsMax){
int Value = Integer.MIN_VALUE;
for(int i=0; i<3; i++){
for(int j=0; j<3; j++){
for(int k=0; k<3; k++){
if(pGameState[i][j][k] == 0){
pGameState[i][j][k] = 2;
int best = MiniMax(CopyArray(pGameState), Depth+1, !IsMax);
if(best > Value)
Value = best;
pGameState[i][j][k] = 0;
}
}
}
}
return Value;
}
else{
int Value = Integer.MAX_VALUE;
for(int i=0; i<3; i++){
for(int j=0; j<3; j++){
for(int k=0; k<3; k++){
if(pGameState[i][j][k] == 0){
pGameState[i][j][k] = 1;
int best = MiniMax(CopyArray(pGameState), Depth+1, !IsMax);
if(best < Value)
Value = best;
pGameState[i][j][k] = 0;
}
}
}
}
return Value;
}
}
我最初这样称呼极小极大
best = MiniMax(CopyArray(GameState), 0, false);
然后,我将best与之前的最大值进行比较。如果best更大,我会将此移动保存为我的计算机移动。
这里有一种方法。
如果多个可能的动作之间出现平局,请计算expectimax,这是一个与随机出局的对手相比,为你提供最高可能得分的动作。
这将导致你阻碍其中一种获胜的方式,希望另一种不会看到最好的选择。
处理第一个可用移动选择问题的一个简单方法是在迭代之前对有效移动进行排序。考虑一下你在这个问题中所描述的位置:
X . .
. X .
X O O
这里O
是移动。在以默认方式(从左到右从上到下)迭代棋盘之前,根据每个移动的好坏来排序四个有效移动((0, 1), (0, 2), (1, 0), (1, 2))
的向量。这样做的一种方法是使用评估函数,该函数将在潜在移动完成后计算每方有多少威胁。片P
(可以是X
或O
)的威胁是具有一个空正方形和两个P
正方形的行,列或对角线(因此是一个片P
距离成为获胜线很短)。让我们看看这个评估函数会告诉我们给定位置的四个有效移动中的每一个。我们计算两个棋子的威胁数量,并分配给位置值S
等于差O_threats-X_threats
。
如果O
移动(0,1)
,则O_威胁=0
,X_威胁=2
,因此分数S=0-2=-2
。
如果O
移动(0,2)
,则O_威胁=1
,X_威胁=1
,因此分数S=1-1=0
。
如果O
移动(1,0)
,则O_威胁=0
,X_威胁=1
,因此分数S=0-1=-1
。
如果O
移动(1,2)
,则O_威胁=1
,X_威胁=2
,因此分数S=1-2=-1
。
根据计算出的分数,访问有效移动的顺序如下:(0,2)、(1,0)、(1,2)、(0,1)
。我们知道,在完美的比赛中,所有四个动作都是失败的。由于他们的分数相等(等于损失值-10
),第一个考虑的移动(0,2)
不会被下一个移动覆盖。这将使程序的动作“更加智能”,因为它现在尊重由动作产生/阻止的威胁(人类在玩tic-tac-toe游戏时经常使用威胁考虑)。通过使用不同的评估函数对有效移动进行排序,可以强制执行不同的访问顺序。
还要注意,移动排序对于增加与αbeta修剪相结合的搜索深度是非常有用的,因为它允许首先考虑好的有效动作,并增加修剪更多节点的机会。虽然alpha-beta修剪对于这样一个简单的游戏来说可能是一种过度的杀伤力,但对于更复杂的游戏来说,它确实很有用。
我想我终于对minimax和Alpha-beta修剪有所了解了,但实现它完全是另一回事! 根据我的理解,基础是:您为某些动作分配一个启发式函数分数(Gomoku为例)。 如果一行有5个,我们应该分配一个高值,比如9999,因为这是一个胜利的举动 当我们必须在Java中实现这一点时,我的问题来了! 我有一块彩色[][]板(8x8),其中黑色是播放器1,白色是播放器2,null表示空白,我不知道我们应
极小极大算法的一个缺点是每个板状态必须被访问两次:一次查找其子级,第二次评估启发式值。 极小极大算法还有其他缺点或优点吗?对于像象棋这样的游戏,还有更好的选择吗?(当然是带有α-β修剪的极小极大算法,但还有其他吗?)
计算机科学中最有趣的事情之一就是编写一个人机博弈的程序。有大量的例子,最出名的是编写一个国际象棋的博弈机器。但不管是什么游戏,程序趋向于遵循一个被称为Minimax算法,伴随着各种各样的子算法在一块。本篇将简要介绍 minimax 算法,并通过实例分析帮助大家更好的理解。 一、概念 Minimax算法又名极小化极大算法,是一种找出失败的最大可能性中的最小值的算法。Minimax算法常用于棋类等由两
我试图在我的象棋引擎中实现alpha-beta剪枝,但没有性能差异,我可能做错了什么?我试着用控制台记录算法剪切一个分支的次数,但它的数量是数百次,因此它可以正确地修剪搜索树。即使这样,该算法也没有明显的性能改进。 董事会评估平均需要80毫秒左右。使用alpha-beta修剪,查看深度3时,minimax/alpha-beta算法需要1.8秒,而不使用minimax/alpha-beta算法需要1
我最近实现了极小极大和阿尔法贝塔修剪算法,我100%确定(自动分级器)我正确地实现了它们。但是当我执行我的程序时,它们的行为不同。我99%确定极小极大和阿尔法贝塔的结束状态应该是相同的。我说得对吗?它们在实现结果的路径上会有所不同吗?因为我们忽略了min将选择的一些值,而max不会选择这些值,反之亦然。
我正在为Gomoku(16x16)构建一个具有极大极小值和α-β修剪的AI,但速度非常慢。到目前为止,我已经尝试过对动作顺序进行预排序,而不是深度复制棋盘,添加和删除动作。此外,我还使用了相关移动的arraylist(距离已放置的块的半径在2以内)来减少搜索栏。然而,即使在3的深度搜索中,人工智能仍在苦苦挣扎<编辑:我发现了一个叫做换位表的东西,但我不知道从哪里开始。任何帮助都会很好!