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.
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_
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.
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_
Algorithm in action, Github, Explaining the process in more details