I have been struggling with creating the unbeatable AI for the Tic Tac Toe project.
I have been googling & using The Coding Train tutorial - however, the AI appears to still be selecting a random square. Although there must be logic behind which square it is choosing, I can't work out what the logic is!
Here is my codepen - https://codepen.io/kath-ldn/pen/oNLZqrp , and I have copied the relevant bits of code below.
If anyone could take a look at this I would be so grateful. I have been stuck for quite a few days now and can’t work out what the issue is.
//functions to assess winning combos
function equals3(a, b, c) {
return a === b && b === c && a !== "";
}
function checkWinner(){
let winner = null;
if (equals3(gameBoard[0], gameBoard[1], gameBoard[2])) {
winner = gameBoard[0];
};
if (equals3(gameBoard[3], gameBoard[4], gameBoard[5])) {
winner = gameBoard[3];
};
if (equals3(gameBoard[6], gameBoard[7], gameBoard[8])) {
winner = gameBoard[6];
};
if (equals3(gameBoard[0], gameBoard[3], gameBoard[6])) {
winner = gameBoard[0];
};
if (equals3(gameBoard[1], gameBoard[4], gameBoard[7])) {
winner = gameBoard[1];
};
if (equals3(gameBoard[2], gameBoard[5], gameBoard[8])) {
winner = gameBoard[0];
};
if (equals3(gameBoard[0], gameBoard[4], gameBoard[8])) {
winner = gameBoard[0];
};
if (equals3(gameBoard[2], gameBoard[4], gameBoard[6])) {
winner = gameBoard[2];
};
let openSpots = 0;
for (let i = 0; i < gameBoard.length; i++) {
if (gameBoard[i] === "") {
openSpots = openSpots + 1;
};
};
if (winner === null && openSpots === 0) {
return 'tie';
} else {
return winner;
};
};
let scores = {
X: 10,
O: -10,
tie: 0
};
//function to create impossible-to-beat AI using minimax algorithm
function bestMove() {
let bestScore = -Infinity;
let move;
for (let i = 0; i < gameBoard.length; i++) {
if (gameBoard[i] === "") {
gameBoard[i] = AI;
let score = minimax(gameBoard, 0, false);
gameBoard[i] = "";
if (score > bestScore) {
bestScore = score;
move = i;
}
}
}
gameBoard[move] = AI;
};
//minimax function
function minimax(gameBoard, depth, isMaximizing){
let result = checkWinner();
if (result !== null) {
return scores[result];
}
if (isMaximizing) {
let bestScore = -Infinity;
for (let i = 0; i < gameBoard.length; i++) {
if (gameBoard[i] === "") {
gameBoard[i] = AI;
let score = minimax(gameBoard, depth + 1, false);
gameBoard[i] = "";
bestScore = Math.max(score, bestScore);
}
}
return bestScore;
} else {
let bestScore = Infinity;
for (let i = 0; i < gameBoard.length; i++) {
if (gameBoard[i] === "") {
gameBoard[i] = human;
let score = minimax(gameBoard, depth + 1, true);
gameBoard[i] = "";
bestScore = Math.min(score, bestScore);
}
}
return bestScore;
}
};
You are looping through the board and calling minimax for each square, this is not necessary and will be super slow. You just need to call it once like this:
move, score = minimax(gameBoard, 8, true)
I am not sure if it is needed, but I would return a 0 from the checkWinner function instead of returning a 'tie' string. It seems hard to chose a max/min valie from e.g. a 1 and a 'te' otherwise.
Then in your minimax function you need to change a few things to actually return the best move it finds. Sorry for any programming language issues, I am used to Python. I hope you get what I mean anyway:
//minimax function
function minimax(gameBoard, depth, isMaximizing){
let result = checkWinner();
if (result !== null) {
return None, scores[result];
}
if (isMaximizing) {
let bestScore = -Infinity;
for (let i = 0; i < gameBoard.length; i++) {
if (gameBoard[i] === "") {
gameBoard[i] = AI;
let score = minimax(gameBoard, depth - 1, false)[1];
gameBoard[i] = "";
if score > bestScore:
bestScore = score
bestMove = gameBoard[i]
}
}
return bestMove, bestScore;
} else {
let bestScore = Infinity;
for (let i = 0; i < gameBoard.length; i++) {
if (gameBoard[i] === "") {
gameBoard[i] = human;
let score = minimax(gameBoard, depth - 1, true)[1];
gameBoard[i] = "";
if score < bestScore:
bestScore = score
bestMove = gameBoard[i]
}
}
return bestMove, bestScore;
}
};