Search code examples
javascriptalgorithmangulartic-tac-toeminimax

Tic tac toe with minimax algorithm


I am confused with minimax algorithm. I have spent 2 days and still can't find mistake. Could you glance at my code to help me to find any bugs?

export default class TicTacToeController {
  /*@ngInject*/
  constructor($scope) {
    this.board = new Array(3);

    this.players = {
      ai: "x",
      player: "o"
    };

    this.move = {
      row: "",
      cell: ""
    };

    this.currentPlayer = this.players.ai;
    this.humanMove = false;

    this.fillBoard();
    this.board[0][0] = "x";
    this.board[0][1] = "o";
    this.board[0][2] = "x";
    this.board[1][0] = "x";
    this.board[1][1] = "x";
    this.board[1][2] = "o";
    this.board[2][0] = "o";

    $scope.$watch("ticTac.currentPlayer", player => {
      if(player === this.players.player) {
        this.humanMove = true;
      } else {
        this.aiMove(this.board);
      }
    })

  }

  fillBoard() {
    for (var i = 0; i < 3; i++) {
      this.board[i] = new Array(3);

      for (var j = 0; j < 3; j++) {
        this.board[i][j] = " ";
      }
    }
  }

  evaluate(board) {
    //check rows
    for (var row = 0; row < 3; row++) {
      if (board[row][0] === board[row][1] && board[row][1] === board[row][2]) {
        if (board[row][0] === this.players.ai) {
          return 10;
        } else if (board[row][0] === this.players.player) {
          return -10;
        }
      }
    }

    //check columns
    for (var col = 0; col < 3; col++) {
      if (board[0][col] === board[1][col] && board[1][col] === board[2][col]) {
        if (board[0][col] === this.players.ai) {
          return 10;
        } else if (board[0][col] === this.players.player) {
          return -10;
        }
      }
    }

    //check for diagonals
    if (board[0][0] === board[1][1] && board[1][1] === board[2][2]) {
      if (board[0][0] === this.players.ai) {
        return 10;
      } else if (board[0][0] === this.players.player) {
        return -10;
      }
    }

    if (board[0][2] === board[1][1] && board[1][1] === board[2][0]) {
      if (board[0][2] === this.players.ai) {
        return 10;
      } else if (board[0][2] === this.players.player) {
        return -10;
      }
    }

    //if no player won return 0
    return 0;
  }

  minimax(board, isMax) {
    var score = this.evaluate(board);

    if (score === 10 || score === -10) {
      return score;
    }

    if(!this.isMoveLeft(board)) {
      return 0;
    }

    if (isMax) {

      var best = -1000;

      for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) {

          if (board[i][j] === " ") {
            board[i][j] = this.players.ai;

            best = Math.max(best, this.minimax(board, !isMax));

            board[i][j] = " ";
          }
        }
      }
      return best;

    } else {

      var best = 1000;

      for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) {

          if (board[i][j] === " ") {
            board[i][j] = this.players.player;

            best = Math.min(best, this.minimax(board, !isMax));

            board[i][j] = " ";
          }
        }
      }

      return best;
    }
  }

  isMoveLeft(board) {
    for (var i = 0; i < 3; i++) {
      for (var j = 0; j < 3; j++) {
        if (board[i][j] === " ")
          return true;
      }
    }
    return false;
  }

  makeBestMove(board) {
    var bestVal = -1000;

    this.move.row = -1;
    this.move.cell = -1;

    for(var i = 0; i < 3; i++) {
      for (var j = 0; j < 3; j++) {
        if(board[i][j] === " ") {
          board[i][j] === this.currentPlayer;

          var moveVal = this.minimax(board, false);

          board[i][j] === " ";

          if(moveVal > bestVal) {
            this.move.row = i;
            this.move.cell = j;
            bestVal = moveVal;
          }
        }
      }
    }

    return this.move;
  }

  aiMove(board) {
    var currentMove;

    if(!this.isMoveLeft(this.board)) {

      return;
    }

    currentMove = this.makeBestMove(board);
    board[currentMove.row][currentMove.cell] = this.currentPlayer;
    this.currentPlayer = this.players.player;
  }

  makeMove(row, cell) {

    if(this.board[row][cell] === " ") {
      this.board[row][cell] = this.players.player;
    }
    this.currentPlayer = this.players.ai;
  }
}

export default TicTacToeController;

Start board looks that:

star board

As you can see, the next optimal move should be (2,2), but minimax algorithm put value to (2,0).

enter image description here

I tried to debug, but I still cant find a mistake.


Solution

  • In your makeBestMove method, these lines:

    board[i][j] === this.currentPlayer;
    ...
    board[i][j] === " ";
    

    should be using an assignment equals:

    board[i][j] = this.currentPlayer;
    ...
    board[i][j] = " ";
    

    === is a comparison operator. So you are comparing the values and getting true without changing anything.

    Currently, as you are not making an assignment prior to calling minimax the same best result is returned each time.