如何使用极大极小算法让你的Tic Tac Toe游戏无敌

303 阅读4分钟

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

我花了几天浏览教程,看视频,用头撞桌子,试图用可靠的人工智能制作一款无敌的Tic Tac Toe游戏。如果你有类似的经历,我想给你介绍Minimax算法。

就像专业的国际象棋选手一样,这个算法会提前几步,设身处地为对手着想。它会一直领先,直到到达棋盘的终端安排(终端状态),结果是平局、赢或输。一旦进入终端状态,AI将为获胜分配一个任意的正分(+10),为失败分配一个负分(-10),或为平局分配一个中立分(0)。

同时,该算法根据玩家的回合来评估导致终端状态的步法。当轮到AI时,它会选择得分最高的招式;当轮到人类玩家时,它会选择得分最低的招式。使用这种策略,Minimax避免了输给人类玩家。

在接下来的游戏中,最好使用Chrome浏览器尝试一下。

Minimax算法可以被定义为递归函数,它可以做以下事情:

  • 如果找到终端状态,返回值(+ 10,0,-10)
  • 浏览一下空缺位置
  • 在每个可用点上调用minimax函数(递归)
  • 计算函数调用的返回值
  • 并返回最佳值

如果你对递归的概念不熟悉,我建议你看哈佛CS50的这个视频。

为了完全掌握Minimax的思想过程,让我们在代码中实现它,并在下面两个部分中看到它的实际作用。

Minimax 代码

在本教程中,你将致力于游戏的接近结束状态,如下图所示。因为minimax会评估游戏的每一种状态(数十万),一个接近结束的状态允许你更容易地跟进minimax的递归调用

对于下图,假设AI是X,人类玩家是O

iYDAcMcMvbr0lBISCQqM-mBbfhqDx2sPqcYl

为了更容易地使用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];
}

\