[algorithm] What algorithm for a tic-tac-toe game can I use to determine the "best move" for the AI?

A Tic-tac-toe adaptation to the min max algorithem

let gameBoard: [
    [null, null, null],
    [null, null, null],
    [null, null, null]
]

const SYMBOLS = {
    X:'X',
    O:'O'
}

const RESULT = {
    INCOMPLETE: "incomplete",
    PLAYER_X_WON: SYMBOLS.x,
    PLAYER_O_WON: SYMBOLS.o,
    tie: "tie"
}

We'll need a function that can check for the result. The function will check for a succession of chars. What ever the state of the board is, the result is one of 4 options: either Incomplete, player X won, Player O won or a tie.

_x000D_
_x000D_
function checkSuccession (line){_x000D_
    if (line === SYMBOLS.X.repeat(3)) return SYMBOLS.X_x000D_
    if (line === SYMBOLS.O.repeat(3)) return SYMBOLS.O_x000D_
    return false _x000D_
}_x000D_
_x000D_
function getResult(board){_x000D_
_x000D_
    let result = RESULT.incomplete_x000D_
    if (moveCount(board)<5){_x000D_
        return result_x000D_
    }_x000D_
_x000D_
    let lines_x000D_
_x000D_
    //first we check row, then column, then diagonal_x000D_
    for (var i = 0 ; i<3 ; i++){_x000D_
        lines.push(board[i].join(''))_x000D_
    }_x000D_
_x000D_
    for (var j=0 ; j<3; j++){_x000D_
        const column = [board[0][j],board[1][j],board[2][j]]_x000D_
        lines.push(column.join(''))_x000D_
    }_x000D_
_x000D_
    const diag1 = [board[0][0],board[1][1],board[2][2]]_x000D_
    lines.push(diag1.join(''))_x000D_
    const diag2 = [board[0][2],board[1][1],board[2][0]]_x000D_
    lines.push(diag2.join(''))_x000D_
    _x000D_
    for (i=0 ; i<lines.length ; i++){_x000D_
        const succession = checkSuccesion(lines[i])_x000D_
        if(succession){_x000D_
            return succession_x000D_
        }_x000D_
    }_x000D_
_x000D_
    //Check for tie_x000D_
    if (moveCount(board)==9){_x000D_
        return RESULT.tie_x000D_
    }_x000D_
_x000D_
    return result_x000D_
}
_x000D_
_x000D_
_x000D_

Our getBestMove function will receive the state of the board, and the symbol of the player for which we want to determine the best possible move. Our function will check all possible moves with the getResult function. If it is a win it will give it a score of 1. if it's a loose it will get a score of -1, a tie will get a score of 0. If it is undetermined we will call the getBestMove function with the new state of the board and the opposite symbol. Since the next move is of the oponent, his victory is the lose of the current player, and the score will be negated. At the end possible move receives a score of either 1,0 or -1, we can sort the moves, and return the move with the highest score.

_x000D_
_x000D_
const copyBoard = (board) => board.map( _x000D_
    row => row.map( square => square  ) _x000D_
)_x000D_
_x000D_
function getAvailableMoves (board) {_x000D_
  let availableMoves = []_x000D_
  for (let row = 0 ; row<3 ; row++){_x000D_
    for (let column = 0 ; column<3 ; column++){_x000D_
      if (board[row][column]===null){_x000D_
        availableMoves.push({row, column})_x000D_
      }_x000D_
    }_x000D_
  }_x000D_
  return availableMoves_x000D_
}_x000D_
_x000D_
function applyMove(board,move, symbol) {_x000D_
  board[move.row][move.column]= symbol_x000D_
  return board_x000D_
}_x000D_
 _x000D_
function getBestMove (board, symbol){_x000D_
_x000D_
    let availableMoves = getAvailableMoves(board)_x000D_
_x000D_
    let availableMovesAndScores = []_x000D_
_x000D_
    for (var i=0 ; i<availableMoves.length ; i++){_x000D_
      let move = availableMoves[i]_x000D_
      let newBoard = copyBoard(board)_x000D_
      newBoard = applyMove(newBoard,move, symbol)_x000D_
      result = getResult(newBoard,symbol).result_x000D_
      let score_x000D_
      if (result == RESULT.tie) {score = 0}_x000D_
      else if (result == symbol) {_x000D_
        score = 1_x000D_
      }_x000D_
      else {_x000D_
        let otherSymbol = (symbol==SYMBOLS.x)? SYMBOLS.o : SYMBOLS.x_x000D_
        nextMove = getBestMove(newBoard, otherSymbol)_x000D_
        score = - (nextMove.score)_x000D_
      }_x000D_
      if(score === 1)  // Performance optimization_x000D_
        return {move, score}_x000D_
      availableMovesAndScores.push({move, score})_x000D_
    }_x000D_
_x000D_
    availableMovesAndScores.sort((moveA, moveB )=>{_x000D_
        return moveB.score - moveA.score_x000D_
      })_x000D_
    return availableMovesAndScores[0]_x000D_
  }
_x000D_
_x000D_
_x000D_

Algorithm in action, Github, Explaining the process in more details