Javascript中的TicTacToe minimax AI

时间:2016-05-09 19:10:43

标签: javascript recursion tic-tac-toe

我在实施这个I found here的TicTacToe AI时遇到了麻烦。我对javascript相对较新,所以我确定我在变量范围内做错了。

代码不会在代码段中运行,而是heres my codepen



choices = {
  0: '#ul',
  1: '#um',
  2: '#ur',
  3: '#ml',
  4: '#mm',
  5: '#mr',
  6: '#ll',
  7: '#lm',
  8: '#lr'
}

function getGrid() {
  var divs = []
  for (var i = 0; i < 9; i++) {
    divs.push($(choices[i]).html())
  }
  return divs
}

function getGame() {
  var divs = []
  for (var i = 0; i < 9; i++) {
    divs.push([$(choices[i]).html(), i])
  }
  return divs
}

function convertGameToGrid(game) {
  var divs = []
  for (var i = 0; i < game.length; i++) {
    divs.push(game[i][0])
  }
  return divs
}

function checkGrid(divs) {
  var options = [
    [divs[0], divs[1], divs[2]],
    [divs[3], divs[4], divs[5]],
    [divs[6], divs[7], divs[8]],
    [divs[0], divs[3], divs[6]],
    [divs[1], divs[4], divs[7]],
    [divs[2], divs[5], divs[8]],
    [divs[0], divs[4], divs[8]],
    [divs[2], divs[4], divs[6]]
  ]

  for (var i = 0; i < options.length; i++) {
    if (options[i][0] == 'X' && options[i][1] == 'X' && options[i][2] == 'X') {
      return 'X'
    } else if (options[i][0] == 'O' && options[i][1] == 'O' && options[i][2] == 'O') {
      return 'O'
    }
  }
  for (var i = 0; i < 9; i++) {
    if (divs[i] == '') {
      return false //still moves
    }
  }
  return 'Tie' //no winner and no moves
}

var player = 'O'
var ai = 'X'

$(document).ready(function() {

  function playerTurn(i) {
    return function() {
      var g = getGrid()
      var cG = checkGrid(g)
      if (!cG) {
        if ($(choices[i]).html() == '') {
          $(choices[i]).html(player)
          var g = getGrid()
          var cG = checkGrid(g)
          if (cG == player) {
            console.log('You win')
          } else if (cG == 'Tie') {
            console.log('Tie')
          } else {
            aiTurn()
          }
        }
      }
    }
  }

  for (var i = 0; i < 9; i++) {
    $(choices[i]).on('click', playerTurn(i));
  }

  function score(g, depth) {
    var cG = checkGrid(g)
    console.log(cG, g)
    if (cG == ai) {
      return 10 - depth
    } else if (cG == player) {
      return depth - 10
    } else {
      return 0
    }
  }

  function minimax(game, depth) {
    var g = convertGameToGrid(game)
    if (checkGrid(g)) {
      return score(g, depth)
    }
    depth += 1
    var scores = []
    var moves = []

    var availMoves = getAvailMoves(game)
    console.log('moves', availMoves)
    for (var i = 0; i < availMoves.length; i++) {
      var possibleGame = game
      if (depth % 2 == 0) {
        possibleGame[availMoves[i]][0] = ai
      } else {
        possibleGame[availMoves[i]][0] = player
      }
      var m = minimax(possibleGame, depth)
      scores.push(m)
      console.log('mm: ', depth, i, scores)
      moves.push(availMoves[i])
    }

    //even depths are ai, odd are player
    if (depth % 2 == 0) {
      var max_score_index = 0
      var max_score = -100000000
      for (var i = 0; i < scores.length; i++) {
        if (scores[i] > max_score) {
          max_score_index = i
          max_score = scores[i]
        }
      }
      if (depth == 0) { //we need the best move
        return moves[max_score_index]
      } else { //otherwise this function needs scores
        return scores[max_score_index]
      }
    } else {
      var min_score_index = 0
      var min_score = 100000000
      for (var i = 0; i < scores.length; i++) {
        if (scores[i] < min_score) {
          min_score_index = i
          min_score = scores[i]
        }
      }
      return scores[max_score_index]
    }
  }

  function getAvailMoves(game) {
    var moves = []
    for (var i = 0; i < game.length; i++) {
      if (game[i][0] == '') {
        moves.push(game[i][1])
      }
    }
    return moves
  }

  function aiTurn() {
    //Dumb ai
    // c = Math.floor(Math.random()*9)
    // while ($(choices[c]).html()) {
    //   c = Math.floor(Math.random()*9)
    // }

    //new strategy taken from http://neverstopbuilding.com/minimax
    console.log('ai')
    var c;
    game = getGame()
    c = minimax(game, -1)

    $(choices[c]).html('X')
    var g = getGrid()
    var cG = checkGrid(g)
    if (cG == ai) {
      console.log('You lose')
    } else if (cG == 'Tie') {
      console.log('Tie')
    }
  }


})
&#13;
#ttt-box {
  position: relative;
  height: 304px;
  width: 304px;
  margin: 30px auto;
  background-color: #bbb;
  border: solid #000 4px;
  border-radius: 20%;
}
#l1,
#l2,
#l3,
#l4 {
  position: absolute;
  background-color: #000;
}
#l1 {
  left: 99px;
  width: 3px;
  height: 296px;
}
#l2 {
  left: 199px;
  width: 3px;
  height: 296px;
}
#l3 {
  top: 99px;
  width: 296px;
  height: 3px;
}
#l4 {
  top: 199px;
  width: 296px;
  height: 3px;
}
#ul,
#um,
#ur,
#ml,
#mm,
#mr,
#ll,
#lm,
#lr {
  cursor: pointer;
  position: absolute;
  width: 99px;
  height: 99px;
  font-size: 70px;
  text-align: center;
}
#ul {
  top: 0;
  left: 0;
}
#um {
  top: 0;
  left: 101px;
}
#ur {
  top: 0;
  left: 201px;
}
#ml {
  top: 101px;
  left: 0;
}
#mm {
  top: 101px;
  left: 101px;
}
#mr {
  top: 101px;
  left: 201px;
}
#ll {
  top: 201px;
  left: 0;
}
#lm {
  top: 201px;
  left: 101px;
}
#lr {
  top: 201px;
  left: 201px;
}
&#13;
<body>
  <div class="container">
    <div id="content">
      <div id="ttt-box">
        <div id="l1"></div>
        <div id="l2"></div>
        <div id="l3"></div>
        <div id="l4"></div>

        <div id="boxes">
          <div id="ul"></div>
          <div id="um"></div>
          <div id="ur"></div>
          <div id="ml"></div>
          <div id="mm"></div>
          <div id="mr"></div>
          <div id="ll"></div>
          <div id="lm"></div>
          <div id="lr"></div>
        </div>

      </div>
    </div>
  </div>
</body>
&#13;
&#13;
&#13;

我认为特别突破的代码是以下部分。在第一个玩家移动之后,我认为console.log应打印出8个!时间因为应该采取的所有不同的路径,而是它只打印8次,就像它沿着单一路径一样。

var availMoves = getAvailMoves(game) console.log('moves',availMoves) for (var i=0;i<availMoves.length;i++) { var possibleGame = game if (depth%2==0) { possibleGame[availMoves[i]][0] = ai } else { possibleGame[availMoves[i]][0] = player } var m = minimax(possibleGame,depth) scores.push(m) console.log('mm: ', depth,i, scores) moves.push(availMoves[i]) }

编辑:我注意到的是,有时minimax递归返回未定义。我已经尝试找到原因(参见我的codepen),但我没有成功。

Edit2:它似乎返回undefined,因为它完全跳过这些递归。我仍然无法找到解决这个问题的方法。

1 个答案:

答案 0 :(得分:2)

首先提出建议,因为您刚刚开始:学习如何使用调试器。在像这样的情况下它将是非常宝贵的,并且大多数现代浏览器都内置它们。

关于您的问题,我还没有查看您的所有代码,但我注意到有一件事可能导致您的minmax功能出现问题。在该功能的底部,你有这个代码:

//even depths are ai, odd are player
if (depth % 2 == 0) {
    var max_score_index = 0
    // snip...
} else {
    var min_score_index = 0
    // snip...
    return scores[max_score_index]
}

请注意,您在max_score_index块中声明并分配if,但也在else块中使用它(不指定它)。这将导致它从else块返回undefined。