Крестики-нолики в JavaScript с минимаксным алгоритмом, дающим ошибку, превышают максимальный размер стека вызовов - PullRequest
0 голосов
/ 02 сентября 2018

Я делаю игру Tic Tac Toe. Мой минимаксный алгоритм отлично работает сам по себе с массивом и двумя проигрывателями, но при работе с html коллекциями div он выдает ошибку превышения максимального размера стека вызовов. Я не понимаю, почему это происходит. Поиском в Google я знаю, что моя функция где-то вызывает себя снова и снова, но не могу понять, что делать, чтобы решить эту проблему. Я попытался преобразовать коллекцию div .cells в массив, но все же произошла ошибка. Кто-нибудь, пожалуйста, помогите мне с этим. Спасибо.

var humanPlayer, aiPlayer;
humanPlayer = 'x';
aiPlayer = 'o';
var cells = document.querySelectorAll(".cell");

/** Tic Tac Toe game object **/
var TicTacToe = {
  checkWinner : function(arr, player){
    if(
      (arr[0] === player && arr[1] === player && arr[2] === player)||
      (arr[3] === player && arr[4] === player && arr[5] === player)||
      (arr[6] === player && arr[7] === player && arr[8] === player)||
      (arr[0] === player && arr[3] === player && arr[6] === player)||
      (arr[1] === player && arr[4] === player && arr[7] === player)||
      (arr[2] === player && arr[5] === player && arr[8] === player)||
      (arr[0] === player && arr[4] === player && arr[8] === player)||
      (arr[2] === player && arr[4] === player && arr[6] === player)
    ){
      return true;
    }
    else{
      return false;
    }
  },//checkWinner function.

  getAvailableSpots : function(arr){
    var spots = [];
    for(var i = 0; i < arr.length; i++){
      if(arr[i].textContent !== "x" && arr[i].textContent !== "o"){
        spots.push(i);
      }
    }
    return spots;
  },// getAvailableSpots function.

  minimax : function(newBoard, player){
    newBoard = Array.from(newBoard);
    var availSpots = this.getAvailableSpots(newBoard);
    if(this.checkWinner(newBoard, aiPlayer)){
      return {score: 10};
    }
    else if(this.checkWinner(newBoard, humanPlayer)){
      return {score: -10};
    }
    else if(availSpots.length === 0){
      return {score: 0};
    }

    var moves = [];

    for(var j = 0; j < availSpots.length; j++){
      var move = {};
      move.index = availSpots[j];
      newBoard[availSpots[j]] = player;
      if(player === aiPlayer){
        var result1 = this.minimax(newBoard, humanPlayer);
        move.score = result1.score;
      }
      else{
        var result2 = this.minimax(newBoard, aiPlayer);
        move.score = result2.score;
      }

      newBoard[availSpots[j]] = move.index;
      moves.push(move);
    }

    var bestMove;
    if(player === aiPlayer){
      var bestScore1 = -100000;
      for(var k = 0; k < moves.length; k++){
        if(moves[k].score > bestScore1){
          bestScore1 = moves[k].score;
          bestMove = k;
        }
      }
    }
    else{
      var bestScore2 = 100000;
      for(var l = 0; l < moves.length; l++){
        if(moves[l].score < bestScore2){
          bestScore2 = moves[l].score;
          bestMove = l;
        }
      }
    }

    return moves[bestMove];
  }// minimax function.


};//TicTacToe object literal.

function move(e){
  if(e.target.className === "cell" && e.target.textContent === ""){
    e.target.textContent = humanPlayer;
    var result = TicTacToe.minimax(cells, aiPlayer);
    cells[result.index].textContent = aiPlayer;
  }
}


document.querySelector('.cells').addEventListener('click', move);
.*{
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}
.cells  {
  width: 390px;
  height: 390px;
  margin: 50px auto;
}

.cell {
  width:128px;
  height: 128px;
  border: 1px solid #dedede;
  float: left;
  text-align: center;
  line-height: 128px;
  font-size: 80px;
  font-weight: bold;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>

  <div class="cells">
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
  </div>
</body>
</html>

1 Ответ

0 голосов
/ 02 сентября 2018

Это потому, что ваша функция minmax является рекурсивной, то есть она пробует каждую возможную цепочку комбинаций. Чтобы не повторять одну и ту же цепочку снова и снова, она создает клон доски при каждой рекурсии (через Array.from(newBoard)).

Теперь, когда вы тестировали его с массивами примитивов, копирование платы работало нормально. Однако теперь массив вашей доски фактически содержит объекты DOM, которые не будут клонированы Array.from(). Таким образом, ваш ИИ в основном изменяет ячейки для каждой попытки, которая вызывает переполнение стека.

Мое предложение состояло бы в том, чтобы преобразовать текущую плату в массив примитивов перед передачей ее функции minmax.

var humanPlayer, aiPlayer;
humanPlayer = 'x';
aiPlayer = 'o';
var cells = document.querySelectorAll(".cell");

/** Tic Tac Toe game object **/
var TicTacToe = {
  checkWinner : function(arr, player){
    if(
      (arr[0] === player && arr[1] === player && arr[2] === player)||
      (arr[3] === player && arr[4] === player && arr[5] === player)||
      (arr[6] === player && arr[7] === player && arr[8] === player)||
      (arr[0] === player && arr[3] === player && arr[6] === player)||
      (arr[1] === player && arr[4] === player && arr[7] === player)||
      (arr[2] === player && arr[5] === player && arr[8] === player)||
      (arr[0] === player && arr[4] === player && arr[8] === player)||
      (arr[2] === player && arr[4] === player && arr[6] === player)
    ){
      return true;
    }
    else{
      return false;
    }
  },//checkWinner function.

  getAvailableSpots : function(arr){
    var spots = [];
    for(var i = 0; i < arr.length; i++){
      if(arr[i] !== "x" && arr[i] !== "o"){
        spots.push(i);
      }
    }
    return spots;
  },// getAvailableSpots function.

  minimax : function(newBoard, player){
    newBoard = Array.from(newBoard);
    var availSpots = this.getAvailableSpots(newBoard);
    if(this.checkWinner(newBoard, aiPlayer)){
      return {score: 10};
    }
    else if(this.checkWinner(newBoard, humanPlayer)){
      return {score: -10};
    }
    else if(availSpots.length === 0){
      return {score: 0};
    }

    var moves = [];

    for(var j = 0; j < availSpots.length; j++){
      var move = {};
      move.index = availSpots[j];
      newBoard[availSpots[j]] = player;
      if(player === aiPlayer){
        var result1 = this.minimax(newBoard, humanPlayer);
        move.score = result1.score;
      }
      else{
        var result2 = this.minimax(newBoard, aiPlayer);
        move.score = result2.score;
      }

      newBoard[availSpots[j]] = move.index;
      moves.push(move);
    }

    var bestMove;
    if(player === aiPlayer){
      var bestScore1 = -100000;
      for(var k = 0; k < moves.length; k++){
        if(moves[k].score > bestScore1){
          bestScore1 = moves[k].score;
          bestMove = k;
        }
      }
    }
    else{
      var bestScore2 = 100000;
      for(var l = 0; l < moves.length; l++){
        if(moves[l].score < bestScore2){
          bestScore2 = moves[l].score;
          bestMove = l;
        }
      }
    }

    return moves[bestMove];
  }// minimax function.


};//TicTacToe object literal.

function move(e){
  if(e.target.className === "cell" && e.target.textContent === ""){
    e.target.textContent = humanPlayer;
    var result = TicTacToe.minimax(domBoardToArray(cells), aiPlayer);
    cells[result.index].textContent = aiPlayer;
  }
}

function domBoardToArray(cells){
  return Array.prototype.slice.call(cells).map(function (cell){
    return cell.textContent
  })
}


document.querySelector('.cells').addEventListener('click', move);
.*{
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}
.cells  {
  width: 390px;
  height: 390px;
  margin: 50px auto;
}

.cell {
  width:128px;
  height: 128px;
  border: 1px solid #dedede;
  float: left;
  text-align: center;
  line-height: 128px;
  font-size: 80px;
  font-weight: bold;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>

  <div class="cells">
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
  </div>
</body>
</html>
...