我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!
我花了几天浏览教程,看视频,用头撞桌子,试图用可靠的人工智能制作一款无敌的Tic Tac Toe游戏。如果你有类似的经历,我想给你介绍Minimax算法。
就像专业的国际象棋选手一样,这个算法会提前几步,设身处地为对手着想。它会一直领先,直到到达棋盘的终端安排(终端状态),结果是平局、赢或输。一旦进入终端状态,AI将为获胜分配一个任意的正分(+10),为失败分配一个负分(-10),或为平局分配一个中立分(0)。
同时,该算法根据玩家的回合来评估导致终端状态的步法。当轮到AI时,它会选择得分最高的招式;当轮到人类玩家时,它会选择得分最低的招式。使用这种策略,Minimax避免了输给人类玩家。
在接下来的游戏中,最好使用Chrome浏览器尝试一下。
Minimax算法可以被定义为递归函数,它可以做以下事情:
- 如果找到终端状态,返回值(+ 10,0,-10)
- 浏览一下空缺位置
- 在每个可用点上调用minimax函数(递归)
- 计算函数调用的返回值
- 并返回最佳值
如果你对递归的概念不熟悉,我建议你看哈佛CS50的这个视频。
为了完全掌握Minimax的思想过程,让我们在代码中实现它,并在下面两个部分中看到它的实际作用。
Minimax 代码
在本教程中,你将致力于游戏的接近结束状态,如下图所示。因为minimax会评估游戏的每一种状态(数十万),一个接近结束的状态允许你更容易地跟进minimax的递归调用
对于下图,假设AI是X,人类玩家是O
为了更容易地使用Ti Tac Toe板,您应该将其定义为一个包含9个项目的数组。每一项都有其索引作为值。这将在以后派上用场。因为上面的黑板已经包含了一些X和Y移动,让我们定义已经包含X和Y移动的黑板
var origBoard = ["O",1,"X","X",4,"X",6,"O","O"];
然后声明aiPlayer和huPlayer变量,并将它们分别设置为“X”和“O”。
此外,您需要一个函数来查找获胜组合并在找到时返回true,以及一个函数来列出棋盘上可用位置的索引。
/* the original board
O | | X
---------
X | | X
---------
| O | O
*/
var origBoard = [“O”,1 ,”X”,”X”,4 ,”X”, 6 ,”O”,”O”];
// human
var huPlayer = “O”;
// ai
var aiPlayer = “X”;
// returns list of the indexes of empty spots on the board
function emptyIndexies(board){
return board.filter(s => s != "O" && s != "X");
}
// winning combinations using the board indexies
function winning(board, player){
if (
(board[0] == player && board[1] == player && board[2] == player) ||
(board[3] == player && board[4] == player && board[5] == player) ||
(board[6] == player && board[7] == player && board[8] == player) ||
(board[0] == player && board[3] == player && board[6] == player) ||
(board[1] == player && board[4] == player && board[7] == player) ||
(board[2] == player && board[5] == player && board[8] == player) ||
(board[0] == player && board[4] == player && board[8] == player) ||
(board[2] == player && board[4] == player && board[6] == player)
) {
return true;
} else {
return false;
}
}
现在,让我们通过定义带有两个参数的Minimax函数newBoard和player来深入到好的部分。然后,您需要找到板中可用位置的索引,并将它们设置为一个名为availSpots的变量
// the main minimax function
function minimax(newBoard, player){
//available spots
var availSpots = emptyIndexies(newBoard);
另外,您需要检查终端状态并相应地返回一个值。如果O赢了你应该返回-10,如果X赢了你应该返回+10。此外,如果availableSpots数组的长度为零,这意味着没有更多的空间来玩,游戏已经导致平局,您应该返回零。
// checks for the terminal states such as win, lose, and tie
//and returning a value accordingly
if (winning(newBoard, huPlayer)){
return {score:-10};
}
else if (winning(newBoard, aiPlayer)){
return {score:10};
}
else if (availSpots.length === 0){
return {score:0};
}
接下来,您需要从每个空白点收集分数,以便稍后进行评估。因此,创建一个名为moves的数组,并循环通过空点,同时在名为move的对象中收集每个move的索引和得分。
然后,将作为数字存储在origBoard中的空点的索引号设置为移动对象的index属性。然后,将新板上的空点设置为当前玩家,并与其他玩家和新更改的新板一起调用minimax函数。接下来,您应该将包含score属性的minimax函数调用的对象存储到move对象的score属性。
最后,Minimax将newBoard重置为之前的状态,并将move对象推入moves数组。
// an array to collect all the objects
var moves = [];
// loop through available spots
for (var i = 0; i < availSpots.length; i++){
//create an object for each and store the index of that spot
var move = {};
move.index = newBoard[availSpots[i]];
// set the empty spot to the current player
newBoard[availSpots[i]] = player;
/*collect the score resulted from calling minimax
on the opponent of the current player*/
if (player == aiPlayer){
var result = minimax(newBoard, huPlayer);
move.score = result.score;
}
else{
var result = minimax(newBoard, aiPlayer);
move.score = result.score;
}
// reset the spot to empty
newBoard[availSpots[i]] = move.index;
// push the object to the array
moves.push(move);
}
然后,minimax算法需要计算moves数组中的最佳步长。它应该在AI下棋时选择得分最高的棋,在人类下棋时选择得分最低的棋。因此,如果玩家是aiPlayer,它将一个名为bestScore的变量设置为一个非常低的数字,并循环遍历moves数组,如果一个move的分数高于bestScore,算法将存储该move。如果有类似分数的移动,只存储第一个。
当玩家是huPlayer时也会发生相同的评估过程,但这一次bestScore将被设置为一个较高的数字,而Minimax将寻找一个得分最低的招式来存储。
最后,Minimax返回存储在bestMove中的对象。
// if it is the computer's turn loop over the moves and choose the move with the highest score
var bestMove;
if(player === aiPlayer){
var bestScore = -10000;
for(var i = 0; i < moves.length; i++){
if(moves[i].score > bestScore){
bestScore = moves[i].score;
bestMove = i;
}
}
}else{
// else loop over the moves and choose the move with the lowest score
var bestScore = 10000;
for(var i = 0; i < moves.length; i++){
if(moves[i].score < bestScore){
bestScore = moves[i].score;
bestMove = i;
}
}
}
// return the chosen move (object) from the moves array
return moves[bestMove];
}
\