【问题标题】:Minimax in Tic-Tac-Toe not returning correct valueTic-Tac-Toe 中的 Minimax 没有返回正确的值
【发布时间】:2017-08-30 14:01:07
【问题描述】:

我正在尝试编写井字游戏并决定使用 MiniMax 算法,但我在实现它时遇到了麻烦。例如: 在一个

  board = [
          "E", "E", "X",
          "E", "E", "X",
          "E", "O", "O"
          ];

轮到 AI 了,函数返回 AiMove { score: -10, coordinates: 0 } 作为最佳移动。我已经尝试调试很长时间了,但是函数的递归性质和可能的游戏树的数量,特别是早期游戏状态,很难理解和调试。

有人可以帮忙吗?

https://jsfiddle.net/LdLqk1z8/4/

var factions = {
  AIplayer: "X",
  humanPlayer: "O"
};

var gameResults = {
  winner: ""
};

var emptyCells = function(board) { //check for empty cells and return an array with the number of empty cells
  var indices = [];
  for (var itr = 0; itr < 9; itr++) {
    if (board[itr] === "E") {
      indices.push(itr);
    }
  }
  return indices;
};

var isGameOver = function(board) {
  var tile = board;

  //check for victory conditions
  for (var i = 0; i <= 6; i = i + 3) {
    if (tile[i] !== "E" && tile[i] === tile[i + 1] && tile[i + 1] === tile[i + 2]) {
      if (factions.AIplayer === tile[i]) {
        gameResults.winner = "AIplayer";
      } else if (tile[i] === factions.humanPlayer) {
        gameResults.winner = "humanPlayer";
      }
      return true;
    }
  }

  for (var i = 0; i <= 2; i++) {
    if (tile[i] !== "E" && tile[i] === tile[i + 3] && tile[i + 3] === tile[i + 6]) {
      if (factions.AIplayer === tile[i]) {
        gameResults.winner = "AIplayer";
      } else if (tile[i] === factions.humanPlayer) {
        gameResults.winner = "humanPlayer";
      }
      return true;
    }
  }

  for (var i = 0, j = 4; i <= 2; i = i + 2, j = j - 2) {
    if (tile[i] !== "E" && tile[i] === tile[i + j] && tile[i + j] === tile[i + 2 * j]) {
      if (factions.AIplayer === tile[i]) {
        gameResults.winner = "AIplayer";
      } else if (tile[i] === factions.humanPlayer) {
        gameResults.winner = "humanPlayer";
      }
      return true;
    }
  }

  var check = emptyCells(board); //check if the game ended with a draw
  if (check.length === 0) {
    gameResults.winner = "draw";
    return true;
  } else {
    return false; //if no condition is matched the game has not concluded
  }
};

var getBestMove = function(board, player) {

  // return an AiMove object initialized to 10 if the AI player wins, -10 if the human player wins and 0 if the game is a draw
  if (isGameOver(board)) {
    if (gameResults.winner === "AIplayer") {
      return new AiMove(10);
    } else if (gameResults.winner === "humanPlayer") {
      return new AiMove(-10);
    } else if (gameResults.winner === "draw") {
      return new AiMove(0);
    }
  }

  var moves = []; //array to store all moves
  var currentPlayer = player;
  for (var i = 0, l = board.length; i < l; i++) { //iterate over the board
    if (board[i] == "E") { //if the tile is empty
      var move = new AiMove; //create new AiMove object and assign a coordinate
      move.coordinates = i;
      board[i] = currentPlayer; //update board
      //call getBestMove recursively with the next player
      if (currentPlayer === factions.AIplayer) {
        move.score = getBestMove(board, factions.humanPlayer).score;
      } else if (currentPlayer === factions.humanPlayer) {
        move.score = getBestMove(board, factions.AIplayer).score;
      }
      moves.push(move);

      board[i] = "E"; //clear tile after move is pushed in to the moves array
    }
  }

  //if it's the AI player's turn select biggest value from the moves array, if it's the human player's turn select the smallest value
  if (currentPlayer === factions.AIplayer) {
    var bestMove = 0;
    var bestScore = -10000;
    for (var i = 0; i < moves.length; i++) {
      if (moves[i].score > bestScore) {
        bestScore = moves[i].score;
        bestMove = i;
      }
    }
  } else if (currentPlayer === factions.humanPlayer) {
    var bestMove = 0;
    var bestScore = 10000;
    for (var i = 0; i < moves.length; i++) {
      if (moves[i].score < bestScore) {
        bestMove = i;
        bestScore = moves[i].score;
      }
    }
  }
  return moves[bestMove]; //return best move
};

var board = [
              "E", "E", "X",
              "E", "E", "X",
              "E", "O", "O"
              ];

function AiMove(score) {
  this.coordinates,
    this.score = score;
}

console.log(getBestMove(board, factions.AIplayer))

编辑: 是不是因为棋盘的设置是不可取的,而人工智能是宿命论的,它“放弃”了?实施“深度”的概念会解决这个问题吗?

【问题讨论】:

  • 好吧,isGameOver 认为这是 人的游戏 - 而且,除非 O 是一只会扔便便的黑猩猩,X 不会赢,O 必须赢
  • 能否提供更多上下文, score: -10 是什么意思?
  • @JaromandaX 是的,看起来人工智能是宿命论的,并假设玩家不会扔掉所说的便便并完美地玩,这导致游戏无法获胜。因此认输。我假设实施深度 - 每次递归调用都会增加负分,从而迫使 AI 阻止对手的移动,即使它最终导致它的灭亡 - 应该解决这个问题吗?
  • @BarneyChambers 代码在我提供的 jsfiddle 链接中被注释掉/解释。这只是一个磨机极小极大实现的运行:迫使 AI 选择最大值,而对手试图选择最小值。

标签: javascript algorithm recursion tic-tac-toe minimax


【解决方案1】:

在等式中引入depth的想法确实是正确的。

moves.push(move) 之前添加以下行:

  move.score = Math.sign(move.score) * (Math.abs(move.score) - 1);

这将使分数降低 1 分(-10 变为 -9,10 变为 9,......等等)。它表达了这样一种思想,即输的越远,越不严重,赢的越近越好。

在您提供的示例棋盘设置中,这将返回 6 作为最佳棋步,从而避免了人类玩家立即获胜。当然,AI 仍然会输给最好的游戏,因为游戏会这样继续:

. . X     . . X     . . X     X . X     X O X
. . X  →  . . X  →  . O X  →  . O X  →  . O X
. O O     X O O     X O O     X O O     X O O

为了更好的调试可能性,我会编写一个将板打印到控制台的函数。例如:

function printBoard(board) {
    console.log(board.slice(0, 3).join (' ').replace(/E/g, '.'));
    console.log(board.slice(3, 6).join (' ').replace(/E/g, '.'));
    console.log(board.slice(6, 9).join (' ').replace(/E/g, '.'));
    console.log('');
}

您还可以考虑使代码更加面向对象,在 Game 对象上编写方法,...等等。

【讨论】:

  • 感谢您的建议。这是实现深度的一种非常优雅的方式。实际上,我已经编写了一个简单的 GUI 和游戏逻辑。我在 jsfiddle 中重写了 AI 代码,以隔离其他不相关的问题。以下是撰写本文时的完整代码:jsfiddle.net/LdLqk1z8/4
  • 将所有代码从humanPlayerTurn移动到generateBoard。请参阅 fiddle.jshell.net/w1cyxwhr/3 了解该更新和其他一些次要更新。确实,您应该通过“”按钮提出新问题:P
  • 我在您回答的同时发现了问题。 AiMove() 没有打电话给 humanPlayerTurn()。因此,在人工智能先行的游戏中,游戏变得冰冷。但是您的解决方案更有意义,没有理由在每次 AI 转弯后继续调用 humanPlayerTurn(),并多次附加事件(即使我在游戏重置时确实有 .off() 功能 - 清除所有点击事件) .无论如何,感谢所有的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-16
相关资源
最近更新 更多