0

我编写了一个基于网络的井字游戏,并为 AI 实现了 Minimax 算法来选择它的动作。一切似乎都正常,但是在人类玩家点击第一个方块和玩家的标记(X 或 O)出现之间有很长的延迟。minimax即使我在updateBoard第一次调用minimax.

这似乎是异步操作的问题,这意味着它没有时间在被递归调用阻塞之前更新 HTML minimax。我尝试向函数添加一个回调参数并在完成时updateBoard执行,但我没有发现任何区别。minimaxupdateBoard

我不确定这个问题实际上与我的 Minimax 实现有关。这似乎工作正常......电脑永远不会丢失。我认为这可能是我对 Javascript 中的同步操作的误解。

有人可以看一下,特别是在处理程序中$squares.click()并告诉我您是否发现出了什么问题?

您可以在 Codepen 上查看它,我在下面复制了它:http ://codepen.io/VAggrippino/pen/ZBqLxO

更新,几秒钟后:

这很奇怪。当我在 SO 上“运行代码片段”时,它不会出现同样的问题。我是否偶然发现了 CodePen 的问题?

$(function(){
  var humanToken = 'X';
  var computerToken = 'O';
  var gameFinished = false;
  var $squares = $(".square");
  
  /*
  $squares.each(function() {
    var id = $(this).attr("id");
    $(this).html("<span style='font-size: 1rem;'>" + id + "</style>");
  });
  */
  
  var board = [
    [null,null,null],
    [null,null,null],
    [null,null,null],
  ];
  
  // Give the human player a choice between "X" and "O".
  var chooseToken = function(e) {
    var $button = $(e.target);
    humanToken = $button.html();
    computerToken = (humanToken === 'X' ? 'O' : 'X');
    $(this).dialog("close");
    gameFinished = false;
  };
  
  function reset(){
    $squares.html("");
    
    board = [
      [null,null,null],
      [null,null,null],
      [null,null,null],
    ];
    
    $("#tokenDialog").dialog("open");
  }
  
  function checkWinner(board) {
    var allFull = true;
    
    var tokens = [humanToken, computerToken];
    for (var t in tokens) {
      var diagonalWin1 = true;
      var diagonalWin2 = true;
      var token = tokens[t];
      
      /* Since the squares are associated with a two-
         dimensional array, these coordinates shouldn't be
         thought of as x/y coordinates like a grid.
      */
      for (var i = 0; i < 3; i++) {
        // Checking 0,0; 1,1; 2,2... top left to bottom right
        if (board[i][i] !== token) {
          diagonalWin1 = false;
        }

        // Checking 2,0; 1,1; 0,2... bottom left to top right
        if (board[2-i][i] !== token) {
          diagonalWin2 = false;
        }

        var verticalWin = true;
        var horizontalWin = true;
        for (var j = 0; j < 3; j++) {
          /* Checking:
               0,0; 0,1; 0,2... horizontal top
               1,0; 1,1; 1,2... horizontal middle
               2,0; 2,1; 2,2... horizontal bottom
          */
          if (board[i][j] !== token) {
            horizontalWin = false;
          }
          
          /* Checking:
               0,0; 1,0; 2,0... vertical left
               0,1; 1,1; 2,1... vertical middle
               0,2; 1,2; 2,2... vertical right
          */
          if (board[j][i] !== token) {
            verticalWin = false;
          }
          
          // If there are any empty squares, set allFull to
          // false, indicating a tie.
          if (board[i][j] === null) {
            allFull = false;
          }

        }
        
        if (horizontalWin || verticalWin) {
          return token;
        }
        
      }
      if (diagonalWin1 || diagonalWin2) {
        return token;
      }
    }
    
    // If all the squares are full and we didn't find a
    // winner, it's a tie. Return -1.
    if (allFull) return -1;
    
    // No winner yet.
    return null;
  }
  
  function updateBoard() {
    // The layout of the board represents the layout of a
    // Javascript array. So, these should thought of as row
    // and column instead of x/y coordinates of a grid.
    for (var r = 0; r < 3; r++) {
      for (var c = 0; c < 3; c++) {
        var squareId = "#s" + r + '' + c;
        $(squareId).html(board[r][c]);
      }
    }
    
    var result = "";
    var winner = checkWinner(board);
    
    if (winner !== null) {
      if (winner === humanToken) {
        result = "You Win!";
      } else if (winner === computerToken) {
        result = "The Computer Wins!";
      } else if (winner === -1) {
        result = "It's a tie!";
      }

      var $resultDialog = $("#resultDialog");
      $resultDialog.dialog("option", "title", result);
      $resultDialog.dialog("open");
    }
  }
  
  $("#reset").click(reset);
  
  $squares.click(function(){
    // If the game is already finished, we're just looking at
    // the results, so don't do anything.
    if (gameFinished) return false;
    
    var $square = $(this);
    var boardPosition = $square.attr("id").match(/(\d)(\d)/);
    var row = boardPosition[1];
    var col = boardPosition[2];
    var currentValue = board[row][col];
    
    // If there's not already a token in the clicked square,
    // place one and check for a winner.
    if (currentValue === null) {
      board[row][col] = humanToken;
      updateBoard();
      board = minimax(board, computerToken)[1];
      updateBoard();
    }
  });
  
  function minimax(board, token) {
    // Check the current layout of the board for a winner.
    var winner = checkWinner(board);
    
    // The computer wins.
    if (winner === computerToken) {
      return [10, board];
    // The human wins.
    } else if (winner === humanToken) {
      return [-10, board];
    // It's a tie
    } else if (winner === -1) {
      return [0, board];
    // There's no winner yet
    } else if (winner === null) {
      var nextScore = null;
      var nextBoard = null;
      
      // Add a token to the board and check it with a
      // recursive call to minimax.
      for (var r = 0; r < 3; r++) {
        for (var c = 0; c < 3; c++) {
          if (board[r][c] === null) {
            
            // Play the current players token, then call
            // minimax for the other player.
            board[r][c] = token;
            var score;
            if (token === humanToken) {
              score = minimax(board, computerToken)[0];
            } else {
              score = minimax(board, humanToken)[0];
            }
            
            /* This is the computer player trying to win.
               If the current player is the computer and the
               score is positive or the current player is
               human and the score is negative assign a copy
               of the board layout as the next board.
            */
            if ((token === computerToken && (nextScore === null || score > nextScore)) ||
                (token === humanToken && (nextScore === null || score < nextScore)) )
            {
              nextBoard = board.map(function(arr) {
                return arr.slice();
              });
              nextScore = score;
            }
            board[r][c] = null;
          }
        }
      }
      return [nextScore, nextBoard];
    } else {
      console.log("Something bad happened.");
      console.log("winner: ");
      console.log(winner);
    }
  }
  
  $("#tokenDialog").dialog({
    "title": "Choose Your Weapon",
    "position": {
      "my": "center",
      "at": "center",
      "of": "#board"
    },
    "autoOpen": false,
    "buttons": {
      "X": chooseToken,
      "O": chooseToken,
    },
    "closeOnEscape": false,
    "draggable": false,
    "modal": true,
    "resizable": false,
    "show": true,
    "classes": {
      "ui-dialog-buttonpane": "tokenDialog-buttonpane",
    },
  });
  
  $("#resultDialog").dialog({
    "position": {
      "my": "center",
      "at": "center",
      "of": "#board"
    },
    "autoOpen": false,
    "closeOnEscape": false,
    "draggable": false,
    "modal": true,
    "resizable": false,
    "show": true,
    "buttons": {
      "Play Again": function(){
        $(this).dialog("close");
        reset();
      }
    },
    "classes": {
    }
  });
});
body, html {
  height: 100%;
  margin: 0;
}

body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: linear-gradient(45deg, #dca 12%, transparent 0, transparent 88%, #dca 0),
    linear-gradient(135deg, transparent 37%, #a85 0, #a85 63%, transparent 0),
    linear-gradient(45deg, transparent 37%, #dca 0, #dca 63%, transparent 0) #753;
  background-size: 25px 25px;
}

div#board {
  background-color: white;
  background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/29229/gimp_pattern_marble1.png);
  background-repeat: repeat;
  width: 21rem;
  padding: 0.25rem;
  margin: 1rem;
  border: 10px solid transparent;
  border-radius: 2rem;
  position: relative;
  background-clip: content-box;
  box-shadow: 0px 5px 10px 3px rgba(0, 0, 0, 0.75);
}

div#board::after {
  content: "";
  z-index: -1;
  position: absolute;
  top: -10px;
  right: -10px;
  bottom: -10px;
  left: -10px;
  border-radius: 2rem;
  background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/29229/gimp_pattern_leather.png);
}

#reset {
  font-size: 2em;
  border-radius: 0.5rem;
}

div.square {
  width: 7rem;
  height: 7rem;
  font-size: 7rem;
  font-family: sans-serif;
  float: left;
  border-width: 0 2px 2px 0;
  border-color: black;
  border-style: solid;
  box-sizing: border-box;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
}

#s02, #s12, #s22 {
  border-right: none;
}

#s20, #s21, #s22 {
  border-bottom: none;
}

.tokenDialog-buttonset {
  width: 100%;
  display: flex;
  justify-content: center;
}

.ui-dialog-buttonpane.tokenDialog-buttonpane {
  padding: 0.5em;
}

.no-close .ui-dialog-titlebar-close {
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/ui-darkness/jquery-ui.css">

<!--
The board configuration represents the layout of a Javascript
array, not a typical grid. so, the square IDs should not be
thought of as x/y coordinates.
-->
<div id="board">
  <div class="row">
    <div class="square" id="s00"></div>
    <div class="square" id="s01"></div>
    <div class="square" id="s02"></div>
  </div>
  <div class="row">
    <div class="square" id="s10"></div>
    <div class="square" id="s11"></div>
    <div class="square" id="s12"></div>
  </div>
  <div class="row">
    <div class="square" id="s20"></div>
    <div class="square" id="s21"></div>
    <div class="square" id="s22"></div>
  </div>
</div>
<div id="buttons">
  <button id="reset">Reset Game</button>
</div>

<div id="tokenDialog"></div>
<div id="resultDialog"></div>

4

0 回答 0